Annotation of embedaddon/curl/lib/curl_ntlm_wb.c, revision 1.1.1.1

1.1       misho       1: /***************************************************************************
                      2:  *                                  _   _ ____  _
                      3:  *  Project                     ___| | | |  _ \| |
                      4:  *                             / __| | | | |_) | |
                      5:  *                            | (__| |_| |  _ <| |___
                      6:  *                             \___|\___/|_| \_\_____|
                      7:  *
                      8:  * Copyright (C) 1998 - 2020, 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 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: #include "strcase.h"
                     57: 
                     58: /* The last 3 #include files should be in this order */
                     59: #include "curl_printf.h"
                     60: #include "curl_memory.h"
                     61: #include "memdebug.h"
                     62: 
                     63: #if DEBUG_ME
                     64: # define DEBUG_OUT(x) x
                     65: #else
                     66: # define DEBUG_OUT(x) Curl_nop_stmt
                     67: #endif
                     68: 
                     69: /* Portable 'sclose_nolog' used only in child process instead of 'sclose'
                     70:    to avoid fooling the socket leak detector */
                     71: #if defined(HAVE_CLOSESOCKET)
                     72: #  define sclose_nolog(x)  closesocket((x))
                     73: #elif defined(HAVE_CLOSESOCKET_CAMEL)
                     74: #  define sclose_nolog(x)  CloseSocket((x))
                     75: #else
                     76: #  define sclose_nolog(x)  close((x))
                     77: #endif
                     78: 
                     79: static void ntlm_wb_cleanup(struct ntlmdata *ntlm)
                     80: {
                     81:   if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) {
                     82:     sclose(ntlm->ntlm_auth_hlpr_socket);
                     83:     ntlm->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD;
                     84:   }
                     85: 
                     86:   if(ntlm->ntlm_auth_hlpr_pid) {
                     87:     int i;
                     88:     for(i = 0; i < 4; i++) {
                     89:       pid_t ret = waitpid(ntlm->ntlm_auth_hlpr_pid, NULL, WNOHANG);
                     90:       if(ret == ntlm->ntlm_auth_hlpr_pid || errno == ECHILD)
                     91:         break;
                     92:       switch(i) {
                     93:       case 0:
                     94:         kill(ntlm->ntlm_auth_hlpr_pid, SIGTERM);
                     95:         break;
                     96:       case 1:
                     97:         /* Give the process another moment to shut down cleanly before
                     98:            bringing down the axe */
                     99:         Curl_wait_ms(1);
                    100:         break;
                    101:       case 2:
                    102:         kill(ntlm->ntlm_auth_hlpr_pid, SIGKILL);
                    103:         break;
                    104:       case 3:
                    105:         break;
                    106:       }
                    107:     }
                    108:     ntlm->ntlm_auth_hlpr_pid = 0;
                    109:   }
                    110: 
                    111:   Curl_safefree(ntlm->challenge);
                    112:   Curl_safefree(ntlm->response);
                    113: }
                    114: 
                    115: static CURLcode ntlm_wb_init(struct Curl_easy *data, struct ntlmdata *ntlm,
                    116:                              const char *userp)
                    117: {
                    118:   curl_socket_t sockfds[2];
                    119:   pid_t child_pid;
                    120:   const char *username;
                    121:   char *slash, *domain = NULL;
                    122:   const char *ntlm_auth = NULL;
                    123:   char *ntlm_auth_alloc = NULL;
                    124: #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
                    125:   struct passwd pw, *pw_res;
                    126:   char pwbuf[1024];
                    127: #endif
                    128:   char buffer[STRERROR_LEN];
                    129: 
                    130: #if defined(CURL_DISABLE_VERBOSE_STRINGS)
                    131:   (void) data;
                    132: #endif
                    133: 
                    134:   /* Return if communication with ntlm_auth already set up */
                    135:   if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD ||
                    136:      ntlm->ntlm_auth_hlpr_pid)
                    137:     return CURLE_OK;
                    138: 
                    139:   username = userp;
                    140:   /* The real ntlm_auth really doesn't like being invoked with an
                    141:      empty username. It won't make inferences for itself, and expects
                    142:      the client to do so (mostly because it's really designed for
                    143:      servers like squid to use for auth, and client support is an
                    144:      afterthought for it). So try hard to provide a suitable username
                    145:      if we don't already have one. But if we can't, provide the
                    146:      empty one anyway. Perhaps they have an implementation of the
                    147:      ntlm_auth helper which *doesn't* need it so we might as well try */
                    148:   if(!username || !username[0]) {
                    149:     username = getenv("NTLMUSER");
                    150:     if(!username || !username[0])
                    151:       username = getenv("LOGNAME");
                    152:     if(!username || !username[0])
                    153:       username = getenv("USER");
                    154: #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
                    155:     if((!username || !username[0]) &&
                    156:        !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) &&
                    157:        pw_res) {
                    158:       username = pw.pw_name;
                    159:     }
                    160: #endif
                    161:     if(!username || !username[0])
                    162:       username = userp;
                    163:   }
                    164:   slash = strpbrk(username, "\\/");
                    165:   if(slash) {
                    166:     domain = strdup(username);
                    167:     if(!domain)
                    168:       return CURLE_OUT_OF_MEMORY;
                    169:     slash = domain + (slash - username);
                    170:     *slash = '\0';
                    171:     username = username + (slash - domain) + 1;
                    172:   }
                    173: 
                    174:   /* For testing purposes, when DEBUGBUILD is defined and environment
                    175:      variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
                    176:      NTLM challenge/response which only accepts commands and output
                    177:      strings pre-written in test case definitions */
                    178: #ifdef DEBUGBUILD
                    179:   ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
                    180:   if(ntlm_auth_alloc)
                    181:     ntlm_auth = ntlm_auth_alloc;
                    182:   else
                    183: #endif
                    184:     ntlm_auth = NTLM_WB_FILE;
                    185: 
                    186:   if(access(ntlm_auth, X_OK) != 0) {
                    187:     failf(data, "Could not access ntlm_auth: %s errno %d: %s",
                    188:           ntlm_auth, errno, Curl_strerror(errno, buffer, sizeof(buffer)));
                    189:     goto done;
                    190:   }
                    191: 
                    192:   if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
                    193:     failf(data, "Could not open socket pair. errno %d: %s",
                    194:           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
                    195:     goto done;
                    196:   }
                    197: 
                    198:   child_pid = fork();
                    199:   if(child_pid == -1) {
                    200:     sclose(sockfds[0]);
                    201:     sclose(sockfds[1]);
                    202:     failf(data, "Could not fork. errno %d: %s",
                    203:           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
                    204:     goto done;
                    205:   }
                    206:   else if(!child_pid) {
                    207:     /*
                    208:      * child process
                    209:      */
                    210: 
                    211:     /* Don't use sclose in the child since it fools the socket leak detector */
                    212:     sclose_nolog(sockfds[0]);
                    213:     if(dup2(sockfds[1], STDIN_FILENO) == -1) {
                    214:       failf(data, "Could not redirect child stdin. errno %d: %s",
                    215:             errno, Curl_strerror(errno, buffer, sizeof(buffer)));
                    216:       exit(1);
                    217:     }
                    218: 
                    219:     if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
                    220:       failf(data, "Could not redirect child stdout. errno %d: %s",
                    221:             errno, Curl_strerror(errno, buffer, sizeof(buffer)));
                    222:       exit(1);
                    223:     }
                    224: 
                    225:     if(domain)
                    226:       execl(ntlm_auth, ntlm_auth,
                    227:             "--helper-protocol", "ntlmssp-client-1",
                    228:             "--use-cached-creds",
                    229:             "--username", username,
                    230:             "--domain", domain,
                    231:             NULL);
                    232:     else
                    233:       execl(ntlm_auth, ntlm_auth,
                    234:             "--helper-protocol", "ntlmssp-client-1",
                    235:             "--use-cached-creds",
                    236:             "--username", username,
                    237:             NULL);
                    238: 
                    239:     sclose_nolog(sockfds[1]);
                    240:     failf(data, "Could not execl(). errno %d: %s",
                    241:           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
                    242:     exit(1);
                    243:   }
                    244: 
                    245:   sclose(sockfds[1]);
                    246:   ntlm->ntlm_auth_hlpr_socket = sockfds[0];
                    247:   ntlm->ntlm_auth_hlpr_pid = child_pid;
                    248:   free(domain);
                    249:   free(ntlm_auth_alloc);
                    250:   return CURLE_OK;
                    251: 
                    252: done:
                    253:   free(domain);
                    254:   free(ntlm_auth_alloc);
                    255:   return CURLE_REMOTE_ACCESS_DENIED;
                    256: }
                    257: 
                    258: /* if larger than this, something is seriously wrong */
                    259: #define MAX_NTLM_WB_RESPONSE 100000
                    260: 
                    261: static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm,
                    262:                                  const char *input, curlntlm state)
                    263: {
                    264:   char *buf = malloc(NTLM_BUFSIZE);
                    265:   size_t len_in = strlen(input), len_out = 0;
                    266: 
                    267: #if defined(CURL_DISABLE_VERBOSE_STRINGS)
                    268:   (void) data;
                    269: #endif
                    270: 
                    271:   if(!buf)
                    272:     return CURLE_OUT_OF_MEMORY;
                    273: 
                    274:   while(len_in > 0) {
                    275:     ssize_t written = swrite(ntlm->ntlm_auth_hlpr_socket, input, len_in);
                    276:     if(written == -1) {
                    277:       /* Interrupted by a signal, retry it */
                    278:       if(errno == EINTR)
                    279:         continue;
                    280:       /* write failed if other errors happen */
                    281:       goto done;
                    282:     }
                    283:     input += written;
                    284:     len_in -= written;
                    285:   }
                    286:   /* Read one line */
                    287:   while(1) {
                    288:     ssize_t size;
                    289:     char *newbuf;
                    290: 
                    291:     size = sread(ntlm->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE);
                    292:     if(size == -1) {
                    293:       if(errno == EINTR)
                    294:         continue;
                    295:       goto done;
                    296:     }
                    297:     else if(size == 0)
                    298:       goto done;
                    299: 
                    300:     len_out += size;
                    301:     if(buf[len_out - 1] == '\n') {
                    302:       buf[len_out - 1] = '\0';
                    303:       break;
                    304:     }
                    305: 
                    306:     if(len_out > MAX_NTLM_WB_RESPONSE) {
                    307:       failf(data, "too large ntlm_wb response!");
                    308:       free(buf);
                    309:       return CURLE_OUT_OF_MEMORY;
                    310:     }
                    311: 
                    312:     newbuf = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE);
                    313:     if(!newbuf)
                    314:       return CURLE_OUT_OF_MEMORY;
                    315: 
                    316:     buf = newbuf;
                    317:   }
                    318: 
                    319:   /* Samba/winbind installed but not configured */
                    320:   if(state == NTLMSTATE_TYPE1 &&
                    321:      len_out == 3 &&
                    322:      buf[0] == 'P' && buf[1] == 'W')
                    323:     goto done;
                    324:   /* invalid response */
                    325:   if(len_out < 4)
                    326:     goto done;
                    327:   if(state == NTLMSTATE_TYPE1 &&
                    328:      (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
                    329:     goto done;
                    330:   if(state == NTLMSTATE_TYPE2 &&
                    331:      (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
                    332:      (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
                    333:     goto done;
                    334: 
                    335:   ntlm->response = aprintf("%.*s", len_out - 4, buf + 3);
                    336:   free(buf);
                    337:   if(!ntlm->response)
                    338:     return CURLE_OUT_OF_MEMORY;
                    339:   return CURLE_OK;
                    340: done:
                    341:   free(buf);
                    342:   return CURLE_REMOTE_ACCESS_DENIED;
                    343: }
                    344: 
                    345: CURLcode Curl_input_ntlm_wb(struct connectdata *conn,
                    346:                             bool proxy,
                    347:                             const char *header)
                    348: {
                    349:   struct ntlmdata *ntlm = proxy ? &conn->proxyntlm : &conn->ntlm;
                    350:   curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state;
                    351: 
                    352:   if(!checkprefix("NTLM", header))
                    353:     return CURLE_BAD_CONTENT_ENCODING;
                    354: 
                    355:   header += strlen("NTLM");
                    356:   while(*header && ISSPACE(*header))
                    357:     header++;
                    358: 
                    359:   if(*header) {
                    360:     ntlm->challenge = strdup(header);
                    361:     if(!ntlm->challenge)
                    362:       return CURLE_OUT_OF_MEMORY;
                    363: 
                    364:     *state = NTLMSTATE_TYPE2; /* We got a type-2 message */
                    365:   }
                    366:   else {
                    367:     if(*state == NTLMSTATE_LAST) {
                    368:       infof(conn->data, "NTLM auth restarted\n");
                    369:       Curl_http_auth_cleanup_ntlm_wb(conn);
                    370:     }
                    371:     else if(*state == NTLMSTATE_TYPE3) {
                    372:       infof(conn->data, "NTLM handshake rejected\n");
                    373:       Curl_http_auth_cleanup_ntlm_wb(conn);
                    374:       *state = NTLMSTATE_NONE;
                    375:       return CURLE_REMOTE_ACCESS_DENIED;
                    376:     }
                    377:     else if(*state >= NTLMSTATE_TYPE1) {
                    378:       infof(conn->data, "NTLM handshake failure (internal error)\n");
                    379:       return CURLE_REMOTE_ACCESS_DENIED;
                    380:     }
                    381: 
                    382:     *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */
                    383:   }
                    384: 
                    385:   return CURLE_OK;
                    386: }
                    387: 
                    388: /*
                    389:  * This is for creating ntlm header output by delegating challenge/response
                    390:  * to Samba's winbind daemon helper ntlm_auth.
                    391:  */
                    392: CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
                    393:                               bool proxy)
                    394: {
                    395:   /* point to the address of the pointer that holds the string to send to the
                    396:      server, which is for a plain host or for a HTTP proxy */
                    397:   char **allocuserpwd;
                    398:   /* point to the name and password for this */
                    399:   const char *userp;
                    400:   struct ntlmdata *ntlm;
                    401:   curlntlm *state;
                    402:   struct auth *authp;
                    403: 
                    404:   CURLcode res = CURLE_OK;
                    405: 
                    406:   DEBUGASSERT(conn);
                    407:   DEBUGASSERT(conn->data);
                    408: 
                    409:   if(proxy) {
                    410:     allocuserpwd = &conn->allocptr.proxyuserpwd;
                    411:     userp = conn->http_proxy.user;
                    412:     ntlm = &conn->proxyntlm;
                    413:     state = &conn->proxy_ntlm_state;
                    414:     authp = &conn->data->state.authproxy;
                    415:   }
                    416:   else {
                    417:     allocuserpwd = &conn->allocptr.userpwd;
                    418:     userp = conn->user;
                    419:     ntlm = &conn->ntlm;
                    420:     state = &conn->http_ntlm_state;
                    421:     authp = &conn->data->state.authhost;
                    422:   }
                    423:   authp->done = FALSE;
                    424: 
                    425:   /* not set means empty */
                    426:   if(!userp)
                    427:     userp = "";
                    428: 
                    429:   switch(*state) {
                    430:   case NTLMSTATE_TYPE1:
                    431:   default:
                    432:     /* Use Samba's 'winbind' daemon to support NTLM authentication,
                    433:      * by delegating the NTLM challenge/response protocol to a helper
                    434:      * in ntlm_auth.
                    435:      * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
                    436:      * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
                    437:      * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
                    438:      * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
                    439:      * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
                    440:      * filename of ntlm_auth helper.
                    441:      * If NTLM authentication using winbind fails, go back to original
                    442:      * request handling process.
                    443:      */
                    444:     /* Create communication with ntlm_auth */
                    445:     res = ntlm_wb_init(conn->data, ntlm, userp);
                    446:     if(res)
                    447:       return res;
                    448:     res = ntlm_wb_response(conn->data, ntlm, "YR\n", *state);
                    449:     if(res)
                    450:       return res;
                    451: 
                    452:     free(*allocuserpwd);
                    453:     *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
                    454:                             proxy ? "Proxy-" : "",
                    455:                             ntlm->response);
                    456:     DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
                    457:     Curl_safefree(ntlm->response);
                    458:     if(!*allocuserpwd)
                    459:       return CURLE_OUT_OF_MEMORY;
                    460:     break;
                    461: 
                    462:   case NTLMSTATE_TYPE2: {
                    463:     char *input = aprintf("TT %s\n", ntlm->challenge);
                    464:     if(!input)
                    465:       return CURLE_OUT_OF_MEMORY;
                    466:     res = ntlm_wb_response(conn->data, ntlm, input, *state);
                    467:     free(input);
                    468:     if(res)
                    469:       return res;
                    470: 
                    471:     free(*allocuserpwd);
                    472:     *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
                    473:                             proxy ? "Proxy-" : "",
                    474:                             ntlm->response);
                    475:     DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
                    476:     *state = NTLMSTATE_TYPE3; /* we sent a type-3 */
                    477:     authp->done = TRUE;
                    478:     Curl_http_auth_cleanup_ntlm_wb(conn);
                    479:     if(!*allocuserpwd)
                    480:       return CURLE_OUT_OF_MEMORY;
                    481:     break;
                    482:   }
                    483:   case NTLMSTATE_TYPE3:
                    484:     /* connection is already authenticated,
                    485:      * don't send a header in future requests */
                    486:     *state = NTLMSTATE_LAST;
                    487:     /* FALLTHROUGH */
                    488:   case NTLMSTATE_LAST:
                    489:     Curl_safefree(*allocuserpwd);
                    490:     authp->done = TRUE;
                    491:     break;
                    492:   }
                    493: 
                    494:   return CURLE_OK;
                    495: }
                    496: 
                    497: void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn)
                    498: {
                    499:   ntlm_wb_cleanup(&conn->ntlm);
                    500:   ntlm_wb_cleanup(&conn->proxyntlm);
                    501: }
                    502: 
                    503: #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>