Home | History | Annotate | Download | only in lib
      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) 1998 - 2016, 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 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  * http://davenport.sourceforge.net/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_wb.h"
     52 #include "url.h"
     53 #include "strerror.h"
     54 #include "strdup.h"
     55 /* The last 3 #include files should be in this order */
     56 #include "curl_printf.h"
     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 = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE);
    298     if(!newbuf)
    299       return CURLE_OUT_OF_MEMORY;
    300 
    301     buf = newbuf;
    302   }
    303 
    304   /* Samba/winbind installed but not configured */
    305   if(state == NTLMSTATE_TYPE1 &&
    306      len_out == 3 &&
    307      buf[0] == 'P' && buf[1] == 'W')
    308     goto done;
    309   /* invalid response */
    310   if(len_out < 4)
    311     goto done;
    312   if(state == NTLMSTATE_TYPE1 &&
    313      (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
    314     goto done;
    315   if(state == NTLMSTATE_TYPE2 &&
    316      (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
    317      (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
    318     goto done;
    319 
    320   conn->response_header = aprintf("NTLM %.*s", len_out - 4, buf + 3);
    321   free(buf);
    322   return CURLE_OK;
    323 done:
    324   free(buf);
    325   return CURLE_REMOTE_ACCESS_DENIED;
    326 }
    327 
    328 /*
    329  * This is for creating ntlm header output by delegating challenge/response
    330  * to Samba's winbind daemon helper ntlm_auth.
    331  */
    332 CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
    333                               bool proxy)
    334 {
    335   /* point to the address of the pointer that holds the string to send to the
    336      server, which is for a plain host or for a HTTP proxy */
    337   char **allocuserpwd;
    338   /* point to the name and password for this */
    339   const char *userp;
    340   /* point to the correct struct with this */
    341   struct ntlmdata *ntlm;
    342   struct auth *authp;
    343 
    344   CURLcode res = CURLE_OK;
    345   char *input;
    346 
    347   DEBUGASSERT(conn);
    348   DEBUGASSERT(conn->data);
    349 
    350   if(proxy) {
    351     allocuserpwd = &conn->allocptr.proxyuserpwd;
    352     userp = conn->http_proxy.user;
    353     ntlm = &conn->proxyntlm;
    354     authp = &conn->data->state.authproxy;
    355   }
    356   else {
    357     allocuserpwd = &conn->allocptr.userpwd;
    358     userp = conn->user;
    359     ntlm = &conn->ntlm;
    360     authp = &conn->data->state.authhost;
    361   }
    362   authp->done = FALSE;
    363 
    364   /* not set means empty */
    365   if(!userp)
    366     userp="";
    367 
    368   switch(ntlm->state) {
    369   case NTLMSTATE_TYPE1:
    370   default:
    371     /* Use Samba's 'winbind' daemon to support NTLM authentication,
    372      * by delegating the NTLM challenge/response protocal to a helper
    373      * in ntlm_auth.
    374      * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
    375      * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
    376      * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
    377      * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
    378      * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
    379      * filename of ntlm_auth helper.
    380      * If NTLM authentication using winbind fails, go back to original
    381      * request handling process.
    382      */
    383     /* Create communication with ntlm_auth */
    384     res = ntlm_wb_init(conn, userp);
    385     if(res)
    386       return res;
    387     res = ntlm_wb_response(conn, "YR\n", ntlm->state);
    388     if(res)
    389       return res;
    390 
    391     free(*allocuserpwd);
    392     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
    393                             proxy ? "Proxy-" : "",
    394                             conn->response_header);
    395     DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
    396     free(conn->response_header);
    397     conn->response_header = NULL;
    398     break;
    399   case NTLMSTATE_TYPE2:
    400     input = aprintf("TT %s\n", conn->challenge_header);
    401     if(!input)
    402       return CURLE_OUT_OF_MEMORY;
    403     res = ntlm_wb_response(conn, input, ntlm->state);
    404     free(input);
    405     input = NULL;
    406     if(res)
    407       return res;
    408 
    409     free(*allocuserpwd);
    410     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
    411                             proxy ? "Proxy-" : "",
    412                             conn->response_header);
    413     DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
    414     ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */
    415     authp->done = TRUE;
    416     Curl_ntlm_wb_cleanup(conn);
    417     break;
    418   case NTLMSTATE_TYPE3:
    419     /* connection is already authenticated,
    420      * don't send a header in future requests */
    421     free(*allocuserpwd);
    422     *allocuserpwd=NULL;
    423     authp->done = TRUE;
    424     break;
    425   }
    426 
    427   return CURLE_OK;
    428 }
    429 
    430 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */
    431