1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2016, 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.haxx.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 ***************************************************************************/ 22 23#include "curl_setup.h" 24 25#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ 26 defined(NTLM_WB_ENABLED) 27 28/* 29 * NTLM details: 30 * 31 * http://davenport.sourceforge.net/ntlm.html 32 * https://www.innovation.ch/java/ntlm.html 33 */ 34 35#define DEBUG_ME 0 36 37#ifdef HAVE_SYS_WAIT_H 38#include <sys/wait.h> 39#endif 40#ifdef HAVE_SIGNAL_H 41#include <signal.h> 42#endif 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_wb.h" 52#include "url.h" 53#include "strerror.h" 54/* The last 3 #include files should be in this order */ 55#include "curl_printf.h" 56#include "curl_memory.h" 57#include "memdebug.h" 58 59#if DEBUG_ME 60# define DEBUG_OUT(x) x 61#else 62# define DEBUG_OUT(x) Curl_nop_stmt 63#endif 64 65/* Portable 'sclose_nolog' used only in child process instead of 'sclose' 66 to avoid fooling the socket leak detector */ 67#if defined(HAVE_CLOSESOCKET) 68# define sclose_nolog(x) closesocket((x)) 69#elif defined(HAVE_CLOSESOCKET_CAMEL) 70# define sclose_nolog(x) CloseSocket((x)) 71#else 72# define sclose_nolog(x) close((x)) 73#endif 74 75void Curl_ntlm_wb_cleanup(struct connectdata *conn) 76{ 77 if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) { 78 sclose(conn->ntlm_auth_hlpr_socket); 79 conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; 80 } 81 82 if(conn->ntlm_auth_hlpr_pid) { 83 int i; 84 for(i = 0; i < 4; i++) { 85 pid_t ret = waitpid(conn->ntlm_auth_hlpr_pid, NULL, WNOHANG); 86 if(ret == conn->ntlm_auth_hlpr_pid || errno == ECHILD) 87 break; 88 switch(i) { 89 case 0: 90 kill(conn->ntlm_auth_hlpr_pid, SIGTERM); 91 break; 92 case 1: 93 /* Give the process another moment to shut down cleanly before 94 bringing down the axe */ 95 Curl_wait_ms(1); 96 break; 97 case 2: 98 kill(conn->ntlm_auth_hlpr_pid, SIGKILL); 99 break; 100 case 3: 101 break; 102 } 103 } 104 conn->ntlm_auth_hlpr_pid = 0; 105 } 106 107 free(conn->challenge_header); 108 conn->challenge_header = NULL; 109 free(conn->response_header); 110 conn->response_header = NULL; 111} 112 113static CURLcode ntlm_wb_init(struct connectdata *conn, const char *userp) 114{ 115 curl_socket_t sockfds[2]; 116 pid_t child_pid; 117 const char *username; 118 char *slash, *domain = NULL; 119 const char *ntlm_auth = NULL; 120 char *ntlm_auth_alloc = NULL; 121#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) 122 struct passwd pw, *pw_res; 123 char pwbuf[1024]; 124#endif 125 int error; 126 127 /* Return if communication with ntlm_auth already set up */ 128 if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD || 129 conn->ntlm_auth_hlpr_pid) 130 return CURLE_OK; 131 132 username = userp; 133 /* The real ntlm_auth really doesn't like being invoked with an 134 empty username. It won't make inferences for itself, and expects 135 the client to do so (mostly because it's really designed for 136 servers like squid to use for auth, and client support is an 137 afterthought for it). So try hard to provide a suitable username 138 if we don't already have one. But if we can't, provide the 139 empty one anyway. Perhaps they have an implementation of the 140 ntlm_auth helper which *doesn't* need it so we might as well try */ 141 if(!username || !username[0]) { 142 username = getenv("NTLMUSER"); 143 if(!username || !username[0]) 144 username = getenv("LOGNAME"); 145 if(!username || !username[0]) 146 username = getenv("USER"); 147#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) 148 if((!username || !username[0]) && 149 !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) && 150 pw_res) { 151 username = pw.pw_name; 152 } 153#endif 154 if(!username || !username[0]) 155 username = userp; 156 } 157 slash = strpbrk(username, "\\/"); 158 if(slash) { 159 if((domain = strdup(username)) == NULL) 160 return CURLE_OUT_OF_MEMORY; 161 slash = domain + (slash - username); 162 *slash = '\0'; 163 username = username + (slash - domain) + 1; 164 } 165 166 /* For testing purposes, when DEBUGBUILD is defined and environment 167 variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform 168 NTLM challenge/response which only accepts commands and output 169 strings pre-written in test case definitions */ 170#ifdef DEBUGBUILD 171 ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE"); 172 if(ntlm_auth_alloc) 173 ntlm_auth = ntlm_auth_alloc; 174 else 175#endif 176 ntlm_auth = NTLM_WB_FILE; 177 178 if(access(ntlm_auth, X_OK) != 0) { 179 error = ERRNO; 180 failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s", 181 ntlm_auth, error, Curl_strerror(conn, error)); 182 goto done; 183 } 184 185 if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) { 186 error = ERRNO; 187 failf(conn->data, "Could not open socket pair. errno %d: %s", 188 error, Curl_strerror(conn, error)); 189 goto done; 190 } 191 192 child_pid = fork(); 193 if(child_pid == -1) { 194 error = ERRNO; 195 sclose(sockfds[0]); 196 sclose(sockfds[1]); 197 failf(conn->data, "Could not fork. errno %d: %s", 198 error, Curl_strerror(conn, error)); 199 goto done; 200 } 201 else if(!child_pid) { 202 /* 203 * child process 204 */ 205 206 /* Don't use sclose in the child since it fools the socket leak detector */ 207 sclose_nolog(sockfds[0]); 208 if(dup2(sockfds[1], STDIN_FILENO) == -1) { 209 error = ERRNO; 210 failf(conn->data, "Could not redirect child stdin. errno %d: %s", 211 error, Curl_strerror(conn, error)); 212 exit(1); 213 } 214 215 if(dup2(sockfds[1], STDOUT_FILENO) == -1) { 216 error = ERRNO; 217 failf(conn->data, "Could not redirect child stdout. errno %d: %s", 218 error, Curl_strerror(conn, error)); 219 exit(1); 220 } 221 222 if(domain) 223 execl(ntlm_auth, ntlm_auth, 224 "--helper-protocol", "ntlmssp-client-1", 225 "--use-cached-creds", 226 "--username", username, 227 "--domain", domain, 228 NULL); 229 else 230 execl(ntlm_auth, ntlm_auth, 231 "--helper-protocol", "ntlmssp-client-1", 232 "--use-cached-creds", 233 "--username", username, 234 NULL); 235 236 error = ERRNO; 237 sclose_nolog(sockfds[1]); 238 failf(conn->data, "Could not execl(). errno %d: %s", 239 error, Curl_strerror(conn, error)); 240 exit(1); 241 } 242 243 sclose(sockfds[1]); 244 conn->ntlm_auth_hlpr_socket = sockfds[0]; 245 conn->ntlm_auth_hlpr_pid = child_pid; 246 free(domain); 247 free(ntlm_auth_alloc); 248 return CURLE_OK; 249 250done: 251 free(domain); 252 free(ntlm_auth_alloc); 253 return CURLE_REMOTE_ACCESS_DENIED; 254} 255 256static CURLcode ntlm_wb_response(struct connectdata *conn, 257 const char *input, curlntlm state) 258{ 259 char *buf = malloc(NTLM_BUFSIZE); 260 size_t len_in = strlen(input), len_out = 0; 261 262 if(!buf) 263 return CURLE_OUT_OF_MEMORY; 264 265 while(len_in > 0) { 266 ssize_t written = swrite(conn->ntlm_auth_hlpr_socket, input, len_in); 267 if(written == -1) { 268 /* Interrupted by a signal, retry it */ 269 if(errno == EINTR) 270 continue; 271 /* write failed if other errors happen */ 272 goto done; 273 } 274 input += written; 275 len_in -= written; 276 } 277 /* Read one line */ 278 while(1) { 279 ssize_t size; 280 char *newbuf; 281 282 size = sread(conn->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE); 283 if(size == -1) { 284 if(errno == EINTR) 285 continue; 286 goto done; 287 } 288 else if(size == 0) 289 goto done; 290 291 len_out += size; 292 if(buf[len_out - 1] == '\n') { 293 buf[len_out - 1] = '\0'; 294 break; 295 } 296 newbuf = realloc(buf, len_out + NTLM_BUFSIZE); 297 if(!newbuf) { 298 free(buf); 299 return CURLE_OUT_OF_MEMORY; 300 } 301 buf = newbuf; 302 } 303 304 /* Samba/winbind installed but not configured */ 305 if(state == NTLMSTATE_TYPE1 && 306 len_out == 3 && 307 buf[0] == 'P' && buf[1] == 'W') 308 goto done; 309 /* invalid response */ 310 if(len_out < 4) 311 goto done; 312 if(state == NTLMSTATE_TYPE1 && 313 (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' ')) 314 goto done; 315 if(state == NTLMSTATE_TYPE2 && 316 (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') && 317 (buf[0]!='A' || buf[1]!='F' || buf[2]!=' ')) 318 goto done; 319 320 conn->response_header = aprintf("NTLM %.*s", len_out - 4, buf + 3); 321 free(buf); 322 return CURLE_OK; 323done: 324 free(buf); 325 return CURLE_REMOTE_ACCESS_DENIED; 326} 327 328/* 329 * This is for creating ntlm header output by delegating challenge/response 330 * to Samba's winbind daemon helper ntlm_auth. 331 */ 332CURLcode Curl_output_ntlm_wb(struct connectdata *conn, 333 bool proxy) 334{ 335 /* point to the address of the pointer that holds the string to send to the 336 server, which is for a plain host or for a HTTP proxy */ 337 char **allocuserpwd; 338 /* point to the name and password for this */ 339 const char *userp; 340 /* point to the correct struct with this */ 341 struct ntlmdata *ntlm; 342 struct auth *authp; 343 344 CURLcode res = CURLE_OK; 345 char *input; 346 347 DEBUGASSERT(conn); 348 DEBUGASSERT(conn->data); 349 350 if(proxy) { 351 allocuserpwd = &conn->allocptr.proxyuserpwd; 352 userp = conn->proxyuser; 353 ntlm = &conn->proxyntlm; 354 authp = &conn->data->state.authproxy; 355 } 356 else { 357 allocuserpwd = &conn->allocptr.userpwd; 358 userp = conn->user; 359 ntlm = &conn->ntlm; 360 authp = &conn->data->state.authhost; 361 } 362 authp->done = FALSE; 363 364 /* not set means empty */ 365 if(!userp) 366 userp=""; 367 368 switch(ntlm->state) { 369 case NTLMSTATE_TYPE1: 370 default: 371 /* Use Samba's 'winbind' daemon to support NTLM authentication, 372 * by delegating the NTLM challenge/response protocal to a helper 373 * in ntlm_auth. 374 * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html 375 * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html 376 * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html 377 * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this 378 * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute 379 * filename of ntlm_auth helper. 380 * If NTLM authentication using winbind fails, go back to original 381 * request handling process. 382 */ 383 /* Create communication with ntlm_auth */ 384 res = ntlm_wb_init(conn, userp); 385 if(res) 386 return res; 387 res = ntlm_wb_response(conn, "YR\n", ntlm->state); 388 if(res) 389 return res; 390 391 free(*allocuserpwd); 392 *allocuserpwd = aprintf("%sAuthorization: %s\r\n", 393 proxy ? "Proxy-" : "", 394 conn->response_header); 395 DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd)); 396 free(conn->response_header); 397 conn->response_header = NULL; 398 break; 399 case NTLMSTATE_TYPE2: 400 input = aprintf("TT %s\n", conn->challenge_header); 401 if(!input) 402 return CURLE_OUT_OF_MEMORY; 403 res = ntlm_wb_response(conn, input, ntlm->state); 404 free(input); 405 input = NULL; 406 if(res) 407 return res; 408 409 free(*allocuserpwd); 410 *allocuserpwd = aprintf("%sAuthorization: %s\r\n", 411 proxy ? "Proxy-" : "", 412 conn->response_header); 413 DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd)); 414 ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */ 415 authp->done = TRUE; 416 Curl_ntlm_wb_cleanup(conn); 417 break; 418 case NTLMSTATE_TYPE3: 419 /* connection is already authenticated, 420 * don't send a header in future requests */ 421 free(*allocuserpwd); 422 *allocuserpwd=NULL; 423 authp->done = TRUE; 424 break; 425 } 426 427 return CURLE_OK; 428} 429 430#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */ 431