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