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