1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
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.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 * SPDX-License-Identifier: curl
23 ***************************************************************************/
25 #include "curl_setup.h"
27 #if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \
28 defined(NTLM_WB_ENABLED)
33 * https://davenport.sourceforge.net/ntlm.html
34 * https://www.innovation.ch/java/ntlm.html
39 #ifdef HAVE_SYS_WAIT_H
52 #include "vauth/ntlm.h"
53 #include "curl_ntlm_core.h"
54 #include "curl_ntlm_wb.h"
60 /* The last 3 #include files should be in this order */
61 #include "curl_printf.h"
62 #include "curl_memory.h"
66 # define DEBUG_OUT(x) x
68 # define DEBUG_OUT(x) Curl_nop_stmt
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))
78 # define sclose_nolog(x) close((x))
81 static void ntlm_wb_cleanup(struct ntlmdata *ntlm)
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;
88 if(ntlm->ntlm_auth_hlpr_pid) {
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)
96 kill(ntlm->ntlm_auth_hlpr_pid, SIGTERM);
99 /* Give the process another moment to shut down cleanly before
100 bringing down the axe */
104 kill(ntlm->ntlm_auth_hlpr_pid, SIGKILL);
110 ntlm->ntlm_auth_hlpr_pid = 0;
113 Curl_safefree(ntlm->challenge);
114 Curl_safefree(ntlm->response);
117 static CURLcode ntlm_wb_init(struct Curl_easy *data, struct ntlmdata *ntlm,
120 curl_socket_t sockfds[2];
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;
130 char buffer[STRERROR_LEN];
132 #if defined(CURL_DISABLE_VERBOSE_STRINGS)
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)
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) &&
160 username = pw.pw_name;
163 if(!username || !username[0])
166 slash = strpbrk(username, "\\/");
168 domain = strdup(username);
170 return CURLE_OUT_OF_MEMORY;
171 slash = domain + (slash - username);
173 username = username + (slash - domain) + 1;
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 */
181 ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
183 ntlm_auth = ntlm_auth_alloc;
186 ntlm_auth = NTLM_WB_FILE;
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)));
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)));
201 if(child_pid == -1) {
204 failf(data, "Could not fork. errno %d: %s",
205 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
208 else if(!child_pid) {
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)));
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)));
228 execl(ntlm_auth, ntlm_auth,
229 "--helper-protocol", "ntlmssp-client-1",
230 "--use-cached-creds",
231 "--username", username,
235 execl(ntlm_auth, ntlm_auth,
236 "--helper-protocol", "ntlmssp-client-1",
237 "--use-cached-creds",
238 "--username", username,
241 sclose_nolog(sockfds[1]);
242 failf(data, "Could not execl(). errno %d: %s",
243 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
248 ntlm->ntlm_auth_hlpr_socket = sockfds[0];
249 ntlm->ntlm_auth_hlpr_pid = child_pid;
251 free(ntlm_auth_alloc);
256 free(ntlm_auth_alloc);
257 return CURLE_REMOTE_ACCESS_DENIED;
260 /* if larger than this, something is seriously wrong */
261 #define MAX_NTLM_WB_RESPONSE 100000
263 static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm,
264 const char *input, curlntlm state)
266 size_t len_in = strlen(input), len_out = 0;
269 unsigned char *buf = (unsigned char *)data->state.buffer;
270 Curl_dyn_init(&b, MAX_NTLM_WB_RESPONSE);
273 ssize_t written = swrite(ntlm->ntlm_auth_hlpr_socket, input, len_in);
275 /* Interrupted by a signal, retry it */
278 /* write failed if other errors happen */
287 sread(ntlm->ntlm_auth_hlpr_socket, buf, data->set.buffer_size);
296 if(Curl_dyn_addn(&b, buf, size))
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';
308 /* Samba/winbind installed but not configured */
309 if(state == NTLMSTATE_TYPE1 &&
311 ptr[0] == 'P' && ptr[1] == 'W')
313 /* invalid response */
316 if(state == NTLMSTATE_TYPE1 &&
317 (ptr[0]!='Y' || ptr[1]!='R' || ptr[2]!=' '))
319 if(state == NTLMSTATE_TYPE2 &&
320 (ptr[0]!='K' || ptr[1]!='K' || ptr[2]!=' ') &&
321 (ptr[0]!='A' || ptr[1]!='F' || ptr[2]!=' '))
324 ntlm->response = strdup(ptr + 3);
327 return CURLE_OUT_OF_MEMORY;
331 return CURLE_REMOTE_ACCESS_DENIED;
334 CURLcode Curl_input_ntlm_wb(struct Curl_easy *data,
335 struct connectdata *conn,
339 struct ntlmdata *ntlm = proxy ? &conn->proxyntlm : &conn->ntlm;
340 curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state;
342 (void) data; /* In case it gets unused by nop log macros. */
344 if(!checkprefix("NTLM", header))
345 return CURLE_BAD_CONTENT_ENCODING;
347 header += strlen("NTLM");
348 while(*header && ISSPACE(*header))
352 ntlm->challenge = strdup(header);
354 return CURLE_OUT_OF_MEMORY;
356 *state = NTLMSTATE_TYPE2; /* We got a type-2 message */
359 if(*state == NTLMSTATE_LAST) {
360 infof(data, "NTLM auth restarted");
361 Curl_http_auth_cleanup_ntlm_wb(conn);
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;
369 else if(*state >= NTLMSTATE_TYPE1) {
370 infof(data, "NTLM handshake failure (internal error)");
371 return CURLE_REMOTE_ACCESS_DENIED;
374 *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */
381 * This is for creating ntlm header output by delegating challenge/response
382 * to Samba's winbind daemon helper ntlm_auth.
384 CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn,
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 */
390 /* point to the name and password for this */
392 struct ntlmdata *ntlm;
396 CURLcode res = CURLE_OK;
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;
409 return CURLE_NOT_BUILT_IN;
413 allocuserpwd = &data->state.aptr.userpwd;
416 state = &conn->http_ntlm_state;
417 authp = &data->state.authhost;
421 /* not set means empty */
426 case NTLMSTATE_TYPE1:
428 /* Use Samba's 'winbind' daemon to support NTLM authentication,
429 * by delegating the NTLM challenge/response protocol to a helper
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.
441 /* Create communication with ntlm_auth */
442 res = ntlm_wb_init(data, ntlm, userp);
445 res = ntlm_wb_response(data, ntlm, "YR\n", *state);
450 *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
451 proxy ? "Proxy-" : "",
453 DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
454 Curl_safefree(ntlm->response);
456 return CURLE_OUT_OF_MEMORY;
459 case NTLMSTATE_TYPE2: {
460 char *input = aprintf("TT %s\n", ntlm->challenge);
462 return CURLE_OUT_OF_MEMORY;
463 res = ntlm_wb_response(data, ntlm, input, *state);
469 *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
470 proxy ? "Proxy-" : "",
472 DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
473 *state = NTLMSTATE_TYPE3; /* we sent a type-3 */
475 Curl_http_auth_cleanup_ntlm_wb(conn);
477 return CURLE_OUT_OF_MEMORY;
480 case NTLMSTATE_TYPE3:
481 /* connection is already authenticated,
482 * don't send a header in future requests */
483 *state = NTLMSTATE_LAST;
486 Curl_safefree(*allocuserpwd);
494 void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn)
496 ntlm_wb_cleanup(&conn->ntlm);
497 ntlm_wb_cleanup(&conn->proxyntlm);
500 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */