extensions/auth/nsAuthSambaNTLM.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/extensions/auth/nsAuthSambaNTLM.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,296 @@
     1.4 +/* vim:set ts=4 sw=4 et cindent: */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "nsAuth.h"
    1.10 +#include "nsAuthSambaNTLM.h"
    1.11 +#include "prenv.h"
    1.12 +#include "plbase64.h"
    1.13 +#include "prerror.h"
    1.14 +#include "mozilla/Telemetry.h"
    1.15 +
    1.16 +#include <stdlib.h>
    1.17 +
    1.18 +nsAuthSambaNTLM::nsAuthSambaNTLM()
    1.19 +    : mInitialMessage(nullptr), mChildPID(nullptr), mFromChildFD(nullptr),
    1.20 +      mToChildFD(nullptr)
    1.21 +{
    1.22 +}
    1.23 +
    1.24 +nsAuthSambaNTLM::~nsAuthSambaNTLM()
    1.25 +{
    1.26 +    // ntlm_auth reads from stdin regularly so closing our file handles
    1.27 +    // should cause it to exit.
    1.28 +    Shutdown();
    1.29 +    free(mInitialMessage);
    1.30 +}
    1.31 +
    1.32 +void
    1.33 +nsAuthSambaNTLM::Shutdown()
    1.34 +{
    1.35 +    if (mFromChildFD) {
    1.36 +        PR_Close(mFromChildFD);
    1.37 +        mFromChildFD = nullptr;
    1.38 +    }
    1.39 +    if (mToChildFD) {
    1.40 +        PR_Close(mToChildFD);
    1.41 +        mToChildFD = nullptr;
    1.42 +    }
    1.43 +    if (mChildPID) {
    1.44 +        int32_t exitCode;
    1.45 +        PR_WaitProcess(mChildPID, &exitCode);
    1.46 +        mChildPID = nullptr;
    1.47 +    }
    1.48 +}
    1.49 +
    1.50 +NS_IMPL_ISUPPORTS(nsAuthSambaNTLM, nsIAuthModule)
    1.51 +
    1.52 +static bool
    1.53 +SpawnIOChild(char* const* aArgs, PRProcess** aPID,
    1.54 +             PRFileDesc** aFromChildFD, PRFileDesc** aToChildFD)
    1.55 +{
    1.56 +    PRFileDesc* toChildPipeRead;
    1.57 +    PRFileDesc* toChildPipeWrite;
    1.58 +    if (PR_CreatePipe(&toChildPipeRead, &toChildPipeWrite) != PR_SUCCESS)
    1.59 +        return false;
    1.60 +    PR_SetFDInheritable(toChildPipeRead, true);
    1.61 +    PR_SetFDInheritable(toChildPipeWrite, false);
    1.62 +
    1.63 +    PRFileDesc* fromChildPipeRead;
    1.64 +    PRFileDesc* fromChildPipeWrite;
    1.65 +    if (PR_CreatePipe(&fromChildPipeRead, &fromChildPipeWrite) != PR_SUCCESS) {
    1.66 +        PR_Close(toChildPipeRead);
    1.67 +        PR_Close(toChildPipeWrite);
    1.68 +        return false;
    1.69 +    }
    1.70 +    PR_SetFDInheritable(fromChildPipeRead, false);
    1.71 +    PR_SetFDInheritable(fromChildPipeWrite, true);
    1.72 +
    1.73 +    PRProcessAttr* attr = PR_NewProcessAttr();
    1.74 +    if (!attr) {
    1.75 +        PR_Close(fromChildPipeRead);
    1.76 +        PR_Close(fromChildPipeWrite);
    1.77 +        PR_Close(toChildPipeRead);
    1.78 +        PR_Close(toChildPipeWrite);
    1.79 +        return false;
    1.80 +    }
    1.81 +
    1.82 +    PR_ProcessAttrSetStdioRedirect(attr, PR_StandardInput, toChildPipeRead);
    1.83 +    PR_ProcessAttrSetStdioRedirect(attr, PR_StandardOutput, fromChildPipeWrite);   
    1.84 +
    1.85 +    PRProcess* process = PR_CreateProcess(aArgs[0], aArgs, nullptr, attr);
    1.86 +    PR_DestroyProcessAttr(attr);
    1.87 +    PR_Close(fromChildPipeWrite);
    1.88 +    PR_Close(toChildPipeRead);
    1.89 +    if (!process) {
    1.90 +        LOG(("ntlm_auth exec failure [%d]", PR_GetError()));
    1.91 +        PR_Close(fromChildPipeRead);
    1.92 +        PR_Close(toChildPipeWrite);
    1.93 +        return false;        
    1.94 +    }
    1.95 +
    1.96 +    *aPID = process;
    1.97 +    *aFromChildFD = fromChildPipeRead;
    1.98 +    *aToChildFD = toChildPipeWrite;
    1.99 +    return true;
   1.100 +}
   1.101 +
   1.102 +static bool WriteString(PRFileDesc* aFD, const nsACString& aString)
   1.103 +{
   1.104 +    int32_t length = aString.Length();
   1.105 +    const char* s = aString.BeginReading();
   1.106 +    LOG(("Writing to ntlm_auth: %s", s));
   1.107 +
   1.108 +    while (length > 0) {
   1.109 +        int result = PR_Write(aFD, s, length);
   1.110 +        if (result <= 0)
   1.111 +            return false;
   1.112 +        s += result;
   1.113 +        length -= result;
   1.114 +    }
   1.115 +    return true;
   1.116 +}
   1.117 +
   1.118 +static bool ReadLine(PRFileDesc* aFD, nsACString& aString)
   1.119 +{
   1.120 +    // ntlm_auth is defined to only send one line in response to each of our
   1.121 +    // input lines. So this simple unbuffered strategy works as long as we
   1.122 +    // read the response immediately after sending one request.
   1.123 +    aString.Truncate();
   1.124 +    for (;;) {
   1.125 +        char buf[1024];
   1.126 +        int result = PR_Read(aFD, buf, sizeof(buf));
   1.127 +        if (result <= 0)
   1.128 +            return false;
   1.129 +        aString.Append(buf, result);
   1.130 +        if (buf[result - 1] == '\n') {
   1.131 +            LOG(("Read from ntlm_auth: %s", nsPromiseFlatCString(aString).get()));
   1.132 +            return true;
   1.133 +        }
   1.134 +    }
   1.135 +}
   1.136 +
   1.137 +/**
   1.138 + * Returns a heap-allocated array of PRUint8s, and stores the length in aLen.
   1.139 + * Returns nullptr if there's an error of any kind.
   1.140 + */
   1.141 +static uint8_t* ExtractMessage(const nsACString& aLine, uint32_t* aLen)
   1.142 +{
   1.143 +    // ntlm_auth sends blobs to us as base64-encoded strings after the "xx "
   1.144 +    // preamble on the response line.
   1.145 +    int32_t length = aLine.Length();
   1.146 +    // The caller should verify there is a valid "xx " prefix and the line
   1.147 +    // is terminated with a \n
   1.148 +    NS_ASSERTION(length >= 4, "Line too short...");
   1.149 +    const char* line = aLine.BeginReading();
   1.150 +    const char* s = line + 3;
   1.151 +    length -= 4; // lose first 3 chars plus trailing \n
   1.152 +    NS_ASSERTION(s[length] == '\n', "aLine not newline-terminated");
   1.153 +    
   1.154 +    if (length & 3) {
   1.155 +        // The base64 encoded block must be multiple of 4. If not, something
   1.156 +        // screwed up.
   1.157 +        NS_WARNING("Base64 encoded block should be a multiple of 4 chars");
   1.158 +        return nullptr;
   1.159 +    } 
   1.160 +
   1.161 +    // Calculate the exact length. I wonder why there isn't a function for this
   1.162 +    // in plbase64.
   1.163 +    int32_t numEquals;
   1.164 +    for (numEquals = 0; numEquals < length; ++numEquals) {
   1.165 +        if (s[length - 1 - numEquals] != '=')
   1.166 +            break;
   1.167 +    }
   1.168 +    *aLen = (length/4)*3 - numEquals;
   1.169 +    return reinterpret_cast<uint8_t*>(PL_Base64Decode(s, length, nullptr));
   1.170 +}
   1.171 +
   1.172 +nsresult
   1.173 +nsAuthSambaNTLM::SpawnNTLMAuthHelper()
   1.174 +{
   1.175 +    const char* username = PR_GetEnv("USER");
   1.176 +    if (!username)
   1.177 +        return NS_ERROR_FAILURE;
   1.178 +
   1.179 +    const char* const args[] = {
   1.180 +        "ntlm_auth",
   1.181 +        "--helper-protocol", "ntlmssp-client-1",
   1.182 +        "--use-cached-creds",
   1.183 +        "--username", username,
   1.184 +        nullptr
   1.185 +    };
   1.186 +
   1.187 +    bool isOK = SpawnIOChild(const_cast<char* const*>(args), &mChildPID, &mFromChildFD, &mToChildFD);
   1.188 +    if (!isOK)  
   1.189 +        return NS_ERROR_FAILURE;
   1.190 +
   1.191 +    if (!WriteString(mToChildFD, NS_LITERAL_CSTRING("YR\n")))
   1.192 +        return NS_ERROR_FAILURE;
   1.193 +    nsCString line;
   1.194 +    if (!ReadLine(mFromChildFD, line))
   1.195 +        return NS_ERROR_FAILURE;
   1.196 +    if (!StringBeginsWith(line, NS_LITERAL_CSTRING("YR "))) {
   1.197 +        // Something went wrong. Perhaps no credentials are accessible.
   1.198 +        return NS_ERROR_FAILURE;
   1.199 +    }
   1.200 +
   1.201 +    // It gave us an initial client-to-server request packet. Save that
   1.202 +    // because we'll need it later.
   1.203 +    mInitialMessage = ExtractMessage(line, &mInitialMessageLen);
   1.204 +    if (!mInitialMessage)
   1.205 +        return NS_ERROR_FAILURE;
   1.206 +    return NS_OK;
   1.207 +}
   1.208 +
   1.209 +NS_IMETHODIMP
   1.210 +nsAuthSambaNTLM::Init(const char *serviceName,
   1.211 +                      uint32_t    serviceFlags,
   1.212 +                      const char16_t *domain,
   1.213 +                      const char16_t *username,
   1.214 +                      const char16_t *password)
   1.215 +{
   1.216 +    NS_ASSERTION(!username && !domain && !password, "unexpected credentials");
   1.217 +
   1.218 +    static bool sTelemetrySent = false;
   1.219 +    if (!sTelemetrySent) {
   1.220 +        mozilla::Telemetry::Accumulate(
   1.221 +            mozilla::Telemetry::NTLM_MODULE_USED_2,
   1.222 +            serviceFlags & nsIAuthModule::REQ_PROXY_AUTH
   1.223 +                ? NTLM_MODULE_SAMBA_AUTH_PROXY
   1.224 +                : NTLM_MODULE_SAMBA_AUTH_DIRECT);
   1.225 +        sTelemetrySent = true;
   1.226 +    }
   1.227 +
   1.228 +    return NS_OK;
   1.229 +}
   1.230 +
   1.231 +NS_IMETHODIMP
   1.232 +nsAuthSambaNTLM::GetNextToken(const void *inToken,
   1.233 +                              uint32_t    inTokenLen,
   1.234 +                              void      **outToken,
   1.235 +                              uint32_t   *outTokenLen)
   1.236 +{
   1.237 +    if (!inToken) {
   1.238 +        /* someone wants our initial message */
   1.239 +        *outToken = nsMemory::Clone(mInitialMessage, mInitialMessageLen);
   1.240 +        if (!*outToken)
   1.241 +            return NS_ERROR_OUT_OF_MEMORY;
   1.242 +        *outTokenLen = mInitialMessageLen;
   1.243 +        return NS_OK;
   1.244 +    }
   1.245 +
   1.246 +    /* inToken must be a type 2 message. Get ntlm_auth to generate our response */
   1.247 +    char* encoded = PL_Base64Encode(static_cast<const char*>(inToken), inTokenLen, nullptr);
   1.248 +    if (!encoded)
   1.249 +        return NS_ERROR_OUT_OF_MEMORY;
   1.250 +
   1.251 +    nsCString request;
   1.252 +    request.AssignLiteral("TT ");
   1.253 +    request.Append(encoded);
   1.254 +    free(encoded);
   1.255 +    request.Append('\n');
   1.256 +
   1.257 +    if (!WriteString(mToChildFD, request))
   1.258 +        return NS_ERROR_FAILURE;
   1.259 +    nsCString line;
   1.260 +    if (!ReadLine(mFromChildFD, line))
   1.261 +        return NS_ERROR_FAILURE;
   1.262 +    if (!StringBeginsWith(line, NS_LITERAL_CSTRING("KK "))) {
   1.263 +        // Something went wrong. Perhaps no credentials are accessible.
   1.264 +        return NS_ERROR_FAILURE;
   1.265 +    }
   1.266 +    uint8_t* buf = ExtractMessage(line, outTokenLen);
   1.267 +    if (!buf)
   1.268 +        return NS_ERROR_FAILURE;
   1.269 +    // *outToken has to be freed by nsMemory::Free, which may not be free() 
   1.270 +    *outToken = nsMemory::Clone(buf, *outTokenLen);
   1.271 +    if (!*outToken) {
   1.272 +        free(buf);
   1.273 +        return NS_ERROR_OUT_OF_MEMORY;
   1.274 +    }
   1.275 +    
   1.276 +    // We're done. Close our file descriptors now and reap the helper
   1.277 +    // process.
   1.278 +    Shutdown();
   1.279 +    return NS_SUCCESS_AUTH_FINISHED;
   1.280 +}
   1.281 +
   1.282 +NS_IMETHODIMP
   1.283 +nsAuthSambaNTLM::Unwrap(const void *inToken,
   1.284 +                        uint32_t    inTokenLen,
   1.285 +                        void      **outToken,
   1.286 +                        uint32_t   *outTokenLen)
   1.287 +{
   1.288 +    return NS_ERROR_NOT_IMPLEMENTED;
   1.289 +}
   1.290 +
   1.291 +NS_IMETHODIMP
   1.292 +nsAuthSambaNTLM::Wrap(const void *inToken,
   1.293 +                      uint32_t    inTokenLen,
   1.294 +                      bool        confidential,
   1.295 +                      void      **outToken,
   1.296 +                      uint32_t   *outTokenLen)
   1.297 +{
   1.298 +    return NS_ERROR_NOT_IMPLEMENTED;
   1.299 +}

mercurial