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