/* OTP SASL plugin
* Ken Murchison
* $Id: otp.c,v 1.41 2009/02/14 12:45:29 mel Exp $
*/
/*
* Copyright (c) 1998-2009 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
* Office of Technology Transfer
* Carnegie Mellon University
* 5000 Forbes Avenue
* Pittsburgh, PA 15213-3890
* (412) 268-4387, fax: (412) 268-7395
* tech-transfer@andrew.cmu.edu
*
* 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
#include <sasl_otp_plugin_decl.h>
#endif
/***************************** Common Section *****************************/
static const char plugin_id[] = "$Id: otp.c,v 1.41 2009/02/14 12:45:29 mel Exp $";
#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}
};
/* 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;
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,
char *secret, char *otp)
{
const EVP_MD *md;
char *key;
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 ((key = utils->malloc(strlen(seed) + strlen(secret) + 1)) == NULL) {
SETERROR(utils, "cannot allocate OTP key");
return SASL_NOMEM;
}
/* initial step */
strcpy(key, seed);
strcat(key, secret);
otp_hash(md, key, strlen(key), otp, alg->swab);
/* computation step */
while (seq-- > 0)
otp_hash(md, otp, OTP_HASH_SIZE, otp, alg->swab);
utils->free(key);
return SASL_OK;
}
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)))
{
EVP_cleanup();
}
/***************************** 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;
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, char *otp,
time_t timeout, sasl_secret_t **secret)
{
size_t sec_len;
unsigned 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 = (*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) {
unsigned 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)
{
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) {
EVP_MD_CTX mdctx;
char hash[EVP_MAX_MD_SIZE];
int 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;
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;
}
/* 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);
}
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);
}
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);
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, cur_otp, OTP_HASH_SIZE, prev_otp, text->alg->swab);
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))
return SASL_BADAUTH;
if ((r = parse_challenge(utils, new_chal, &alg, &seq, seed, 1))
!= SASL_OK) {
return r;
}
if (seq < 1 || !strcasecmp(seed, text->seed))
return SASL_BADAUTH;
/* 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);
return SASL_BADAUTH;
}
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);
}
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);
}
}
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",
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",
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 ? 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 __attribute__((unused)),
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];
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, (char*) pass, 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 ? 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,
&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];
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, 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];
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, 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;
}