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