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