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 +}