michael@0: /* vim:set ts=4 sw=4 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsAuth.h" michael@0: #include "nsAuthSambaNTLM.h" michael@0: #include "prenv.h" michael@0: #include "plbase64.h" michael@0: #include "prerror.h" michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: #include michael@0: michael@0: nsAuthSambaNTLM::nsAuthSambaNTLM() michael@0: : mInitialMessage(nullptr), mChildPID(nullptr), mFromChildFD(nullptr), michael@0: mToChildFD(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsAuthSambaNTLM::~nsAuthSambaNTLM() michael@0: { michael@0: // ntlm_auth reads from stdin regularly so closing our file handles michael@0: // should cause it to exit. michael@0: Shutdown(); michael@0: free(mInitialMessage); michael@0: } michael@0: michael@0: void michael@0: nsAuthSambaNTLM::Shutdown() michael@0: { michael@0: if (mFromChildFD) { michael@0: PR_Close(mFromChildFD); michael@0: mFromChildFD = nullptr; michael@0: } michael@0: if (mToChildFD) { michael@0: PR_Close(mToChildFD); michael@0: mToChildFD = nullptr; michael@0: } michael@0: if (mChildPID) { michael@0: int32_t exitCode; michael@0: PR_WaitProcess(mChildPID, &exitCode); michael@0: mChildPID = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsAuthSambaNTLM, nsIAuthModule) michael@0: michael@0: static bool michael@0: SpawnIOChild(char* const* aArgs, PRProcess** aPID, michael@0: PRFileDesc** aFromChildFD, PRFileDesc** aToChildFD) michael@0: { michael@0: PRFileDesc* toChildPipeRead; michael@0: PRFileDesc* toChildPipeWrite; michael@0: if (PR_CreatePipe(&toChildPipeRead, &toChildPipeWrite) != PR_SUCCESS) michael@0: return false; michael@0: PR_SetFDInheritable(toChildPipeRead, true); michael@0: PR_SetFDInheritable(toChildPipeWrite, false); michael@0: michael@0: PRFileDesc* fromChildPipeRead; michael@0: PRFileDesc* fromChildPipeWrite; michael@0: if (PR_CreatePipe(&fromChildPipeRead, &fromChildPipeWrite) != PR_SUCCESS) { michael@0: PR_Close(toChildPipeRead); michael@0: PR_Close(toChildPipeWrite); michael@0: return false; michael@0: } michael@0: PR_SetFDInheritable(fromChildPipeRead, false); michael@0: PR_SetFDInheritable(fromChildPipeWrite, true); michael@0: michael@0: PRProcessAttr* attr = PR_NewProcessAttr(); michael@0: if (!attr) { michael@0: PR_Close(fromChildPipeRead); michael@0: PR_Close(fromChildPipeWrite); michael@0: PR_Close(toChildPipeRead); michael@0: PR_Close(toChildPipeWrite); michael@0: return false; michael@0: } michael@0: michael@0: PR_ProcessAttrSetStdioRedirect(attr, PR_StandardInput, toChildPipeRead); michael@0: PR_ProcessAttrSetStdioRedirect(attr, PR_StandardOutput, fromChildPipeWrite); michael@0: michael@0: PRProcess* process = PR_CreateProcess(aArgs[0], aArgs, nullptr, attr); michael@0: PR_DestroyProcessAttr(attr); michael@0: PR_Close(fromChildPipeWrite); michael@0: PR_Close(toChildPipeRead); michael@0: if (!process) { michael@0: LOG(("ntlm_auth exec failure [%d]", PR_GetError())); michael@0: PR_Close(fromChildPipeRead); michael@0: PR_Close(toChildPipeWrite); michael@0: return false; michael@0: } michael@0: michael@0: *aPID = process; michael@0: *aFromChildFD = fromChildPipeRead; michael@0: *aToChildFD = toChildPipeWrite; michael@0: return true; michael@0: } michael@0: michael@0: static bool WriteString(PRFileDesc* aFD, const nsACString& aString) michael@0: { michael@0: int32_t length = aString.Length(); michael@0: const char* s = aString.BeginReading(); michael@0: LOG(("Writing to ntlm_auth: %s", s)); michael@0: michael@0: while (length > 0) { michael@0: int result = PR_Write(aFD, s, length); michael@0: if (result <= 0) michael@0: return false; michael@0: s += result; michael@0: length -= result; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool ReadLine(PRFileDesc* aFD, nsACString& aString) michael@0: { michael@0: // ntlm_auth is defined to only send one line in response to each of our michael@0: // input lines. So this simple unbuffered strategy works as long as we michael@0: // read the response immediately after sending one request. michael@0: aString.Truncate(); michael@0: for (;;) { michael@0: char buf[1024]; michael@0: int result = PR_Read(aFD, buf, sizeof(buf)); michael@0: if (result <= 0) michael@0: return false; michael@0: aString.Append(buf, result); michael@0: if (buf[result - 1] == '\n') { michael@0: LOG(("Read from ntlm_auth: %s", nsPromiseFlatCString(aString).get())); michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Returns a heap-allocated array of PRUint8s, and stores the length in aLen. michael@0: * Returns nullptr if there's an error of any kind. michael@0: */ michael@0: static uint8_t* ExtractMessage(const nsACString& aLine, uint32_t* aLen) michael@0: { michael@0: // ntlm_auth sends blobs to us as base64-encoded strings after the "xx " michael@0: // preamble on the response line. michael@0: int32_t length = aLine.Length(); michael@0: // The caller should verify there is a valid "xx " prefix and the line michael@0: // is terminated with a \n michael@0: NS_ASSERTION(length >= 4, "Line too short..."); michael@0: const char* line = aLine.BeginReading(); michael@0: const char* s = line + 3; michael@0: length -= 4; // lose first 3 chars plus trailing \n michael@0: NS_ASSERTION(s[length] == '\n', "aLine not newline-terminated"); michael@0: michael@0: if (length & 3) { michael@0: // The base64 encoded block must be multiple of 4. If not, something michael@0: // screwed up. michael@0: NS_WARNING("Base64 encoded block should be a multiple of 4 chars"); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Calculate the exact length. I wonder why there isn't a function for this michael@0: // in plbase64. michael@0: int32_t numEquals; michael@0: for (numEquals = 0; numEquals < length; ++numEquals) { michael@0: if (s[length - 1 - numEquals] != '=') michael@0: break; michael@0: } michael@0: *aLen = (length/4)*3 - numEquals; michael@0: return reinterpret_cast(PL_Base64Decode(s, length, nullptr)); michael@0: } michael@0: michael@0: nsresult michael@0: nsAuthSambaNTLM::SpawnNTLMAuthHelper() michael@0: { michael@0: const char* username = PR_GetEnv("USER"); michael@0: if (!username) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: const char* const args[] = { michael@0: "ntlm_auth", michael@0: "--helper-protocol", "ntlmssp-client-1", michael@0: "--use-cached-creds", michael@0: "--username", username, michael@0: nullptr michael@0: }; michael@0: michael@0: bool isOK = SpawnIOChild(const_cast(args), &mChildPID, &mFromChildFD, &mToChildFD); michael@0: if (!isOK) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (!WriteString(mToChildFD, NS_LITERAL_CSTRING("YR\n"))) michael@0: return NS_ERROR_FAILURE; michael@0: nsCString line; michael@0: if (!ReadLine(mFromChildFD, line)) michael@0: return NS_ERROR_FAILURE; michael@0: if (!StringBeginsWith(line, NS_LITERAL_CSTRING("YR "))) { michael@0: // Something went wrong. Perhaps no credentials are accessible. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // It gave us an initial client-to-server request packet. Save that michael@0: // because we'll need it later. michael@0: mInitialMessage = ExtractMessage(line, &mInitialMessageLen); michael@0: if (!mInitialMessage) michael@0: return NS_ERROR_FAILURE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAuthSambaNTLM::Init(const char *serviceName, michael@0: uint32_t serviceFlags, michael@0: const char16_t *domain, michael@0: const char16_t *username, michael@0: const char16_t *password) michael@0: { michael@0: NS_ASSERTION(!username && !domain && !password, "unexpected credentials"); michael@0: michael@0: static bool sTelemetrySent = false; michael@0: if (!sTelemetrySent) { michael@0: mozilla::Telemetry::Accumulate( michael@0: mozilla::Telemetry::NTLM_MODULE_USED_2, michael@0: serviceFlags & nsIAuthModule::REQ_PROXY_AUTH michael@0: ? NTLM_MODULE_SAMBA_AUTH_PROXY michael@0: : NTLM_MODULE_SAMBA_AUTH_DIRECT); michael@0: sTelemetrySent = true; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAuthSambaNTLM::GetNextToken(const void *inToken, michael@0: uint32_t inTokenLen, michael@0: void **outToken, michael@0: uint32_t *outTokenLen) michael@0: { michael@0: if (!inToken) { michael@0: /* someone wants our initial message */ michael@0: *outToken = nsMemory::Clone(mInitialMessage, mInitialMessageLen); michael@0: if (!*outToken) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: *outTokenLen = mInitialMessageLen; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* inToken must be a type 2 message. Get ntlm_auth to generate our response */ michael@0: char* encoded = PL_Base64Encode(static_cast(inToken), inTokenLen, nullptr); michael@0: if (!encoded) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: nsCString request; michael@0: request.AssignLiteral("TT "); michael@0: request.Append(encoded); michael@0: free(encoded); michael@0: request.Append('\n'); michael@0: michael@0: if (!WriteString(mToChildFD, request)) michael@0: return NS_ERROR_FAILURE; michael@0: nsCString line; michael@0: if (!ReadLine(mFromChildFD, line)) michael@0: return NS_ERROR_FAILURE; michael@0: if (!StringBeginsWith(line, NS_LITERAL_CSTRING("KK "))) { michael@0: // Something went wrong. Perhaps no credentials are accessible. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: uint8_t* buf = ExtractMessage(line, outTokenLen); michael@0: if (!buf) michael@0: return NS_ERROR_FAILURE; michael@0: // *outToken has to be freed by nsMemory::Free, which may not be free() michael@0: *outToken = nsMemory::Clone(buf, *outTokenLen); michael@0: if (!*outToken) { michael@0: free(buf); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: // We're done. Close our file descriptors now and reap the helper michael@0: // process. michael@0: Shutdown(); michael@0: return NS_SUCCESS_AUTH_FINISHED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAuthSambaNTLM::Unwrap(const void *inToken, michael@0: uint32_t inTokenLen, michael@0: void **outToken, michael@0: uint32_t *outTokenLen) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAuthSambaNTLM::Wrap(const void *inToken, michael@0: uint32_t inTokenLen, michael@0: bool confidential, michael@0: void **outToken, michael@0: uint32_t *outTokenLen) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: }