1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2020, 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.haxx.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 connectdata *conn,
336 struct ntlmdata *ntlm = proxy ? &conn->proxyntlm : &conn->ntlm;
337 curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state;
339 if(!checkprefix("NTLM", header))
340 return CURLE_BAD_CONTENT_ENCODING;
342 header += strlen("NTLM");
343 while(*header && ISSPACE(*header))
347 ntlm->challenge = strdup(header);
349 return CURLE_OUT_OF_MEMORY;
351 *state = NTLMSTATE_TYPE2; /* We got a type-2 message */
354 if(*state == NTLMSTATE_LAST) {
355 infof(conn->data, "NTLM auth restarted\n");
356 Curl_http_auth_cleanup_ntlm_wb(conn);
358 else if(*state == NTLMSTATE_TYPE3) {
359 infof(conn->data, "NTLM handshake rejected\n");
360 Curl_http_auth_cleanup_ntlm_wb(conn);
361 *state = NTLMSTATE_NONE;
362 return CURLE_REMOTE_ACCESS_DENIED;
364 else if(*state >= NTLMSTATE_TYPE1) {
365 infof(conn->data, "NTLM handshake failure (internal error)\n");
366 return CURLE_REMOTE_ACCESS_DENIED;
369 *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */
376 * This is for creating ntlm header output by delegating challenge/response
377 * to Samba's winbind daemon helper ntlm_auth.
379 CURLcode Curl_output_ntlm_wb(struct connectdata *conn, bool proxy)
381 /* point to the address of the pointer that holds the string to send to the
382 server, which is for a plain host or for a HTTP proxy */
384 /* point to the name and password for this */
386 struct ntlmdata *ntlm;
389 struct Curl_easy *data = conn->data;
391 CURLcode res = CURLE_OK;
394 DEBUGASSERT(conn->data);
397 #ifndef CURL_DISABLE_PROXY
398 allocuserpwd = &data->state.aptr.proxyuserpwd;
399 userp = conn->http_proxy.user;
400 ntlm = &conn->proxyntlm;
401 state = &conn->proxy_ntlm_state;
402 authp = &conn->data->state.authproxy;
404 return CURLE_NOT_BUILT_IN;
408 allocuserpwd = &data->state.aptr.userpwd;
411 state = &conn->http_ntlm_state;
412 authp = &conn->data->state.authhost;
416 /* not set means empty */
421 case NTLMSTATE_TYPE1:
423 /* Use Samba's 'winbind' daemon to support NTLM authentication,
424 * by delegating the NTLM challenge/response protocol to a helper
426 * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
427 * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
428 * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
429 * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
430 * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
431 * filename of ntlm_auth helper.
432 * If NTLM authentication using winbind fails, go back to original
433 * request handling process.
435 /* Create communication with ntlm_auth */
436 res = ntlm_wb_init(conn->data, ntlm, userp);
439 res = ntlm_wb_response(conn->data, ntlm, "YR\n", *state);
444 *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
445 proxy ? "Proxy-" : "",
447 DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
448 Curl_safefree(ntlm->response);
450 return CURLE_OUT_OF_MEMORY;
453 case NTLMSTATE_TYPE2: {
454 char *input = aprintf("TT %s\n", ntlm->challenge);
456 return CURLE_OUT_OF_MEMORY;
457 res = ntlm_wb_response(conn->data, ntlm, input, *state);
463 *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
464 proxy ? "Proxy-" : "",
466 DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
467 *state = NTLMSTATE_TYPE3; /* we sent a type-3 */
469 Curl_http_auth_cleanup_ntlm_wb(conn);
471 return CURLE_OUT_OF_MEMORY;
474 case NTLMSTATE_TYPE3:
475 /* connection is already authenticated,
476 * don't send a header in future requests */
477 *state = NTLMSTATE_LAST;
480 Curl_safefree(*allocuserpwd);
488 void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn)
490 ntlm_wb_cleanup(&conn->ntlm);
491 ntlm_wb_cleanup(&conn->proxyntlm);
494 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */