1 : /* vim:set ts=4 sw=4 et cindent: */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Samba NTLM Authentication.
16 : *
17 : * The Initial Developer of the Original Code is Novell.
18 : * Portions created by the Initial Developer are Copyright (C) 2005
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Robert O'Callahan (rocallahan@novell.com)
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either the GNU General Public License Version 2 or later (the "GPL"), or
26 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsAuth.h"
39 : #include "nsAuthSambaNTLM.h"
40 : #include "prenv.h"
41 : #include "plbase64.h"
42 : #include "prerror.h"
43 :
44 : #include <stdlib.h>
45 :
46 0 : nsAuthSambaNTLM::nsAuthSambaNTLM()
47 : : mInitialMessage(nsnull), mChildPID(nsnull), mFromChildFD(nsnull),
48 0 : mToChildFD(nsnull)
49 : {
50 0 : }
51 :
52 0 : nsAuthSambaNTLM::~nsAuthSambaNTLM()
53 : {
54 : // ntlm_auth reads from stdin regularly so closing our file handles
55 : // should cause it to exit.
56 0 : Shutdown();
57 0 : free(mInitialMessage);
58 0 : }
59 :
60 : void
61 0 : nsAuthSambaNTLM::Shutdown()
62 : {
63 0 : if (mFromChildFD) {
64 0 : PR_Close(mFromChildFD);
65 0 : mFromChildFD = nsnull;
66 : }
67 0 : if (mToChildFD) {
68 0 : PR_Close(mToChildFD);
69 0 : mToChildFD = nsnull;
70 : }
71 0 : if (mChildPID) {
72 : PRInt32 exitCode;
73 0 : PR_WaitProcess(mChildPID, &exitCode);
74 0 : mChildPID = nsnull;
75 : }
76 0 : }
77 :
78 0 : NS_IMPL_ISUPPORTS1(nsAuthSambaNTLM, nsIAuthModule)
79 :
80 : static bool
81 0 : SpawnIOChild(char** aArgs, PRProcess** aPID,
82 : PRFileDesc** aFromChildFD, PRFileDesc** aToChildFD)
83 : {
84 : PRFileDesc* toChildPipeRead;
85 : PRFileDesc* toChildPipeWrite;
86 0 : if (PR_CreatePipe(&toChildPipeRead, &toChildPipeWrite) != PR_SUCCESS)
87 0 : return false;
88 0 : PR_SetFDInheritable(toChildPipeRead, true);
89 0 : PR_SetFDInheritable(toChildPipeWrite, false);
90 :
91 : PRFileDesc* fromChildPipeRead;
92 : PRFileDesc* fromChildPipeWrite;
93 0 : if (PR_CreatePipe(&fromChildPipeRead, &fromChildPipeWrite) != PR_SUCCESS) {
94 0 : PR_Close(toChildPipeRead);
95 0 : PR_Close(toChildPipeWrite);
96 0 : return false;
97 : }
98 0 : PR_SetFDInheritable(fromChildPipeRead, false);
99 0 : PR_SetFDInheritable(fromChildPipeWrite, true);
100 :
101 0 : PRProcessAttr* attr = PR_NewProcessAttr();
102 0 : if (!attr) {
103 0 : PR_Close(fromChildPipeRead);
104 0 : PR_Close(fromChildPipeWrite);
105 0 : PR_Close(toChildPipeRead);
106 0 : PR_Close(toChildPipeWrite);
107 0 : return false;
108 : }
109 :
110 0 : PR_ProcessAttrSetStdioRedirect(attr, PR_StandardInput, toChildPipeRead);
111 0 : PR_ProcessAttrSetStdioRedirect(attr, PR_StandardOutput, fromChildPipeWrite);
112 :
113 0 : PRProcess* process = PR_CreateProcess(aArgs[0], aArgs, nsnull, attr);
114 0 : PR_DestroyProcessAttr(attr);
115 0 : PR_Close(fromChildPipeWrite);
116 0 : PR_Close(toChildPipeRead);
117 0 : if (!process) {
118 0 : LOG(("ntlm_auth exec failure [%d]", PR_GetError()));
119 0 : PR_Close(fromChildPipeRead);
120 0 : PR_Close(toChildPipeWrite);
121 0 : return false;
122 : }
123 :
124 0 : *aPID = process;
125 0 : *aFromChildFD = fromChildPipeRead;
126 0 : *aToChildFD = toChildPipeWrite;
127 0 : return true;
128 : }
129 :
130 0 : static bool WriteString(PRFileDesc* aFD, const nsACString& aString)
131 : {
132 0 : PRInt32 length = aString.Length();
133 0 : const char* s = aString.BeginReading();
134 0 : LOG(("Writing to ntlm_auth: %s", s));
135 :
136 0 : while (length > 0) {
137 0 : int result = PR_Write(aFD, s, length);
138 0 : if (result <= 0)
139 0 : return false;
140 0 : s += result;
141 0 : length -= result;
142 : }
143 0 : return true;
144 : }
145 :
146 0 : static bool ReadLine(PRFileDesc* aFD, nsACString& aString)
147 : {
148 : // ntlm_auth is defined to only send one line in response to each of our
149 : // input lines. So this simple unbuffered strategy works as long as we
150 : // read the response immediately after sending one request.
151 0 : aString.Truncate();
152 0 : for (;;) {
153 : char buf[1024];
154 0 : int result = PR_Read(aFD, buf, sizeof(buf));
155 0 : if (result <= 0)
156 0 : return false;
157 0 : aString.Append(buf, result);
158 0 : if (buf[result - 1] == '\n') {
159 0 : LOG(("Read from ntlm_auth: %s", nsPromiseFlatCString(aString).get()));
160 0 : return true;
161 : }
162 : }
163 : }
164 :
165 : /**
166 : * Returns a heap-allocated array of PRUint8s, and stores the length in aLen.
167 : * Returns nsnull if there's an error of any kind.
168 : */
169 0 : static PRUint8* ExtractMessage(const nsACString& aLine, PRUint32* aLen)
170 : {
171 : // ntlm_auth sends blobs to us as base64-encoded strings after the "xx "
172 : // preamble on the response line.
173 0 : PRInt32 length = aLine.Length();
174 : // The caller should verify there is a valid "xx " prefix and the line
175 : // is terminated with a \n
176 0 : NS_ASSERTION(length >= 4, "Line too short...");
177 0 : const char* line = aLine.BeginReading();
178 0 : const char* s = line + 3;
179 0 : length -= 4; // lose first 3 chars plus trailing \n
180 0 : NS_ASSERTION(s[length] == '\n', "aLine not newline-terminated");
181 :
182 0 : if (length & 3) {
183 : // The base64 encoded block must be multiple of 4. If not, something
184 : // screwed up.
185 0 : NS_WARNING("Base64 encoded block should be a multiple of 4 chars");
186 0 : return nsnull;
187 : }
188 :
189 : // Calculate the exact length. I wonder why there isn't a function for this
190 : // in plbase64.
191 : PRInt32 numEquals;
192 0 : for (numEquals = 0; numEquals < length; ++numEquals) {
193 0 : if (s[length - 1 - numEquals] != '=')
194 0 : break;
195 : }
196 0 : *aLen = (length/4)*3 - numEquals;
197 0 : return reinterpret_cast<PRUint8*>(PL_Base64Decode(s, length, nsnull));
198 : }
199 :
200 : nsresult
201 0 : nsAuthSambaNTLM::SpawnNTLMAuthHelper()
202 : {
203 0 : const char* username = PR_GetEnv("USER");
204 0 : if (!username)
205 0 : return NS_ERROR_FAILURE;
206 :
207 : char* args[] = {
208 : "ntlm_auth",
209 : "--helper-protocol", "ntlmssp-client-1",
210 : "--use-cached-creds",
211 : "--username", const_cast<char*>(username),
212 : nsnull
213 0 : };
214 :
215 0 : bool isOK = SpawnIOChild(args, &mChildPID, &mFromChildFD, &mToChildFD);
216 0 : if (!isOK)
217 0 : return NS_ERROR_FAILURE;
218 :
219 0 : if (!WriteString(mToChildFD, NS_LITERAL_CSTRING("YR\n")))
220 0 : return NS_ERROR_FAILURE;
221 0 : nsCString line;
222 0 : if (!ReadLine(mFromChildFD, line))
223 0 : return NS_ERROR_FAILURE;
224 0 : if (!StringBeginsWith(line, NS_LITERAL_CSTRING("YR "))) {
225 : // Something went wrong. Perhaps no credentials are accessible.
226 0 : return NS_ERROR_FAILURE;
227 : }
228 :
229 : // It gave us an initial client-to-server request packet. Save that
230 : // because we'll need it later.
231 0 : mInitialMessage = ExtractMessage(line, &mInitialMessageLen);
232 0 : if (!mInitialMessage)
233 0 : return NS_ERROR_FAILURE;
234 0 : return NS_OK;
235 : }
236 :
237 : NS_IMETHODIMP
238 0 : nsAuthSambaNTLM::Init(const char *serviceName,
239 : PRUint32 serviceFlags,
240 : const PRUnichar *domain,
241 : const PRUnichar *username,
242 : const PRUnichar *password)
243 : {
244 0 : NS_ASSERTION(!username && !domain && !password, "unexpected credentials");
245 0 : return NS_OK;
246 : }
247 :
248 : NS_IMETHODIMP
249 0 : nsAuthSambaNTLM::GetNextToken(const void *inToken,
250 : PRUint32 inTokenLen,
251 : void **outToken,
252 : PRUint32 *outTokenLen)
253 : {
254 0 : if (!inToken) {
255 : /* someone wants our initial message */
256 0 : *outToken = nsMemory::Clone(mInitialMessage, mInitialMessageLen);
257 0 : if (!*outToken)
258 0 : return NS_ERROR_OUT_OF_MEMORY;
259 0 : *outTokenLen = mInitialMessageLen;
260 0 : return NS_OK;
261 : }
262 :
263 : /* inToken must be a type 2 message. Get ntlm_auth to generate our response */
264 0 : char* encoded = PL_Base64Encode(static_cast<const char*>(inToken), inTokenLen, nsnull);
265 0 : if (!encoded)
266 0 : return NS_ERROR_OUT_OF_MEMORY;
267 :
268 0 : nsCString request;
269 0 : request.AssignLiteral("TT ");
270 0 : request.Append(encoded);
271 0 : free(encoded);
272 0 : request.Append('\n');
273 :
274 0 : if (!WriteString(mToChildFD, request))
275 0 : return NS_ERROR_FAILURE;
276 0 : nsCString line;
277 0 : if (!ReadLine(mFromChildFD, line))
278 0 : return NS_ERROR_FAILURE;
279 0 : if (!StringBeginsWith(line, NS_LITERAL_CSTRING("KK "))) {
280 : // Something went wrong. Perhaps no credentials are accessible.
281 0 : return NS_ERROR_FAILURE;
282 : }
283 0 : PRUint8* buf = ExtractMessage(line, outTokenLen);
284 0 : if (!buf)
285 0 : return NS_ERROR_FAILURE;
286 : // *outToken has to be freed by nsMemory::Free, which may not be free()
287 0 : *outToken = nsMemory::Clone(buf, *outTokenLen);
288 0 : if (!*outToken) {
289 0 : free(buf);
290 0 : return NS_ERROR_OUT_OF_MEMORY;
291 : }
292 :
293 : // We're done. Close our file descriptors now and reap the helper
294 : // process.
295 0 : Shutdown();
296 0 : return NS_SUCCESS_AUTH_FINISHED;
297 : }
298 :
299 : NS_IMETHODIMP
300 0 : nsAuthSambaNTLM::Unwrap(const void *inToken,
301 : PRUint32 inTokenLen,
302 : void **outToken,
303 : PRUint32 *outTokenLen)
304 : {
305 0 : return NS_ERROR_NOT_IMPLEMENTED;
306 : }
307 :
308 : NS_IMETHODIMP
309 0 : nsAuthSambaNTLM::Wrap(const void *inToken,
310 : PRUint32 inTokenLen,
311 : bool confidential,
312 : void **outToken,
313 : PRUint32 *outTokenLen)
314 : {
315 0 : return NS_ERROR_NOT_IMPLEMENTED;
316 : }
|