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