Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* vim:set ts=4 sw=4 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/. */
6 #include "nsAuth.h"
7 #include "nsAuthSambaNTLM.h"
8 #include "prenv.h"
9 #include "plbase64.h"
10 #include "prerror.h"
11 #include "mozilla/Telemetry.h"
13 #include <stdlib.h>
15 nsAuthSambaNTLM::nsAuthSambaNTLM()
16 : mInitialMessage(nullptr), mChildPID(nullptr), mFromChildFD(nullptr),
17 mToChildFD(nullptr)
18 {
19 }
21 nsAuthSambaNTLM::~nsAuthSambaNTLM()
22 {
23 // ntlm_auth reads from stdin regularly so closing our file handles
24 // should cause it to exit.
25 Shutdown();
26 free(mInitialMessage);
27 }
29 void
30 nsAuthSambaNTLM::Shutdown()
31 {
32 if (mFromChildFD) {
33 PR_Close(mFromChildFD);
34 mFromChildFD = nullptr;
35 }
36 if (mToChildFD) {
37 PR_Close(mToChildFD);
38 mToChildFD = nullptr;
39 }
40 if (mChildPID) {
41 int32_t exitCode;
42 PR_WaitProcess(mChildPID, &exitCode);
43 mChildPID = nullptr;
44 }
45 }
47 NS_IMPL_ISUPPORTS(nsAuthSambaNTLM, nsIAuthModule)
49 static bool
50 SpawnIOChild(char* const* aArgs, PRProcess** aPID,
51 PRFileDesc** aFromChildFD, PRFileDesc** aToChildFD)
52 {
53 PRFileDesc* toChildPipeRead;
54 PRFileDesc* toChildPipeWrite;
55 if (PR_CreatePipe(&toChildPipeRead, &toChildPipeWrite) != PR_SUCCESS)
56 return false;
57 PR_SetFDInheritable(toChildPipeRead, true);
58 PR_SetFDInheritable(toChildPipeWrite, false);
60 PRFileDesc* fromChildPipeRead;
61 PRFileDesc* fromChildPipeWrite;
62 if (PR_CreatePipe(&fromChildPipeRead, &fromChildPipeWrite) != PR_SUCCESS) {
63 PR_Close(toChildPipeRead);
64 PR_Close(toChildPipeWrite);
65 return false;
66 }
67 PR_SetFDInheritable(fromChildPipeRead, false);
68 PR_SetFDInheritable(fromChildPipeWrite, true);
70 PRProcessAttr* attr = PR_NewProcessAttr();
71 if (!attr) {
72 PR_Close(fromChildPipeRead);
73 PR_Close(fromChildPipeWrite);
74 PR_Close(toChildPipeRead);
75 PR_Close(toChildPipeWrite);
76 return false;
77 }
79 PR_ProcessAttrSetStdioRedirect(attr, PR_StandardInput, toChildPipeRead);
80 PR_ProcessAttrSetStdioRedirect(attr, PR_StandardOutput, fromChildPipeWrite);
82 PRProcess* process = PR_CreateProcess(aArgs[0], aArgs, nullptr, attr);
83 PR_DestroyProcessAttr(attr);
84 PR_Close(fromChildPipeWrite);
85 PR_Close(toChildPipeRead);
86 if (!process) {
87 LOG(("ntlm_auth exec failure [%d]", PR_GetError()));
88 PR_Close(fromChildPipeRead);
89 PR_Close(toChildPipeWrite);
90 return false;
91 }
93 *aPID = process;
94 *aFromChildFD = fromChildPipeRead;
95 *aToChildFD = toChildPipeWrite;
96 return true;
97 }
99 static bool WriteString(PRFileDesc* aFD, const nsACString& aString)
100 {
101 int32_t length = aString.Length();
102 const char* s = aString.BeginReading();
103 LOG(("Writing to ntlm_auth: %s", s));
105 while (length > 0) {
106 int result = PR_Write(aFD, s, length);
107 if (result <= 0)
108 return false;
109 s += result;
110 length -= result;
111 }
112 return true;
113 }
115 static bool ReadLine(PRFileDesc* aFD, nsACString& aString)
116 {
117 // ntlm_auth is defined to only send one line in response to each of our
118 // input lines. So this simple unbuffered strategy works as long as we
119 // read the response immediately after sending one request.
120 aString.Truncate();
121 for (;;) {
122 char buf[1024];
123 int result = PR_Read(aFD, buf, sizeof(buf));
124 if (result <= 0)
125 return false;
126 aString.Append(buf, result);
127 if (buf[result - 1] == '\n') {
128 LOG(("Read from ntlm_auth: %s", nsPromiseFlatCString(aString).get()));
129 return true;
130 }
131 }
132 }
134 /**
135 * Returns a heap-allocated array of PRUint8s, and stores the length in aLen.
136 * Returns nullptr if there's an error of any kind.
137 */
138 static uint8_t* ExtractMessage(const nsACString& aLine, uint32_t* aLen)
139 {
140 // ntlm_auth sends blobs to us as base64-encoded strings after the "xx "
141 // preamble on the response line.
142 int32_t length = aLine.Length();
143 // The caller should verify there is a valid "xx " prefix and the line
144 // is terminated with a \n
145 NS_ASSERTION(length >= 4, "Line too short...");
146 const char* line = aLine.BeginReading();
147 const char* s = line + 3;
148 length -= 4; // lose first 3 chars plus trailing \n
149 NS_ASSERTION(s[length] == '\n', "aLine not newline-terminated");
151 if (length & 3) {
152 // The base64 encoded block must be multiple of 4. If not, something
153 // screwed up.
154 NS_WARNING("Base64 encoded block should be a multiple of 4 chars");
155 return nullptr;
156 }
158 // Calculate the exact length. I wonder why there isn't a function for this
159 // in plbase64.
160 int32_t numEquals;
161 for (numEquals = 0; numEquals < length; ++numEquals) {
162 if (s[length - 1 - numEquals] != '=')
163 break;
164 }
165 *aLen = (length/4)*3 - numEquals;
166 return reinterpret_cast<uint8_t*>(PL_Base64Decode(s, length, nullptr));
167 }
169 nsresult
170 nsAuthSambaNTLM::SpawnNTLMAuthHelper()
171 {
172 const char* username = PR_GetEnv("USER");
173 if (!username)
174 return NS_ERROR_FAILURE;
176 const char* const args[] = {
177 "ntlm_auth",
178 "--helper-protocol", "ntlmssp-client-1",
179 "--use-cached-creds",
180 "--username", username,
181 nullptr
182 };
184 bool isOK = SpawnIOChild(const_cast<char* const*>(args), &mChildPID, &mFromChildFD, &mToChildFD);
185 if (!isOK)
186 return NS_ERROR_FAILURE;
188 if (!WriteString(mToChildFD, NS_LITERAL_CSTRING("YR\n")))
189 return NS_ERROR_FAILURE;
190 nsCString line;
191 if (!ReadLine(mFromChildFD, line))
192 return NS_ERROR_FAILURE;
193 if (!StringBeginsWith(line, NS_LITERAL_CSTRING("YR "))) {
194 // Something went wrong. Perhaps no credentials are accessible.
195 return NS_ERROR_FAILURE;
196 }
198 // It gave us an initial client-to-server request packet. Save that
199 // because we'll need it later.
200 mInitialMessage = ExtractMessage(line, &mInitialMessageLen);
201 if (!mInitialMessage)
202 return NS_ERROR_FAILURE;
203 return NS_OK;
204 }
206 NS_IMETHODIMP
207 nsAuthSambaNTLM::Init(const char *serviceName,
208 uint32_t serviceFlags,
209 const char16_t *domain,
210 const char16_t *username,
211 const char16_t *password)
212 {
213 NS_ASSERTION(!username && !domain && !password, "unexpected credentials");
215 static bool sTelemetrySent = false;
216 if (!sTelemetrySent) {
217 mozilla::Telemetry::Accumulate(
218 mozilla::Telemetry::NTLM_MODULE_USED_2,
219 serviceFlags & nsIAuthModule::REQ_PROXY_AUTH
220 ? NTLM_MODULE_SAMBA_AUTH_PROXY
221 : NTLM_MODULE_SAMBA_AUTH_DIRECT);
222 sTelemetrySent = true;
223 }
225 return NS_OK;
226 }
228 NS_IMETHODIMP
229 nsAuthSambaNTLM::GetNextToken(const void *inToken,
230 uint32_t inTokenLen,
231 void **outToken,
232 uint32_t *outTokenLen)
233 {
234 if (!inToken) {
235 /* someone wants our initial message */
236 *outToken = nsMemory::Clone(mInitialMessage, mInitialMessageLen);
237 if (!*outToken)
238 return NS_ERROR_OUT_OF_MEMORY;
239 *outTokenLen = mInitialMessageLen;
240 return NS_OK;
241 }
243 /* inToken must be a type 2 message. Get ntlm_auth to generate our response */
244 char* encoded = PL_Base64Encode(static_cast<const char*>(inToken), inTokenLen, nullptr);
245 if (!encoded)
246 return NS_ERROR_OUT_OF_MEMORY;
248 nsCString request;
249 request.AssignLiteral("TT ");
250 request.Append(encoded);
251 free(encoded);
252 request.Append('\n');
254 if (!WriteString(mToChildFD, request))
255 return NS_ERROR_FAILURE;
256 nsCString line;
257 if (!ReadLine(mFromChildFD, line))
258 return NS_ERROR_FAILURE;
259 if (!StringBeginsWith(line, NS_LITERAL_CSTRING("KK "))) {
260 // Something went wrong. Perhaps no credentials are accessible.
261 return NS_ERROR_FAILURE;
262 }
263 uint8_t* buf = ExtractMessage(line, outTokenLen);
264 if (!buf)
265 return NS_ERROR_FAILURE;
266 // *outToken has to be freed by nsMemory::Free, which may not be free()
267 *outToken = nsMemory::Clone(buf, *outTokenLen);
268 if (!*outToken) {
269 free(buf);
270 return NS_ERROR_OUT_OF_MEMORY;
271 }
273 // We're done. Close our file descriptors now and reap the helper
274 // process.
275 Shutdown();
276 return NS_SUCCESS_AUTH_FINISHED;
277 }
279 NS_IMETHODIMP
280 nsAuthSambaNTLM::Unwrap(const void *inToken,
281 uint32_t inTokenLen,
282 void **outToken,
283 uint32_t *outTokenLen)
284 {
285 return NS_ERROR_NOT_IMPLEMENTED;
286 }
288 NS_IMETHODIMP
289 nsAuthSambaNTLM::Wrap(const void *inToken,
290 uint32_t inTokenLen,
291 bool confidential,
292 void **outToken,
293 uint32_t *outTokenLen)
294 {
295 return NS_ERROR_NOT_IMPLEMENTED;
296 }