// Copyright 2019 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_ntlm_mechanism.h"

#include "base/base64.h"
#include "base/containers/span.h"
#include "base/logging.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_challenge_tokenizer.h"
#include "net/http/http_auth_multi_round_parse.h"
#include "net/http/http_auth_preferences.h"
#include "net/http/http_auth_scheme.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);
}

// static
HttpAuthNtlmMechanism::GetMSTimeProc g_get_ms_time_proc = GetMSTime;

// static
HttpAuthNtlmMechanism::GenerateRandomProc g_generate_random_proc =
    GenerateRandom;

// static
HttpAuthNtlmMechanism::HostNameProc g_host_name_proc = GetHostName;

template <typename T>
T SwapOut(T* target, T source) {
  T t = *target;
  *target = source;
  return t;
}

int SetAuthTokenFromBinaryToken(std::string* auth_token,
                                const std::vector<uint8_t>& next_token) {
  if (next_token.empty())
    return ERR_UNEXPECTED;

  std::string encode_output;
  base::Base64Encode(
      base::StringPiece(reinterpret_cast<const char*>(next_token.data()),
                        next_token.size()),
      &encode_output);

  *auth_token = std::string("NTLM ") + encode_output;
  return OK;
}

}  // namespace

HttpAuthNtlmMechanism::ScopedProcSetter::ScopedProcSetter(
    GetMSTimeProc ms_time_proc,
    GenerateRandomProc random_proc,
    HostNameProc host_name_proc) {
  old_ms_time_proc_ = SwapOut(&g_get_ms_time_proc, ms_time_proc);
  old_random_proc_ = SwapOut(&g_generate_random_proc, random_proc);
  old_host_name_proc_ = SwapOut(&g_host_name_proc, host_name_proc);
}

HttpAuthNtlmMechanism::ScopedProcSetter::~ScopedProcSetter() {
  g_get_ms_time_proc = old_ms_time_proc_;
  g_generate_random_proc = old_random_proc_;
  g_host_name_proc = old_host_name_proc_;
}

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

HttpAuthNtlmMechanism::~HttpAuthNtlmMechanism() = default;

bool HttpAuthNtlmMechanism::Init(const NetLogWithSource& net_log) {
  return true;
}

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

bool HttpAuthNtlmMechanism::AllowsExplicitCredentials() const {
  return true;
}

HttpAuth::AuthorizationResult HttpAuthNtlmMechanism::ParseChallenge(
    HttpAuthChallengeTokenizer* tok) {
  if (!first_token_sent_)
    return ParseFirstRoundChallenge(HttpAuth::Scheme::AUTH_SCHEME_NTLM, tok);

  challenge_token_.clear();
  std::string encoded_token;
  return ParseLaterRoundChallenge(HttpAuth::Scheme::AUTH_SCHEME_NTLM, tok,
                                  &encoded_token, &challenge_token_);
}

int HttpAuthNtlmMechanism::GenerateAuthToken(
    const AuthCredentials* credentials,
    const std::string& spn,
    const std::string& channel_bindings,
    std::string* auth_token,
    const NetLogWithSource& net_log,
    CompletionOnceCallback callback) {
  if (!credentials) {
    LOG(ERROR) << "Username and password are expected to be non-nullptr.";
    return ERR_MISSING_AUTH_CREDENTIALS;
  }

  if (challenge_token_.empty()) {
    if (first_token_sent_)
      return ERR_UNEXPECTED;
    first_token_sent_ = true;
    return SetAuthTokenFromBinaryToken(auth_token,
                                       ntlm_client_.GetNegotiateMessage());
  }

  // The username may be in the form "DOMAIN\user".  Parse it into the two
  // components.
  base::string16 domain;
  base::string16 user;
  const base::string16& username = credentials->username();
  const base::char16 backslash_character = '\\';
  size_t backslash_idx = username.find(backslash_character);
  if (backslash_idx == base::string16::npos) {
    user = username;
  } else {
    domain = username.substr(0, backslash_idx);
    user = username.substr(backslash_idx + 1);
  }

  std::string hostname = g_host_name_proc();
  if (hostname.empty())
    return ERR_UNEXPECTED;

  uint8_t client_challenge[8];
  g_generate_random_proc(client_challenge, 8);

  auto next_token = ntlm_client_.GenerateAuthenticateMessage(
      domain, user, credentials->password(), hostname, channel_bindings, spn,
      g_get_ms_time_proc(), client_challenge,
      base::as_bytes(base::make_span(challenge_token_)));

  return SetAuthTokenFromBinaryToken(auth_token, next_token);
}

void HttpAuthNtlmMechanism::SetDelegation(
    HttpAuth::DelegationType delegation_type) {
  // Nothing to do.
}

}  // namespace net
