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 /* The last 3 #include files should be in this order */
     55 #include "curl_printf.h"
     56 #include "curl_memory.h"
     57 #include "memdebug.h"
     58 
     59 #if DEBUG_ME
     60 # define DEBUG_OUT(x) x
     61 #else
     62 # define DEBUG_OUT(x) Curl_nop_stmt
     63 #endif
     64 
     65 /* Portable 'sclose_nolog' used only in child process instead of 'sclose'
     66    to avoid fooling the socket leak detector */
     67 #if defined(HAVE_CLOSESOCKET)
     68 #  define sclose_nolog(x)  closesocket((x))
     69 #elif defined(HAVE_CLOSESOCKET_CAMEL)
     70 #  define sclose_nolog(x)  CloseSocket((x))
     71 #else
     72 #  define sclose_nolog(x)  close((x))
     73 #endif
     74 
     75 void Curl_ntlm_wb_cleanup(struct connectdata *conn)
     76 {
     77   if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) {
     78     sclose(conn->ntlm_auth_hlpr_socket);
     79     conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD;
     80   }
     81 
     82   if(conn->ntlm_auth_hlpr_pid) {
     83     int i;
     84     for(i = 0; i < 4; i++) {
     85       pid_t ret = waitpid(conn->ntlm_auth_hlpr_pid, NULL, WNOHANG);
     86       if(ret == conn->ntlm_auth_hlpr_pid || errno == ECHILD)
     87         break;
     88       switch(i) {
     89       case 0:
     90         kill(conn->ntlm_auth_hlpr_pid, SIGTERM);
     91         break;
     92       case 1:
     93         /* Give the process another moment to shut down cleanly before
     94            bringing down the axe */
     95         Curl_wait_ms(1);
     96         break;
     97       case 2:
     98         kill(conn->ntlm_auth_hlpr_pid, SIGKILL);
     99         break;
    100       case 3:
    101         break;
    102       }
    103     }
    104     conn->ntlm_auth_hlpr_pid = 0;
    105   }
    106 
    107   free(conn->challenge_header);
    108   conn->challenge_header = NULL;
    109   free(conn->response_header);
    110   conn->response_header = NULL;
    111 }
    112 
    113 static CURLcode ntlm_wb_init(struct connectdata *conn, const char *userp)
    114 {
    115   curl_socket_t sockfds[2];
    116   pid_t child_pid;
    117   const char *username;
    118   char *slash, *domain = NULL;
    119   const char *ntlm_auth = NULL;
    120   char *ntlm_auth_alloc = NULL;
    121 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
    122   struct passwd pw, *pw_res;
    123   char pwbuf[1024];
    124 #endif
    125   int error;
    126 
    127   /* Return if communication with ntlm_auth already set up */
    128   if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD ||
    129      conn->ntlm_auth_hlpr_pid)
    130     return CURLE_OK;
    131 
    132   username = userp;
    133   /* The real ntlm_auth really doesn't like being invoked with an
    134      empty username. It won't make inferences for itself, and expects
    135      the client to do so (mostly because it's really designed for
    136      servers like squid to use for auth, and client support is an
    137      afterthought for it). So try hard to provide a suitable username
    138      if we don't already have one. But if we can't, provide the
    139      empty one anyway. Perhaps they have an implementation of the
    140      ntlm_auth helper which *doesn't* need it so we might as well try */
    141   if(!username || !username[0]) {
    142     username = getenv("NTLMUSER");
    143     if(!username || !username[0])
    144       username = getenv("LOGNAME");
    145     if(!username || !username[0])
    146       username = getenv("USER");
    147 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
    148     if((!username || !username[0]) &&
    149        !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) &&
    150        pw_res) {
    151       username = pw.pw_name;
    152     }
    153 #endif
    154     if(!username || !username[0])
    155       username = userp;
    156   }
    157   slash = strpbrk(username, "\\/");
    158   if(slash) {
    159     if((domain = strdup(username)) == NULL)
    160       return CURLE_OUT_OF_MEMORY;
    161     slash = domain + (slash - username);
    162     *slash = '\0';
    163     username = username + (slash - domain) + 1;
    164   }
    165 
    166   /* For testing purposes, when DEBUGBUILD is defined and environment
    167      variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
    168      NTLM challenge/response which only accepts commands and output
    169      strings pre-written in test case definitions */
    170 #ifdef DEBUGBUILD
    171   ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
    172   if(ntlm_auth_alloc)
    173     ntlm_auth = ntlm_auth_alloc;
    174   else
    175 #endif
    176     ntlm_auth = NTLM_WB_FILE;
    177 
    178   if(access(ntlm_auth, X_OK) != 0) {
    179     error = ERRNO;
    180     failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s",
    181           ntlm_auth, error, Curl_strerror(conn, error));
    182     goto done;
    183   }
    184 
    185   if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
    186     error = ERRNO;
    187     failf(conn->data, "Could not open socket pair. errno %d: %s",
    188           error, Curl_strerror(conn, error));
    189     goto done;
    190   }
    191 
    192   child_pid = fork();
    193   if(child_pid == -1) {
    194     error = ERRNO;
    195     sclose(sockfds[0]);
    196     sclose(sockfds[1]);
    197     failf(conn->data, "Could not fork. errno %d: %s",
    198           error, Curl_strerror(conn, error));
    199     goto done;
    200   }
    201   else if(!child_pid) {
    202     /*
    203      * child process
    204      */
    205 
    206     /* Don't use sclose in the child since it fools the socket leak detector */
    207     sclose_nolog(sockfds[0]);
    208     if(dup2(sockfds[1], STDIN_FILENO) == -1) {
    209       error = ERRNO;
    210       failf(conn->data, "Could not redirect child stdin. errno %d: %s",
    211             error, Curl_strerror(conn, error));
    212       exit(1);
    213     }
    214 
    215     if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
    216       error = ERRNO;
    217       failf(conn->data, "Could not redirect child stdout. errno %d: %s",
    218             error, Curl_strerror(conn, error));
    219       exit(1);
    220     }
    221 
    222     if(domain)
    223       execl(ntlm_auth, ntlm_auth,
    224             "--helper-protocol", "ntlmssp-client-1",
    225             "--use-cached-creds",
    226             "--username", username,
    227             "--domain", domain,
    228             NULL);
    229     else
    230       execl(ntlm_auth, ntlm_auth,
    231             "--helper-protocol", "ntlmssp-client-1",
    232             "--use-cached-creds",
    233             "--username", username,
    234             NULL);
    235 
    236     error = ERRNO;
    237     sclose_nolog(sockfds[1]);
    238     failf(conn->data, "Could not execl(). errno %d: %s",
    239           error, Curl_strerror(conn, error));
    240     exit(1);
    241   }
    242 
    243   sclose(sockfds[1]);
    244   conn->ntlm_auth_hlpr_socket = sockfds[0];
    245   conn->ntlm_auth_hlpr_pid = child_pid;
    246   free(domain);
    247   free(ntlm_auth_alloc);
    248   return CURLE_OK;
    249 
    250 done:
    251   free(domain);
    252   free(ntlm_auth_alloc);
    253   return CURLE_REMOTE_ACCESS_DENIED;
    254 }
    255 
    256 static CURLcode ntlm_wb_response(struct connectdata *conn,
    257                                  const char *input, curlntlm state)
    258 {
    259   char *buf = malloc(NTLM_BUFSIZE);
    260   size_t len_in = strlen(input), len_out = 0;
    261 
    262   if(!buf)
    263     return CURLE_OUT_OF_MEMORY;
    264 
    265   while(len_in > 0) {
    266     ssize_t written = swrite(conn->ntlm_auth_hlpr_socket, input, len_in);
    267     if(written == -1) {
    268       /* Interrupted by a signal, retry it */
    269       if(errno == EINTR)
    270         continue;
    271       /* write failed if other errors happen */
    272       goto done;
    273     }
    274     input += written;
    275     len_in -= written;
    276   }
    277   /* Read one line */
    278   while(1) {
    279     ssize_t size;
    280     char *newbuf;
    281 
    282     size = sread(conn->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE);
    283     if(size == -1) {
    284       if(errno == EINTR)
    285         continue;
    286       goto done;
    287     }
    288     else if(size == 0)
    289       goto done;
    290 
    291     len_out += size;
    292     if(buf[len_out - 1] == '\n') {
    293       buf[len_out - 1] = '\0';
    294       break;
    295     }
    296     newbuf = realloc(buf, len_out + NTLM_BUFSIZE);
    297     if(!newbuf) {
    298       free(buf);
    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->proxyuser;
    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