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