3 * $Id: otp.c,v 1.36 2004/06/23 18:43:37 rjs3 Exp $
6 * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
20 * 3. The name "Carnegie Mellon University" must not be used to
21 * endorse or promote products derived from this software without
22 * prior written permission. For permission or any other legal
23 * details, please contact
24 * Office of Technology Transfer
25 * Carnegie Mellon University
27 * Pittsburgh, PA 15213-3890
28 * (412) 268-4387, fax: (412) 268-7395
29 * tech-transfer@andrew.cmu.edu
31 * 4. Redistributions of any form whatsoever must retain the following
33 * "This product includes software developed by Computing Services
34 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
36 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
37 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
38 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
39 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
40 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
41 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
42 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
56 #include <openssl/evp.h>
57 #include <openssl/md5.h> /* XXX hack for OpenBSD/OpenSSL cruftiness */
60 #define MD5_H /* suppress internal MD5 */
63 #include "plugin_common.h"
66 #include <sasl_otp_plugin_decl.h>
69 /***************************** Common Section *****************************/
71 static const char plugin_id[] = "$Id: otp.c,v 1.36 2004/06/23 18:43:37 rjs3 Exp $";
73 #define OTP_SEQUENCE_MAX 9999
74 #define OTP_SEQUENCE_DEFAULT 499
75 #define OTP_SEQUENCE_REINIT 490
76 #define OTP_SEED_MIN 1
77 #define OTP_SEED_MAX 16
78 #define OTP_HASH_SIZE 8 /* 64 bits */
79 #define OTP_CHALLENGE_MAX 100
80 #define OTP_RESPONSE_MAX 100
81 #define OTP_HEX_TYPE "hex:"
82 #define OTP_WORD_TYPE "word:"
83 #define OTP_INIT_HEX_TYPE "init-hex:"
84 #define OTP_INIT_WORD_TYPE "init-word:"
86 typedef struct algorithm_option_s {
87 const char *name; /* name used in challenge/response */
88 int swab; /* number of bytes to swab (0, 1, 2, 4, 8) */
89 const char *evp_name; /* name used for lookup in EVP table */
92 static algorithm_option_t algorithm_options[] = {
99 /* Convert the binary data into ASCII hex */
100 void bin2hex(unsigned char *bin, int binlen, char *hex)
105 for (i = 0; i < binlen; i++) {
106 c = (bin[i] >> 4) & 0xf;
107 hex[i*2] = (c > 9) ? ('a' + c - 10) : ('0' + c);
109 hex[i*2+1] = (c > 9) ? ('a' + c - 10) : ('0' + c);
115 * Hash the data using the given algorithm and fold it into 64 bits,
116 * swabbing bytes if necessary.
118 static void otp_hash(const EVP_MD *md, char *in, int inlen,
119 unsigned char *out, int swab)
122 char hash[EVP_MAX_MD_SIZE];
125 EVP_DigestInit(&mdctx, md);
126 EVP_DigestUpdate(&mdctx, in, inlen);
127 EVP_DigestFinal(&mdctx, hash, &hashlen);
129 /* Fold the result into 64 bits */
130 for (i = OTP_HASH_SIZE; i < hashlen; i++) {
131 hash[i % OTP_HASH_SIZE] ^= hash[i];
136 for (i = 0; i < OTP_HASH_SIZE;) {
137 for (j = swab-1; j > -swab; i++, j-=2)
142 memcpy(out, hash, OTP_HASH_SIZE);
145 static int generate_otp(const sasl_utils_t *utils,
146 algorithm_option_t *alg, unsigned seq, char *seed,
147 char *secret, char *otp)
152 if (!(md = EVP_get_digestbyname(alg->evp_name))) {
153 utils->seterror(utils->conn, 0,
154 "OTP algorithm %s is not available", alg->evp_name);
158 if ((key = utils->malloc(strlen(seed) + strlen(secret) + 1)) == NULL) {
159 SETERROR(utils, "cannot allocate OTP key");
166 otp_hash(md, key, strlen(key), otp, alg->swab);
168 /* computation step */
170 otp_hash(md, otp, OTP_HASH_SIZE, otp, alg->swab);
177 static int parse_challenge(const sasl_utils_t *utils,
178 char *chal, algorithm_option_t **alg,
179 unsigned *seq, char *seed, int is_init)
182 algorithm_option_t *opt;
187 /* eat leading whitespace */
188 while (*c && isspace((int) *c)) c++;
191 /* check the prefix */
192 if (!*c || strncmp(c, "otp-", 4)) {
193 SETERROR(utils, "not a OTP challenge");
197 /* skip the prefix */
201 /* find the algorithm */
202 opt = algorithm_options;
204 if (!strncmp(c, opt->name, strlen(opt->name))) {
210 /* didn't find the algorithm in our list */
212 utils->seterror(utils->conn, 0, "OTP algorithm '%s' not supported", c);
216 /* skip algorithm name */
217 c += strlen(opt->name);
221 if (!isspace((int) *c)) {
222 SETERROR(utils, "no whitespace between OTP algorithm and sequence");
225 while (*c && isspace((int) *c)) c++;
227 /* grab the sequence */
228 if ((*seq = strtoul(c, &c, 10)) > OTP_SEQUENCE_MAX) {
229 utils->seterror(utils->conn, 0, "sequence > %u", OTP_SEQUENCE_MAX);
234 if (!isspace((int) *c)) {
235 SETERROR(utils, "no whitespace between OTP sequence and seed");
238 while (*c && isspace((int) *c)) c++;
240 /* grab the seed, converting to lowercase as we go */
242 while (*c && isalnum((int) *c) && (n < OTP_SEED_MAX))
243 seed[n++] = tolower((int) *c++);
244 if (n > OTP_SEED_MAX) {
245 utils->seterror(utils->conn, 0, "OTP seed length > %u", OTP_SEED_MAX);
248 else if (n < OTP_SEED_MIN) {
249 utils->seterror(utils->conn, 0, "OTP seed length < %u", OTP_SEED_MIN);
256 if (!isspace((int) *c)) {
257 SETERROR(utils, "no whitespace between OTP seed and extensions");
260 while (*c && isspace((int) *c)) c++;
262 /* make sure this is an extended challenge */
263 if (strncmp(c, "ext", 3) ||
265 !(isspace((int) *c) || (*c == ',') ||
266 (*c == '\r') || (*c == '\n')))) {
267 SETERROR(utils, "not an OTP extended challenge");
276 otp_common_mech_free(void *global_context __attribute__((unused)),
277 const sasl_utils_t *utils __attribute__((unused)))
282 /***************************** Server Section *****************************/
288 typedef struct server_context {
292 int locked; /* is the user's secret locked? */
293 algorithm_option_t *alg;
299 char seed[OTP_SEED_MAX+1];
300 unsigned char otp[OTP_HASH_SIZE];
301 time_t timestamp; /* time we locked the secret */
302 #endif /* HAVE_OPIE */
305 unsigned out_buf_len;
308 static int otp_server_mech_new(void *glob_context __attribute__((unused)),
309 sasl_server_params_t *sparams,
310 const char *challenge __attribute__((unused)),
311 unsigned challen __attribute__((unused)),
314 server_context_t *text;
316 /* holds state are in */
317 text = sparams->utils->malloc(sizeof(server_context_t));
319 MEMERROR(sparams->utils);
323 memset(text, 0, sizeof(server_context_t));
327 *conn_context = text;
335 #define OPIE_KEYFILE "/etc/opiekeys"
338 static int opie_server_mech_step(void *conn_context,
339 sasl_server_params_t *params,
340 const char *clientin,
341 unsigned clientinlen,
342 const char **serverout,
343 unsigned *serveroutlen,
344 sasl_out_params_t *oparams)
346 server_context_t *text = (server_context_t *) conn_context;
351 switch (text->state) {
360 /* should have received authzid NUL authid */
364 while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup;
366 if (lup >= clientinlen) {
367 SETERROR(params->utils, "Can only find OTP authzid (no authid)");
373 authid = clientin + lup;
374 while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup;
376 authid_len = clientin + lup - authid;
378 if (lup != clientinlen) {
379 SETERROR(params->utils,
380 "Got more data than we were expecting in the OTP plugin\n");
384 text->authid = params->utils->malloc(authid_len + 1);
385 if (text->authid == NULL) {
386 MEMERROR(params->utils);
390 /* we can't assume that authen is null-terminated */
391 strncpy(text->authid, authid, authid_len);
392 text->authid[authid_len] = '\0';
394 result = params->canon_user(params->utils->conn, text->authid, 0,
395 SASL_CU_AUTHID, oparams);
396 if (result != SASL_OK) return result;
398 result = params->canon_user(params->utils->conn,
399 strlen(authzid) ? authzid : text->authid,
400 0, SASL_CU_AUTHZID, oparams);
401 if (result != SASL_OK) return result;
403 result = _plug_buf_alloc(params->utils, &(text->out_buf),
404 &(text->out_buf_len), OTP_CHALLENGE_MAX+1);
405 if (result != SASL_OK) return result;
407 /* create challenge - return sasl_continue on success */
408 result = opiechallenge(&text->opie, text->authid, text->out_buf);
414 *serverout = text->out_buf;
415 *serveroutlen = strlen(text->out_buf);
418 return SASL_CONTINUE;
421 SETERROR(params->utils, "opiechallenge: user not found or locked");
425 SETERROR(params->utils,
426 "opiechallenge: system error (file, memory, I/O)");
432 char response[OPIE_RESPONSE_MAX+1];
435 /* should have received extended response,
436 but we'll take anything that we can verify */
438 if (clientinlen > OPIE_RESPONSE_MAX) {
439 SETERROR(params->utils, "response too long");
443 /* we can't assume that the response is null-terminated */
444 strncpy(response, clientin, clientinlen);
445 response[clientinlen] = '\0';
447 /* verify response */
448 result = opieverify(&text->opie, response);
454 oparams->doneflag = 1;
455 oparams->mech_ssf = 0;
456 oparams->maxoutbuf = 0;
457 oparams->encode_context = NULL;
458 oparams->encode = NULL;
459 oparams->decode_context = NULL;
460 oparams->decode = NULL;
461 oparams->param_version = 0;
466 SETERROR(params->utils, "opieverify: invalid/incorrect response");
470 SETERROR(params->utils,
471 "opieverify: system error (file, memory, I/O)");
477 params->utils->log(NULL, SASL_LOG_ERR,
478 "Invalid OTP server step %d\n", text->state);
482 return SASL_FAIL; /* should never get here */
485 static void opie_server_mech_dispose(void *conn_context,
486 const sasl_utils_t *utils)
488 server_context_t *text = (server_context_t *) conn_context;
492 /* if we created a challenge, but bailed before the verification of the
493 response, do a verify here to release the lock on the user key */
494 if (text->locked) opieverify(&text->opie, "");
496 if (text->authid) _plug_free_string(utils, &(text->authid));
498 if (text->out_buf) utils->free(text->out_buf);
503 static int opie_mech_avail(void *glob_context __attribute__((unused)),
504 sasl_server_params_t *sparams,
505 void **conn_context __attribute__((unused)))
510 sparams->utils->getopt(sparams->utils->getopt_context,
511 "OTP", "opiekeys", &fname, &len);
513 if (!fname) fname = OPIE_KEYFILE;
515 if (access(fname, R_OK|W_OK) != 0) {
516 sparams->utils->log(NULL, SASL_LOG_ERR,
517 "OTP unavailable because "
518 "can't read/write key database %s: %m",
526 static sasl_server_plug_t otp_server_plugins[] =
532 | SASL_SEC_NOANONYMOUS
533 | SASL_SEC_FORWARD_SECRECY,
534 SASL_FEAT_WANT_CLIENT_FIRST
535 | SASL_FEAT_ALLOWS_PROXY,
537 &otp_server_mech_new,
538 &opie_server_mech_step,
539 &opie_server_mech_dispose,
540 &otp_common_mech_free,
548 #else /* HAVE_OPIE */
552 #define OTP_MDA_DEFAULT "md5"
553 #define OTP_LOCK_TIMEOUT 5 * 60 /* 5 minutes */
555 /* Convert the ASCII hex into binary data */
556 int hex2bin(char *hex, unsigned char *bin, int binlen)
560 unsigned char msn, lsn;
562 memset(bin, 0, binlen);
564 for (c = hex, i = 0; i < binlen; c++) {
566 if (isspace((int) *c))
568 /* end of string, or non-hex char */
569 if (!*c || !*(c+1) || !isxdigit((int) *c))
572 msn = (*c > '9') ? tolower((int) *c) - 'a' + 10 : *c - '0';
574 lsn = (*c > '9') ? tolower((int) *c) - 'a' + 10 : *c - '0';
576 bin[i++] = (unsigned char) (msn << 4) | lsn;
579 return (i < binlen) ? SASL_BADAUTH : SASL_OK;
582 static int make_secret(const sasl_utils_t *utils,
583 const char *alg, unsigned seq, char *seed, char *otp,
584 time_t timeout, sasl_secret_t **secret)
588 char buf[2*OTP_HASH_SIZE+1];
591 * secret is stored as:
593 * <alg> \t <seq> \t <seed> \t <otp> \t <timeout> \0
595 * <timeout> is used as a "lock" when an auth is in progress
596 * we just set it to zero here (no lock)
598 sec_len = strlen(alg)+1+4+1+strlen(seed)+1+2*OTP_HASH_SIZE+1+20+1;
599 *secret = utils->malloc(sizeof(sasl_secret_t)+sec_len);
604 (*secret)->len = sec_len;
605 data = (*secret)->data;
607 bin2hex(otp, OTP_HASH_SIZE, buf);
608 buf[2*OTP_HASH_SIZE] = '\0';
610 sprintf(data, "%s\t%04d\t%s\t%s\t%020ld",
611 alg, seq, seed, buf, timeout);
616 static int parse_secret(const sasl_utils_t *utils,
617 char *secret, size_t seclen,
618 char *alg, unsigned *seq, char *seed,
622 if (strlen(secret) < seclen) {
626 * old-style (binary) secret is stored as:
628 * <alg> \0 <seq> \0 <seed> \0 <otp> <timeout>
632 if (seclen < (3+1+1+1+OTP_SEED_MIN+1+OTP_HASH_SIZE+sizeof(time_t))) {
633 SETERROR(utils, "OTP secret too short");
639 strcpy(alg, (char*) c);
642 *seq = strtoul(c, NULL, 10);
645 strcpy(seed, (char*) c);
648 memcpy(otp, c, OTP_HASH_SIZE);
651 memcpy(timeout, c, sizeof(time_t));
657 char buf[2*OTP_HASH_SIZE+1];
660 * new-style (ASCII) secret is stored as:
662 * <alg> \t <seq> \t <seed> \t <otp> \t <timeout> \0
666 if (seclen < (3+1+1+1+OTP_SEED_MIN+1+2*OTP_HASH_SIZE+1+20)) {
667 SETERROR(utils, "OTP secret too short");
671 sscanf(secret, "%s\t%04d\t%s\t%s\t%020ld",
672 alg, seq, seed, buf, timeout);
674 hex2bin(buf, otp, OTP_HASH_SIZE);
680 /* Compare two string pointers */
681 static int strptrcasecmp(const void *arg1, const void *arg2)
683 return (strcasecmp(*((char**) arg1), *((char**) arg2)));
686 /* Convert the 6 words into binary data */
687 static int word2bin(const sasl_utils_t *utils,
688 char *words, unsigned char *bin, const EVP_MD *md)
691 char *c, *word, buf[OTP_RESPONSE_MAX+1];
695 unsigned char bits[OTP_HASH_SIZE+1]; /* 1 for checksum */
696 unsigned char chksum;
697 int bit, fbyte, lbyte;
698 const char **str_ptr;
701 /* this is a destructive operation, so make a work copy */
705 for (c = buf, bit = 0, i = 0; i < 6; i++, c++, bit+=11) {
706 while (*c && isspace((int) *c)) c++;
708 while (*c && isalpha((int) *c)) c++;
709 if (!*c && i < 5) break;
711 if (strlen(word) < 1 || strlen(word) > 4) {
712 utils->log(NULL, SASL_LOG_DEBUG,
713 "incorrect word length '%s'", word);
717 /* standard dictionary */
719 if (strlen(word) < 4) {
721 nmemb = OTP_4LETTER_OFFSET;
724 base = otp_std_dict + OTP_4LETTER_OFFSET;
725 nmemb = OTP_STD_DICT_SIZE - OTP_4LETTER_OFFSET;
728 str_ptr = (const char**) bsearch((void*) &word, base, nmemb,
732 x = str_ptr - otp_std_dict;
735 /* couldn't find first word, try alternate dictionary */
739 utils->log(NULL, SASL_LOG_DEBUG,
740 "word '%s' not found in dictionary", word);
745 /* alternate dictionary */
748 char hash[EVP_MAX_MD_SIZE];
751 EVP_DigestInit(&mdctx, md);
752 EVP_DigestUpdate(&mdctx, word, strlen(word));
753 EVP_DigestFinal(&mdctx, hash, &hashlen);
755 /* use lowest 11 bits */
756 x = ((hash[hashlen-2] & 0x7) << 8) | hash[hashlen-1];
759 /* left align 11 bits on byte boundary */
760 x <<= (8 - ((bit+11) % 8));
761 /* first output byte containing some of our 11 bits */
763 /* last output byte containing some of our 11 bits */
764 lbyte = (bit+11) / 8;
765 /* populate the output bytes with the 11 bits */
766 for (j = lbyte; j >= fbyte; j--, x >>= 8)
767 bits[j] |= (unsigned char) (x & 0xff);
771 utils->log(NULL, SASL_LOG_DEBUG, "not enough words (%d)", i);
775 /* see if the 2-bit checksum is correct */
776 for (chksum = 0, i = 0; i < 8; i++) {
777 for (j = 0; j < 4; j++) {
778 chksum += ((bits[i] >> (2 * j)) & 0x3);
783 if (chksum != bits[8]) {
784 utils->log(NULL, SASL_LOG_DEBUG, "incorrect parity");
788 memcpy(bin, bits, OTP_HASH_SIZE);
793 static int verify_response(server_context_t *text, const sasl_utils_t *utils,
799 unsigned char cur_otp[OTP_HASH_SIZE], prev_otp[OTP_HASH_SIZE];
803 if (!(md = EVP_get_digestbyname(text->alg->evp_name))) {
804 utils->seterror(utils->conn, 0,
805 "OTP algorithm %s is not available",
806 text->alg->evp_name);
810 /* eat leading whitespace */
812 while (isspace((int) *c)) c++;
814 if (strchr(c, ':')) {
815 if (!strncasecmp(c, OTP_HEX_TYPE, strlen(OTP_HEX_TYPE))) {
816 r = hex2bin(c+strlen(OTP_HEX_TYPE), cur_otp, OTP_HASH_SIZE);
818 else if (!strncasecmp(c, OTP_WORD_TYPE, strlen(OTP_WORD_TYPE))) {
819 r = word2bin(utils, c+strlen(OTP_WORD_TYPE), cur_otp, md);
821 else if (!strncasecmp(c, OTP_INIT_HEX_TYPE,
822 strlen(OTP_INIT_HEX_TYPE))) {
824 r = hex2bin(c+strlen(OTP_INIT_HEX_TYPE), cur_otp, OTP_HASH_SIZE);
826 else if (!strncasecmp(c, OTP_INIT_WORD_TYPE,
827 strlen(OTP_INIT_WORD_TYPE))) {
829 r = word2bin(utils, c+strlen(OTP_INIT_WORD_TYPE), cur_otp, md);
832 SETERROR(utils, "unknown OTP extended response type");
837 /* standard response, try word first, and then hex */
838 r = word2bin(utils, c, cur_otp, md);
840 r = hex2bin(c, cur_otp, OTP_HASH_SIZE);
844 /* do one more hash (previous otp) and compare to stored otp */
845 otp_hash(md, cur_otp, OTP_HASH_SIZE, prev_otp, text->alg->swab);
847 if (!memcmp(prev_otp, text->otp, OTP_HASH_SIZE)) {
848 /* update the secret with this seq/otp */
849 memcpy(text->otp, cur_otp, OTP_HASH_SIZE);
857 /* if this is an init- attempt, let's check it out */
858 if (r == SASL_OK && do_init) {
859 char *new_chal = NULL, *new_resp = NULL;
860 algorithm_option_t *alg;
862 char seed[OTP_SEED_MAX+1];
863 unsigned char new_otp[OTP_HASH_SIZE];
865 /* find the challenge and response fields */
866 new_chal = strchr(c+strlen(OTP_INIT_WORD_TYPE), ':');
869 new_resp = strchr(new_chal, ':');
874 if (!(new_chal && new_resp))
877 if ((r = parse_challenge(utils, new_chal, &alg, &seq, seed, 1))
882 if (seq < 1 || !strcasecmp(seed, text->seed))
886 if (!(md = EVP_get_digestbyname(alg->evp_name))) {
887 utils->seterror(utils->conn, 0,
888 "OTP algorithm %s is not available",
893 if (!strncasecmp(c, OTP_INIT_HEX_TYPE, strlen(OTP_INIT_HEX_TYPE))) {
894 r = hex2bin(new_resp, new_otp, OTP_HASH_SIZE);
896 else if (!strncasecmp(c, OTP_INIT_WORD_TYPE,
897 strlen(OTP_INIT_WORD_TYPE))) {
898 r = word2bin(utils, new_resp, new_otp, md);
902 /* setup for new secret */
905 strcpy(text->seed, seed);
906 memcpy(text->otp, new_otp, OTP_HASH_SIZE);
913 static int otp_server_mech_step1(server_context_t *text,
914 sasl_server_params_t *params,
915 const char *clientin,
916 unsigned clientinlen,
917 const char **serverout,
918 unsigned *serveroutlen,
919 sasl_out_params_t *oparams)
926 const char *lookup_request[] = { "*cmusaslsecretOTP",
928 const char *store_request[] = { "cmusaslsecretOTP",
930 struct propval auxprop_values[2];
933 sasl_secret_t *sec = NULL;
934 struct propctx *propctx = NULL;
936 /* should have received authzid NUL authid */
940 while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup;
942 if (lup >= clientinlen) {
943 SETERROR(params->utils, "Can only find OTP authzid (no authid)");
949 authidp = clientin + lup;
950 while ((lup < clientinlen) && (clientin[lup] != 0)) ++lup;
952 authid_len = clientin + lup - authidp;
954 if (lup != clientinlen) {
955 SETERROR(params->utils,
956 "Got more data than we were expecting in the OTP plugin\n");
960 text->authid = params->utils->malloc(authid_len + 1);
961 if (text->authid == NULL) {
962 MEMERROR(params->utils);
966 /* we can't assume that authid is null-terminated */
967 strncpy(text->authid, authidp, authid_len);
968 text->authid[authid_len] = '\0';
972 /* Get user secret */
973 result = params->utils->prop_request(params->propctx,
975 if (result != SASL_OK) return result;
977 /* this will trigger the getting of the aux properties.
978 Must use the fully qualified authid here */
979 result = params->canon_user(params->utils->conn, text->authid, 0,
980 SASL_CU_AUTHID, oparams);
981 if (result != SASL_OK) return result;
983 result = params->canon_user(params->utils->conn,
984 strlen(authzid) ? authzid : text->authid,
985 0, SASL_CU_AUTHZID, oparams);
986 if (result != SASL_OK) return result;
988 result = params->utils->prop_getnames(params->propctx,
992 (!auxprop_values[0].name || !auxprop_values[0].values)) {
993 /* We didn't find this username */
994 params->utils->seterror(params->utils->conn,0,
995 "no OTP secret in database");
996 result = params->transition ? SASL_TRANS : SASL_NOUSER;
1000 if (auxprop_values[0].name && auxprop_values[0].values) {
1001 result = parse_secret(params->utils,
1002 (char*) auxprop_values[0].values[0],
1003 auxprop_values[0].valsize,
1004 mda, &text->seq, text->seed, text->otp,
1007 if (result != SASL_OK) return result;
1009 params->utils->seterror(params->utils->conn, 0,
1010 "don't have a OTP secret");
1014 text->timestamp = time(0);
1017 * check lock timeout
1019 * we try 10 times in 1 second intervals in order to give the other
1020 * auth attempt time to finish
1022 while ((text->timestamp < timeout) && (n++ < 10) && !sleep(1));
1024 if (text->timestamp < timeout) {
1025 SETERROR(params->utils,
1026 "simultaneous OTP authentications not permitted");
1027 return SASL_TRYAGAIN;
1030 /* check sequence number */
1031 if (text->seq <= 1) {
1032 SETERROR(params->utils, "OTP has expired (sequence <= 1)");
1033 return SASL_EXPIRED;
1036 /* find algorithm */
1037 text->alg = algorithm_options;
1038 while (text->alg->name) {
1039 if (!strcasecmp(text->alg->name, mda))
1045 if (!text->alg->name) {
1046 params->utils->seterror(params->utils->conn, 0,
1047 "unknown OTP algorithm '%s'", mda);
1051 /* remake the secret with a timeout */
1052 result = make_secret(params->utils, text->alg->name, text->seq,
1053 text->seed, text->otp,
1054 text->timestamp + OTP_LOCK_TIMEOUT, &sec);
1055 if (result != SASL_OK) {
1056 SETERROR(params->utils, "error making OTP secret");
1061 propctx = params->utils->prop_new(0);
1064 if (result == SASL_OK)
1065 result = params->utils->prop_request(propctx, store_request);
1066 if (result == SASL_OK)
1067 result = params->utils->prop_set(propctx, "cmusaslsecretOTP",
1068 sec->data, sec->len);
1069 if (result == SASL_OK)
1070 result = params->utils->auxprop_store(params->utils->conn,
1071 propctx, text->authid);
1073 params->utils->prop_dispose(&propctx);
1075 if (sec) params->utils->free(sec);
1077 if (result != SASL_OK) {
1078 SETERROR(params->utils, "Error putting OTP secret");
1084 result = _plug_buf_alloc(params->utils, &(text->out_buf),
1085 &(text->out_buf_len), OTP_CHALLENGE_MAX+1);
1086 if (result != SASL_OK) return result;
1088 /* create challenge */
1089 sprintf(text->out_buf, "otp-%s %u %s ext",
1090 text->alg->name, text->seq-1, text->seed);
1092 *serverout = text->out_buf;
1093 *serveroutlen = strlen(text->out_buf);
1097 return SASL_CONTINUE;
1101 otp_server_mech_step2(server_context_t *text,
1102 sasl_server_params_t *params,
1103 const char *clientin,
1104 unsigned clientinlen,
1105 const char **serverout __attribute__((unused)),
1106 unsigned *serveroutlen __attribute__((unused)),
1107 sasl_out_params_t *oparams)
1109 char response[OTP_RESPONSE_MAX+1];
1111 sasl_secret_t *sec = NULL;
1112 struct propctx *propctx = NULL;
1113 const char *store_request[] = { "cmusaslsecretOTP",
1116 if (clientinlen > OTP_RESPONSE_MAX) {
1117 SETERROR(params->utils, "OTP response too long");
1118 return SASL_BADPROT;
1121 /* we can't assume that the response is null-terminated */
1122 strncpy(response, clientin, clientinlen);
1123 response[clientinlen] = '\0';
1126 if (time(0) > text->timestamp + OTP_LOCK_TIMEOUT) {
1127 SETERROR(params->utils, "OTP: server timed out");
1128 return SASL_UNAVAIL;
1131 /* verify response */
1132 result = verify_response(text, params->utils, response);
1133 if (result != SASL_OK) return result;
1135 /* make the new secret */
1136 result = make_secret(params->utils, text->alg->name, text->seq,
1137 text->seed, text->otp, 0, &sec);
1138 if (result != SASL_OK) {
1139 SETERROR(params->utils, "error making OTP secret");
1143 propctx = params->utils->prop_new(0);
1146 if (result == SASL_OK)
1147 result = params->utils->prop_request(propctx, store_request);
1148 if (result == SASL_OK)
1149 result = params->utils->prop_set(propctx, "cmusaslsecretOTP",
1150 sec->data, sec->len);
1151 if (result == SASL_OK)
1152 result = params->utils->auxprop_store(params->utils->conn,
1153 propctx, text->authid);
1155 params->utils->prop_dispose(&propctx);
1158 params->utils->seterror(params->utils->conn, 0,
1159 "Error putting OTP secret");
1164 if (sec) _plug_free_secret(params->utils, &sec);
1167 oparams->doneflag = 1;
1168 oparams->mech_ssf = 0;
1169 oparams->maxoutbuf = 0;
1170 oparams->encode_context = NULL;
1171 oparams->encode = NULL;
1172 oparams->decode_context = NULL;
1173 oparams->decode = NULL;
1174 oparams->param_version = 0;
1179 static int otp_server_mech_step(void *conn_context,
1180 sasl_server_params_t *params,
1181 const char *clientin,
1182 unsigned clientinlen,
1183 const char **serverout,
1184 unsigned *serveroutlen,
1185 sasl_out_params_t *oparams)
1187 server_context_t *text = (server_context_t *) conn_context;
1192 switch (text->state) {
1195 return otp_server_mech_step1(text, params, clientin, clientinlen,
1196 serverout, serveroutlen, oparams);
1199 return otp_server_mech_step2(text, params, clientin, clientinlen,
1200 serverout, serveroutlen, oparams);
1203 params->utils->log(NULL, SASL_LOG_ERR,
1204 "Invalid OTP server step %d\n", text->state);
1208 return SASL_FAIL; /* should never get here */
1211 static void otp_server_mech_dispose(void *conn_context,
1212 const sasl_utils_t *utils)
1214 server_context_t *text = (server_context_t *) conn_context;
1216 struct propctx *propctx = NULL;
1217 const char *store_request[] = { "cmusaslsecretOTP",
1223 /* if we created a challenge, but bailed before the verification of the
1224 response, release the lock on the user key */
1225 if (text->locked && (time(0) < text->timestamp + OTP_LOCK_TIMEOUT)) {
1226 r = make_secret(utils, text->alg->name, text->seq,
1227 text->seed, text->otp, 0, &sec);
1229 SETERROR(utils, "error making OTP secret");
1230 if (sec) utils->free(sec);
1235 propctx = utils->prop_new(0);
1239 r = utils->prop_request(propctx, store_request);
1241 r = utils->prop_set(propctx, "cmusaslsecretOTP",
1242 (sec ? sec->data : NULL),
1243 (sec ? sec->len : 0));
1245 r = utils->auxprop_store(utils->conn, propctx, text->authid);
1247 utils->prop_dispose(&propctx);
1250 SETERROR(utils, "Error putting OTP secret");
1253 if (sec) _plug_free_secret(utils, &sec);
1256 if (text->authid) _plug_free_string(utils, &(text->authid));
1257 if (text->realm) _plug_free_string(utils, &(text->realm));
1259 if (text->out_buf) utils->free(text->out_buf);
1264 static int otp_setpass(void *glob_context __attribute__((unused)),
1265 sasl_server_params_t *sparams,
1266 const char *userstr,
1268 unsigned passlen __attribute__((unused)),
1269 const char *oldpass __attribute__((unused)),
1270 unsigned oldpasslen __attribute__((unused)),
1275 char *user_only = NULL;
1278 struct propctx *propctx = NULL;
1279 const char *store_request[] = { "cmusaslsecretOTP",
1282 /* Do we have a backend that can store properties? */
1283 if (!sparams->utils->auxprop_store ||
1284 sparams->utils->auxprop_store(NULL, NULL, NULL) != SASL_OK) {
1285 SETERROR(sparams->utils, "OTP: auxprop backend can't store properties");
1289 r = _plug_parseuser(sparams->utils, &user_only, &realm, sparams->user_realm,
1290 sparams->serverFQDN, userstr);
1292 sparams->utils->seterror(sparams->utils->conn, 0,
1293 "OTP: Error parsing user");
1297 r = _plug_make_fulluser(sparams->utils, &user, user_only, realm);
1302 if ((flags & SASL_SET_DISABLE) || pass == NULL) {
1305 algorithm_option_t *algs;
1308 unsigned short randnum;
1309 char seed[OTP_SEED_MAX+1];
1310 char otp[OTP_HASH_SIZE];
1312 sparams->utils->getopt(sparams->utils->getopt_context,
1313 "OTP", "otp_mda", &mda, &len);
1314 if (!mda) mda = OTP_MDA_DEFAULT;
1316 algs = algorithm_options;
1317 while (algs->name) {
1318 if (!strcasecmp(algs->name, mda) ||
1319 !strcasecmp(algs->evp_name, mda))
1326 sparams->utils->seterror(sparams->utils->conn, 0,
1327 "unknown OTP algorithm '%s'", mda);
1332 sparams->utils->rand(sparams->utils->rpool,
1333 (char*) &randnum, sizeof(randnum));
1334 sprintf(seed, "%.2s%04u", sparams->serverFQDN, (randnum % 9999) + 1);
1336 r = generate_otp(sparams->utils, algs, OTP_SEQUENCE_DEFAULT,
1337 seed, (char*) pass, otp);
1339 /* generate_otp() takes care of error message */
1343 r = make_secret(sparams->utils, algs->name, OTP_SEQUENCE_DEFAULT,
1344 seed, otp, 0, &sec);
1346 SETERROR(sparams->utils, "error making OTP secret");
1352 propctx = sparams->utils->prop_new(0);
1356 r = sparams->utils->prop_request(propctx, store_request);
1358 r = sparams->utils->prop_set(propctx, "cmusaslsecretOTP",
1359 (sec ? sec->data : NULL),
1360 (sec ? sec->len : 0));
1362 r = sparams->utils->auxprop_store(sparams->utils->conn, propctx, user);
1364 sparams->utils->prop_dispose(&propctx);
1367 sparams->utils->seterror(sparams->utils->conn, 0,
1368 "Error putting OTP secret");
1372 sparams->utils->log(NULL, SASL_LOG_DEBUG, "Setpass for OTP successful\n");
1376 if (user) _plug_free_string(sparams->utils, &user);
1377 if (user_only) _plug_free_string(sparams->utils, &user_only);
1378 if (realm) _plug_free_string(sparams->utils, &realm);
1379 if (sec) _plug_free_secret(sparams->utils, &sec);
1384 static int otp_mech_avail(void *glob_context __attribute__((unused)),
1385 sasl_server_params_t *sparams,
1386 void **conn_context __attribute__((unused)))
1388 /* Do we have a backend that can store properties? */
1389 if (!sparams->utils->auxprop_store ||
1390 sparams->utils->auxprop_store(NULL, NULL, NULL) != SASL_OK) {
1391 SETERROR(sparams->utils, "OTP: auxprop backend can't store properties");
1398 static sasl_server_plug_t otp_server_plugins[] =
1401 "OTP", /* mech_name */
1403 SASL_SEC_NOPLAINTEXT
1404 | SASL_SEC_NOANONYMOUS
1405 | SASL_SEC_FORWARD_SECRECY, /* security_flags */
1406 SASL_FEAT_WANT_CLIENT_FIRST
1407 | SASL_FEAT_ALLOWS_PROXY, /* features */
1408 NULL, /* glob_context */
1409 &otp_server_mech_new, /* mech_new */
1410 &otp_server_mech_step, /* mech_step */
1411 &otp_server_mech_dispose, /* mech_dispose */
1412 &otp_common_mech_free, /* mech_free */
1413 &otp_setpass, /* setpass */
1414 NULL, /* user_query */
1416 &otp_mech_avail, /* mech avail */
1420 #endif /* HAVE_OPIE */
1422 int otp_server_plug_init(const sasl_utils_t *utils,
1425 sasl_server_plug_t **pluglist,
1428 if (maxversion < SASL_SERVER_PLUG_VERSION) {
1429 SETERROR(utils, "OTP version mismatch");
1430 return SASL_BADVERS;
1433 *out_version = SASL_SERVER_PLUG_VERSION;
1434 *pluglist = otp_server_plugins;
1437 /* Add all digests */
1438 OpenSSL_add_all_digests();
1443 /***************************** Client Section *****************************/
1445 typedef struct client_context {
1448 sasl_secret_t *password;
1449 unsigned int free_password; /* set if we need to free password */
1451 const char *otpassword;
1454 unsigned out_buf_len;
1457 static int otp_client_mech_new(void *glob_context __attribute__((unused)),
1458 sasl_client_params_t *params,
1459 void **conn_context)
1461 client_context_t *text;
1463 /* holds state are in */
1464 text = params->utils->malloc(sizeof(client_context_t));
1466 MEMERROR( params->utils );
1470 memset(text, 0, sizeof(client_context_t));
1474 *conn_context = text;
1479 static int otp_client_mech_step1(client_context_t *text,
1480 sasl_client_params_t *params,
1481 const char *serverin __attribute__((unused)),
1482 unsigned serverinlen __attribute__((unused)),
1483 sasl_interact_t **prompt_need,
1484 const char **clientout,
1485 unsigned *clientoutlen,
1486 sasl_out_params_t *oparams)
1488 const char *user = NULL, *authid = NULL;
1489 int user_result = SASL_OK;
1490 int auth_result = SASL_OK;
1491 int pass_result = SASL_OK;
1492 sasl_chalprompt_t *echo_cb;
1496 /* check if sec layer strong enough */
1497 if (params->props.min_ssf > params->external_ssf) {
1498 SETERROR( params->utils, "SSF requested of OTP plugin");
1499 return SASL_TOOWEAK;
1502 /* try to get the authid */
1503 if (oparams->authid == NULL) {
1504 auth_result = _plug_get_authid(params->utils, &authid, prompt_need);
1506 if ((auth_result != SASL_OK) && (auth_result != SASL_INTERACT))
1510 /* try to get the userid */
1511 if (oparams->user == NULL) {
1512 user_result = _plug_get_userid(params->utils, &user, prompt_need);
1514 if ((user_result != SASL_OK) && (user_result != SASL_INTERACT))
1518 /* try to get the secret pass-phrase if we don't have a chalprompt */
1519 if ((params->utils->getcallback(params->utils->conn, SASL_CB_ECHOPROMPT,
1520 &echo_cb, &echo_context) == SASL_FAIL) &&
1521 (text->password == NULL)) {
1522 pass_result = _plug_get_password(params->utils, &text->password,
1523 &text->free_password, prompt_need);
1525 if ((pass_result != SASL_OK) && (pass_result != SASL_INTERACT))
1529 /* free prompts we got */
1530 if (prompt_need && *prompt_need) {
1531 params->utils->free(*prompt_need);
1532 *prompt_need = NULL;
1535 /* if there are prompts not filled in */
1536 if ((user_result == SASL_INTERACT) || (auth_result == SASL_INTERACT) ||
1537 (pass_result == SASL_INTERACT)) {
1538 /* make the prompt list */
1540 _plug_make_prompts(params->utils, prompt_need,
1541 user_result == SASL_INTERACT ?
1542 "Please enter your authorization name" : NULL,
1544 auth_result == SASL_INTERACT ?
1545 "Please enter your authentication name" : NULL,
1547 pass_result == SASL_INTERACT ?
1548 "Please enter your secret pass-phrase" : NULL,
1552 if (result != SASL_OK) return result;
1554 return SASL_INTERACT;
1557 if (!user || !*user) {
1558 result = params->canon_user(params->utils->conn, authid, 0,
1559 SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams);
1562 result = params->canon_user(params->utils->conn, user, 0,
1563 SASL_CU_AUTHZID, oparams);
1564 if (result != SASL_OK) return result;
1566 result = params->canon_user(params->utils->conn, authid, 0,
1567 SASL_CU_AUTHID, oparams);
1569 if (result != SASL_OK) return result;
1571 /* send authorized id NUL authentication id */
1572 *clientoutlen = oparams->ulen + 1 + oparams->alen;
1574 /* remember the extra NUL on the end for stupid clients */
1575 result = _plug_buf_alloc(params->utils, &(text->out_buf),
1576 &(text->out_buf_len), *clientoutlen + 1);
1577 if (result != SASL_OK) return result;
1579 memset(text->out_buf, 0, *clientoutlen + 1);
1580 memcpy(text->out_buf, oparams->user, oparams->ulen);
1581 memcpy(text->out_buf+oparams->ulen+1, oparams->authid, oparams->alen);
1582 *clientout = text->out_buf;
1586 return SASL_CONTINUE;
1589 static int otp_client_mech_step2(client_context_t *text,
1590 sasl_client_params_t *params,
1591 const char *serverin,
1592 unsigned serverinlen,
1593 sasl_interact_t **prompt_need,
1594 const char **clientout,
1595 unsigned *clientoutlen,
1596 sasl_out_params_t *oparams)
1598 int echo_result = SASL_OK;
1599 char challenge[OTP_CHALLENGE_MAX+1];
1602 if (serverinlen > OTP_CHALLENGE_MAX) {
1603 SETERROR(params->utils, "OTP challenge too long");
1604 return SASL_BADPROT;
1607 /* we can't assume that challenge is null-terminated */
1608 strncpy(challenge, serverin, serverinlen);
1609 challenge[serverinlen] = '\0';
1611 /* try to get the one-time password if we don't ave the secret */
1612 if ((text->password == NULL) && (text->otpassword == NULL)) {
1613 echo_result = _plug_challenge_prompt(params->utils, SASL_CB_ECHOPROMPT,
1615 "Please enter your one-time password",
1616 &text->otpassword, prompt_need);
1618 if ((echo_result != SASL_OK) && (echo_result != SASL_INTERACT))
1622 /* free prompts we got */
1623 if (prompt_need && *prompt_need) {
1624 params->utils->free(*prompt_need);
1625 *prompt_need = NULL;
1628 /* if there are prompts not filled in */
1629 if (echo_result == SASL_INTERACT) {
1630 /* make the prompt list */
1632 _plug_make_prompts(params->utils, prompt_need,
1636 challenge, echo_result == SASL_INTERACT ?
1637 "Please enter your one-time password" : NULL,
1640 if (result != SASL_OK) return result;
1642 return SASL_INTERACT;
1645 /* the application provided us with a one-time password so use it */
1646 if (text->otpassword) {
1647 *clientout = text->otpassword;
1648 *clientoutlen = strlen(text->otpassword);
1651 /* generate our own response using the user's secret pass-phrase */
1653 algorithm_option_t *alg;
1655 char seed[OTP_SEED_MAX+1];
1656 char otp[OTP_HASH_SIZE];
1659 /* parse challenge */
1660 result = parse_challenge(params->utils,
1661 challenge, &alg, &seq, seed, 0);
1662 if (result != SASL_OK) return result;
1664 if (!text->password) {
1665 PARAMERROR(params->utils);
1666 return SASL_BADPARAM;
1670 SETERROR(params->utils, "OTP has expired (sequence < 1)");
1671 return SASL_EXPIRED;
1675 result = generate_otp(params->utils, alg, seq, seed,
1676 text->password->data, otp);
1677 if (result != SASL_OK) return result;
1679 result = _plug_buf_alloc(params->utils, &(text->out_buf),
1680 &(text->out_buf_len), OTP_RESPONSE_MAX+1);
1681 if (result != SASL_OK) return result;
1683 if (seq < OTP_SEQUENCE_REINIT) {
1684 unsigned short randnum;
1685 char new_seed[OTP_SEED_MAX+1];
1686 char new_otp[OTP_HASH_SIZE];
1688 /* try to reinitialize */
1690 /* make sure we have a different seed */
1692 params->utils->rand(params->utils->rpool,
1693 (char*) &randnum, sizeof(randnum));
1694 sprintf(new_seed, "%.2s%04u", params->serverFQDN,
1695 (randnum % 9999) + 1);
1696 } while (!strcasecmp(seed, new_seed));
1698 result = generate_otp(params->utils, alg, OTP_SEQUENCE_DEFAULT,
1699 new_seed, text->password->data, new_otp);
1701 if (result == SASL_OK) {
1702 /* create an init-hex response */
1703 strcpy(text->out_buf, OTP_INIT_HEX_TYPE);
1704 bin2hex(otp, OTP_HASH_SIZE,
1705 text->out_buf+strlen(text->out_buf));
1706 sprintf(text->out_buf+strlen(text->out_buf), ":%s %u %s:",
1707 alg->name, OTP_SEQUENCE_DEFAULT, new_seed);
1708 bin2hex(new_otp, OTP_HASH_SIZE,
1709 text->out_buf+strlen(text->out_buf));
1713 /* just do a regular response */
1718 /* created hex response */
1719 strcpy(text->out_buf, OTP_HEX_TYPE);
1720 bin2hex(otp, OTP_HASH_SIZE, text->out_buf+strlen(text->out_buf));
1723 *clientout = text->out_buf;
1724 *clientoutlen = strlen(text->out_buf);
1728 oparams->doneflag = 1;
1729 oparams->mech_ssf = 0;
1730 oparams->maxoutbuf = 0;
1731 oparams->encode_context = NULL;
1732 oparams->encode = NULL;
1733 oparams->decode_context = NULL;
1734 oparams->decode = NULL;
1735 oparams->param_version = 0;
1740 static int otp_client_mech_step(void *conn_context,
1741 sasl_client_params_t *params,
1742 const char *serverin,
1743 unsigned serverinlen,
1744 sasl_interact_t **prompt_need,
1745 const char **clientout,
1746 unsigned *clientoutlen,
1747 sasl_out_params_t *oparams)
1749 client_context_t *text = (client_context_t *) conn_context;
1754 switch (text->state) {
1757 return otp_client_mech_step1(text, params, serverin, serverinlen,
1758 prompt_need, clientout, clientoutlen,
1762 return otp_client_mech_step2(text, params, serverin, serverinlen,
1763 prompt_need, clientout, clientoutlen,
1767 params->utils->log(NULL, SASL_LOG_ERR,
1768 "Invalid OTP client step %d\n", text->state);
1772 return SASL_FAIL; /* should never get here */
1775 static void otp_client_mech_dispose(void *conn_context,
1776 const sasl_utils_t *utils)
1778 client_context_t *text = (client_context_t *) conn_context;
1782 if (text->free_password) _plug_free_secret(utils, &(text->password));
1784 if (text->out_buf) utils->free(text->out_buf);
1789 static sasl_client_plug_t otp_client_plugins[] =
1792 "OTP", /* mech_name */
1794 SASL_SEC_NOPLAINTEXT
1795 | SASL_SEC_NOANONYMOUS
1796 | SASL_SEC_FORWARD_SECRECY, /* security_flags */
1797 SASL_FEAT_WANT_CLIENT_FIRST
1798 | SASL_FEAT_ALLOWS_PROXY, /* features */
1799 NULL, /* required_prompts */
1800 NULL, /* glob_context */
1801 &otp_client_mech_new, /* mech_new */
1802 &otp_client_mech_step, /* mech_step */
1803 &otp_client_mech_dispose, /* mech_dispose */
1804 &otp_common_mech_free, /* mech_free */
1811 int otp_client_plug_init(sasl_utils_t *utils,
1814 sasl_client_plug_t **pluglist,
1817 if (maxversion < SASL_CLIENT_PLUG_VERSION) {
1818 SETERROR(utils, "OTP version mismatch");
1819 return SASL_BADVERS;
1822 *out_version = SASL_CLIENT_PLUG_VERSION;
1823 *pluglist = otp_client_plugins;
1826 /* Add all digests */
1827 OpenSSL_add_all_digests();