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