tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsAuthSambaNTLM.cpp (7904B)


      1 /* vim:set ts=4 sw=2 et cindent: */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "base/process_util.h"
      7 #include "nsAuth.h"
      8 #include "nsAuthSambaNTLM.h"
      9 #include "nspr.h"
     10 #include "prenv.h"
     11 #include "prerror.h"
     12 #include "mozilla/glean/SecurityManagerSslMetrics.h"
     13 #include "mozilla/Base64.h"
     14 
     15 #include <stdlib.h>
     16 #include <sys/wait.h>
     17 
     18 nsAuthSambaNTLM::nsAuthSambaNTLM() = default;
     19 
     20 nsAuthSambaNTLM::~nsAuthSambaNTLM() {
     21  // ntlm_auth reads from stdin regularly so closing our file handles
     22  // should cause it to exit.
     23  Shutdown();
     24  PR_Free(mInitialMessage);
     25 }
     26 
     27 void nsAuthSambaNTLM::Shutdown() {
     28  mFromChildFD = nullptr;
     29  mToChildFD = nullptr;
     30 
     31  if (mChildPID != -1) {
     32    // Kill and wait for the process to exit.
     33    kill(mChildPID, SIGKILL);
     34 
     35    int status = 0;
     36    pid_t result;
     37    do {
     38      result = waitpid(mChildPID, &status, 0);
     39    } while (result == -1 && errno == EINTR);
     40 
     41    mChildPID = -1;
     42  }
     43 }
     44 
     45 NS_IMPL_ISUPPORTS(nsAuthSambaNTLM, nsIAuthModule)
     46 
     47 [[nodiscard]] static bool CreatePipe(mozilla::UniqueFileHandle* aReadPipe,
     48                                     mozilla::UniqueFileHandle* aWritePipe) {
     49  int fds[2];
     50  if (pipe(fds) == -1) {
     51    return false;
     52  }
     53 
     54  aReadPipe->reset(fds[0]);
     55  aWritePipe->reset(fds[1]);
     56  return true;
     57 }
     58 
     59 static bool WriteString(const mozilla::UniqueFileHandle& aFD,
     60                        const nsACString& aString) {
     61  size_t length = aString.Length();
     62  const char* s = aString.BeginReading();
     63  LOG(("Writing to ntlm_auth: %s", s));
     64 
     65  while (length > 0) {
     66    ssize_t result;
     67    do {
     68      result = write(aFD.get(), s, length);
     69    } while (result == -1 && errno == EINTR);
     70    if (result <= 0) return false;
     71    s += result;
     72    length -= result;
     73  }
     74  return true;
     75 }
     76 
     77 static bool ReadLine(const mozilla::UniqueFileHandle& aFD,
     78                     nsACString& aString) {
     79  // ntlm_auth is defined to only send one line in response to each of our
     80  // input lines. So this simple unbuffered strategy works as long as we
     81  // read the response immediately after sending one request.
     82  aString.Truncate();
     83  for (;;) {
     84    char buf[1024];
     85    ssize_t result;
     86    do {
     87      result = read(aFD.get(), buf, sizeof(buf));
     88    } while (result == -1 && errno == EINTR);
     89    if (result <= 0) return false;
     90    aString.Append(buf, result);
     91    if (buf[result - 1] == '\n') {
     92      LOG(("Read from ntlm_auth: %s", nsPromiseFlatCString(aString).get()));
     93      return true;
     94    }
     95  }
     96 }
     97 
     98 /**
     99 * Returns a heap-allocated array of PRUint8s, and stores the length in aLen.
    100 * Returns nullptr if there's an error of any kind.
    101 */
    102 static uint8_t* ExtractMessage(const nsACString& aLine, uint32_t* aLen) {
    103  // ntlm_auth sends blobs to us as base64-encoded strings after the "xx "
    104  // preamble on the response line.
    105  int32_t length = aLine.Length();
    106  // The caller should verify there is a valid "xx " prefix and the line
    107  // is terminated with a \n
    108  NS_ASSERTION(length >= 4, "Line too short...");
    109  const char* line = aLine.BeginReading();
    110  const char* s = line + 3;
    111  length -= 4;  // lose first 3 chars plus trailing \n
    112  NS_ASSERTION(s[length] == '\n', "aLine not newline-terminated");
    113 
    114  if (length & 3) {
    115    // The base64 encoded block must be multiple of 4. If not, something
    116    // screwed up.
    117    NS_WARNING("Base64 encoded block should be a multiple of 4 chars");
    118    return nullptr;
    119  }
    120 
    121  char* base64;
    122  if (NS_FAILED(mozilla::Base64Decode(s, length, &base64, aLen))) {
    123    return nullptr;
    124  }
    125  return (uint8_t*)base64;
    126 }
    127 
    128 nsresult nsAuthSambaNTLM::SpawnNTLMAuthHelper() {
    129  const char* username = PR_GetEnv("USER");
    130  if (!username) return NS_ERROR_FAILURE;
    131 
    132  // Use base::LaunchApp to run the child process. This code is posix-only, as
    133  // this will not be used on Windows.
    134  {
    135    mozilla::UniqueFileHandle toChildPipeRead;
    136    mozilla::UniqueFileHandle toChildPipeWrite;
    137    if (!CreatePipe(&toChildPipeRead, &toChildPipeWrite)) {
    138      return NS_ERROR_FAILURE;
    139    }
    140 
    141    mozilla::UniqueFileHandle fromChildPipeRead;
    142    mozilla::UniqueFileHandle fromChildPipeWrite;
    143    if (!CreatePipe(&fromChildPipeRead, &fromChildPipeWrite)) {
    144      return NS_ERROR_FAILURE;
    145    }
    146 
    147    base::LaunchOptions options;
    148    options.fds_to_remap.push_back(
    149        std::pair{toChildPipeRead.get(), STDIN_FILENO});
    150    options.fds_to_remap.push_back(
    151        std::pair{fromChildPipeWrite.get(), STDOUT_FILENO});
    152 
    153    std::vector<std::string> argvVec{"ntlm_auth",        "--helper-protocol",
    154                                     "ntlmssp-client-1", "--use-cached-creds",
    155                                     "--username",       username};
    156 
    157    auto result = base::LaunchApp(argvVec, std::move(options), &mChildPID);
    158    if (result.isErr()) {
    159      return NS_ERROR_FAILURE;
    160    }
    161 
    162    mToChildFD = std::move(toChildPipeWrite);
    163    mFromChildFD = std::move(fromChildPipeRead);
    164  }
    165 
    166  if (!WriteString(mToChildFD, "YR\n"_ns)) return NS_ERROR_FAILURE;
    167  nsCString line;
    168  if (!ReadLine(mFromChildFD, line)) return NS_ERROR_FAILURE;
    169  if (!StringBeginsWith(line, "YR "_ns)) {
    170    // Something went wrong. Perhaps no credentials are accessible.
    171    return NS_ERROR_FAILURE;
    172  }
    173 
    174  // It gave us an initial client-to-server request packet. Save that
    175  // because we'll need it later.
    176  mInitialMessage = ExtractMessage(line, &mInitialMessageLen);
    177  if (!mInitialMessage) return NS_ERROR_FAILURE;
    178  return NS_OK;
    179 }
    180 
    181 NS_IMETHODIMP
    182 nsAuthSambaNTLM::Init(const nsACString& serviceName, uint32_t serviceFlags,
    183                      const nsAString& domain, const nsAString& username,
    184                      const nsAString& password) {
    185  NS_ASSERTION(username.IsEmpty() && domain.IsEmpty() && password.IsEmpty(),
    186               "unexpected credentials");
    187 
    188  static bool sTelemetrySent = false;
    189  if (!sTelemetrySent) {
    190    mozilla::glean::security::ntlm_module_used.AccumulateSingleSample(
    191        serviceFlags & nsIAuthModule::REQ_PROXY_AUTH
    192            ? NTLM_MODULE_SAMBA_AUTH_PROXY
    193            : NTLM_MODULE_SAMBA_AUTH_DIRECT);
    194    sTelemetrySent = true;
    195  }
    196 
    197  return NS_OK;
    198 }
    199 
    200 NS_IMETHODIMP
    201 nsAuthSambaNTLM::GetNextToken(const void* inToken, uint32_t inTokenLen,
    202                              void** outToken, uint32_t* outTokenLen) {
    203  if (!inToken) {
    204    /* someone wants our initial message */
    205    *outToken = moz_xmemdup(mInitialMessage, mInitialMessageLen);
    206    *outTokenLen = mInitialMessageLen;
    207    return NS_OK;
    208  }
    209 
    210  /* inToken must be a type 2 message. Get ntlm_auth to generate our response */
    211  nsCString request;
    212  request.AssignLiteral("TT ");
    213  if (NS_FAILED(mozilla::Base64EncodeAppend(static_cast<const char*>(inToken),
    214                                            inTokenLen, request))) {
    215    return NS_ERROR_OUT_OF_MEMORY;
    216  }
    217  request.Append('\n');
    218 
    219  if (!WriteString(mToChildFD, request)) return NS_ERROR_FAILURE;
    220  nsCString line;
    221  if (!ReadLine(mFromChildFD, line)) return NS_ERROR_FAILURE;
    222  if (!StringBeginsWith(line, "KK "_ns) && !StringBeginsWith(line, "AF "_ns)) {
    223    // Something went wrong. Perhaps no credentials are accessible.
    224    return NS_ERROR_FAILURE;
    225  }
    226  uint8_t* buf = ExtractMessage(line, outTokenLen);
    227  if (!buf) return NS_ERROR_FAILURE;
    228  *outToken = moz_xmemdup(buf, *outTokenLen);
    229  PR_Free(buf);
    230 
    231  // We're done. Close our file descriptors now and reap the helper
    232  // process.
    233  Shutdown();
    234  return NS_SUCCESS_AUTH_FINISHED;
    235 }
    236 
    237 NS_IMETHODIMP
    238 nsAuthSambaNTLM::Unwrap(const void* inToken, uint32_t inTokenLen,
    239                        void** outToken, uint32_t* outTokenLen) {
    240  return NS_ERROR_NOT_IMPLEMENTED;
    241 }
    242 
    243 NS_IMETHODIMP
    244 nsAuthSambaNTLM::Wrap(const void* inToken, uint32_t inTokenLen,
    245                      bool confidential, void** outToken,
    246                      uint32_t* outTokenLen) {
    247  return NS_ERROR_NOT_IMPLEMENTED;
    248 }