Home | History | Annotate | Download | only in lib
      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) 2012 - 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  * RFC2195 CRAM-MD5 authentication
     22  * RFC2617 Basic and Digest Access Authentication
     23  * RFC2831 DIGEST-MD5 authentication
     24  * RFC4422 Simple Authentication and Security Layer (SASL)
     25  * RFC4616 PLAIN authentication
     26  * RFC6749 OAuth 2.0 Authorization Framework
     27  * RFC7628 A Set of SASL Mechanisms for OAuth
     28  * Draft   LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt>
     29  *
     30  ***************************************************************************/
     31 
     32 #include "curl_setup.h"
     33 
     34 #include <curl/curl.h>
     35 #include "urldata.h"
     36 
     37 #include "curl_base64.h"
     38 #include "curl_md5.h"
     39 #include "vauth/vauth.h"
     40 #include "vtls/vtls.h"
     41 #include "curl_hmac.h"
     42 #include "curl_sasl.h"
     43 #include "warnless.h"
     44 #include "strtok.h"
     45 #include "strequal.h"
     46 #include "rawstr.h"
     47 #include "sendf.h"
     48 #include "non-ascii.h" /* included for Curl_convert_... prototypes */
     49 /* The last 3 #include files should be in this order */
     50 #include "curl_printf.h"
     51 #include "curl_memory.h"
     52 #include "memdebug.h"
     53 
     54 /* Supported mechanisms */
     55 const struct {
     56   const char   *name;  /* Name */
     57   size_t        len;   /* Name length */
     58   unsigned int  bit;   /* Flag bit */
     59 } mechtable[] = {
     60   { "LOGIN",        5,  SASL_MECH_LOGIN },
     61   { "PLAIN",        5,  SASL_MECH_PLAIN },
     62   { "CRAM-MD5",     8,  SASL_MECH_CRAM_MD5 },
     63   { "DIGEST-MD5",   10, SASL_MECH_DIGEST_MD5 },
     64   { "GSSAPI",       6,  SASL_MECH_GSSAPI },
     65   { "EXTERNAL",     8,  SASL_MECH_EXTERNAL },
     66   { "NTLM",         4,  SASL_MECH_NTLM },
     67   { "XOAUTH2",      7,  SASL_MECH_XOAUTH2 },
     68   { "OAUTHBEARER",  11, SASL_MECH_OAUTHBEARER },
     69   { ZERO_NULL,      0,  0 }
     70 };
     71 
     72 /*
     73  * Curl_sasl_cleanup()
     74  *
     75  * This is used to cleanup any libraries or curl modules used by the sasl
     76  * functions.
     77  *
     78  * Parameters:
     79  *
     80  * conn     [in]     - The connection data.
     81  * authused [in]     - The authentication mechanism used.
     82  */
     83 void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused)
     84 {
     85 #if defined(USE_KERBEROS5)
     86   /* Cleanup the gssapi structure */
     87   if(authused == SASL_MECH_GSSAPI) {
     88     Curl_auth_gssapi_cleanup(&conn->krb5);
     89   }
     90 #endif
     91 
     92 #if defined(USE_NTLM)
     93   /* Cleanup the NTLM structure */
     94   if(authused == SASL_MECH_NTLM) {
     95     Curl_auth_ntlm_cleanup(&conn->ntlm);
     96   }
     97 #endif
     98 
     99 #if !defined(USE_KERBEROS5) && !defined(USE_NTLM)
    100   /* Reserved for future use */
    101   (void)conn;
    102   (void)authused;
    103 #endif
    104 }
    105 
    106 /*
    107  * Curl_sasl_decode_mech()
    108  *
    109  * Convert a SASL mechanism name into a token.
    110  *
    111  * Parameters:
    112  *
    113  * ptr    [in]     - The mechanism string.
    114  * maxlen [in]     - Maximum mechanism string length.
    115  * len    [out]    - If not NULL, effective name length.
    116  *
    117  * Returns the SASL mechanism token or 0 if no match.
    118  */
    119 unsigned int Curl_sasl_decode_mech(const char *ptr, size_t maxlen, size_t *len)
    120 {
    121   unsigned int i;
    122   char c;
    123 
    124   for(i = 0; mechtable[i].name; i++) {
    125     if(maxlen >= mechtable[i].len &&
    126        !memcmp(ptr, mechtable[i].name, mechtable[i].len)) {
    127       if(len)
    128         *len = mechtable[i].len;
    129 
    130       if(maxlen == mechtable[i].len)
    131         return mechtable[i].bit;
    132 
    133       c = ptr[mechtable[i].len];
    134       if(!ISUPPER(c) && !ISDIGIT(c) && c != '-' && c != '_')
    135         return mechtable[i].bit;
    136     }
    137   }
    138 
    139   return 0;
    140 }
    141 
    142 /*
    143  * Curl_sasl_parse_url_auth_option()
    144  *
    145  * Parse the URL login options.
    146  */
    147 CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl,
    148                                          const char *value, size_t len)
    149 {
    150   CURLcode result = CURLE_OK;
    151   unsigned int mechbit;
    152   size_t mechlen;
    153 
    154   if(!len)
    155     return CURLE_URL_MALFORMAT;
    156 
    157   if(sasl->resetprefs) {
    158     sasl->resetprefs = FALSE;
    159     sasl->prefmech = SASL_AUTH_NONE;
    160   }
    161 
    162   if(strnequal(value, "*", len))
    163     sasl->prefmech = SASL_AUTH_DEFAULT;
    164   else {
    165     mechbit = Curl_sasl_decode_mech(value, len, &mechlen);
    166     if(mechbit && mechlen == len)
    167       sasl->prefmech |= mechbit;
    168     else
    169       result = CURLE_URL_MALFORMAT;
    170   }
    171 
    172   return result;
    173 }
    174 
    175 /*
    176  * Curl_sasl_init()
    177  *
    178  * Initializes the SASL structure.
    179  */
    180 void Curl_sasl_init(struct SASL *sasl, const struct SASLproto *params)
    181 {
    182   sasl->params = params;           /* Set protocol dependent parameters */
    183   sasl->state = SASL_STOP;         /* Not yet running */
    184   sasl->authmechs = SASL_AUTH_NONE; /* No known authentication mechanism yet */
    185   sasl->prefmech = SASL_AUTH_DEFAULT; /* Prefer all mechanisms */
    186   sasl->authused = SASL_AUTH_NONE; /* No the authentication mechanism used */
    187   sasl->resetprefs = TRUE;         /* Reset prefmech upon AUTH parsing. */
    188   sasl->mutual_auth = FALSE;       /* No mutual authentication (GSSAPI only) */
    189   sasl->force_ir = FALSE;          /* Respect external option */
    190 }
    191 
    192 /*
    193  * state()
    194  *
    195  * This is the ONLY way to change SASL state!
    196  */
    197 static void state(struct SASL *sasl, struct connectdata *conn,
    198                   saslstate newstate)
    199 {
    200 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
    201   /* for debug purposes */
    202   static const char * const names[]={
    203     "STOP",
    204     "PLAIN",
    205     "LOGIN",
    206     "LOGIN_PASSWD",
    207     "EXTERNAL",
    208     "CRAMMD5",
    209     "DIGESTMD5",
    210     "DIGESTMD5_RESP",
    211     "NTLM",
    212     "NTLM_TYPE2MSG",
    213     "GSSAPI",
    214     "GSSAPI_TOKEN",
    215     "GSSAPI_NO_DATA",
    216     "OAUTH2",
    217     "OAUTH2_RESP",
    218     "CANCEL",
    219     "FINAL",
    220     /* LAST */
    221   };
    222 
    223   if(sasl->state != newstate)
    224     infof(conn->data, "SASL %p state change from %s to %s\n",
    225           (void *)sasl, names[sasl->state], names[newstate]);
    226 #else
    227   (void) conn;
    228 #endif
    229 
    230   sasl->state = newstate;
    231 }
    232 
    233 /*
    234  * Curl_sasl_can_authenticate()
    235  *
    236  * Check if we have enough auth data and capabilities to authenticate.
    237  */
    238 bool Curl_sasl_can_authenticate(struct SASL *sasl, struct connectdata *conn)
    239 {
    240   /* Have credentials been provided? */
    241   if(conn->bits.user_passwd)
    242     return TRUE;
    243 
    244   /* EXTERNAL can authenticate without a user name and/or password */
    245   if(sasl->authmechs & sasl->prefmech & SASL_MECH_EXTERNAL)
    246     return TRUE;
    247 
    248   return FALSE;
    249 }
    250 
    251 /*
    252  * Curl_sasl_start()
    253  *
    254  * Calculate the required login details for SASL authentication.
    255  */
    256 CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn,
    257                          bool force_ir, saslprogress *progress)
    258 {
    259   CURLcode result = CURLE_OK;
    260   struct Curl_easy *data = conn->data;
    261   unsigned int enabledmechs;
    262   const char *mech = NULL;
    263   char *resp = NULL;
    264   size_t len = 0;
    265   saslstate state1 = SASL_STOP;
    266   saslstate state2 = SASL_FINAL;
    267 #if defined(USE_KERBEROS5)
    268   const char* service = data->set.str[STRING_SERVICE_NAME] ?
    269                         data->set.str[STRING_SERVICE_NAME] :
    270                         sasl->params->service;
    271 #endif
    272 
    273   sasl->force_ir = force_ir;    /* Latch for future use */
    274   sasl->authused = 0;           /* No mechanism used yet */
    275   enabledmechs = sasl->authmechs & sasl->prefmech;
    276   *progress = SASL_IDLE;
    277 
    278   /* Calculate the supported authentication mechanism, by decreasing order of
    279      security, as well as the initial response where appropriate */
    280   if((enabledmechs & SASL_MECH_EXTERNAL) && !conn->passwd[0]) {
    281     mech = SASL_MECH_STRING_EXTERNAL;
    282     state1 = SASL_EXTERNAL;
    283     sasl->authused = SASL_MECH_EXTERNAL;
    284 
    285     if(force_ir || data->set.sasl_ir)
    286       result = Curl_auth_create_external_message(data, conn->user, &resp,
    287                                                  &len);
    288   }
    289   else if(conn->bits.user_passwd) {
    290 #if defined(USE_KERBEROS5)
    291     if(enabledmechs & SASL_MECH_GSSAPI) {
    292       sasl->mutual_auth = FALSE; /* TODO: Calculate mutual authentication */
    293       mech = SASL_MECH_STRING_GSSAPI;
    294       state1 = SASL_GSSAPI;
    295       state2 = SASL_GSSAPI_TOKEN;
    296       sasl->authused = SASL_MECH_GSSAPI;
    297 
    298       if(force_ir || data->set.sasl_ir)
    299         result = Curl_auth_create_gssapi_user_message(data, conn->user,
    300                                                       conn->passwd,
    301                                                       service,
    302                                                       data->easy_conn->
    303                                                             host.name,
    304                                                       sasl->mutual_auth,
    305                                                       NULL, &conn->krb5,
    306                                                       &resp, &len);
    307     }
    308     else
    309 #endif
    310 #ifndef CURL_DISABLE_CRYPTO_AUTH
    311     if(enabledmechs & SASL_MECH_DIGEST_MD5) {
    312       mech = SASL_MECH_STRING_DIGEST_MD5;
    313       state1 = SASL_DIGESTMD5;
    314       sasl->authused = SASL_MECH_DIGEST_MD5;
    315     }
    316     else if(enabledmechs & SASL_MECH_CRAM_MD5) {
    317       mech = SASL_MECH_STRING_CRAM_MD5;
    318       state1 = SASL_CRAMMD5;
    319       sasl->authused = SASL_MECH_CRAM_MD5;
    320     }
    321     else
    322 #endif
    323 #ifdef USE_NTLM
    324     if(enabledmechs & SASL_MECH_NTLM) {
    325       mech = SASL_MECH_STRING_NTLM;
    326       state1 = SASL_NTLM;
    327       state2 = SASL_NTLM_TYPE2MSG;
    328       sasl->authused = SASL_MECH_NTLM;
    329 
    330       if(force_ir || data->set.sasl_ir)
    331         result = Curl_auth_create_ntlm_type1_message(conn->user, conn->passwd,
    332                                                      &conn->ntlm, &resp, &len);
    333       }
    334     else
    335 #endif
    336     if((enabledmechs & SASL_MECH_OAUTHBEARER) && conn->oauth_bearer) {
    337       mech = SASL_MECH_STRING_OAUTHBEARER;
    338       state1 = SASL_OAUTH2;
    339       state2 = SASL_OAUTH2_RESP;
    340       sasl->authused = SASL_MECH_OAUTHBEARER;
    341 
    342       if(force_ir || data->set.sasl_ir)
    343         result = Curl_auth_create_oauth_bearer_message(data, conn->user,
    344                                                        conn->host.name,
    345                                                        conn->port,
    346                                                        conn->oauth_bearer,
    347                                                        &resp, &len);
    348     }
    349     else if((enabledmechs & SASL_MECH_XOAUTH2) && conn->oauth_bearer) {
    350       mech = SASL_MECH_STRING_XOAUTH2;
    351       state1 = SASL_OAUTH2;
    352       sasl->authused = SASL_MECH_XOAUTH2;
    353 
    354       if(force_ir || data->set.sasl_ir)
    355         result = Curl_auth_create_oauth_bearer_message(data, conn->user,
    356                                                        NULL, 0,
    357                                                        conn->oauth_bearer,
    358                                                        &resp, &len);
    359     }
    360     else if(enabledmechs & SASL_MECH_LOGIN) {
    361       mech = SASL_MECH_STRING_LOGIN;
    362       state1 = SASL_LOGIN;
    363       state2 = SASL_LOGIN_PASSWD;
    364       sasl->authused = SASL_MECH_LOGIN;
    365 
    366       if(force_ir || data->set.sasl_ir)
    367         result = Curl_auth_create_login_message(data, conn->user, &resp, &len);
    368     }
    369     else if(enabledmechs & SASL_MECH_PLAIN) {
    370       mech = SASL_MECH_STRING_PLAIN;
    371       state1 = SASL_PLAIN;
    372       sasl->authused = SASL_MECH_PLAIN;
    373 
    374       if(force_ir || data->set.sasl_ir)
    375         result = Curl_auth_create_plain_message(data, conn->user, conn->passwd,
    376                                                 &resp, &len);
    377     }
    378   }
    379 
    380   if(!result && mech) {
    381     if(resp && sasl->params->maxirlen &&
    382        strlen(mech) + len > sasl->params->maxirlen) {
    383       free(resp);
    384       resp = NULL;
    385     }
    386 
    387     result = sasl->params->sendauth(conn, mech, resp);
    388     if(!result) {
    389       *progress = SASL_INPROGRESS;
    390       state(sasl, conn, resp ? state2 : state1);
    391     }
    392   }
    393 
    394   free(resp);
    395 
    396   return result;
    397 }
    398 
    399 /*
    400  * Curl_sasl_continue()
    401  *
    402  * Continue the authentication.
    403  */
    404 CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn,
    405                             int code, saslprogress *progress)
    406 {
    407   CURLcode result = CURLE_OK;
    408   struct Curl_easy *data = conn->data;
    409   saslstate newstate = SASL_FINAL;
    410   char *resp = NULL;
    411 #if !defined(CURL_DISABLE_CRYPTO_AUTH)
    412   char *serverdata;
    413   char *chlg = NULL;
    414   size_t chlglen = 0;
    415 #endif
    416 #if !defined(CURL_DISABLE_CRYPTO_AUTH) || defined(USE_KERBEROS5)
    417   const char *service = data->set.str[STRING_SERVICE_NAME] ?
    418                         data->set.str[STRING_SERVICE_NAME] :
    419                         sasl->params->service;
    420 #endif
    421   size_t len = 0;
    422 
    423   *progress = SASL_INPROGRESS;
    424 
    425   if(sasl->state == SASL_FINAL) {
    426     if(code != sasl->params->finalcode)
    427       result = CURLE_LOGIN_DENIED;
    428     *progress = SASL_DONE;
    429     state(sasl, conn, SASL_STOP);
    430     return result;
    431   }
    432 
    433   if(sasl->state != SASL_CANCEL && sasl->state != SASL_OAUTH2_RESP &&
    434      code != sasl->params->contcode) {
    435     *progress = SASL_DONE;
    436     state(sasl, conn, SASL_STOP);
    437     return CURLE_LOGIN_DENIED;
    438   }
    439 
    440   switch(sasl->state) {
    441   case SASL_STOP:
    442     *progress = SASL_DONE;
    443     return result;
    444   case SASL_PLAIN:
    445     result = Curl_auth_create_plain_message(data, conn->user, conn->passwd,
    446                                             &resp,
    447                                             &len);
    448     break;
    449   case SASL_LOGIN:
    450     result = Curl_auth_create_login_message(data, conn->user, &resp, &len);
    451     newstate = SASL_LOGIN_PASSWD;
    452     break;
    453   case SASL_LOGIN_PASSWD:
    454     result = Curl_auth_create_login_message(data, conn->passwd, &resp, &len);
    455     break;
    456   case SASL_EXTERNAL:
    457     result = Curl_auth_create_external_message(data, conn->user, &resp, &len);
    458     break;
    459 
    460 #ifndef CURL_DISABLE_CRYPTO_AUTH
    461   case SASL_CRAMMD5:
    462     sasl->params->getmessage(data->state.buffer, &serverdata);
    463     result = Curl_auth_decode_cram_md5_message(serverdata, &chlg, &chlglen);
    464     if(!result)
    465       result = Curl_auth_create_cram_md5_message(data, chlg, conn->user,
    466                                                  conn->passwd, &resp, &len);
    467     free(chlg);
    468     break;
    469   case SASL_DIGESTMD5:
    470     sasl->params->getmessage(data->state.buffer, &serverdata);
    471     result = Curl_auth_create_digest_md5_message(data, serverdata,
    472                                                  conn->user, conn->passwd,
    473                                                  service,
    474                                                  &resp, &len);
    475     newstate = SASL_DIGESTMD5_RESP;
    476     break;
    477   case SASL_DIGESTMD5_RESP:
    478     resp = strdup("");
    479     if(!resp)
    480       result = CURLE_OUT_OF_MEMORY;
    481     break;
    482 #endif
    483 
    484 #ifdef USE_NTLM
    485   case SASL_NTLM:
    486     /* Create the type-1 message */
    487     result = Curl_auth_create_ntlm_type1_message(conn->user, conn->passwd,
    488                                                  &conn->ntlm, &resp, &len);
    489     newstate = SASL_NTLM_TYPE2MSG;
    490     break;
    491   case SASL_NTLM_TYPE2MSG:
    492     /* Decode the type-2 message */
    493     sasl->params->getmessage(data->state.buffer, &serverdata);
    494     result = Curl_auth_decode_ntlm_type2_message(data, serverdata,
    495                                                  &conn->ntlm);
    496     if(!result)
    497       result = Curl_auth_create_ntlm_type3_message(data, conn->user,
    498                                                    conn->passwd, &conn->ntlm,
    499                                                    &resp, &len);
    500     break;
    501 #endif
    502 
    503 #if defined(USE_KERBEROS5)
    504   case SASL_GSSAPI:
    505     result = Curl_auth_create_gssapi_user_message(data, conn->user,
    506                                                   conn->passwd,
    507                                                   service,
    508                                                   data->easy_conn->host.name,
    509                                                   sasl->mutual_auth, NULL,
    510                                                   &conn->krb5,
    511                                                   &resp, &len);
    512     newstate = SASL_GSSAPI_TOKEN;
    513     break;
    514   case SASL_GSSAPI_TOKEN:
    515     sasl->params->getmessage(data->state.buffer, &serverdata);
    516     if(sasl->mutual_auth) {
    517       /* Decode the user token challenge and create the optional response
    518          message */
    519       result = Curl_auth_create_gssapi_user_message(data, NULL, NULL,
    520                                                     NULL, NULL,
    521                                                     sasl->mutual_auth,
    522                                                     serverdata, &conn->krb5,
    523                                                     &resp, &len);
    524       newstate = SASL_GSSAPI_NO_DATA;
    525     }
    526     else
    527       /* Decode the security challenge and create the response message */
    528       result = Curl_auth_create_gssapi_security_message(data, serverdata,
    529                                                         &conn->krb5,
    530                                                         &resp, &len);
    531     break;
    532   case SASL_GSSAPI_NO_DATA:
    533     sasl->params->getmessage(data->state.buffer, &serverdata);
    534     /* Decode the security challenge and create the response message */
    535     result = Curl_auth_create_gssapi_security_message(data, serverdata,
    536                                                       &conn->krb5,
    537                                                       &resp, &len);
    538     break;
    539 #endif
    540 
    541   case SASL_OAUTH2:
    542     /* Create the authorisation message */
    543     if(sasl->authused == SASL_MECH_OAUTHBEARER) {
    544       result = Curl_auth_create_oauth_bearer_message(data, conn->user,
    545                                                      conn->host.name,
    546                                                      conn->port,
    547                                                      conn->oauth_bearer,
    548                                                      &resp, &len);
    549 
    550       /* Failures maybe sent by the server as continuations for OAUTHBEARER */
    551       newstate = SASL_OAUTH2_RESP;
    552     }
    553     else
    554       result = Curl_auth_create_oauth_bearer_message(data, conn->user,
    555                                                      NULL, 0,
    556                                                      conn->oauth_bearer,
    557                                                      &resp, &len);
    558     break;
    559 
    560   case SASL_OAUTH2_RESP:
    561     /* The continuation is optional so check the response code */
    562     if(code == sasl->params->finalcode) {
    563       /* Final response was received so we are done */
    564       *progress = SASL_DONE;
    565       state(sasl, conn, SASL_STOP);
    566       return result;
    567     }
    568     else if(code == sasl->params->contcode) {
    569       /* Acknowledge the continuation by sending a 0x01 response base64
    570          encoded */
    571       resp = strdup("AQ==");
    572       if(!resp)
    573         result = CURLE_OUT_OF_MEMORY;
    574       break;
    575     }
    576     else {
    577       *progress = SASL_DONE;
    578       state(sasl, conn, SASL_STOP);
    579       return CURLE_LOGIN_DENIED;
    580     }
    581 
    582   case SASL_CANCEL:
    583     /* Remove the offending mechanism from the supported list */
    584     sasl->authmechs ^= sasl->authused;
    585 
    586     /* Start an alternative SASL authentication */
    587     result = Curl_sasl_start(sasl, conn, sasl->force_ir, progress);
    588     newstate = sasl->state;   /* Use state from Curl_sasl_start() */
    589     break;
    590   default:
    591     failf(data, "Unsupported SASL authentication mechanism");
    592     result = CURLE_UNSUPPORTED_PROTOCOL;  /* Should not happen */
    593     break;
    594   }
    595 
    596   switch(result) {
    597   case CURLE_BAD_CONTENT_ENCODING:
    598     /* Cancel dialog */
    599     result = sasl->params->sendcont(conn, "*");
    600     newstate = SASL_CANCEL;
    601     break;
    602   case CURLE_OK:
    603     if(resp)
    604       result = sasl->params->sendcont(conn, resp);
    605     break;
    606   default:
    607     newstate = SASL_STOP;    /* Stop on error */
    608     *progress = SASL_DONE;
    609     break;
    610   }
    611 
    612   free(resp);
    613 
    614   state(sasl, conn, newstate);
    615 
    616   return result;
    617 }
    618