diff options
author | molotkov-and <[email protected]> | 2023-08-18 17:20:47 +0300 |
---|---|---|
committer | molotkov-and <[email protected]> | 2023-08-18 19:42:07 +0300 |
commit | 73215359bc33e76f5b94d1832a377072bf245cfc (patch) | |
tree | 9cb8ad61d8c3cd107353d42951560ff3cf1b966d /contrib/libs/sasl/plugins | |
parent | 1cbfd34a55732f7b1d407986b45e40853f01f2c2 (diff) |
KIKIMR-18220: Enrich token with groups from LDAP
Add ldap functions wrapper and separate in different files for compatibility with different OS.
Add user groups fetching from ldap server.
Limitations:
- Fixed 'memberOf' attribute
- No tests to check how filter for search created
- Fetched groups are returned in event as is.
Diffstat (limited to 'contrib/libs/sasl/plugins')
-rw-r--r-- | contrib/libs/sasl/plugins/anonymous.c | 387 | ||||
-rw-r--r-- | contrib/libs/sasl/plugins/cram.c | 687 | ||||
-rw-r--r-- | contrib/libs/sasl/plugins/digestmd5.c | 4778 | ||||
-rw-r--r-- | contrib/libs/sasl/plugins/otp.c | 1895 | ||||
-rw-r--r-- | contrib/libs/sasl/plugins/otp.h | 311 | ||||
-rw-r--r-- | contrib/libs/sasl/plugins/plain.c | 489 | ||||
-rw-r--r-- | contrib/libs/sasl/plugins/sasldb.c | 317 | ||||
-rw-r--r-- | contrib/libs/sasl/plugins/scram.c | 3087 |
8 files changed, 11951 insertions, 0 deletions
diff --git a/contrib/libs/sasl/plugins/anonymous.c b/contrib/libs/sasl/plugins/anonymous.c new file mode 100644 index 00000000000..a57266a746b --- /dev/null +++ b/contrib/libs/sasl/plugins/anonymous.c @@ -0,0 +1,387 @@ +/* Anonymous SASL plugin + * Rob Siemborski + * Tim Martin + */ +/* + * Copyright (c) 1998-2016 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <config.h> +#include <stdio.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <sasl.h> +#include <saslplug.h> + +#include "plugin_common.h" + +#ifdef macintosh +#error #include <sasl_anonymous_plugin_decl.h> +#endif + +/***************************** Common Section *****************************/ + +static const char anonymous_id[] = "anonymous"; + +/***************************** Server Section *****************************/ + +static int +anonymous_server_mech_new(void *glob_context __attribute__((unused)), + sasl_server_params_t *sparams, + const char *challenge __attribute__((unused)), + unsigned challen __attribute__((unused)), + void **conn_context) +{ + /* holds state are in */ + if (!conn_context) { + PARAMERROR( sparams->utils ); + return SASL_BADPARAM; + } + + *conn_context = NULL; + + return SASL_OK; +} + +static int +anonymous_server_mech_step(void *conn_context __attribute__((unused)), + sasl_server_params_t *sparams, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + char *clientdata; + int result; + + if (!sparams + || !serverout + || !serveroutlen + || !oparams) { + if (sparams) PARAMERROR( sparams->utils ); + return SASL_BADPARAM; + } + + *serverout = NULL; + *serveroutlen = 0; + + if (!clientin) { + return SASL_CONTINUE; + } + + /* We force a truncation 255 characters (specified by RFC 2245) */ + if (clientinlen > 255) clientinlen = 255; + + /* NULL-terminate the clientin... */ + clientdata = sparams->utils->malloc(clientinlen + 1); + if (!clientdata) { + MEMERROR(sparams->utils); + return SASL_NOMEM; + } + + strncpy(clientdata, clientin, clientinlen); + clientdata[clientinlen] = '\0'; + + sparams->utils->log(sparams->utils->conn, + SASL_LOG_NOTE, + "ANONYMOUS login: \"%s\"", + clientdata); + + if (clientdata != clientin) + sparams->utils->free(clientdata); + + result = sparams->canon_user(sparams->utils->conn, + anonymous_id, 0, + SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); + + if (result != SASL_OK) return result; + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + return SASL_OK; +} + +static sasl_server_plug_t anonymous_server_plugins[] = +{ + { + "ANONYMOUS", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOPLAINTEXT, /* security_flags */ + SASL_FEAT_WANT_CLIENT_FIRST + | SASL_FEAT_DONTUSE_USERPASSWD, /* features */ + NULL, /* glob_context */ + &anonymous_server_mech_new, /* mech_new */ + &anonymous_server_mech_step, /* mech_step */ + NULL, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + NULL, /* mech_avail */ + NULL /* spare */ + } +}; + +int anonymous_server_plug_init(const sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_server_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_SERVER_PLUG_VERSION) { + SETERROR( utils, "ANONYMOUS version mismatch" ); + return SASL_BADVERS; + } + + *out_version = SASL_SERVER_PLUG_VERSION; + *pluglist = anonymous_server_plugins; + *plugcount = 1; + + return SASL_OK; +} + +/***************************** Client Section *****************************/ + +typedef struct client_context { + char *out_buf; + unsigned out_buf_len; +} client_context_t; + +static int +anonymous_client_mech_new(void *glob_context __attribute__((unused)), + sasl_client_params_t *cparams, + void **conn_context) +{ + client_context_t *text; + + if (!conn_context) { + PARAMERROR(cparams->utils); + return SASL_BADPARAM; + } + + /* holds state are in */ + text = cparams->utils->malloc(sizeof(client_context_t)); + if (text == NULL) { + MEMERROR(cparams->utils); + return SASL_NOMEM; + } + + memset(text, 0, sizeof(client_context_t)); + + *conn_context = text; + + return SASL_OK; +} + +static int +anonymous_client_mech_step(void *conn_context, + sasl_client_params_t *cparams, + const char *serverin __attribute__((unused)), + unsigned serverinlen, + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + client_context_t *text = (client_context_t *) conn_context; + size_t userlen; + char hostname[256]; + const char *user = NULL; + int user_result = SASL_OK; + int result; + + if (!cparams + || !clientout + || !clientoutlen + || !oparams) { + if (cparams) PARAMERROR( cparams->utils ); + return SASL_BADPARAM; + } + + *clientout = NULL; + *clientoutlen = 0; + + if (serverinlen != 0) { + SETERROR( cparams->utils, + "Nonzero serverinlen in ANONYMOUS continue_step" ); + return SASL_BADPROT; + } + + /* check if sec layer strong enough */ + if (cparams->props.min_ssf > cparams->external_ssf) { + SETERROR( cparams->utils, "SSF requested of ANONYMOUS plugin"); + return SASL_TOOWEAK; + } + + /* try to get the trace info */ + if (user == NULL) { + user_result = _plug_get_userid(cparams->utils, &user, prompt_need); + + if ((user_result != SASL_OK) && (user_result != SASL_INTERACT)) { + return user_result; + } + } + + /* free prompts we got */ + if (prompt_need && *prompt_need) { + cparams->utils->free(*prompt_need); + *prompt_need = NULL; + } + + /* if there are prompts not filled in */ + if (user_result == SASL_INTERACT) { + /* make the prompt list */ + result = + _plug_make_prompts(cparams->utils, prompt_need, + "Please enter anonymous identification", + "", + NULL, NULL, + NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL); + if (result != SASL_OK) return result; + + return SASL_INTERACT; + } + + if (!user || !*user) { + user = anonymous_id; + } + userlen = strlen(user); + + result = cparams->canon_user(cparams->utils->conn, + anonymous_id, 0, + SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); + if (result != SASL_OK) return result; + + memset(hostname, 0, sizeof(hostname)); + gethostname(hostname, sizeof(hostname)); + hostname[sizeof(hostname)-1] = '\0'; + + *clientoutlen = (unsigned) (userlen + strlen(hostname) + 1); + + result = _plug_buf_alloc(cparams->utils, &text->out_buf, + &text->out_buf_len, *clientoutlen); + + if (result != SASL_OK) return result; + + strcpy(text->out_buf, user); + text->out_buf[userlen] = '@'; + /* use memcpy() instead of strcpy() so we don't add the NUL */ + memcpy(text->out_buf + userlen + 1, hostname, strlen(hostname)); + + *clientout = text->out_buf; + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + return SASL_OK; +} + +static void anonymous_client_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + client_context_t *text = (client_context_t *) conn_context; + + if(!text) return; + + if (text->out_buf) utils->free(text->out_buf); + + utils->free(text); +} + +static const unsigned long anonymous_required_prompts[] = { + SASL_CB_LIST_END +}; + +static sasl_client_plug_t anonymous_client_plugins[] = +{ + { + "ANONYMOUS", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOPLAINTEXT, /* security_flags */ + SASL_FEAT_WANT_CLIENT_FIRST, /* features */ + anonymous_required_prompts, /* required_prompts */ + NULL, /* glob_context */ + &anonymous_client_mech_new, /* mech_new */ + &anonymous_client_mech_step, /* mech_step */ + &anonymous_client_dispose, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* idle */ + NULL, /* spare */ + NULL /* spare */ + } +}; + +int anonymous_client_plug_init(const sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_client_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_CLIENT_PLUG_VERSION) { + SETERROR( utils, "ANONYMOUS version mismatch" ); + return SASL_BADVERS; + } + + *out_version = SASL_CLIENT_PLUG_VERSION; + *pluglist = anonymous_client_plugins; + *plugcount = 1; + + return SASL_OK; +} diff --git a/contrib/libs/sasl/plugins/cram.c b/contrib/libs/sasl/plugins/cram.c new file mode 100644 index 00000000000..4d19a640686 --- /dev/null +++ b/contrib/libs/sasl/plugins/cram.c @@ -0,0 +1,687 @@ +/* CRAM-MD5 SASL plugin + * Rob Siemborski + * Tim Martin + */ +/* + * Copyright (c) 1998-2016 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <config.h> + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#ifndef macintosh +#include <sys/stat.h> +#endif +#include <fcntl.h> + +#include <sasl.h> +#include <saslplug.h> +#include <saslutil.h> + +#include "plugin_common.h" + +#ifdef macintosh +#error #include <sasl_cram_plugin_decl.h> +#endif + +/***************************** Common Section *****************************/ + +/* convert a string of 8bit chars to it's representation in hex + * using lowercase letters + */ +static char *convert16(unsigned char *in, int inlen, const sasl_utils_t *utils) +{ + static char hex[]="0123456789abcdef"; + int lup; + char *out; + + out = utils->malloc(inlen*2+1); + if (out == NULL) return NULL; + + for (lup=0; lup < inlen; lup++) { + out[lup*2] = hex[in[lup] >> 4]; + out[lup*2+1] = hex[in[lup] & 15]; + } + + out[lup*2] = 0; + return out; +} + + +/***************************** Server Section *****************************/ + +typedef struct server_context { + int state; + + char *challenge; +} server_context_t; + +static int +crammd5_server_mech_new(void *glob_context __attribute__((unused)), + sasl_server_params_t *sparams, + const char *challenge __attribute__((unused)), + unsigned challen __attribute__((unused)), + void **conn_context) +{ + server_context_t *text; + + /* holds state are in */ + text = sparams->utils->malloc(sizeof(server_context_t)); + if (text == NULL) { + MEMERROR( sparams->utils ); + return SASL_NOMEM; + } + + memset(text, 0, sizeof(server_context_t)); + + text->state = 1; + + *conn_context = text; + + return SASL_OK; +} + +/* + * Returns the current time (or part of it) in string form + * maximum length=15 + */ +static char *gettime(sasl_server_params_t *sparams) +{ + char *ret; + time_t t; + + t=time(NULL); + ret= sparams->utils->malloc(15); + if (ret==NULL) return NULL; + + /* the bottom bits are really the only random ones so if + we overflow we don't want to loose them */ + snprintf(ret,15,"%lu",t%(0xFFFFFF)); + + return ret; +} + +static char *randomdigits(sasl_server_params_t *sparams) +{ + unsigned int num; + char *ret; + unsigned char temp[5]; /* random 32-bit number */ + + sparams->utils->rand(sparams->utils->rpool,(char *) temp,4); + num=(temp[0] * 256 * 256 * 256) + + (temp[1] * 256 * 256) + + (temp[2] * 256) + + (temp[3] ); + + ret = sparams->utils->malloc(15); /* there's no way an unsigned can be longer than this right? */ + if (ret == NULL) return NULL; + sprintf(ret, "%u", num); + + return ret; +} + +static int +crammd5_server_mech_step1(server_context_t *text, + sasl_server_params_t *sparams, + const char *clientin __attribute__((unused)), + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams __attribute__((unused))) +{ + char *time, *randdigits; + + /* we shouldn't have received anything */ + if (clientinlen != 0) { + SETERROR(sparams->utils, "CRAM-MD5 does not accept inital data"); + return SASL_BADPROT; + } + + /* get time and a random number for the nonce */ + time = gettime(sparams); + randdigits = randomdigits(sparams); + if ((time == NULL) || (randdigits == NULL)) { + MEMERROR( sparams->utils ); + return SASL_NOMEM; + } + + /* allocate some space for the challenge */ + text->challenge = sparams->utils->malloc(200 + 1); + if (text->challenge == NULL) { + MEMERROR(sparams->utils); + return SASL_NOMEM; + } + + /* create the challenge */ + snprintf(text->challenge, 200, "<%s.%s@%s>", randdigits, time, + sparams->serverFQDN); + + *serverout = text->challenge; + *serveroutlen = (unsigned) strlen(text->challenge); + + /* free stuff */ + sparams->utils->free(time); + sparams->utils->free(randdigits); + + text->state = 2; + + return SASL_CONTINUE; +} + +static int +crammd5_server_mech_step2(server_context_t *text, + sasl_server_params_t *sparams, + const char *clientin, + unsigned clientinlen, + const char **serverout __attribute__((unused)), + unsigned *serveroutlen __attribute__((unused)), + sasl_out_params_t *oparams) +{ + char *userid = NULL; + sasl_secret_t *sec = NULL; + int pos; + size_t len; + int result = SASL_FAIL; + const char *password_request[] = { SASL_AUX_PASSWORD, +#if defined(OBSOLETE_CRAM_ATTR) + "*cmusaslsecretCRAM-MD5", +#endif + NULL }; + struct propval auxprop_values[3]; + HMAC_MD5_CTX tmphmac; + HMAC_MD5_STATE md5state; + int clear_md5state = 0; + char *digest_str = NULL; + SASL_UINT4 digest[4]; + + /* extract userid; everything before last space */ + pos = clientinlen-1; + while ((pos > 0) && (clientin[pos] != ' ')) pos--; + + if (pos <= 0) { + SETERROR( sparams->utils,"need authentication name"); + return SASL_BADPROT; + } + + userid = (char *) sparams->utils->malloc(pos+1); + if (userid == NULL) { + MEMERROR( sparams->utils); + return SASL_NOMEM; + } + + /* copy authstr out */ + memcpy(userid, clientin, pos); + userid[pos] = '\0'; + + result = sparams->utils->prop_request(sparams->propctx, password_request); + if (result != SASL_OK) goto done; + + /* this will trigger the getting of the aux properties */ + result = sparams->canon_user(sparams->utils->conn, + userid, 0, SASL_CU_AUTHID | SASL_CU_AUTHZID, + oparams); + if (result != SASL_OK) goto done; + + result = sparams->utils->prop_getnames(sparams->propctx, + password_request, + auxprop_values); + if (result < 0 || + ((!auxprop_values[0].name || !auxprop_values[0].values) +#if defined(OBSOLETE_CRAM_ATTR) + && (!auxprop_values[1].name || !auxprop_values[1].values) +#endif + )) { + /* We didn't find this username */ + sparams->utils->seterror(sparams->utils->conn,0, + "no secret in database"); + result = sparams->transition ? SASL_TRANS : SASL_NOUSER; + goto done; + } + + if (auxprop_values[0].name && auxprop_values[0].values) { + len = strlen(auxprop_values[0].values[0]); + if (len == 0) { + sparams->utils->seterror(sparams->utils->conn,0, + "empty secret"); + result = SASL_FAIL; + goto done; + } + + sec = sparams->utils->malloc(sizeof(sasl_secret_t) + len); + if (!sec) goto done; + + sec->len = (unsigned) len; + strncpy((char *)sec->data, auxprop_values[0].values[0], len + 1); + + clear_md5state = 1; + /* Do precalculation on plaintext secret */ + sparams->utils->hmac_md5_precalc(&md5state, /* OUT */ + sec->data, + sec->len); +#if defined(OBSOLETE_CRAM_ATTR) + } else if (auxprop_values[1].name && auxprop_values[1].values) { + /* We have a precomputed secret */ + memcpy(&md5state, auxprop_values[1].values[0], + sizeof(HMAC_MD5_STATE)); +#endif + } else { + sparams->utils->seterror(sparams->utils->conn, 0, + "Have neither type of secret"); + return SASL_FAIL; + } + + /* erase the plaintext password */ + sparams->utils->prop_erase(sparams->propctx, password_request[0]); + + /* ok this is annoying: + so we have this half-way hmac transform instead of the plaintext + that means we half to: + -import it back into a md5 context + -do an md5update with the nonce + -finalize it + */ + sparams->utils->hmac_md5_import(&tmphmac, (HMAC_MD5_STATE *) &md5state); + sparams->utils->MD5Update(&(tmphmac.ictx), + (const unsigned char *) text->challenge, + (unsigned) strlen(text->challenge)); + sparams->utils->hmac_md5_final((unsigned char *) &digest, &tmphmac); + + /* convert to base 16 with lower case letters */ + digest_str = convert16((unsigned char *) digest, 16, sparams->utils); + + /* if same then verified + * - we know digest_str is null terminated but clientin might not be + * - verify the length of clientin anyway! + */ + len = strlen(digest_str); + if (clientinlen-pos-1 < len || + strncmp(digest_str, clientin+pos+1, len) != 0) { + sparams->utils->seterror(sparams->utils->conn, 0, + "incorrect digest response"); + result = SASL_BADAUTH; + goto done; + } + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + result = SASL_OK; + + done: + if (userid) sparams->utils->free(userid); + if (sec) _plug_free_secret(sparams->utils, &sec); + + if (digest_str) sparams->utils->free(digest_str); + if (clear_md5state) memset(&md5state, 0, sizeof(md5state)); + + return result; +} + +static int crammd5_server_mech_step(void *conn_context, + sasl_server_params_t *sparams, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + server_context_t *text = (server_context_t *) conn_context; + + *serverout = NULL; + *serveroutlen = 0; + + if (text == NULL) { + return SASL_BADPROT; + } + + /* this should be well more than is ever needed */ + if (clientinlen > 1024) { + SETERROR(sparams->utils, "CRAM-MD5 input longer than 1024 bytes"); + return SASL_BADPROT; + } + + switch (text->state) { + + case 1: + return crammd5_server_mech_step1(text, sparams, + clientin, clientinlen, + serverout, serveroutlen, + oparams); + + case 2: + return crammd5_server_mech_step2(text, sparams, + clientin, clientinlen, + serverout, serveroutlen, + oparams); + + default: /* should never get here */ + sparams->utils->log(NULL, SASL_LOG_ERR, + "Invalid CRAM-MD5 server step %d\n", text->state); + return SASL_FAIL; + } + + return SASL_FAIL; /* should never get here */ +} + +static void crammd5_server_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + server_context_t *text = (server_context_t *) conn_context; + + if (!text) return; + + if (text->challenge) _plug_free_string(utils,&(text->challenge)); + + utils->free(text); +} + +static sasl_server_plug_t crammd5_server_plugins[] = +{ + { + "CRAM-MD5", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS, /* security_flags */ + SASL_FEAT_SERVER_FIRST, /* features */ + NULL, /* glob_context */ + &crammd5_server_mech_new, /* mech_new */ + &crammd5_server_mech_step, /* mech_step */ + &crammd5_server_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + NULL, /* mech avail */ + NULL /* spare */ + } +}; + +int crammd5_server_plug_init(const sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_server_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_SERVER_PLUG_VERSION) { + SETERROR( utils, "CRAM version mismatch"); + return SASL_BADVERS; + } + + *out_version = SASL_SERVER_PLUG_VERSION; + *pluglist = crammd5_server_plugins; + *plugcount = 1; + + return SASL_OK; +} + +/***************************** Client Section *****************************/ + +typedef struct client_context { + char *out_buf; + unsigned out_buf_len; +} client_context_t; + +static int crammd5_client_mech_new(void *glob_context __attribute__((unused)), + sasl_client_params_t *params, + void **conn_context) +{ + client_context_t *text; + + /* holds state are in */ + text = params->utils->malloc(sizeof(client_context_t)); + if (text == NULL) { + MEMERROR(params->utils); + return SASL_NOMEM; + } + + memset(text, 0, sizeof(client_context_t)); + + *conn_context = text; + + return SASL_OK; +} + +static char *make_hashed(sasl_secret_t *sec, char *nonce, int noncelen, + const sasl_utils_t *utils) +{ + unsigned char digest[24]; + char *in16; + + if (sec == NULL) return NULL; + + /* do the hmac md5 hash output 128 bits */ + utils->hmac_md5((unsigned char *) nonce, noncelen, + sec->data, sec->len, digest); + + /* convert that to hex form */ + in16 = convert16(digest, 16, utils); + if (in16 == NULL) return NULL; + + return in16; +} + +static int crammd5_client_mech_step(void *conn_context, + sasl_client_params_t *params, + const char *serverin, + unsigned serverinlen, + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + client_context_t *text = (client_context_t *) conn_context; + const char *authid = NULL; + sasl_secret_t *password = NULL; + unsigned int free_password = 0; /* set if we need to free password */ + int auth_result = SASL_OK; + int pass_result = SASL_OK; + int result; + size_t maxsize; + char *in16 = NULL; + + *clientout = NULL; + *clientoutlen = 0; + + /* First check for absurd lengths */ + if (serverinlen > 1024) { + params->utils->seterror(params->utils->conn, 0, + "CRAM-MD5 input longer than 1024 bytes"); + return SASL_BADPROT; + } + + /* check if sec layer strong enough */ + if (params->props.min_ssf > params->external_ssf) { + SETERROR( params->utils, "SSF requested of CRAM-MD5 plugin"); + return SASL_TOOWEAK; + } + + /* try to get the userid */ + if (oparams->authid == NULL) { + auth_result=_plug_get_authid(params->utils, &authid, prompt_need); + + if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT)) + return auth_result; + } + + /* try to get the password */ + if (password == NULL) { + pass_result=_plug_get_password(params->utils, &password, + &free_password, prompt_need); + + if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT)) + return pass_result; + } + + /* free prompts we got */ + if (prompt_need && *prompt_need) { + params->utils->free(*prompt_need); + *prompt_need = NULL; + } + + /* if there are prompts not filled in */ + if ((auth_result == SASL_INTERACT) || (pass_result == SASL_INTERACT)) { + /* make the prompt list */ + result = + _plug_make_prompts(params->utils, prompt_need, + NULL, NULL, + auth_result == SASL_INTERACT ? + "Please enter your authentication name" : NULL, + NULL, + pass_result == SASL_INTERACT ? + "Please enter your password" : NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL); + if (result != SASL_OK) goto cleanup; + + return SASL_INTERACT; + } + + if (!password) { + PARAMERROR(params->utils); + return SASL_BADPARAM; + } + + result = params->canon_user(params->utils->conn, authid, 0, + SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); + if (result != SASL_OK) goto cleanup; + + /* + * username SP digest (keyed md5 where key is passwd) + */ + + in16 = make_hashed(password, (char *) serverin, serverinlen, + params->utils); + + if (in16 == NULL) { + SETERROR(params->utils, "whoops, make_hashed failed us this time"); + result = SASL_FAIL; + goto cleanup; + } + + maxsize = 32+1+strlen(oparams->authid)+30; + result = _plug_buf_alloc(params->utils, &(text->out_buf), + &(text->out_buf_len), (unsigned) maxsize); + if (result != SASL_OK) goto cleanup; + + snprintf(text->out_buf, maxsize, "%s %s", oparams->authid, in16); + + *clientout = text->out_buf; + *clientoutlen = (unsigned) strlen(*clientout); + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + result = SASL_OK; + + cleanup: + /* get rid of private information */ + if (in16) _plug_free_string(params->utils, &in16); + + /* get rid of all sensitive info */ + if (free_password) _plug_free_secret(params-> utils, &password); + + return result; +} + +static void crammd5_client_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + client_context_t *text = (client_context_t *) conn_context; + + if (!text) return; + + if (text->out_buf) utils->free(text->out_buf); + + utils->free(text); +} + +static sasl_client_plug_t crammd5_client_plugins[] = +{ + { + "CRAM-MD5", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS, /* security_flags */ + SASL_FEAT_SERVER_FIRST, /* features */ + NULL, /* required_prompts */ + NULL, /* glob_context */ + &crammd5_client_mech_new, /* mech_new */ + &crammd5_client_mech_step, /* mech_step */ + &crammd5_client_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* idle */ + NULL, /* spare */ + NULL /* spare */ + } +}; + +int crammd5_client_plug_init(const sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_client_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_CLIENT_PLUG_VERSION) { + SETERROR( utils, "CRAM version mismatch"); + return SASL_BADVERS; + } + + *out_version = SASL_CLIENT_PLUG_VERSION; + *pluglist = crammd5_client_plugins; + *plugcount = 1; + + return SASL_OK; +} diff --git a/contrib/libs/sasl/plugins/digestmd5.c b/contrib/libs/sasl/plugins/digestmd5.c new file mode 100644 index 00000000000..59f87135f68 --- /dev/null +++ b/contrib/libs/sasl/plugins/digestmd5.c @@ -0,0 +1,4778 @@ +/* DIGEST-MD5 SASL plugin + * Ken Murchison + * Rob Siemborski + * Tim Martin + * Alexey Melnikov + */ +/* + * Copyright (c) 1998-2016 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#ifndef macintosh +#include <sys/types.h> +#include <sys/stat.h> +#endif +#include <fcntl.h> +#include <ctype.h> + +/* DES support */ +#ifdef WITH_DES +# ifdef WITH_SSL_DES +# include <openssl/des.h> +# include <openssl/opensslv.h> +# if (OPENSSL_VERSION_NUMBER >= 0x0090700f) && \ + !defined(OPENSSL_ENABLE_OLD_DES_SUPPORT) +# define des_cblock DES_cblock +# define des_key_schedule DES_key_schedule +# define des_key_sched(k,ks) \ + DES_key_sched((k),&(ks)) +# define des_cbc_encrypt(i,o,l,k,iv,e) \ + DES_cbc_encrypt((i),(o),(l),&(k),(iv),(e)) +# define des_ede2_cbc_encrypt(i,o,l,k1,k2,iv,e) \ + DES_ede2_cbc_encrypt((i),(o),(l),&(k1),&(k2),(iv),(e)) +# endif /* OpenSSL 0.9.7+ w/o old DES support */ +# else /* system DES library */ +#ifdef HAVE_DES_H +# error #include <des.h> +#endif +# endif +#endif /* WITH_DES */ + +#ifdef WIN32 +# include <winsock2.h> +#else /* Unix */ +# include <netinet/in.h> +#endif /* WIN32 */ + +#include <sasl.h> +#include <saslplug.h> + +#include "plugin_common.h" + +#ifndef WIN32 +extern int strcasecmp(const char *s1, const char *s2); +#endif /* end WIN32 */ + +#ifdef macintosh +#error #include <sasl_md5_plugin_decl.h> +#endif + +/* external definitions */ + +#define bool int + +#ifndef TRUE +#define TRUE (1) +#define FALSE (0) +#endif + +/* MAX_UIN32_DIV_10 * 10 + MAX_UIN32_MOD_10 == 2^32-1 == 4294967295 */ +#define MAX_UIN32_DIV_10 429496729 +#define MAX_UIN32_MOD_10 5 + +#define DEFAULT_BUFSIZE 0xFFFF +#define MAX_SASL_BUFSIZE 0xFFFFFF + +/***************************** Common Section *****************************/ + +/* Definitions */ +#define NONCE_SIZE (32) /* arbitrary */ + +/* Layer Flags */ +#define DIGEST_NOLAYER (1) +#define DIGEST_INTEGRITY (2) +#define DIGEST_PRIVACY (4) + +/* defines */ +#define HASHLEN 16 +typedef unsigned char HASH[HASHLEN + 1]; +#define HASHHEXLEN 32 +typedef unsigned char HASHHEX[HASHHEXLEN + 1]; + +#define MAC_SIZE 10 +#define MAC_OFFS 2 + +const char *SEALING_CLIENT_SERVER="Digest H(A1) to client-to-server sealing key magic constant"; +const char *SEALING_SERVER_CLIENT="Digest H(A1) to server-to-client sealing key magic constant"; + +const char *SIGNING_CLIENT_SERVER="Digest session key to client-to-server signing key magic constant"; +const char *SIGNING_SERVER_CLIENT="Digest session key to server-to-client signing key magic constant"; + +#define HT (9) +#define CR (13) +#define LF (10) +#define SP (32) +#define DEL (127) + +#define NEED_ESCAPING "\"\\" + +#define REALM_CHAL_PREFIX "Available realms:" + +static char *quote (char *str); + +struct context; + +/* function definitions for cipher encode/decode */ +typedef int cipher_function_t(struct context *, + const char *, + unsigned, + unsigned char[], + char *, + unsigned *); + +typedef int cipher_init_t(struct context *, unsigned char [16], + unsigned char [16]); +typedef void cipher_free_t(struct context *); + +enum Context_type { SERVER = 0, CLIENT = 1 }; + +typedef struct cipher_context cipher_context_t; + +/* cached auth info used for fast reauth */ +typedef struct reauth_entry { + char *authid; + char *realm; + unsigned char *nonce; + unsigned int nonce_count; + unsigned char *cnonce; + + union { + struct { + time_t timestamp; + } s; /* server stuff */ + + struct { + char *serverFQDN; + int protection; + struct digest_cipher *cipher; + unsigned long server_maxbuf; + + /* for HTTP mode (RFC 2617) only */ + char *algorithm; + unsigned char *opaque; + } c; /* client stuff */ + } u; +} reauth_entry_t; + +typedef struct reauth_cache { + /* static stuff */ + enum Context_type i_am; /* are we the client or server? */ + time_t timeout; + void *mutex; + unsigned size; + + reauth_entry_t *e; /* fixed-size hash table of entries */ +} reauth_cache_t; + +/* global context for reauth use */ +typedef struct digest_glob_context { + reauth_cache_t *reauth; +} digest_glob_context_t; + +/* context that stores info */ +typedef struct context { + int state; /* state in the authentication we are in */ + enum Context_type i_am; /* are we the client or server? */ + int http_mode; /* use RFC 2617 compatible protocol? */ + + reauth_cache_t *reauth; + + char *authid; + char *realm; + unsigned char *nonce; + unsigned int nonce_count; + unsigned char *cnonce; + + /* only used by the client */ + char ** realms; + int realm_cnt; + + char *response_value; + + unsigned int seqnum; + unsigned int rec_seqnum; /* for checking integrity */ + + HASH Ki_send; + HASH Ki_receive; + + HASH HA1; /* Kcc or Kcs */ + + /* copy of utils from the params structures */ + const sasl_utils_t *utils; + + /* For general use */ + char *out_buf; + unsigned out_buf_len; + + /* for encoding/decoding */ + buffer_info_t *enc_in_buf; + char *encode_buf, *decode_buf, *decode_packet_buf; + unsigned encode_buf_len, decode_buf_len, decode_packet_buf_len; + + decode_context_t decode_context; + + /* if privacy mode is used use these functions for encode and decode */ + cipher_function_t *cipher_enc; + cipher_function_t *cipher_dec; + cipher_init_t *cipher_init; + cipher_free_t *cipher_free; + struct cipher_context *cipher_enc_context; + struct cipher_context *cipher_dec_context; +} context_t; + +struct digest_cipher { + char *name; + sasl_ssf_t ssf; + int n; /* bits to make privacy key */ + int flag; /* a bitmask to make things easier for us */ + + cipher_function_t *cipher_enc; + cipher_function_t *cipher_dec; + cipher_init_t *cipher_init; + cipher_free_t *cipher_free; +}; +#if 0 +static const unsigned char *COLON = ":"; +#else +static const unsigned char COLON[] = { ':', '\0' }; +#endif +/* Hashes a string to produce an unsigned short */ +static unsigned hash(const char *str) +{ + unsigned val = 0; + int i; + + while (str && *str) { + i = (int) *str; + val ^= i; + val <<= 1; + str++; + } + + return val; +} + +static void CvtHex(HASH Bin, HASHHEX Hex) +{ + unsigned short i; + unsigned char j; + + for (i = 0; i < HASHLEN; i++) { + j = (Bin[i] >> 4) & 0xf; + if (j <= 9) + Hex[i * 2] = (j + '0'); + else + Hex[i * 2] = (j + 'a' - 10); + j = Bin[i] & 0xf; + if (j <= 9) + Hex[i * 2 + 1] = (j + '0'); + else + Hex[i * 2 + 1] = (j + 'a' - 10); + } + Hex[HASHHEXLEN] = '\0'; +} + +/* + * calculate request-digest/response-digest as per HTTP Digest spec + */ +void +DigestCalcResponse(const sasl_utils_t * utils, + HASHHEX HA1, /* HEX(H(A1)) */ + unsigned char *pszNonce, /* nonce from server */ + unsigned int pszNonceCount, /* 8 hex digits */ + unsigned char *pszCNonce, /* client nonce */ + unsigned char *pszQop, /* qop-value: "", "auth", + * "auth-int" */ + unsigned char *pszDigestUri, /* requested URL */ + unsigned char *pszMethod, + HASHHEX HEntity, /* H(entity body) if qop="auth-int" */ + HASHHEX Response /* request-digest or response-digest */ + ) +{ + MD5_CTX Md5Ctx; + HASH HA2; + HASH RespHash; + HASHHEX HA2Hex; + unsigned char ncvalue[10]; + + /* calculate H(A2) */ + utils->MD5Init(&Md5Ctx); + + if (pszMethod != NULL) { + utils->MD5Update(&Md5Ctx, pszMethod, (unsigned) strlen((char *) pszMethod)); + } + utils->MD5Update(&Md5Ctx, (unsigned char *) COLON, 1); + + /* utils->MD5Update(&Md5Ctx, (unsigned char *) "AUTHENTICATE:", 13); */ + utils->MD5Update(&Md5Ctx, pszDigestUri, (unsigned) strlen((char *) pszDigestUri)); + if (strcasecmp((char *) pszQop, "auth") != 0) { + /* append ":00000000000000000000000000000000" */ + utils->MD5Update(&Md5Ctx, COLON, 1); + utils->MD5Update(&Md5Ctx, HEntity, HASHHEXLEN); + } + utils->MD5Final(HA2, &Md5Ctx); + CvtHex(HA2, HA2Hex); + + /* calculate response */ + utils->MD5Init(&Md5Ctx); + utils->MD5Update(&Md5Ctx, HA1, HASHHEXLEN); + utils->MD5Update(&Md5Ctx, COLON, 1); + utils->MD5Update(&Md5Ctx, pszNonce, (unsigned) strlen((char *) pszNonce)); + utils->MD5Update(&Md5Ctx, COLON, 1); + if (*pszQop) { + sprintf((char *)ncvalue, "%08x", pszNonceCount); + utils->MD5Update(&Md5Ctx, ncvalue, (unsigned) strlen((char *)ncvalue)); + utils->MD5Update(&Md5Ctx, COLON, 1); + utils->MD5Update(&Md5Ctx, pszCNonce, (unsigned) strlen((char *) pszCNonce)); + utils->MD5Update(&Md5Ctx, COLON, 1); + utils->MD5Update(&Md5Ctx, pszQop, (unsigned) strlen((char *) pszQop)); + utils->MD5Update(&Md5Ctx, COLON, 1); + } + utils->MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN); + utils->MD5Final(RespHash, &Md5Ctx); + CvtHex(RespHash, Response); +} + +static bool UTF8_In_8859_1(const unsigned char *base, size_t len) +{ + const unsigned char *scan, *end; + + end = base + len; + for (scan = base; scan < end; ++scan) { + if (*scan > 0xC3) + break; /* abort if outside 8859-1 */ + if (*scan >= 0xC0 && *scan <= 0xC3) { + if (++scan == end || *scan < 0x80 || *scan > 0xBF) + break; + } + } + + /* if scan >= end, then this is a 8859-1 string. */ + return (scan >= end); +} + +/* + * if the string is entirely in the 8859-1 subset of UTF-8, then translate to + * 8859-1 prior to MD5 + */ +static void MD5_UTF8_8859_1(const sasl_utils_t * utils, + MD5_CTX * ctx, + bool In_ISO_8859_1, + const unsigned char *base, + int len) +{ + const unsigned char *scan, *end; + unsigned char cbuf; + + end = base + len; + + /* if we found a character outside 8859-1, don't alter string */ + if (!In_ISO_8859_1) { + utils->MD5Update(ctx, base, len); + return; + } + /* convert to 8859-1 prior to applying hash */ + do { + for (scan = base; scan < end && *scan < 0xC0; ++scan); + if (scan != base) + utils->MD5Update(ctx, base, (unsigned) (scan - base)); + if (scan + 1 >= end) + break; + cbuf = ((scan[0] & 0x3) << 6) | (scan[1] & 0x3f); + utils->MD5Update(ctx, &cbuf, 1); + base = scan + 2; + } + while (base < end); +} + +/** + * Returns true if it mangled the username. + */ +static bool DigestCalcSecret(const sasl_utils_t * utils, + unsigned char *pszUserName, + unsigned char *pszRealm, + unsigned char *Password, + int PasswordLen, + bool Ignore_8859, + HASH HA1) +{ + bool In_8859_1; + bool Any_8859_1 = FALSE; + MD5_CTX Md5Ctx; + + /* Chris Newman clarified that the following text in DIGEST-MD5 spec + is bogus: "if name and password are both in ISO 8859-1 charset" + We shoud use code example instead */ + + utils->MD5Init(&Md5Ctx); + + /* We have to convert UTF-8 to ISO-8859-1 if possible */ + if (Ignore_8859 == FALSE) { + In_8859_1 = UTF8_In_8859_1(pszUserName, strlen((char *) pszUserName)); + MD5_UTF8_8859_1(utils, &Md5Ctx, In_8859_1, + pszUserName, (unsigned) strlen((char *) pszUserName)); + Any_8859_1 |= In_8859_1; + } else { + utils->MD5Update(&Md5Ctx, pszUserName, (unsigned) strlen((char *) pszUserName)); + } + + utils->MD5Update(&Md5Ctx, COLON, 1); + + /* a NULL realm is equivalent to the empty string */ + if (pszRealm != NULL && pszRealm[0] != '\0') { + if (Ignore_8859 == FALSE) { + /* We have to convert UTF-8 to ISO-8859-1 if possible */ + In_8859_1 = UTF8_In_8859_1(pszRealm, strlen((char *) pszRealm)); + MD5_UTF8_8859_1(utils, &Md5Ctx, In_8859_1, + pszRealm, (unsigned) strlen((char *) pszRealm)); + Any_8859_1 |= In_8859_1; + } else { + utils->MD5Update(&Md5Ctx, pszRealm, (unsigned) strlen((char *) pszRealm)); + } + } + + utils->MD5Update(&Md5Ctx, COLON, 1); + + if (Ignore_8859 == FALSE) { + /* We have to convert UTF-8 to ISO-8859-1 if possible */ + In_8859_1 = UTF8_In_8859_1(Password, PasswordLen); + MD5_UTF8_8859_1(utils, &Md5Ctx, In_8859_1, + Password, PasswordLen); + Any_8859_1 |= In_8859_1; + } else { + utils->MD5Update(&Md5Ctx, Password, PasswordLen); + } + utils->MD5Final(HA1, &Md5Ctx); + + return Any_8859_1; +} + +static unsigned char *create_nonce(const sasl_utils_t * utils) +{ + unsigned char *base64buf; + int base64len; + + char *ret = (char *) utils->malloc(NONCE_SIZE); + if (ret == NULL) + return NULL; + + utils->rand(utils->rpool, (char *) ret, NONCE_SIZE); + + /* base 64 encode it so it has valid chars */ + base64len = (NONCE_SIZE * 4 / 3) + (NONCE_SIZE % 3 ? 4 : 0); + + base64buf = (unsigned char *) utils->malloc(base64len + 1); + if (base64buf == NULL) { + utils->seterror(utils->conn, 0, "Unable to allocate final buffer"); + return NULL; + } + + /* + * Returns SASL_OK on success, SASL_BUFOVER if result won't fit + */ + if (utils->encode64(ret, NONCE_SIZE, + (char *) base64buf, base64len, NULL) != SASL_OK) { + utils->free(ret); + return NULL; + } + utils->free(ret); + + return base64buf; +} + +static int add_to_challenge(const sasl_utils_t *utils, + char **str, unsigned *buflen, unsigned *curlen, + char *name, + unsigned char *value, + bool need_quotes) +{ + size_t namesize = strlen(name); + size_t valuesize = strlen((char *) value); + unsigned newlen; + int ret; + + newlen = (unsigned) (*curlen + 1 + namesize + 2 + valuesize + 2); + ret = _plug_buf_alloc(utils, str, buflen, newlen); + if(ret != SASL_OK) return ret; + + if (*curlen > 0) { + strcat(*str, ","); + strcat(*str, name); + } else { + strcpy(*str, name); + } + + if (need_quotes) { + strcat(*str, "=\""); + + /* Check if the value needs quoting */ + if (strpbrk ((char *)value, NEED_ESCAPING) != NULL) { + char * quoted = quote ((char *) value); + if (quoted == NULL) + MEMERROR(utils); + valuesize = strlen(quoted); + /* As the quoted string is bigger, make sure we have enough + space now */ + ret = _plug_buf_alloc(utils, str, buflen, newlen); + if (ret == SASL_OK) { + strcat(*str, quoted); + free (quoted); + } else { + free (quoted); + return ret; + } + } else { + strcat(*str, (char *) value); + } + strcat(*str, "\""); + } else { + strcat(*str, "="); + strcat(*str, (char *) value); + } + + *curlen = newlen; + return SASL_OK; +} + +static int is_lws_char (char c) +{ + return (c == ' ' || c == HT || c == CR || c == LF); +} + +static char *skip_lws (char *s) +{ + if (!s) return NULL; + + /* skipping spaces: */ + while (is_lws_char(s[0])) { + if (s[0] == '\0') break; + s++; + } + + return s; +} + +/* Same as skip_lws, but do this right to left */ +/* skip LWSP at the end of the value (if any), skip_r_lws returns pointer to + the first LWSP character, NUL (if there were none) or NULL if the value + is entirely from LWSP characters */ +static char *skip_r_lws (char *s) +{ + char *end; + size_t len; + + if (!s) return NULL; + + len = strlen(s); + if (len == 0) return NULL; + + /* the last character before terminating NUL */ + end = s + len - 1; + + /* skipping spaces: */ + while (end > s && (end[0] == ' ' || end[0] == HT || end[0] == CR || end[0] == LF)) { + end--; + } + + /* If all string from spaces, return NULL */ + if (end == s && (end[0] == ' ' || end[0] == HT || end[0] == CR || end[0] == LF)) { + return NULL; + } else { + return (end + 1); + } +} + +static char *skip_token (char *s, int caseinsensitive) +{ + if(!s) return NULL; + + while (s[0]>SP) { + if (s[0]==DEL || s[0]=='(' || s[0]==')' || s[0]=='<' || s[0]=='>' || + s[0]=='@' || s[0]==',' || s[0]==';' || s[0]==':' || s[0]=='\\' || + s[0]=='\'' || s[0]=='/' || s[0]=='[' || s[0]==']' || s[0]== '?' || + s[0]=='=' || s[0]== '{' || s[0]== '}') { + if (caseinsensitive == 1) { + if (!isupper((unsigned char) s[0])) + break; + } else { + break; + } + } + s++; + } + return s; +} + +/* Convert a string to 32 bit unsigned integer. + Any number of trailing spaces is allowed, but not a string + entirely comprised of spaces */ +static bool str2ul32 (char *str, unsigned long * value) +{ + unsigned int n; + char c; + + if (str == NULL) { + return (FALSE); + } + + *value = 0; + + str = skip_lws (str); + if (str[0] == '\0') { + return (FALSE); + } + + n = 0; + while (str[0] != '\0') { + c = str[0]; + if (!isdigit((int)c)) { + return (FALSE); + } + +/* Will overflow after adding additional digit */ + if (n > MAX_UIN32_DIV_10) { + return (FALSE); + } else if (n == MAX_UIN32_DIV_10 && ((unsigned) (c - '0') > MAX_UIN32_MOD_10)) { + return (FALSE); + } + + n = n * 10 + (unsigned) (c - '0'); + str++; + } + + *value = n; + return (TRUE); +} + +/* NULL - error (unbalanced quotes), + otherwise pointer to the first character after the value. + The function performs work in place. */ +static char *unquote (char *qstr) +{ + char *endvalue; + int escaped = 0; + char *outptr; + + if(!qstr) return NULL; + + if (qstr[0] == '"') { + qstr++; + outptr = qstr; + + for (endvalue = qstr; endvalue[0] != '\0'; endvalue++, outptr++) { + if (escaped) { + outptr[0] = endvalue[0]; + escaped = 0; + } + else if (endvalue[0] == '\\') { + escaped = 1; + outptr--; /* Will be incremented at the end of the loop */ + } + else if (endvalue[0] == '"') { + break; + } + else { + outptr[0] = endvalue[0]; + } + } + + if (endvalue[0] != '"') { + return NULL; + } + + while (outptr <= endvalue) { + outptr[0] = '\0'; + outptr++; + } + endvalue++; + } + else { /* not qouted value (token) */ + /* qstr already contains output */ + endvalue = skip_token(qstr,0); + }; + + return endvalue; +} + +/* Unlike unquote, this function returns an allocated quoted copy */ +static char *quote (char *str) +{ + char *p; + char *outp; + char *result; + int num_to_escape; /* How many characters need escaping */ + + if (!str) return NULL; + + num_to_escape = 0; + p = strpbrk (str, NEED_ESCAPING); + while (p != NULL) { + num_to_escape++; + p = strpbrk (p + 1, NEED_ESCAPING); + } + + if (num_to_escape == 0) { + return (strdup (str)); + } + + result = malloc (strlen(str) + num_to_escape + 1); + if (result == NULL) { + return NULL; + } + for (p = str, outp = result; *p; p++) { + if (*p == '"' || *p == '\\') { + *outp = '\\'; + outp++; + } + *outp = *p; + outp++; + } + + *outp = '\0'; + + return (result); +} + +static void get_pair(char **in, char **name, char **value) +{ + char *endpair; + char *curp = *in; + *name = NULL; + *value = NULL; + + if (curp == NULL) return; + + while (curp[0] != '\0') { + /* skipping spaces: */ + curp = skip_lws(curp); + + /* 'LWS "," LWS "," ...' is allowed by the DIGEST-MD5 ABNF */ + if (curp[0] == ',') { + curp++; + } else { + break; + } + } + + if (curp[0] == '\0') { + /* End of the string is not an error */ + *name = ""; + return; + } + + *name = curp; + + curp = skip_token(curp,1); + + /* strip wierd chars */ + if (curp[0] != '=' && curp[0] != '\0') { + *curp++ = '\0'; + }; + + curp = skip_lws(curp); + + if (curp[0] != '=') { /* No '=' sign */ + *name = NULL; + return; + } + + curp[0] = '\0'; + curp++; + + curp = skip_lws(curp); + + *value = (curp[0] == '"') ? curp+1 : curp; + + endpair = unquote (curp); + if (endpair == NULL) { /* Unbalanced quotes */ + *name = NULL; + *value = NULL; + return; + } + + /* An optional LWS is allowed after the value. Skip it. */ + if (is_lws_char (endpair[0])) { + /* Remove the trailing LWS from the value */ + *endpair++ = '\0'; + endpair = skip_lws(endpair); + } + + /* syntax check: MUST be '\0' or ',' */ + if (endpair[0] == ',') { + endpair[0] = '\0'; + endpair++; /* skipping <,> */ + } else if (endpair[0] != '\0') { + *name = NULL; + *value = NULL; + return; + } + + *in = endpair; +} + +#ifdef WITH_DES +struct des_context_s { + des_key_schedule keysched; /* key schedule for des initialization */ + des_cblock ivec; /* initial vector for encoding */ + des_key_schedule keysched2; /* key schedule for 3des initialization */ +}; + +typedef struct des_context_s des_context_t; + +/* slide the first 7 bytes of 'inbuf' into the high seven bits of the + first 8 bytes of 'keybuf'. 'keybuf' better be 8 bytes long or longer. */ +static void slidebits(unsigned char *keybuf, unsigned char *inbuf) +{ + keybuf[0] = inbuf[0]; + keybuf[1] = (inbuf[0]<<7) | (inbuf[1]>>1); + keybuf[2] = (inbuf[1]<<6) | (inbuf[2]>>2); + keybuf[3] = (inbuf[2]<<5) | (inbuf[3]>>3); + keybuf[4] = (inbuf[3]<<4) | (inbuf[4]>>4); + keybuf[5] = (inbuf[4]<<3) | (inbuf[5]>>5); + keybuf[6] = (inbuf[5]<<2) | (inbuf[6]>>6); + keybuf[7] = (inbuf[6]<<1); +} + +/****************************** + * + * 3DES functions + * + *****************************/ + +static int dec_3des(context_t *text, + const char *input, + unsigned inputlen, + unsigned char digest[16] __attribute__((unused)), + char *output, + unsigned *outputlen) +{ + des_context_t *c = (des_context_t *) text->cipher_dec_context; + int padding, p; + + des_ede2_cbc_encrypt((void *) input, + (void *) output, + inputlen, + c->keysched, + c->keysched2, + &c->ivec, + DES_DECRYPT); + + /* now chop off the padding */ + padding = output[inputlen - 11]; + if (padding < 1 || padding > 8) { + /* invalid padding length */ + return SASL_FAIL; + } + /* verify all padding is correct */ + for (p = 1; p <= padding; p++) { + if (output[inputlen - 10 - p] != padding) { + return SASL_FAIL; + } + } + + /* chop off the padding */ + *outputlen = inputlen - padding - 10; + + return SASL_OK; +} + +static int enc_3des(context_t *text, + const char *input, + unsigned inputlen, + unsigned char digest[16], + char *output, + unsigned *outputlen) +{ + des_context_t *c = (des_context_t *) text->cipher_enc_context; + int len; + int paddinglen; + + /* determine padding length */ + paddinglen = 8 - ((inputlen + 10) % 8); + + /* now construct the full stuff to be ciphered */ + memcpy(output, input, inputlen); /* text */ + memset(output+inputlen, paddinglen, paddinglen);/* pad */ + memcpy(output+inputlen+paddinglen, digest, 10); /* hmac */ + + len=inputlen+paddinglen+10; + + des_ede2_cbc_encrypt((void *) output, + (void *) output, + len, + c->keysched, + c->keysched2, + &c->ivec, + DES_ENCRYPT); + + *outputlen=len; + + return SASL_OK; +} + +static int init_3des(context_t *text, + unsigned char enckey[16], + unsigned char deckey[16]) +{ + des_context_t *c; + unsigned char keybuf[8]; + + /* allocate enc & dec context */ + c = (des_context_t *) text->utils->malloc(2 * sizeof(des_context_t)); + if (c == NULL) return SASL_NOMEM; + + /* setup enc context */ + slidebits(keybuf, enckey); + if (des_key_sched((des_cblock *) keybuf, c->keysched) < 0) + return SASL_FAIL; + + slidebits(keybuf, enckey + 7); + if (des_key_sched((des_cblock *) keybuf, c->keysched2) < 0) + return SASL_FAIL; + memcpy(c->ivec, ((char *) enckey) + 8, 8); + + text->cipher_enc_context = (cipher_context_t *) c; + + /* setup dec context */ + c++; + slidebits(keybuf, deckey); + if (des_key_sched((des_cblock *) keybuf, c->keysched) < 0) + return SASL_FAIL; + + slidebits(keybuf, deckey + 7); + if (des_key_sched((des_cblock *) keybuf, c->keysched2) < 0) + return SASL_FAIL; + + memcpy(c->ivec, ((char *) deckey) + 8, 8); + + text->cipher_dec_context = (cipher_context_t *) c; + + return SASL_OK; +} + + +/****************************** + * + * DES functions + * + *****************************/ + +static int dec_des(context_t *text, + const char *input, + unsigned inputlen, + unsigned char digest[16] __attribute__((unused)), + char *output, + unsigned *outputlen) +{ + des_context_t *c = (des_context_t *) text->cipher_dec_context; + int p, padding = 0; + + des_cbc_encrypt((void *) input, + (void *) output, + inputlen, + c->keysched, + &c->ivec, + DES_DECRYPT); + + /* Update the ivec (des_cbc_encrypt implementations tend to be broken in + this way) */ + memcpy(c->ivec, input + (inputlen - 8), 8); + + /* now chop off the padding */ + padding = output[inputlen - 11]; + if (padding < 1 || padding > 8) { + /* invalid padding length */ + return SASL_FAIL; + } + /* verify all padding is correct */ + for (p = 1; p <= padding; p++) { + if (output[inputlen - 10 - p] != padding) { + return SASL_FAIL; + } + } + + /* chop off the padding */ + *outputlen = inputlen - padding - 10; + + return SASL_OK; +} + +static int enc_des(context_t *text, + const char *input, + unsigned inputlen, + unsigned char digest[16], + char *output, + unsigned *outputlen) +{ + des_context_t *c = (des_context_t *) text->cipher_enc_context; + int len; + int paddinglen; + + /* determine padding length */ + paddinglen = 8 - ((inputlen+10) % 8); + + /* now construct the full stuff to be ciphered */ + memcpy(output, input, inputlen); /* text */ + memset(output+inputlen, paddinglen, paddinglen);/* pad */ + memcpy(output+inputlen+paddinglen, digest, 10); /* hmac */ + + len = inputlen + paddinglen + 10; + + des_cbc_encrypt((void *) output, + (void *) output, + len, + c->keysched, + &c->ivec, + DES_ENCRYPT); + + /* Update the ivec (des_cbc_encrypt implementations tend to be broken in + this way) */ + memcpy(c->ivec, output + (len - 8), 8); + + *outputlen = len; + + return SASL_OK; +} + +static int init_des(context_t *text, + unsigned char enckey[16], + unsigned char deckey[16]) +{ + des_context_t *c; + unsigned char keybuf[8]; + + /* allocate enc context */ + c = (des_context_t *) text->utils->malloc(2 * sizeof(des_context_t)); + if (c == NULL) return SASL_NOMEM; + + /* setup enc context */ + slidebits(keybuf, enckey); + des_key_sched((des_cblock *) keybuf, c->keysched); + + memcpy(c->ivec, ((char *) enckey) + 8, 8); + + text->cipher_enc_context = (cipher_context_t *) c; + + /* setup dec context */ + c++; + slidebits(keybuf, deckey); + des_key_sched((des_cblock *) keybuf, c->keysched); + + memcpy(c->ivec, ((char *) deckey) + 8, 8); + + text->cipher_dec_context = (cipher_context_t *) c; + + return SASL_OK; +} + +static void free_des(context_t *text) +{ + /* free des contextss. only cipher_enc_context needs to be free'd, + since cipher_dec_context was allocated at the same time. */ + if (text->cipher_enc_context) text->utils->free(text->cipher_enc_context); +} + +#endif /* WITH_DES */ + +#ifdef WITH_RC4 +#ifdef HAVE_OPENSSL +#include <openssl/evp.h> + +static void free_rc4(context_t *text) +{ + if (text->cipher_enc_context) { + EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *)text->cipher_enc_context); + text->cipher_enc_context = NULL; + } + if (text->cipher_dec_context) { + EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *)text->cipher_dec_context); + text->cipher_dec_context = NULL; + } +} + +static int init_rc4(context_t *text, + unsigned char enckey[16], + unsigned char deckey[16]) +{ + EVP_CIPHER_CTX *ctx; + int rc; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) return SASL_NOMEM; + + rc = EVP_EncryptInit_ex(ctx, EVP_rc4(), NULL, enckey, NULL); + if (rc != 1) return SASL_FAIL; + + text->cipher_enc_context = (void *)ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) return SASL_NOMEM; + + rc = EVP_DecryptInit_ex(ctx, EVP_rc4(), NULL, deckey, NULL); + if (rc != 1) return SASL_FAIL; + + text->cipher_dec_context = (void *)ctx; + + return SASL_OK; +} + +static int dec_rc4(context_t *text, + const char *input, + unsigned inputlen, + unsigned char digest[16] __attribute__((unused)), + char *output, + unsigned *outputlen) +{ + int len; + int rc; + + /* decrypt the text part & HMAC */ + rc = EVP_DecryptUpdate((EVP_CIPHER_CTX *)text->cipher_dec_context, + (unsigned char *)output, &len, + (const unsigned char *)input, inputlen); + if (rc != 1) return SASL_FAIL; + + *outputlen = len; + + rc = EVP_DecryptFinal_ex((EVP_CIPHER_CTX *)text->cipher_dec_context, + (unsigned char *)output + len, &len); + if (rc != 1) return SASL_FAIL; + + *outputlen += len; + + /* subtract the HMAC to get the text length */ + *outputlen -= 10; + + return SASL_OK; +} + +static int enc_rc4(context_t *text, + const char *input, + unsigned inputlen, + unsigned char digest[16], + char *output, + unsigned *outputlen) +{ + int len; + int rc; + /* encrypt the text part */ + rc = EVP_EncryptUpdate((EVP_CIPHER_CTX *)text->cipher_enc_context, + (unsigned char *)output, &len, + (const unsigned char *)input, inputlen); + if (rc != 1) return SASL_FAIL; + + *outputlen = len; + + /* encrypt the `MAC part */ + rc = EVP_EncryptUpdate((EVP_CIPHER_CTX *)text->cipher_enc_context, + (unsigned char *)output + *outputlen, &len, + digest, 10); + if (rc != 1) return SASL_FAIL; + + *outputlen += len; + + rc = EVP_EncryptFinal_ex((EVP_CIPHER_CTX *)text->cipher_enc_context, + (unsigned char *)output + *outputlen, &len); + if (rc != 1) return SASL_FAIL; + + *outputlen += len; + + return SASL_OK; +} +#else +/* quick generic implementation of RC4 */ +struct rc4_context_s { + unsigned char sbox[256]; + int i, j; +}; + +typedef struct rc4_context_s rc4_context_t; + +static void rc4_init(rc4_context_t *text, + const unsigned char *key, + unsigned keylen) +{ + int i, j; + + /* fill in linearly s0=0 s1=1... */ + for (i=0;i<256;i++) + text->sbox[i]=i; + + j=0; + for (i = 0; i < 256; i++) { + unsigned char tmp; + /* j = (j + Si + Ki) mod 256 */ + j = (j + text->sbox[i] + key[i % keylen]) % 256; + + /* swap Si and Sj */ + tmp = text->sbox[i]; + text->sbox[i] = text->sbox[j]; + text->sbox[j] = tmp; + } + + /* counters initialized to 0 */ + text->i = 0; + text->j = 0; +} + +static void rc4_encrypt(rc4_context_t *text, + const char *input, + char *output, + unsigned len) +{ + int tmp; + int i = text->i; + int j = text->j; + int t; + int K; + const char *input_end = input + len; + + while (input < input_end) { + i = (i + 1) % 256; + + j = (j + text->sbox[i]) % 256; + + /* swap Si and Sj */ + tmp = text->sbox[i]; + text->sbox[i] = text->sbox[j]; + text->sbox[j] = tmp; + + t = (text->sbox[i] + text->sbox[j]) % 256; + + K = text->sbox[t]; + + /* byte K is Xor'ed with plaintext */ + *output++ = *input++ ^ K; + } + + text->i = i; + text->j = j; +} + +static void rc4_decrypt(rc4_context_t *text, + const char *input, + char *output, + unsigned len) +{ + int tmp; + int i = text->i; + int j = text->j; + int t; + int K; + const char *input_end = input + len; + + while (input < input_end) { + i = (i + 1) % 256; + + j = (j + text->sbox[i]) % 256; + + /* swap Si and Sj */ + tmp = text->sbox[i]; + text->sbox[i] = text->sbox[j]; + text->sbox[j] = tmp; + + t = (text->sbox[i] + text->sbox[j]) % 256; + + K = text->sbox[t]; + + /* byte K is Xor'ed with plaintext */ + *output++ = *input++ ^ K; + } + + text->i = i; + text->j = j; +} + +static void free_rc4(context_t *text) +{ + /* free rc4 context structures */ + + if (text->cipher_enc_context) { + text->utils->free(text->cipher_enc_context); + text->cipher_enc_context = NULL; + } + if (text->cipher_dec_context) { + text->utils->free(text->cipher_dec_context); + text->cipher_dec_context = NULL; + } +} + +static int init_rc4(context_t *text, + unsigned char enckey[16], + unsigned char deckey[16]) +{ + /* allocate rc4 context structures */ + text->cipher_enc_context= + (cipher_context_t *) text->utils->malloc(sizeof(rc4_context_t)); + if (text->cipher_enc_context == NULL) return SASL_NOMEM; + + text->cipher_dec_context= + (cipher_context_t *) text->utils->malloc(sizeof(rc4_context_t)); + if (text->cipher_dec_context == NULL) return SASL_NOMEM; + + /* initialize them */ + rc4_init((rc4_context_t *) text->cipher_enc_context, + (const unsigned char *) enckey, 16); + rc4_init((rc4_context_t *) text->cipher_dec_context, + (const unsigned char *) deckey, 16); + + return SASL_OK; +} + +static int dec_rc4(context_t *text, + const char *input, + unsigned inputlen, + unsigned char digest[16] __attribute__((unused)), + char *output, + unsigned *outputlen) +{ + /* decrypt the text part & HMAC */ + rc4_decrypt((rc4_context_t *) text->cipher_dec_context, + input, output, inputlen); + + /* no padding so we just subtract the HMAC to get the text length */ + *outputlen = inputlen - 10; + + return SASL_OK; +} + +static int enc_rc4(context_t *text, + const char *input, + unsigned inputlen, + unsigned char digest[16], + char *output, + unsigned *outputlen) +{ + /* pad is zero */ + *outputlen = inputlen+10; + + /* encrypt the text part */ + rc4_encrypt((rc4_context_t *) text->cipher_enc_context, + input, + output, + inputlen); + + /* encrypt the HMAC part */ + rc4_encrypt((rc4_context_t *) text->cipher_enc_context, + (const char *) digest, + (output)+inputlen, 10); + + return SASL_OK; +} +#endif /* HAVE_OPENSSL */ +#endif /* WITH_RC4 */ + +struct digest_cipher available_ciphers[] = +{ +#ifdef WITH_RC4 + { "rc4-40", 40, 5, 0x01, &enc_rc4, &dec_rc4, &init_rc4, &free_rc4 }, + { "rc4-56", 56, 7, 0x02, &enc_rc4, &dec_rc4, &init_rc4, &free_rc4 }, + { "rc4", 128, 16, 0x04, &enc_rc4, &dec_rc4, &init_rc4, &free_rc4 }, +#endif +#ifdef WITH_DES + { "des", 55, 16, 0x08, &enc_des, &dec_des, &init_des, &free_des }, + { "3des", 112, 16, 0x10, &enc_3des, &dec_3des, &init_3des, &free_des }, +#endif + { NULL, 0, 0, 0, NULL, NULL, NULL, NULL } +}; + +static int create_layer_keys(context_t *text, + const sasl_utils_t *utils, + HASH key, int keylen, + unsigned char enckey[16], + unsigned char deckey[16]) +{ + MD5_CTX Md5Ctx; + + utils->log(utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 create_layer_keys()"); + + utils->MD5Init(&Md5Ctx); + utils->MD5Update(&Md5Ctx, key, keylen); + if (text->i_am == SERVER) { + utils->MD5Update(&Md5Ctx, (const unsigned char *) SEALING_SERVER_CLIENT, + (unsigned) strlen(SEALING_SERVER_CLIENT)); + } else { + utils->MD5Update(&Md5Ctx, (const unsigned char *) SEALING_CLIENT_SERVER, + (unsigned) strlen(SEALING_CLIENT_SERVER)); + } + utils->MD5Final(enckey, &Md5Ctx); + + utils->MD5Init(&Md5Ctx); + utils->MD5Update(&Md5Ctx, key, keylen); + if (text->i_am != SERVER) { + utils->MD5Update(&Md5Ctx, (const unsigned char *) SEALING_SERVER_CLIENT, + (unsigned) strlen(SEALING_SERVER_CLIENT)); + } else { + utils->MD5Update(&Md5Ctx, (const unsigned char *) SEALING_CLIENT_SERVER, + (unsigned) strlen(SEALING_CLIENT_SERVER)); + } + utils->MD5Final(deckey, &Md5Ctx); + + /* create integrity keys */ + /* sending */ + utils->MD5Init(&Md5Ctx); + utils->MD5Update(&Md5Ctx, text->HA1, HASHLEN); + if (text->i_am == SERVER) { + utils->MD5Update(&Md5Ctx, (const unsigned char *)SIGNING_SERVER_CLIENT, + (unsigned) strlen(SIGNING_SERVER_CLIENT)); + } else { + utils->MD5Update(&Md5Ctx, (const unsigned char *)SIGNING_CLIENT_SERVER, + (unsigned) strlen(SIGNING_CLIENT_SERVER)); + } + utils->MD5Final(text->Ki_send, &Md5Ctx); + + /* receiving */ + utils->MD5Init(&Md5Ctx); + utils->MD5Update(&Md5Ctx, text->HA1, HASHLEN); + if (text->i_am != SERVER) { + utils->MD5Update(&Md5Ctx, (const unsigned char *)SIGNING_SERVER_CLIENT, + (unsigned) strlen(SIGNING_SERVER_CLIENT)); + } else { + utils->MD5Update(&Md5Ctx, (const unsigned char *)SIGNING_CLIENT_SERVER, + (unsigned) strlen(SIGNING_CLIENT_SERVER)); + } + utils->MD5Final(text->Ki_receive, &Md5Ctx); + + return SASL_OK; +} + +static const unsigned short version = 1; + +/* + * privacy: + * len, CIPHER(Kc, {msg, pag, HMAC(ki, {SeqNum, msg})[0..9]}), x0001, SeqNum + * + * integrity: + * len, HMAC(ki, {SeqNum, msg})[0..9], x0001, SeqNum + */ +static int digestmd5_encode(void *context, + const struct iovec *invec, + unsigned numiov, + const char **output, + unsigned *outputlen) +{ + context_t *text = (context_t *) context; + int tmp; + unsigned int tmpnum; + unsigned short int tmpshort; + int ret; + char *out; + struct buffer_info *inblob, bufinfo; + + if(!context || !invec || !numiov || !output || !outputlen) { + if (text) PARAMERROR(text->utils); + return SASL_BADPARAM; + } + + if (numiov > 1) { + ret = _plug_iovec_to_buf(text->utils, invec, numiov, &text->enc_in_buf); + if (ret != SASL_OK) return ret; + inblob = text->enc_in_buf; + } else { + /* avoid the data copy */ + bufinfo.data = invec[0].iov_base; + bufinfo.curlen = invec[0].iov_len; + inblob = &bufinfo; + } + + /* make sure the output buffer is big enough for this blob */ + ret = _plug_buf_alloc(text->utils, &(text->encode_buf), + &(text->encode_buf_len), + (4 + /* for length */ + inblob->curlen + /* for content */ + 10 + /* for MAC */ + 8 + /* maximum pad */ + 6)); /* for ver and seqnum */ + if(ret != SASL_OK) return ret; + + /* skip by the length for now */ + out = (text->encode_buf)+4; + + /* construct (seqnum, msg) + * + * Use the output buffer so that the message text is already in place + * for an integrity-only layer. + */ + tmpnum = htonl(text->seqnum); + memcpy(text->encode_buf, &tmpnum, 4); + memcpy(text->encode_buf + 4, inblob->data, inblob->curlen); + + if (text->cipher_enc) { + unsigned char digest[16]; + + /* HMAC(ki, (seqnum, msg) ) */ + text->utils->hmac_md5((const unsigned char *) text->encode_buf, + inblob->curlen + 4, + text->Ki_send, HASHLEN, digest); + + /* calculate the encrypted part */ + text->cipher_enc(text, inblob->data, inblob->curlen, + digest, out, outputlen); + out+=(*outputlen); + } + else { + /* HMAC(ki, (seqnum, msg) ) -- put directly into output buffer */ + text->utils->hmac_md5((const unsigned char *) text->encode_buf, + inblob->curlen + 4, + text->Ki_send, HASHLEN, + (unsigned char *) text->encode_buf + + inblob->curlen + 4); + + *outputlen = inblob->curlen + 10; /* for message + CMAC */ + out+=inblob->curlen + 10; + } + + /* copy in version */ + tmpshort = htons(version); + memcpy(out, &tmpshort, 2); /* 2 bytes = version */ + + out+=2; + (*outputlen)+=2; /* for version */ + + /* put in seqnum */ + tmpnum = htonl(text->seqnum); + memcpy(out, &tmpnum, 4); /* 4 bytes = seq # */ + + (*outputlen)+=4; /* for seqnum */ + + /* put the 1st 4 bytes in */ + tmp=htonl(*outputlen); + memcpy(text->encode_buf, &tmp, 4); + + (*outputlen)+=4; + + *output = text->encode_buf; + text->seqnum++; + + return SASL_OK; +} + +static int digestmd5_decode_packet(void *context, + const char *input, + unsigned inputlen, + char **output, + unsigned *outputlen) +{ + context_t *text = (context_t *) context; + int result; + unsigned char *digest; + int tmpnum; + int lup; + unsigned short ver; + unsigned int seqnum; + unsigned char checkdigest[16]; + + if (inputlen < 16) { + text->utils->seterror(text->utils->conn, 0, "DIGEST-MD5 SASL packets must be at least 16 bytes long"); + return SASL_FAIL; + } + + /* check the version number */ + memcpy(&ver, input+inputlen-6, 2); + ver = ntohs(ver); + if (ver != version) { + text->utils->seterror(text->utils->conn, 0, "Wrong Version"); + return SASL_FAIL; + } + + /* check the sequence number */ + memcpy(&seqnum, input+inputlen-4, 4); + seqnum = ntohl(seqnum); + + if (seqnum != text->rec_seqnum) { + text->utils->seterror(text->utils->conn, 0, + "Incorrect Sequence Number: received %u, expected %u", + seqnum, + text->rec_seqnum); + return SASL_FAIL; + } + + /* allocate a buffer large enough for the output */ + result = _plug_buf_alloc(text->utils, &text->decode_packet_buf, + &text->decode_packet_buf_len, + inputlen /* length of message */ + - 6 /* skip ver and seqnum */ + + 4); /* prepend seqnum */ + if (result != SASL_OK) return result; + + /* construct (seqnum, msg) */ + tmpnum = htonl(text->rec_seqnum); + memcpy(text->decode_packet_buf, &tmpnum, 4); + + text->rec_seqnum++; /* now increment it */ + + *output = text->decode_packet_buf + 4; /* skip seqnum */ + + if (text->cipher_dec) { + /* decrypt message & HMAC into output buffer */ + result = text->cipher_dec(text, input, inputlen-6, NULL, + *output, outputlen); + if (result != SASL_OK) return result; + } + else { + /* copy message & HMAC into output buffer */ + memcpy(*output, input, inputlen - 6); + *outputlen = inputlen - 16; /* -16 to skip HMAC, ver and seqnum */ + } + digest = (unsigned char *) *output + (inputlen - 16); + + /* check the CMAC */ + + /* HMAC(ki, (seqnum, msg) ) */ + text->utils->hmac_md5((const unsigned char *) text->decode_packet_buf, + (*outputlen) + 4, + text->Ki_receive, HASHLEN, checkdigest); + + /* now check it */ + for (lup = 0; lup < 10; lup++) + if (checkdigest[lup] != digest[lup]) { + text->utils->seterror(text->utils->conn, 0, + "CMAC doesn't match at byte %d!", lup); + return SASL_FAIL; + } + + return SASL_OK; +} + +static int digestmd5_decode(void *context, + const char *input, unsigned inputlen, + const char **output, unsigned *outputlen) +{ + context_t *text = (context_t *) context; + int ret; + + ret = _plug_decode(&text->decode_context, input, inputlen, + &text->decode_buf, &text->decode_buf_len, outputlen, + digestmd5_decode_packet, text); + + *output = text->decode_buf; + + return ret; +} + +static void digestmd5_common_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + context_t *text = (context_t *) conn_context; + int lup; + + if (!text || !utils) return; + + utils->log(utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 common mech dispose"); + + if (text->authid) utils->free(text->authid); + if (text->realm) utils->free(text->realm); + + if (text->realms) { + /* need to free all the realms */ + for (lup = 0; lup < text->realm_cnt; lup++) + utils->free (text->realms[lup]); + + utils->free(text->realms); + } + + if (text->nonce) utils->free(text->nonce); + if (text->cnonce) utils->free(text->cnonce); + + if (text->cipher_free) text->cipher_free(text); + + /* free the stuff in the context */ + if (text->response_value) utils->free(text->response_value); + + _plug_decode_free(&text->decode_context); + if (text->encode_buf) utils->free(text->encode_buf); + if (text->decode_buf) utils->free(text->decode_buf); + if (text->decode_packet_buf) utils->free(text->decode_packet_buf); + if (text->out_buf) utils->free(text->out_buf); + + if (text->enc_in_buf) { + if (text->enc_in_buf->data) utils->free(text->enc_in_buf->data); + utils->free(text->enc_in_buf); + } + + utils->free(conn_context); +} + +static void clear_reauth_entry(reauth_entry_t *reauth, enum Context_type type, + const sasl_utils_t *utils) +{ + if (!reauth) return; + + if (reauth->authid) utils->free(reauth->authid); + if (reauth->realm) utils->free(reauth->realm); + if (reauth->nonce) utils->free(reauth->nonce); + if (reauth->cnonce) utils->free(reauth->cnonce); + + if (type == CLIENT) { + if (reauth->u.c.serverFQDN) utils->free(reauth->u.c.serverFQDN); + } + + memset(reauth, 0, sizeof(reauth_entry_t)); +} + +static void digestmd5_common_mech_free(void *glob_context, + const sasl_utils_t *utils) +{ + digest_glob_context_t *my_glob_context = + (digest_glob_context_t *) glob_context; + reauth_cache_t *reauth_cache = my_glob_context->reauth; + size_t n; + + utils->log(utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 common mech free"); + + /* Prevent anybody else from freeing this as well */ + my_glob_context->reauth = NULL; + + if (!reauth_cache) return; + + for (n = 0; n < reauth_cache->size; n++) { + clear_reauth_entry(&reauth_cache->e[n], reauth_cache->i_am, utils); + } + if (reauth_cache->e) utils->free(reauth_cache->e); + + if (reauth_cache->mutex) { + utils->mutex_free(reauth_cache->mutex); + reauth_cache->mutex = NULL; + } + + utils->free(reauth_cache); +} + +/***************************** Server Section *****************************/ + +typedef struct server_context { + context_t common; + + time_t timestamp; + int stale; /* last nonce is stale */ + sasl_ssf_t limitssf, requiressf; /* application defined bounds */ +} server_context_t; + +static digest_glob_context_t server_glob_context; + +static void DigestCalcHA1FromSecret(context_t * text, + const sasl_utils_t * utils, + HASH HA1, + unsigned char *authorization_id, + unsigned char *pszNonce, + unsigned char *pszCNonce, + HASHHEX SessionKey) +{ + MD5_CTX Md5Ctx; + + /* calculate session key */ + utils->MD5Init(&Md5Ctx); + if (text->http_mode) { + /* per RFC 2617 Errata ID 1649 */ + HASHHEX HA1Hex; + + CvtHex(HA1, HA1Hex); + utils->MD5Update(&Md5Ctx, HA1Hex, HASHHEXLEN); + } + else { + /* per RFC 2831 */ + utils->MD5Update(&Md5Ctx, HA1, HASHLEN); + } + utils->MD5Update(&Md5Ctx, COLON, 1); + utils->MD5Update(&Md5Ctx, pszNonce, (unsigned) strlen((char *) pszNonce)); + utils->MD5Update(&Md5Ctx, COLON, 1); + utils->MD5Update(&Md5Ctx, pszCNonce, (unsigned) strlen((char *) pszCNonce)); + if (authorization_id != NULL) { + utils->MD5Update(&Md5Ctx, COLON, 1); + utils->MD5Update(&Md5Ctx, authorization_id, + (unsigned) strlen((char *) authorization_id)); + } + utils->MD5Final(HA1, &Md5Ctx); + + CvtHex(HA1, SessionKey); + + + /* save HA1 because we need it to make the privacy and integrity keys */ + memcpy(text->HA1, HA1, sizeof(HASH)); +} + +static char *create_response(context_t * text, + const sasl_utils_t * utils, + unsigned char *nonce, + unsigned int ncvalue, + unsigned char *cnonce, + char *qop, + const sasl_http_request_t *request, + HASH Secret, + char *authorization_id, + char **response_value) +{ + HASHHEX SessionKey; + HASH EntityHash; + HASHHEX HEntity; + HASHHEX Response; + char *result; + + if (qop == NULL) qop = "auth"; + + DigestCalcHA1FromSecret(text, + utils, + Secret, + (unsigned char *) authorization_id, + nonce, + cnonce, + SessionKey); + + if (text->http_mode) { + /* per RFC 2617 */ + MD5_CTX Md5Ctx; + + utils->MD5Init(&Md5Ctx); + utils->MD5Update(&Md5Ctx, request->entity, request->elen); + utils->MD5Final(EntityHash, &Md5Ctx); + } + else { + /* per RFC 2831 */ + memset(EntityHash, 0, HASHLEN); + } + CvtHex(EntityHash, HEntity); + + /* Calculate response for comparison with client's response */ + DigestCalcResponse(utils, + SessionKey,/* HEX(H(A1)) */ + nonce, /* nonce from server */ + ncvalue, /* 8 hex digits */ + cnonce, /* client nonce */ + (unsigned char *) qop, /* qop-value: "", "auth", + * "auth-int" */ + (unsigned char *) request->uri, /* requested URL */ + (unsigned char *) request->method, + HEntity, /* H(entity body) if qop="auth-int" */ + Response /* request-digest or response-digest */ + ); + + result = utils->malloc(HASHHEXLEN + 1); + memcpy(result, Response, HASHHEXLEN); + result[HASHHEXLEN] = 0; + + /* Calculate response value for mutual auth with the client (NO Method) */ + if (response_value != NULL) { + char * new_response_value; + + DigestCalcResponse(utils, + SessionKey, /* HEX(H(A1)) */ + nonce, /* nonce from server */ + ncvalue, /* 8 hex digits */ + cnonce, /* client nonce */ + (unsigned char *) qop, /* qop-value: "", "auth", + * "auth-int" */ + (unsigned char *) request->uri, /* requested URL */ + NULL, + HEntity, /* H(entity body) if qop="auth-int" */ + Response /* request-digest or response-digest */ + ); + + new_response_value = utils->realloc(*response_value, HASHHEXLEN + 1); + if (new_response_value == NULL) { + free (*response_value); + *response_value = NULL; + return NULL; + } + *response_value = new_response_value; + + memcpy(*response_value, Response, HASHHEXLEN); + (*response_value)[HASHHEXLEN] = 0; + } + return result; +} + +static int get_server_realm(sasl_server_params_t * params, char **realm) +{ + /* look at user realm first */ + if (params->user_realm != NULL) { + if(params->user_realm[0] != '\0') { + *realm = (char *) params->user_realm; + } else { + /* Catch improperly converted apps */ + params->utils->seterror(params->utils->conn, 0, + "user_realm is an empty string!"); + return SASL_BADPARAM; + } + } else if (params->serverFQDN != NULL) { + *realm = (char *) params->serverFQDN; + } else { + params->utils->seterror(params->utils->conn, 0, + "no way to obtain DIGEST-MD5 realm"); + return SASL_FAIL; + } + + return SASL_OK; +} + +/* + * Convert hex string to int + */ +static int htoi(unsigned char *hexin, unsigned int *res) +{ + size_t lup, inlen; + inlen = strlen((char *) hexin); + + *res = 0; + for (lup = 0; lup < inlen; lup++) { + switch (hexin[lup]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + *res = (*res << 4) + (hexin[lup] - '0'); + break; + + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + *res = (*res << 4) + (hexin[lup] - 'a' + 10); + break; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + *res = (*res << 4) + (hexin[lup] - 'A' + 10); + break; + + default: + return SASL_BADPARAM; + } + + } + + return SASL_OK; +} + +static int digestmd5_server_mech_new(void *glob_context, + sasl_server_params_t * sparams, + const char *challenge __attribute__((unused)), + unsigned challen __attribute__((unused)), + void **conn_context) +{ + context_t *text; + + /* holds state are in -- allocate server size */ + text = sparams->utils->malloc(sizeof(server_context_t)); + if (text == NULL) + return SASL_NOMEM; + memset((server_context_t *)text, 0, sizeof(server_context_t)); + + text->state = 1; + text->i_am = SERVER; + text->http_mode = (sparams->flags & SASL_NEED_HTTP); + text->reauth = ((digest_glob_context_t *) glob_context)->reauth; + + *conn_context = text; + return SASL_OK; +} + +static int +digestmd5_server_mech_step1(server_context_t *stext, + sasl_server_params_t *sparams, + const char *clientin __attribute__((unused)), + unsigned clientinlen __attribute__((unused)), + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t * oparams __attribute__((unused))) +{ + context_t *text = (context_t *) stext; + int result; + char *realm; + unsigned char *nonce; + char *charset = "utf-8"; + char qop[1024], cipheropts[1024]; + struct digest_cipher *cipher; + unsigned resplen; + int added_conf = 0; + char maxbufstr[64]; + + sparams->utils->log(sparams->utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 server step 1"); + + /* get realm */ + result = get_server_realm(sparams, &realm); + if(result != SASL_OK) return result; + + /* what options should we offer the client? */ + qop[0] = '\0'; + cipheropts[0] = '\0'; + if (stext->requiressf == 0) { + if (*qop) strcat(qop, ","); + strcat(qop, "auth"); + } + if (stext->requiressf <= 1 && stext->limitssf >= 1) { + if (*qop) strcat(qop, ","); + strcat(qop, "auth-int"); + } + + cipher = available_ciphers; + while (cipher->name) { + /* do we allow this particular cipher? */ + if (stext->requiressf <= cipher->ssf && + stext->limitssf >= cipher->ssf) { + if (!added_conf) { + if (*qop) strcat(qop, ","); + strcat(qop, "auth-conf"); + added_conf = 1; + } + if (strlen(cipheropts) + strlen(cipher->name) + 1 >= 1024) + return SASL_FAIL; + if (*cipheropts) strcat(cipheropts, ","); + strcat(cipheropts, cipher->name); + } + cipher++; + } + + if (*qop == '\0') { + /* we didn't allow anything?!? we'll return SASL_TOOWEAK, since + that's close enough */ + return SASL_TOOWEAK; + } + + /* + * digest-challenge = 1#( realm | nonce | qop-options | stale | maxbuf | + * charset | cipher-opts | auth-param ) + */ + + nonce = create_nonce(sparams->utils); + if (nonce == NULL) { + SETERROR(sparams->utils, "internal erorr: failed creating a nonce"); + return SASL_FAIL; + } + + resplen = 0; + text->out_buf = NULL; + text->out_buf_len = 0; + if (add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "nonce", (unsigned char *) nonce, + TRUE) != SASL_OK) { + SETERROR(sparams->utils, "internal error: add_to_challenge failed"); + return SASL_FAIL; + } + + /* add to challenge; if we chose not to specify a realm, we won't + * send one to the client */ + if (realm && add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "realm", (unsigned char *) realm, + TRUE) != SASL_OK) { + SETERROR(sparams->utils, "internal error: add_to_challenge failed"); + return SASL_FAIL; + } + /* + * qop-options A quoted string of one or more tokens indicating the + * "quality of protection" values supported by the server. The value + * "auth" indicates authentication; the value "auth-int" indicates + * authentication with integrity protection; the value "auth-conf" + * indicates authentication with integrity protection and encryption. + */ + + /* add qop to challenge */ + if (add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "qop", + (unsigned char *) qop, TRUE) != SASL_OK) { + SETERROR(sparams->utils, "internal error: add_to_challenge 3 failed"); + return SASL_FAIL; + } + + /* + * Cipheropts - list of ciphers server supports + */ + /* add cipher-opts to challenge; only add if there are some */ + if (strcmp(cipheropts,"")!=0) + { + if (add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "cipher", (unsigned char *) cipheropts, + TRUE) != SASL_OK) { + SETERROR(sparams->utils, + "internal error: add_to_challenge 4 failed"); + return SASL_FAIL; + } + } + + /* "stale" is true if a reauth failed because of a nonce timeout */ + if (stext->stale && + add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "stale", (unsigned char *) "true", FALSE) != SASL_OK) { + SETERROR(sparams->utils, "internal error: add_to_challenge failed"); + return SASL_FAIL; + } + + /* + * maxbuf A number indicating the size of the largest buffer the server + * is able to receive when using "auth-int". If this directive is + * missing, the default value is 65536. This directive may appear at most + * once; if multiple instances are present, the client should abort the + * authentication exchange. + */ + if(sparams->props.maxbufsize) { + snprintf(maxbufstr, sizeof(maxbufstr), "%u", + sparams->props.maxbufsize); + if (add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "maxbuf", + (unsigned char *) maxbufstr, FALSE) != SASL_OK) { + SETERROR(sparams->utils, + "internal error: add_to_challenge 5 failed"); + return SASL_FAIL; + } + } + + if (add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "charset", + (unsigned char *) charset, FALSE) != SASL_OK) { + SETERROR(sparams->utils, "internal error: add_to_challenge 6 failed"); + return SASL_FAIL; + } + + + /* + * algorithm + * This directive is required for backwards compatibility with HTTP + * Digest, which supports other algorithms. This directive is + * required and MUST appear exactly once; if not present, or if multiple + * instances are present, the client should abort the authentication + * exchange. + * + * algorithm = "algorithm" "=" "md5-sess" + */ + + if (add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "algorithm", + (unsigned char *) "md5-sess", FALSE)!=SASL_OK) { + SETERROR(sparams->utils, "internal error: add_to_challenge 7 failed"); + return SASL_FAIL; + } + + /* + * The size of a digest-challenge MUST be less than 2048 bytes!!! + */ + if (*serveroutlen > 2048) { + SETERROR(sparams->utils, + "internal error: challenge larger than 2048 bytes"); + return SASL_FAIL; + } + + text->authid = NULL; + if (_plug_strdup(sparams->utils, realm, &text->realm, NULL) != SASL_OK) { + SETERROR(sparams->utils, + "internal error: out of memory when saving realm"); + return SASL_FAIL; + } + + if (text->http_mode && text->reauth->timeout && + sparams->utils->mutex_lock(text->reauth->mutex) == SASL_OK) { /* LOCK */ + + /* Create an initial cache entry for non-persistent HTTP connections */ + unsigned val = hash((char *) nonce) % text->reauth->size; + + clear_reauth_entry(&text->reauth->e[val], SERVER, sparams->utils); + text->reauth->e[val].authid = NULL; + text->reauth->e[val].realm = text->realm; text->realm = NULL; + text->reauth->e[val].nonce = nonce; + text->reauth->e[val].nonce_count = 1; + text->reauth->e[val].cnonce = NULL; + text->reauth->e[val].u.s.timestamp = time(0); + + sparams->utils->mutex_unlock(text->reauth->mutex); /* UNLOCK */ + } + else { + text->nonce = nonce; + text->nonce_count = 1; + text->cnonce = NULL; + stext->timestamp = time(0); + } + + *serveroutlen = (unsigned) strlen(text->out_buf); + *serverout = text->out_buf; + + text->state = 2; + + return SASL_CONTINUE; +} + +static int digestmd5_server_mech_step2(server_context_t *stext, + sasl_server_params_t *sparams, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t * oparams) +{ + context_t *text = (context_t *) stext; + /* verify digest */ + sasl_secret_t *sec = NULL; + int result; + char *serverresponse = NULL; + char *username = NULL; + char *authorization_id = NULL; + char *realm = NULL; + unsigned char *nonce = NULL, *cnonce = NULL; + unsigned int noncecount = 0; + char *qop = NULL; + char *digesturi = NULL; + sasl_http_request_t rfc2831_request; + const sasl_http_request_t *request; + char *response = NULL; + + /* setting the default value (65536) */ + unsigned long client_maxbuf = 65536; + int maxbuf_count = 0; /* How many maxbuf instances was found */ + + char *charset = NULL; + char *cipher = NULL; + unsigned int n = 0; + + HASH Secret; + HASH SecretBogus; + bool Try_8859_1 = FALSE; + int client_ignores_realm = 0; + char *full_username = NULL; + char *internal_username = NULL; + int canon_flags; + + /* password prop_request */ + const char *password_request[] = { SASL_AUX_PASSWORD, +#if defined(OBSOLETE_DIGEST_ATTR) + "*cmusaslsecretDIGEST-MD5", +#endif + NULL }; + size_t len; + struct propval auxprop_values[2]; + + /* can we mess with clientin? copy it to be safe */ + char *in_start = NULL; + char *in = NULL; + cipher_free_t *old_cipher_free = NULL; + + sparams->utils->log(sparams->utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 server step 2"); + + if (clientinlen == 0) { + SETERROR(sparams->utils, "input expected in DIGEST-MD5, step 2"); + result = SASL_BADAUTH; + goto FreeAllMem; + } + + if (text->http_mode) { + /* per RFC 2617 (HTTP Request as set by calling application) */ + request = sparams->http_request; + if (!request) { + SETERROR(sparams->utils, + "missing HTTP request in DIGEST-MD5, step 2"); + result = SASL_BADPARAM; + goto FreeAllMem; + } + } + else { + /* per RFC 2831 */ + rfc2831_request.method = "AUTHENTICATE"; + rfc2831_request.uri = NULL; /* to be filled in below from response */ + rfc2831_request.entity = NULL; + rfc2831_request.elen = 0; + rfc2831_request.non_persist = 0; + request = &rfc2831_request; + } + + in = sparams->utils->malloc(clientinlen + 1); + + memcpy(in, clientin, clientinlen); + in[clientinlen] = 0; + + in_start = in; + + + /* parse what we got */ + while (in[0] != '\0') { + char *name = NULL, *value = NULL; + get_pair(&in, &name, &value); + + if (name == NULL) { + SETERROR(sparams->utils, + "Parse error"); + result = SASL_BADAUTH; + goto FreeAllMem; + } + + if (*name == '\0') { + break; + } + + /* Extracting parameters */ + + /* + * digest-response = 1#( username | realm | nonce | cnonce | + * nonce-count | qop | digest-uri | response | maxbuf | charset | + * cipher | auth-param ) + */ + + if (strcasecmp(name, "username") == 0) { + _plug_strdup(sparams->utils, value, &username, NULL); + } else if (strcasecmp(name, "authzid") == 0) { + _plug_strdup(sparams->utils, value, &authorization_id, NULL); + } else if (strcasecmp(name, "cnonce") == 0) { + _plug_strdup(sparams->utils, value, (char **) &cnonce, NULL); + } else if (strcasecmp(name, "nc") == 0) { + if (htoi((unsigned char *) value, &noncecount) != SASL_OK) { + SETERROR(sparams->utils, + "error converting hex to int"); + result = SASL_BADAUTH; + goto FreeAllMem; + } + } else if (strcasecmp(name, "realm") == 0) { + if (realm) { + SETERROR(sparams->utils, + "duplicate realm: authentication aborted"); + result = SASL_FAIL; + goto FreeAllMem; + } + _plug_strdup(sparams->utils, value, &realm, NULL); + } else if (strcasecmp(name, "nonce") == 0) { + _plug_strdup(sparams->utils, value, (char **) &nonce, NULL); + } else if (strcasecmp(name, "qop") == 0) { + if (qop) { + SETERROR(sparams->utils, + "duplicate qop: authentication aborted"); + result = SASL_FAIL; + goto FreeAllMem; + } + _plug_strdup(sparams->utils, value, &qop, NULL); + } else if (strcasecmp(name, "digest-uri") == 0 || /* per RFC 2831 */ + (text->http_mode && + strcasecmp(name, "uri") == 0)) { /* per RFC 2617 */ + size_t service_len; + + if (digesturi) { + SETERROR(sparams->utils, + "duplicate digest-uri: authentication aborted"); + result = SASL_FAIL; + goto FreeAllMem; + } + + _plug_strdup(sparams->utils, value, &digesturi, NULL); + + if (text->http_mode && request && request->uri) { + /* Verify digest-uri matches HTTP request (per RFC 2617) */ + if (strcmp(digesturi, request->uri)) { + result = SASL_BADAUTH; + SETERROR(sparams->utils, + "bad digest-uri: doesn't match HTTP request"); + goto FreeAllMem; + } + } + else { + /* Verify digest-uri format (per RFC 2831): + * + * digest-uri-value = serv-type "/" host [ "/" serv-name ] + */ + + /* make sure it's the service that we're expecting */ + service_len = strlen(sparams->service); + if (strncasecmp(digesturi, sparams->service, service_len) || + digesturi[service_len] != '/') { + result = SASL_BADAUTH; + SETERROR(sparams->utils, + "bad digest-uri: doesn't match service"); + goto FreeAllMem; + } + + /* xxx we don't verify the hostname component */ + + rfc2831_request.uri = digesturi; + } + + } else if (strcasecmp(name, "response") == 0) { + _plug_strdup(sparams->utils, value, &response, NULL); + } else if (strcasecmp(name, "cipher") == 0) { + _plug_strdup(sparams->utils, value, &cipher, NULL); + } else if (strcasecmp(name, "maxbuf") == 0) { + maxbuf_count++; + if (maxbuf_count != 1) { + result = SASL_BADAUTH; + SETERROR(sparams->utils, + "duplicate maxbuf: authentication aborted"); + goto FreeAllMem; + } else if (str2ul32 (value, &client_maxbuf) == FALSE) { + result = SASL_BADAUTH; + SETERROR(sparams->utils, "invalid maxbuf parameter"); + goto FreeAllMem; + } else { + if (client_maxbuf <= 16) { + result = SASL_BADAUTH; + SETERROR(sparams->utils, + "maxbuf parameter too small"); + goto FreeAllMem; + } + + if (client_maxbuf > MAX_SASL_BUFSIZE) { + result = SASL_BADAUTH; + SETERROR(sparams->utils, + "maxbuf parameter too big"); + goto FreeAllMem; + } + } + } else if (strcasecmp(name, "charset") == 0) { + if (strcasecmp(value, "utf-8") != 0) { + SETERROR(sparams->utils, "client doesn't support UTF-8"); + result = SASL_FAIL; + goto FreeAllMem; + } + _plug_strdup(sparams->utils, value, &charset, NULL); + } else if (strcasecmp(name,"algorithm") == 0) { + /* per RFC 2831: algorithm MUST be ignored if received */ + if (text->http_mode && strcasecmp(value, "md5-sess") != 0) { + /* per RFC 2617: algorithm MUST match that sent in challenge */ + SETERROR(sparams->utils, "'algorithm' isn't 'md5-sess'"); + result = SASL_FAIL; + goto FreeAllMem; + } + } else { + sparams->utils->log(sparams->utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 unrecognized pair %s/%s: ignoring", + name, value); + } + } + + /* + * username = "username" "=" <"> username-value <"> + * username-value = qdstr-val + * cnonce = "cnonce" "=" <"> cnonce-value <"> + * cnonce-value = qdstr-val + * nonce-count = "nc" "=" nc-value + * nc-value = 8LHEX + * qop = "qop" "=" qop-value + * digest-uri = "digest-uri" "=" digest-uri-value + * digest-uri-value = serv-type "/" host [ "/" serv-name ] + * serv-type = 1*ALPHA + * host = 1*( ALPHA | DIGIT | "-" | "." ) + * service = host + * response = "response" "=" <"> response-value <"> + * response-value = 32LHEX + * LHEX = "0" | "1" | "2" | "3" | "4" | "5" | + * "6" | "7" | "8" | "9" | "a" | "b" | "c" | "d" | "e" | "f" + * cipher = "cipher" "=" cipher-value + */ + /* Verifing that all required parameters were received */ + if ((username == NULL)) { + SETERROR(sparams->utils, "required parameters missing: username"); + result = SASL_BADAUTH; + goto FreeAllMem; + } + if ((nonce == NULL)) { + SETERROR(sparams->utils, "required parameters missing: nonce"); + result = SASL_BADAUTH; + goto FreeAllMem; + } + if ((noncecount == 0)) { + SETERROR(sparams->utils, "required parameters missing: noncecount"); + result = SASL_BADAUTH; + goto FreeAllMem; + } + if ((cnonce == NULL)) { + SETERROR(sparams->utils, "required parameters missing: cnonce"); + result = SASL_BADAUTH; + goto FreeAllMem; + } + if ((digesturi == NULL)) { + SETERROR(sparams->utils, "required parameters missing: digesturi"); + result = SASL_BADAUTH; + goto FreeAllMem; + } + if ((response == NULL)) { + SETERROR(sparams->utils, "required parameters missing: response"); + result = SASL_BADAUTH; + goto FreeAllMem; + } + + if (realm == NULL) { + /* From 2831bis: + If the directive is missing, "realm-value" will set to + the empty string when computing A1. */ + _plug_strdup(sparams->utils, "", &realm, NULL); + sparams->utils->log(sparams->utils->conn, SASL_LOG_DEBUG, + "The client didn't send a realm, assuming empty string."); +#if 0 + if (text->realm[0] != '\0') { + SETERROR(sparams->utils, + "realm changed: authentication aborted"); + result = SASL_BADAUTH; + goto FreeAllMem; + } +#endif + } + + if (!text->nonce && text->reauth->timeout && text->reauth->size > 0) { + unsigned val = hash((char *) nonce) % text->reauth->size; + + /* reauth attempt or continuation of HTTP Digest on a + non-persistent connection, see if we have any info for this nonce */ + if (sparams->utils->mutex_lock(text->reauth->mutex) == SASL_OK) { /* LOCK */ + if (text->reauth->e[val].realm && + !strcmp(realm, text->reauth->e[val].realm) && + ((text->reauth->e[val].nonce_count == 1) || + (text->reauth->e[val].authid && + !strcmp(username, text->reauth->e[val].authid)))) { + + _plug_strdup(sparams->utils, text->reauth->e[val].realm, + &text->realm, NULL); + _plug_strdup(sparams->utils, (char *) text->reauth->e[val].nonce, + (char **) &text->nonce, NULL); + text->nonce_count = text->reauth->e[val].nonce_count; +#if 0 /* XXX Neither RFC 2617 nor RFC 2831 state that the cnonce + needs to remain constant for subsequent authentication to work */ + _plug_strdup(sparams->utils, (char *) text->reauth->e[val].cnonce, + (char **) &text->cnonce, NULL); +#endif + stext->timestamp = text->reauth->e[val].u.s.timestamp; + } + sparams->utils->mutex_unlock(text->reauth->mutex); /* UNLOCK */ + } + + if (!text->nonce) { + /* we don't have any reauth info */ + sparams->utils->log(sparams->utils->conn, SASL_LOG_DEBUG, + "No reauth info for '%s' found", nonce); + + /* we will continue processing the response to determine + if the client knows the password and return stale accordingly */ + } + } + + /* Sanity check the parameters */ + if (text->nonce) { + /* CLAIM: realm is not NULL below */ + if (text->realm == NULL) { + sparams->utils->log(sparams->utils->conn, SASL_LOG_DEBUG, + "The client specifies a realm when the server hasn't provided one. Using client's realm."); + _plug_strdup(sparams->utils, realm, &text->realm, NULL); + } else if ((strcmp(realm, text->realm) != 0) && + /* XXX - Not sure why the check for text->realm not being empty is needed, + as it should always be non-empty */ + (text->realm[0] != 0)) { + + client_ignores_realm = 1; + sparams->utils->log(sparams->utils->conn, SASL_LOG_DEBUG, + "The client tries to override server provided realm"); + if (text->realm) sparams->utils->free(text->realm); + _plug_strdup(sparams->utils, realm, &text->realm, NULL); + } + + if (strcmp((char *) nonce, (char *) text->nonce) != 0) { + SETERROR(sparams->utils, + "nonce changed: authentication aborted"); + result = SASL_BADAUTH; + goto FreeAllMem; + } +#if 0 /* XXX Possible replay attack, but we will continue processing + * the response to determine if the client knows the password and + return stale accordingly */ + if (noncecount != text->nonce_count) { + SETERROR(sparams->utils, + "incorrect nonce-count: authentication aborted"); + result = SASL_BADAUTH; + goto FreeAllMem; + } +#endif +#if 0 /* XXX Neither RFC 2617 nor RFC 2831 state that the cnonce + needs to remain constant for subsequent authentication to work */ + if (text->cnonce && strcmp((char *) cnonce, (char *) text->cnonce) != 0) { + SETERROR(sparams->utils, + "cnonce changed: authentication aborted"); + result = SASL_BADAUTH; + goto FreeAllMem; + } +#endif + } + + result = sparams->utils->prop_request(sparams->propctx, password_request); + if(result != SASL_OK) { + SETERROR(sparams->utils, "unable to obtain user password"); + goto FreeAllMem; + } + + /* this will trigger the getting of the aux properties */ + /* Note that if we don't have an authorization id, we don't use it... */ + + if (client_ignores_realm) { + if (strlen(text->realm) == 0) { + /* Don't put @ at the end of the username, if the realm is empty */ + _plug_strdup(sparams->utils, username, &full_username, NULL); + } else { + full_username = (char *) sparams->utils->malloc(strlen(username) + + strlen(text->realm) + 2); + full_username[0] = '\0'; + sprintf (full_username, "%s@%s", username, text->realm); + } + internal_username = full_username; + } else { + internal_username = username; + } + + canon_flags = SASL_CU_AUTHID; + if (!authorization_id || !*authorization_id) { + canon_flags |= SASL_CU_AUTHZID; + } + + result = sparams->canon_user(sparams->utils->conn, + internal_username, + 0, + canon_flags, + oparams); + if (result != SASL_OK) { + SETERROR(sparams->utils, "unable to canonify user and get auxprops"); + goto FreeAllMem; + } + + if (authorization_id != NULL && *authorization_id != '\0') { + result = sparams->canon_user(sparams->utils->conn, + authorization_id, 0, SASL_CU_AUTHZID, + oparams); + } + if (result != SASL_OK) { + SETERROR(sparams->utils, "unable to canonify authorization ID"); + goto FreeAllMem; + } + + result = sparams->utils->prop_getnames(sparams->propctx, password_request, + auxprop_values); + if (result < 0 || + ((!auxprop_values[0].name || !auxprop_values[0].values) +#if defined(OBSOLETE_DIGEST_ATTR) + && (!auxprop_values[1].name || !auxprop_values[1].values) +#endif + )) { + /* We didn't find this username */ + sparams->utils->seterror(sparams->utils->conn, 0, + "no secret in database"); + result = sparams->transition ? SASL_TRANS : SASL_NOUSER; + goto FreeAllMem; + } + + if (auxprop_values[0].name && auxprop_values[0].values) { + len = strlen(auxprop_values[0].values[0]); + if (len == 0) { + sparams->utils->seterror(sparams->utils->conn,0, + "empty secret"); + result = SASL_FAIL; + goto FreeAllMem; + } + + sec = sparams->utils->malloc(sizeof(sasl_secret_t) + len); + if (!sec) { + SETERROR(sparams->utils, "unable to allocate secret"); + result = SASL_FAIL; + goto FreeAllMem; + } + + sec->len = (unsigned) len; + strncpy((char *) sec->data, auxprop_values[0].values[0], len + 1); + + /* + * Verifying response obtained from client + * + * H_URP = H({ username-value,":",realm-value,":",passwd}) sec->data + * contains H_URP + */ + + /* Calculate the secret from the plaintext password */ + { + /* + * Secret = { H( { username-value, ":", realm-value, ":", passwd } ) } + * + * (used to build A1) + */ + + Try_8859_1 = DigestCalcSecret(sparams->utils, + (unsigned char *) username, + (unsigned char *) realm, + sec->data, + sec->len, + FALSE, + Secret); + Secret[HASHLEN] = '\0'; + } + if (Try_8859_1) { + /* + * Secret = { H( { username-value, ":", realm-value, ":", passwd } ) } + * + * (used to build A1) + */ + + DigestCalcSecret(sparams->utils, + (unsigned char *) username, + (unsigned char *) realm, + sec->data, + sec->len, + TRUE, + SecretBogus); + SecretBogus[HASHLEN] = '\0'; + } + + /* We're done with sec now. Let's get rid of it */ + _plug_free_secret(sparams->utils, &sec); +#if defined(OBSOLETE_DIGEST_ATTR) + } else if (auxprop_values[1].name && auxprop_values[1].values) { + /* NB: This will most likely fail for clients that + choose to ignore server-advertised realm */ + memcpy(Secret, auxprop_values[1].values[0], HASHLEN); + Secret[HASHLEN] = '\0'; +#endif + } else { + sparams->utils->seterror(sparams->utils->conn, 0, + "Have neither type of secret"); + return SASL_FAIL; + } + + /* erase the plaintext password */ + sparams->utils->prop_erase(sparams->propctx, password_request[0]); + + /* defaulting qop to "auth" if not specified */ + if (qop == NULL) { + _plug_strdup(sparams->utils, "auth", &qop, NULL); + } + + if (oparams->mech_ssf > 1) { + /* Remember the old cipher free function (if any). + It will be called later, once we are absolutely + sure that authentication was successful. */ + old_cipher_free = text->cipher_free; + /* free the old cipher context first */ + } + + /* check which layer/cipher to use */ + if ((!strcasecmp(qop, "auth-conf")) && (cipher != NULL)) { + /* see what cipher was requested */ + struct digest_cipher *cptr; + + cptr = available_ciphers; + while (cptr->name) { + /* find the cipher requested & make sure it's one we're happy + with by policy */ + if (!strcasecmp(cipher, cptr->name) && + stext->requiressf <= cptr->ssf && + stext->limitssf >= cptr->ssf) { + /* found it! */ + break; + } + cptr++; + } + + if (cptr->name) { + text->cipher_enc = cptr->cipher_enc; + text->cipher_dec = cptr->cipher_dec; + text->cipher_init = cptr->cipher_init; + text->cipher_free = cptr->cipher_free; + oparams->mech_ssf = cptr->ssf; + n = cptr->n; + } else { + /* erg? client requested something we didn't advertise! */ + sparams->utils->log(sparams->utils->conn, SASL_LOG_WARN, + "protocol violation: client requested invalid cipher"); + SETERROR(sparams->utils, "client requested invalid cipher"); + /* Mark that we attempted security layer negotiation */ + oparams->mech_ssf = 2; + result = SASL_FAIL; + goto FreeAllMem; + } + + oparams->encode=&digestmd5_encode; + oparams->decode=&digestmd5_decode; + } else if (!strcasecmp(qop, "auth-int") && + stext->requiressf <= 1 && stext->limitssf >= 1) { + oparams->encode = &digestmd5_encode; + oparams->decode = &digestmd5_decode; + oparams->mech_ssf = 1; + } else if (!strcasecmp(qop, "auth") && stext->requiressf == 0) { + oparams->encode = NULL; + oparams->decode = NULL; + oparams->mech_ssf = 0; + } else { + SETERROR(sparams->utils, + "protocol violation: client requested invalid qop"); + result = SASL_FAIL; + goto FreeAllMem; + } + + serverresponse = create_response(text, + sparams->utils, + nonce, + noncecount, + cnonce, + qop, + request, + Secret, + authorization_id, + &text->response_value); + + if (serverresponse == NULL) { + SETERROR(sparams->utils, "internal error: unable to create response"); + result = SASL_NOMEM; + goto FreeAllMem; + } + + /* if ok verified */ + if (strcmp(serverresponse, response) != 0) { + if (Try_8859_1) { + sparams->utils->free(serverresponse); + serverresponse = create_response(text, + sparams->utils, + nonce, + noncecount, + cnonce, + qop, + request, + SecretBogus, + authorization_id, + &text->response_value); + + if (serverresponse == NULL) { + SETERROR(sparams->utils, "internal error: unable to create response"); + result = SASL_NOMEM; + goto FreeAllMem; + } + + /* if ok verified */ + if (strcmp(serverresponse, response) != 0) { + SETERROR(sparams->utils, + "client response doesn't match what we generated (tried bogus)"); + result = SASL_BADAUTH; + + goto FreeAllMem; + } + + } else { + SETERROR(sparams->utils, + "client response doesn't match what we generated"); + result = SASL_BADAUTH; + + goto FreeAllMem; + } + } + + /* see if our nonce expired */ + if (!text->nonce || + (noncecount != text->nonce_count) || + (text->reauth->timeout && + time(0) - stext->timestamp > text->reauth->timeout)) { + if (!text->nonce) SETERROR(sparams->utils, "no cached server nonce"); + else if (noncecount != text->nonce_count) + SETERROR(sparams->utils, "incorrect nonce-count"); + else SETERROR(sparams->utils, "server nonce expired"); + stext->stale = 1; + result = SASL_BADAUTH; + + goto FreeAllMem; + } + + /* + * nothing more to do; authenticated set oparams information + */ + oparams->doneflag = 1; + oparams->maxoutbuf = client_maxbuf - 4; + if (oparams->mech_ssf > 1) { + /* MAC block (privacy) */ + oparams->maxoutbuf -= 25; + } else if(oparams->mech_ssf == 1) { + /* MAC block (integrity) */ + oparams->maxoutbuf -= 16; + } + + oparams->param_version = 0; + + text->seqnum = 0; /* for integrity/privacy */ + text->rec_seqnum = 0; /* for integrity/privacy */ + text->utils = sparams->utils; + + /* Free the old security layer, if any */ + if (old_cipher_free) old_cipher_free(text); + + /* used by layers */ + _plug_decode_init(&text->decode_context, text->utils, + sparams->props.maxbufsize ? sparams->props.maxbufsize : + DEFAULT_BUFSIZE); + + if (oparams->mech_ssf > 0) { + unsigned char enckey[16]; + unsigned char deckey[16]; + + create_layer_keys(text, sparams->utils,text->HA1,n,enckey,deckey); + + /* initialize cipher if need be */ + if (text->cipher_init) { + if (text->cipher_init(text, enckey, deckey) != SASL_OK) { + sparams->utils->seterror(sparams->utils->conn, 0, + "couldn't init cipher"); + } + } + } + + /* + * The server receives and validates the "digest-response". The server + * checks that the nonce-count is "00000001". If it supports subsequent + * authentication, it saves the value of the nonce and the nonce-count. + */ + + /* + * The "username-value", "realm-value" and "passwd" are encoded according + * to the value of the "charset" directive. If "charset=UTF-8" is + * present, and all the characters of either "username-value" or "passwd" + * are in the ISO 8859-1 character set, then it must be converted to + * UTF-8 before being hashed. A sample implementation of this conversion + * is in section 8. + */ + + /* add to challenge */ + { + unsigned resplen = 0; + + if (add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "rspauth", (unsigned char *) text->response_value, + text->http_mode ? TRUE : FALSE) != SASL_OK) { + SETERROR(sparams->utils, "internal error: add_to_challenge failed"); + result = SASL_FAIL; + goto FreeAllMem; + } + + if (text->http_mode) { + /* per RFC 2617 */ + char ncvalue[10]; + + if (add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "cnonce", cnonce, TRUE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllMem; + } + snprintf(ncvalue, sizeof(ncvalue), "%08x", text->nonce_count); + if (add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "nc", (unsigned char *) ncvalue, FALSE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllMem; + } + if (add_to_challenge(sparams->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "qop", (unsigned char *) qop, TRUE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllMem; + } + } + + /* self check */ + if (strlen(text->out_buf) > 2048) { + result = SASL_FAIL; + goto FreeAllMem; + } + } + + *serveroutlen = (unsigned) strlen(text->out_buf); + *serverout = text->out_buf; + + result = SASL_OK; + + FreeAllMem: + if (clientinlen > 0 && + text->reauth->timeout && + sparams->utils->mutex_lock(text->reauth->mutex) == SASL_OK) { /* LOCK */ + + /* Look for an entry for the nonce value */ + unsigned val = hash((char *) nonce) % text->reauth->size; + + switch (result) { + case SASL_OK: + /* successful auth, setup for future reauth */ + if (text->nonce_count == 1) { + /* successful initial auth, create new entry */ + clear_reauth_entry(&text->reauth->e[val], SERVER, sparams->utils); + text->reauth->e[val].authid = username; username = NULL; + text->reauth->e[val].realm = text->realm; text->realm = NULL; + text->reauth->e[val].nonce = text->nonce; text->nonce = NULL; + text->reauth->e[val].cnonce = cnonce; cnonce = NULL; + } + if (text->nonce_count < text->reauth->e[val].nonce_count) { + /* paranoia. prevent replay attacks */ + clear_reauth_entry(&text->reauth->e[val], SERVER, sparams->utils); + } + else { + text->reauth->e[val].nonce_count = ++text->nonce_count; + text->reauth->e[val].u.s.timestamp = time(0); + } + break; + default: + if (text->nonce_count > 1) { + /* failed reauth, clear entry */ + clear_reauth_entry(&text->reauth->e[val], SERVER, sparams->utils); + } + else { + /* failed initial auth, leave existing cache */ + } + } + sparams->utils->mutex_unlock(text->reauth->mutex); /* UNLOCK */ + } + + /* free everything */ + if (in_start) sparams->utils->free (in_start); + + if (full_username != NULL) + sparams->utils->free (full_username); + if (username != NULL) + sparams->utils->free (username); + if (authorization_id != NULL) + sparams->utils->free (authorization_id); + if (realm != NULL) + sparams->utils->free (realm); + if (nonce != NULL) + sparams->utils->free (nonce); + if (cnonce != NULL) + sparams->utils->free (cnonce); + if (response != NULL) + sparams->utils->free (response); + if (cipher != NULL) + sparams->utils->free (cipher); + if (serverresponse != NULL) + sparams->utils->free(serverresponse); + if (charset != NULL) + sparams->utils->free (charset); + if (digesturi != NULL) + sparams->utils->free (digesturi); + if (qop!=NULL) + sparams->utils->free (qop); + if (sec) + _plug_free_secret(sparams->utils, &sec); + + return result; +} + +static int digestmd5_server_mech_step(void *conn_context, + sasl_server_params_t *sparams, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + context_t *text = (context_t *) conn_context; + server_context_t *stext = (server_context_t *) conn_context; + + *serverout = NULL; + *serveroutlen = 0; + + if (clientinlen > 4096) return SASL_BADPROT; + + if (text == NULL) { + return SASL_BADPROT; + } + + switch (text->state) { + + case 1: + /* setup SSF limits */ + if (!text->http_mode && /* HTTP Digest doesn't need buffer */ + !sparams->props.maxbufsize) { + stext->limitssf = 0; + stext->requiressf = 0; + } else { + if (sparams->props.max_ssf < sparams->external_ssf) { + stext->limitssf = 0; + } else { + stext->limitssf = + sparams->props.max_ssf - sparams->external_ssf; + } + if (sparams->props.min_ssf < sparams->external_ssf) { + stext->requiressf = 0; + } else { + stext->requiressf = + sparams->props.min_ssf - sparams->external_ssf; + } + } + + if (clientin && text->reauth->timeout) { + /* here's where we attempt fast reauth if possible */ + if (digestmd5_server_mech_step2(stext, sparams, + clientin, clientinlen, + serverout, serveroutlen, + oparams) == SASL_OK) { + return SASL_OK; + } + + sparams->utils->log(NULL, SASL_LOG_WARN, + "DIGEST-MD5 reauth failed\n"); + + /* re-initialize everything for a fresh start */ + memset(oparams, 0, sizeof(sasl_out_params_t)); + if (text->nonce) sparams->utils->free(text->nonce); + if (text->realm) sparams->utils->free(text->realm); + text->realm = NULL; + text->nonce = NULL; + + /* fall through and issue challenge */ + } + + return digestmd5_server_mech_step1(stext, sparams, + clientin, clientinlen, + serverout, serveroutlen, oparams); + + case 2: + return digestmd5_server_mech_step2(stext, sparams, + clientin, clientinlen, + serverout, serveroutlen, oparams); + + default: + sparams->utils->log(NULL, SASL_LOG_ERR, + "Invalid DIGEST-MD5 server step %d\n", text->state); + return SASL_FAIL; + } + + return SASL_FAIL; /* should never get here */ +} + +static void digestmd5_server_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + server_context_t *stext = (server_context_t *) conn_context; + + if (!stext || !utils) return; + + digestmd5_common_mech_dispose(conn_context, utils); +} + +static sasl_server_plug_t digestmd5_server_plugins[] = +{ + { + "DIGEST-MD5", /* mech_name */ +#ifdef WITH_RC4 + 128, /* max_ssf */ +#elif defined(WITH_DES) + 112, +#else + 1, +#endif + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP, /* features */ + &server_glob_context, /* glob_context */ + &digestmd5_server_mech_new, /* mech_new */ + &digestmd5_server_mech_step, /* mech_step */ + &digestmd5_server_mech_dispose, /* mech_dispose */ + &digestmd5_common_mech_free, /* mech_free */ + NULL, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + NULL, /* mech avail */ + NULL /* spare */ + } +}; + +int digestmd5_server_plug_init(sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_server_plug_t **pluglist, + int *plugcount) +{ + reauth_cache_t *reauth_cache; + const char *timeout = NULL; + unsigned int len; + + if (maxversion < SASL_SERVER_PLUG_VERSION) { + return SASL_BADVERS; + } + + /* reauth cache */ + reauth_cache = utils->malloc(sizeof(reauth_cache_t)); + if (reauth_cache == NULL) { + return SASL_NOMEM; + } + memset(reauth_cache, 0, sizeof(reauth_cache_t)); + reauth_cache->i_am = SERVER; + + /* fetch and canonify the reauth_timeout */ + utils->getopt(utils->getopt_context, "DIGEST-MD5", "reauth_timeout", + &timeout, &len); + if (timeout) { + reauth_cache->timeout = (time_t) 60 * strtol(timeout, NULL, 10); + } + if (reauth_cache->timeout < 0) { + reauth_cache->timeout = 0; + } + + if (reauth_cache->timeout) { + /* mutex */ + reauth_cache->mutex = utils->mutex_alloc(); + if (!reauth_cache->mutex) { + utils->free(reauth_cache); + return SASL_FAIL; + } + + /* entries */ + reauth_cache->size = 100; + reauth_cache->e = utils->malloc(reauth_cache->size * + sizeof(reauth_entry_t)); + if (reauth_cache->e == NULL) { + utils->mutex_free(reauth_cache->mutex); + utils->free(reauth_cache); + return SASL_NOMEM; + } + memset(reauth_cache->e, 0, reauth_cache->size * sizeof(reauth_entry_t)); + } + + ((digest_glob_context_t *) digestmd5_server_plugins[0].glob_context)->reauth = reauth_cache; + + *out_version = SASL_SERVER_PLUG_VERSION; + *pluglist = digestmd5_server_plugins; + *plugcount = 1; + + return SASL_OK; +} + +/***************************** Client Section *****************************/ + +typedef struct client_context { + context_t common; + + sasl_secret_t *password; /* user password */ + unsigned int free_password; /* set if we need to free password */ + + int protection; + struct digest_cipher *cipher; + unsigned long server_maxbuf; + + /* for HTTP mode (RFC 2617) only */ + char *algorithm; + unsigned char *opaque; +} client_context_t; + +static digest_glob_context_t client_glob_context; + +/* calculate H(A1) as per spec */ +static void DigestCalcHA1(context_t * text, + const sasl_utils_t * utils, + char *pszAlg, + unsigned char *pszUserName, + unsigned char *pszRealm, + sasl_secret_t * pszPassword, + unsigned char *pszAuthorization_id, + unsigned char *pszNonce, + unsigned char *pszCNonce, + HASHHEX SessionKey) +{ + MD5_CTX Md5Ctx; + HASH HA1; + + DigestCalcSecret(utils, + pszUserName, + pszRealm, + (unsigned char *) pszPassword->data, + pszPassword->len, + FALSE, + HA1); + + if (!text->http_mode || /* per RFC 2831 */ + (pszAlg && strcasecmp(pszAlg, "md5-sess") == 0)) { /* per RFC 2617 */ + /* calculate the session key */ + utils->MD5Init(&Md5Ctx); + if (text->http_mode) { + /* per RFC 2617 Errata ID 1649 */ + HASHHEX HA1Hex; + + CvtHex(HA1, HA1Hex); + utils->MD5Update(&Md5Ctx, HA1Hex, HASHHEXLEN); + } + else { + /* per RFC 2831 */ + utils->MD5Update(&Md5Ctx, HA1, HASHLEN); + } + utils->MD5Update(&Md5Ctx, COLON, 1); + utils->MD5Update(&Md5Ctx, pszNonce, (unsigned) strlen((char *) pszNonce)); + utils->MD5Update(&Md5Ctx, COLON, 1); + utils->MD5Update(&Md5Ctx, pszCNonce, (unsigned) strlen((char *) pszCNonce)); + if (pszAuthorization_id != NULL) { + utils->MD5Update(&Md5Ctx, COLON, 1); + utils->MD5Update(&Md5Ctx, pszAuthorization_id, + (unsigned) strlen((char *) pszAuthorization_id)); + } + utils->MD5Final(HA1, &Md5Ctx); + } + + CvtHex(HA1, SessionKey); + + /* xxx rc-* use different n */ + + /* save HA1 because we'll need it for the privacy and integrity keys */ + memcpy(text->HA1, HA1, sizeof(HASH)); + +} + +static char *calculate_response(context_t * text, + const sasl_utils_t * utils, + char *algorithm, + unsigned char *username, + unsigned char *realm, + unsigned char *nonce, + unsigned int ncvalue, + unsigned char *cnonce, + char *qop, + const sasl_http_request_t *request, + sasl_secret_t * passwd, + unsigned char *authorization_id, + char **response_value) +{ + HASHHEX SessionKey; + HASH EntityHash; + HASHHEX HEntity; + HASHHEX Response; + char *result; + + /* Verifing that all parameters was defined */ + if(!username || !cnonce || !nonce || !ncvalue || !request || !passwd) { + PARAMERROR( utils ); + return NULL; + } + + if (realm == NULL) { + /* a NULL realm is equivalent to the empty string */ + realm = (unsigned char *) ""; + } + + if (qop == NULL) { + /* default to a qop of just authentication */ + qop = "auth"; + } + + DigestCalcHA1(text, + utils, + algorithm, + username, + realm, + passwd, + authorization_id, + nonce, + cnonce, + SessionKey); + + if (text->http_mode) { + /* per RFC 2617 */ + MD5_CTX Md5Ctx; + + utils->MD5Init(&Md5Ctx); + utils->MD5Update(&Md5Ctx, request->entity, request->elen); + utils->MD5Final(EntityHash, &Md5Ctx); + } + else { + /* per RFC 2831 */ + memset(EntityHash, 0, HASHLEN); + } + CvtHex(EntityHash, HEntity); + + DigestCalcResponse(utils, + SessionKey,/* HEX(H(A1)) */ + nonce, /* nonce from server */ + ncvalue, /* 8 hex digits */ + cnonce, /* client nonce */ + (unsigned char *) qop, /* qop-value: "", "auth", + * "auth-int" */ + (unsigned char *) request->uri, /* requested URL */ + (unsigned char *) request->method, + HEntity, /* H(entity body) if qop="auth-int" */ + Response /* request-digest or response-digest */ + ); + + result = utils->malloc(HASHHEXLEN + 1); + memcpy(result, Response, HASHHEXLEN); + result[HASHHEXLEN] = 0; + + if (response_value != NULL) { + char * new_response_value; + + DigestCalcResponse(utils, + SessionKey, /* HEX(H(A1)) */ + nonce, /* nonce from server */ + ncvalue, /* 8 hex digits */ + cnonce, /* client nonce */ + (unsigned char *) qop, /* qop-value: "", "auth", + * "auth-int" */ + (unsigned char *) request->uri, /* requested URL */ + NULL, + HEntity, /* H(entity body) if qop="auth-int" */ + Response /* request-digest or response-digest */ + ); + + new_response_value = utils->realloc(*response_value, HASHHEXLEN + 1); + if (new_response_value == NULL) { + free (*response_value); + *response_value = NULL; + return NULL; + } + *response_value = new_response_value; + + memcpy(*response_value, Response, HASHHEXLEN); + (*response_value)[HASHHEXLEN] = 0; + + } + + return result; +} + +static int make_client_response(context_t *text, + sasl_client_params_t *params, + sasl_out_params_t *oparams) +{ + client_context_t *ctext = (client_context_t *) text; + char *qop = NULL; + unsigned nbits = 0; + char *digesturi = NULL; + bool IsUTF8 = FALSE; + char ncvalue[10]; + char maxbufstr[64]; + char *response = NULL; + unsigned resplen = 0; + int result = SASL_OK; + cipher_free_t *old_cipher_free = NULL; + sasl_http_request_t rfc2831_request; + const sasl_http_request_t *request; + + params->utils->log(params->utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 make_client_response()"); + + if (oparams->mech_ssf > 1) { + /* Remember the old cipher free function (if any). + It will be called later, once we are absolutely + sure that authentication was successful. */ + old_cipher_free = text->cipher_free; + /* free the old cipher context first */ + } + + switch (ctext->protection) { + case DIGEST_PRIVACY: + qop = "auth-conf"; + oparams->encode = &digestmd5_encode; + oparams->decode = &digestmd5_decode; + oparams->mech_ssf = ctext->cipher->ssf; + + nbits = ctext->cipher->n; + text->cipher_enc = ctext->cipher->cipher_enc; + text->cipher_dec = ctext->cipher->cipher_dec; + text->cipher_free = ctext->cipher->cipher_free; + text->cipher_init = ctext->cipher->cipher_init; + break; + case DIGEST_INTEGRITY: + qop = "auth-int"; + oparams->encode = &digestmd5_encode; + oparams->decode = &digestmd5_decode; + oparams->mech_ssf = 1; + break; + case DIGEST_NOLAYER: + default: + qop = "auth"; + oparams->encode = NULL; + oparams->decode = NULL; + oparams->mech_ssf = 0; + } + + if (text->http_mode) { + /* per RFC 2617 (HTTP Request as set by calling application) */ + request = params->http_request; + } + else { + /* per RFC 2831 */ + digesturi = params->utils->malloc(strlen(params->service) + 1 + + strlen(params->serverFQDN) + 1 + + 1); + if (digesturi == NULL) { + result = SASL_NOMEM; + goto FreeAllocatedMem; + } + + /* allocated exactly this. safe */ + strcpy(digesturi, params->service); + strcat(digesturi, "/"); + strcat(digesturi, params->serverFQDN); + /* + * strcat (digesturi, "/"); strcat (digesturi, params->serverFQDN); + */ + + rfc2831_request.method = "AUTHENTICATE"; + rfc2831_request.uri = digesturi; + rfc2831_request.entity = NULL; + rfc2831_request.elen = 0; + rfc2831_request.non_persist = 0; + request = &rfc2831_request; + } + + /* response */ + response = + calculate_response(text, + params->utils, + ctext->algorithm, + (unsigned char *) oparams->authid, + (unsigned char *) text->realm, + text->nonce, + text->nonce_count, + text->cnonce, + qop, + request, + ctext->password, + strcmp(oparams->user, oparams->authid) ? + (unsigned char *) oparams->user : NULL, + &text->response_value); + + + resplen = 0; + if (text->out_buf) params->utils->free(text->out_buf); + text->out_buf = NULL; + text->out_buf_len = 0; + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "username", (unsigned char *) oparams->authid, + TRUE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "realm", (unsigned char *) text->realm, + TRUE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + if (strcmp(oparams->user, oparams->authid)) { + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "authzid", (unsigned char *) oparams->user, TRUE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + } + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "nonce", text->nonce, TRUE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "cnonce", text->cnonce, TRUE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + snprintf(ncvalue, sizeof(ncvalue), "%08x", text->nonce_count); + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "nc", (unsigned char *) ncvalue, FALSE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "qop", (unsigned char *) qop, FALSE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + if (ctext->cipher != NULL) { + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "cipher", + (unsigned char *) ctext->cipher->name, + FALSE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + } + + if (params->props.maxbufsize) { + snprintf(maxbufstr, sizeof(maxbufstr), "%d", params->props.maxbufsize); + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "maxbuf", (unsigned char *) maxbufstr, + FALSE) != SASL_OK) { + SETERROR(params->utils, + "internal error: add_to_challenge maxbuf failed"); + goto FreeAllocatedMem; + } + } + + if (IsUTF8) { + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "charset", (unsigned char *) "utf-8", + FALSE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + } + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + text->http_mode ? "uri" /* per RFC 2617 */ + : "digest-uri", /* per RFC 2831 */ + (unsigned char *) request->uri, + TRUE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + if (text->http_mode) { + /* per RFC 2617: algorithm & opaque MUST be sent back to server */ + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "algorithm", (unsigned char *) ctext->algorithm, + FALSE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + if (ctext->opaque) { + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "opaque", ctext->opaque, TRUE) != SASL_OK) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + } + } + if (add_to_challenge(params->utils, + &text->out_buf, &text->out_buf_len, &resplen, + "response", (unsigned char *) response, + FALSE) != SASL_OK) { + + result = SASL_FAIL; + goto FreeAllocatedMem; + } + + /* self check */ + if (strlen(text->out_buf) > 2048) { + result = SASL_FAIL; + goto FreeAllocatedMem; + } + + /* set oparams */ + oparams->maxoutbuf = ctext->server_maxbuf; + if(oparams->mech_ssf > 1) { + /* MAC block (privacy) */ + oparams->maxoutbuf -= 25; + } else if(oparams->mech_ssf == 1) { + /* MAC block (integrity) */ + oparams->maxoutbuf -= 16; + } + + text->seqnum = 0; /* for integrity/privacy */ + text->rec_seqnum = 0; /* for integrity/privacy */ + text->utils = params->utils; + + /* Free the old security layer, if any */ + if (old_cipher_free) old_cipher_free(text); + + /* used by layers */ + _plug_decode_init(&text->decode_context, text->utils, + params->props.maxbufsize ? params->props.maxbufsize : + DEFAULT_BUFSIZE); + + if (oparams->mech_ssf > 0) { + unsigned char enckey[16]; + unsigned char deckey[16]; + + create_layer_keys(text, params->utils, text->HA1, nbits, + enckey, deckey); + + /* initialize cipher if need be */ + if (text->cipher_init) { + text->cipher_init(text, enckey, deckey); + } + } + + result = SASL_OK; + + FreeAllocatedMem: + if (digesturi) params->utils->free(digesturi); + if (response) params->utils->free(response); + + return result; +} + +static int parse_server_challenge(client_context_t *ctext, + sasl_client_params_t *params, + const char *serverin, unsigned serverinlen, + char ***outrealms, int *noutrealm) +{ + context_t *text = (context_t *) ctext; + int result = SASL_OK; + char *in_start = NULL; + char *in = NULL; + char **realms = NULL; + int nrealm = 0; + sasl_ssf_t limit, musthave = 0; + sasl_ssf_t external; + int protection = 0; + int saw_qop = 0; + int ciphers = 0; + int maxbuf_count = 0; + int algorithm_count = 0; + int opaque_count = 0; + + params->utils->log(params->utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 parse_server_challenge()"); + + if (!serverin || !serverinlen) { + params->utils->seterror(params->utils->conn, 0, + "no server challenge"); + return SASL_FAIL; + } + + in_start = in = params->utils->malloc(serverinlen + 1); + if (in == NULL) return SASL_NOMEM; + + memcpy(in, serverin, serverinlen); + in[serverinlen] = 0; + + ctext->server_maxbuf = 65536; /* Default value for maxbuf */ + + /* create a new cnonce */ + text->cnonce = create_nonce(params->utils); + if (text->cnonce == NULL) { + params->utils->seterror(params->utils->conn, 0, + "failed to create cnonce"); + result = SASL_FAIL; + goto FreeAllocatedMem; + } + + /* parse the challenge */ + while (in[0] != '\0') { + char *name, *value; + + get_pair(&in, &name, &value); + + /* if parse error */ + if (name == NULL) { + params->utils->seterror(params->utils->conn, 0, "Parse error"); + result = SASL_BADAUTH; + goto FreeAllocatedMem; + } + + if (*name == '\0') { + break; + } + + if (strcasecmp(name, "realm") == 0) { + nrealm++; + + if(!realms) + realms = params->utils->malloc(sizeof(char *) * (nrealm + 1)); + else + realms = params->utils->realloc(realms, + sizeof(char *) * (nrealm + 1)); + + if (realms == NULL) { + result = SASL_NOMEM; + goto FreeAllocatedMem; + } + + _plug_strdup(params->utils, value, &realms[nrealm-1], NULL); + realms[nrealm] = NULL; + } else if (strcasecmp(name, "nonce") == 0) { + _plug_strdup(params->utils, value, (char **) &text->nonce, + NULL); + text->nonce_count = 1; + } else if (strcasecmp(name, "qop") == 0) { + saw_qop = 1; + while (value && *value) { + char *comma; + char *end_val; + +SKIP_SPACES_IN_QOP: + /* skipping spaces: */ + value = skip_lws(value); + if (*value == '\0') { + break; + } + + /* check for an extreme case when there is no data: LWSP ',' */ + if (*value == ',') { + value++; + goto SKIP_SPACES_IN_QOP; + } + + comma = strchr(value, ','); + + if (comma != NULL) { + *comma++ = '\0'; + } + + /* skip LWSP at the end of the value (if any), skip_r_lws returns pointer to + the first LWSP character, NUL (if there were none) or NULL if the value + is entirely from LWSP characters */ + end_val = skip_r_lws (value); + if (end_val == NULL) { + value = comma; + continue; + } else { + /* strip LWSP */ + *end_val = '\0'; + } + + if (strcasecmp(value, "auth-conf") == 0) { + protection |= DIGEST_PRIVACY; + } else if (strcasecmp(value, "auth-int") == 0) { + protection |= DIGEST_INTEGRITY; + } else if (strcasecmp(value, "auth") == 0) { + protection |= DIGEST_NOLAYER; + } else { + params->utils->log(params->utils->conn, SASL_LOG_DEBUG, + "Server supports unknown layer: %s\n", + value); + } + + value = comma; + } + } else if (strcasecmp(name, "cipher") == 0) { + while (value && *value) { + struct digest_cipher *cipher = available_ciphers; + char *comma; + char *end_val; + +SKIP_SPACES_IN_CIPHER: + /* skipping spaces: */ + value = skip_lws(value); + if (*value == '\0') { + break; + } + + /* check for an extreme case when there is no data: LWSP ',' */ + if (*value == ',') { + value++; + goto SKIP_SPACES_IN_CIPHER; + } + + comma = strchr(value, ','); + + if (comma != NULL) { + *comma++ = '\0'; + } + + /* skip LWSP at the end of the value, skip_r_lws returns pointer to + the first LWSP character or NULL */ + end_val = skip_r_lws (value); + if (end_val == NULL) { + value = comma; + continue; + } else { + /* strip LWSP */ + *end_val = '\0'; + } + + /* do we support this cipher? */ + while (cipher->name) { + if (!strcasecmp(value, cipher->name)) break; + cipher++; + } + if (cipher->name) { + ciphers |= cipher->flag; + } else { + params->utils->log(params->utils->conn, SASL_LOG_DEBUG, + "Server supports unknown cipher: %s\n", + value); + } + + value = comma; + } + } else if (strcasecmp(name, "stale") == 0 && ctext->password) { + /* clear any cached password */ + if (ctext->free_password) + _plug_free_secret(params->utils, &ctext->password); + ctext->password = NULL; + } else if (strcasecmp(name, "maxbuf") == 0) { + /* maxbuf A number indicating the size of the largest + * buffer the server is able to receive when using + * "auth-int". If this directive is missing, the default + * value is 65536. This directive may appear at most once; + * if multiple instances are present, the client should + * abort the authentication exchange. + */ + maxbuf_count++; + + if (maxbuf_count != 1) { + result = SASL_BADAUTH; + params->utils->seterror(params->utils->conn, 0, + "At least two maxbuf directives found. Authentication aborted"); + goto FreeAllocatedMem; + } + + if (str2ul32 (value, &ctext->server_maxbuf) == FALSE) { + result = SASL_BADAUTH; + params->utils->seterror(params->utils->conn, 0, + "Invalid maxbuf parameter received from server (%s)", value); + goto FreeAllocatedMem; + } + + if (ctext->server_maxbuf <= 16) { + result = SASL_BADAUTH; + params->utils->seterror(params->utils->conn, 0, + "Invalid maxbuf parameter received from server (too small: %s)", value); + goto FreeAllocatedMem; + } + + if (ctext->server_maxbuf > MAX_SASL_BUFSIZE) { + result = SASL_BADAUTH; + params->utils->seterror(params->utils->conn, 0, + "Invalid maxbuf parameter received from server (too big: %s)", value); + goto FreeAllocatedMem; + } + } else if (strcasecmp(name, "charset") == 0) { + if (strcasecmp(value, "utf-8") != 0) { + result = SASL_BADAUTH; + params->utils->seterror(params->utils->conn, 0, + "Charset must be UTF-8"); + goto FreeAllocatedMem; + } + } else if (strcasecmp(name,"algorithm")==0) { + if (text->http_mode && strcasecmp(value, "md5") == 0) { + /* per RFC 2617: need to support both "md5" and "md5-sess" */ + } + else if (strcasecmp(value, "md5-sess") != 0) + { + params->utils->seterror(params->utils->conn, 0, + "'algorithm' isn't 'md5-sess'"); + result = SASL_FAIL; + goto FreeAllocatedMem; + } + + if (text->http_mode) { + /* per RFC 2617: algorithm MUST be saved */ + _plug_strdup(params->utils, value, (char **) &ctext->algorithm, + NULL); + } + algorithm_count++; + if (algorithm_count > 1) + { + params->utils->seterror(params->utils->conn, 0, + "Must see 'algorithm' only once"); + result = SASL_FAIL; + goto FreeAllocatedMem; + } + } else if (strcasecmp(name,"opaque")==0) { + /* per RFC 2831: opaque MUST be ignored if received */ + if (text->http_mode) { + /* per RFC 2617: opaque MUST be saved */ + _plug_strdup(params->utils, value, (char **) &ctext->opaque, + NULL); + opaque_count++; + if (opaque_count > 1) + { + params->utils->seterror(params->utils->conn, 0, + "Must see 'opaque' only once"); + result = SASL_FAIL; + goto FreeAllocatedMem; + } + } + } else { + params->utils->log(params->utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 unrecognized pair %s/%s: ignoring", + name, value); + } + } + + if (protection == 0) { + /* From RFC 2831[bis]: + This directive is optional; if not present it defaults to "auth". */ + if (saw_qop == 0) { + protection = DIGEST_NOLAYER; + } else { + result = SASL_BADAUTH; + params->utils->seterror(params->utils->conn, 0, + "Server doesn't support any known qop level"); + goto FreeAllocatedMem; + } + } + + if (algorithm_count != 1) { + params->utils->seterror(params->utils->conn, 0, + "Must see 'algorithm' once. Didn't see at all"); + result = SASL_FAIL; + goto FreeAllocatedMem; + } + + /* make sure we have everything we require */ + if (text->nonce == NULL) { + params->utils->seterror(params->utils->conn, 0, + "Don't have nonce."); + result = SASL_FAIL; + goto FreeAllocatedMem; + } + + /* get requested ssf */ + external = params->external_ssf; + + /* what do we _need_? how much is too much? */ + if (!text->http_mode && /* HTTP Digest doesn't need buffer */ + params->props.maxbufsize == 0) { + musthave = 0; + limit = 0; + } else { + if (params->props.max_ssf > external) { + limit = params->props.max_ssf - external; + } else { + limit = 0; + } + if (params->props.min_ssf > external) { + musthave = params->props.min_ssf - external; + } else { + musthave = 0; + } + } + + /* we now go searching for an option that gives us at least "musthave" + and at most "limit" bits of ssf. */ + if ((limit > 1) && (protection & DIGEST_PRIVACY)) { + struct digest_cipher *cipher; + + /* let's find an encryption scheme that we like */ + cipher = available_ciphers; + while (cipher->name) { + /* examine each cipher we support, see if it meets our security + requirements, and see if the server supports it. + choose the best one of these */ + if ((limit >= cipher->ssf) && (musthave <= cipher->ssf) && + (ciphers & cipher->flag) && + (!ctext->cipher || (cipher->ssf > ctext->cipher->ssf))) { + ctext->cipher = cipher; + } + cipher++; + } + + if (ctext->cipher) { + /* we found a cipher we like */ + ctext->protection = DIGEST_PRIVACY; + } else { + /* we didn't find any ciphers we like */ + params->utils->seterror(params->utils->conn, 0, + "No good privacy layers"); + } + } + + if (ctext->cipher == NULL) { + /* we failed to find an encryption layer we liked; + can we use integrity or nothing? */ + + if ((limit >= 1) && (musthave <= 1) + && (protection & DIGEST_INTEGRITY)) { + /* integrity */ + ctext->protection = DIGEST_INTEGRITY; + } else if (musthave <= 0) { + /* no layer */ + ctext->protection = DIGEST_NOLAYER; + + /* See if server supports not having a layer */ + if ((protection & DIGEST_NOLAYER) != DIGEST_NOLAYER) { + params->utils->seterror(params->utils->conn, 0, + "Server doesn't support \"no layer\""); + result = SASL_FAIL; + goto FreeAllocatedMem; + } + } else { + params->utils->seterror(params->utils->conn, 0, + "Can't find an acceptable layer"); + result = SASL_TOOWEAK; + goto FreeAllocatedMem; + } + } + + *outrealms = realms; + *noutrealm = nrealm; + + FreeAllocatedMem: + if (in_start) params->utils->free(in_start); + + if (result != SASL_OK && realms) { + int lup; + + /* need to free all the realms */ + for (lup = 0;lup < nrealm; lup++) + params->utils->free(realms[lup]); + + params->utils->free(realms); + } + + return result; +} + +static int ask_user_info(client_context_t *ctext, + sasl_client_params_t *params, + char **realms, int nrealm, + sasl_interact_t **prompt_need, + sasl_out_params_t *oparams) +{ + context_t *text = (context_t *) ctext; + int result = SASL_OK; + const char *authid = NULL, *userid = NULL, *realm = NULL; + char *realm_chal = NULL; + int user_result = SASL_OK; + int auth_result = SASL_OK; + int pass_result = SASL_OK; + int realm_result = SASL_FAIL; + int i; + size_t len; + + params->utils->log(params->utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 ask_user_info()"); + + /* try to get the authid */ + if (oparams->authid == NULL) { + auth_result = _plug_get_authid(params->utils, &authid, prompt_need); + + if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT)) { + return auth_result; + } + } + + /* try to get the userid */ + if (oparams->user == NULL) { + user_result = _plug_get_userid(params->utils, &userid, prompt_need); + + if ((user_result != SASL_OK) && (user_result != SASL_INTERACT)) { + return user_result; + } + } + + /* try to get the password */ + if (ctext->password == NULL) { + pass_result = _plug_get_password(params->utils, &ctext->password, + &ctext->free_password, prompt_need); + if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT)) { + return pass_result; + } + } + + /* try to get the realm */ + if (text->realm == NULL) { + if (realms) { + if(nrealm == 1) { + /* only one choice */ + realm = realms[0]; + realm_result = SASL_OK; + } else { + /* ask the user */ + realm_result = _plug_get_realm(params->utils, + (const char **) realms, + (const char **) &realm, + prompt_need); + } + } + + /* fake the realm if we must */ + if ((realm_result != SASL_OK) && (realm_result != SASL_INTERACT)) { + if (params->serverFQDN) { + realm = params->serverFQDN; + } else { + return realm_result; + } + } + } + + /* free prompts we got */ + if (prompt_need && *prompt_need) { + params->utils->free(*prompt_need); + *prompt_need = NULL; + } + + /* if there are prompts not filled in */ + if ((user_result == SASL_INTERACT) || (auth_result == SASL_INTERACT) || + (pass_result == SASL_INTERACT) || (realm_result == SASL_INTERACT)) { + + /* make our default realm */ + if (realm_result == SASL_INTERACT) { + if (realms) { + len = strlen(REALM_CHAL_PREFIX); + for (i = 0; i < nrealm; i++) { + len += strlen(realms[i]) + 4 /* " {}," */; + } + realm_chal = params->utils->malloc(len + 1); + strcpy (realm_chal, REALM_CHAL_PREFIX); + for (i = 0; i < nrealm; i++) { + strcat (realm_chal, " {"); + strcat (realm_chal, realms[i]); + strcat (realm_chal, "},"); + } + /* Replace the terminating comma with dot */ + realm_chal[len-1] = '.'; + + } else if (params->serverFQDN) { + realm_chal = params->utils->malloc(3+strlen(params->serverFQDN)); + if (realm_chal) { + sprintf(realm_chal, "{%s}", params->serverFQDN); + } else { + return SASL_NOMEM; + } + } + } + + /* make the prompt list */ + result = + _plug_make_prompts(params->utils, prompt_need, + user_result == SASL_INTERACT ? + "Please enter your authorization name" : NULL, + NULL, + auth_result == SASL_INTERACT ? + "Please enter your authentication name" : NULL, + NULL, + pass_result == SASL_INTERACT ? + "Please enter your password" : NULL, NULL, + NULL, NULL, NULL, + realm_chal ? realm_chal : "{}", + realm_result == SASL_INTERACT ? + "Please enter your realm" : NULL, + params->serverFQDN ? params->serverFQDN : NULL); + + if (result == SASL_OK) return SASL_INTERACT; + + return result; + } + + if (oparams->authid == NULL) { + if (!userid || !*userid) { + result = params->canon_user(params->utils->conn, authid, 0, + SASL_CU_AUTHID | SASL_CU_AUTHZID, + oparams); + } + else { + result = params->canon_user(params->utils->conn, + authid, 0, SASL_CU_AUTHID, oparams); + if (result != SASL_OK) return result; + + result = params->canon_user(params->utils->conn, + userid, 0, SASL_CU_AUTHZID, oparams); + } + if (result != SASL_OK) return result; + } + + /* Get an allocated version of the realm into the structure */ + if (realm && text->realm == NULL) { + _plug_strdup(params->utils, realm, (char **) &text->realm, NULL); + } + + return result; +} + +static int digestmd5_client_mech_new(void *glob_context, + sasl_client_params_t * params, + void **conn_context) +{ + context_t *text; + + if ((params->flags & SASL_NEED_HTTP) && !params->http_request) { + SETERROR(params->utils, + "DIGEST-MD5 unavailable due to lack of HTTP request"); + return SASL_BADPARAM; + } + + /* holds state are in -- allocate client size */ + text = params->utils->malloc(sizeof(client_context_t)); + if (text == NULL) + return SASL_NOMEM; + memset((client_context_t *)text, 0, sizeof(client_context_t)); + + text->state = 1; + text->i_am = CLIENT; + text->http_mode = (params->flags & SASL_NEED_HTTP); + text->reauth = ((digest_glob_context_t *) glob_context)->reauth; + + *conn_context = text; + + return SASL_OK; +} + +static int +digestmd5_client_mech_step1(client_context_t *ctext, + sasl_client_params_t *params, + const char *serverin __attribute__((unused)), + unsigned serverinlen __attribute__((unused)), + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + context_t *text = (context_t *) ctext; + int result = SASL_FAIL; + unsigned val; + + params->utils->log(params->utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 client step 1"); + + result = ask_user_info(ctext, params, NULL, 0, prompt_need, oparams); + if (result != SASL_OK) return result; + + /* check if we have cached info for this user on this server */ + val = hash(params->serverFQDN) % text->reauth->size; + if (params->utils->mutex_lock(text->reauth->mutex) == SASL_OK) { /* LOCK */ + if (text->reauth->e[val].u.c.serverFQDN && + !strcasecmp(text->reauth->e[val].u.c.serverFQDN, + params->serverFQDN) && + !strcmp(text->reauth->e[val].authid, oparams->authid)) { + + /* we have info, so use it */ + if (text->realm) params->utils->free(text->realm); + _plug_strdup(params->utils, text->reauth->e[val].realm, + &text->realm, NULL); + _plug_strdup(params->utils, (char *) text->reauth->e[val].nonce, + (char **) &text->nonce, NULL); + text->nonce_count = ++text->reauth->e[val].nonce_count; + _plug_strdup(params->utils, (char *) text->reauth->e[val].cnonce, + (char **) &text->cnonce, NULL); + if (text->http_mode) { + /* per RFC 2617: algorithm & opaque MUST be sent back to server */ + _plug_strdup(params->utils, + (char *) text->reauth->e[val].u.c.algorithm, + (char **) &ctext->algorithm, NULL); + if (text->reauth->e[val].u.c.opaque) { + _plug_strdup(params->utils, + (char *) text->reauth->e[val].u.c.opaque, + (char **) &ctext->opaque, NULL); + } + } + ctext->protection = text->reauth->e[val].u.c.protection; + ctext->cipher = text->reauth->e[val].u.c.cipher; + ctext->server_maxbuf = text->reauth->e[val].u.c.server_maxbuf; + } + params->utils->mutex_unlock(text->reauth->mutex); /* UNLOCK */ + } + + if (!text->nonce) { + /* we don't have any reauth info, so just return + * that there is no initial client send */ + text->state = 2; + return SASL_CONTINUE; + } + + /* + * (username | realm | nonce | cnonce | nonce-count | qop digest-uri | + * response | maxbuf | charset | auth-param ) + */ + + result = make_client_response(text, params, oparams); + if (result != SASL_OK) return result; + + *clientoutlen = (unsigned) strlen(text->out_buf); + *clientout = text->out_buf; + + /* check for next state (2 or 3) is done in digestmd5_client_mech_step() */ + return SASL_CONTINUE; +} + +static int digestmd5_client_mech_step2(client_context_t *ctext, + sasl_client_params_t *params, + const char *serverin, + unsigned serverinlen, + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + context_t *text = (context_t *) ctext; + int result = SASL_FAIL; + char **realms = NULL; + int nrealm = 0; + + params->utils->log(params->utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 client step 2"); + + if (params->props.min_ssf > params->props.max_ssf) { + return SASL_BADPARAM; + } + + /* don't bother parsing the challenge more than once */ + if (text->nonce == NULL) { + result = parse_server_challenge(ctext, params, serverin, serverinlen, + &realms, &nrealm); + if (result != SASL_OK) goto FreeAllocatedMem; + + if (nrealm == 1) { + /* only one choice! */ + if (text->realm) params->utils->free(text->realm); + text->realm = realms[0]; + + /* free realms */ + params->utils->free(realms); + realms = NULL; + } else { + /* Save realms for later use */ + text->realms = realms; + text->realm_cnt = nrealm; + } + } else { + /* Restore the list of realms */ + realms = text->realms; + nrealm = text->realm_cnt; + } + + result = ask_user_info(ctext, params, realms, nrealm, + prompt_need, oparams); + if (result != SASL_OK) goto FreeAllocatedMem; + + /* + * (username | realm | nonce | cnonce | nonce-count | qop | digest-uri | + * response | maxbuf | charset | auth-param ) + */ + + result = make_client_response(text, params, oparams); + if (result != SASL_OK) goto FreeAllocatedMem; + + *clientoutlen = (unsigned) strlen(text->out_buf); + *clientout = text->out_buf; + + text->state = 3; + + result = SASL_CONTINUE; + + FreeAllocatedMem: + return result; +} + +static int +digestmd5_client_mech_step3(client_context_t *ctext, + sasl_client_params_t *params, + const char *serverin, + unsigned serverinlen, + sasl_interact_t **prompt_need __attribute__((unused)), + const char **clientout __attribute__((unused)), + unsigned *clientoutlen __attribute__((unused)), + sasl_out_params_t *oparams) +{ + context_t *text = (context_t *) ctext; + char *in = NULL; + char *in_start; + int result = SASL_FAIL; + + params->utils->log(params->utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 client step 3"); + + /* Verify that server is really what he claims to be */ + in_start = in = params->utils->malloc(serverinlen + 1); + if (in == NULL) return SASL_NOMEM; + + memcpy(in, serverin, serverinlen); + in[serverinlen] = 0; + + /* parse the response */ + while (in[0] != '\0') { + char *name, *value; + get_pair(&in, &name, &value); + + if (name == NULL) { + params->utils->seterror(params->utils->conn, 0, + "DIGEST-MD5 Received Garbage"); + result = SASL_BADAUTH; + break; + } + + if (*name == '\0') { + break; + } + + if (strcasecmp(name, "rspauth") == 0) { + + if (strcmp(text->response_value, value) != 0) { + params->utils->seterror(params->utils->conn, 0, + "DIGEST-MD5: This server wants us to believe that he knows shared secret"); + result = SASL_BADSERV; + } else { + oparams->doneflag = 1; + oparams->param_version = 0; + + result = SASL_OK; + } + break; + } else { + params->utils->log(params->utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 unrecognized pair %s/%s: ignoring", + name, value); + } + } + + params->utils->free(in_start); + + if (params->utils->mutex_lock(text->reauth->mutex) == SASL_OK) { /* LOCK */ + unsigned val = hash(params->serverFQDN) % text->reauth->size; + switch (result) { + case SASL_OK: + if (text->nonce_count == 1) { + /* successful initial auth, setup for future reauth */ + clear_reauth_entry(&text->reauth->e[val], CLIENT, params->utils); + _plug_strdup(params->utils, oparams->authid, + &text->reauth->e[val].authid, NULL); + text->reauth->e[val].realm = text->realm; text->realm = NULL; + text->reauth->e[val].nonce = text->nonce; text->nonce = NULL; + text->reauth->e[val].nonce_count = text->nonce_count; + text->reauth->e[val].cnonce = text->cnonce; text->cnonce = NULL; + _plug_strdup(params->utils, params->serverFQDN, + &text->reauth->e[val].u.c.serverFQDN, NULL); + if (text->http_mode) { + /* per RFC 2617: algorithm & opaque MUST be saved */ + text->reauth->e[val].u.c.algorithm = ctext->algorithm; + ctext->algorithm = NULL; + text->reauth->e[val].u.c.opaque = ctext->opaque; + ctext->opaque = NULL; + } + text->reauth->e[val].u.c.protection = ctext->protection; + text->reauth->e[val].u.c.cipher = ctext->cipher; + text->reauth->e[val].u.c.server_maxbuf = ctext->server_maxbuf; + } + else { + /* reauth, we already incremented nonce_count */ + } + break; + default: + if (text->nonce_count > 1) { + /* failed reauth, clear cache */ + clear_reauth_entry(&text->reauth->e[val], CLIENT, params->utils); + } + else { + /* failed initial auth, leave existing cache */ + } + } + params->utils->mutex_unlock(text->reauth->mutex); /* UNLOCK */ + } + + return result; +} + +static int digestmd5_client_mech_step(void *conn_context, + sasl_client_params_t *params, + const char *serverin, + unsigned serverinlen, + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + context_t *text = (context_t *) conn_context; + client_context_t *ctext = (client_context_t *) conn_context; + unsigned val = hash(params->serverFQDN) % text->reauth->size; + + if (serverinlen > 2048) return SASL_BADPROT; + + *clientout = NULL; + *clientoutlen = 0; + + switch (text->state) { + + case 1: + if (!serverin) { + /* here's where we attempt fast reauth if possible */ + int reauth = 0; + + /* check if we have saved info for this server */ + if (params->utils->mutex_lock(text->reauth->mutex) == SASL_OK) { /* LOCK */ + reauth = text->reauth->e[val].u.c.serverFQDN && + !strcasecmp(text->reauth->e[val].u.c.serverFQDN, + params->serverFQDN); + params->utils->mutex_unlock(text->reauth->mutex); /* UNLOCK */ + } + if (reauth) { + return digestmd5_client_mech_step1(ctext, params, + serverin, serverinlen, + prompt_need, + clientout, clientoutlen, + oparams); + } + else { + /* we don't have any reauth info, so just return + * that there is no initial client send */ + text->state = 2; + return SASL_CONTINUE; + } + } + else if (!strncasecmp(serverin, "rspauth=", 8)) { + /* server accepted fast reauth */ + text->state = 3; + goto step3; + } + + /* fall through and respond to challenge */ + text->state = 2; + + /* cleanup after a failed reauth attempt */ + if (params->utils->mutex_lock(text->reauth->mutex) == SASL_OK) { /* LOCK */ + clear_reauth_entry(&text->reauth->e[val], CLIENT, params->utils); + + params->utils->mutex_unlock(text->reauth->mutex); /* UNLOCK */ + } + + if (text->realm) params->utils->free(text->realm); + if (text->nonce) params->utils->free(text->nonce); + if (text->cnonce) params->utils->free(text->cnonce); + text->realm = NULL; + text->nonce = text->cnonce = NULL; + ctext->cipher = NULL; + + GCC_FALLTHROUGH + + case 2: + return digestmd5_client_mech_step2(ctext, params, + serverin, serverinlen, + prompt_need, + clientout, clientoutlen, + oparams); + + case 3: + step3: + return digestmd5_client_mech_step3(ctext, params, + serverin, serverinlen, + prompt_need, + clientout, clientoutlen, + oparams); + + default: + params->utils->log(NULL, SASL_LOG_ERR, + "Invalid DIGEST-MD5 client step %d\n", text->state); + return SASL_FAIL; + } + + return SASL_FAIL; /* should never get here */ +} + +static void digestmd5_client_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + client_context_t *ctext = (client_context_t *) conn_context; + + if (!ctext || !utils) return; + + utils->log(utils->conn, SASL_LOG_DEBUG, + "DIGEST-MD5 client mech dispose"); + + if (ctext->free_password) _plug_free_secret(utils, &ctext->password); + if (ctext->algorithm) utils->free(ctext->algorithm); + if (ctext->opaque) utils->free(ctext->opaque); + + digestmd5_common_mech_dispose(conn_context, utils); +} + +static sasl_client_plug_t digestmd5_client_plugins[] = +{ + { + "DIGEST-MD5", +#ifdef WITH_RC4 /* mech_name */ + 128, /* max ssf */ +#elif defined(WITH_DES) + 112, +#else + 1, +#endif + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_NEEDSERVERFQDN + | SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP, /* features */ + NULL, /* required_prompts */ + &client_glob_context, /* glob_context */ + &digestmd5_client_mech_new, /* mech_new */ + &digestmd5_client_mech_step, /* mech_step */ + &digestmd5_client_mech_dispose, /* mech_dispose */ + &digestmd5_common_mech_free, /* mech_free */ + NULL, /* idle */ + NULL, /* spare1 */ + NULL /* spare2 */ + } +}; + +int digestmd5_client_plug_init(sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_client_plug_t **pluglist, + int *plugcount) +{ + reauth_cache_t *reauth_cache; + + if (maxversion < SASL_CLIENT_PLUG_VERSION) + return SASL_BADVERS; + + /* reauth cache */ + reauth_cache = utils->malloc(sizeof(reauth_cache_t)); + if (reauth_cache == NULL) + return SASL_NOMEM; + memset(reauth_cache, 0, sizeof(reauth_cache_t)); + reauth_cache->i_am = CLIENT; + + /* mutex */ + reauth_cache->mutex = utils->mutex_alloc(); + if (!reauth_cache->mutex) + return SASL_FAIL; + + /* entries */ + reauth_cache->size = 10; + reauth_cache->e = utils->malloc(reauth_cache->size * + sizeof(reauth_entry_t)); + if (reauth_cache->e == NULL) + return SASL_NOMEM; + memset(reauth_cache->e, 0, reauth_cache->size * sizeof(reauth_entry_t)); + + ((digest_glob_context_t *) digestmd5_client_plugins[0].glob_context)->reauth = reauth_cache; + + *out_version = SASL_CLIENT_PLUG_VERSION; + *pluglist = digestmd5_client_plugins; + *plugcount = 1; + + return SASL_OK; +} diff --git a/contrib/libs/sasl/plugins/otp.c b/contrib/libs/sasl/plugins/otp.c new file mode 100644 index 00000000000..28d1f51fc22 --- /dev/null +++ b/contrib/libs/sasl/plugins/otp.c @@ -0,0 +1,1895 @@ +/* OTP SASL plugin + * Ken Murchison + */ +/* + * Copyright (c) 1998-2016 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> + +#include <openssl/evp.h> +#include <openssl/md5.h> /* XXX hack for OpenBSD/OpenSSL cruftiness */ + +#include <sasl.h> +#define MD5_H /* suppress internal MD5 */ +#include <saslplug.h> + +#include "plugin_common.h" + +#ifdef macintosh +#error #include <sasl_otp_plugin_decl.h> +#endif + +/***************************** Common Section *****************************/ + +#define OTP_SEQUENCE_MAX 9999 +#define OTP_SEQUENCE_DEFAULT 499 +#define OTP_SEQUENCE_REINIT 490 +#define OTP_SEED_MIN 1 +#define OTP_SEED_MAX 16 +#define OTP_HASH_SIZE 8 /* 64 bits */ +#define OTP_CHALLENGE_MAX 100 +#define OTP_RESPONSE_MAX 100 +#define OTP_HEX_TYPE "hex:" +#define OTP_WORD_TYPE "word:" +#define OTP_INIT_HEX_TYPE "init-hex:" +#define OTP_INIT_WORD_TYPE "init-word:" + +typedef struct algorithm_option_s { + const char *name; /* name used in challenge/response */ + int swab; /* number of bytes to swab (0, 1, 2, 4, 8) */ + const char *evp_name; /* name used for lookup in EVP table */ +} algorithm_option_t; + +static algorithm_option_t algorithm_options[] = { + {"md4", 0, "md4"}, + {"md5", 0, "md5"}, + {"sha1", 4, "sha1"}, + {NULL, 0, NULL} +}; + +static EVP_MD_CTX *_plug_EVP_MD_CTX_new(const sasl_utils_t *utils) +{ + utils->log(NULL, SASL_LOG_DEBUG, "_plug_EVP_MD_CTX_new()"); + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + return EVP_MD_CTX_new(); +#else + return utils->malloc(sizeof(EVP_MD_CTX)); +#endif +} + +static void _plug_EVP_MD_CTX_free(EVP_MD_CTX *ctx, const sasl_utils_t *utils) +{ + utils->log(NULL, SASL_LOG_DEBUG, "_plug_EVP_MD_CTX_free()"); + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + EVP_MD_CTX_free(ctx); +#else + utils->free(ctx); +#endif +} + +/* Convert the binary data into ASCII hex */ +void bin2hex(unsigned char *bin, int binlen, char *hex) +{ + int i; + unsigned char c; + + for (i = 0; i < binlen; i++) { + c = (bin[i] >> 4) & 0xf; + hex[i*2] = (c > 9) ? ('a' + c - 10) : ('0' + c); + c = bin[i] & 0xf; + hex[i*2+1] = (c > 9) ? ('a' + c - 10) : ('0' + c); + } + hex[i*2] = '\0'; +} + +/* + * Hash the data using the given algorithm and fold it into 64 bits, + * swabbing bytes if necessary. + */ +static void otp_hash(const EVP_MD *md, char *in, size_t inlen, + unsigned char *out, int swab, EVP_MD_CTX *mdctx) +{ + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int i; + int j; + unsigned hashlen; + + EVP_DigestInit(mdctx, md); + EVP_DigestUpdate(mdctx, in, inlen); + EVP_DigestFinal(mdctx, hash, &hashlen); + + /* Fold the result into 64 bits */ + for (i = OTP_HASH_SIZE; i < hashlen; i++) { + hash[i % OTP_HASH_SIZE] ^= hash[i]; + } + + /* Swab bytes */ + if (swab) { + for (i = 0; i < OTP_HASH_SIZE;) { + for (j = swab-1; j > -swab; i++, j-=2) + out[i] = hash[i+j]; + } + } + else + memcpy(out, hash, OTP_HASH_SIZE); +} + +static int generate_otp(const sasl_utils_t *utils, + algorithm_option_t *alg, unsigned seq, char *seed, + unsigned char *secret, unsigned secret_len, + unsigned char *otp) +{ + const EVP_MD *md; + EVP_MD_CTX *mdctx = NULL; + char *key = NULL; + int r = SASL_OK; + + if (!(md = EVP_get_digestbyname(alg->evp_name))) { + utils->seterror(utils->conn, 0, + "OTP algorithm %s is not available", alg->evp_name); + return SASL_FAIL; + } + + if ((mdctx = _plug_EVP_MD_CTX_new(utils)) == NULL) { + SETERROR(utils, "cannot allocate MD CTX"); + r = SASL_NOMEM; + goto done; + } + + if ((key = utils->malloc(strlen(seed) + secret_len + 1)) == NULL) { + SETERROR(utils, "cannot allocate OTP key"); + r = SASL_NOMEM; + goto done; + } + + /* initial step */ + sprintf(key, "%s%.*s", seed, secret_len, secret); + otp_hash(md, key, strlen(key), otp, alg->swab, mdctx); + + /* computation step */ + while (seq-- > 0) + otp_hash(md, (char *) otp, OTP_HASH_SIZE, otp, alg->swab, mdctx); + +done: + if (key) utils->free(key); + if (mdctx) _plug_EVP_MD_CTX_free(mdctx, utils); + + return r; +} + +static int parse_challenge(const sasl_utils_t *utils, + char *chal, algorithm_option_t **alg, + unsigned *seq, char *seed, int is_init) +{ + char *c; + algorithm_option_t *opt; + int n; + + c = chal; + + /* eat leading whitespace */ + while (*c && isspace((int) *c)) c++; + + if (!is_init) { + /* check the prefix */ + if (!*c || strncmp(c, "otp-", 4)) { + SETERROR(utils, "not an OTP challenge"); + return SASL_BADPROT; + } + + /* skip the prefix */ + c += 4; + } + + /* find the algorithm */ + opt = algorithm_options; + while (opt->name) { + if (!strncmp(c, opt->name, strlen(opt->name))) { + break; + } + opt++; + } + + /* didn't find the algorithm in our list */ + if (!opt->name) { + utils->seterror(utils->conn, 0, "OTP algorithm '%s' not supported", c); + return SASL_BADPROT; + } + + /* skip algorithm name */ + c += strlen(opt->name); + *alg = opt; + + /* eat whitespace */ + if (!isspace((int) *c)) { + SETERROR(utils, "no whitespace between OTP algorithm and sequence"); + return SASL_BADPROT; + } + while (*c && isspace((int) *c)) c++; + + /* grab the sequence */ + if ((*seq = strtoul(c, &c, 10)) > OTP_SEQUENCE_MAX) { + utils->seterror(utils->conn, 0, "sequence > %u", OTP_SEQUENCE_MAX); + return SASL_BADPROT; + } + + /* eat whitespace */ + if (!isspace((int) *c)) { + SETERROR(utils, "no whitespace between OTP sequence and seed"); + return SASL_BADPROT; + } + while (*c && isspace((int) *c)) c++; + + /* grab the seed, converting to lowercase as we go */ + n = 0; + while (*c && isalnum((int) *c) && (n < OTP_SEED_MAX)) + seed[n++] = tolower((int) *c++); + if (n > OTP_SEED_MAX) { + utils->seterror(utils->conn, 0, "OTP seed length > %u", OTP_SEED_MAX); + return SASL_BADPROT; + } + else if (n < OTP_SEED_MIN) { + utils->seterror(utils->conn, 0, "OTP seed length < %u", OTP_SEED_MIN); + return SASL_BADPROT; + } + seed[n] = '\0'; + + if (!is_init) { + /* eat whitespace */ + if (!isspace((int) *c)) { + SETERROR(utils, "no whitespace between OTP seed and extensions"); + return SASL_BADPROT; + } + while (*c && isspace((int) *c)) c++; + + /* make sure this is an extended challenge */ + if (strncmp(c, "ext", 3) || + (*(c+=3) && + !(isspace((int) *c) || (*c == ',') || + (*c == '\r') || (*c == '\n')))) { + SETERROR(utils, "not an OTP extended challenge"); + return SASL_BADPROT; + } + } + + return SASL_OK; +} + +static void +otp_common_mech_free(void *global_context __attribute__((unused)), + const sasl_utils_t *utils __attribute__((unused))) +{ + /* Don't call EVP_cleanup(); here, as this might confuse the calling + application if it also uses OpenSSL */ +} + +/***************************** Server Section *****************************/ + +#ifdef HAVE_OPIE +#include <opie.h> +#endif + +typedef struct server_context { + int state; + + char *authid; + int locked; /* is the user's secret locked? */ + algorithm_option_t *alg; +#ifdef HAVE_OPIE + struct opie opie; +#else + char *realm; + unsigned seq; + char seed[OTP_SEED_MAX+1]; + unsigned char otp[OTP_HASH_SIZE]; + time_t timestamp; /* time we locked the secret */ +#endif /* HAVE_OPIE */ + + char *out_buf; + unsigned out_buf_len; +} server_context_t; + +static int otp_server_mech_new(void *glob_context __attribute__((unused)), + sasl_server_params_t *sparams, + const char *challenge __attribute__((unused)), + unsigned challen __attribute__((unused)), + void **conn_context) +{ + server_context_t *text; + + /* holds state are in */ + text = sparams->utils->malloc(sizeof(server_context_t)); + if (text == NULL) { + MEMERROR(sparams->utils); + return SASL_NOMEM; + } + + memset(text, 0, sizeof(server_context_t)); + + text->state = 1; + + *conn_context = text; + + return SASL_OK; +} + +#ifdef HAVE_OPIE + +#ifndef OPIE_KEYFILE +#define OPIE_KEYFILE "/etc/opiekeys" +#endif + +static int opie_server_mech_step(void *conn_context, + sasl_server_params_t *params, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + server_context_t *text = (server_context_t *) conn_context; + + *serverout = NULL; + *serveroutlen = 0; + + if (text == NULL) { + return SASL_BADPROT; + } + + switch (text->state) { + + case 1: { + const char *authzid; + const char *authid; + size_t authid_len; + unsigned lup = 0; + int result; + + /* should have received authzid NUL authid */ + + /* get authzid */ + authzid = clientin; + while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup; + + if (lup >= clientinlen) { + SETERROR(params->utils, "Can only find OTP authzid (no authid)"); + return SASL_BADPROT; + } + + /* get authid */ + ++lup; + authid = clientin + lup; + while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup; + + authid_len = clientin + lup - authid; + + if (lup != clientinlen) { + SETERROR(params->utils, + "Got more data than we were expecting in the OTP plugin\n"); + return SASL_BADPROT; + } + + text->authid = params->utils->malloc(authid_len + 1); + if (text->authid == NULL) { + MEMERROR(params->utils); + return SASL_NOMEM; + } + + /* we can't assume that authen is null-terminated */ + strncpy(text->authid, authid, authid_len); + text->authid[authid_len] = '\0'; + + result = params->canon_user(params->utils->conn, text->authid, 0, + SASL_CU_AUTHID, oparams); + if (result != SASL_OK) return result; + + result = params->canon_user(params->utils->conn, + strlen(authzid) ? authzid : text->authid, + 0, SASL_CU_AUTHZID, oparams); + if (result != SASL_OK) return result; + + result = _plug_buf_alloc(params->utils, &(text->out_buf), + &(text->out_buf_len), OTP_CHALLENGE_MAX+1); + if (result != SASL_OK) return result; + + /* create challenge - return sasl_continue on success */ + result = opiechallenge(&text->opie, text->authid, text->out_buf); + + switch (result) { + case 0: + text->locked = 1; + + *serverout = text->out_buf; + *serveroutlen = strlen(text->out_buf); + + text->state = 2; + return SASL_CONTINUE; + + case 1: + SETERROR(params->utils, "opiechallenge: user not found or locked"); + return SASL_NOUSER; + + default: + SETERROR(params->utils, + "opiechallenge: system error (file, memory, I/O)"); + return SASL_FAIL; + } + } + + case 2: { + char response[OPIE_RESPONSE_MAX+1]; + int result; + + /* should have received extended response, + but we'll take anything that we can verify */ + + if (clientinlen > OPIE_RESPONSE_MAX) { + SETERROR(params->utils, "response too long"); + return SASL_BADPROT; + } + + /* we can't assume that the response is null-terminated */ + strncpy(response, clientin, clientinlen); + response[clientinlen] = '\0'; + + /* verify response */ + result = opieverify(&text->opie, response); + text->locked = 0; + + switch (result) { + case 0: + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + return SASL_OK; + + case 1: + SETERROR(params->utils, "opieverify: invalid/incorrect response"); + return SASL_BADAUTH; + + default: + SETERROR(params->utils, + "opieverify: system error (file, memory, I/O)"); + return SASL_FAIL; + } + } + + default: + params->utils->log(NULL, SASL_LOG_ERR, + "Invalid OTP server step %d\n", text->state); + return SASL_FAIL; + } + + return SASL_FAIL; /* should never get here */ +} + +static void opie_server_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + server_context_t *text = (server_context_t *) conn_context; + + if (!text) return; + + /* if we created a challenge, but bailed before the verification of the + response, do a verify here to release the lock on the user key */ + if (text->locked) opieverify(&text->opie, ""); + + if (text->authid) _plug_free_string(utils, &(text->authid)); + + if (text->out_buf) utils->free(text->out_buf); + + utils->free(text); +} + +static int opie_mech_avail(void *glob_context __attribute__((unused)), + sasl_server_params_t *sparams, + void **conn_context __attribute__((unused))) +{ + const char *fname; + unsigned int len; + + sparams->utils->getopt(sparams->utils->getopt_context, + "OTP", "opiekeys", &fname, &len); + + if (!fname) fname = OPIE_KEYFILE; + + if (access(fname, R_OK|W_OK) != 0) { + sparams->utils->log(NULL, SASL_LOG_ERR, + "OTP unavailable because " + "can't read/write key database %s: %m", + fname, errno); + return SASL_NOMECH; + } + + return SASL_OK; +} + +static sasl_server_plug_t otp_server_plugins[] = +{ + { + "OTP", + 0, + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS + | SASL_SEC_FORWARD_SECRECY, + SASL_FEAT_WANT_CLIENT_FIRST + | SASL_FEAT_DONTUSE_USERPASSWD + | SASL_FEAT_ALLOWS_PROXY, + NULL, + &otp_server_mech_new, + &opie_server_mech_step, + &opie_server_mech_dispose, + &otp_common_mech_free, + NULL, + NULL, + NULL, + &opie_mech_avail, + NULL + } +}; +#else /* HAVE_OPIE */ + +#include "otp.h" + +#define OTP_MDA_DEFAULT "md5" +#define OTP_LOCK_TIMEOUT 5 * 60 /* 5 minutes */ + +/* Convert the ASCII hex into binary data */ +int hex2bin(char *hex, unsigned char *bin, int binlen) +{ + int i; + char *c; + unsigned char msn, lsn; + + memset(bin, 0, binlen); + + for (c = hex, i = 0; i < binlen; c++) { + /* whitespace */ + if (isspace((int) *c)) + continue; + /* end of string, or non-hex char */ + if (!*c || !*(c+1) || !isxdigit((int) *c)) + break; + + msn = (*c > '9') ? tolower((int) *c) - 'a' + 10 : *c - '0'; + c++; + lsn = (*c > '9') ? tolower((int) *c) - 'a' + 10 : *c - '0'; + + bin[i++] = (unsigned char) (msn << 4) | lsn; + } + + return (i < binlen) ? SASL_BADAUTH : SASL_OK; +} + +static int make_secret(const sasl_utils_t *utils, const char *alg, + unsigned seq, char *seed, unsigned char *otp, + time_t timeout, sasl_secret_t **secret) +{ + size_t sec_len; + char *data; + char buf[2*OTP_HASH_SIZE+1]; + + /* + * secret is stored as: + * + * <alg> \t <seq> \t <seed> \t <otp> \t <timeout> \0 + * + * <timeout> is used as a "lock" when an auth is in progress + * we just set it to zero here (no lock) + */ + sec_len = strlen(alg)+1+4+1+strlen(seed)+1+2*OTP_HASH_SIZE+1+20+1; + *secret = utils->malloc(sizeof(sasl_secret_t)+sec_len); + if (!*secret) { + return SASL_NOMEM; + } + + (*secret)->len = (unsigned) sec_len; + data = (char *) (*secret)->data; + + bin2hex(otp, OTP_HASH_SIZE, buf); + buf[2*OTP_HASH_SIZE] = '\0'; + + sprintf(data, "%s\t%04d\t%s\t%s\t%020ld", + alg, seq, seed, buf, timeout); + + return SASL_OK; +} + +static int parse_secret(const sasl_utils_t *utils, + char *secret, size_t seclen, + char *alg, unsigned *seq, char *seed, + unsigned char *otp, + time_t *timeout) +{ + if (strlen(secret) < seclen) { + char *c; + + /* + * old-style (binary) secret is stored as: + * + * <alg> \0 <seq> \0 <seed> \0 <otp> <timeout> + * + */ + + if (seclen < (3+1+1+1+OTP_SEED_MIN+1+OTP_HASH_SIZE+sizeof(time_t))) { + SETERROR(utils, "OTP secret too short"); + return SASL_FAIL; + } + + c = secret; + + strcpy(alg, (char*) c); + c += strlen(alg)+1; + + *seq = strtoul(c, NULL, 10); + c += 5; + + strcpy(seed, (char*) c); + c += strlen(seed)+1; + + memcpy(otp, c, OTP_HASH_SIZE); + c += OTP_HASH_SIZE; + + memcpy(timeout, c, sizeof(time_t)); + + return SASL_OK; + } + + else { + char buf[2*OTP_HASH_SIZE+1]; + + /* + * new-style (ASCII) secret is stored as: + * + * <alg> \t <seq> \t <seed> \t <otp> \t <timeout> \0 + * + */ + + if (seclen < (3+1+1+1+OTP_SEED_MIN+1+2*OTP_HASH_SIZE+1+20)) { + SETERROR(utils, "OTP secret too short"); + return SASL_FAIL; + } + + sscanf(secret, "%s\t%04d\t%s\t%s\t%020ld", + alg, seq, seed, buf, timeout); + + hex2bin(buf, otp, OTP_HASH_SIZE); + + return SASL_OK; + } +} + +/* Compare two string pointers */ +static int strptrcasecmp(const void *arg1, const void *arg2) +{ + return (strcasecmp(*((char**) arg1), *((char**) arg2))); +} + +/* Convert the 6 words into binary data */ +static int word2bin(const sasl_utils_t *utils, + char *words, unsigned char *bin, const EVP_MD *md, + EVP_MD_CTX *mdctx) +{ + int i, j; + char *c, *word, buf[OTP_RESPONSE_MAX+1]; + void *base; + int nmemb; + unsigned long x = 0; + unsigned char bits[OTP_HASH_SIZE+1]; /* 1 for checksum */ + unsigned char chksum; + int bit, fbyte, lbyte; + const char **str_ptr; + int alt_dict = 0; + + /* this is a destructive operation, so make a work copy */ + strcpy(buf, words); + memset(bits, 0, 9); + + for (c = buf, bit = 0, i = 0; i < 6; i++, c++, bit+=11) { + while (*c && isspace((int) *c)) c++; + word = c; + while (*c && isalpha((int) *c)) c++; + if (!*c && i < 5) break; + *c = '\0'; + if (strlen(word) < 1 || strlen(word) > 4) { + utils->log(NULL, SASL_LOG_DEBUG, + "incorrect word length '%s'", word); + return SASL_BADAUTH; + } + + /* standard dictionary */ + if (!alt_dict) { + if (strlen(word) < 4) { + base = otp_std_dict; + nmemb = OTP_4LETTER_OFFSET; + } + else { + base = otp_std_dict + OTP_4LETTER_OFFSET; + nmemb = OTP_STD_DICT_SIZE - OTP_4LETTER_OFFSET; + } + + str_ptr = (const char**) bsearch((void*) &word, base, nmemb, + sizeof(const char*), + strptrcasecmp); + if (str_ptr) { + x = (unsigned long) (str_ptr - otp_std_dict); + } + else if (i == 0) { + /* couldn't find first word, try alternate dictionary */ + alt_dict = 1; + } + else { + utils->log(NULL, SASL_LOG_DEBUG, + "word '%s' not found in dictionary", word); + return SASL_BADAUTH; + } + } + + /* alternate dictionary */ + if (alt_dict) { + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned hashlen; + + EVP_DigestInit(mdctx, md); + EVP_DigestUpdate(mdctx, word, strlen(word)); + EVP_DigestFinal(mdctx, hash, &hashlen); + + /* use lowest 11 bits */ + x = ((hash[hashlen-2] & 0x7) << 8) | hash[hashlen-1]; + } + + /* left align 11 bits on byte boundary */ + x <<= (8 - ((bit+11) % 8)); + /* first output byte containing some of our 11 bits */ + fbyte = bit / 8; + /* last output byte containing some of our 11 bits */ + lbyte = (bit+11) / 8; + /* populate the output bytes with the 11 bits */ + for (j = lbyte; j >= fbyte; j--, x >>= 8) + bits[j] |= (unsigned char) (x & 0xff); + } + + if (i < 6) { + utils->log(NULL, SASL_LOG_DEBUG, "not enough words (%d)", i); + return SASL_BADAUTH; + } + + /* see if the 2-bit checksum is correct */ + for (chksum = 0, i = 0; i < 8; i++) { + for (j = 0; j < 4; j++) { + chksum += ((bits[i] >> (2 * j)) & 0x3); + } + } + chksum <<= 6; + + if (chksum != bits[8]) { + utils->log(NULL, SASL_LOG_DEBUG, "incorrect parity"); + return SASL_BADAUTH; + } + + memcpy(bin, bits, OTP_HASH_SIZE); + + return SASL_OK; +} + +static int verify_response(server_context_t *text, const sasl_utils_t *utils, + char *response) +{ + const EVP_MD *md; + EVP_MD_CTX *mdctx = NULL; + char *c; + int do_init = 0; + unsigned char cur_otp[OTP_HASH_SIZE], prev_otp[OTP_HASH_SIZE]; + int r; + + /* find the MDA */ + if (!(md = EVP_get_digestbyname(text->alg->evp_name))) { + utils->seterror(utils->conn, 0, + "OTP algorithm %s is not available", + text->alg->evp_name); + return SASL_FAIL; + } + + if ((mdctx = _plug_EVP_MD_CTX_new(utils)) == NULL) { + SETERROR(utils, "cannot allocate MD CTX"); + return SASL_NOMEM; + } + + /* eat leading whitespace */ + c = response; + while (isspace((int) *c)) c++; + + if (strchr(c, ':')) { + if (!strncasecmp(c, OTP_HEX_TYPE, strlen(OTP_HEX_TYPE))) { + r = hex2bin(c+strlen(OTP_HEX_TYPE), cur_otp, OTP_HASH_SIZE); + } + else if (!strncasecmp(c, OTP_WORD_TYPE, strlen(OTP_WORD_TYPE))) { + r = word2bin(utils, c+strlen(OTP_WORD_TYPE), cur_otp, md, mdctx); + } + else if (!strncasecmp(c, OTP_INIT_HEX_TYPE, + strlen(OTP_INIT_HEX_TYPE))) { + do_init = 1; + r = hex2bin(c+strlen(OTP_INIT_HEX_TYPE), cur_otp, OTP_HASH_SIZE); + } + else if (!strncasecmp(c, OTP_INIT_WORD_TYPE, + strlen(OTP_INIT_WORD_TYPE))) { + do_init = 1; + r = word2bin(utils, c+strlen(OTP_INIT_WORD_TYPE), cur_otp, md, mdctx); + } + else { + SETERROR(utils, "unknown OTP extended response type"); + r = SASL_BADAUTH; + } + } + else { + /* standard response, try word first, and then hex */ + r = word2bin(utils, c, cur_otp, md, mdctx); + if (r != SASL_OK) + r = hex2bin(c, cur_otp, OTP_HASH_SIZE); + } + + if (r == SASL_OK) { + /* do one more hash (previous otp) and compare to stored otp */ + otp_hash(md, (char *) cur_otp, OTP_HASH_SIZE, + prev_otp, text->alg->swab, mdctx); + + if (!memcmp(prev_otp, text->otp, OTP_HASH_SIZE)) { + /* update the secret with this seq/otp */ + memcpy(text->otp, cur_otp, OTP_HASH_SIZE); + text->seq--; + r = SASL_OK; + } + else + r = SASL_BADAUTH; + } + + /* if this is an init- attempt, let's check it out */ + if (r == SASL_OK && do_init) { + char *new_chal = NULL, *new_resp = NULL; + algorithm_option_t *alg; + unsigned seq; + char seed[OTP_SEED_MAX+1]; + unsigned char new_otp[OTP_HASH_SIZE]; + + /* find the challenge and response fields */ + new_chal = strchr(c+strlen(OTP_INIT_WORD_TYPE), ':'); + if (new_chal) { + *new_chal++ = '\0'; + new_resp = strchr(new_chal, ':'); + if (new_resp) + *new_resp++ = '\0'; + } + + if (!(new_chal && new_resp)) { + r = SASL_BADAUTH; + goto done; + } + + if ((r = parse_challenge(utils, new_chal, &alg, &seq, seed, 1)) + != SASL_OK) { + goto done; + } + + if (seq < 1 || !strcasecmp(seed, text->seed)) { + r = SASL_BADAUTH; + goto done; + } + + /* find the MDA */ + if (!(md = EVP_get_digestbyname(alg->evp_name))) { + utils->seterror(utils->conn, 0, + "OTP algorithm %s is not available", + alg->evp_name); + r = SASL_BADAUTH; + goto done; + } + + if (!strncasecmp(c, OTP_INIT_HEX_TYPE, strlen(OTP_INIT_HEX_TYPE))) { + r = hex2bin(new_resp, new_otp, OTP_HASH_SIZE); + } + else if (!strncasecmp(c, OTP_INIT_WORD_TYPE, + strlen(OTP_INIT_WORD_TYPE))) { + r = word2bin(utils, new_resp, new_otp, md, mdctx); + } + + if (r == SASL_OK) { + /* setup for new secret */ + text->alg = alg; + text->seq = seq; + strcpy(text->seed, seed); + memcpy(text->otp, new_otp, OTP_HASH_SIZE); + } + } + + done: + if (mdctx) _plug_EVP_MD_CTX_free(mdctx, utils); + + return r; +} + +static int otp_server_mech_step1(server_context_t *text, + sasl_server_params_t *params, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + const char *authzid; + const char *authidp; + size_t authid_len; + unsigned lup = 0; + int result, n; + const char *lookup_request[] = { "*cmusaslsecretOTP", + NULL }; + const char *store_request[] = { "cmusaslsecretOTP", + NULL }; + struct propval auxprop_values[2]; + char mda[10]; + time_t timeout; + sasl_secret_t *sec = NULL; + struct propctx *propctx = NULL; + + /* should have received authzid NUL authid */ + + /* get authzid */ + authzid = clientin; + while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup; + + if (lup >= clientinlen) { + SETERROR(params->utils, "Can only find OTP authzid (no authid)"); + return SASL_BADPROT; + } + + /* get authid */ + ++lup; + authidp = clientin + lup; + while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup; + + authid_len = clientin + lup - authidp; + + if (lup != clientinlen) { + SETERROR(params->utils, + "Got more data than we were expecting in the OTP plugin\n"); + return SASL_BADPROT; + } + + text->authid = params->utils->malloc(authid_len + 1); + if (text->authid == NULL) { + MEMERROR(params->utils); + return SASL_NOMEM; + } + + /* we can't assume that authid is null-terminated */ + strncpy(text->authid, authidp, authid_len); + text->authid[authid_len] = '\0'; + + n = 0; + do { + /* Get user secret */ + result = params->utils->prop_request(params->propctx, + lookup_request); + if (result != SASL_OK) return result; + + /* this will trigger the getting of the aux properties. + Must use the fully qualified authid here */ + result = params->canon_user(params->utils->conn, text->authid, 0, + SASL_CU_AUTHID, oparams); + if (result != SASL_OK) return result; + + result = params->canon_user(params->utils->conn, + strlen(authzid) ? authzid : text->authid, + 0, SASL_CU_AUTHZID, oparams); + if (result != SASL_OK) return result; + + result = params->utils->prop_getnames(params->propctx, + lookup_request, + auxprop_values); + if (result < 0 || + (!auxprop_values[0].name || !auxprop_values[0].values)) { + /* We didn't find this username */ + SETERROR(params->utils, "no OTP secret in database"); + result = params->transition ? SASL_TRANS : SASL_NOUSER; + return (result); + } + + if (auxprop_values[0].name && auxprop_values[0].values) { + result = parse_secret(params->utils, + (char*) auxprop_values[0].values[0], + auxprop_values[0].valsize, + mda, &text->seq, text->seed, text->otp, + &timeout); + + if (result != SASL_OK) return result; + } else { + SETERROR(params->utils, "don't have an OTP secret"); + return SASL_FAIL; + } + + text->timestamp = time(0); + } + /* + * check lock timeout + * + * we try 10 times in 1 second intervals in order to give the other + * auth attempt time to finish + */ + while ((text->timestamp < timeout) && (n++ < 10) && !sleep(1)); + + if (text->timestamp < timeout) { + SETERROR(params->utils, + "simultaneous OTP authentications not permitted"); + return SASL_TRYAGAIN; + } + + /* check sequence number */ + if (text->seq <= 1) { + SETERROR(params->utils, "OTP has expired (sequence <= 1)"); + return SASL_EXPIRED; + } + + /* find algorithm */ + text->alg = algorithm_options; + while (text->alg->name) { + if (!strcasecmp(text->alg->name, mda)) + break; + + text->alg++; + } + + if (!text->alg->name) { + params->utils->seterror(params->utils->conn, 0, + "unknown OTP algorithm '%s'", mda); + return SASL_FAIL; + } + + /* remake the secret with a timeout */ + result = make_secret(params->utils, text->alg->name, text->seq, + text->seed, text->otp, + text->timestamp + OTP_LOCK_TIMEOUT, &sec); + if (result != SASL_OK) { + SETERROR(params->utils, "error making OTP secret"); + return result; + } + + /* do the store */ + propctx = params->utils->prop_new(0); + if (!propctx) + result = SASL_FAIL; + if (result == SASL_OK) + result = params->utils->prop_request(propctx, store_request); + if (result == SASL_OK) + result = params->utils->prop_set(propctx, "cmusaslsecretOTP", + (char *) sec->data, sec->len); + if (result == SASL_OK) + result = params->utils->auxprop_store(params->utils->conn, + propctx, text->authid); + if (propctx) + params->utils->prop_dispose(&propctx); + + if (sec) params->utils->free(sec); + + if (result != SASL_OK) { + SETERROR(params->utils, "Error putting OTP secret"); + return result; + } + + text->locked = 1; + + result = _plug_buf_alloc(params->utils, &(text->out_buf), + &(text->out_buf_len), OTP_CHALLENGE_MAX+1); + if (result != SASL_OK) return result; + + /* create challenge */ + sprintf(text->out_buf, "otp-%s %u %s ext", + text->alg->name, text->seq-1, text->seed); + + *serverout = text->out_buf; + *serveroutlen = (unsigned) strlen(text->out_buf); + + text->state = 2; + + return SASL_CONTINUE; +} + +static int +otp_server_mech_step2(server_context_t *text, + sasl_server_params_t *params, + const char *clientin, + unsigned clientinlen, + const char **serverout __attribute__((unused)), + unsigned *serveroutlen __attribute__((unused)), + sasl_out_params_t *oparams) +{ + char response[OTP_RESPONSE_MAX+1]; + int result; + sasl_secret_t *sec = NULL; + struct propctx *propctx = NULL; + const char *store_request[] = { "cmusaslsecretOTP", + NULL }; + + if (clientinlen > OTP_RESPONSE_MAX) { + SETERROR(params->utils, "OTP response too long"); + return SASL_BADPROT; + } + + /* we can't assume that the response is null-terminated */ + strncpy(response, clientin, clientinlen); + response[clientinlen] = '\0'; + + /* check timeout */ + if (time(0) > text->timestamp + OTP_LOCK_TIMEOUT) { + SETERROR(params->utils, "OTP: server timed out"); + return SASL_UNAVAIL; + } + + /* verify response */ + result = verify_response(text, params->utils, response); + if (result != SASL_OK) return result; + + /* make the new secret */ + result = make_secret(params->utils, text->alg->name, text->seq, + text->seed, text->otp, 0, &sec); + if (result != SASL_OK) { + SETERROR(params->utils, "error making OTP secret"); + } + + /* do the store */ + propctx = params->utils->prop_new(0); + if (!propctx) + result = SASL_FAIL; + if (result == SASL_OK) + result = params->utils->prop_request(propctx, store_request); + if (result == SASL_OK) + result = params->utils->prop_set(propctx, "cmusaslsecretOTP", + (char *) sec->data, sec->len); + if (result == SASL_OK) + result = params->utils->auxprop_store(params->utils->conn, + propctx, text->authid); + if (propctx) + params->utils->prop_dispose(&propctx); + + if (result) { + SETERROR(params->utils, "Error putting OTP secret"); + } + + text->locked = 0; + + if (sec) _plug_free_secret(params->utils, &sec); + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + return result; +} + +static int otp_server_mech_step(void *conn_context, + sasl_server_params_t *params, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + server_context_t *text = (server_context_t *) conn_context; + + *serverout = NULL; + *serveroutlen = 0; + + switch (text->state) { + + case 1: + return otp_server_mech_step1(text, params, clientin, clientinlen, + serverout, serveroutlen, oparams); + + case 2: + return otp_server_mech_step2(text, params, clientin, clientinlen, + serverout, serveroutlen, oparams); + + default: + params->utils->log(NULL, SASL_LOG_ERR, + "Invalid OTP server step %d\n", text->state); + return SASL_FAIL; + } + + return SASL_FAIL; /* should never get here */ +} + +static void otp_server_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + server_context_t *text = (server_context_t *) conn_context; + sasl_secret_t *sec; + struct propctx *propctx = NULL; + const char *store_request[] = { "cmusaslsecretOTP", + NULL }; + int r; + + if (!text) return; + + /* if we created a challenge, but bailed before the verification of the + response, release the lock on the user key */ + if (text->locked && (time(0) < text->timestamp + OTP_LOCK_TIMEOUT)) { + r = make_secret(utils, text->alg->name, text->seq, + text->seed, text->otp, 0, &sec); + if (r != SASL_OK) { + SETERROR(utils, "error making OTP secret"); + if (sec) utils->free(sec); + sec = NULL; + } + + /* do the store */ + propctx = utils->prop_new(0); + if (!propctx) + r = SASL_FAIL; + if (!r) + r = utils->prop_request(propctx, store_request); + if (!r) + r = utils->prop_set(propctx, "cmusaslsecretOTP", + (sec ? (char *) sec->data : NULL), + (sec ? sec->len : 0)); + if (!r) + r = utils->auxprop_store(utils->conn, propctx, text->authid); + if (propctx) + utils->prop_dispose(&propctx); + + if (r) { + SETERROR(utils, "Error putting OTP secret"); + } + + if (sec) _plug_free_secret(utils, &sec); + } + + if (text->authid) _plug_free_string(utils, &(text->authid)); + if (text->realm) _plug_free_string(utils, &(text->realm)); + + if (text->out_buf) utils->free(text->out_buf); + + utils->free(text); +} + +static int otp_setpass(void *glob_context __attribute__((unused)), + sasl_server_params_t *sparams, + const char *userstr, + const char *pass, unsigned passlen, + const char *oldpass __attribute__((unused)), + unsigned oldpasslen __attribute__((unused)), + unsigned flags) +{ + int r; + char *user = NULL; + char *user_only = NULL; + char *realm = NULL; + sasl_secret_t *sec; + struct propctx *propctx = NULL; + const char *store_request[] = { "cmusaslsecretOTP", + NULL }; + + /* Do we have a backend that can store properties? */ + if (!sparams->utils->auxprop_store || + sparams->utils->auxprop_store(NULL, NULL, NULL) != SASL_OK) { + SETERROR(sparams->utils, "OTP: auxprop backend can't store properties"); + return SASL_NOMECH; + } + + r = _plug_parseuser(sparams->utils, + &user_only, + &realm, + sparams->user_realm, + sparams->serverFQDN, + userstr); + if (r) { + SETERROR(sparams->utils, "OTP: Error parsing user"); + return r; + } + + r = _plug_make_fulluser(sparams->utils, &user, user_only, realm); + if (r) { + goto cleanup; + } + + if ((flags & SASL_SET_DISABLE) || pass == NULL) { + sec = NULL; + } else { + algorithm_option_t *algs; + const char *mda; + unsigned int len; + unsigned short randnum; + char seed[OTP_SEED_MAX+1]; + unsigned char otp[OTP_HASH_SIZE]; + + sparams->utils->getopt(sparams->utils->getopt_context, + "OTP", "otp_mda", &mda, &len); + if (!mda) mda = OTP_MDA_DEFAULT; + + algs = algorithm_options; + while (algs->name) { + if (!strcasecmp(algs->name, mda) || + !strcasecmp(algs->evp_name, mda)) + break; + + algs++; + } + + if (!algs->name) { + sparams->utils->seterror(sparams->utils->conn, 0, + "unknown OTP algorithm '%s'", mda); + r = SASL_FAIL; + goto cleanup; + } + + sparams->utils->rand(sparams->utils->rpool, + (char*) &randnum, sizeof(randnum)); + sprintf(seed, "%.2s%04u", sparams->serverFQDN, (randnum % 9999) + 1); + + r = generate_otp(sparams->utils, algs, OTP_SEQUENCE_DEFAULT, + seed, (unsigned char *) pass, passlen, otp); + if (r != SASL_OK) { + /* generate_otp() takes care of error message */ + goto cleanup; + } + + r = make_secret(sparams->utils, algs->name, OTP_SEQUENCE_DEFAULT, + seed, otp, 0, &sec); + if (r != SASL_OK) { + SETERROR(sparams->utils, "error making OTP secret"); + goto cleanup; + } + } + + /* do the store */ + propctx = sparams->utils->prop_new(0); + if (!propctx) + r = SASL_FAIL; + if (!r) + r = sparams->utils->prop_request(propctx, store_request); + if (!r) + r = sparams->utils->prop_set(propctx, "cmusaslsecretOTP", + (sec ? (char *) sec->data : NULL), + (sec ? sec->len : 0)); + if (!r) + r = sparams->utils->auxprop_store(sparams->utils->conn, propctx, user); + if (propctx) + sparams->utils->prop_dispose(&propctx); + + if (r) { + SETERROR(sparams->utils, "Error putting OTP secret"); + goto cleanup; + } + + sparams->utils->log(NULL, SASL_LOG_DEBUG, "Setpass for OTP successful\n"); + + cleanup: + + if (user) _plug_free_string(sparams->utils, &user); + if (user_only) _plug_free_string(sparams->utils, &user_only); + if (realm) _plug_free_string(sparams->utils, &realm); + if (sec) _plug_free_secret(sparams->utils, &sec); + + return r; +} + +static int otp_mech_avail(void *glob_context __attribute__((unused)), + sasl_server_params_t *sparams, + void **conn_context __attribute__((unused))) +{ + /* Do we have a backend that can store properties? */ + if (!sparams->utils->auxprop_store || + sparams->utils->auxprop_store(NULL, NULL, NULL) != SASL_OK) { + sparams->utils->log(NULL, + SASL_LOG_DEBUG, + "OTP: auxprop backend can't store properties"); + return SASL_NOMECH; + } + + return SASL_OK; +} + +static sasl_server_plug_t otp_server_plugins[] = +{ + { + "OTP", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS + | SASL_SEC_FORWARD_SECRECY, /* security_flags */ + SASL_FEAT_WANT_CLIENT_FIRST + | SASL_FEAT_ALLOWS_PROXY, /* features */ + NULL, /* glob_context */ + &otp_server_mech_new, /* mech_new */ + &otp_server_mech_step, /* mech_step */ + &otp_server_mech_dispose, /* mech_dispose */ + &otp_common_mech_free, /* mech_free */ + &otp_setpass, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + &otp_mech_avail, /* mech avail */ + NULL /* spare */ + } +}; +#endif /* HAVE_OPIE */ + +int otp_server_plug_init(const sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_server_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_SERVER_PLUG_VERSION) { + SETERROR(utils, "OTP version mismatch"); + return SASL_BADVERS; + } + + *out_version = SASL_SERVER_PLUG_VERSION; + *pluglist = otp_server_plugins; + *plugcount = 1; + + /* Add all digests */ + OpenSSL_add_all_digests(); + + return SASL_OK; +} + +/***************************** Client Section *****************************/ + +typedef struct client_context { + int state; + + sasl_secret_t *password; + unsigned int free_password; /* set if we need to free password */ + + const char *otpassword; + + char *out_buf; + unsigned out_buf_len; + + char challenge[OTP_CHALLENGE_MAX+1]; +} client_context_t; + +static int otp_client_mech_new(void *glob_context __attribute__((unused)), + sasl_client_params_t *params, + void **conn_context) +{ + client_context_t *text; + + /* holds state are in */ + text = params->utils->malloc(sizeof(client_context_t)); + if (text == NULL) { + MEMERROR( params->utils ); + return SASL_NOMEM; + } + + memset(text, 0, sizeof(client_context_t)); + + text->state = 1; + + *conn_context = text; + + return SASL_OK; +} + +static int otp_client_mech_step1(client_context_t *text, + sasl_client_params_t *params, + const char *serverin __attribute__((unused)), + unsigned serverinlen __attribute__((unused)), + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + const char *user = NULL, *authid = NULL; + int user_result = SASL_OK; + int auth_result = SASL_OK; + int pass_result = SASL_OK; + sasl_chalprompt_t *echo_cb; + void *echo_context; + int result; + + /* check if sec layer strong enough */ + if (params->props.min_ssf > params->external_ssf) { + SETERROR( params->utils, "SSF requested of OTP plugin"); + return SASL_TOOWEAK; + } + + /* try to get the authid */ + if (oparams->authid == NULL) { + auth_result = _plug_get_authid(params->utils, &authid, prompt_need); + + if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT)) + return auth_result; + } + + /* try to get the userid */ + if (oparams->user == NULL) { + user_result = _plug_get_userid(params->utils, &user, prompt_need); + + if ((user_result != SASL_OK) && (user_result != SASL_INTERACT)) + return user_result; + } + + /* try to get the secret pass-phrase if we don't have a chalprompt */ + if ((params->utils->getcallback(params->utils->conn, SASL_CB_ECHOPROMPT, + (sasl_callback_ft *)&echo_cb, &echo_context) == SASL_FAIL) && + (text->password == NULL)) { + pass_result = _plug_get_password(params->utils, &text->password, + &text->free_password, prompt_need); + + if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT)) + return pass_result; + } + + /* free prompts we got */ + if (prompt_need && *prompt_need) { + params->utils->free(*prompt_need); + *prompt_need = NULL; + } + + /* if there are prompts not filled in */ + if ((user_result == SASL_INTERACT) || (auth_result == SASL_INTERACT) || + (pass_result == SASL_INTERACT)) { + /* make the prompt list */ + result = + _plug_make_prompts(params->utils, prompt_need, + user_result == SASL_INTERACT ? + "Please enter your authorization name" : NULL, + NULL, + auth_result == SASL_INTERACT ? + "Please enter your authentication name" : NULL, + NULL, + pass_result == SASL_INTERACT ? + "Please enter your secret pass-phrase" : NULL, + NULL, + NULL, NULL, NULL, + NULL, NULL, NULL); + if (result != SASL_OK) return result; + + return SASL_INTERACT; + } + + if (!user || !*user) { + result = params->canon_user(params->utils->conn, authid, 0, + SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); + } + else { + result = params->canon_user(params->utils->conn, user, 0, + SASL_CU_AUTHZID, oparams); + if (result != SASL_OK) return result; + + result = params->canon_user(params->utils->conn, authid, 0, + SASL_CU_AUTHID, oparams); + } + if (result != SASL_OK) return result; + + /* send authorized id NUL authentication id */ + *clientoutlen = oparams->ulen + 1 + oparams->alen; + + /* remember the extra NUL on the end for stupid clients */ + result = _plug_buf_alloc(params->utils, &(text->out_buf), + &(text->out_buf_len), *clientoutlen + 1); + if (result != SASL_OK) return result; + + memset(text->out_buf, 0, *clientoutlen + 1); + memcpy(text->out_buf, oparams->user, oparams->ulen); + memcpy(text->out_buf+oparams->ulen+1, oparams->authid, oparams->alen); + *clientout = text->out_buf; + + text->state = 2; + + return SASL_CONTINUE; +} + +static int otp_client_mech_step2(client_context_t *text, + sasl_client_params_t *params, + const char *serverin, + unsigned serverinlen, + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + int echo_result = SASL_OK; + int result; + + if (serverinlen > OTP_CHALLENGE_MAX) { + SETERROR(params->utils, "OTP challenge too long"); + return SASL_BADPROT; + } + + /* we can't assume that challenge is null-terminated */ + strncpy(text->challenge, serverin, serverinlen); + text->challenge[serverinlen] = '\0'; + + /* try to get the one-time password if we don't have the secret */ + if ((text->password == NULL) && (text->otpassword == NULL)) { + echo_result = _plug_challenge_prompt(params->utils, + SASL_CB_ECHOPROMPT, + text->challenge, + "Please enter your one-time password", + &text->otpassword, + prompt_need); + + if ((echo_result != SASL_OK) && (echo_result != SASL_INTERACT)) + return echo_result; + } + + /* free prompts we got */ + if (prompt_need && *prompt_need) { + params->utils->free(*prompt_need); + *prompt_need = NULL; + } + + /* if there are prompts not filled in */ + if (echo_result == SASL_INTERACT) { + /* make the prompt list */ + result = + _plug_make_prompts(params->utils, + prompt_need, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + text->challenge, + "Please enter your one-time password", + NULL, + NULL, + NULL, + NULL); + if (result != SASL_OK) return result; + + return SASL_INTERACT; + } + + /* the application provided us with a one-time password so use it */ + if (text->otpassword) { + *clientout = text->otpassword; + *clientoutlen = (unsigned) strlen(text->otpassword); + } + /* generate our own response using the user's secret pass-phrase */ + else { + algorithm_option_t *alg; + unsigned seq; + char seed[OTP_SEED_MAX+1]; + unsigned char otp[OTP_HASH_SIZE]; + int init_done = 0; + + /* parse challenge */ + result = parse_challenge(params->utils, + text->challenge, + &alg, + &seq, + seed, + 0); + if (result != SASL_OK) return result; + + if (!text->password) { + PARAMERROR(params->utils); + return SASL_BADPARAM; + } + + if (seq < 1) { + SETERROR(params->utils, "OTP has expired (sequence < 1)"); + return SASL_EXPIRED; + } + + /* generate otp */ + result = generate_otp(params->utils, alg, seq, seed, + text->password->data, text->password->len, otp); + if (result != SASL_OK) return result; + + result = _plug_buf_alloc(params->utils, &(text->out_buf), + &(text->out_buf_len), OTP_RESPONSE_MAX+1); + if (result != SASL_OK) return result; + + if (seq < OTP_SEQUENCE_REINIT) { + unsigned short randnum; + char new_seed[OTP_SEED_MAX+1]; + unsigned char new_otp[OTP_HASH_SIZE]; + + /* try to reinitialize */ + + /* make sure we have a different seed */ + do { + params->utils->rand(params->utils->rpool, + (char*) &randnum, sizeof(randnum)); + sprintf(new_seed, "%.2s%04u", params->serverFQDN, + (randnum % 9999) + 1); + } while (!strcasecmp(seed, new_seed)); + + result = generate_otp(params->utils, alg, OTP_SEQUENCE_DEFAULT, + new_seed, text->password->data, text->password->len, new_otp); + + if (result == SASL_OK) { + /* create an init-hex response */ + strcpy(text->out_buf, OTP_INIT_HEX_TYPE); + bin2hex(otp, OTP_HASH_SIZE, + text->out_buf+strlen(text->out_buf)); + sprintf(text->out_buf+strlen(text->out_buf), ":%s %u %s:", + alg->name, OTP_SEQUENCE_DEFAULT, new_seed); + bin2hex(new_otp, OTP_HASH_SIZE, + text->out_buf+strlen(text->out_buf)); + init_done = 1; + } + else { + /* just do a regular response */ + } + } + + if (!init_done) { + /* created hex response */ + strcpy(text->out_buf, OTP_HEX_TYPE); + bin2hex(otp, OTP_HASH_SIZE, text->out_buf+strlen(text->out_buf)); + } + + *clientout = text->out_buf; + *clientoutlen = (unsigned) strlen(text->out_buf); + } + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + return SASL_OK; +} + +static int otp_client_mech_step(void *conn_context, + sasl_client_params_t *params, + const char *serverin, + unsigned serverinlen, + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + client_context_t *text = (client_context_t *) conn_context; + + *clientout = NULL; + *clientoutlen = 0; + + switch (text->state) { + + case 1: + return otp_client_mech_step1(text, params, serverin, serverinlen, + prompt_need, clientout, clientoutlen, + oparams); + + case 2: + return otp_client_mech_step2(text, params, serverin, serverinlen, + prompt_need, clientout, clientoutlen, + oparams); + + default: + params->utils->log(NULL, SASL_LOG_ERR, + "Invalid OTP client step %d\n", text->state); + return SASL_FAIL; + } + + return SASL_FAIL; /* should never get here */ +} + +static void otp_client_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + client_context_t *text = (client_context_t *) conn_context; + + if (!text) return; + + if (text->free_password) _plug_free_secret(utils, &(text->password)); + + if (text->out_buf) utils->free(text->out_buf); + + utils->free(text); +} + +static sasl_client_plug_t otp_client_plugins[] = +{ + { + "OTP", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS + | SASL_SEC_FORWARD_SECRECY, /* security_flags */ + SASL_FEAT_WANT_CLIENT_FIRST + | SASL_FEAT_ALLOWS_PROXY, /* features */ + NULL, /* required_prompts */ + NULL, /* glob_context */ + &otp_client_mech_new, /* mech_new */ + &otp_client_mech_step, /* mech_step */ + &otp_client_mech_dispose, /* mech_dispose */ + &otp_common_mech_free, /* mech_free */ + NULL, /* idle */ + NULL, /* spare */ + NULL /* spare */ + } +}; + +int otp_client_plug_init(sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_client_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_CLIENT_PLUG_VERSION) { + SETERROR(utils, "OTP version mismatch"); + return SASL_BADVERS; + } + + *out_version = SASL_CLIENT_PLUG_VERSION; + *pluglist = otp_client_plugins; + *plugcount = 1; + + /* Add all digests */ + OpenSSL_add_all_digests(); + + return SASL_OK; +} diff --git a/contrib/libs/sasl/plugins/otp.h b/contrib/libs/sasl/plugins/otp.h new file mode 100644 index 00000000000..06ad32c5c69 --- /dev/null +++ b/contrib/libs/sasl/plugins/otp.h @@ -0,0 +1,311 @@ +/* OTP SASL plugin + * Ken Murchison + */ +/* + * Copyright (c) 1998-2016 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _OTP_H_ +#define _OTP_H_ + +/* Standard dictionary from RFC2289 */ +#define OTP_STD_DICT_SIZE 2048 +#define OTP_4LETTER_OFFSET 571 + +static const char *otp_std_dict[OTP_STD_DICT_SIZE] = +{ "A", "ABE", "ACE", "ACT", "AD", "ADA", "ADD", +"AGO", "AID", "AIM", "AIR", "ALL", "ALP", "AM", "AMY", +"AN", "ANA", "AND", "ANN", "ANT", "ANY", "APE", "APS", +"APT", "ARC", "ARE", "ARK", "ARM", "ART", "AS", "ASH", +"ASK", "AT", "ATE", "AUG", "AUK", "AVE", "AWE", "AWK", +"AWL", "AWN", "AX", "AYE", "BAD", "BAG", "BAH", "BAM", +"BAN", "BAR", "BAT", "BAY", "BE", "BED", "BEE", "BEG", +"BEN", "BET", "BEY", "BIB", "BID", "BIG", "BIN", "BIT", +"BOB", "BOG", "BON", "BOO", "BOP", "BOW", "BOY", "BUB", +"BUD", "BUG", "BUM", "BUN", "BUS", "BUT", "BUY", "BY", +"BYE", "CAB", "CAL", "CAM", "CAN", "CAP", "CAR", "CAT", +"CAW", "COD", "COG", "COL", "CON", "COO", "COP", "COT", +"COW", "COY", "CRY", "CUB", "CUE", "CUP", "CUR", "CUT", +"DAB", "DAD", "DAM", "DAN", "DAR", "DAY", "DEE", "DEL", +"DEN", "DES", "DEW", "DID", "DIE", "DIG", "DIN", "DIP", +"DO", "DOE", "DOG", "DON", "DOT", "DOW", "DRY", "DUB", +"DUD", "DUE", "DUG", "DUN", "EAR", "EAT", "ED", "EEL", +"EGG", "EGO", "ELI", "ELK", "ELM", "ELY", "EM", "END", +"EST", "ETC", "EVA", "EVE", "EWE", "EYE", "FAD", "FAN", +"FAR", "FAT", "FAY", "FED", "FEE", "FEW", "FIB", "FIG", +"FIN", "FIR", "FIT", "FLO", "FLY", "FOE", "FOG", "FOR", +"FRY", "FUM", "FUN", "FUR", "GAB", "GAD", "GAG", "GAL", +"GAM", "GAP", "GAS", "GAY", "GEE", "GEL", "GEM", "GET", +"GIG", "GIL", "GIN", "GO", "GOT", "GUM", "GUN", "GUS", +"GUT", "GUY", "GYM", "GYP", "HA", "HAD", "HAL", "HAM", +"HAN", "HAP", "HAS", "HAT", "HAW", "HAY", "HE", "HEM", +"HEN", "HER", "HEW", "HEY", "HI", "HID", "HIM", "HIP", +"HIS", "HIT", "HO", "HOB", "HOC", "HOE", "HOG", "HOP", +"HOT", "HOW", "HUB", "HUE", "HUG", "HUH", "HUM", "HUT", +"I", "ICY", "IDA", "IF", "IKE", "ILL", "INK", "INN", +"IO", "ION", "IQ", "IRA", "IRE", "IRK", "IS", "IT", +"ITS", "IVY", "JAB", "JAG", "JAM", "JAN", "JAR", "JAW", +"JAY", "JET", "JIG", "JIM", "JO", "JOB", "JOE", "JOG", +"JOT", "JOY", "JUG", "JUT", "KAY", "KEG", "KEN", "KEY", +"KID", "KIM", "KIN", "KIT", "LA", "LAB", "LAC", "LAD", +"LAG", "LAM", "LAP", "LAW", "LAY", "LEA", "LED", "LEE", +"LEG", "LEN", "LEO", "LET", "LEW", "LID", "LIE", "LIN", +"LIP", "LIT", "LO", "LOB", "LOG", "LOP", "LOS", "LOT", +"LOU", "LOW", "LOY", "LUG", "LYE", "MA", "MAC", "MAD", +"MAE", "MAN", "MAO", "MAP", "MAT", "MAW", "MAY", "ME", +"MEG", "MEL", "MEN", "MET", "MEW", "MID", "MIN", "MIT", +"MOB", "MOD", "MOE", "MOO", "MOP", "MOS", "MOT", "MOW", +"MUD", "MUG", "MUM", "MY", "NAB", "NAG", "NAN", "NAP", +"NAT", "NAY", "NE", "NED", "NEE", "NET", "NEW", "NIB", +"NIL", "NIP", "NIT", "NO", "NOB", "NOD", "NON", "NOR", +"NOT", "NOV", "NOW", "NU", "NUN", "NUT", "O", "OAF", +"OAK", "OAR", "OAT", "ODD", "ODE", "OF", "OFF", "OFT", +"OH", "OIL", "OK", "OLD", "ON", "ONE", "OR", "ORB", +"ORE", "ORR", "OS", "OTT", "OUR", "OUT", "OVA", "OW", +"OWE", "OWL", "OWN", "OX", "PA", "PAD", "PAL", "PAM", +"PAN", "PAP", "PAR", "PAT", "PAW", "PAY", "PEA", "PEG", +"PEN", "PEP", "PER", "PET", "PEW", "PHI", "PI", "PIE", +"PIN", "PIT", "PLY", "PO", "POD", "POE", "POP", "POT", +"POW", "PRO", "PRY", "PUB", "PUG", "PUN", "PUP", "PUT", +"QUO", "RAG", "RAM", "RAN", "RAP", "RAT", "RAW", "RAY", +"REB", "RED", "REP", "RET", "RIB", "RID", "RIG", "RIM", +"RIO", "RIP", "ROB", "ROD", "ROE", "RON", "ROT", "ROW", +"ROY", "RUB", "RUE", "RUG", "RUM", "RUN", "RYE", "SAC", +"SAD", "SAG", "SAL", "SAM", "SAN", "SAP", "SAT", "SAW", +"SAY", "SEA", "SEC", "SEE", "SEN", "SET", "SEW", "SHE", +"SHY", "SIN", "SIP", "SIR", "SIS", "SIT", "SKI", "SKY", +"SLY", "SO", "SOB", "SOD", "SON", "SOP", "SOW", "SOY", +"SPA", "SPY", "SUB", "SUD", "SUE", "SUM", "SUN", "SUP", +"TAB", "TAD", "TAG", "TAN", "TAP", "TAR", "TEA", "TED", +"TEE", "TEN", "THE", "THY", "TIC", "TIE", "TIM", "TIN", +"TIP", "TO", "TOE", "TOG", "TOM", "TON", "TOO", "TOP", +"TOW", "TOY", "TRY", "TUB", "TUG", "TUM", "TUN", "TWO", +"UN", "UP", "US", "USE", "VAN", "VAT", "VET", "VIE", +"WAD", "WAG", "WAR", "WAS", "WAY", "WE", "WEB", "WED", +"WEE", "WET", "WHO", "WHY", "WIN", "WIT", "WOK", "WON", +"WOO", "WOW", "WRY", "WU", "YAM", "YAP", "YAW", "YE", +"YEA", "YES", "YET", "YOU", "ABED", "ABEL", "ABET", "ABLE", +"ABUT", "ACHE", "ACID", "ACME", "ACRE", "ACTA", "ACTS", "ADAM", +"ADDS", "ADEN", "AFAR", "AFRO", "AGEE", "AHEM", "AHOY", "AIDA", +"AIDE", "AIDS", "AIRY", "AJAR", "AKIN", "ALAN", "ALEC", "ALGA", +"ALIA", "ALLY", "ALMA", "ALOE", "ALSO", "ALTO", "ALUM", "ALVA", +"AMEN", "AMES", "AMID", "AMMO", "AMOK", "AMOS", "AMRA", "ANDY", +"ANEW", "ANNA", "ANNE", "ANTE", "ANTI", "AQUA", "ARAB", "ARCH", +"AREA", "ARGO", "ARID", "ARMY", "ARTS", "ARTY", "ASIA", "ASKS", +"ATOM", "AUNT", "AURA", "AUTO", "AVER", "AVID", "AVIS", "AVON", +"AVOW", "AWAY", "AWRY", "BABE", "BABY", "BACH", "BACK", "BADE", +"BAIL", "BAIT", "BAKE", "BALD", "BALE", "BALI", "BALK", "BALL", +"BALM", "BAND", "BANE", "BANG", "BANK", "BARB", "BARD", "BARE", +"BARK", "BARN", "BARR", "BASE", "BASH", "BASK", "BASS", "BATE", +"BATH", "BAWD", "BAWL", "BEAD", "BEAK", "BEAM", "BEAN", "BEAR", +"BEAT", "BEAU", "BECK", "BEEF", "BEEN", "BEER", "BEET", "BELA", +"BELL", "BELT", "BEND", "BENT", "BERG", "BERN", "BERT", "BESS", +"BEST", "BETA", "BETH", "BHOY", "BIAS", "BIDE", "BIEN", "BILE", +"BILK", "BILL", "BIND", "BING", "BIRD", "BITE", "BITS", "BLAB", +"BLAT", "BLED", "BLEW", "BLOB", "BLOC", "BLOT", "BLOW", "BLUE", +"BLUM", "BLUR", "BOAR", "BOAT", "BOCA", "BOCK", "BODE", "BODY", +"BOGY", "BOHR", "BOIL", "BOLD", "BOLO", "BOLT", "BOMB", "BONA", +"BOND", "BONE", "BONG", "BONN", "BONY", "BOOK", "BOOM", "BOON", +"BOOT", "BORE", "BORG", "BORN", "BOSE", "BOSS", "BOTH", "BOUT", +"BOWL", "BOYD", "BRAD", "BRAE", "BRAG", "BRAN", "BRAY", "BRED", +"BREW", "BRIG", "BRIM", "BROW", "BUCK", "BUDD", "BUFF", "BULB", +"BULK", "BULL", "BUNK", "BUNT", "BUOY", "BURG", "BURL", "BURN", +"BURR", "BURT", "BURY", "BUSH", "BUSS", "BUST", "BUSY", "BYTE", +"CADY", "CAFE", "CAGE", "CAIN", "CAKE", "CALF", "CALL", "CALM", +"CAME", "CANE", "CANT", "CARD", "CARE", "CARL", "CARR", "CART", +"CASE", "CASH", "CASK", "CAST", "CAVE", "CEIL", "CELL", "CENT", +"CERN", "CHAD", "CHAR", "CHAT", "CHAW", "CHEF", "CHEN", "CHEW", +"CHIC", "CHIN", "CHOU", "CHOW", "CHUB", "CHUG", "CHUM", "CITE", +"CITY", "CLAD", "CLAM", "CLAN", "CLAW", "CLAY", "CLOD", "CLOG", +"CLOT", "CLUB", "CLUE", "COAL", "COAT", "COCA", "COCK", "COCO", +"CODA", "CODE", "CODY", "COED", "COIL", "COIN", "COKE", "COLA", +"COLD", "COLT", "COMA", "COMB", "COME", "COOK", "COOL", "COON", +"COOT", "CORD", "CORE", "CORK", "CORN", "COST", "COVE", "COWL", +"CRAB", "CRAG", "CRAM", "CRAY", "CREW", "CRIB", "CROW", "CRUD", +"CUBA", "CUBE", "CUFF", "CULL", "CULT", "CUNY", "CURB", "CURD", +"CURE", "CURL", "CURT", "CUTS", "DADE", "DALE", "DAME", "DANA", +"DANE", "DANG", "DANK", "DARE", "DARK", "DARN", "DART", "DASH", +"DATA", "DATE", "DAVE", "DAVY", "DAWN", "DAYS", "DEAD", "DEAF", +"DEAL", "DEAN", "DEAR", "DEBT", "DECK", "DEED", "DEEM", "DEER", +"DEFT", "DEFY", "DELL", "DENT", "DENY", "DESK", "DIAL", "DICE", +"DIED", "DIET", "DIME", "DINE", "DING", "DINT", "DIRE", "DIRT", +"DISC", "DISH", "DISK", "DIVE", "DOCK", "DOES", "DOLE", "DOLL", +"DOLT", "DOME", "DONE", "DOOM", "DOOR", "DORA", "DOSE", "DOTE", +"DOUG", "DOUR", "DOVE", "DOWN", "DRAB", "DRAG", "DRAM", "DRAW", +"DREW", "DRUB", "DRUG", "DRUM", "DUAL", "DUCK", "DUCT", "DUEL", +"DUET", "DUKE", "DULL", "DUMB", "DUNE", "DUNK", "DUSK", "DUST", +"DUTY", "EACH", "EARL", "EARN", "EASE", "EAST", "EASY", "EBEN", +"ECHO", "EDDY", "EDEN", "EDGE", "EDGY", "EDIT", "EDNA", "EGAN", +"ELAN", "ELBA", "ELLA", "ELSE", "EMIL", "EMIT", "EMMA", "ENDS", +"ERIC", "EROS", "EVEN", "EVER", "EVIL", "EYED", "FACE", "FACT", +"FADE", "FAIL", "FAIN", "FAIR", "FAKE", "FALL", "FAME", "FANG", +"FARM", "FAST", "FATE", "FAWN", "FEAR", "FEAT", "FEED", "FEEL", +"FEET", "FELL", "FELT", "FEND", "FERN", "FEST", "FEUD", "FIEF", +"FIGS", "FILE", "FILL", "FILM", "FIND", "FINE", "FINK", "FIRE", +"FIRM", "FISH", "FISK", "FIST", "FITS", "FIVE", "FLAG", "FLAK", +"FLAM", "FLAT", "FLAW", "FLEA", "FLED", "FLEW", "FLIT", "FLOC", +"FLOG", "FLOW", "FLUB", "FLUE", "FOAL", "FOAM", "FOGY", "FOIL", +"FOLD", "FOLK", "FOND", "FONT", "FOOD", "FOOL", "FOOT", "FORD", +"FORE", "FORK", "FORM", "FORT", "FOSS", "FOUL", "FOUR", "FOWL", +"FRAU", "FRAY", "FRED", "FREE", "FRET", "FREY", "FROG", "FROM", +"FUEL", "FULL", "FUME", "FUND", "FUNK", "FURY", "FUSE", "FUSS", +"GAFF", "GAGE", "GAIL", "GAIN", "GAIT", "GALA", "GALE", "GALL", +"GALT", "GAME", "GANG", "GARB", "GARY", "GASH", "GATE", "GAUL", +"GAUR", "GAVE", "GAWK", "GEAR", "GELD", "GENE", "GENT", "GERM", +"GETS", "GIBE", "GIFT", "GILD", "GILL", "GILT", "GINA", "GIRD", +"GIRL", "GIST", "GIVE", "GLAD", "GLEE", "GLEN", "GLIB", "GLOB", +"GLOM", "GLOW", "GLUE", "GLUM", "GLUT", "GOAD", "GOAL", "GOAT", +"GOER", "GOES", "GOLD", "GOLF", "GONE", "GONG", "GOOD", "GOOF", +"GORE", "GORY", "GOSH", "GOUT", "GOWN", "GRAB", "GRAD", "GRAY", +"GREG", "GREW", "GREY", "GRID", "GRIM", "GRIN", "GRIT", "GROW", +"GRUB", "GULF", "GULL", "GUNK", "GURU", "GUSH", "GUST", "GWEN", +"GWYN", "HAAG", "HAAS", "HACK", "HAIL", "HAIR", "HALE", "HALF", +"HALL", "HALO", "HALT", "HAND", "HANG", "HANK", "HANS", "HARD", +"HARK", "HARM", "HART", "HASH", "HAST", "HATE", "HATH", "HAUL", +"HAVE", "HAWK", "HAYS", "HEAD", "HEAL", "HEAR", "HEAT", "HEBE", +"HECK", "HEED", "HEEL", "HEFT", "HELD", "HELL", "HELM", "HERB", +"HERD", "HERE", "HERO", "HERS", "HESS", "HEWN", "HICK", "HIDE", +"HIGH", "HIKE", "HILL", "HILT", "HIND", "HINT", "HIRE", "HISS", +"HIVE", "HOBO", "HOCK", "HOFF", "HOLD", "HOLE", "HOLM", "HOLT", +"HOME", "HONE", "HONK", "HOOD", "HOOF", "HOOK", "HOOT", "HORN", +"HOSE", "HOST", "HOUR", "HOVE", "HOWE", "HOWL", "HOYT", "HUCK", +"HUED", "HUFF", "HUGE", "HUGH", "HUGO", "HULK", "HULL", "HUNK", +"HUNT", "HURD", "HURL", "HURT", "HUSH", "HYDE", "HYMN", "IBIS", +"ICON", "IDEA", "IDLE", "IFFY", "INCA", "INCH", "INTO", "IONS", +"IOTA", "IOWA", "IRIS", "IRMA", "IRON", "ISLE", "ITCH", "ITEM", +"IVAN", "JACK", "JADE", "JAIL", "JAKE", "JANE", "JAVA", "JEAN", +"JEFF", "JERK", "JESS", "JEST", "JIBE", "JILL", "JILT", "JIVE", +"JOAN", "JOBS", "JOCK", "JOEL", "JOEY", "JOHN", "JOIN", "JOKE", +"JOLT", "JOVE", "JUDD", "JUDE", "JUDO", "JUDY", "JUJU", "JUKE", +"JULY", "JUNE", "JUNK", "JUNO", "JURY", "JUST", "JUTE", "KAHN", +"KALE", "KANE", "KANT", "KARL", "KATE", "KEEL", "KEEN", "KENO", +"KENT", "KERN", "KERR", "KEYS", "KICK", "KILL", "KIND", "KING", +"KIRK", "KISS", "KITE", "KLAN", "KNEE", "KNEW", "KNIT", "KNOB", +"KNOT", "KNOW", "KOCH", "KONG", "KUDO", "KURD", "KURT", "KYLE", +"LACE", "LACK", "LACY", "LADY", "LAID", "LAIN", "LAIR", "LAKE", +"LAMB", "LAME", "LAND", "LANE", "LANG", "LARD", "LARK", "LASS", +"LAST", "LATE", "LAUD", "LAVA", "LAWN", "LAWS", "LAYS", "LEAD", +"LEAF", "LEAK", "LEAN", "LEAR", "LEEK", "LEER", "LEFT", "LEND", +"LENS", "LENT", "LEON", "LESK", "LESS", "LEST", "LETS", "LIAR", +"LICE", "LICK", "LIED", "LIEN", "LIES", "LIEU", "LIFE", "LIFT", +"LIKE", "LILA", "LILT", "LILY", "LIMA", "LIMB", "LIME", "LIND", +"LINE", "LINK", "LINT", "LION", "LISA", "LIST", "LIVE", "LOAD", +"LOAF", "LOAM", "LOAN", "LOCK", "LOFT", "LOGE", "LOIS", "LOLA", +"LONE", "LONG", "LOOK", "LOON", "LOOT", "LORD", "LORE", "LOSE", +"LOSS", "LOST", "LOUD", "LOVE", "LOWE", "LUCK", "LUCY", "LUGE", +"LUKE", "LULU", "LUND", "LUNG", "LURA", "LURE", "LURK", "LUSH", +"LUST", "LYLE", "LYNN", "LYON", "LYRA", "MACE", "MADE", "MAGI", +"MAID", "MAIL", "MAIN", "MAKE", "MALE", "MALI", "MALL", "MALT", +"MANA", "MANN", "MANY", "MARC", "MARE", "MARK", "MARS", "MART", +"MARY", "MASH", "MASK", "MASS", "MAST", "MATE", "MATH", "MAUL", +"MAYO", "MEAD", "MEAL", "MEAN", "MEAT", "MEEK", "MEET", "MELD", +"MELT", "MEMO", "MEND", "MENU", "MERT", "MESH", "MESS", "MICE", +"MIKE", "MILD", "MILE", "MILK", "MILL", "MILT", "MIMI", "MIND", +"MINE", "MINI", "MINK", "MINT", "MIRE", "MISS", "MIST", "MITE", +"MITT", "MOAN", "MOAT", "MOCK", "MODE", "MOLD", "MOLE", "MOLL", +"MOLT", "MONA", "MONK", "MONT", "MOOD", "MOON", "MOOR", "MOOT", +"MORE", "MORN", "MORT", "MOSS", "MOST", "MOTH", "MOVE", "MUCH", +"MUCK", "MUDD", "MUFF", "MULE", "MULL", "MURK", "MUSH", "MUST", +"MUTE", "MUTT", "MYRA", "MYTH", "NAGY", "NAIL", "NAIR", "NAME", +"NARY", "NASH", "NAVE", "NAVY", "NEAL", "NEAR", "NEAT", "NECK", +"NEED", "NEIL", "NELL", "NEON", "NERO", "NESS", "NEST", "NEWS", +"NEWT", "NIBS", "NICE", "NICK", "NILE", "NINA", "NINE", "NOAH", +"NODE", "NOEL", "NOLL", "NONE", "NOOK", "NOON", "NORM", "NOSE", +"NOTE", "NOUN", "NOVA", "NUDE", "NULL", "NUMB", "OATH", "OBEY", +"OBOE", "ODIN", "OHIO", "OILY", "OINT", "OKAY", "OLAF", "OLDY", +"OLGA", "OLIN", "OMAN", "OMEN", "OMIT", "ONCE", "ONES", "ONLY", +"ONTO", "ONUS", "ORAL", "ORGY", "OSLO", "OTIS", "OTTO", "OUCH", +"OUST", "OUTS", "OVAL", "OVEN", "OVER", "OWLY", "OWNS", "QUAD", +"QUIT", "QUOD", "RACE", "RACK", "RACY", "RAFT", "RAGE", "RAID", +"RAIL", "RAIN", "RAKE", "RANK", "RANT", "RARE", "RASH", "RATE", +"RAVE", "RAYS", "READ", "REAL", "REAM", "REAR", "RECK", "REED", +"REEF", "REEK", "REEL", "REID", "REIN", "RENA", "REND", "RENT", +"REST", "RICE", "RICH", "RICK", "RIDE", "RIFT", "RILL", "RIME", +"RING", "RINK", "RISE", "RISK", "RITE", "ROAD", "ROAM", "ROAR", +"ROBE", "ROCK", "RODE", "ROIL", "ROLL", "ROME", "ROOD", "ROOF", +"ROOK", "ROOM", "ROOT", "ROSA", "ROSE", "ROSS", "ROSY", "ROTH", +"ROUT", "ROVE", "ROWE", "ROWS", "RUBE", "RUBY", "RUDE", "RUDY", +"RUIN", "RULE", "RUNG", "RUNS", "RUNT", "RUSE", "RUSH", "RUSK", +"RUSS", "RUST", "RUTH", "SACK", "SAFE", "SAGE", "SAID", "SAIL", +"SALE", "SALK", "SALT", "SAME", "SAND", "SANE", "SANG", "SANK", +"SARA", "SAUL", "SAVE", "SAYS", "SCAN", "SCAR", "SCAT", "SCOT", +"SEAL", "SEAM", "SEAR", "SEAT", "SEED", "SEEK", "SEEM", "SEEN", +"SEES", "SELF", "SELL", "SEND", "SENT", "SETS", "SEWN", "SHAG", +"SHAM", "SHAW", "SHAY", "SHED", "SHIM", "SHIN", "SHOD", "SHOE", +"SHOT", "SHOW", "SHUN", "SHUT", "SICK", "SIDE", "SIFT", "SIGH", +"SIGN", "SILK", "SILL", "SILO", "SILT", "SINE", "SING", "SINK", +"SIRE", "SITE", "SITS", "SITU", "SKAT", "SKEW", "SKID", "SKIM", +"SKIN", "SKIT", "SLAB", "SLAM", "SLAT", "SLAY", "SLED", "SLEW", +"SLID", "SLIM", "SLIT", "SLOB", "SLOG", "SLOT", "SLOW", "SLUG", +"SLUM", "SLUR", "SMOG", "SMUG", "SNAG", "SNOB", "SNOW", "SNUB", +"SNUG", "SOAK", "SOAR", "SOCK", "SODA", "SOFA", "SOFT", "SOIL", +"SOLD", "SOME", "SONG", "SOON", "SOOT", "SORE", "SORT", "SOUL", +"SOUR", "SOWN", "STAB", "STAG", "STAN", "STAR", "STAY", "STEM", +"STEW", "STIR", "STOW", "STUB", "STUN", "SUCH", "SUDS", "SUIT", +"SULK", "SUMS", "SUNG", "SUNK", "SURE", "SURF", "SWAB", "SWAG", +"SWAM", "SWAN", "SWAT", "SWAY", "SWIM", "SWUM", "TACK", "TACT", +"TAIL", "TAKE", "TALE", "TALK", "TALL", "TANK", "TASK", "TATE", +"TAUT", "TEAL", "TEAM", "TEAR", "TECH", "TEEM", "TEEN", "TEET", +"TELL", "TEND", "TENT", "TERM", "TERN", "TESS", "TEST", "THAN", +"THAT", "THEE", "THEM", "THEN", "THEY", "THIN", "THIS", "THUD", +"THUG", "TICK", "TIDE", "TIDY", "TIED", "TIER", "TILE", "TILL", +"TILT", "TIME", "TINA", "TINE", "TINT", "TINY", "TIRE", "TOAD", +"TOGO", "TOIL", "TOLD", "TOLL", "TONE", "TONG", "TONY", "TOOK", +"TOOL", "TOOT", "TORE", "TORN", "TOTE", "TOUR", "TOUT", "TOWN", +"TRAG", "TRAM", "TRAY", "TREE", "TREK", "TRIG", "TRIM", "TRIO", +"TROD", "TROT", "TROY", "TRUE", "TUBA", "TUBE", "TUCK", "TUFT", +"TUNA", "TUNE", "TUNG", "TURF", "TURN", "TUSK", "TWIG", "TWIN", +"TWIT", "ULAN", "UNIT", "URGE", "USED", "USER", "USES", "UTAH", +"VAIL", "VAIN", "VALE", "VARY", "VASE", "VAST", "VEAL", "VEDA", +"VEIL", "VEIN", "VEND", "VENT", "VERB", "VERY", "VETO", "VICE", +"VIEW", "VINE", "VISE", "VOID", "VOLT", "VOTE", "WACK", "WADE", +"WAGE", "WAIL", "WAIT", "WAKE", "WALE", "WALK", "WALL", "WALT", +"WAND", "WANE", "WANG", "WANT", "WARD", "WARM", "WARN", "WART", +"WASH", "WAST", "WATS", "WATT", "WAVE", "WAVY", "WAYS", "WEAK", +"WEAL", "WEAN", "WEAR", "WEED", "WEEK", "WEIR", "WELD", "WELL", +"WELT", "WENT", "WERE", "WERT", "WEST", "WHAM", "WHAT", "WHEE", +"WHEN", "WHET", "WHOA", "WHOM", "WICK", "WIFE", "WILD", "WILL", +"WIND", "WINE", "WING", "WINK", "WINO", "WIRE", "WISE", "WISH", +"WITH", "WOLF", "WONT", "WOOD", "WOOL", "WORD", "WORE", "WORK", +"WORM", "WORN", "WOVE", "WRIT", "WYNN", "YALE", "YANG", "YANK", +"YARD", "YARN", "YAWL", "YAWN", "YEAH", "YEAR", "YELL", "YOGA", +"YOKE" }; + +#endif /* _OTP_H_ */ diff --git a/contrib/libs/sasl/plugins/plain.c b/contrib/libs/sasl/plugins/plain.c new file mode 100644 index 00000000000..eb486870322 --- /dev/null +++ b/contrib/libs/sasl/plugins/plain.c @@ -0,0 +1,489 @@ +/* Plain SASL plugin + * Rob Siemborski + * Tim Martin + */ +/* + * Copyright (c) 1998-2016 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <config.h> +#include <stdio.h> +#include <string.h> +#include <sasl.h> +#include <saslplug.h> + +#include "plugin_common.h" + +#ifdef macintosh +#error #include <sasl_plain_plugin_decl.h> +#endif + +/***************************** Common Section *****************************/ + +/***************************** Server Section *****************************/ + +static int plain_server_mech_new(void *glob_context __attribute__((unused)), + sasl_server_params_t *sparams, + const char *challenge __attribute__((unused)), + unsigned challen __attribute__((unused)), + void **conn_context) +{ + /* holds state are in */ + if (!conn_context) { + PARAMERROR( sparams->utils ); + return SASL_BADPARAM; + } + + *conn_context = NULL; + + return SASL_OK; +} + +static int plain_server_mech_step(void *conn_context __attribute__((unused)), + sasl_server_params_t *params, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + const char *author; + const char *authen; + const char *password; + unsigned password_len; + unsigned lup = 0; + int result; + char *passcopy; + unsigned canon_flags = 0; + + *serverout = NULL; + *serveroutlen = 0; + + /* should have received author-id NUL authen-id NUL password */ + + /* get author */ + author = clientin; + while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup; + + if (lup >= clientinlen) { + SETERROR(params->utils, "Can only find author (no password)"); + return SASL_BADPROT; + } + + /* get authen */ + ++lup; + authen = clientin + lup; + while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup; + + if (lup >= clientinlen) { + params->utils->seterror(params->utils->conn, 0, + "Can only find author/en (no password)"); + return SASL_BADPROT; + } + + /* get password */ + lup++; + password = clientin + lup; + while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup; + + password_len = (unsigned) (clientin + lup - password); + + if (lup != clientinlen) { + SETERROR(params->utils, + "Got more data than we were expecting in the PLAIN plugin\n"); + return SASL_BADPROT; + } + + /* this kinda sucks. we need password to be null terminated + but we can't assume there is an allocated byte at the end + of password so we have to copy it */ + passcopy = params->utils->malloc(password_len + 1); + if (passcopy == NULL) { + MEMERROR(params->utils); + return SASL_NOMEM; + } + + strncpy(passcopy, password, password_len); + passcopy[password_len] = '\0'; + + /* Canonicalize userid first, so that password verification is only + * against the canonical id */ + if (!author || !*author) { + author = authen; + canon_flags = SASL_CU_AUTHZID; + } else if (strcmp(author, authen) == 0) { + /* While this isn't going to find out that <user> and <user>@<defaultdomain> + are the same thing, this is good enough for many cases */ + canon_flags = SASL_CU_AUTHZID; + } + + result = params->canon_user(params->utils->conn, + authen, + 0, + SASL_CU_AUTHID | canon_flags | SASL_CU_EXTERNALLY_VERIFIED, + oparams); + if (result != SASL_OK) { + _plug_free_string(params->utils, &passcopy); + return result; + } + + /* verify password (and possibly fetch both authentication and + authorization identity related properties) - return SASL_OK + on success */ + result = params->utils->checkpass(params->utils->conn, + oparams->authid, + oparams->alen, + passcopy, + password_len); + + _plug_free_string(params->utils, &passcopy); + + if (result != SASL_OK) { + params->utils->seterror(params->utils->conn, 0, + "Password verification failed"); + return result; + } + + /* Canonicalize and store the authorization ID */ + /* We need to do this after calling verify_user just in case verify_user + * needed to get auxprops itself */ + if (canon_flags == 0) { + const struct propval *pr; + int i; + + pr = params->utils->prop_get(params->propctx); + if (!pr) { + return SASL_FAIL; + } + + /* params->utils->checkpass() might have fetched authorization identity related properties + for the wrong user name. Free these values. */ + for (i = 0; pr[i].name; i++) { + if (pr[i].name[0] == '*') { + continue; + } + + if (pr[i].values) { + params->utils->prop_erase(params->propctx, pr[i].name); + } + } + + result = params->canon_user(params->utils->conn, + author, + 0, + SASL_CU_AUTHZID, + oparams); + if (result != SASL_OK) { + return result; + } + } + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + return SASL_OK; +} + +static sasl_server_plug_t plain_server_plugins[] = +{ + { + "PLAIN", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOANONYMOUS + | SASL_SEC_PASS_CREDENTIALS, /* security_flags */ + SASL_FEAT_WANT_CLIENT_FIRST + | SASL_FEAT_ALLOWS_PROXY, /* features */ + NULL, /* glob_context */ + &plain_server_mech_new, /* mech_new */ + &plain_server_mech_step, /* mech_step */ + NULL, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + NULL, /* mech_avail */ + NULL /* spare */ + } +}; + +int plain_server_plug_init(const sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_server_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_SERVER_PLUG_VERSION) { + SETERROR(utils, "PLAIN version mismatch"); + return SASL_BADVERS; + } + + *out_version = SASL_SERVER_PLUG_VERSION; + *pluglist = plain_server_plugins; + *plugcount = 1; + + return SASL_OK; +} + +/***************************** Client Section *****************************/ + +typedef struct client_context { + char *out_buf; + unsigned out_buf_len; +} client_context_t; + +static int plain_client_mech_new(void *glob_context __attribute__((unused)), + sasl_client_params_t *params, + void **conn_context) +{ + client_context_t *text; + + /* holds state are in */ + text = params->utils->malloc(sizeof(client_context_t)); + if (text == NULL) { + MEMERROR( params->utils ); + return SASL_NOMEM; + } + + memset(text, 0, sizeof(client_context_t)); + + *conn_context = text; + + return SASL_OK; +} + +static int plain_client_mech_step(void *conn_context, + sasl_client_params_t *params, + const char *serverin __attribute__((unused)), + unsigned serverinlen __attribute__((unused)), + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + client_context_t *text = (client_context_t *) conn_context; + const char *user = NULL, *authid = NULL; + sasl_secret_t *password = NULL; + unsigned int free_password = 0; /* set if we need to free password */ + int user_result = SASL_OK; + int auth_result = SASL_OK; + int pass_result = SASL_OK; + int result; + char *p; + + *clientout = NULL; + *clientoutlen = 0; + + /* doesn't really matter how the server responds */ + + /* check if sec layer strong enough */ + if (params->props.min_ssf > params->external_ssf) { + SETERROR( params->utils, "SSF requested of PLAIN plugin"); + return SASL_TOOWEAK; + } + + /* try to get the authid */ + if (oparams->authid == NULL) { + auth_result = _plug_get_authid(params->utils, &authid, prompt_need); + + if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT)) + return auth_result; + } + + /* try to get the userid */ + if (oparams->user == NULL) { + user_result = _plug_get_userid(params->utils, &user, prompt_need); + + if ((user_result != SASL_OK) && (user_result != SASL_INTERACT)) + return user_result; + } + + /* try to get the password */ + if (password == NULL) { + pass_result = _plug_get_password(params->utils, &password, + &free_password, prompt_need); + + if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT)) + return pass_result; + } + + /* free prompts we got */ + if (prompt_need && *prompt_need) { + params->utils->free(*prompt_need); + *prompt_need = NULL; + } + + /* if there are prompts not filled in */ + if ((user_result == SASL_INTERACT) || (auth_result == SASL_INTERACT) || + (pass_result == SASL_INTERACT)) { + /* make the prompt list */ + result = + _plug_make_prompts(params->utils, prompt_need, + user_result == SASL_INTERACT ? + "Please enter your authorization name" : NULL, + NULL, + auth_result == SASL_INTERACT ? + "Please enter your authentication name" : NULL, + NULL, + pass_result == SASL_INTERACT ? + "Please enter your password" : NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL); + if (result != SASL_OK) goto cleanup; + + return SASL_INTERACT; + } + + if (!password) { + PARAMERROR(params->utils); + return SASL_BADPARAM; + } + + if (!user || !*user) { + result = params->canon_user(params->utils->conn, authid, 0, + SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); + } + else { + result = params->canon_user(params->utils->conn, user, 0, + SASL_CU_AUTHZID, oparams); + if (result != SASL_OK) goto cleanup; + + result = params->canon_user(params->utils->conn, authid, 0, + SASL_CU_AUTHID, oparams); + } + if (result != SASL_OK) goto cleanup; + + /* send authorized id NUL authentication id NUL password */ + *clientoutlen = ((user && *user ? oparams->ulen : 0) + + 1 + oparams->alen + + 1 + password->len); + + /* remember the extra NUL on the end for stupid clients */ + result = _plug_buf_alloc(params->utils, &(text->out_buf), + &(text->out_buf_len), *clientoutlen + 1); + if (result != SASL_OK) goto cleanup; + + memset(text->out_buf, 0, *clientoutlen + 1); + p = text->out_buf; + if (user && *user) { + memcpy(p, oparams->user, oparams->ulen); + p += oparams->ulen; + } + memcpy(++p, oparams->authid, oparams->alen); + p += oparams->alen; + memcpy(++p, password->data, password->len); + + *clientout = text->out_buf; + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + result = SASL_OK; + + cleanup: + /* free sensitive info */ + if (free_password) _plug_free_secret(params->utils, &password); + + return result; +} + +static void plain_client_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + client_context_t *text = (client_context_t *) conn_context; + + if (!text) return; + + if (text->out_buf) utils->free(text->out_buf); + + utils->free(text); +} + +static sasl_client_plug_t plain_client_plugins[] = +{ + { + "PLAIN", /* mech_name */ + 0, /* max_ssf */ + SASL_SEC_NOANONYMOUS + | SASL_SEC_PASS_CREDENTIALS, /* security_flags */ + SASL_FEAT_WANT_CLIENT_FIRST + | SASL_FEAT_ALLOWS_PROXY, /* features */ + NULL, /* required_prompts */ + NULL, /* glob_context */ + &plain_client_mech_new, /* mech_new */ + &plain_client_mech_step, /* mech_step */ + &plain_client_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* idle */ + NULL, /* spare */ + NULL /* spare */ + } +}; + +int plain_client_plug_init(sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_client_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_CLIENT_PLUG_VERSION) { + SETERROR(utils, "PLAIN version mismatch"); + return SASL_BADVERS; + } + + *out_version = SASL_CLIENT_PLUG_VERSION; + *pluglist = plain_client_plugins; + *plugcount = 1; + + return SASL_OK; +} diff --git a/contrib/libs/sasl/plugins/sasldb.c b/contrib/libs/sasl/plugins/sasldb.c new file mode 100644 index 00000000000..f4e5412b171 --- /dev/null +++ b/contrib/libs/sasl/plugins/sasldb.c @@ -0,0 +1,317 @@ +/* SASL server API implementation + * Rob Siemborski + * Tim Martin + */ +/* + * Copyright (c) 1998-2016 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <config.h> + +/* sasldb stuff */ + +#include <stdio.h> + +#include "sasl.h" +#include "saslutil.h" +#include "saslplug.h" +#include "../sasldb/sasldb.h" + +#include "plugin_common.h" + +static int sasldb_auxprop_lookup(void *glob_context __attribute__((unused)), + sasl_server_params_t *sparams, + unsigned flags, + const char *user, + unsigned ulen) +{ + char *userid = NULL; + char *realm = NULL; + const char *user_realm = NULL; + int ret; + const struct propval *to_fetch, *cur; + char value[8192]; + size_t value_len; + char *user_buf; + int verify_against_hashed_password; + int saw_user_password = 0; + + if (!sparams || !user) return SASL_BADPARAM; + + user_buf = sparams->utils->malloc(ulen + 1); + if(!user_buf) { + ret = SASL_NOMEM; + goto done; + } + + memcpy(user_buf, user, ulen); + user_buf[ulen] = '\0'; + + if(sparams->user_realm) { + user_realm = sparams->user_realm; + } else { + user_realm = sparams->serverFQDN; + } + + ret = _plug_parseuser(sparams->utils, &userid, &realm, user_realm, + sparams->serverFQDN, user_buf); + if(ret != SASL_OK) goto done; + + to_fetch = sparams->utils->prop_get(sparams->propctx); + if (!to_fetch) { + ret = SASL_NOMEM; + goto done; + } + + verify_against_hashed_password = flags & SASL_AUXPROP_VERIFY_AGAINST_HASH; + + /* Use a fake value to signal that we have no property to lookup */ + ret = SASL_CONTINUE; + for(cur = to_fetch; cur->name; cur++) { + int cur_ret; + const char *realname = cur->name; + + /* Only look up properties that apply to this lookup! */ + if(cur->name[0] == '*' && (flags & SASL_AUXPROP_AUTHZID)) continue; + if(!(flags & SASL_AUXPROP_AUTHZID)) { + if(cur->name[0] != '*') continue; + else realname = cur->name + 1; + } + + /* If it's there already, we want to see if it needs to be + * overridden. userPassword is a special case, because it's value + is always present if SASL_AUXPROP_VERIFY_AGAINST_HASH is specified. + When SASL_AUXPROP_VERIFY_AGAINST_HASH is set, we just clear userPassword. */ + if (cur->values && !(flags & SASL_AUXPROP_OVERRIDE) && + (verify_against_hashed_password == 0 || + strcasecmp(realname, SASL_AUX_PASSWORD_PROP) != 0)) { + continue; + } else if (cur->values) { + sparams->utils->prop_erase(sparams->propctx, cur->name); + } + + if (strcasecmp(realname, SASL_AUX_PASSWORD_PROP) == 0) { + saw_user_password = 1; + } + + cur_ret = _sasldb_getdata(sparams->utils, + sparams->utils->conn, userid, realm, + realname, value, sizeof(value), &value_len); + + /* Assumption: cur_ret is never SASL_CONTINUE */ + + /* If this is the first property we've tried to fetch ==> + always set the global error code. + If we had SASL_NOUSER ==> any other error code overrides it + (including SASL_NOUSER). */ + if (ret == SASL_CONTINUE || ret == SASL_NOUSER) { + ret = cur_ret; + } else if (ret == SASL_OK) { + /* Any error code other than SASL_NOUSER overrides SASL_OK. + (And SASL_OK overrides SASL_OK as well) */ + if (cur_ret != SASL_NOUSER) { + ret = cur_ret; + } + } + /* Any other global error code is left as is */ + + if (cur_ret != SASL_OK) { + if (cur_ret != SASL_NOUSER) { + /* No point in continuing if we hit any serious error */ + break; + } + /* We didn't find it, leave it as not found */ + continue; + } + + sparams->utils->prop_set(sparams->propctx, cur->name, + value, (unsigned) value_len); + } + + /* [Keep in sync with LDAPDB, SQL] + If ret is SASL_CONTINUE, it means that no properties were requested + (or maybe some were requested, but they already have values and + SASL_AUXPROP_OVERRIDE flag is not set). + Always return SASL_OK in this case. */ + if (ret == SASL_CONTINUE) { + ret = SASL_OK; + } + + if (flags & SASL_AUXPROP_AUTHZID) { + /* This is a lie, but the caller can't handle + when we return SASL_NOUSER for authorization identity lookup. */ + if (ret == SASL_NOUSER) { + ret = SASL_OK; + } + } else { + if (ret == SASL_NOUSER && saw_user_password == 0) { + /* Verify user existence by checking presence of + the userPassword attribute */ + ret = _sasldb_getdata(sparams->utils, + sparams->utils->conn, + userid, + realm, + SASL_AUX_PASSWORD_PROP, + value, + sizeof(value), + &value_len); + } + } + + done: + if (userid) sparams->utils->free(userid); + if (realm) sparams->utils->free(realm); + if (user_buf) sparams->utils->free(user_buf); + + return ret; +} + +static int sasldb_auxprop_store(void *glob_context __attribute__((unused)), + sasl_server_params_t *sparams, + struct propctx *ctx, + const char *user, + unsigned ulen) +{ + char *userid = NULL; + char *realm = NULL; + const char *user_realm = NULL; + int ret = SASL_FAIL; + const struct propval *to_store, *cur; + char *user_buf; + + /* just checking if we are enabled */ + if(!ctx) return SASL_OK; + + if(!sparams || !user) return SASL_BADPARAM; + + user_buf = sparams->utils->malloc(ulen + 1); + if(!user_buf) { + ret = SASL_NOMEM; + goto done; + } + + memcpy(user_buf, user, ulen); + user_buf[ulen] = '\0'; + + if(sparams->user_realm) { + user_realm = sparams->user_realm; + } else { + user_realm = sparams->serverFQDN; + } + + ret = _plug_parseuser(sparams->utils, &userid, &realm, user_realm, + sparams->serverFQDN, user_buf); + if(ret != SASL_OK) goto done; + + to_store = sparams->utils->prop_get(ctx); + if(!to_store) { + ret = SASL_BADPARAM; + goto done; + } + + ret = SASL_OK; + for (cur = to_store; cur->name; cur++) { + const char *value = (cur->values && cur->values[0]) ? cur->values[0] : NULL; + + if (cur->name[0] == '*') { + continue; + } + + /* WARN: We only support one value right now. */ + ret = _sasldb_putdata(sparams->utils, + sparams->utils->conn, + userid, + realm, + cur->name, + value, + value ? strlen(value) : 0); + + if (value == NULL && ret == SASL_NOUSER) { + /* Deleting something which is not there is not an error */ + ret = SASL_OK; + } + + if (ret != SASL_OK) { + /* We've already failed, no point in continuing */ + break; + } + } + + done: + if (userid) sparams->utils->free(userid); + if (realm) sparams->utils->free(realm); + if (user_buf) sparams->utils->free(user_buf); + + return ret; +} + +static sasl_auxprop_plug_t sasldb_auxprop_plugin = { + 0, /* Features */ + 0, /* spare */ + NULL, /* glob_context */ + sasldb_auxprop_free, /* auxprop_free */ + sasldb_auxprop_lookup, /* auxprop_lookup */ + "sasldb", /* name */ + sasldb_auxprop_store /* auxprop_store */ +}; + +int sasldb_auxprop_plug_init(const sasl_utils_t *utils, + int max_version, + int *out_version, + sasl_auxprop_plug_t **plug, + const char *plugname __attribute__((unused))) +{ + if(!out_version || !plug) return SASL_BADPARAM; + + /* Do we have database support? */ + /* Note that we can use a NULL sasl_conn_t because our + * sasl_utils_t is "blessed" with the global callbacks */ + if(_sasl_check_db(utils, NULL) != SASL_OK) + return SASL_NOMECH; + + /* Check if libsasl API is older than ours. If it is, fail */ + if(max_version < SASL_AUXPROP_PLUG_VERSION) return SASL_BADVERS; + + *out_version = SASL_AUXPROP_PLUG_VERSION; + + *plug = &sasldb_auxprop_plugin; + + return SASL_OK; +} diff --git a/contrib/libs/sasl/plugins/scram.c b/contrib/libs/sasl/plugins/scram.c new file mode 100644 index 00000000000..36f61086b89 --- /dev/null +++ b/contrib/libs/sasl/plugins/scram.c @@ -0,0 +1,3087 @@ +/* SCRAM-SHA-1/SHA-2 SASL plugin + * Alexey Melnikov + */ +/* + * Copyright (c) 2009-2016 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Carnegie Mellon University + * Center for Technology Transfer and Enterprise Creation + * 4615 Forbes Avenue + * Suite 302 + * Pittsburgh, PA 15213 + * (412) 268-7393, fax: (412) 268-7395 + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <config.h> + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#ifndef macintosh +#include <sys/stat.h> +#endif +#include <fcntl.h> +#include <errno.h> + +#include <sasl.h> +#include <saslplug.h> +#include <saslutil.h> + +#include "plugin_common.h" + +#ifdef macintosh +#error #include <sasl_scram_plugin_decl.h> +#endif + +#include <openssl/sha.h> +#include <openssl/evp.h> +#include <openssl/hmac.h> + +/***************************** Common Section *****************************/ + +#define NONCE_SIZE (32) /* arbitrary */ +#define SALT_SIZE (16) /* arbitrary */ + +/* TODO: make this a configurable option? */ +#define DEFAULT_ITERATION_COUNTER 4096 +#define MIN_ITERATION_COUNTER 4096 + +#define MAX_ITERATION_COUNTER 0x10000 + +/* maximum length of the iteration_counter (as a string). Assume it is 32bits */ +#define ITERATION_COUNTER_BUF_LEN 20 + +#define BASE64_LEN(size) (((size) / 3 * 4) + (((size) % 3) ? 4 : 0)) + +#define MAX_CLIENTIN_LEN 2048 +#define MAX_SERVERIN_LEN 2048 + +#define STRINGIZE(x) #x +#define MAX_CLIENTIN_LEN_STR STRINGIZE((MAX_CLIENTIN_LEN)) +#define MAX_SERVERIN_LEN_STR STRINGIZE((MAX_SERVERIN_LEN)) + +#define CLIENT_KEY_CONSTANT "Client Key" +#define SERVER_KEY_CONSTANT "Server Key" +#define CLIENT_KEY_CONSTANT_LEN sizeof(CLIENT_KEY_CONSTANT)-1 +#define SERVER_KEY_CONSTANT_LEN sizeof(SERVER_KEY_CONSTANT)-1 + +#define SCRAM_CB_FLAG_MASK 0x0F +#define SCRAM_CB_FLAG_N 0x00 +#define SCRAM_CB_FLAG_P 0x01 +#define SCRAM_CB_FLAG_Y 0x02 + +#ifdef SCRAM_DEBUG +#define PRINT_HASH(func,hash,size) print_hash(func,hash,size) +#else +#define PRINT_HASH(func,hash,size) +#endif + +/* NB: A temporary mapping for "internal errors". It would be better to add + a new SASL error code for that */ +#define SASL_SCRAM_INTERNAL SASL_NOMEM + + +/* Holds the core salt to avoid regenerating salt each auth. */ +static unsigned char g_salt_key[SALT_SIZE]; + +/* Note that currently only SHA-* variants are supported! */ +static const char * +scram_sasl_mech_name(size_t hash_size) +{ + switch (hash_size) { + case 64: + return "SCRAM-SHA-512"; + + case 48: + return "SCRAM-SHA-384"; + + case 32: + return "SCRAM-SHA-256"; + + case 28: + return "SCRAM-SHA-224"; + + case 20: + return "SCRAM-SHA-1"; + } + + return NULL; +} + +/* Convert saslname = 1*(value-safe-char / "=2C" / "=3D") in place. + Returns SASL_FAIL if the encoding is invalid, otherwise SASL_OK */ +static int +decode_saslname (char *buf) +{ + char * inp; + char * outp; + + inp = outp = buf; + + while (*inp) { + if (*inp == '=') { + inp++; + if (*inp == '\0') { + return SASL_FAIL; + } + if (inp[0] == '2' && inp[1] == 'C') { + *outp = ','; + inp += 2; + } else if (inp[0] == '3' && inp[1] == 'D') { + *outp = '='; + inp += 2; + } else { + return SASL_FAIL; + } + } else { + *outp = *inp; + inp++; + } + outp++; + } + + *outp = '\0'; + + return SASL_OK; +} + +/* Convert a username to saslname = 1*(value-safe-char / "=2C" / "=3D") + and return an allocated copy. + "freeme" contains pointer to the allocated output, or NULL, + if encoded_saslname just points to saslname. + Returns SASL_NOMEM if can't allocate memory for the output, otherwise SASL_OK */ +static int +encode_saslname (const char *saslname, + const char **encoded_saslname, + char **freeme) +{ + const char * inp; + char * outp; + int special_chars = 0; + + /* Found out if anything needs encoding */ + for (inp = saslname; *inp; inp++) { + if (*inp == ',' || *inp == '=') { + special_chars++; + } + } + + if (special_chars == 0) { + *encoded_saslname = saslname; + *freeme = NULL; + return SASL_OK; + } + + outp = malloc(strlen(saslname) + special_chars * 2 + 1); + *encoded_saslname = outp; + *freeme = outp; + if (outp == NULL) { + return SASL_NOMEM; + } + + for (inp = saslname; *inp; inp++) { + switch (*inp) { + case ',': + *outp++ = '='; + *outp++ = '2'; + *outp++ = 'C'; + break; + + case '=': + *outp++ = '='; + *outp++ = '3'; + *outp++ = 'D'; + break; + + default: + *outp++ = *inp; + } + } + + *outp = '\0'; + + return SASL_OK; +} + +static char * +create_nonce(const sasl_utils_t * utils, + char *buffer, + size_t buflen) /* Including the terminating NUL */ +{ + char *intbuf; + unsigned int estimated; + + if ((buflen - 1) % 4 != 0) { + /* NB: the algorithm below doesn't work for such length. + It needs to be adjusted to allocate + 4 bytes, + encode the last 4 bytes to a separate buffer and + then copy the necessary number of bytes to the end of the output */ + return NULL; + } + + estimated = (unsigned int)((buflen - 1) / 4 * 3); + intbuf = (char *) utils->malloc(estimated + 1); + if (intbuf == NULL) { + return NULL; + } + + utils->rand(utils->rpool, intbuf, estimated); + + /* base 64 encode it so it has valid chars */ + if (utils->encode64(intbuf, + estimated, + buffer, + (unsigned int)buflen, + NULL) != SASL_OK) { + utils->free(intbuf); + return NULL; + } + + utils->free(intbuf); + + buffer[buflen-1] = '\0'; + + return buffer; +} + +#ifdef SCRAM_DEBUG +/* Useful for debugging interop issues */ +static void +print_hash (const char * func, const char * hash, size_t hash_size) +{ + int i; + + printf (" HASH in %s:", func); + for (i = 0; i < hash_size; i++) { + printf (" %.2X", (unsigned char)hash[i]); + } + printf ("\n"); +} +#endif + + +/* The result variable need to point to a buffer big enough for the [SHA-*] hash */ +static void +Hi (const sasl_utils_t * utils, + const EVP_MD *md, + const char * str, + size_t str_len, + const char * salt, + size_t salt_len, + unsigned int iteration_count, + char * result) +{ + char * initial_key = NULL; + unsigned int i; + char * temp_result; + unsigned int hash_len = 0; + size_t k, hash_size = EVP_MD_size(md); + + initial_key = utils->malloc(salt_len + 4); + memcpy (initial_key, salt, salt_len); + initial_key[salt_len] = 0; + initial_key[salt_len+1] = 0; + initial_key[salt_len+2] = 0; + initial_key[salt_len+3] = 1; + + temp_result = utils->malloc(hash_size); + + /* U1 := HMAC(str, salt || INT(1)) */ + + if (HMAC(md, + (const unsigned char *) str, + (int)str_len, + (const unsigned char *) initial_key, + (int)salt_len + 4, + (unsigned char *)result, + &hash_len) == NULL) { + } + + memcpy(temp_result, result, hash_size); + + PRINT_HASH ("first HMAC in Hi()", temp_result, hash_size); + + /* On each loop iteration j "temp_result" contains Uj, + while "result" contains "U1 XOR ... XOR Uj" */ + for (i = 2; i <= iteration_count; i++) { + if (HMAC(md, + (const unsigned char *) str, + (int)str_len, + (const unsigned char *) temp_result, + hash_size, + (unsigned char *)temp_result, + &hash_len) == NULL) { + } + + PRINT_HASH ("Hi() HMAC inside loop", temp_result, hash_size); + + for (k = 0; k < hash_size; k++) { + result[k] ^= temp_result[k]; + } + + PRINT_HASH ("Hi() - accumulated result inside loop", result, hash_size); + } + + utils->free(initial_key); + utils->free(temp_result); +} + +/** + * User salt is Hi(username,salt_key); + * This is fixed per reboot, to allow caching of SCRAM + * SaltedPassword. + */ +static unsigned char * +scram_server_user_salt(const sasl_utils_t * utils, + const EVP_MD *md, + const char * username, + size_t * p_salt_len) +{ + size_t hash_size = EVP_MD_size(md); + char * result = utils->malloc(hash_size); + Hi(utils, md, username, strlen(username), (const char *) g_salt_key, SALT_SIZE, + 20 /* iterations */, result); + *p_salt_len = hash_size; + return (unsigned char *) result; +} + +static int +GenerateScramSecrets (const sasl_utils_t * utils, + const EVP_MD *md, + const char * password, + size_t password_len, + char * salt, + size_t salt_len, + unsigned int iteration_count, + char * StoredKey, + char * ServerKey, + char ** error_text) +{ + char SaltedPassword[EVP_MAX_MD_SIZE]; + char ClientKey[EVP_MAX_MD_SIZE]; + sasl_secret_t *sec = NULL; + unsigned int hash_len = 0; + int result; + size_t hash_size = EVP_MD_size(md); + + *error_text = NULL; + + if (password_len == 0) { + *error_text = "empty secret"; + result = SASL_FAIL; + goto cleanup; + } + + sec = utils->malloc(sizeof(sasl_secret_t) + password_len); + if (sec == NULL) { + result = SASL_NOMEM; + goto cleanup; + } + + sec->len = (unsigned) password_len; + strncpy((char *)sec->data, password, password_len + 1); + + /* SaltedPassword := Hi(password, salt) */ + Hi (utils, + md, + (const char *) sec->data, + sec->len, + salt, + salt_len, + iteration_count, + SaltedPassword); + + /* ClientKey := HMAC(SaltedPassword, "Client Key") */ + if (HMAC(md, + (const unsigned char *) SaltedPassword, + hash_size, + (const unsigned char *) CLIENT_KEY_CONSTANT, + CLIENT_KEY_CONSTANT_LEN, + (unsigned char *)ClientKey, + &hash_len) == NULL) { + *error_text = "HMAC call failed"; + result = SASL_SCRAM_INTERNAL; + goto cleanup; + } + + /* StoredKey := H(ClientKey) */ + if (EVP_Digest((const unsigned char *) ClientKey, hash_size, + (unsigned char *) StoredKey, NULL, md, NULL) == 0) { + *error_text = "Digest call failed"; + result = SASL_SCRAM_INTERNAL; + goto cleanup; + + } + + /* ServerKey := HMAC(SaltedPassword, "Server Key") */ + if (HMAC(md, + (const unsigned char *) SaltedPassword, + hash_size, + (const unsigned char *) SERVER_KEY_CONSTANT, + SERVER_KEY_CONSTANT_LEN, + (unsigned char *)ServerKey, + &hash_len) == NULL) { + *error_text = "HMAC call failed"; + result = SASL_SCRAM_INTERNAL; + goto cleanup; + } + + result = SASL_OK; + +cleanup: + if (sec) { + _plug_free_secret(utils, &sec); + } + return result; +} + +/***************************** Server Section *****************************/ + +typedef struct server_context { + int state; + + const EVP_MD *md; /* underlying MDA */ + + char * authentication_id; + char * authorization_id; + + char * out_buf; + unsigned out_buf_len; + char * auth_message; + size_t auth_message_len; + char * nonce; + /* in binary form */ + char * salt; + size_t salt_len; + unsigned int iteration_count; + char StoredKey[EVP_MAX_MD_SIZE + 1]; + char ServerKey[EVP_MAX_MD_SIZE + 1]; + + int cb_flags; + char *cbindingname; + char *gs2_header; + size_t gs2_header_length; +} server_context_t; + +static int +scram_server_mech_new(void *glob_context, + sasl_server_params_t *sparams, + const char *challenge __attribute__((unused)), + unsigned challen __attribute__((unused)), + void **conn_context) +{ + server_context_t *text; + + /* holds state are in */ + text = sparams->utils->malloc(sizeof(server_context_t)); + if (text == NULL) { + MEMERROR( sparams->utils ); + return SASL_NOMEM; + } + + memset(text, 0, sizeof(server_context_t)); + /* text->state = 0; */ + + text->md = EVP_get_digestbyname((const char *) glob_context); + + *conn_context = text; + + return SASL_OK; +} + +static int +scram_server_mech_step1(server_context_t *text, + sasl_server_params_t *sparams, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams __attribute__((unused))) +{ + char * authorization_id; + char * authentication_id; + char * p; + char * nonce; + size_t client_nonce_len; + char * base64_salt = NULL; + size_t base64len; + size_t estimated_challenge_len; + size_t pure_scram_length; + char * inbuf = NULL; + const char *password_request[] = { SASL_AUX_PASSWORD, + "*authPassword", + NULL }; + int canon_flags; + struct propval auxprop_values[3]; + int result; + size_t hash_size = EVP_MD_size(text->md); + const char *scram_sasl_mech = scram_sasl_mech_name(hash_size); + + if (clientinlen == 0) { + sparams->utils->seterror(sparams->utils->conn, 0, + "%s input expected", scram_sasl_mech); + return SASL_BADPROT; + } + + /* Expecting: 'gs2-cbind-flag "," [ authzid ] "," [reserved-mext ","] + username "," nonce ["," extensions]' */ + + if (clientinlen < 10) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid %s input", scram_sasl_mech); + return SASL_BADPROT; + } + + inbuf = sparams->utils->malloc (clientinlen + 1); + + if (inbuf == NULL) { + MEMERROR( sparams->utils ); + return SASL_NOMEM; + } + + memcpy(inbuf, clientin, clientinlen); + inbuf[clientinlen] = 0; + + if (strlen(inbuf) != clientinlen) { + sparams->utils->seterror(sparams->utils->conn, 0, + "NULs found in %s input", scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + p = inbuf; + + /* gs2-cbind-flag = "p=" cb-name / "n" / "y" + ;; "n" -> client doesn't support channel binding + ;; "y" -> client does support channel binding + ;; but thinks the server does not. + ;; "p" -> client requires channel binding. + ;; The selected channel binding follows "p=". */ + switch (p[0]) { + case 'p': + if (p[1] != '=') { + sparams->utils->seterror(sparams->utils->conn, 0, + "The initial 'p' needs to be followed by '=' in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + p++; + + text->cbindingname = p + 1; + p = strchr (p, ','); + if (p == NULL) { + text->cbindingname = NULL; + + sparams->utils->seterror(sparams->utils->conn, 0, + "Channel binding name must be terminated by a comma in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + *p = '\0'; + _plug_strdup(sparams->utils, text->cbindingname, &text->cbindingname, NULL); + *p = ','; + + text->cb_flags = SCRAM_CB_FLAG_P; + break; + + case 'n': + text->cb_flags = SCRAM_CB_FLAG_N; + /* We always have at least 10 bytes, so this is safe */ + p++; + break; + + case 'y': + text->cb_flags = SCRAM_CB_FLAG_Y; + /* We always have at least 10 bytes, so this is safe */ + p++; + break; + + default: + sparams->utils->seterror(sparams->utils->conn, 0, + "The initial %s client response needs to start with 'y', 'n' or 'p'", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + if (p[0] != ',') { + sparams->utils->seterror(sparams->utils->conn, 0, + "',' expected in %s input", scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + p++; + + if (p[0] == 'a' && p[1] == '=') { + authorization_id = p + 2; + + p = strchr (authorization_id, ','); + if (p == NULL) { + sparams->utils->seterror(sparams->utils->conn, 0, + "At least nonce is expected in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + /* End of the GS2 header */ + p[0] = '\0'; + /* The GS2 header length DOES include the terminating comma */ + text->gs2_header_length = p - inbuf + 1; + + p++; + + /* Make a read-write copy we can modify */ + _plug_strdup(sparams->utils, authorization_id, &text->authorization_id, NULL); + + if (decode_saslname(text->authorization_id) != SASL_OK) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid authorization identity encoding in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + } else if (p[0] != ',') { + sparams->utils->seterror(sparams->utils->conn, 0, + "',' expected in %s input", scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } else { + /* End of the GS2 header */ + p[0] = '\0'; + /* The GS2 header length DOES include the terminating comma */ + text->gs2_header_length = p - inbuf + 1; + + p++; + } + + text->gs2_header = sparams->utils->malloc (text->gs2_header_length + 1); + if (text->gs2_header == NULL) { + MEMERROR( sparams->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + memcpy(text->gs2_header, inbuf, text->gs2_header_length - 1); + /* Remember the comma */ + text->gs2_header[text->gs2_header_length - 1] = ','; + text->gs2_header[text->gs2_header_length] = 0; + + + + if (p[1] != '=') { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid %s input", scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + if (p[0] == 'm') { + sparams->utils->seterror(sparams->utils->conn, 0, + "Unsupported mandatory extension to %s", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + if (p[0] != 'n') { + sparams->utils->seterror(sparams->utils->conn, 0, + "Username (n=) expected in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + authentication_id = p + 2; + p = strchr (authentication_id, ','); + + /* MUST be followed by a nonce */ + if (p == NULL) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Nonce expected after the username in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + *p = '\0'; + p++; + + if (decode_saslname(authentication_id) != SASL_OK) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid username encoding in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + _plug_strdup(sparams->utils, authentication_id, &text->authentication_id, NULL); + + if (strncmp(p, "r=", 2) != 0) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Nonce expected after the username in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + p += 2; + nonce = p; + p = strchr (nonce, ','); + + if (p == NULL) { + p = nonce + strlen(nonce); + } else { + *p = '\0'; + } + + /* Generate server nonce, by appending some random stuff to the client nonce */ + client_nonce_len = strlen(nonce); + text->nonce = sparams->utils->malloc (client_nonce_len + NONCE_SIZE + 1); + + if (text->nonce == NULL) { + MEMERROR( sparams->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + strcpy (text->nonce, nonce); + + if (create_nonce(sparams->utils, + text->nonce + client_nonce_len, + NONCE_SIZE + 1) == NULL) { + MEMERROR( sparams->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + + + /* Now we fetch user's password and calculate our secret */ + result = sparams->utils->prop_request(sparams->propctx, password_request); + if (result != SASL_OK) { + goto cleanup; + } + + /* this will trigger the getting of the aux properties */ + canon_flags = SASL_CU_AUTHID; + if (text->authorization_id == NULL || *text->authorization_id == '\0') { + canon_flags |= SASL_CU_AUTHZID; + } + + result = sparams->canon_user(sparams->utils->conn, + text->authentication_id, + 0, + canon_flags, + oparams); + if (result != SASL_OK) { + SETERROR(sparams->utils, "unable to canonify user and get auxprops"); + goto cleanup; + } + + if (text->authorization_id != NULL && *text->authorization_id != '\0') { + result = sparams->canon_user(sparams->utils->conn, + text->authorization_id, + 0, + SASL_CU_AUTHZID, + oparams); + } + if (result != SASL_OK) { + SETERROR(sparams->utils, "unable to canonify authorization ID"); + goto cleanup; + } + + result = sparams->utils->prop_getnames(sparams->propctx, + password_request, + auxprop_values); + if (result < 0 || + ((!auxprop_values[0].name || !auxprop_values[0].values) && + (!auxprop_values[1].name || !auxprop_values[1].values))) { + /* We didn't find this username */ + sparams->utils->seterror(sparams->utils->conn,0, + "no secret in database"); + result = sparams->transition ? SASL_TRANS : SASL_NOUSER; + goto cleanup; + } + + if (auxprop_values[0].name && auxprop_values[0].values) { + char * error_text = NULL; + char * s_iteration_count; + char * end; + + text->salt = (char *) scram_server_user_salt(sparams->utils, text->md, text->authentication_id, &text->salt_len); + + sparams->utils->getopt(sparams->utils->getopt_context, + /* Different SCRAM hashes can have different strengh */ + scram_sasl_mech, + "scram_iteration_counter", + (const char **) &s_iteration_count, + NULL); + + if (s_iteration_count != NULL) { + errno = 0; + text->iteration_count = strtoul(s_iteration_count, &end, 10); + if (s_iteration_count == end || *end != '\0' || errno != 0) { + sparams->utils->log(NULL, + SASL_LOG_DEBUG, + "Invalid iteration-count in scram_iteration_count SASL option: not a number. Using the default instead."); + s_iteration_count = NULL; + } + } + + if (s_iteration_count == NULL) { + text->iteration_count = DEFAULT_ITERATION_COUNTER; + } + + result = GenerateScramSecrets (sparams->utils, + text->md, + auxprop_values[0].values[0], + strlen(auxprop_values[0].values[0]), + text->salt, + text->salt_len, + text->iteration_count, + text->StoredKey, + text->ServerKey, + &error_text); + if (result != SASL_OK) { + if (error_text != NULL) { + sparams->utils->seterror(sparams->utils->conn, 0, "%s", + error_text); + } + goto cleanup; + } + + } else if (auxprop_values[1].name && auxprop_values[1].values) { + char s_iteration_count[ITERATION_COUNTER_BUF_LEN+1]; + size_t base64_salt_len; + unsigned int exact_key_len; + const char * scram_hash; + const char * p_field; + char * end; + int i; + size_t scram_sasl_mech_len = strlen(scram_sasl_mech); + + result = SASL_SCRAM_INTERNAL; + + for (i = 0; auxprop_values[1].values[i] != NULL; i++) { + scram_hash = auxprop_values[1].values[i]; + + /* Skip the leading spaces */ + while (*scram_hash == ' ') { + scram_hash++; + } + + if (strncmp(scram_hash, scram_sasl_mech, scram_sasl_mech_len) != 0) { + continue; + } + scram_hash += scram_sasl_mech_len; + + /* Skip spaces */ + while (*scram_hash == ' ') { + scram_hash++; + } + + if (*scram_hash != '$') { + /* syntax error, ignore the value */ + continue; + } + scram_hash++; + + /* Skip spaces */ + while (*scram_hash == ' ') { + scram_hash++; + } + + p_field = strchr(scram_hash, ':'); + if (p_field == NULL || p_field == scram_hash) { + /* syntax error, ignore the value */ + continue; + } + + if ((p_field - scram_hash) > ITERATION_COUNTER_BUF_LEN) { + /* The iteration counter is too big for us */ + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid iteration-count in %s input: the value is too big", + scram_sasl_mech); + continue; + } + + memcpy(s_iteration_count, scram_hash, p_field - scram_hash); + s_iteration_count[p_field - scram_hash] = '\0'; + + errno = 0; + text->iteration_count = strtoul(s_iteration_count, &end, 10); + if (s_iteration_count == end || *end != '\0' || errno != 0) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid iteration-count in %s input: not a number", + scram_sasl_mech); + continue; + } + + scram_hash = p_field + 1; + + p_field = scram_hash + strcspn(scram_hash, "$ "); + if (p_field == scram_hash || *p_field == '\0') { + /* syntax error, ignore the value */ + continue; + } + + base64_salt_len = p_field - scram_hash; + text->salt = (char *) sparams->utils->malloc(base64_salt_len); + if (sparams->utils->decode64(scram_hash, + (unsigned int)base64_salt_len, + text->salt, + (unsigned int)base64_salt_len, + (unsigned int *) &text->salt_len) != SASL_OK) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid base64 encoding of the salt in %s stored value", + scram_sasl_mech); + continue; + } + + scram_hash = p_field; + + /* Skip spaces */ + while (*scram_hash == ' ') { + scram_hash++; + } + + if (*scram_hash != '$') { + /* syntax error, ignore the value */ + sparams->utils->free(text->salt); + text->salt = NULL; + continue; + } + scram_hash++; + + /* Skip spaces */ + while (*scram_hash == ' ') { + scram_hash++; + } + + p_field = strchr(scram_hash, ':'); + if (p_field == NULL || p_field == scram_hash) { + /* syntax error, ignore the value */ + sparams->utils->free(text->salt); + text->salt = NULL; + continue; + } + + if (sparams->utils->decode64(scram_hash, + (unsigned int)(p_field - scram_hash), + text->StoredKey, + hash_size + 1, + &exact_key_len) != SASL_OK) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid base64 encoding of StoredKey in %s per-user storage", + scram_sasl_mech); + sparams->utils->free(text->salt); + text->salt = NULL; + continue; + } + + if (exact_key_len != hash_size) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid StoredKey in %s per-user storage", + scram_sasl_mech); + sparams->utils->free(text->salt); + text->salt = NULL; + continue; + } + + scram_hash = p_field + 1; + + p_field = strchr(scram_hash, ' '); + if (p_field == NULL) { + p_field = scram_hash + strlen(scram_hash); + } + + + if (sparams->utils->decode64(scram_hash, + (unsigned int)(p_field - scram_hash), + text->ServerKey, + hash_size + 1, + &exact_key_len) != SASL_OK) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid base64 encoding of ServerKey in %s per-user storage", + scram_sasl_mech); + sparams->utils->free(text->salt); + text->salt = NULL; + continue; + } + + if (exact_key_len != hash_size) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid ServerKey in %s per-user storage", scram_sasl_mech); + sparams->utils->free(text->salt); + text->salt = NULL; + continue; + } + + result = SASL_OK; + break; + } + + if (result != SASL_OK) { + sparams->utils->seterror(sparams->utils->conn, + 0, "No valid %s secret found", + scram_sasl_mech); + goto cleanup; + } + + } else { + sparams->utils->seterror(sparams->utils->conn, + 0, + "Have neither type of secret"); + return SASL_FAIL; + } + + /* erase the plaintext password */ + sparams->utils->prop_erase(sparams->propctx, password_request[0]); + + + + /* base 64 encode it so it has valid chars */ + base64len = (text->salt_len / 3 * 4) + ((text->salt_len % 3) ? 4 : 0); + + base64_salt = (char *) sparams->utils->malloc(base64len + 1); + if (base64_salt == NULL) { + MEMERROR( sparams->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + /* + * Returns SASL_OK on success, SASL_BUFOVER if result won't fit + */ + if (sparams->utils->encode64(text->salt, + (unsigned int)text->salt_len, + base64_salt, + (unsigned int)base64len + 1, + NULL) != SASL_OK) { + MEMERROR( sparams->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + base64_salt[base64len] = '\0'; + + /* Now we generate server challenge */ + estimated_challenge_len = client_nonce_len + NONCE_SIZE + + base64len + + ITERATION_COUNTER_BUF_LEN + + strlen("r=,s=,i="); + result = _plug_buf_alloc(sparams->utils, + &(text->out_buf), + &(text->out_buf_len), + (unsigned) estimated_challenge_len + 1); + if (result != SASL_OK) { + MEMERROR( sparams->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + sprintf(text->out_buf, + "r=%s,s=%s,i=%u", + text->nonce, + base64_salt, + text->iteration_count); + + + /* Save the (client response, ",", server challenge, ","). + Note, we skip the GS2 prefix here */ + pure_scram_length = clientinlen - text->gs2_header_length; + text->auth_message_len = pure_scram_length + 1 + estimated_challenge_len + 1; + text->auth_message = sparams->utils->malloc (text->auth_message_len + 1); + if (text->auth_message == NULL) { + MEMERROR( sparams->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + memcpy(text->auth_message, clientin + text->gs2_header_length, pure_scram_length); + text->auth_message[pure_scram_length] = ','; + strcpy (text->auth_message + pure_scram_length + 1, text->out_buf); + strcat (text->auth_message + pure_scram_length + 1, ","); + + /* Now remember the exact length, not the estimated one */ + text->auth_message_len = strlen(text->auth_message); + + *serverout = text->out_buf; + *serveroutlen = (unsigned) strlen(text->out_buf); + + result = SASL_CONTINUE; + text->state = 2; + +cleanup: + if (inbuf != NULL) { + sparams->utils->free(inbuf); + } + if (base64_salt != NULL) { + sparams->utils->free(base64_salt); + } + return result; +} + +static int +scram_server_mech_step2(server_context_t *text, + sasl_server_params_t *sparams, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + char *channel_binding = NULL; + size_t channel_binding_len = 0; + char *binary_channel_binding = NULL; + unsigned binary_channel_binding_len = 0; + char *client_proof = NULL; + char *inbuf = NULL; + char *p; + int result = SASL_FAIL; + size_t proof_offset; + char * full_auth_message; + char ReceivedClientKey[EVP_MAX_MD_SIZE]; + char DecodedClientProof[EVP_MAX_MD_SIZE + 1]; + char CalculatedStoredKey[EVP_MAX_MD_SIZE]; + char ClientSignature[EVP_MAX_MD_SIZE]; + char ServerSignature[EVP_MAX_MD_SIZE]; + char * nonce; + size_t client_proof_len; + size_t server_proof_len; + unsigned exact_client_proof_len; + unsigned int hash_len = 0; + size_t k, hash_size = EVP_MD_size(text->md); + const char *scram_sasl_mech = scram_sasl_mech_name(hash_size); + + if (clientinlen == 0) { + sparams->utils->seterror(sparams->utils->conn, 0, + "%s input expected", scram_sasl_mech); + return SASL_BADPROT; + } + + if (clientinlen < 3 || clientin[1] != '=') { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid %s input", scram_sasl_mech); + return SASL_BADPROT; + } + + inbuf = sparams->utils->malloc (clientinlen + 1); + + if (inbuf == NULL) { + MEMERROR( sparams->utils ); + return SASL_NOMEM; + } + + memcpy(inbuf, clientin, clientinlen); + inbuf[clientinlen] = 0; + + if (strlen(inbuf) != clientinlen) { + sparams->utils->seterror(sparams->utils->conn, 0, + "NULs found in %s input", scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + /* Expecting: channel-binding "," nonce ["," extensions] "," proof */ + + p = inbuf; + + if (strncmp(p, "c=", 2) != 0) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Channel binding expected in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + channel_binding = p + 2; + + p = strchr (channel_binding, ','); + if (p == NULL) { + sparams->utils->seterror(sparams->utils->conn, 0, + "At least nonce is expected in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + *p = '\0'; + p++; + + channel_binding_len = strlen(channel_binding); + + /* We can calculate the exact length, but the decoded (binary) data + is always shorter than its base64 version. */ + binary_channel_binding = (char *) sparams->utils->malloc(channel_binding_len + 1); + + if (sparams->utils->decode64(channel_binding, + (unsigned int)channel_binding_len, + binary_channel_binding, + (unsigned int)channel_binding_len, + &binary_channel_binding_len) != SASL_OK) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid base64 encoding of the channel bindings in %s", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + if (binary_channel_binding_len < text->gs2_header_length || + strncmp(binary_channel_binding, text->gs2_header, text->gs2_header_length) != 0) { + sparams->utils->seterror (sparams->utils->conn, + 0, + "Channel bindings prefix doesn't match the one received in the GS2 header of %s. Expected \"%s\"", + scram_sasl_mech, text->gs2_header); + result = SASL_BADPROT; + goto cleanup; + } + + switch (text->cb_flags & SCRAM_CB_FLAG_MASK) { + case SCRAM_CB_FLAG_P: + binary_channel_binding_len -= (unsigned)text->gs2_header_length; + if (binary_channel_binding_len == 0) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Channel bindings data expected in %s", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + if (sparams->cbinding == NULL) { + sparams->utils->seterror (sparams->utils->conn, + 0, + "Server does not support channel binding type received in %s. Received: %s", + scram_sasl_mech, + text->cbindingname); + result = SASL_BADPROT; + goto cleanup; + } + + if (strcmp(sparams->cbinding->name, text->cbindingname) != 0) { + sparams->utils->seterror (sparams->utils->conn, + 0, + "Unsupported channel bindings type received in %s. Expected: %s, received: %s", + scram_sasl_mech, + sparams->cbinding->name, + text->cbindingname); + result = SASL_BADPROT; + goto cleanup; + } + + if (binary_channel_binding_len != sparams->cbinding->len) { + sparams->utils->seterror (sparams->utils->conn, + 0, + "Unsupported channel bindings length received in %s. Expected length: %lu, received: %d", + scram_sasl_mech, + sparams->cbinding->len, + binary_channel_binding_len); + result = SASL_BADPROT; + goto cleanup; + } + + if (memcmp(binary_channel_binding + text->gs2_header_length, + sparams->cbinding->data, + binary_channel_binding_len) != 0) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Channel bindings mismatch in %s", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + break; + } + + if (strncmp(p, "r=", 2) != 0) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Nonce expected in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + nonce = p + 2; + + p = strchr (nonce, ','); + if (p == NULL) { + sparams->utils->seterror(sparams->utils->conn, 0, + "At least proof is expected in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + *p = '\0'; + p++; + + if (strcmp(nonce, text->nonce) != 0) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Nonce mismatch %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + while (p[0] != '\0') { + if (strncmp(p, "p=", 2) == 0) { + client_proof = p + 2; + proof_offset = p - inbuf - 1; + break; + } + + p = strchr (p, ','); + if (p == NULL) { + break; + } + p++; + } + + if (client_proof == NULL) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Client proof is expected in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + /* Check that no extension data exists after the proof */ + p = strchr (client_proof, ','); + if (p != NULL) { + sparams->utils->seterror(sparams->utils->conn, 0, + "No extension data is allowed after the client proof in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + if (strlen(client_proof) != (hash_size / 3 * 4 + (hash_size % 3 ? 4 : 0))) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid client proof length in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + /* Construct the full AuthMessage */ + full_auth_message = sparams->utils->realloc(text->auth_message, + text->auth_message_len + proof_offset + 1); + if (full_auth_message == NULL) { + MEMERROR( sparams->utils ); + result = SASL_NOMEM; + goto cleanup; + } + text->auth_message = full_auth_message; + + memcpy(text->auth_message + text->auth_message_len, clientin, proof_offset); + + text->auth_message_len += proof_offset; + text->auth_message[text->auth_message_len] = '\0'; + + + /* ClientSignature := HMAC(StoredKey, AuthMessage) */ + if (HMAC(text->md, + (const unsigned char *) text->StoredKey, + hash_size, + (const unsigned char *)text->auth_message, + (int)text->auth_message_len, + (unsigned char *)ClientSignature, + &hash_len) == NULL) { + sparams->utils->seterror(sparams->utils->conn, 0, + "HMAC-%s call failed", scram_sasl_mech+6); + result = SASL_SCRAM_INTERNAL; + goto cleanup; + } + + client_proof_len = strlen(client_proof); + if (sparams->utils->decode64(client_proof, + (unsigned int)client_proof_len, + DecodedClientProof, + hash_size + 1, + &exact_client_proof_len) != SASL_OK) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid base64 encoding of the client proof in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + if (exact_client_proof_len != hash_size) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Invalid client proof (truncated) in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + for (k = 0; k < hash_size; k++) { + ReceivedClientKey[k] = DecodedClientProof[k] ^ ClientSignature[k]; + } + + /* StoredKey := H(ClientKey) */ + if (EVP_Digest((const unsigned char *) ReceivedClientKey, hash_size, + (unsigned char *) CalculatedStoredKey, NULL, text->md, NULL) == 0) { + sparams->utils->seterror(sparams->utils->conn,0, + "%s call failed", scram_sasl_mech+6); + result = SASL_SCRAM_INTERNAL; + goto cleanup; + } + + for (k = 0; k < hash_size; k++) { + if (CalculatedStoredKey[k] != text->StoredKey[k]) { + SETERROR(sparams->utils, "StoredKey mismatch"); + result = SASL_BADAUTH; + goto cleanup; + } + } + + /* ServerSignature := HMAC(ServerKey, AuthMessage) */ + if (HMAC(text->md, + (const unsigned char *) text->ServerKey, + hash_size, + (unsigned char *) text->auth_message, + (int)text->auth_message_len, + (unsigned char *)ServerSignature, + &hash_len) == NULL) { + sparams->utils->seterror(sparams->utils->conn,0, + "HMAC-%s call failed", scram_sasl_mech+6); + result = SASL_SCRAM_INTERNAL; + goto cleanup; + } + + server_proof_len = (hash_size / 3 * 4 + (hash_size % 3 ? 4 : 0)); + result = _plug_buf_alloc(sparams->utils, + &(text->out_buf), + &(text->out_buf_len), + (unsigned) server_proof_len + strlen("v=") + 1); + if (result != SASL_OK) { + MEMERROR( sparams->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + text->out_buf[0] = 'v'; + text->out_buf[1] = '='; + + + if (sparams->utils->encode64(ServerSignature, + hash_size, + text->out_buf+2, + (unsigned int)server_proof_len + 1, + NULL) != SASL_OK) { + SETERROR(sparams->utils, "Internal error"); + /* This is not quite right, but better than alternatives */ + result = SASL_NOMEM; + goto cleanup; + } + + text->out_buf[server_proof_len + 2] = '\0'; + + *serverout = text->out_buf; + *serveroutlen = (unsigned) strlen(text->out_buf); + + + /* set oparams */ + + switch (text->cb_flags & SCRAM_CB_FLAG_MASK) { + case SCRAM_CB_FLAG_N: + oparams->cbindingdisp = SASL_CB_DISP_NONE; + break; + case SCRAM_CB_FLAG_P: + oparams->cbindingdisp = SASL_CB_DISP_USED; + oparams->cbindingname = text->cbindingname; + break; + case SCRAM_CB_FLAG_Y: + oparams->cbindingdisp = SASL_CB_DISP_WANT; + break; + } + + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + result = SASL_OK; + +cleanup: + if (inbuf != NULL) { + sparams->utils->free(inbuf); + } + if (binary_channel_binding != NULL) { + sparams->utils->free(binary_channel_binding); + } + + return result; +} + +static int scram_server_mech_step(void *conn_context, + sasl_server_params_t *sparams, + const char *clientin, + unsigned clientinlen, + const char **serverout, + unsigned *serveroutlen, + sasl_out_params_t *oparams) +{ + server_context_t *text = (server_context_t *) conn_context; + const char *scram_sasl_mech = NULL; + + *serverout = NULL; + *serveroutlen = 0; + + if (text == NULL) { + return SASL_BADPROT; + } + + scram_sasl_mech = scram_sasl_mech_name(EVP_MD_size(text->md)); + + /* this should be well more than is ever needed */ + if (clientinlen > MAX_CLIENTIN_LEN) { + sparams->utils->seterror(sparams->utils->conn, 0, + "%s input longer than " + STRINGIZE((MAX_CLIENTIN_LEN)) " bytes", + scram_sasl_mech); + return SASL_BADPROT; + } + + switch (text->state) { + case 0: + text->state++; + /* Assume the protocol doesn't support initial client response */ + if (clientinlen == 0) { + return SASL_CONTINUE; + } + /* fall through */ + + case 1: + return scram_server_mech_step1(text, + sparams, + clientin, + clientinlen, + serverout, + serveroutlen, + oparams); + + case 2: + text->state++; + return scram_server_mech_step2(text, + sparams, + clientin, + clientinlen, + serverout, + serveroutlen, + oparams); + + default: /* should never get here */ + sparams->utils->log(NULL, SASL_LOG_ERR, + "Invalid %s server step %d\n", + scram_sasl_mech, text->state); + return SASL_FAIL; + } + + return SASL_FAIL; /* should never get here */ +} + +static int scram_setpass(void *glob_context, + sasl_server_params_t *sparams, + const char *userstr, + const char *pass, + unsigned passlen, + const char *oldpass __attribute__((unused)), + unsigned oldpasslen __attribute__((unused)), + unsigned flags) +{ + int r; + char *user = NULL; + char *user_only = NULL; + char *realm = NULL; + sasl_secret_t *sec = NULL; + struct propctx *propctx = NULL; + const char *store_request[] = { "authPassword", + NULL }; + const char *generate_scram_secret; + const EVP_MD *md = EVP_get_digestbyname((const char *) glob_context); + size_t hash_size = EVP_MD_size(md); + const char *scram_sasl_mech = scram_sasl_mech_name(hash_size); + + /* Do we have a backend that can store properties? */ + if (!sparams->utils->auxprop_store || + sparams->utils->auxprop_store(NULL, NULL, NULL) != SASL_OK) { + sparams->utils->seterror(sparams->utils->conn, 0, + "%s: auxprop backend can't store properties", + scram_sasl_mech); + return SASL_NOMECH; + } + + sparams->utils->getopt(sparams->utils->getopt_context, + /* This affects all SCRAM plugins, not just SCRAM-SHA-1 */ + "SCRAM", + "scram_secret_generate", + &generate_scram_secret, + NULL); + + /* NOTE: The default (when this option is not set) is NOT to generate authPassword secret */ + if (!(generate_scram_secret && + (generate_scram_secret[0] == '1' || generate_scram_secret[0] == 'y' || + (generate_scram_secret[0] == 'o' && generate_scram_secret[1] == 'n') || + generate_scram_secret[0] == 't'))) { + /* Pretend that everything is Ok, no need to generate noise in the logs */ + return SASL_OK; + } + + r = _plug_parseuser(sparams->utils, + &user_only, + &realm, + sparams->user_realm, + sparams->serverFQDN, + userstr); + if (r) { + sparams->utils->seterror(sparams->utils->conn, 0, + "%s: Error parsing user", scram_sasl_mech); + return r; + } + + r = _plug_make_fulluser(sparams->utils, &user, user_only, realm); + if (r) { + goto cleanup; + } + + if ((flags & SASL_SET_DISABLE) || pass == NULL) { + sec = NULL; + } else { + char * error_text = NULL; + char salt[SALT_SIZE + 1]; + char base64_salt[BASE64_LEN(SALT_SIZE) + 1]; + /* size_t salt_len = SALT_SIZE; */ + char StoredKey[EVP_MAX_MD_SIZE + 1]; + char ServerKey[EVP_MAX_MD_SIZE + 1]; + char base64_StoredKey[BASE64_LEN(EVP_MAX_MD_SIZE) + 1]; + char base64_ServerKey[BASE64_LEN(EVP_MAX_MD_SIZE) + 1]; + size_t secret_len; + unsigned int iteration_count = DEFAULT_ITERATION_COUNTER; + char * s_iteration_count; + char * end; + + sparams->utils->getopt(sparams->utils->getopt_context, + /* Different SCRAM hashes can have different strengh */ + scram_sasl_mech, + "scram_iteration_counter", + (const char **) &s_iteration_count, + NULL); + + if (s_iteration_count != NULL) { + errno = 0; + iteration_count = strtoul(s_iteration_count, &end, 10); + if (s_iteration_count == end || *end != '\0' || errno != 0) { + sparams->utils->log(NULL, + SASL_LOG_DEBUG, + "Invalid iteration-count in scram_iteration_count SASL option: not a number. Using the default instead."); + s_iteration_count = NULL; + } + } + + if (s_iteration_count == NULL) { + iteration_count = DEFAULT_ITERATION_COUNTER; + } + + sparams->utils->rand(sparams->utils->rpool, salt, SALT_SIZE); + + r = GenerateScramSecrets (sparams->utils, + md, + pass, + passlen, + salt, + SALT_SIZE, + iteration_count, + StoredKey, + ServerKey, + &error_text); + if (r != SASL_OK) { + if (error_text != NULL) { + sparams->utils->seterror(sparams->utils->conn, 0, "%s", + error_text); + } + goto cleanup; + } + + /* Returns SASL_OK on success, SASL_BUFOVER if result won't fit */ + if (sparams->utils->encode64(salt, + SALT_SIZE, + base64_salt, + BASE64_LEN(SALT_SIZE) + 1, + NULL) != SASL_OK) { + MEMERROR( sparams->utils ); + r = SASL_NOMEM; + goto cleanup; + } + + base64_salt[BASE64_LEN(SALT_SIZE)] = '\0'; + + + /* Returns SASL_OK on success, SASL_BUFOVER if result won't fit */ + if (sparams->utils->encode64(StoredKey, + hash_size, + base64_StoredKey, + BASE64_LEN(hash_size) + 1, + NULL) != SASL_OK) { + MEMERROR( sparams->utils ); + r = SASL_NOMEM; + goto cleanup; + } + + base64_StoredKey[BASE64_LEN(hash_size)] = '\0'; + + + + /* Returns SASL_OK on success, SASL_BUFOVER if result won't fit */ + if (sparams->utils->encode64(ServerKey, + hash_size, + base64_ServerKey, + BASE64_LEN(hash_size) + 1, + NULL) != SASL_OK) { + MEMERROR( sparams->utils ); + r = SASL_NOMEM; + goto cleanup; + } + + base64_ServerKey[BASE64_LEN(hash_size)] = '\0'; + + secret_len = strlen(scram_sasl_mech) + strlen("$:$:") + + ITERATION_COUNTER_BUF_LEN + + sizeof(base64_salt) + + sizeof(base64_StoredKey) + + sizeof(base64_ServerKey); + + sec = sparams->utils->malloc(sizeof(sasl_secret_t) + secret_len); + if (sec == NULL) { + MEMERROR( sparams->utils ); + r = SASL_NOMEM; + goto cleanup; + } + + sprintf((char *) sec->data, + "%s$%u:%s$%s:%s", + scram_sasl_mech, + iteration_count, + base64_salt, + base64_StoredKey, + base64_ServerKey); + sec->len = (unsigned int) strlen((const char *) sec->data); + } + + /* do the store */ + propctx = sparams->utils->prop_new(0); + if (!propctx) { + r = SASL_FAIL; + } + if (!r) { + r = sparams->utils->prop_request(propctx, store_request); + } + if (!r) { + r = sparams->utils->prop_set(propctx, + "authPassword", + (const char *) (sec ? sec->data : NULL), + (sec ? sec->len : 0)); + } + if (!r) { + r = sparams->utils->auxprop_store(sparams->utils->conn, propctx, user); + } + if (propctx) { + sparams->utils->prop_dispose(&propctx); + } + + if (r) { + sparams->utils->seterror(sparams->utils->conn, 0, + "Error putting %s secret", + scram_sasl_mech); + goto cleanup; + } + + sparams->utils->log(NULL, SASL_LOG_DEBUG, "Setpass for %s successful\n", + scram_sasl_mech); + + cleanup: + if (user) _plug_free_string(sparams->utils, &user); + if (user_only) _plug_free_string(sparams->utils, &user_only); + if (realm) _plug_free_string(sparams->utils, &realm); + if (sec) _plug_free_secret(sparams->utils, &sec); + + return r; +} + +static void scram_server_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + server_context_t *text = (server_context_t *) conn_context; + + if (!text) return; + + if (text->authentication_id) _plug_free_string(utils,&(text->authentication_id)); + if (text->authorization_id) _plug_free_string(utils,&(text->authorization_id)); + if (text->out_buf) _plug_free_string(utils,&(text->out_buf)); + if (text->auth_message) _plug_free_string(utils,&(text->auth_message)); + if (text->nonce) _plug_free_string(utils,&(text->nonce)); + if (text->salt) utils->free(text->salt); + if (text->cbindingname != NULL) { + utils->free(text->cbindingname); + text->cbindingname = NULL; + } + if (text->gs2_header != NULL) { + utils->free(text->gs2_header); + text->gs2_header = NULL; + } + + utils->free(text); +} + +static sasl_server_plug_t scram_server_plugins[] = +{ +#ifdef HAVE_SHA512 + { + "SCRAM-SHA-512", /* mech_name */ + 0, /* max_ssf */ + SASL_SET_HASH_STRENGTH_BITS(512) | + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOACTIVE + | SASL_SEC_NOANONYMOUS + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP + | SASL_FEAT_CHANNEL_BINDING, /* features */ + "SHA512", /* glob_context */ + &scram_server_mech_new, /* mech_new */ + &scram_server_mech_step, /* mech_step */ + &scram_server_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + &scram_setpass, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + NULL, /* mech avail */ + NULL /* spare */ + }, + { + "SCRAM-SHA-384", /* mech_name */ + 0, /* max_ssf */ + SASL_SET_HASH_STRENGTH_BITS(384) | + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOACTIVE + | SASL_SEC_NOANONYMOUS + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP + | SASL_FEAT_CHANNEL_BINDING, /* features */ + "SHA384", /* glob_context */ + &scram_server_mech_new, /* mech_new */ + &scram_server_mech_step, /* mech_step */ + &scram_server_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + &scram_setpass, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + NULL, /* mech avail */ + NULL /* spare */ + }, + { + "SCRAM-SHA-256", /* mech_name */ + 0, /* max_ssf */ + SASL_SET_HASH_STRENGTH_BITS(256) | + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOACTIVE + | SASL_SEC_NOANONYMOUS + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP + | SASL_FEAT_CHANNEL_BINDING, /* features */ + "SHA256", /* glob_context */ + &scram_server_mech_new, /* mech_new */ + &scram_server_mech_step, /* mech_step */ + &scram_server_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + &scram_setpass, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + NULL, /* mech avail */ + NULL /* spare */ + }, + { + "SCRAM-SHA-224", /* mech_name */ + 0, /* max_ssf */ + SASL_SET_HASH_STRENGTH_BITS(224) | + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOACTIVE + | SASL_SEC_NOANONYMOUS + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP + | SASL_FEAT_CHANNEL_BINDING, /* features */ + "SHA224", /* glob_context */ + &scram_server_mech_new, /* mech_new */ + &scram_server_mech_step, /* mech_step */ + &scram_server_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + &scram_setpass, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + NULL, /* mech avail */ + NULL /* spare */ + }, +#endif + { + "SCRAM-SHA-1", /* mech_name */ + 0, /* max_ssf */ + SASL_SET_HASH_STRENGTH_BITS(160) | + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOACTIVE + | SASL_SEC_NOANONYMOUS + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP + | SASL_FEAT_CHANNEL_BINDING, /* features */ + "SHA1", /* glob_context */ + &scram_server_mech_new, /* mech_new */ + &scram_server_mech_step, /* mech_step */ + &scram_server_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + &scram_setpass, /* setpass */ + NULL, /* user_query */ + NULL, /* idle */ + NULL, /* mech avail */ + NULL /* spare */ + } +}; + +int scram_server_plug_init(const sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_server_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_SERVER_PLUG_VERSION) { + SETERROR( utils, "SCRAM-SHA-* version mismatch"); + return SASL_BADVERS; + } + + *out_version = SASL_SERVER_PLUG_VERSION; + *pluglist = scram_server_plugins; +#ifdef HAVE_SHA512 + *plugcount = 5; +#else + *plugcount = 1; +#endif + utils->rand(utils->rpool, (char *)g_salt_key, SALT_SIZE); + + return SASL_OK; +} + +/***************************** Client Section *****************************/ + +typedef struct client_context { + int state; + + const EVP_MD *md; /* underlying MDA */ + + sasl_secret_t *password; /* user password */ + unsigned int free_password; /* set if we need to free the password */ + + char * gs2_header; + size_t gs2_header_length; + char * out_buf; + unsigned out_buf_len; + char * auth_message; + size_t auth_message_len; + char * nonce; + /* in binary form */ + char * salt; + size_t salt_len; + unsigned int iteration_count; + char SaltedPassword[EVP_MAX_MD_SIZE]; + + int cb_flags; +} client_context_t; + +static int scram_client_mech_new(void *glob_context, + sasl_client_params_t *params, + void **conn_context) +{ + client_context_t *text; + + /* holds state are in */ + text = params->utils->malloc(sizeof(client_context_t)); + if (text == NULL) { + MEMERROR(params->utils); + return SASL_NOMEM; + } + + memset(text, 0, sizeof(client_context_t)); + + text->md = EVP_get_digestbyname((const char *) glob_context); + + *conn_context = text; + + return SASL_OK; +} + +static int +scram_client_mech_step1(client_context_t *text, + sasl_client_params_t *params, + const char *serverin __attribute__((unused)), + unsigned serverinlen __attribute__((unused)), + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + const char *authid = NULL; + const char *userid = NULL; + int user_result = SASL_OK; + int auth_result = SASL_OK; + int pass_result = SASL_OK; + int result; + size_t maxsize; + char * encoded_authcid; + char * freeme = NULL; + char * freeme2 = NULL; + char channel_binding_state = 'n'; + const char * channel_binding_name = NULL; + char * encoded_authorization_id = NULL; + const char *scram_sasl_mech = scram_sasl_mech_name(EVP_MD_size(text->md)); + + /* check if sec layer strong enough */ + if (params->props.min_ssf > params->external_ssf) { + params->utils->seterror(params->utils->conn, 0, + "SSF requested of %s plugin", + scram_sasl_mech); + return SASL_TOOWEAK; + } + + /* try to get the userid */ + if (oparams->authid == NULL) { + auth_result=_plug_get_authid(params->utils, &authid, prompt_need); + + if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT)) + return auth_result; + } + + /* try to get the userid */ + if (oparams->user == NULL) { + user_result = _plug_get_userid(params->utils, &userid, prompt_need); + + if ((user_result != SASL_OK) && (user_result != SASL_INTERACT)) { + return user_result; + } + } + + /* try to get the password */ + if (text->password == NULL) { + pass_result = _plug_get_password(params->utils, + &text->password, + &text->free_password, + prompt_need); + if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT)) { + return pass_result; + } + } + + /* free prompts we got */ + if (prompt_need && *prompt_need) { + params->utils->free(*prompt_need); + *prompt_need = NULL; + } + + /* if there are prompts not filled in */ + if ((auth_result == SASL_INTERACT) || + (user_result == SASL_INTERACT) || + (pass_result == SASL_INTERACT)) { + /* make the prompt list */ + result = + _plug_make_prompts(params->utils, + prompt_need, + user_result == SASL_INTERACT ? + "Please enter your authorization name" : NULL, + NULL, + auth_result == SASL_INTERACT ? + "Please enter your authentication name" : NULL, + NULL, + pass_result == SASL_INTERACT ? + "Please enter your password" : NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL); + + if (result != SASL_OK) { + goto cleanup; + } + + return SASL_INTERACT; + } + + if (!text->password) { + PARAMERROR(params->utils); + return SASL_BADPARAM; + } + + if (oparams->authid == NULL) { + if (!userid || !*userid) { + result = params->canon_user(params->utils->conn, + authid, + 0, + SASL_CU_AUTHID | SASL_CU_AUTHZID, + oparams); + } + else { + result = params->canon_user(params->utils->conn, + authid, + 0, + SASL_CU_AUTHID, + oparams); + if (result != SASL_OK) { + goto cleanup; + } + + result = params->canon_user(params->utils->conn, + userid, + 0, + SASL_CU_AUTHZID, + oparams); + } + if (result != SASL_OK) { + goto cleanup; + } + } + + switch (params->cbindingdisp) { + case SASL_CB_DISP_NONE: + text->cb_flags = SCRAM_CB_FLAG_N; + channel_binding_state = 'n'; + break; + case SASL_CB_DISP_USED: + if (!SASL_CB_PRESENT(params)) { + result = SASL_BADPARAM; + goto cleanup; + } + channel_binding_name = params->cbinding->name; + text->cb_flags = SCRAM_CB_FLAG_P; + channel_binding_state = 'p'; + break; + case SASL_CB_DISP_WANT: + text->cb_flags = SCRAM_CB_FLAG_Y; + channel_binding_state = 'y'; + break; + } + + text->nonce = params->utils->malloc (NONCE_SIZE + 1); + + if (text->nonce == NULL) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + if (create_nonce(params->utils, + text->nonce, + NONCE_SIZE + 1) == NULL) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + if (userid != NULL && *userid != '\0') { + result = encode_saslname (oparams->user, + (const char **) &encoded_authorization_id, + &freeme2); + + if (result != SASL_OK) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + } + + result = encode_saslname (oparams->authid, + (const char **) &encoded_authcid, + &freeme); + if (result != SASL_OK) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + maxsize = strlen("p=,a=,n=,r=") + + ((channel_binding_name != NULL) ? strlen(channel_binding_name) : 0) + + ((encoded_authorization_id != NULL) ? strlen(encoded_authorization_id) : 0) + + strlen(encoded_authcid) + + strlen(text->nonce); + result = _plug_buf_alloc(params->utils, + &(text->out_buf), + &(text->out_buf_len), + (unsigned) maxsize + 1); + if (result != SASL_OK) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + snprintf(text->out_buf, + maxsize + 1, + "%c%s%s,%s%s,", + channel_binding_state, + (channel_binding_name != NULL) ? "=" : "", + (channel_binding_name != NULL) ? channel_binding_name : "", + (encoded_authorization_id != NULL) ? "a=" : "", + (encoded_authorization_id != NULL) ? encoded_authorization_id : ""); + + text->gs2_header_length = strlen(text->out_buf); + _plug_strdup(params->utils, text->out_buf, &text->gs2_header, NULL); + + sprintf(text->out_buf + text->gs2_header_length, + "n=%s,r=%s", + encoded_authcid, + text->nonce); + + /* Save the copy of the client-first-message */ + + /* Need to skip the GS2 prefix here */ + _plug_strdup(params->utils, + text->out_buf + text->gs2_header_length, + &text->auth_message, + NULL); + if (text->auth_message == NULL) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + text->auth_message_len = strlen(text->auth_message); + + *clientout = text->out_buf; + *clientoutlen = (unsigned) strlen(*clientout); + + result = SASL_CONTINUE; + +cleanup: + if (freeme != NULL) _plug_free_string(params->utils, &freeme); + if (freeme2 != NULL) _plug_free_string(params->utils, &freeme2); + + return result; +} + +static int +scram_client_mech_step2(client_context_t *text, + sasl_client_params_t *params, + const char *serverin, + unsigned serverinlen, + sasl_interact_t **prompt_need __attribute__((unused)), + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams __attribute__((unused))) +{ + char * p; + char * nonce; + size_t server_nonce_len; + char * base64_salt = NULL; + size_t base64_salt_len; + unsigned exact_salt_len; + char * counter; + char * end; + char * inbuf = NULL; + size_t estimated_response_len; + size_t length_no_proof; + char * full_auth_message; + size_t cb_bin_length; + size_t channel_binding_data_len = 0; + size_t cb_encoded_length; + const char * channel_binding_data = NULL; + char * cb_encoded = NULL; + char * cb_bin = NULL; + int result; + char ClientKey[EVP_MAX_MD_SIZE]; + char StoredKey[EVP_MAX_MD_SIZE]; + char ClientSignature[EVP_MAX_MD_SIZE]; + char ClientProof[EVP_MAX_MD_SIZE]; + char * client_proof = NULL; + size_t client_proof_len; + unsigned int hash_len = 0; + size_t k, hash_size = EVP_MD_size(text->md); + const char *scram_sasl_mech = scram_sasl_mech_name(hash_size); + + if (serverinlen == 0) { + params->utils->seterror(params->utils->conn, 0, + "%s input expected", scram_sasl_mech); + return SASL_BADPROT; + } + + /* [reserved-mext ","] nonce "," salt "," iteration-count ["," extensions] */ + + if (serverinlen < 3 || serverin[1] != '=') { + params->utils->seterror(params->utils->conn, 0, + "Invalid %s input", scram_sasl_mech); + return SASL_BADPROT; + } + + if (serverin[0] == 'm') { + params->utils->seterror(params->utils->conn, 0, + "Unsupported mandatory extension to %s", + scram_sasl_mech); + return SASL_BADPROT; + } + + if (serverin[0] != 'r') { + params->utils->seterror(params->utils->conn, 0, + "Nonce (r=) expected in %s input", + scram_sasl_mech); + return SASL_BADPROT; + } + + inbuf = params->utils->malloc (serverinlen + 1); + if (inbuf == NULL) { + MEMERROR( params->utils ); + return SASL_NOMEM; + } + + memcpy(inbuf, serverin, serverinlen); + inbuf[serverinlen] = 0; + + if (strlen(inbuf) != serverinlen) { + params->utils->seterror(params->utils->conn, 0, + "NULs found in %s input", scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + nonce = inbuf + 2; + p = strchr (nonce, ','); + + /* MUST be followed by a salt */ + if (p == NULL) { + params->utils->seterror(params->utils->conn, 0, + "Salt expected after the nonce in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + *p = '\0'; + p++; + + if (strncmp(p, "s=", 2) != 0) { + params->utils->seterror(params->utils->conn, 0, + "Salt expected after the nonce in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + p += 2; + base64_salt = p; + + p = strchr (base64_salt, ','); + + /* MUST be followed by an iteration-count */ + if (p == NULL) { + params->utils->seterror(params->utils->conn, 0, + "iteration-count expected after the salt in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + *p = '\0'; + p++; + + if (strncmp(p, "i=", 2) != 0) { + params->utils->seterror(params->utils->conn, 0, + "iteration-count expected after the salt in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + p += 2; + counter = p; + p = strchr (counter, ','); + + if (p == NULL) { + p = counter + strlen(counter); + } else { + *p = '\0'; + } + + errno = 0; + text->iteration_count = strtoul(counter, &end, 10); + if (counter == end || *end != '\0' || errno != 0) { + params->utils->seterror(params->utils->conn, 0, + "Invalid iteration-count in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + if (text->iteration_count < MIN_ITERATION_COUNTER) { + } + + if (text->iteration_count > MAX_ITERATION_COUNTER) { + SETERROR(params->utils, "iteration-count is too big, refusing to compute"); + result = SASL_BADPROT; + goto cleanup; + } + + /* The client MUST verify that the initial part of the nonce + used in subsequent messages is the same as the nonce it + initially specified. */ + server_nonce_len = strlen(nonce); + + if (server_nonce_len <= NONCE_SIZE || + strncmp(nonce, text->nonce, NONCE_SIZE) != 0) { + SETERROR(params->utils, "The nonce received from the server doesn't start from the nonce sent by the client"); + result = SASL_BADPROT; + goto cleanup; + } + + /* Now we can forget about our nonce */ + params->utils->free(text->nonce); + + _plug_strdup(params->utils, nonce, &text->nonce, NULL); + + if (text->nonce == NULL) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + /* base64 decode salt */ + base64_salt_len = strlen(base64_salt); + + if (base64_salt_len == 0) { + SETERROR(params->utils, "The salt can't be empty"); + result = SASL_BADPROT; + goto cleanup; + } + + if (base64_salt_len % 4 != 0) { + SETERROR(params->utils, "Invalid base64 encoding of the salt"); + result = SASL_BADPROT; + goto cleanup; + } + + text->salt_len = base64_salt_len / 4 * 3; + + text->salt = (char *) params->utils->malloc(text->salt_len + 1); + if (text->salt == NULL) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + if (params->utils->decode64(base64_salt, + (unsigned int)base64_salt_len, + text->salt, + (unsigned int)text->salt_len + 1, + &exact_salt_len) != SASL_OK) { + params->utils->seterror(params->utils->conn, 0, + "Invalid base64 encoding of the salt in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + text->salt_len = exact_salt_len; + + /* Now we generate client response */ + + if (text->gs2_header[0] == 'p') { + + if (params->cbinding == NULL) { + result = SASL_FAIL; + goto cleanup; + } + + channel_binding_data = (const char *) params->cbinding->data; + channel_binding_data_len = params->cbinding->len; + } + + cb_bin_length = text->gs2_header_length + + ((channel_binding_data != NULL) ? channel_binding_data_len : 0); + cb_encoded_length = (cb_bin_length / 3 * 4) + ((cb_bin_length % 3) ? 4 : 0); + + if (channel_binding_data != NULL) { + cb_bin = (char *) params->utils->malloc(cb_bin_length + 1); + if (cb_bin == NULL) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + memcpy(cb_bin, text->gs2_header, text->gs2_header_length); + memcpy(cb_bin + text->gs2_header_length, channel_binding_data, channel_binding_data_len); + } + + cb_encoded = (char *) params->utils->malloc(cb_encoded_length + 1); + if (cb_encoded == NULL) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + /* + * Returns SASL_OK on success, SASL_BUFOVER if result won't fit + */ + if (params->utils->encode64((cb_bin != NULL) ? cb_bin : text->gs2_header, + (unsigned int)cb_bin_length, + cb_encoded, + (unsigned int)cb_encoded_length + 1, + NULL) != SASL_OK) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + cb_encoded[cb_encoded_length] = '\0'; + + client_proof_len = hash_size / 3 * 4 + ((hash_size % 3) ? 4 : 0); + estimated_response_len = strlen(cb_encoded)+ + strlen(text->nonce)+ + client_proof_len + + strlen("c=,r=,p="); + result = _plug_buf_alloc(params->utils, + &(text->out_buf), + &(text->out_buf_len), + (unsigned) estimated_response_len + 1); + if (result != SASL_OK) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + /* channel-binding "," nonce ["," extensions] */ + sprintf(text->out_buf, + "c=%s,r=%s", + cb_encoded, + text->nonce); + + length_no_proof = strlen(text->out_buf); + + /* Build AuthMessage */ + full_auth_message = params->utils->realloc(text->auth_message, + text->auth_message_len + 1 + + serverinlen + 1 + + length_no_proof + 1); + if (full_auth_message == NULL) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + text->auth_message = full_auth_message; + + text->auth_message[text->auth_message_len] = ','; + memcpy(text->auth_message + text->auth_message_len + 1, serverin, serverinlen); + text->auth_message[text->auth_message_len + 1 + serverinlen] = ','; + memcpy(text->auth_message + text->auth_message_len + 1 + serverinlen + 1, + text->out_buf, + length_no_proof); + text->auth_message_len += serverinlen + 2 + length_no_proof; + text->auth_message[text->auth_message_len] = '\0'; + + /* Calculate ClientProof */ + + /* SaltedPassword := Hi(password, salt) */ + Hi (params->utils, + text->md, + (const char *) text->password->data, + text->password->len, + text->salt, + text->salt_len, + text->iteration_count, + text->SaltedPassword); + + PRINT_HASH ("SaltedPassword", text->SaltedPassword, hash_size); + + /* ClientKey := HMAC(SaltedPassword, "Client Key") */ + if (HMAC(text->md, + (const unsigned char *) text->SaltedPassword, + hash_size, + (const unsigned char *) CLIENT_KEY_CONSTANT, + CLIENT_KEY_CONSTANT_LEN, + (unsigned char *)ClientKey, + &hash_len) == NULL) { + params->utils->seterror(params->utils->conn,0, + "HMAC-%s call failed", scram_sasl_mech+6); + result = SASL_SCRAM_INTERNAL; + goto cleanup; + } + + PRINT_HASH ("ClientKey", ClientKey, hash_size); + + /* StoredKey := H(ClientKey) */ + if (EVP_Digest((const unsigned char *) ClientKey, hash_size, + (unsigned char *) StoredKey, NULL, text->md, NULL) == 0) { + params->utils->seterror(params->utils->conn,0, + "%s call failed", scram_sasl_mech+6); + result = SASL_SCRAM_INTERNAL; + goto cleanup; + } + + PRINT_HASH ("StoredKey", StoredKey, hash_size); + + /* ClientSignature := HMAC(StoredKey, AuthMessage) */ + if (HMAC(text->md, + (const unsigned char *)StoredKey, + hash_size, + (const unsigned char *) text->auth_message, + (int)text->auth_message_len, + (unsigned char *)ClientSignature, + &hash_len) == NULL) { + params->utils->seterror(params->utils->conn,0, + "HMAC-%s call failed", scram_sasl_mech+6); + result = SASL_SCRAM_INTERNAL; + goto cleanup; + } + + PRINT_HASH ("ClientSignature", ClientSignature, hash_size); + + /* ClientProof := ClientKey XOR ClientSignature */ + for (k = 0; k < hash_size; k++) { + ClientProof[k] = ClientKey[k] ^ ClientSignature[k]; + } + + PRINT_HASH ("ClientProof", ClientProof, hash_size); + + /* base64-encode ClientProof */ + client_proof = (char *) params->utils->malloc(client_proof_len + 1); + if (client_proof == NULL) { + MEMERROR( params->utils ); + result = SASL_NOMEM; + goto cleanup; + } + + result = params->utils->encode64(ClientProof, + hash_size, + client_proof, + (unsigned int)client_proof_len + 1, + NULL); + + if (result != SASL_OK) { + goto cleanup; + } + + client_proof[client_proof_len] = '\0'; + + sprintf(text->out_buf + length_no_proof, + ",p=%s", + client_proof); + + *clientout = text->out_buf; + *clientoutlen = (unsigned) strlen(text->out_buf); + + result = SASL_CONTINUE; + +cleanup: + if (inbuf != NULL) { + params->utils->free(inbuf); + } + + if (client_proof != NULL) { + params->utils->free(client_proof); + } + + if (cb_encoded != NULL) { + params->utils->free(cb_encoded); + } + + if (cb_bin != NULL) { + params->utils->free(cb_bin); + } + + return result; +} + + +static int +scram_client_mech_step3(client_context_t *text, + sasl_client_params_t *params, + const char *serverin, + unsigned serverinlen, + sasl_interact_t **prompt_need __attribute__((unused)), + const char **clientout __attribute__((unused)), + unsigned *clientoutlen __attribute__((unused)), + sasl_out_params_t *oparams) +{ + char * p; + int result; + size_t server_proof_len; + unsigned exact_server_proof_len; + char DecodedServerProof[EVP_MAX_MD_SIZE + 1]; + char ServerKey[EVP_MAX_MD_SIZE]; + char ServerSignature[EVP_MAX_MD_SIZE]; + unsigned int hash_len = 0; + size_t k, hash_size = EVP_MD_size(text->md); + const char *scram_sasl_mech = scram_sasl_mech_name(hash_size); + + if (serverinlen < 3) { + params->utils->seterror(params->utils->conn, 0, + "Invalid %s input expected", + scram_sasl_mech); + return SASL_BADPROT; + } + + /* Expecting: 'verifier ["," extensions]' */ + + if (strncmp(serverin, "v=", 2) != 0) { + params->utils->seterror(params->utils->conn, 0, + "ServerSignature expected in %s input", + scram_sasl_mech); + return SASL_BADPROT; + } + + /* Use memchr instead of the original strchr as there is no guarantee that + the input data is NUL terminated */ + p = memchr (serverin + 2, ',', serverinlen - 2); + if (p != NULL) { + server_proof_len = p - (serverin + 2) - 1; + } else { + server_proof_len = serverinlen - 2; + } + + if (params->utils->decode64(serverin + 2, /* ServerProof */ + (unsigned int)server_proof_len, + DecodedServerProof, + hash_size + 1, + &exact_server_proof_len) != SASL_OK) { + params->utils->seterror(params->utils->conn, 0, + "Invalid base64 encoding of the server proof in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + if (exact_server_proof_len != hash_size) { + params->utils->seterror(params->utils->conn, 0, + "Invalid server proof (truncated) in %s input", + scram_sasl_mech); + result = SASL_BADPROT; + goto cleanup; + } + + /* ServerKey := HMAC(SaltedPassword, "Server Key") */ + if (HMAC(text->md, + (const unsigned char *)text->SaltedPassword, + hash_size, + (const unsigned char *) SERVER_KEY_CONSTANT, + SERVER_KEY_CONSTANT_LEN, + (unsigned char *)ServerKey, + &hash_len) == NULL) { + params->utils->seterror(params->utils->conn,0, + "HMAC-%s call failed", scram_sasl_mech+6); + result = SASL_SCRAM_INTERNAL; + goto cleanup; + } + + /* ServerSignature := HMAC(ServerKey, AuthMessage) */ + if (HMAC(text->md, + (const unsigned char *)ServerKey, + hash_size, + (const unsigned char *) text->auth_message, + (int)text->auth_message_len, + (unsigned char *)ServerSignature, + &hash_len) == NULL) { + params->utils->seterror(params->utils->conn,0, + "HMAC-%s call failed", scram_sasl_mech+6); + result = SASL_SCRAM_INTERNAL; + goto cleanup; + } + + for (k = 0; k < hash_size; k++) { + if (DecodedServerProof[k] != ServerSignature[k]) { + SETERROR(params->utils, "ServerSignature mismatch"); + result = SASL_BADAUTH; + goto cleanup; + } + } + + /* set oparams */ + oparams->doneflag = 1; + oparams->mech_ssf = 0; + oparams->maxoutbuf = 0; + oparams->encode_context = NULL; + oparams->encode = NULL; + oparams->decode_context = NULL; + oparams->decode = NULL; + oparams->param_version = 0; + + result = SASL_OK; + +cleanup: + return result; +} + +static int scram_client_mech_step(void *conn_context, + sasl_client_params_t *params, + const char *serverin, + unsigned serverinlen, + sasl_interact_t **prompt_need, + const char **clientout, + unsigned *clientoutlen, + sasl_out_params_t *oparams) +{ + int result = SASL_FAIL; + client_context_t *text = (client_context_t *) conn_context; + const char *scram_sasl_mech = scram_sasl_mech_name(EVP_MD_size(text->md)); + + *clientout = NULL; + *clientoutlen = 0; + + /* this should be well more than is ever needed */ + if (serverinlen > MAX_SERVERIN_LEN) { + params->utils->seterror(params->utils->conn, 0, + "%s input longer than " STRINGIZE((MAX_SERVERIN_LEN)) " bytes", + scram_sasl_mech); + return SASL_BADPROT; + } + + switch (text->state) { + case 0: + result = scram_client_mech_step1(text, + params, + serverin, + serverinlen, + prompt_need, + clientout, + clientoutlen, + oparams); + break; + + case 1: + result = scram_client_mech_step2(text, + params, + serverin, + serverinlen, + prompt_need, + clientout, + clientoutlen, + oparams); + break; + + case 2: + result = scram_client_mech_step3(text, + params, + serverin, + serverinlen, + prompt_need, + clientout, + clientoutlen, + oparams); + break; + + default: /* should never get here */ + params->utils->log(NULL, SASL_LOG_ERR, + "Invalid %s client step %d\n", + scram_sasl_mech, text->state); + return SASL_FAIL; + } + + if (result != SASL_INTERACT) { + text->state++; + } + return result; +} + +static void scram_client_mech_dispose(void *conn_context, + const sasl_utils_t *utils) +{ + client_context_t *text = (client_context_t *) conn_context; + + if (!text) return; + + /* get rid of all sensitive info */ + if (text->free_password) { + _plug_free_secret(utils, &text->password); + text->free_password = 0; + } + + if (text->gs2_header) { + utils->free(text->gs2_header); + text->gs2_header = NULL; + } + + if (text->out_buf) { + utils->free(text->out_buf); + text->out_buf = NULL; + } + + if (text->auth_message) _plug_free_string(utils,&(text->auth_message)); + if (text->nonce) _plug_free_string(utils,&(text->nonce)); + if (text->salt) utils->free(text->salt); + + utils->free(text); +} + +static sasl_client_plug_t scram_client_plugins[] = +{ +#ifdef HAVE_SHA512 + { + "SCRAM-SHA-512", /* mech_name */ + 0, /* max_ssf */ + SASL_SET_HASH_STRENGTH_BITS(512) | + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS + | SASL_SEC_NOACTIVE + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP + | SASL_FEAT_CHANNEL_BINDING, /* features */ + NULL, /* required_prompts */ + "SHA512", /* glob_context */ + &scram_client_mech_new, /* mech_new */ + &scram_client_mech_step, /* mech_step */ + &scram_client_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* idle */ + NULL, /* spare */ + NULL /* spare */ + }, + { + "SCRAM-SHA-384", /* mech_name */ + 0, /* max_ssf */ + SASL_SET_HASH_STRENGTH_BITS(384) | + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS + | SASL_SEC_NOACTIVE + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP + | SASL_FEAT_CHANNEL_BINDING, /* features */ + NULL, /* required_prompts */ + "SHA384", /* glob_context */ + &scram_client_mech_new, /* mech_new */ + &scram_client_mech_step, /* mech_step */ + &scram_client_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* idle */ + NULL, /* spare */ + NULL /* spare */ + }, + { + "SCRAM-SHA-256", /* mech_name */ + 0, /* max_ssf */ + SASL_SET_HASH_STRENGTH_BITS(256) | + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS + | SASL_SEC_NOACTIVE + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP + | SASL_FEAT_CHANNEL_BINDING, /* features */ + NULL, /* required_prompts */ + "SHA256", /* glob_context */ + &scram_client_mech_new, /* mech_new */ + &scram_client_mech_step, /* mech_step */ + &scram_client_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* idle */ + NULL, /* spare */ + NULL /* spare */ + }, + { + "SCRAM-SHA-224", /* mech_name */ + 0, /* max_ssf */ + SASL_SET_HASH_STRENGTH_BITS(224) | + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS + | SASL_SEC_NOACTIVE + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP + | SASL_FEAT_CHANNEL_BINDING, /* features */ + NULL, /* required_prompts */ + "SHA224", /* glob_context */ + &scram_client_mech_new, /* mech_new */ + &scram_client_mech_step, /* mech_step */ + &scram_client_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* idle */ + NULL, /* spare */ + NULL /* spare */ + }, +#endif + { + "SCRAM-SHA-1", /* mech_name */ + 0, /* max_ssf */ + SASL_SET_HASH_STRENGTH_BITS(160) | + SASL_SEC_NOPLAINTEXT + | SASL_SEC_NOANONYMOUS + | SASL_SEC_NOACTIVE + | SASL_SEC_MUTUAL_AUTH, /* security_flags */ + SASL_FEAT_ALLOWS_PROXY + | SASL_FEAT_SUPPORTS_HTTP + | SASL_FEAT_CHANNEL_BINDING, /* features */ + NULL, /* required_prompts */ + "SHA1", /* glob_context */ + &scram_client_mech_new, /* mech_new */ + &scram_client_mech_step, /* mech_step */ + &scram_client_mech_dispose, /* mech_dispose */ + NULL, /* mech_free */ + NULL, /* idle */ + NULL, /* spare */ + NULL /* spare */ + } +}; + +int scram_client_plug_init(const sasl_utils_t *utils, + int maxversion, + int *out_version, + sasl_client_plug_t **pluglist, + int *plugcount) +{ + if (maxversion < SASL_CLIENT_PLUG_VERSION) { + SETERROR( utils, "SCRAM-SHA-* version mismatch"); + return SASL_BADVERS; + } + + *out_version = SASL_CLIENT_PLUG_VERSION; + *pluglist = scram_client_plugins; +#ifdef HAVE_SHA512 + *plugcount = 5; +#else + *plugcount = 1; +#endif + + return SASL_OK; +} |