Skip to content

Commit

Permalink
SASL: common state engine for imap/pop3/smtp
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick Monnerat committed Jan 20, 2015
1 parent e1ea18f commit 79543ca
Show file tree
Hide file tree
Showing 8 changed files with 577 additions and 2,352 deletions.
331 changes: 330 additions & 1 deletion lib/curl_sasl.c
Expand Up @@ -43,6 +43,7 @@
#include "strtok.h"
#include "strequal.h"
#include "rawstr.h"
#include "sendf.h"
#include "non-ascii.h" /* included for Curl_convert_... prototypes */

#define _MPRINTF_REPLACE /* use our functions only */
Expand Down Expand Up @@ -1271,11 +1272,339 @@ CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl,
*
* Initializes an SASL structure.
*/
void Curl_sasl_init(struct SASL *sasl)
void Curl_sasl_init(struct SASL *sasl, const struct SASLproto *params)
{
sasl->params = params; /* Set protocol dependent parameters */
sasl->state = SASL_STOP; /* Not yet running */
sasl->authmechs = SASL_AUTH_NONE; /* No known authentication mechanism yet */
sasl->prefmech = SASL_AUTH_ANY; /* Prefer all mechanisms */
sasl->authused = SASL_AUTH_NONE; /* No the authentication mechanism used */
sasl->resetprefs = TRUE; /* Reset prefmech upon AUTH parsing. */
sasl->mutual_auth = FALSE; /* No mutual authentication (GSSAPI only) */
sasl->force_ir = FALSE; /* Respect external option */
}

/*
* state()
*
* This is the ONLY way to change SASL state!
*/
static void state(struct SASL *sasl,
struct connectdata *conn, saslstate newstate)
{
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
/* for debug purposes */
static const char * const names[]={
"STOP",
"PLAIN",
"LOGIN",
"LOGIN_PASSWD",
"CRAMMD5",
"DIGESTMD5",
"DIGESTMD5_RESP",
"NTLM",
"NTLM_TYPE2MSG",
"GSSAPI",
"GSSAPI_TOKEN",
"GSSAPI_NO_DATA",
"XOAUTH2",
"CANCEL",
"FINAL",
/* LAST */
};

if(sasl->state != newstate)
infof(conn->data, "SASL %p state change from %s to %s\n",
(void *)sasl, names[sasl->state], names[newstate]);
#endif

sasl->state = newstate;
}

/*
* Curl_sasl_start()
*
* Calculate the required login details for SASL authentication.
*/
CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn,
bool force_ir, saslprogress *progress)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
unsigned int enabledmechs;
const char *mech = NULL;
char *resp = NULL;
size_t len = 0;
saslstate state1 = SASL_STOP;
saslstate state2 = SASL_FINAL;

sasl->force_ir = force_ir; /* Latch for future use */
sasl->authused = 0; /* No mechanism used yet */
enabledmechs = sasl->authmechs & sasl->prefmech;
*progress = SASL_IDLE;

/* Calculate the supported authentication mechanism, by decreasing order of
* security, as well as the initial response where appropriate */
#if defined(USE_KERBEROS5)
if(enabledmechs & SASL_MECH_GSSAPI) {
sasl->mutual_auth = FALSE; /* TODO: Calculate mutual authentication */
mech = SASL_MECH_STRING_GSSAPI;
state1 = SASL_GSSAPI;
state2 = SASL_GSSAPI_TOKEN;
sasl->authused = SASL_MECH_GSSAPI;

if(force_ir || data->set.sasl_ir)
result = Curl_sasl_create_gssapi_user_message(data, conn->user,
conn->passwd,
sasl->params->service,
sasl->mutual_auth,
NULL, &conn->krb5,
&resp, &len);
}
else
#endif
#ifndef CURL_DISABLE_CRYPTO_AUTH
if(enabledmechs & SASL_MECH_DIGEST_MD5) {
mech = SASL_MECH_STRING_DIGEST_MD5;
state1 = SASL_DIGESTMD5;
sasl->authused = SASL_MECH_DIGEST_MD5;
}
else if(enabledmechs & SASL_MECH_CRAM_MD5) {
mech = SASL_MECH_STRING_CRAM_MD5;
state1 = SASL_CRAMMD5;
sasl->authused = SASL_MECH_CRAM_MD5;
}
else
#endif
#ifdef USE_NTLM
if(enabledmechs & SASL_MECH_NTLM) {
mech = SASL_MECH_STRING_NTLM;
state1 = SASL_NTLM;
state2 = SASL_NTLM_TYPE2MSG;
sasl->authused = SASL_MECH_NTLM;

if(force_ir || data->set.sasl_ir)
result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd,
&conn->ntlm, &resp, &len);
}
else
#endif
if((((enabledmechs & SASL_MECH_XOAUTH2) &&
sasl->prefmech != SASL_AUTH_ANY)) || conn->xoauth2_bearer) {
mech = SASL_MECH_STRING_XOAUTH2;
state1 = SASL_XOAUTH2;
sasl->authused = SASL_MECH_XOAUTH2;

if(force_ir || data->set.sasl_ir)
result = Curl_sasl_create_xoauth2_message(data, conn->user,
conn->xoauth2_bearer,
&resp, &len);
}
else if(enabledmechs & SASL_MECH_LOGIN) {
mech = SASL_MECH_STRING_LOGIN;
state1 = SASL_LOGIN;
state2 = SASL_LOGIN_PASSWD;
sasl->authused = SASL_MECH_LOGIN;

if(force_ir || data->set.sasl_ir)
result = Curl_sasl_create_login_message(data, conn->user, &resp, &len);
}
else if(enabledmechs & SASL_MECH_PLAIN) {
mech = SASL_MECH_STRING_PLAIN;
state1 = SASL_PLAIN;
sasl->authused = SASL_MECH_PLAIN;

if(force_ir || data->set.sasl_ir)
result = Curl_sasl_create_plain_message(data, conn->user, conn->passwd,
&resp, &len);
}
else
state2 = SASL_STOP; /* No authentication started */

if(!result) {
if(resp && sasl->params->maxirlen &&
strlen(mech) + len > sasl->params->maxirlen) {
Curl_safefree(resp);
resp = NULL;
}
if(mech) {
result = sasl->params->sendauth(conn, mech, resp);
if(!result) {
*progress = SASL_INPROGRESS;
state(sasl, conn, resp? state2: state1);
}
}
}

Curl_safefree(resp);
return result;
}

/*
* Curl_sasl_continue()
*
* Continue an SASL authentication.
*/
CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn,
int code, saslprogress *progress)
{
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
saslstate newstate = SASL_FINAL;
char *chlg = NULL;
char *resp = NULL;
char *serverdata;
size_t len = 0;
size_t chlglen = 0;

*progress = SASL_INPROGRESS;

if(sasl->state == SASL_FINAL) {
if(code != sasl->params->finalcode)
result = CURLE_LOGIN_DENIED;
*progress = SASL_DONE;
state(sasl, conn, SASL_STOP);
return result;
}

if(sasl->state != SASL_CANCEL && code != sasl->params->contcode) {
*progress = SASL_DONE;
state(sasl, conn, SASL_STOP);
return CURLE_LOGIN_DENIED;
}

switch(sasl->state) {
case SASL_STOP:
*progress = SASL_DONE;
return result;
case SASL_PLAIN:
result = Curl_sasl_create_plain_message(data, conn->user, conn->passwd,
&resp, &len);
break;
case SASL_LOGIN:
result = Curl_sasl_create_login_message(data, conn->user, &resp, &len);
newstate = SASL_LOGIN_PASSWD;
break;
case SASL_LOGIN_PASSWD:
result = Curl_sasl_create_login_message(data, conn->passwd, &resp, &len);
break;

#ifndef CURL_DISABLE_CRYPTO_AUTH
case SASL_CRAMMD5:
sasl->params->getmessage(data->state.buffer, &serverdata);
result = Curl_sasl_decode_cram_md5_message(serverdata, &chlg, &chlglen);
if(!result)
result = Curl_sasl_create_cram_md5_message(data, chlg, conn->user,
conn->passwd, &resp, &len);
Curl_safefree(chlg);
break;
case SASL_DIGESTMD5:
sasl->params->getmessage(data->state.buffer, &serverdata);
result = Curl_sasl_create_digest_md5_message(data, serverdata,
conn->user, conn->passwd,
sasl->params->service,
&resp, &len);
newstate = SASL_DIGESTMD5_RESP;
break;
case SASL_DIGESTMD5_RESP:
if(!(resp = strdup("")))
result = CURLE_OUT_OF_MEMORY;
break;
#endif

#ifdef USE_NTLM
case SASL_NTLM:
/* Create the type-1 message */
result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd,
&conn->ntlm,
&resp, &len);
newstate = SASL_NTLM_TYPE2MSG;
break;
case SASL_NTLM_TYPE2MSG:
/* Decode the type-2 message */
sasl->params->getmessage(data->state.buffer, &serverdata);
result = Curl_sasl_decode_ntlm_type2_message(data,
serverdata, &conn->ntlm);
if(!result)
result = Curl_sasl_create_ntlm_type3_message(data, conn->user,
conn->passwd, &conn->ntlm,
&resp, &len);
break;
#endif

#if defined(USE_KERBEROS5)
case SASL_GSSAPI:
result = Curl_sasl_create_gssapi_user_message(data, conn->user,
conn->passwd,
sasl->params->service,
sasl->mutual_auth, NULL,
&conn->krb5,
&resp, &len);
newstate = SASL_GSSAPI_TOKEN;
break;
case SASL_GSSAPI_TOKEN:
sasl->params->getmessage(data->state.buffer, &serverdata);
if(sasl->mutual_auth) {
/* Decode the user token challenge and create the optional response
message */
result = Curl_sasl_create_gssapi_user_message(data, NULL, NULL, NULL,
sasl->mutual_auth,
serverdata, &conn->krb5,
&resp, &len);
newstate = SASL_GSSAPI_NO_DATA;
}
else
/* Decode the security challenge and create the response message */
result = Curl_sasl_create_gssapi_security_message(data, serverdata,
&conn->krb5,
&resp, &len);
break;
case SASL_GSSAPI_NO_DATA:
sasl->params->getmessage(data->state.buffer, &serverdata);
/* Decode the security challenge and create the response message */
result = Curl_sasl_create_gssapi_security_message(data, serverdata,
&conn->krb5,
&resp, &len);
break;
#endif

case SASL_XOAUTH2:
/* Create the authorisation message */
result = Curl_sasl_create_xoauth2_message(data, conn->user,
conn->xoauth2_bearer,
&resp, &len);
break;
case SASL_CANCEL:
/* Remove the offending mechanism from the supported list */
sasl->authmechs ^= sasl->authused;

/* Start an alternative SASL authentication */
result = Curl_sasl_start(sasl, conn, sasl->force_ir, progress);
newstate = sasl->state; /* Use state from Curl_sasl_start() */
break;
default:
failf(data, "Unsupported SASL authentication mechanism");
result = CURLE_UNSUPPORTED_PROTOCOL; /* Should not happen */
break;
}

switch(result) {
case CURLE_BAD_CONTENT_ENCODING:
/* Cancel dialog */
result = sasl->params->sendcont(conn, "*");
newstate = SASL_CANCEL;
break;
case CURLE_OK:
if(resp)
result = sasl->params->sendcont(conn, resp);
break;
default:
newstate = SASL_STOP; /* Stop on error */
*progress = SASL_DONE;
break;
}

Curl_safefree(resp);
state(sasl, conn, newstate);
return result;
}

0 comments on commit 79543ca

Please sign in to comment.