summaryrefslogtreecommitdiffstats
path: root/contrib/libs/sasl/plugins
diff options
context:
space:
mode:
authormolotkov-and <[email protected]>2023-08-18 17:20:47 +0300
committermolotkov-and <[email protected]>2023-08-18 19:42:07 +0300
commit73215359bc33e76f5b94d1832a377072bf245cfc (patch)
tree9cb8ad61d8c3cd107353d42951560ff3cf1b966d /contrib/libs/sasl/plugins
parent1cbfd34a55732f7b1d407986b45e40853f01f2c2 (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.c387
-rw-r--r--contrib/libs/sasl/plugins/cram.c687
-rw-r--r--contrib/libs/sasl/plugins/digestmd5.c4778
-rw-r--r--contrib/libs/sasl/plugins/otp.c1895
-rw-r--r--contrib/libs/sasl/plugins/otp.h311
-rw-r--r--contrib/libs/sasl/plugins/plain.c489
-rw-r--r--contrib/libs/sasl/plugins/sasldb.c317
-rw-r--r--contrib/libs/sasl/plugins/scram.c3087
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;
+}