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/. */
7 #include "nsAuthSambaNTLM.h"
12 #include "mozilla/Telemetry.h"
16 nsAuthSambaNTLM::nsAuthSambaNTLM()
17 : mInitialMessage(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.
26 PR_Free(mInitialMessage
);
29 void nsAuthSambaNTLM::Shutdown() {
31 PR_Close(mFromChildFD
);
32 mFromChildFD
= nullptr;
39 PR_KillProcess(mChildPID
);
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
) {
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
);
63 PR_SetFDInheritable(fromChildPipeRead
, false);
64 PR_SetFDInheritable(fromChildPipeWrite
, true);
66 PRProcessAttr
* attr
= PR_NewProcessAttr();
68 PR_Close(fromChildPipeRead
);
69 PR_Close(fromChildPipeWrite
);
70 PR_Close(toChildPipeRead
);
71 PR_Close(toChildPipeWrite
);
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
);
83 LOG(("ntlm_auth exec failure [%d]", PR_GetError()));
84 PR_Close(fromChildPipeRead
);
85 PR_Close(toChildPipeWrite
);
90 *aFromChildFD
= fromChildPipeRead
;
91 *aToChildFD
= toChildPipeWrite
;
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
));
101 int result
= PR_Write(aFD
, s
, length
);
102 if (result
<= 0) return false;
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.
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()));
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");
143 // The base64 encoded block must be multiple of 4. If not, something
145 NS_WARNING("Base64 encoded block should be a multiple of 4 chars");
149 // Calculate the exact length. I wonder why there isn't a function for this
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",
166 "--use-cached-creds",
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
;
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
;
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;
210 nsAuthSambaNTLM::GetNextToken(const void* inToken
, uint32_t inTokenLen
,
211 void** outToken
, uint32_t* outTokenLen
) {
213 /* someone wants our initial message */
214 *outToken
= moz_xmemdup(mInitialMessage
, mInitialMessageLen
);
215 *outTokenLen
= mInitialMessageLen
;
219 /* inToken must be a type 2 message. Get ntlm_auth to generate our response */
221 PL_Base64Encode(static_cast<const char*>(inToken
), inTokenLen
, nullptr);
222 if (!encoded
) return NS_ERROR_OUT_OF_MEMORY
;
225 request
.AssignLiteral("TT ");
226 request
.Append(encoded
);
228 request
.Append('\n');
230 if (!WriteString(mToChildFD
, request
)) return NS_ERROR_FAILURE
;
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
);
242 // We're done. Close our file descriptors now and reap the helper
245 return NS_SUCCESS_AUTH_FINISHED
;
249 nsAuthSambaNTLM::Unwrap(const void* inToken
, uint32_t inTokenLen
,
250 void** outToken
, uint32_t* outTokenLen
) {
251 return NS_ERROR_NOT_IMPLEMENTED
;
255 nsAuthSambaNTLM::Wrap(const void* inToken
, uint32_t inTokenLen
,
256 bool confidential
, void** outToken
,
257 uint32_t* outTokenLen
) {
258 return NS_ERROR_NOT_IMPLEMENTED
;