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