Home | History | Annotate | Download | only in lib
      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel (at) 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(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  * http://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 "curl_ntlm_msgs.h"
     51 #include "curl_ntlm_wb.h"
     52 #include "url.h"
     53 #include "strerror.h"
     54 #include "curl_printf.h"
     55 
     56 /* The last #include files should be: */
     57 #include "curl_memory.h"
     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 
     76 void 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   free(conn->challenge_header);
    109   conn->challenge_header = NULL;
    110   free(conn->response_header);
    111   conn->response_header = NULL;
    112 }
    113 
    114 static 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 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
    123   struct passwd pw, *pw_res;
    124   char pwbuf[1024];
    125 #endif
    126   int error;
    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     if((domain = strdup(username)) == NULL)
    161       return CURLE_OUT_OF_MEMORY;
    162     slash = domain + (slash - username);
    163     *slash = '\0';
    164     username = username + (slash - domain) + 1;
    165   }
    166 
    167   /* For testing purposes, when DEBUGBUILD is defined and environment
    168      variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
    169      NTLM challenge/response which only accepts commands and output
    170      strings pre-written in test case definitions */
    171 #ifdef DEBUGBUILD
    172   ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
    173   if(ntlm_auth_alloc)
    174     ntlm_auth = ntlm_auth_alloc;
    175   else
    176 #endif
    177     ntlm_auth = NTLM_WB_FILE;
    178 
    179   if(access(ntlm_auth, X_OK) != 0) {
    180     error = ERRNO;
    181     failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s",
    182           ntlm_auth, error, Curl_strerror(conn, error));
    183     goto done;
    184   }
    185 
    186   if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
    187     error = ERRNO;
    188     failf(conn->data, "Could not open socket pair. errno %d: %s",
    189           error, Curl_strerror(conn, error));
    190     goto done;
    191   }
    192 
    193   child_pid = fork();
    194   if(child_pid == -1) {
    195     error = ERRNO;
    196     sclose(sockfds[0]);
    197     sclose(sockfds[1]);
    198     failf(conn->data, "Could not fork. errno %d: %s",
    199           error, Curl_strerror(conn, error));
    200     goto done;
    201   }
    202   else if(!child_pid) {
    203     /*
    204      * child process
    205      */
    206 
    207     /* Don't use sclose in the child since it fools the socket leak detector */
    208     sclose_nolog(sockfds[0]);
    209     if(dup2(sockfds[1], STDIN_FILENO) == -1) {
    210       error = ERRNO;
    211       failf(conn->data, "Could not redirect child stdin. errno %d: %s",
    212             error, Curl_strerror(conn, error));
    213       exit(1);
    214     }
    215 
    216     if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
    217       error = ERRNO;
    218       failf(conn->data, "Could not redirect child stdout. errno %d: %s",
    219             error, Curl_strerror(conn, error));
    220       exit(1);
    221     }
    222 
    223     if(domain)
    224       execl(ntlm_auth, ntlm_auth,
    225             "--helper-protocol", "ntlmssp-client-1",
    226             "--use-cached-creds",
    227             "--username", username,
    228             "--domain", domain,
    229             NULL);
    230     else
    231       execl(ntlm_auth, ntlm_auth,
    232             "--helper-protocol", "ntlmssp-client-1",
    233             "--use-cached-creds",
    234             "--username", username,
    235             NULL);
    236 
    237     error = ERRNO;
    238     sclose_nolog(sockfds[1]);
    239     failf(conn->data, "Could not execl(). errno %d: %s",
    240           error, Curl_strerror(conn, error));
    241     exit(1);
    242   }
    243 
    244   sclose(sockfds[1]);
    245   conn->ntlm_auth_hlpr_socket = sockfds[0];
    246   conn->ntlm_auth_hlpr_pid = child_pid;
    247   free(domain);
    248   free(ntlm_auth_alloc);
    249   return CURLE_OK;
    250 
    251 done:
    252   free(domain);
    253   free(ntlm_auth_alloc);
    254   return CURLE_REMOTE_ACCESS_DENIED;
    255 }
    256 
    257 static CURLcode ntlm_wb_response(struct connectdata *conn,
    258                                  const char *input, curlntlm state)
    259 {
    260   char *buf = malloc(NTLM_BUFSIZE);
    261   size_t len_in = strlen(input), len_out = 0;
    262 
    263   if(!buf)
    264     return CURLE_OUT_OF_MEMORY;
    265 
    266   while(len_in > 0) {
    267     ssize_t written = swrite(conn->ntlm_auth_hlpr_socket, input, len_in);
    268     if(written == -1) {
    269       /* Interrupted by a signal, retry it */
    270       if(errno == EINTR)
    271         continue;
    272       /* write failed if other errors happen */
    273       goto done;
    274     }
    275     input += written;
    276     len_in -= written;
    277   }
    278   /* Read one line */
    279   while(1) {
    280     ssize_t size;
    281     char *newbuf;
    282 
    283     size = sread(conn->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE);
    284     if(size == -1) {
    285       if(errno == EINTR)
    286         continue;
    287       goto done;
    288     }
    289     else if(size == 0)
    290       goto done;
    291 
    292     len_out += size;
    293     if(buf[len_out - 1] == '\n') {
    294       buf[len_out - 1] = '\0';
    295       break;
    296     }
    297     newbuf = realloc(buf, len_out + NTLM_BUFSIZE);
    298     if(!newbuf) {
    299       free(buf);
    300       return CURLE_OUT_OF_MEMORY;
    301     }
    302     buf = newbuf;
    303   }
    304 
    305   /* Samba/winbind installed but not configured */
    306   if(state == NTLMSTATE_TYPE1 &&
    307      len_out == 3 &&
    308      buf[0] == 'P' && buf[1] == 'W')
    309     return CURLE_REMOTE_ACCESS_DENIED;
    310   /* invalid response */
    311   if(len_out < 4)
    312     goto done;
    313   if(state == NTLMSTATE_TYPE1 &&
    314      (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
    315     goto done;
    316   if(state == NTLMSTATE_TYPE2 &&
    317      (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
    318      (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
    319     goto done;
    320 
    321   conn->response_header = aprintf("NTLM %.*s", len_out - 4, buf + 3);
    322   free(buf);
    323   return CURLE_OK;
    324 done:
    325   free(buf);
    326   return CURLE_REMOTE_ACCESS_DENIED;
    327 }
    328 
    329 /*
    330  * This is for creating ntlm header output by delegating challenge/response
    331  * to Samba's winbind daemon helper ntlm_auth.
    332  */
    333 CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
    334                               bool proxy)
    335 {
    336   /* point to the address of the pointer that holds the string to send to the
    337      server, which is for a plain host or for a HTTP proxy */
    338   char **allocuserpwd;
    339   /* point to the name and password for this */
    340   const char *userp;
    341   /* point to the correct struct with this */
    342   struct ntlmdata *ntlm;
    343   struct auth *authp;
    344 
    345   CURLcode res = CURLE_OK;
    346   char *input;
    347 
    348   DEBUGASSERT(conn);
    349   DEBUGASSERT(conn->data);
    350 
    351   if(proxy) {
    352     allocuserpwd = &conn->allocptr.proxyuserpwd;
    353     userp = conn->proxyuser;
    354     ntlm = &conn->proxyntlm;
    355     authp = &conn->data->state.authproxy;
    356   }
    357   else {
    358     allocuserpwd = &conn->allocptr.userpwd;
    359     userp = conn->user;
    360     ntlm = &conn->ntlm;
    361     authp = &conn->data->state.authhost;
    362   }
    363   authp->done = FALSE;
    364 
    365   /* not set means empty */
    366   if(!userp)
    367     userp="";
    368 
    369   switch(ntlm->state) {
    370   case NTLMSTATE_TYPE1:
    371   default:
    372     /* Use Samba's 'winbind' daemon to support NTLM authentication,
    373      * by delegating the NTLM challenge/response protocal to a helper
    374      * in ntlm_auth.
    375      * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
    376      * http://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
    377      * http://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
    378      * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
    379      * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
    380      * filename of ntlm_auth helper.
    381      * If NTLM authentication using winbind fails, go back to original
    382      * request handling process.
    383      */
    384     /* Create communication with ntlm_auth */
    385     res = ntlm_wb_init(conn, userp);
    386     if(res)
    387       return res;
    388     res = ntlm_wb_response(conn, "YR\n", ntlm->state);
    389     if(res)
    390       return res;
    391 
    392     free(*allocuserpwd);
    393     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
    394                             proxy ? "Proxy-" : "",
    395                             conn->response_header);
    396     DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
    397     free(conn->response_header);
    398     conn->response_header = NULL;
    399     break;
    400   case NTLMSTATE_TYPE2:
    401     input = aprintf("TT %s\n", conn->challenge_header);
    402     if(!input)
    403       return CURLE_OUT_OF_MEMORY;
    404     res = ntlm_wb_response(conn, input, ntlm->state);
    405     free(input);
    406     input = NULL;
    407     if(res)
    408       return res;
    409 
    410     free(*allocuserpwd);
    411     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
    412                             proxy ? "Proxy-" : "",
    413                             conn->response_header);
    414     DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
    415     ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */
    416     authp->done = TRUE;
    417     Curl_ntlm_wb_cleanup(conn);
    418     break;
    419   case NTLMSTATE_TYPE3:
    420     /* connection is already authenticated,
    421      * don't send a header in future requests */
    422     free(*allocuserpwd);
    423     *allocuserpwd=NULL;
    424     authp->done = TRUE;
    425     break;
    426   }
    427 
    428   return CURLE_OK;
    429 }
    430 
    431 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */
    432