Line data Source code
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 0 : nsAuthSambaNTLM::nsAuthSambaNTLM()
16 : : mInitialMessage(nullptr), mChildPID(nullptr), mFromChildFD(nullptr),
17 0 : mToChildFD(nullptr)
18 : {
19 0 : }
20 :
21 0 : nsAuthSambaNTLM::~nsAuthSambaNTLM()
22 : {
23 : // ntlm_auth reads from stdin regularly so closing our file handles
24 : // should cause it to exit.
25 0 : Shutdown();
26 0 : free(mInitialMessage);
27 0 : }
28 :
29 : void
30 0 : nsAuthSambaNTLM::Shutdown()
31 : {
32 0 : if (mFromChildFD) {
33 0 : PR_Close(mFromChildFD);
34 0 : mFromChildFD = nullptr;
35 : }
36 0 : if (mToChildFD) {
37 0 : PR_Close(mToChildFD);
38 0 : mToChildFD = nullptr;
39 : }
40 0 : if (mChildPID) {
41 : int32_t exitCode;
42 0 : PR_WaitProcess(mChildPID, &exitCode);
43 0 : mChildPID = nullptr;
44 : }
45 0 : }
46 :
47 0 : NS_IMPL_ISUPPORTS(nsAuthSambaNTLM, nsIAuthModule)
48 :
49 : static bool
50 0 : SpawnIOChild(char* const* aArgs, PRProcess** aPID,
51 : PRFileDesc** aFromChildFD, PRFileDesc** aToChildFD)
52 : {
53 : PRFileDesc* toChildPipeRead;
54 : PRFileDesc* toChildPipeWrite;
55 0 : if (PR_CreatePipe(&toChildPipeRead, &toChildPipeWrite) != PR_SUCCESS)
56 0 : return false;
57 0 : PR_SetFDInheritable(toChildPipeRead, true);
58 0 : PR_SetFDInheritable(toChildPipeWrite, false);
59 :
60 : PRFileDesc* fromChildPipeRead;
61 : PRFileDesc* fromChildPipeWrite;
62 0 : if (PR_CreatePipe(&fromChildPipeRead, &fromChildPipeWrite) != PR_SUCCESS) {
63 0 : PR_Close(toChildPipeRead);
64 0 : PR_Close(toChildPipeWrite);
65 0 : return false;
66 : }
67 0 : PR_SetFDInheritable(fromChildPipeRead, false);
68 0 : PR_SetFDInheritable(fromChildPipeWrite, true);
69 :
70 0 : PRProcessAttr* attr = PR_NewProcessAttr();
71 0 : if (!attr) {
72 0 : PR_Close(fromChildPipeRead);
73 0 : PR_Close(fromChildPipeWrite);
74 0 : PR_Close(toChildPipeRead);
75 0 : PR_Close(toChildPipeWrite);
76 0 : return false;
77 : }
78 :
79 0 : PR_ProcessAttrSetStdioRedirect(attr, PR_StandardInput, toChildPipeRead);
80 0 : PR_ProcessAttrSetStdioRedirect(attr, PR_StandardOutput, fromChildPipeWrite);
81 :
82 0 : PRProcess* process = PR_CreateProcess(aArgs[0], aArgs, nullptr, attr);
83 0 : PR_DestroyProcessAttr(attr);
84 0 : PR_Close(fromChildPipeWrite);
85 0 : PR_Close(toChildPipeRead);
86 0 : if (!process) {
87 0 : LOG(("ntlm_auth exec failure [%d]", PR_GetError()));
88 0 : PR_Close(fromChildPipeRead);
89 0 : PR_Close(toChildPipeWrite);
90 0 : return false;
91 : }
92 :
93 0 : *aPID = process;
94 0 : *aFromChildFD = fromChildPipeRead;
95 0 : *aToChildFD = toChildPipeWrite;
96 0 : return true;
97 : }
98 :
99 0 : static bool WriteString(PRFileDesc* aFD, const nsACString& aString)
100 : {
101 0 : int32_t length = aString.Length();
102 0 : const char* s = aString.BeginReading();
103 0 : LOG(("Writing to ntlm_auth: %s", s));
104 :
105 0 : while (length > 0) {
106 0 : int result = PR_Write(aFD, s, length);
107 0 : if (result <= 0)
108 0 : return false;
109 0 : s += result;
110 0 : length -= result;
111 : }
112 0 : return true;
113 : }
114 :
115 0 : 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 0 : aString.Truncate();
121 : for (;;) {
122 : char buf[1024];
123 0 : int result = PR_Read(aFD, buf, sizeof(buf));
124 0 : if (result <= 0)
125 0 : return false;
126 0 : aString.Append(buf, result);
127 0 : if (buf[result - 1] == '\n') {
128 0 : LOG(("Read from ntlm_auth: %s", nsPromiseFlatCString(aString).get()));
129 0 : return true;
130 : }
131 0 : }
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 0 : 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 0 : 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 0 : NS_ASSERTION(length >= 4, "Line too short...");
146 0 : const char* line = aLine.BeginReading();
147 0 : const char* s = line + 3;
148 0 : length -= 4; // lose first 3 chars plus trailing \n
149 0 : NS_ASSERTION(s[length] == '\n', "aLine not newline-terminated");
150 :
151 0 : if (length & 3) {
152 : // The base64 encoded block must be multiple of 4. If not, something
153 : // screwed up.
154 0 : NS_WARNING("Base64 encoded block should be a multiple of 4 chars");
155 0 : 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 0 : for (numEquals = 0; numEquals < length; ++numEquals) {
162 0 : if (s[length - 1 - numEquals] != '=')
163 0 : break;
164 : }
165 0 : *aLen = (length/4)*3 - numEquals;
166 0 : return reinterpret_cast<uint8_t*>(PL_Base64Decode(s, length, nullptr));
167 : }
168 :
169 : nsresult
170 0 : nsAuthSambaNTLM::SpawnNTLMAuthHelper()
171 : {
172 0 : const char* username = PR_GetEnv("USER");
173 0 : if (!username)
174 0 : 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 0 : };
183 :
184 0 : bool isOK = SpawnIOChild(const_cast<char* const*>(args), &mChildPID, &mFromChildFD, &mToChildFD);
185 0 : if (!isOK)
186 0 : return NS_ERROR_FAILURE;
187 :
188 0 : if (!WriteString(mToChildFD, NS_LITERAL_CSTRING("YR\n")))
189 0 : return NS_ERROR_FAILURE;
190 0 : nsCString line;
191 0 : if (!ReadLine(mFromChildFD, line))
192 0 : return NS_ERROR_FAILURE;
193 0 : if (!StringBeginsWith(line, NS_LITERAL_CSTRING("YR "))) {
194 : // Something went wrong. Perhaps no credentials are accessible.
195 0 : 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 0 : mInitialMessage = ExtractMessage(line, &mInitialMessageLen);
201 0 : if (!mInitialMessage)
202 0 : return NS_ERROR_FAILURE;
203 0 : return NS_OK;
204 : }
205 :
206 : NS_IMETHODIMP
207 0 : 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 0 : NS_ASSERTION(!username && !domain && !password, "unexpected credentials");
214 :
215 : static bool sTelemetrySent = false;
216 0 : if (!sTelemetrySent) {
217 0 : mozilla::Telemetry::Accumulate(
218 : mozilla::Telemetry::NTLM_MODULE_USED_2,
219 0 : serviceFlags & nsIAuthModule::REQ_PROXY_AUTH
220 : ? NTLM_MODULE_SAMBA_AUTH_PROXY
221 0 : : NTLM_MODULE_SAMBA_AUTH_DIRECT);
222 0 : sTelemetrySent = true;
223 : }
224 :
225 0 : return NS_OK;
226 : }
227 :
228 : NS_IMETHODIMP
229 0 : nsAuthSambaNTLM::GetNextToken(const void *inToken,
230 : uint32_t inTokenLen,
231 : void **outToken,
232 : uint32_t *outTokenLen)
233 : {
234 0 : if (!inToken) {
235 : /* someone wants our initial message */
236 0 : *outToken = nsMemory::Clone(mInitialMessage, mInitialMessageLen);
237 0 : if (!*outToken)
238 0 : return NS_ERROR_OUT_OF_MEMORY;
239 0 : *outTokenLen = mInitialMessageLen;
240 0 : return NS_OK;
241 : }
242 :
243 : /* 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 :
248 0 : nsCString request;
249 0 : request.AssignLiteral("TT ");
250 0 : request.Append(encoded);
251 0 : free(encoded);
252 0 : request.Append('\n');
253 :
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 : // Something went wrong. Perhaps no credentials are accessible.
262 0 : return NS_ERROR_FAILURE;
263 : }
264 0 : uint8_t* buf = ExtractMessage(line, outTokenLen);
265 0 : if (!buf)
266 0 : return NS_ERROR_FAILURE;
267 0 : *outToken = nsMemory::Clone(buf, *outTokenLen);
268 0 : free(buf);
269 0 : if (!*outToken) {
270 0 : return NS_ERROR_OUT_OF_MEMORY;
271 : }
272 :
273 : // We're done. Close our file descriptors now and reap the helper
274 : // process.
275 0 : Shutdown();
276 0 : return NS_SUCCESS_AUTH_FINISHED;
277 : }
278 :
279 : NS_IMETHODIMP
280 0 : nsAuthSambaNTLM::Unwrap(const void *inToken,
281 : uint32_t inTokenLen,
282 : void **outToken,
283 : uint32_t *outTokenLen)
284 : {
285 0 : return NS_ERROR_NOT_IMPLEMENTED;
286 : }
287 :
288 : NS_IMETHODIMP
289 0 : nsAuthSambaNTLM::Wrap(const void *inToken,
290 : uint32_t inTokenLen,
291 : bool confidential,
292 : void **outToken,
293 : uint32_t *outTokenLen)
294 : {
295 0 : return NS_ERROR_NOT_IMPLEMENTED;
296 : }
|