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