1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2021, 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 ***************************************************************************/
23 #include "curl_setup.h"
25 #if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \
26 defined(NTLM_WB_ENABLED)
31 * https://davenport.sourceforge.io/ntlm.html
32 * https://www.innovation.ch/java/ntlm.html
37 #ifdef HAVE_SYS_WAIT_H
50 #include "vauth/ntlm.h"
51 #include "curl_ntlm_core.h"
52 #include "curl_ntlm_wb.h"
58 /* The last 3 #include files should be in this order */
59 #include "curl_printf.h"
60 #include "curl_memory.h"
64 # define DEBUG_OUT(x) x
66 # define DEBUG_OUT(x) Curl_nop_stmt
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))
76 # define sclose_nolog(x) close((x))
79 static void ntlm_wb_cleanup(struct ntlmdata *ntlm)
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;
86 if(ntlm->ntlm_auth_hlpr_pid) {
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)
94 kill(ntlm->ntlm_auth_hlpr_pid, SIGTERM);
97 /* Give the process another moment to shut down cleanly before
98 bringing down the axe */
102 kill(ntlm->ntlm_auth_hlpr_pid, SIGKILL);
108 ntlm->ntlm_auth_hlpr_pid = 0;
111 Curl_safefree(ntlm->challenge);
112 Curl_safefree(ntlm->response);
115 static CURLcode ntlm_wb_init(struct Curl_easy *data, struct ntlmdata *ntlm,
118 curl_socket_t sockfds[2];
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;
128 char buffer[STRERROR_LEN];
130 #if defined(CURL_DISABLE_VERBOSE_STRINGS)
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)
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) &&
158 username = pw.pw_name;
161 if(!username || !username[0])
164 slash = strpbrk(username, "\\/");
166 domain = strdup(username);
168 return CURLE_OUT_OF_MEMORY;
169 slash = domain + (slash - username);
171 username = username + (slash - domain) + 1;
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 */
179 ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
181 ntlm_auth = ntlm_auth_alloc;
184 ntlm_auth = NTLM_WB_FILE;
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)));
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)));
199 if(child_pid == -1) {
202 failf(data, "Could not fork. errno %d: %s",
203 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
206 else if(!child_pid) {
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)));
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)));
226 execl(ntlm_auth, ntlm_auth,
227 "--helper-protocol", "ntlmssp-client-1",
228 "--use-cached-creds",
229 "--username", username,
233 execl(ntlm_auth, ntlm_auth,
234 "--helper-protocol", "ntlmssp-client-1",
235 "--use-cached-creds",
236 "--username", username,
239 sclose_nolog(sockfds[1]);
240 failf(data, "Could not execl(). errno %d: %s",
241 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
246 ntlm->ntlm_auth_hlpr_socket = sockfds[0];
247 ntlm->ntlm_auth_hlpr_pid = child_pid;
249 free(ntlm_auth_alloc);
254 free(ntlm_auth_alloc);
255 return CURLE_REMOTE_ACCESS_DENIED;
258 /* if larger than this, something is seriously wrong */
259 #define MAX_NTLM_WB_RESPONSE 100000
261 static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm,
262 const char *input, curlntlm state)
264 size_t len_in = strlen(input), len_out = 0;
267 unsigned char *buf = (unsigned char *)data->state.buffer;
268 Curl_dyn_init(&b, MAX_NTLM_WB_RESPONSE);
271 ssize_t written = swrite(ntlm->ntlm_auth_hlpr_socket, input, len_in);
273 /* Interrupted by a signal, retry it */
276 /* write failed if other errors happen */
285 sread(ntlm->ntlm_auth_hlpr_socket, buf, data->set.buffer_size);
294 if(Curl_dyn_addn(&b, buf, size))
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';
306 /* Samba/winbind installed but not configured */
307 if(state == NTLMSTATE_TYPE1 &&
309 ptr[0] == 'P' && ptr[1] == 'W')
311 /* invalid response */
314 if(state == NTLMSTATE_TYPE1 &&
315 (ptr[0]!='Y' || ptr[1]!='R' || ptr[2]!=' '))
317 if(state == NTLMSTATE_TYPE2 &&
318 (ptr[0]!='K' || ptr[1]!='K' || ptr[2]!=' ') &&
319 (ptr[0]!='A' || ptr[1]!='F' || ptr[2]!=' '))
322 ntlm->response = strdup(ptr + 3);
325 return CURLE_OUT_OF_MEMORY;
329 return CURLE_REMOTE_ACCESS_DENIED;
332 CURLcode Curl_input_ntlm_wb(struct Curl_easy *data,
333 struct connectdata *conn,
337 struct ntlmdata *ntlm = proxy ? &conn->proxyntlm : &conn->ntlm;
338 curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state;
340 (void) data; /* In case it gets unused by nop log macros. */
342 if(!checkprefix("NTLM", header))
343 return CURLE_BAD_CONTENT_ENCODING;
345 header += strlen("NTLM");
346 while(*header && ISSPACE(*header))
350 ntlm->challenge = strdup(header);
352 return CURLE_OUT_OF_MEMORY;
354 *state = NTLMSTATE_TYPE2; /* We got a type-2 message */
357 if(*state == NTLMSTATE_LAST) {
358 infof(data, "NTLM auth restarted");
359 Curl_http_auth_cleanup_ntlm_wb(conn);
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;
367 else if(*state >= NTLMSTATE_TYPE1) {
368 infof(data, "NTLM handshake failure (internal error)");
369 return CURLE_REMOTE_ACCESS_DENIED;
372 *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */
379 * This is for creating ntlm header output by delegating challenge/response
380 * to Samba's winbind daemon helper ntlm_auth.
382 CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn,
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 */
388 /* point to the name and password for this */
390 struct ntlmdata *ntlm;
394 CURLcode res = CURLE_OK;
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;
407 return CURLE_NOT_BUILT_IN;
411 allocuserpwd = &data->state.aptr.userpwd;
414 state = &conn->http_ntlm_state;
415 authp = &data->state.authhost;
419 /* not set means empty */
424 case NTLMSTATE_TYPE1:
426 /* Use Samba's 'winbind' daemon to support NTLM authentication,
427 * by delegating the NTLM challenge/response protocol to a helper
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.
439 /* Create communication with ntlm_auth */
440 res = ntlm_wb_init(data, ntlm, userp);
443 res = ntlm_wb_response(data, ntlm, "YR\n", *state);
448 *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
449 proxy ? "Proxy-" : "",
451 DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
452 Curl_safefree(ntlm->response);
454 return CURLE_OUT_OF_MEMORY;
457 case NTLMSTATE_TYPE2: {
458 char *input = aprintf("TT %s\n", ntlm->challenge);
460 return CURLE_OUT_OF_MEMORY;
461 res = ntlm_wb_response(data, ntlm, input, *state);
467 *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
468 proxy ? "Proxy-" : "",
470 DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
471 *state = NTLMSTATE_TYPE3; /* we sent a type-3 */
473 Curl_http_auth_cleanup_ntlm_wb(conn);
475 return CURLE_OUT_OF_MEMORY;
478 case NTLMSTATE_TYPE3:
479 /* connection is already authenticated,
480 * don't send a header in future requests */
481 *state = NTLMSTATE_LAST;
484 Curl_safefree(*allocuserpwd);
492 void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn)
494 ntlm_wb_cleanup(&conn->ntlm);
495 ntlm_wb_cleanup(&conn->proxyntlm);
498 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */