// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/http/mock_gssapi_library_posix.h"

#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {

namespace test {

struct GssNameMockImpl {
  std::string name;
  gss_OID_desc name_type;
};

}  // namespace test

namespace {

// gss_OID helpers.
// NOTE: gss_OID's do not own the data they point to, which should be static.
void ClearOid(gss_OID dest) {
  if (!dest)
    return;
  dest->length = 0;
  dest->elements = NULL;
}

void SetOid(gss_OID dest, const void* src, size_t length) {
  if (!dest)
    return;
  ClearOid(dest);
  if (!src)
    return;
  dest->length = length;
  if (length)
    dest->elements = const_cast<void*>(src);
}

void CopyOid(gss_OID dest, const gss_OID_desc* src) {
  if (!dest)
    return;
  ClearOid(dest);
  if (!src)
    return;
  SetOid(dest, src->elements, src->length);
}

// gss_buffer_t helpers.
void ClearBuffer(gss_buffer_t dest) {
  if (!dest)
    return;
  dest->length = 0;
  delete [] reinterpret_cast<char*>(dest->value);
  dest->value = NULL;
}

void SetBuffer(gss_buffer_t dest, const void* src, size_t length) {
  if (!dest)
    return;
  ClearBuffer(dest);
  if (!src)
    return;
  dest->length = length;
  if (length) {
    dest->value = new char[length];
    memcpy(dest->value, src, length);
  }
}

void CopyBuffer(gss_buffer_t dest, const gss_buffer_t src) {
  if (!dest)
    return;
  ClearBuffer(dest);
  if (!src)
    return;
  SetBuffer(dest, src->value, src->length);
}

std::string BufferToString(const gss_buffer_t src) {
  std::string dest;
  if (!src)
    return dest;
  const char* string = reinterpret_cast<char*>(src->value);
  dest.assign(string, src->length);
  return dest;
}

void BufferFromString(const std::string& src, gss_buffer_t dest) {
  if (!dest)
    return;
  SetBuffer(dest, src.c_str(), src.length());
}

// gss_name_t helpers.
void ClearName(gss_name_t dest) {
  if (!dest)
    return;
  test::GssNameMockImpl* name = reinterpret_cast<test::GssNameMockImpl*>(dest);
  name->name.clear();
  ClearOid(&name->name_type);
}

void SetName(gss_name_t dest, const void* src, size_t length) {
  if (!dest)
    return;
  ClearName(dest);
  if (!src)
    return;
  test::GssNameMockImpl* name = reinterpret_cast<test::GssNameMockImpl*>(dest);
  name->name.assign(reinterpret_cast<const char*>(src), length);
}

std::string NameToString(const gss_name_t& src) {
  std::string dest;
  if (!src)
    return dest;
  test::GssNameMockImpl* string =
      reinterpret_cast<test::GssNameMockImpl*>(src);
  dest = string->name;
  return dest;
}

void NameFromString(const std::string& src, gss_name_t dest) {
  if (!dest)
    return;
  SetName(dest, src.c_str(), src.length());
}

}  // namespace

namespace test {

GssContextMockImpl::GssContextMockImpl()
  : lifetime_rec(0),
    ctx_flags(0),
    locally_initiated(0),
    open(0) {
  ClearOid(&mech_type);
}

GssContextMockImpl::GssContextMockImpl(const GssContextMockImpl& other)
  : src_name(other.src_name),
    targ_name(other.targ_name),
    lifetime_rec(other.lifetime_rec),
    ctx_flags(other.ctx_flags),
    locally_initiated(other.locally_initiated),
    open(other.open) {
  CopyOid(&mech_type, &other.mech_type);
}

GssContextMockImpl::GssContextMockImpl(const char* src_name_in,
                                       const char* targ_name_in,
                                       OM_uint32 lifetime_rec_in,
                                       const gss_OID_desc& mech_type_in,
                                       OM_uint32 ctx_flags_in,
                                       int locally_initiated_in,
                                       int open_in)
    : src_name(src_name_in ? src_name_in : ""),
      targ_name(targ_name_in ? targ_name_in : ""),
      lifetime_rec(lifetime_rec_in),
      ctx_flags(ctx_flags_in),
      locally_initiated(locally_initiated_in),
      open(open_in) {
  CopyOid(&mech_type, &mech_type_in);
}

GssContextMockImpl::~GssContextMockImpl() {
  ClearOid(&mech_type);
}

void GssContextMockImpl::Assign(
    const GssContextMockImpl& other) {
  if (&other == this)
    return;
  src_name = other.src_name;
  targ_name = other.targ_name;
  lifetime_rec = other.lifetime_rec;
  CopyOid(&mech_type, &other.mech_type);
  ctx_flags = other.ctx_flags;
  locally_initiated = other.locally_initiated;
  open = other.open;
}

MockGSSAPILibrary::SecurityContextQuery::SecurityContextQuery()
    : expected_package(),
      response_code(0),
      minor_response_code(0),
      context_info() {
  expected_input_token.length = 0;
  expected_input_token.value = NULL;
  output_token.length = 0;
  output_token.value = NULL;
}

MockGSSAPILibrary::SecurityContextQuery::SecurityContextQuery(
    const std::string& in_expected_package,
    OM_uint32 in_response_code,
    OM_uint32 in_minor_response_code,
    const test::GssContextMockImpl& in_context_info,
    const char* in_expected_input_token,
    const char* in_output_token)
    : expected_package(in_expected_package),
      response_code(in_response_code),
      minor_response_code(in_minor_response_code),
      context_info(in_context_info) {
  if (in_expected_input_token) {
    expected_input_token.length = strlen(in_expected_input_token);
    expected_input_token.value = const_cast<char*>(in_expected_input_token);
  } else {
    expected_input_token.length = 0;
    expected_input_token.value = NULL;
  }

  if (in_output_token) {
    output_token.length = strlen(in_output_token);
    output_token.value = const_cast<char*>(in_output_token);
  } else {
    output_token.length = 0;
    output_token.value = NULL;
  }
}

MockGSSAPILibrary::SecurityContextQuery::SecurityContextQuery(
    const SecurityContextQuery& other) = default;

MockGSSAPILibrary::SecurityContextQuery::~SecurityContextQuery() = default;

MockGSSAPILibrary::MockGSSAPILibrary() = default;

MockGSSAPILibrary::~MockGSSAPILibrary() = default;

void MockGSSAPILibrary::ExpectSecurityContext(
    const std::string& expected_package,
    OM_uint32 response_code,
    OM_uint32 minor_response_code,
    const GssContextMockImpl& context_info,
    const gss_buffer_desc& expected_input_token,
    const gss_buffer_desc& output_token) {
  SecurityContextQuery security_query;
  security_query.expected_package = expected_package;
  security_query.response_code = response_code;
  security_query.minor_response_code = minor_response_code;
  security_query.context_info.Assign(context_info);
  security_query.expected_input_token = expected_input_token;
  security_query.output_token = output_token;
  expected_security_queries_.push_back(security_query);
}

bool MockGSSAPILibrary::Init() {
  return true;
}

// These methods match the ones in the GSSAPI library.
OM_uint32 MockGSSAPILibrary::import_name(
      OM_uint32* minor_status,
      const gss_buffer_t input_name_buffer,
      const gss_OID input_name_type,
      gss_name_t* output_name) {
  if (minor_status)
    *minor_status = 0;
  if (!output_name)
    return GSS_S_BAD_NAME;
  if (!input_name_buffer)
    return GSS_S_CALL_BAD_STRUCTURE;
  if (!input_name_type)
    return GSS_S_BAD_NAMETYPE;
  GssNameMockImpl* output = new GssNameMockImpl;
  if (output == NULL)
    return GSS_S_FAILURE;
  output->name_type.length = 0;
  output->name_type.elements = NULL;

  // Save the data.
  output->name = BufferToString(input_name_buffer);
  CopyOid(&output->name_type, input_name_type);
  *output_name = reinterpret_cast<gss_name_t>(output);

  return GSS_S_COMPLETE;
}

OM_uint32 MockGSSAPILibrary::release_name(
      OM_uint32* minor_status,
      gss_name_t* input_name) {
  if (minor_status)
    *minor_status = 0;
  if (!input_name)
    return GSS_S_BAD_NAME;
  if (!*input_name)
    return GSS_S_COMPLETE;
  GssNameMockImpl* name = *reinterpret_cast<GssNameMockImpl**>(input_name);
  ClearName(*input_name);
  delete name;
  *input_name = NULL;
  return GSS_S_COMPLETE;
}

OM_uint32 MockGSSAPILibrary::release_buffer(
      OM_uint32* minor_status,
      gss_buffer_t buffer) {
  if (minor_status)
    *minor_status = 0;
  if (!buffer)
    return GSS_S_BAD_NAME;
  ClearBuffer(buffer);
  return GSS_S_COMPLETE;
}

OM_uint32 MockGSSAPILibrary::display_name(
    OM_uint32* minor_status,
    const gss_name_t input_name,
    gss_buffer_t output_name_buffer,
    gss_OID* output_name_type) {
  if (minor_status)
    *minor_status = 0;
  if (!input_name)
    return GSS_S_BAD_NAME;
  if (!output_name_buffer)
    return GSS_S_CALL_BAD_STRUCTURE;
  if (!output_name_type)
    return GSS_S_CALL_BAD_STRUCTURE;
  std::string name(NameToString(input_name));
  BufferFromString(name, output_name_buffer);
  GssNameMockImpl* internal_name =
      *reinterpret_cast<GssNameMockImpl**>(input_name);
  if (output_name_type)
    *output_name_type = internal_name ? &internal_name->name_type : NULL;
  return GSS_S_COMPLETE;
}

OM_uint32 MockGSSAPILibrary::display_status(
      OM_uint32* minor_status,
      OM_uint32 status_value,
      int status_type,
      const gss_OID mech_type,
      OM_uint32* message_context,
      gss_buffer_t status_string) {
  if (minor_status)
    *minor_status = 0;
  std::string msg = base::StringPrintf("Value: %u, Type %u",
                                       status_value,
                                       status_type);
  if (message_context)
    *message_context = 0;
  BufferFromString(msg, status_string);
  return GSS_S_COMPLETE;
}

OM_uint32 MockGSSAPILibrary::init_sec_context(
      OM_uint32* minor_status,
      const gss_cred_id_t initiator_cred_handle,
      gss_ctx_id_t* context_handle,
      const gss_name_t target_name,
      const gss_OID mech_type,
      OM_uint32 req_flags,
      OM_uint32 time_req,
      const gss_channel_bindings_t input_chan_bindings,
      const gss_buffer_t input_token,
      gss_OID* actual_mech_type,
      gss_buffer_t output_token,
      OM_uint32* ret_flags,
      OM_uint32* time_rec) {
  if (minor_status)
    *minor_status = 0;
  if (!context_handle)
    return GSS_S_CALL_BAD_STRUCTURE;
  GssContextMockImpl** internal_context_handle =
      reinterpret_cast<test::GssContextMockImpl**>(context_handle);
  // Create it if necessary.
  if (!*internal_context_handle) {
    *internal_context_handle = new GssContextMockImpl;
  }
  EXPECT_TRUE(*internal_context_handle);
  GssContextMockImpl& context = **internal_context_handle;
  if (expected_security_queries_.empty()) {
    return GSS_S_UNAVAILABLE;
  }
  SecurityContextQuery security_query = expected_security_queries_.front();
  expected_security_queries_.pop_front();
  EXPECT_EQ(std::string("Negotiate"), security_query.expected_package);
  OM_uint32 major_status = security_query.response_code;
  if (minor_status)
    *minor_status = security_query.minor_response_code;
  context.src_name = security_query.context_info.src_name;
  context.targ_name = security_query.context_info.targ_name;
  context.lifetime_rec = security_query.context_info.lifetime_rec;
  CopyOid(&context.mech_type, &security_query.context_info.mech_type);
  context.ctx_flags = security_query.context_info.ctx_flags;
  context.locally_initiated = security_query.context_info.locally_initiated;
  context.open = security_query.context_info.open;
  if (!input_token) {
    EXPECT_FALSE(security_query.expected_input_token.length);
  } else {
    EXPECT_EQ(input_token->length, security_query.expected_input_token.length);
    if (input_token->length) {
      EXPECT_EQ(0, memcmp(input_token->value,
                          security_query.expected_input_token.value,
                          input_token->length));
    }
  }
  CopyBuffer(output_token, &security_query.output_token);
  if (actual_mech_type)
    CopyOid(*actual_mech_type, mech_type);
  if (ret_flags)
    *ret_flags = req_flags;
  return major_status;
}

OM_uint32 MockGSSAPILibrary::wrap_size_limit(
      OM_uint32* minor_status,
      const gss_ctx_id_t context_handle,
      int conf_req_flag,
      gss_qop_t qop_req,
      OM_uint32 req_output_size,
      OM_uint32* max_input_size) {
  if (minor_status)
    *minor_status = 0;
  ADD_FAILURE();
  return GSS_S_UNAVAILABLE;
}

OM_uint32 MockGSSAPILibrary::delete_sec_context(
      OM_uint32* minor_status,
      gss_ctx_id_t* context_handle,
      gss_buffer_t output_token) {
  if (minor_status)
    *minor_status = 0;
  if (!context_handle)
    return GSS_S_CALL_BAD_STRUCTURE;
  GssContextMockImpl** internal_context_handle =
      reinterpret_cast<GssContextMockImpl**>(context_handle);
  if (*internal_context_handle) {
    delete *internal_context_handle;
    *internal_context_handle = NULL;
  }
  return GSS_S_COMPLETE;
}

OM_uint32 MockGSSAPILibrary::inquire_context(
    OM_uint32* minor_status,
    const gss_ctx_id_t context_handle,
    gss_name_t* src_name,
    gss_name_t* targ_name,
    OM_uint32* lifetime_rec,
    gss_OID* mech_type,
    OM_uint32* ctx_flags,
    int* locally_initiated,
    int* open) {
  if (minor_status)
    *minor_status = 0;
  if (!context_handle)
    return GSS_S_CALL_BAD_STRUCTURE;
  GssContextMockImpl* internal_context_ptr =
      reinterpret_cast<GssContextMockImpl*>(context_handle);
  GssContextMockImpl& context = *internal_context_ptr;
  if (src_name)
    NameFromString(context.src_name, *src_name);
  if (targ_name)
    NameFromString(context.targ_name, *targ_name);
  if (lifetime_rec)
    *lifetime_rec = context.lifetime_rec;
  if (mech_type)
    CopyOid(*mech_type, &context.mech_type);
  if (ctx_flags)
    *ctx_flags = context.ctx_flags;
  if (locally_initiated)
    *locally_initiated = context.locally_initiated;
  if (open)
    *open = context.open;
  return GSS_S_COMPLETE;
}

const std::string& MockGSSAPILibrary::GetLibraryNameForTesting() {
  return library_name_;
}

}  // namespace test

}  // namespace net
