1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 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 * 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.net/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#include <signal.h> 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#ifdef HAVE_PIPE 72# define sclose_nolog(x) close((x)) 73#elif 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(wakeup_create(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 wakeup_close(sockfds[0]); 203 wakeup_close(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[1024]; 270 Curl_dyn_init(&b, MAX_NTLM_WB_RESPONSE); 271 272 while(len_in > 0) { 273 ssize_t written = wakeup_write(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 wakeup_read(ntlm->ntlm_auth_hlpr_socket, buf, sizeof(buf)); 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 an 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