1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.haxx.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ***************************************************************************/
22
23#include "curl_setup.h"
24
25#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \
26 defined(NTLM_WB_ENABLED)
27
28/*
29 * NTLM details:
30 *
31 * https://davenport.sourceforge.io/ntlm.html
32 * https://www.innovation.ch/java/ntlm.html
33 */
34
35#define DEBUG_ME 0
36
37#ifdef HAVE_SYS_WAIT_H
38#include <sys/wait.h>
39#endif
40#ifdef HAVE_SIGNAL_H
41#include <signal.h>
42#endif
43#ifdef HAVE_PWD_H
44#include <pwd.h>
45#endif
46
47#include "urldata.h"
48#include "sendf.h"
49#include "select.h"
50#include "vauth/ntlm.h"
51#include "curl_ntlm_core.h"
52#include "curl_ntlm_wb.h"
53#include "url.h"
54#include "strerror.h"
55#include "strdup.h"
56#include "strcase.h"
57
58/* The last 3 #include files should be in this order */
59#include "curl_printf.h"
60#include "curl_memory.h"
61#include "memdebug.h"
62
63#if DEBUG_ME
64# define DEBUG_OUT(x) x
65#else
66# define DEBUG_OUT(x) Curl_nop_stmt
67#endif
68
69/* Portable 'sclose_nolog' used only in child process instead of 'sclose'
70 to avoid fooling the socket leak detector */
71#if defined(HAVE_CLOSESOCKET)
72# define sclose_nolog(x) closesocket((x))
73#elif defined(HAVE_CLOSESOCKET_CAMEL)
74# define sclose_nolog(x) CloseSocket((x))
75#else
76# define sclose_nolog(x) close((x))
77#endif
78
79void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn)
80{
81 if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) {
82 sclose(conn->ntlm_auth_hlpr_socket);
83 conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD;
84 }
85
86 if(conn->ntlm_auth_hlpr_pid) {
87 int i;
88 for(i = 0; i < 4; i++) {
89 pid_t ret = waitpid(conn->ntlm_auth_hlpr_pid, NULL, WNOHANG);
90 if(ret == conn->ntlm_auth_hlpr_pid || errno == ECHILD)
91 break;
92 switch(i) {
93 case 0:
94 kill(conn->ntlm_auth_hlpr_pid, SIGTERM);
95 break;
96 case 1:
97 /* Give the process another moment to shut down cleanly before
98 bringing down the axe */
99 Curl_wait_ms(1);
100 break;
101 case 2:
102 kill(conn->ntlm_auth_hlpr_pid, SIGKILL);
103 break;
104 case 3:
105 break;
106 }
107 }
108 conn->ntlm_auth_hlpr_pid = 0;
109 }
110
111 free(conn->challenge_header);
112 conn->challenge_header = NULL;
113 free(conn->response_header);
114 conn->response_header = NULL;
115}
116
117static CURLcode ntlm_wb_init(struct connectdata *conn, const char *userp)
118{
119 curl_socket_t sockfds[2];
120 pid_t child_pid;
121 const char *username;
122 char *slash, *domain = NULL;
123 const char *ntlm_auth = NULL;
124 char *ntlm_auth_alloc = NULL;
125#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
126 struct passwd pw, *pw_res;
127 char pwbuf[1024];
128#endif
129 char buffer[STRERROR_LEN];
130
131 /* Return if communication with ntlm_auth already set up */
132 if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD ||
133 conn->ntlm_auth_hlpr_pid)
134 return CURLE_OK;
135
136 username = userp;
137 /* The real ntlm_auth really doesn't like being invoked with an
138 empty username. It won't make inferences for itself, and expects
139 the client to do so (mostly because it's really designed for
140 servers like squid to use for auth, and client support is an
141 afterthought for it). So try hard to provide a suitable username
142 if we don't already have one. But if we can't, provide the
143 empty one anyway. Perhaps they have an implementation of the
144 ntlm_auth helper which *doesn't* need it so we might as well try */
145 if(!username || !username[0]) {
146 username = getenv("NTLMUSER");
147 if(!username || !username[0])
148 username = getenv("LOGNAME");
149 if(!username || !username[0])
150 username = getenv("USER");
151#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
152 if((!username || !username[0]) &&
153 !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) &&
154 pw_res) {
155 username = pw.pw_name;
156 }
157#endif
158 if(!username || !username[0])
159 username = userp;
160 }
161 slash = strpbrk(username, "\\/");
162 if(slash) {
163 domain = strdup(username);
164 if(!domain)
165 return CURLE_OUT_OF_MEMORY;
166 slash = domain + (slash - username);
167 *slash = '\0';
168 username = username + (slash - domain) + 1;
169 }
170
171 /* For testing purposes, when DEBUGBUILD is defined and environment
172 variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
173 NTLM challenge/response which only accepts commands and output
174 strings pre-written in test case definitions */
175#ifdef DEBUGBUILD
176 ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
177 if(ntlm_auth_alloc)
178 ntlm_auth = ntlm_auth_alloc;
179 else
180#endif
181 ntlm_auth = NTLM_WB_FILE;
182
183 if(access(ntlm_auth, X_OK) != 0) {
184 failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s",
185 ntlm_auth, errno, Curl_strerror(errno, buffer, sizeof(buffer)));
186 goto done;
187 }
188
189 if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
190 failf(conn->data, "Could not open socket pair. errno %d: %s",
191 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
192 goto done;
193 }
194
195 child_pid = fork();
196 if(child_pid == -1) {
197 sclose(sockfds[0]);
198 sclose(sockfds[1]);
199 failf(conn->data, "Could not fork. errno %d: %s",
200 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
201 goto done;
202 }
203 else if(!child_pid) {
204 /*
205 * child process
206 */
207
208 /* Don't use sclose in the child since it fools the socket leak detector */
209 sclose_nolog(sockfds[0]);
210 if(dup2(sockfds[1], STDIN_FILENO) == -1) {
211 failf(conn->data, "Could not redirect child stdin. errno %d: %s",
212 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
213 exit(1);
214 }
215
216 if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
217 failf(conn->data, "Could not redirect child stdout. errno %d: %s",
218 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
219 exit(1);
220 }
221
222 if(domain)
223 execl(ntlm_auth, ntlm_auth,
224 "--helper-protocol", "ntlmssp-client-1",
225 "--use-cached-creds",
226 "--username", username,
227 "--domain", domain,
228 NULL);
229 else
230 execl(ntlm_auth, ntlm_auth,
231 "--helper-protocol", "ntlmssp-client-1",
232 "--use-cached-creds",
233 "--username", username,
234 NULL);
235
236 sclose_nolog(sockfds[1]);
237 failf(conn->data, "Could not execl(). errno %d: %s",
238 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
239 exit(1);
240 }
241
242 sclose(sockfds[1]);
243 conn->ntlm_auth_hlpr_socket = sockfds[0];
244 conn->ntlm_auth_hlpr_pid = child_pid;
245 free(domain);
246 free(ntlm_auth_alloc);
247 return CURLE_OK;
248
249done:
250 free(domain);
251 free(ntlm_auth_alloc);
252 return CURLE_REMOTE_ACCESS_DENIED;
253}
254
255/* if larger than this, something is seriously wrong */
256#define MAX_NTLM_WB_RESPONSE 100000
257
258static CURLcode ntlm_wb_response(struct connectdata *conn,
259 const char *input, curlntlm state)
260{
261 char *buf = malloc(NTLM_BUFSIZE);
262 size_t len_in = strlen(input), len_out = 0;
263
264 if(!buf)
265 return CURLE_OUT_OF_MEMORY;
266
267 while(len_in > 0) {
268 ssize_t written = swrite(conn->ntlm_auth_hlpr_socket, input, len_in);
269 if(written == -1) {
270 /* Interrupted by a signal, retry it */
271 if(errno == EINTR)
272 continue;
273 /* write failed if other errors happen */
274 goto done;
275 }
276 input += written;
277 len_in -= written;
278 }
279 /* Read one line */
280 while(1) {
281 ssize_t size;
282 char *newbuf;
283
284 size = sread(conn->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE);
285 if(size == -1) {
286 if(errno == EINTR)
287 continue;
288 goto done;
289 }
290 else if(size == 0)
291 goto done;
292
293 len_out += size;
294 if(buf[len_out - 1] == '\n') {
295 buf[len_out - 1] = '\0';
296 break;
297 }
298
299 if(len_out > MAX_NTLM_WB_RESPONSE) {
300 failf(conn->data, "too large ntlm_wb response!");
301 free(buf);
302 return CURLE_OUT_OF_MEMORY;
303 }
304
305 newbuf = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE);
306 if(!newbuf)
307 return CURLE_OUT_OF_MEMORY;
308
309 buf = newbuf;
310 }
311
312 /* Samba/winbind installed but not configured */
313 if(state == NTLMSTATE_TYPE1 &&
314 len_out == 3 &&
315 buf[0] == 'P' && buf[1] == 'W')
316 goto done;
317 /* invalid response */
318 if(len_out < 4)
319 goto done;
320 if(state == NTLMSTATE_TYPE1 &&
321 (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
322 goto done;
323 if(state == NTLMSTATE_TYPE2 &&
324 (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
325 (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
326 goto done;
327
328 conn->response_header = aprintf("NTLM %.*s", len_out - 4, buf + 3);
329 free(buf);
330 if(!conn->response_header)
331 return CURLE_OUT_OF_MEMORY;
332 return CURLE_OK;
333done:
334 free(buf);
335 return CURLE_REMOTE_ACCESS_DENIED;
336}
337
338CURLcode Curl_input_ntlm_wb(struct connectdata *conn,
339 bool proxy,
340 const char *header)
341{
342 curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state;
343
344 if(!checkprefix("NTLM", header))
345 return CURLE_BAD_CONTENT_ENCODING;
346
347 header += strlen("NTLM");
348 while(*header && ISSPACE(*header))
349 header++;
350
351 if(*header) {
352 conn->challenge_header = strdup(header);
353 if(!conn->challenge_header)
354 return CURLE_OUT_OF_MEMORY;
355
356 *state = NTLMSTATE_TYPE2; /* We got a type-2 message */
357 }
358 else {
359 if(*state == NTLMSTATE_LAST) {
360 infof(conn->data, "NTLM auth restarted\n");
361 Curl_http_auth_cleanup_ntlm_wb(conn);
362 }
363 else if(*state == NTLMSTATE_TYPE3) {
364 infof(conn->data, "NTLM handshake rejected\n");
365 Curl_http_auth_cleanup_ntlm_wb(conn);
366 *state = NTLMSTATE_NONE;
367 return CURLE_REMOTE_ACCESS_DENIED;
368 }
369 else if(*state >= NTLMSTATE_TYPE1) {
370 infof(conn->data, "NTLM handshake failure (internal error)\n");
371 return CURLE_REMOTE_ACCESS_DENIED;
372 }
373
374 *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */
375 }
376
377 return CURLE_OK;
378}
379
380/*
381 * This is for creating ntlm header output by delegating challenge/response
382 * to Samba's winbind daemon helper ntlm_auth.
383 */
384CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
385 bool proxy)
386{
387 /* point to the address of the pointer that holds the string to send to the
388 server, which is for a plain host or for a HTTP proxy */
389 char **allocuserpwd;
390 /* point to the name and password for this */
391 const char *userp;
392 curlntlm *state;
393 struct auth *authp;
394
395 CURLcode res = CURLE_OK;
396 char *input;
397
398 DEBUGASSERT(conn);
399 DEBUGASSERT(conn->data);
400
401 if(proxy) {
402 allocuserpwd = &conn->allocptr.proxyuserpwd;
403 userp = conn->http_proxy.user;
404 state = &conn->proxy_ntlm_state;
405 authp = &conn->data->state.authproxy;
406 }
407 else {
408 allocuserpwd = &conn->allocptr.userpwd;
409 userp = conn->user;
410 state = &conn->http_ntlm_state;
411 authp = &conn->data->state.authhost;
412 }
413 authp->done = FALSE;
414
415 /* not set means empty */
416 if(!userp)
417 userp = "";
418
419 switch(*state) {
420 case NTLMSTATE_TYPE1:
421 default:
422 /* Use Samba's 'winbind' daemon to support NTLM authentication,
423 * by delegating the NTLM challenge/response protocol to a helper
424 * in ntlm_auth.
425 * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
426 * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
427 * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
428 * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
429 * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
430 * filename of ntlm_auth helper.
431 * If NTLM authentication using winbind fails, go back to original
432 * request handling process.
433 */
434 /* Create communication with ntlm_auth */
435 res = ntlm_wb_init(conn, userp);
436 if(res)
437 return res;
438 res = ntlm_wb_response(conn, "YR\n", *state);
439 if(res)
440 return res;
441
442 free(*allocuserpwd);
443 *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
444 proxy ? "Proxy-" : "",
445 conn->response_header);
446 DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
447 free(conn->response_header);
448 if(!*allocuserpwd)
449 return CURLE_OUT_OF_MEMORY;
450 conn->response_header = NULL;
451 break;
452
453 case NTLMSTATE_TYPE2:
454 input = aprintf("TT %s\n", conn->challenge_header);
455 if(!input)
456 return CURLE_OUT_OF_MEMORY;
457 res = ntlm_wb_response(conn, input, *state);
458 free(input);
459 input = NULL;
460 if(res)
461 return res;
462
463 free(*allocuserpwd);
464 *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
465 proxy ? "Proxy-" : "",
466 conn->response_header);
467 DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
468 *state = NTLMSTATE_TYPE3; /* we sent a type-3 */
469 authp->done = TRUE;
470 Curl_http_auth_cleanup_ntlm_wb(conn);
471 if(!*allocuserpwd)
472 return CURLE_OUT_OF_MEMORY;
473 break;
474
475 case NTLMSTATE_TYPE3:
476 /* connection is already authenticated,
477 * don't send a header in future requests */
478 *state = NTLMSTATE_LAST;
479 /* FALLTHROUGH */
480 case NTLMSTATE_LAST:
481 Curl_safefree(*allocuserpwd);
482 authp->done = TRUE;
483 break;
484 }
485
486 return CURLE_OK;
487}
488
489#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */
490