// Copyright (c) 2012 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/http_auth_handler_ntlm.h"

#include "base/rand_util.h"
#include "base/time/time.h"
#include "net/base/net_errors.h"
#include "net/base/network_interfaces.h"
#include "net/http/http_auth_preferences.h"

namespace net {

namespace {

uint64_t GetMSTime() {
  return base::Time::Now().since_origin().InMicroseconds() * 10;
}

void GenerateRandom(uint8_t* output, size_t n) {
  base::RandBytes(output, n);
}

}  // namespace

// static
HttpAuthHandlerNTLM::GetMSTimeProc HttpAuthHandlerNTLM::get_ms_time_proc_ =
    GetMSTime;

// static
HttpAuthHandlerNTLM::GenerateRandomProc
    HttpAuthHandlerNTLM::generate_random_proc_ = GenerateRandom;

// static
HttpAuthHandlerNTLM::HostNameProc HttpAuthHandlerNTLM::get_host_name_proc_ =
    GetHostName;

HttpAuthHandlerNTLM::HttpAuthHandlerNTLM(
    const HttpAuthPreferences* http_auth_preferences)
    : ntlm_client_(ntlm::NtlmFeatures(
          http_auth_preferences ? http_auth_preferences->NtlmV2Enabled()
                                : true)) {}

bool HttpAuthHandlerNTLM::NeedsIdentity() {
  // This gets called for each round-trip.  Only require identity on
  // the first call (when auth_data_ is empty).  On subsequent calls,
  // we use the initially established identity.
  return auth_data_.empty();
}

bool HttpAuthHandlerNTLM::AllowsDefaultCredentials() {
  // Default credentials are not supported in the portable implementation of
  // NTLM, but are supported in the SSPI implementation.
  return false;
}

int HttpAuthHandlerNTLM::InitializeBeforeFirstChallenge() {
  return OK;
}

HttpAuthHandlerNTLM::~HttpAuthHandlerNTLM() = default;

// static
HttpAuthHandlerNTLM::GetMSTimeProc HttpAuthHandlerNTLM::SetGetMSTimeProc(
    GetMSTimeProc proc) {
  GetMSTimeProc old_proc = get_ms_time_proc_;
  get_ms_time_proc_ = proc;
  return old_proc;
}

// static
HttpAuthHandlerNTLM::GenerateRandomProc
HttpAuthHandlerNTLM::SetGenerateRandomProc(GenerateRandomProc proc) {
  GenerateRandomProc old_proc = generate_random_proc_;
  generate_random_proc_ = proc;
  return old_proc;
}

// static
HttpAuthHandlerNTLM::HostNameProc HttpAuthHandlerNTLM::SetHostNameProc(
    HostNameProc proc) {
  HostNameProc old_proc = get_host_name_proc_;
  get_host_name_proc_ = proc;
  return old_proc;
}

HttpAuthHandlerNTLM::Factory::Factory() = default;

HttpAuthHandlerNTLM::Factory::~Factory() = default;

std::vector<uint8_t> HttpAuthHandlerNTLM::GetNextToken(
    base::span<const uint8_t> in_token) {
  // If in_token is non-empty, then assume it contains a challenge message,
  // and generate the Authenticate message in reply. Otherwise return the
  // Negotiate message.
  if (in_token.empty()) {
    return ntlm_client_.GetNegotiateMessage();
  }

  std::string hostname = get_host_name_proc_();
  if (hostname.empty())
    return {};
  uint8_t client_challenge[8];
  generate_random_proc_(client_challenge, 8);
  uint64_t client_time = get_ms_time_proc_();

  return ntlm_client_.GenerateAuthenticateMessage(
      domain_, credentials_.username(), credentials_.password(), hostname,
      channel_bindings_, CreateSPN(origin_), client_time, client_challenge,
      in_token);
}

int HttpAuthHandlerNTLM::Factory::CreateAuthHandler(
    HttpAuthChallengeTokenizer* challenge,
    HttpAuth::Target target,
    const SSLInfo& ssl_info,
    const GURL& origin,
    CreateReason reason,
    int digest_nonce_count,
    const NetLogWithSource& net_log,
    std::unique_ptr<HttpAuthHandler>* handler) {
  if (reason == CREATE_PREEMPTIVE)
    return ERR_UNSUPPORTED_AUTH_SCHEME;
  // TODO(cbentzel): Move towards model of parsing in the factory
  //                 method and only constructing when valid.
  // NOTE: Default credentials are not supported for the portable implementation
  // of NTLM.
  std::unique_ptr<HttpAuthHandler> tmp_handler(
      new HttpAuthHandlerNTLM(http_auth_preferences()));
  if (!tmp_handler->InitFromChallenge(challenge, target, ssl_info, origin,
                                      net_log))
    return ERR_INVALID_RESPONSE;
  handler->swap(tmp_handler);
  return OK;
}

}  // namespace net
