|
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/. */ |
|
5 |
|
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" |
|
12 |
|
13 #include <stdlib.h> |
|
14 |
|
15 nsAuthSambaNTLM::nsAuthSambaNTLM() |
|
16 : mInitialMessage(nullptr), mChildPID(nullptr), mFromChildFD(nullptr), |
|
17 mToChildFD(nullptr) |
|
18 { |
|
19 } |
|
20 |
|
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 } |
|
28 |
|
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 } |
|
46 |
|
47 NS_IMPL_ISUPPORTS(nsAuthSambaNTLM, nsIAuthModule) |
|
48 |
|
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); |
|
59 |
|
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); |
|
69 |
|
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 } |
|
78 |
|
79 PR_ProcessAttrSetStdioRedirect(attr, PR_StandardInput, toChildPipeRead); |
|
80 PR_ProcessAttrSetStdioRedirect(attr, PR_StandardOutput, fromChildPipeWrite); |
|
81 |
|
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 } |
|
92 |
|
93 *aPID = process; |
|
94 *aFromChildFD = fromChildPipeRead; |
|
95 *aToChildFD = toChildPipeWrite; |
|
96 return true; |
|
97 } |
|
98 |
|
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)); |
|
104 |
|
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 } |
|
114 |
|
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 } |
|
133 |
|
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"); |
|
150 |
|
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 } |
|
157 |
|
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 } |
|
168 |
|
169 nsresult |
|
170 nsAuthSambaNTLM::SpawnNTLMAuthHelper() |
|
171 { |
|
172 const char* username = PR_GetEnv("USER"); |
|
173 if (!username) |
|
174 return NS_ERROR_FAILURE; |
|
175 |
|
176 const char* const args[] = { |
|
177 "ntlm_auth", |
|
178 "--helper-protocol", "ntlmssp-client-1", |
|
179 "--use-cached-creds", |
|
180 "--username", username, |
|
181 nullptr |
|
182 }; |
|
183 |
|
184 bool isOK = SpawnIOChild(const_cast<char* const*>(args), &mChildPID, &mFromChildFD, &mToChildFD); |
|
185 if (!isOK) |
|
186 return NS_ERROR_FAILURE; |
|
187 |
|
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 } |
|
197 |
|
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 } |
|
205 |
|
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"); |
|
214 |
|
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 } |
|
224 |
|
225 return NS_OK; |
|
226 } |
|
227 |
|
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 } |
|
242 |
|
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; |
|
247 |
|
248 nsCString request; |
|
249 request.AssignLiteral("TT "); |
|
250 request.Append(encoded); |
|
251 free(encoded); |
|
252 request.Append('\n'); |
|
253 |
|
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 } |
|
272 |
|
273 // We're done. Close our file descriptors now and reap the helper |
|
274 // process. |
|
275 Shutdown(); |
|
276 return NS_SUCCESS_AUTH_FINISHED; |
|
277 } |
|
278 |
|
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 } |
|
287 |
|
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 } |