Commit 79543caf authored by Patrick Monnerat's avatar Patrick Monnerat
Browse files

SASL: common state engine for imap/pop3/smtp

parent e1ea18f9
Loading
Loading
Loading
Loading
+330 −1
Original line number Diff line number Diff line
@@ -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 */
@@ -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;
}
+53 −1
Original line number Diff line number Diff line
@@ -68,13 +68,57 @@ enum {
  CURLDIGESTALGO_MD5SESS
};

/* SASL machine states */
typedef enum {
  SASL_STOP,
  SASL_PLAIN,
  SASL_LOGIN,
  SASL_LOGIN_PASSWD,
  SASL_CRAMMD5,
  SASL_DIGESTMD5,
  SASL_DIGESTMD5_RESP,
  SASL_NTLM,
  SASL_NTLM_TYPE2MSG,
  SASL_GSSAPI,
  SASL_GSSAPI_TOKEN,
  SASL_GSSAPI_NO_DATA,
  SASL_XOAUTH2,
  SASL_CANCEL,
  SASL_FINAL
} saslstate;

/* Progress indicator */
typedef enum {
  SASL_IDLE,
  SASL_INPROGRESS,
  SASL_DONE
} saslprogress;

/* Protocol dependent SASL parameters */
struct SASLproto {
  const char *service;     /* The service name */
  int contcode;            /* Code to receive when continuation is expected */
  int finalcode;           /* Code to receive upon authentication success */
  size_t maxirlen;         /* Maximum initial response length */
  CURLcode (*sendauth)(struct connectdata *conn,
                       const char *mech, const char *ir);
                           /* Send authentication command */
  CURLcode (*sendcont)(struct connectdata *conn, const char *contauth);
                           /* Send authentication continuation */
  void (*getmessage)(char *buffer, char **outptr);
                           /* Get SASL response message */
};

/* Per-connection parameters */
struct SASL {
  const struct SASLproto *params; /* Protocol dependent parameters */
  saslstate state;         /* Current machine state */
  unsigned int authmechs;  /* Accepted authentication mechanisms */
  unsigned int prefmech;   /* Preferred authentication mechanism */
  unsigned int authused;   /* Auth mechanism used for the connection */
  bool resetprefs;         /* For URL auth option parsing. */
  bool mutual_auth;        /* Mutual authentication enabled (GSSAPI only) */
  bool force_ir;           /* Protocol always supports initial response */
};

/* This is used to test whether the line starts with the given mechanism */
@@ -211,6 +255,14 @@ CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl,
                                         const char *value, size_t len);

/* Initializes an SASL structure */
void Curl_sasl_init(struct SASL *sasl);
void Curl_sasl_init(struct SASL *sasl, const struct SASLproto *params);

/* Calculate the required login details for SASL authentication  */
CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn,
                         bool force_ir, saslprogress *progress);

/* Continue an SASL authentication  */
CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn,
                         int code, saslprogress *progress);

#endif /* HEADER_CURL_SASL_H */
+64 −774

File changed.

Preview size limit exceeded, changes collapsed.

+1 −14
Original line number Diff line number Diff line
@@ -36,20 +36,7 @@ typedef enum {
  IMAP_STARTTLS,
  IMAP_UPGRADETLS,   /* asynchronously upgrade the connection to SSL/TLS
                       (multi mode only) */
  IMAP_AUTHENTICATE_PLAIN,
  IMAP_AUTHENTICATE_LOGIN,
  IMAP_AUTHENTICATE_LOGIN_PASSWD,
  IMAP_AUTHENTICATE_CRAMMD5,
  IMAP_AUTHENTICATE_DIGESTMD5,
  IMAP_AUTHENTICATE_DIGESTMD5_RESP,
  IMAP_AUTHENTICATE_NTLM,
  IMAP_AUTHENTICATE_NTLM_TYPE2MSG,
  IMAP_AUTHENTICATE_GSSAPI,
  IMAP_AUTHENTICATE_GSSAPI_TOKEN,
  IMAP_AUTHENTICATE_GSSAPI_NO_DATA,
  IMAP_AUTHENTICATE_XOAUTH2,
  IMAP_AUTHENTICATE_CANCEL,
  IMAP_AUTHENTICATE_FINAL,
  IMAP_AUTHENTICATE,
  IMAP_LOGIN,
  IMAP_LIST,
  IMAP_SELECT,
+74 −775

File changed.

Preview size limit exceeded, changes collapsed.

Loading