1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2021, 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.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
79static void ntlm_wb_cleanup(struct ntlmdata *ntlm)
80{
81 if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) {
82 sclose(ntlm->ntlm_auth_hlpr_socket);
83 ntlm->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD;
84 }
85
86 if(ntlm->ntlm_auth_hlpr_pid) {
87 int i;
88 for(i = 0; i < 4; i++) {
89 pid_t ret = waitpid(ntlm->ntlm_auth_hlpr_pid, NULL, WNOHANG);
90 if(ret == ntlm->ntlm_auth_hlpr_pid || errno == ECHILD)
91 break;
92 switch(i) {
93 case 0:
94 kill(ntlm->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(ntlm->ntlm_auth_hlpr_pid, SIGKILL);
103 break;
104 case 3:
105 break;
106 }
107 }
108 ntlm->ntlm_auth_hlpr_pid = 0;
109 }
110
111 Curl_safefree(ntlm->challenge);
112 Curl_safefree(ntlm->response);
113}
114
115static CURLcode ntlm_wb_init(struct Curl_easy *data, struct ntlmdata *ntlm,
116 const char *userp)
117{
118 curl_socket_t sockfds[2];
119 pid_t child_pid;
120 const char *username;
121 char *slash, *domain = NULL;
122 const char *ntlm_auth = NULL;
123 char *ntlm_auth_alloc = NULL;
124#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
125 struct passwd pw, *pw_res;
126 char pwbuf[1024];
127#endif
128 char buffer[STRERROR_LEN];
129
130#if defined(CURL_DISABLE_VERBOSE_STRINGS)
131 (void) data;
132#endif
133
134 /* Return if communication with ntlm_auth already set up */
135 if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD ||
136 ntlm->ntlm_auth_hlpr_pid)
137 return CURLE_OK;
138
139 username = userp;
140 /* The real ntlm_auth really doesn't like being invoked with an
141 empty username. It won't make inferences for itself, and expects
142 the client to do so (mostly because it's really designed for
143 servers like squid to use for auth, and client support is an
144 afterthought for it). So try hard to provide a suitable username
145 if we don't already have one. But if we can't, provide the
146 empty one anyway. Perhaps they have an implementation of the
147 ntlm_auth helper which *doesn't* need it so we might as well try */
148 if(!username || !username[0]) {
149 username = getenv("NTLMUSER");
150 if(!username || !username[0])
151 username = getenv("LOGNAME");
152 if(!username || !username[0])
153 username = getenv("USER");
154#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
155 if((!username || !username[0]) &&
156 !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) &&
157 pw_res) {
158 username = pw.pw_name;
159 }
160#endif
161 if(!username || !username[0])
162 username = userp;
163 }
164 slash = strpbrk(username, "\\/");
165 if(slash) {
166 domain = strdup(username);
167 if(!domain)
168 return CURLE_OUT_OF_MEMORY;
169 slash = domain + (slash - username);
170 *slash = '\0';
171 username = username + (slash - domain) + 1;
172 }
173
174 /* For testing purposes, when DEBUGBUILD is defined and environment
175 variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
176 NTLM challenge/response which only accepts commands and output
177 strings pre-written in test case definitions */
178#ifdef DEBUGBUILD
179 ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
180 if(ntlm_auth_alloc)
181 ntlm_auth = ntlm_auth_alloc;
182 else
183#endif
184 ntlm_auth = NTLM_WB_FILE;
185
186 if(access(ntlm_auth, X_OK) != 0) {
187 failf(data, "Could not access ntlm_auth: %s errno %d: %s",
188 ntlm_auth, errno, Curl_strerror(errno, buffer, sizeof(buffer)));
189 goto done;
190 }
191
192 if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
193 failf(data, "Could not open socket pair. errno %d: %s",
194 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
195 goto done;
196 }
197
198 child_pid = fork();
199 if(child_pid == -1) {
200 sclose(sockfds[0]);
201 sclose(sockfds[1]);
202 failf(data, "Could not fork. errno %d: %s",
203 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
204 goto done;
205 }
206 else if(!child_pid) {
207 /*
208 * child process
209 */
210
211 /* Don't use sclose in the child since it fools the socket leak detector */
212 sclose_nolog(sockfds[0]);
213 if(dup2(sockfds[1], STDIN_FILENO) == -1) {
214 failf(data, "Could not redirect child stdin. errno %d: %s",
215 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
216 exit(1);
217 }
218
219 if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
220 failf(data, "Could not redirect child stdout. errno %d: %s",
221 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
222 exit(1);
223 }
224
225 if(domain)
226 execl(ntlm_auth, ntlm_auth,
227 "--helper-protocol", "ntlmssp-client-1",
228 "--use-cached-creds",
229 "--username", username,
230 "--domain", domain,
231 NULL);
232 else
233 execl(ntlm_auth, ntlm_auth,
234 "--helper-protocol", "ntlmssp-client-1",
235 "--use-cached-creds",
236 "--username", username,
237 NULL);
238
239 sclose_nolog(sockfds[1]);
240 failf(data, "Could not execl(). errno %d: %s",
241 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
242 exit(1);
243 }
244
245 sclose(sockfds[1]);
246 ntlm->ntlm_auth_hlpr_socket = sockfds[0];
247 ntlm->ntlm_auth_hlpr_pid = child_pid;
248 free(domain);
249 free(ntlm_auth_alloc);
250 return CURLE_OK;
251
252done:
253 free(domain);
254 free(ntlm_auth_alloc);
255 return CURLE_REMOTE_ACCESS_DENIED;
256}
257
258/* if larger than this, something is seriously wrong */
259#define MAX_NTLM_WB_RESPONSE 100000
260
261static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm,
262 const char *input, curlntlm state)
263{
264 size_t len_in = strlen(input), len_out = 0;
265 struct dynbuf b;
266 char *ptr = NULL;
267 unsigned char *buf = (unsigned char *)data->state.buffer;
268 Curl_dyn_init(&b, MAX_NTLM_WB_RESPONSE);
269
270 while(len_in > 0) {
271 ssize_t written = swrite(ntlm->ntlm_auth_hlpr_socket, input, len_in);
272 if(written == -1) {
273 /* Interrupted by a signal, retry it */
274 if(errno == EINTR)
275 continue;
276 /* write failed if other errors happen */
277 goto done;
278 }
279 input += written;
280 len_in -= written;
281 }
282 /* Read one line */
283 while(1) {
284 ssize_t size =
285 sread(ntlm->ntlm_auth_hlpr_socket, buf, data->set.buffer_size);
286 if(size == -1) {
287 if(errno == EINTR)
288 continue;
289 goto done;
290 }
291 else if(size == 0)
292 goto done;
293
294 if(Curl_dyn_addn(&b, buf, size))
295 goto done;
296
297 len_out = Curl_dyn_len(&b);
298 ptr = Curl_dyn_ptr(&b);
299 if(len_out && ptr[len_out - 1] == '\n') {
300 ptr[len_out - 1] = '\0';
301 break; /* done! */
302 }
303 /* loop */
304 }
305
306 /* Samba/winbind installed but not configured */
307 if(state == NTLMSTATE_TYPE1 &&
308 len_out == 3 &&
309 ptr[0] == 'P' && ptr[1] == 'W')
310 goto done;
311 /* invalid response */
312 if(len_out < 4)
313 goto done;
314 if(state == NTLMSTATE_TYPE1 &&
315 (ptr[0]!='Y' || ptr[1]!='R' || ptr[2]!=' '))
316 goto done;
317 if(state == NTLMSTATE_TYPE2 &&
318 (ptr[0]!='K' || ptr[1]!='K' || ptr[2]!=' ') &&
319 (ptr[0]!='A' || ptr[1]!='F' || ptr[2]!=' '))
320 goto done;
321
322 ntlm->response = strdup(ptr + 3);
323 Curl_dyn_free(&b);
324 if(!ntlm->response)
325 return CURLE_OUT_OF_MEMORY;
326 return CURLE_OK;
327done:
328 Curl_dyn_free(&b);
329 return CURLE_REMOTE_ACCESS_DENIED;
330}
331
332CURLcode Curl_input_ntlm_wb(struct Curl_easy *data,
333 struct connectdata *conn,
334 bool proxy,
335 const char *header)
336{
337 struct ntlmdata *ntlm = proxy ? &conn->proxyntlm : &conn->ntlm;
338 curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state;
339
340 (void) data; /* In case it gets unused by nop log macros. */
341
342 if(!checkprefix("NTLM", header))
343 return CURLE_BAD_CONTENT_ENCODING;
344
345 header += strlen("NTLM");
346 while(*header && ISSPACE(*header))
347 header++;
348
349 if(*header) {
350 ntlm->challenge = strdup(header);
351 if(!ntlm->challenge)
352 return CURLE_OUT_OF_MEMORY;
353
354 *state = NTLMSTATE_TYPE2; /* We got a type-2 message */
355 }
356 else {
357 if(*state == NTLMSTATE_LAST) {
358 infof(data, "NTLM auth restarted");
359 Curl_http_auth_cleanup_ntlm_wb(conn);
360 }
361 else if(*state == NTLMSTATE_TYPE3) {
362 infof(data, "NTLM handshake rejected");
363 Curl_http_auth_cleanup_ntlm_wb(conn);
364 *state = NTLMSTATE_NONE;
365 return CURLE_REMOTE_ACCESS_DENIED;
366 }
367 else if(*state >= NTLMSTATE_TYPE1) {
368 infof(data, "NTLM handshake failure (internal error)");
369 return CURLE_REMOTE_ACCESS_DENIED;
370 }
371
372 *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */
373 }
374
375 return CURLE_OK;
376}
377
378/*
379 * This is for creating ntlm header output by delegating challenge/response
380 * to Samba's winbind daemon helper ntlm_auth.
381 */
382CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn,
383 bool proxy)
384{
385 /* point to the address of the pointer that holds the string to send to the
386 server, which is for a plain host or for a HTTP proxy */
387 char **allocuserpwd;
388 /* point to the name and password for this */
389 const char *userp;
390 struct ntlmdata *ntlm;
391 curlntlm *state;
392 struct auth *authp;
393
394 CURLcode res = CURLE_OK;
395
396 DEBUGASSERT(conn);
397 DEBUGASSERT(data);
398
399 if(proxy) {
400#ifndef CURL_DISABLE_PROXY
401 allocuserpwd = &data->state.aptr.proxyuserpwd;
402 userp = conn->http_proxy.user;
403 ntlm = &conn->proxyntlm;
404 state = &conn->proxy_ntlm_state;
405 authp = &data->state.authproxy;
406#else
407 return CURLE_NOT_BUILT_IN;
408#endif
409 }
410 else {
411 allocuserpwd = &data->state.aptr.userpwd;
412 userp = conn->user;
413 ntlm = &conn->ntlm;
414 state = &conn->http_ntlm_state;
415 authp = &data->state.authhost;
416 }
417 authp->done = FALSE;
418
419 /* not set means empty */
420 if(!userp)
421 userp = "";
422
423 switch(*state) {
424 case NTLMSTATE_TYPE1:
425 default:
426 /* Use Samba's 'winbind' daemon to support NTLM authentication,
427 * by delegating the NTLM challenge/response protocol to a helper
428 * in ntlm_auth.
429 * https://web.archive.org/web/20190925164737
430 * /devel.squid-cache.org/ntlm/squid_helper_protocol.html
431 * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
432 * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
433 * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
434 * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
435 * filename of ntlm_auth helper.
436 * If NTLM authentication using winbind fails, go back to original
437 * request handling process.
438 */
439 /* Create communication with ntlm_auth */
440 res = ntlm_wb_init(data, ntlm, userp);
441 if(res)
442 return res;
443 res = ntlm_wb_response(data, ntlm, "YR\n", *state);
444 if(res)
445 return res;
446
447 free(*allocuserpwd);
448 *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
449 proxy ? "Proxy-" : "",
450 ntlm->response);
451 DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
452 Curl_safefree(ntlm->response);
453 if(!*allocuserpwd)
454 return CURLE_OUT_OF_MEMORY;
455 break;
456
457 case NTLMSTATE_TYPE2: {
458 char *input = aprintf("TT %s\n", ntlm->challenge);
459 if(!input)
460 return CURLE_OUT_OF_MEMORY;
461 res = ntlm_wb_response(data, ntlm, input, *state);
462 free(input);
463 if(res)
464 return res;
465
466 free(*allocuserpwd);
467 *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
468 proxy ? "Proxy-" : "",
469 ntlm->response);
470 DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
471 *state = NTLMSTATE_TYPE3; /* we sent a type-3 */
472 authp->done = TRUE;
473 Curl_http_auth_cleanup_ntlm_wb(conn);
474 if(!*allocuserpwd)
475 return CURLE_OUT_OF_MEMORY;
476 break;
477 }
478 case NTLMSTATE_TYPE3:
479 /* connection is already authenticated,
480 * don't send a header in future requests */
481 *state = NTLMSTATE_LAST;
482 /* FALLTHROUGH */
483 case NTLMSTATE_LAST:
484 Curl_safefree(*allocuserpwd);
485 authp->done = TRUE;
486 break;
487 }
488
489 return CURLE_OK;
490}
491
492void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn)
493{
494 ntlm_wb_cleanup(&conn->ntlm);
495 ntlm_wb_cleanup(&conn->proxyntlm);
496}
497
498#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */
499