# (default: /etc/otppasswd)
#pwdfile = /etc/otppasswd
- # Directory containing sync mode and state info.
- # This directory must be mode 0700, and owned by the
- # the user radiusd runs as.
- # (default: /etc/otpsync.d)
- #syncdir = /etc/otpsync.d
+ # State manager rendezvous point.
+ # (default: /var/run/lsmd/socket)
+ #lsmd_rp = /var/run/lsmd/socket
# Text to use for the challenge. The '%' character is
# disallowed, except that you MUST have a single "%s"
#
#######################################################################
TARGET = @targetname@
-SRCS = otp_rlm.c otp_util.c otp_radstate.c otp_x99.c otp_sync.c
+SRCS = otp_rlm.c otp_util.c otp_radstate.c otp_x99.c otp_state.c
SRCS += otp_site.c otp_pwe.c otp_log.c otp_cardops.c
-HEADERS = otp.h otp_rad.h otp_sync.h otp_pwe.h otp_cardops.h
+HEADERS = otp.h otp_rad.h otp_pwe.h otp_cardops.h
RLM_CFLAGS = @otp_cflags@ $(OPENSSL_INCLUDE)
CARDOPS_LTLIBS = $(patsubst %.c,%.lo,$(wildcard cardops/*.c))
RLM_LIBS = @otp_ldflags@ $(OPENSSL_LIBS) $(CARDOPS_LTLIBS)
* Returns 0 on success, non-zero otherwise.
*/
static int
-cryptocard_challenge(const char *syncdir, otp_user_info_t *user_info,
- int ewin,
+cryptocard_challenge(const otp_user_info_t *user_info, unsigned int ewin,
#ifdef __GNUC__
__attribute__ ((unused))
#endif
char challenge[OTP_MAX_CHALLENGE_LEN + 1])
{
unsigned char output[8];
- int i, rc = -1;
-
- if (ewin == 0) {
- /* Return currently stored next challenge. */
- return otp_get_sync_challenge(syncdir, user_info->username, challenge);
-
- } else if (challenge[0]) {
- /* iterate once on the supplied challenge */
- ewin = 1;
- } else {
- /* iterate ewin times on the stored next challenge */
- rc = otp_get_sync_challenge(syncdir, user_info->username, challenge);
- if (rc)
- return rc;
- }
+ int i, rc = 0;
+
/* CRYPTOCard sync challenges are always 8 bytes. */
- if (strlen(challenge) != 8) {
+ if (strlen(challenge) != 8)
return -1;
- }
+ /* iterate ewin times on the challenge */
while (ewin--) {
if ((rc = otp_x99_mac(challenge, 8, output,
user_info->keyblock)) == 0) {
static int cryptocard_name2fm(const char *name, uint32_t *featuremask);
static int cryptocard_keystring2keyblock(const char *keystring,
unsigned char keyblock[]);
-static int cryptocard_challenge(const char *syncdir,
- otp_user_info_t *user_info,
- int ewin, int twin,
+static int cryptocard_challenge(const otp_user_info_t *user_info,
+ unsigned int ewin, int twin,
char challenge[OTP_MAX_CHALLENGE_LEN + 1]);
static int cryptocard_response(otp_user_info_t *user_info,
const char *challenge,
#ifndef OTP_H
#define OTP_H
+#ifndef _REENTRANT
+#define _REENTRANT
+#endif
+#include <pthread.h>
+
#include <inttypes.h>
#include <openssl/des.h> /* des_cblock */
#include <time.h> /* time_t */
/* Default passwd file */
#define OTP_PWDFILE "/etc/otppasswd"
-/* Default sync dir */
-#define OTP_SYNCDIR "/etc/otpsync.d"
+/* state manager rendezvous point */
+#define OTP_LSMD_RP "/var/run/lsmd/socket"
/* Default prompt for presentation of challenge */
#define OTP_CHALLENGE_PROMPT "Challenge: %s\n Response: "
/* struct used for instance/option data */
typedef struct otp_option_t {
char *pwdfile; /* file containing user:card_type:key entries */
- char *syncdir; /* dir containing sync mode and state info */
+ char *lsmd_rp; /* state manager rendezvous point */
char *chal_prompt; /* text to present challenge to user, must have %s */
int chal_len; /* challenge length, min 5 digits */
int softfail; /* number of auth fails before time delay starts */
#endif
} otp_user_info_t;
+/* user-specific state info */
+#define OTP_MAX_CSD_LEN 64
+typedef struct otp_user_state_t {
+ int locked; /* locked aka success flag */
+ int *fdp; /* fd for return data */
+ int updated; /* state updated? (1 unless err) */
+ char challenge[OTP_MAX_CHALLENGE_LEN+1]; /* next sync chal */
+ char csd[OTP_MAX_CSD_LEN+1]; /* card specific data */
+ unsigned failcount; /* number of consecutive failures */
+ time_t authtime; /* time of last auth */
+ int authpos; /* window position for softfail */
+} otp_user_state_t;
+
+/* fc (failcondition) shortcuts */
+#define OTP_FC_FAIL_NONE 0 /* no failures */
+#define OTP_FC_FAIL_HARD 1 /* failed hard */
+#define OTP_FC_FAIL_SOFT 2 /* failed soft */
+
/* otp_cardops.c */
/* return codes from otp_pw_valid() */
#define OTP_RC_OK 0
/* otp_x99.c */
extern int otp_x99_mac(const unsigned char *input, size_t len,
unsigned char output[8],
- unsigned char keyblock[OTP_MAX_KEY_LEN]);
+ const unsigned char keyblock[OTP_MAX_KEY_LEN]);
/* otp_util.c */
/* Character maps for generic hex and vendor specific decimal modes */
extern int otp_get_user_info(const char *pwdfile, const char *username,
otp_user_info_t *user_info);
-/* otp_sync.c */
-#define OTP_FC_FAIL_ERR -1
-#define OTP_FC_FAIL_HARD -2
-#define OTP_FC_FAIL_SOFT -3
-
-extern int otp_get_sync_challenge(const char *syncdir, const char *username,
- char challenge[OTP_MAX_CHALLENGE_LEN + 1]);
-extern int otp_set_sync_data(const char *syncdir, const char *username,
- const char *challenge, const des_cblock keyblock);
-extern int otp_check_failcount(const char *syncdir, const otp_option_t *inst);
-extern int otp_incr_failcount(const char *syncdir, const char *username);
-extern int otp_reset_failcount(const char *syncdir, const char *username);
-extern int otp_get_last_auth(const char *syncdir, const char *username,
- time_t *last_auth);
-extern int otp_upd_last_auth(const char *syncdir, const char *username);
-extern unsigned otp_get_last_auth_pos(const char *syncdir,const char *username);
-extern int otp_set_last_auth_pos(const char *syncdir, const char *username,
- unsigned pos);
+/* otp_state.c */
+extern int otp_state_get(const otp_option_t *opt, const char *username,
+ otp_user_state_t *user_state, const char *log_prefix);
+extern int otp_state_put(const char *username, otp_user_state_t *user_state);
/* otp_site.c */
extern int otp_challenge_transform(const char *username,
#endif
#endif /* OTP_H */
-
static const char rcsid[] = "$Id$";
+#include <limits.h>
#include <stdlib.h>
#include <string.h>
cmpfunc_t cmpfunc, void *data,
const char *log_prefix)
{
- int rc, fc, nmatch, i;
+ int rc, nmatch, i;
+ int fc = OTP_FC_FAIL_NONE; /* failcondition */
/* expected response */
char e_response[OTP_MAX_RESPONSE_LEN + OTP_MAX_PIN_LEN + 1];
int pin_adjust = 0; /* pin offset in e_response */
- unsigned auth_pos = 0; /* window position of this authentication */
- time_t last_auth_time; /* time of last authentication */
+ int authpos = -2; /* window pos of last success auth, or -2 */
- otp_user_info_t user_info = { .cardops = NULL };
+ otp_user_info_t user_info = { .cardops = NULL };
+ otp_user_state_t user_state = { .locked = 0 };
/* sanity */
if (!challenge) {
pin_adjust = strlen(e_response);
}
- /* Get the time of the last authentication. */
- if (otp_get_last_auth(opt->syncdir, username, &last_auth_time) != 0) {
- otp_log(OTP_LOG_ERR,
- "%s: unable to get last auth time for [%s]",
+ /* Get user state. */
+ if (otp_state_get(opt, username, &user_state, log_prefix) != 0) {
+ otp_log(OTP_LOG_ERR, "%s: unable to get state for [%s]",
log_prefix, username);
rc = OTP_RC_SERVICE_ERR;
goto auth_done_service_err;
- /* NB: last_auth_time, failcount not updated. */
}
- /* Get failure count for later evaluation. */
- fc = otp_check_failcount(username, opt);
- if (fc == OTP_FC_FAIL_ERR) {
- rc = OTP_RC_SERVICE_ERR;
- goto auth_done_service_err;
- /* NB: last_auth_time, failcount not updated. */
+ /* Set fc (failcondition). */
+ if (opt->hardfail && user_state.failcount >= opt->hardfail) {
+ fc = OTP_FC_FAIL_HARD;
+ } else if (opt->softfail && user_state.failcount >= opt->softfail) {
+ time_t when;
+ int fcount;
+
+ /*
+ * Determine the next time this user can authenticate.
+ *
+ * Once we hit softfail, we introduce a 1m delay before the user
+ * can authenticate. For each successive failed authentication,
+ * we double the delay time, up to a max of 32 minutes. While in
+ * the "delay mode" of operation, all authentication ATTEMPTS are
+ * considered failures. Also, each attempt during the delay period
+ * restarts the clock.
+ *
+ * The advantage of a delay instead of a simple lockout is that an
+ * attacker can't lock out a user as easily; the user need only wait
+ * a bit before he can authenticate.
+ */
+ fcount = user_state.failcount - opt->softfail;
+ when = user_state.authtime +
+ (fcount > 5 ? 32 * 60 : (1 << fcount) * 60);
+ if (time(NULL) < when)
+ fc = OTP_FC_FAIL_SOFT;
}
async_response:
log_prefix, username);
rc = OTP_RC_SERVICE_ERR;
goto auth_done_service_err;
- /* NB: last_auth_time, failcount not updated. */
+ /* NB: state not updated. */
}
/* Calculate the async response. */
"to challenge %s", log_prefix, username, challenge);
rc = OTP_RC_SERVICE_ERR;
goto auth_done_service_err;
- /* NB: last_auth_time, failcount not updated. */
+ /* NB: state not updated. */
}
/* NOTE: We do not display the PIN. */
#if defined(FREERADIUS)
if (fc == OTP_FC_FAIL_HARD) {
otp_log(OTP_LOG_AUTH,
"%s: bad async auth for [%s]: valid but in hardfail",
- log_prefix, username);
+ " (%d/%d failed/max)", log_prefix, username,
+ user_state.failcount, opt->hardfail);
rc = OTP_RC_MAXTRIES;
goto auth_done;
}
if (fc == OTP_FC_FAIL_SOFT) {
otp_log(OTP_LOG_AUTH,
"%s: bad async auth for [%s]: valid but in softfail",
- log_prefix, username);
+ " (%d/%d failed/max)", log_prefix, username,
+ user_state.failcount, opt->softfail);
rc = OTP_RC_MAXTRIES;
goto auth_done;
}
* a previous authentication is further ahead in the
* window (when in softfail), because we want to
* report a correct passcode even if it is behind
- * the currently acceptable window,
+ * the currently acceptable window.
*/
if ((user_info.featuremask & OTP_CF_SM) && opt->allow_sync) {
- int end = opt->ewindow_size, last_auth_pos = 0;
+ int end = opt->ewindow_size;
/* Increase window for ewindow2. */
- if (opt->ewindow2_size && fc == OTP_FC_FAIL_SOFT) {
- last_auth_pos = otp_get_last_auth_pos(opt->syncdir, username);
- if (last_auth_pos < 0) {
- otp_log(OTP_LOG_ERR,
- "%s: unable to get last auth window position for [%s]",
- log_prefix, username);
- rc = OTP_RC_SERVICE_ERR;
- goto auth_done_service_err;
- /* NB: last_auth_time, failcount not updated. */
- }
+ if (opt->ewindow2_size && fc == OTP_FC_FAIL_SOFT)
end = opt->ewindow2_size;
- }
- for (i = 0; i <= end; ++i) {
- /* Get sync challenge and key. */
- if (user_info.cardops->challenge(opt->syncdir, &user_info,
- i, 0, challenge) != 0) {
- otp_log(OTP_LOG_ERR,
- "%s: unable to get sync challenge e:%d t:%d for [%s]",
- log_prefix, i, 0, username);
- rc = OTP_RC_SERVICE_ERR;
- goto auth_done_service_err;
- /* NB: last_auth_time, failcount not updated. */
- }
+ /* Setup initial challenge. */
+ (void) strcpy(challenge, user_state.challenge);
+ /* Test each sync response in the window. */
+ for (i = 0; i <= end; ++i) {
/* Calculate sync response. */
if (user_info.cardops->response(&user_info, challenge,
&e_response[pin_adjust]) != 0) {
log_prefix, i, 0, username, challenge);
rc = OTP_RC_SERVICE_ERR;
goto auth_done_service_err;
- /* NB: last_auth_time, failcount not updated. */
+ /* NB: state not updated. */
}
/* NOTE: We do not display the PIN. */
#if defined(FREERADIUS)
nmatch = cmpfunc(data, e_response);
if (!nmatch) {
if (fc == OTP_FC_FAIL_HARD) {
- otp_log(OTP_LOG_AUTH, "%s: bad sync auth for [%s]: "
- "valid but in hardfail",
- log_prefix, username);
+ otp_log(OTP_LOG_AUTH,
+ "%s: bad sync auth for [%s]: valid but in hardfail",
+ " (%d/%d failed/max)", log_prefix, username,
+ user_state.failcount, opt->hardfail);
rc = OTP_RC_MAXTRIES;
goto auth_done;
}
if (fc == OTP_FC_FAIL_SOFT) {
if (!opt->ewindow2_size) {
/* ewindow2 mode not configured */
- otp_log(OTP_LOG_AUTH, "%s: bad sync auth for [%s]: "
- "valid but in softfail",
- log_prefix, username);
+ otp_log(OTP_LOG_AUTH,
+ "%s: bad sync auth for [%s]: "
+ "valid but in softfail",
+ " (%d/%d failed/max)", log_prefix, username,
+ user_state.failcount, opt->softfail);
rc = OTP_RC_MAXTRIES;
goto auth_done;
}
/*
* User must enter two consecutive correct sync passcodes
* for ewindow2 softfail override.
- *
- * last_auth_pos == 0 could mean that the last entry was
- * correct and at the zeroeth sync position, or that the
- * last entry was correct and async, or that the last
- * entry was incorrect. Since we can't differentiate,
- * we can't use a 0 last_auth_pos as the first passcode
- * in the ewindow2 sequence. This means that users who
- * start an ewindow2 softfail override at the very left
- * edge of the window must enter 3 passcodes (0,1,2)
- * instead of 2.
- * TODO: update get_last_auth_pos to return pos+1.
*/
- if (last_auth_pos && (i == last_auth_pos + 1) &&
+ if ((i == user_state.authpos + 1) &&
/* ... within ewindow2_delay seconds. */
- (time(NULL) - last_auth_time < opt->ewindow2_delay)) {
+ (time(NULL) - user_state.authtime < opt->ewindow2_delay)) {
/* This is the 2nd of two consecutive responses. */
otp_log(OTP_LOG_AUTH,
"%s: ewindow2 softfail override for [%s] at "
"%s: auth: [%s] ewindow2 candidate "
"at position %i", log_prefix, username, i);
#endif
- auth_pos = i;
+ authpos = i;
rc = OTP_RC_AUTH_ERR;
goto auth_done;
}
- }
+ } /* if (in softfail) */
/* Authenticated in sync mode. */
rc = OTP_RC_OK;
goto auth_done;
} /* if (passcode is valid) */
+
+ /* Get next challenge (extra work at end of loop; TODO: fix). */
+ if (user_info.cardops->challenge(&user_info,
+ 1, 0, challenge) != 0) {
+ otp_log(OTP_LOG_ERR,
+ "%s: unable to get sync challenge e:%d t:%d for [%s]",
+ log_prefix, i, 0, username);
+ rc = OTP_RC_SERVICE_ERR;
+ goto auth_done_service_err;
+ /* NB: state not updated. */
+ }
} /* for (each slot in the window) */
} /* if (sync mode possible) */
auth_done:
if (rc == OTP_RC_OK) {
if (resync) {
- /*
- * Resync the card.
- *
- * We "fail-out" if we can't do this, because for sync mode the
- * response can be reused until sync data is updated, an obvious
- * replay attack.
- *
- * For async mode with RADIUS, if we can't update the last auth
- * time (a side effect of otp_set_sync_data()), we will be open
- * to a less obvious replay attack over the lifetime of the State
- * attribute (opt->chal_delay): if someone that can see RADIUS
- * traffic captures an Access-Request containing a State
- * attribute, and can cause the NAS to cycle the request id
- * within opt->chal_delay secs, then they can login to the NAS
- * and insert the captured State attribute into the new
- * Access-Request, and we'll give an Access-Accept.
- */
- if (user_info.cardops->challenge(opt->syncdir, &user_info,
+ /* Resync the card. */
+ if (user_info.cardops->challenge(&user_info,
1, 0, challenge) != 0) {
otp_log(OTP_LOG_ERR, "%s: unable to get sync challenge "
"e:%d t:%d for [%s] (for resync)",
log_prefix, 1, 0, username);
rc = OTP_RC_SERVICE_ERR;
- } else if (otp_set_sync_data(opt->syncdir, username, challenge,
- user_info.keyblock) != 0) {
- otp_log(OTP_LOG_ERR,
- "%s: unable to set sync data for [%s] (for resync)",
- log_prefix, username);
- rc = OTP_RC_SERVICE_ERR;
- }
- } else {
- /* Just update failcount, last_auth_time, auth_pos. */
- if (otp_reset_failcount(opt->syncdir, username) != 0) {
- otp_log(OTP_LOG_ERR,
- "%s: unable to reset failcount for [%s]",
- log_prefix, username);
- rc = OTP_RC_SERVICE_ERR;
+ goto auth_done_service_err;
+ /* NB: state not updated. */
}
+ (void) strcpy(user_state.challenge, challenge);
}
+ user_state.failcount = 0;
+ user_state.authtime = time(NULL);
+ user_state.authpos = 0;
+ user_state.updated = 1;
} else {
- if (otp_incr_failcount(opt->syncdir, username) != 0) {
- otp_log(OTP_LOG_ERR,
- "%s: unable to increment failure count for user [%s]",
- log_prefix, username);
- rc = OTP_RC_SERVICE_ERR;
- }
- if (otp_set_last_auth_pos(opt->syncdir, username, auth_pos)) {
- otp_log(OTP_LOG_ERR,
- "%s: unable to set auth window position for user [%s]",
- log_prefix, username);
- rc = OTP_RC_SERVICE_ERR;
- }
/*
- * TODO: consolidate reset_failcount, incr_failcount, s_l_a_p
- * into set_sync_data with longterm lock.
+ * Note that we initialized authpos to -2 to accomodate ewindow2 test
+ * (i == user_state.authpos + 1) above; if we'd only set it to -1,
+ * after a failure authpos+1 == 0 and user can login with only one
+ * correct passcode (viz. the zeroeth passcode).
*/
+ user_state.authpos = authpos;
+ if (++user_state.failcount == UINT_MAX)
+ user_state.failcount--;
+ user_state.updated = 1;
}
auth_done_service_err: /* exit here for system errors */
+ /*
+ * Release and update state.
+ *
+ * We "fail-out" if we can't do this, because for sync mode the
+ * response can be reused until state data is updated, an obvious
+ * replay attack.
+ *
+ * For async mode with RADIUS, if we can't update the last auth
+ * time, we will be open to a less obvious replay attack over the
+ * lifetime of the State attribute (opt->chal_delay): if someone
+ * that can see RADIUS traffic captures an Access-Request containing
+ * a State attribute, and can cause the NAS to cycle the request id
+ * within opt->chal_delay secs, then they can login to the NAS and
+ * insert the captured State attribute into the new Access-Request,
+ * and we'll give an Access-Accept.
+ */
+ if (user_state.locked) {
+ if (otp_state_put(username, &user_state) != 0) {
+ otp_log(OTP_LOG_ERR,
+ "%s: unable to put state for [%s]",
+ log_prefix, username);
+ rc = OTP_RC_SERVICE_ERR; /* no matter what it might have been */
+ }
+ }
+
return rc;
}
int (*name2fm)(const char *name, uint32_t *featuremask);
int (*keystring2keyblock)(const char *keystring, unsigned char keyblock[]);
- int (*challenge)(const char *syncdir, otp_user_info_t *user_info,
- int ewin, int twin, char challenge[]);
+ int (*challenge)(const otp_user_info_t *user_info,
+ unsigned int ewin, int twin, char challenge[]);
int (*response)(otp_user_info_t *user_info, const char *challenge,
char response[OTP_MAX_RESPONSE_LEN + 1]);
} cardops_t;
static const CONF_PARSER module_config[] = {
{ "pwdfile", PW_TYPE_STRING_PTR, offsetof(otp_option_t, pwdfile),
NULL, OTP_PWDFILE },
- { "syncdir", PW_TYPE_STRING_PTR, offsetof(otp_option_t, syncdir),
- NULL, OTP_SYNCDIR },
+ { "lsmd_rp", PW_TYPE_STRING_PTR, offsetof(otp_option_t, lsmd_rp),
+ NULL, OTP_LSMD_RP },
{ "challenge_prompt", PW_TYPE_STRING_PTR,offsetof(otp_option_t,chal_prompt),
NULL, OTP_CHALLENGE_PROMPT },
{ "challenge_length", PW_TYPE_INTEGER, offsetof(otp_option_t, chal_len),
{
otp_option_t *opt;
char *p;
- struct stat st;
/* Set up a storage area for instance data. */
opt = rad_malloc(sizeof(*opt));
}
#endif
- if (stat(opt->syncdir, &st) != 0) {
- otp_log(OTP_LOG_ERR, "syncdir %s error: %s",
- opt->syncdir, strerror(errno));
- free(opt);
- return -1;
- }
- if (st.st_mode != (S_IFDIR|S_IRWXU)) {
- otp_log(OTP_LOG_ERR, "syncdir %s has loose permissions", opt->syncdir);
- free(opt);
- return -1;
- }
-
/* Set the instance name (for use with authorize()) */
opt->name = cf_section_name2(conf);
if (!opt->name)
auth_type_found = 0;
if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
auth_type_found = 1;
- if (strcmp(vp->vp_strvalue, inst->name)) {
+ if (strcmp(vp->strvalue, inst->name)) {
return RLM_MODULE_NOOP;
}
}
"auth: Attribute \"User-Name\" required for authentication.");
return RLM_MODULE_INVALID;
}
- username = request->username->vp_strvalue;
+ username = request->username->strvalue;
if ((data.pwattr = otp_pwe_present(request)) == 0) {
otp_log(OTP_LOG_AUTH, "auth: Attribute \"User-Password\" "
if (inst->allow_async) {
/* Verify the state. */
(void) memset(challenge, 0, sizeof(challenge));
- (void) memcpy(challenge, vp->vp_strvalue, inst->chal_len);
- (void) memcpy(&sflags, vp->vp_strvalue + inst->chal_len, 4);
- (void) memcpy(&then, vp->vp_strvalue + inst->chal_len + 4, 4);
+ (void) memcpy(challenge, vp->strvalue, inst->chal_len);
+ (void) memcpy(&sflags, vp->strvalue + inst->chal_len, 4);
+ (void) memcpy(&then, vp->strvalue + inst->chal_len + 4, 4);
if (otp_gen_state(NULL, &state, challenge,
sflags, then, hmac_key) != 0) {
otp_log(OTP_LOG_ERR, "auth: failed to generate state");
return RLM_MODULE_FAIL;
}
- if (memcmp(state, vp->vp_strvalue, vp->length)) {
+ if (memcmp(state, vp->strvalue, vp->length)) {
otp_log(OTP_LOG_AUTH,
"auth: bad state for [%s]: hmac", username);
free(state);
"otp",
RLM_TYPE_THREAD_SAFE, /* type */
otp_instantiate, /* instantiation */
- otp_detach, /* detach */
{
otp_authenticate, /* authentication */
otp_authorize, /* authorization */
NULL, /* post-proxy */
NULL /* post-auth */
},
+ otp_detach, /* detach */
};
+++ /dev/null
-/*
- * otp_sync.c
- * $Id$
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- * Copyright 2001,2002 Google, Inc.
- * Copyright 2005 Frank Cusack
- */
-
-#include "otp.h"
-#include "otp_sync.h"
-
-#include <assert.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <openssl/des.h> /* des_cblock */
-
-static const char rcsid[] = "$Id$";
-
-
-/*
- * Sync data fields changed slightly between v1 and v2, and were renamed.
- * These routines, however, retain the v1 names. The name:field mapping is:
- * *_last_auth: last_auth_t last authentication time
- * *_failcount: last_auth_s number of consecutive auth failures
- * *_last_auth_pos: last_auth_p window pos. of last auth (not in v1)
- */
-
-
-/* Get stored sync challenge for a given user.
- * Returns 0 on success, non-zero otherwise.
- */
-int
-otp_get_sync_challenge(const char *syncdir, const char *username,
- char challenge[OTP_MAX_CHALLENGE_LEN + 1])
-{
- int rc;
- char *lock;
-
- if ((lock = otp_acquire_sd_lock(syncdir, username)) == NULL)
- return -1;
- rc = otp_get_sd(syncdir, username, challenge, NULL, NULL, NULL);
- otp_release_sd_lock(lock);
- return rc;
-}
-
-
-/*
- * Set sync data for a given user.
- * Returns 0 on success, non-zero otherwise.
- * Side effects:
- * - Resets failure count to 0 on successful return.
- * - Sets last auth time to "now" on successful return.
- * - Sets last auth window position to 0.
- * Because of the failure count reset, this should only be called for/after
- * successful authentications.
- *
- * username: duh
- * challenge: The challenge to be stored.
- * keyblock: The key to be stored. This is for sync modes in which the
- * key changes for successive challenges. (NOT IMPLEMENTED)
- */
-int
-otp_set_sync_data(const char *syncdir, const char *username,
- const char *challenge,
-#ifdef __GNUC__
-__attribute__ ((unused))
-#endif
- const des_cblock keyblock)
-{
- int rc;
- char *lock;
-
- if ((lock = otp_acquire_sd_lock(syncdir, username)) == NULL)
- return -1;
-
- rc = otp_set_sd(syncdir, username, challenge, 0, time(NULL), 0);
- otp_release_sd_lock(lock);
- return rc;
-}
-
-
-/*
- * Return the last time the user authenticated.
- * Returns 0 on success, non-zero otherwise.
- */
-int
-otp_get_last_auth(const char *syncdir, const char *username, time_t *last_auth)
-{
- int rc;
- char *lock;
-
- if ((lock = otp_acquire_sd_lock(syncdir, username)) == NULL)
- return -1;
- rc = otp_get_sd(syncdir, username, NULL, NULL, last_auth, NULL);
- otp_release_sd_lock(lock);
- return rc;
-}
-
-/*
- * Set the last auth time for a user to "now".
- * Returns 0 on success, non-zero otherwise.
- * Note that otp_set_sync_data() also resets the auth time.
- * This function is no longer called, (the failcount() routines do this work),
- * but I'm saving it here for reference.
- */
-int
-otp_upd_last_auth(const char *syncdir, const char *username)
-{
- int failcount, rc;
- char *lock;
- char challenge[OTP_MAX_CHALLENGE_LEN + 1];
- unsigned pos;
-
- if ((lock = otp_acquire_sd_lock(syncdir, username)) == NULL)
- return -1;
-
- rc = otp_get_sd(syncdir, username, challenge, &failcount, NULL, &pos);
- if (rc == 0)
- rc = otp_set_sd(syncdir, username, challenge, failcount, time(NULL),
- pos);
-
- otp_release_sd_lock(lock);
- return rc;
-}
-
-
-/*
- * Atomically increment a user's failed login count.
- * Also updates last_auth.
- */
-int
-otp_incr_failcount(const char *syncdir, const char *username)
-{
- int failcount, rc;
- char *lock;
- char challenge[OTP_MAX_CHALLENGE_LEN + 1];
- unsigned pos;
-
- if ((lock = otp_acquire_sd_lock(syncdir, username)) == NULL)
- return -1;
-
- /* Get current value. */
- rc = otp_get_sd(syncdir, username, challenge, &failcount, NULL, &pos);
- if (rc == 0) {
- /* Increment. */
- if (++failcount == INT_MAX)
- failcount--;
- rc = otp_set_sd(syncdir, username, challenge, failcount, time(NULL),
- pos);
- }
-
- otp_release_sd_lock(lock);
- return rc;
-}
-
-/*
- * Reset failure count to 0. Also updates last_auth and resets pos.
- * Returns 0 on success, non-zero otherwise.
- * This is almost just like otp_incr_failcount().
- * otp_set_sync_data() resets the failcount also, but that's because
- * we keep the failcount and other sync data together; we don't want
- * to necessarily make that visible to our callers (otp_cardops.c).
- */
-int
-otp_reset_failcount(const char *syncdir, const char *username)
-{
- int rc;
- char *lock;
- char challenge[OTP_MAX_CHALLENGE_LEN + 1];
-
- if ((lock = otp_acquire_sd_lock(syncdir, username)) == NULL)
- return -1;
-
- rc = otp_get_sd(syncdir, username, challenge, NULL, NULL, NULL);
- if (rc == 0)
- rc = otp_set_sd(syncdir, username, challenge, 0, time(NULL), 0);
-
- otp_release_sd_lock(lock);
- return rc;
-}
-
-
-/*
- * checks the failure counter.
- * returns 0 if the user is allowed to authenticate, < 0 otherwise:
- * OTP_FC_FAIL_ERR if the user is failed due to internal error,
- * OTP_FC_FAIL_HARD if the user is failed "hard",
- * OTP_FC_FAIL_SOFT if the user is failed "soft".
- * caller does not need to log failures, we do it (in order to be specific).
- */
-int
-otp_check_failcount(const char *username, const otp_option_t *inst)
-{
- time_t last_auth;
- int failcount;
-
- if (otp_get_last_auth(inst->syncdir, username, &last_auth) != 0) {
- otp_log(OTP_LOG_ERR,
- "auth: unable to get last auth time for [%s]", username);
- return OTP_FC_FAIL_ERR;
- }
- if (otp_get_failcount(inst->syncdir, username, &failcount) != 0) {
- otp_log(OTP_LOG_ERR,
- "auth: unable to get failure count for [%s]", username);
- return OTP_FC_FAIL_ERR;
- }
-
- /* Check against hardfail setting. */
- if (inst->hardfail && failcount >= inst->hardfail) {
- otp_log(OTP_LOG_AUTH,
- "auth: %d/%d failed/max authentications for [%s]",
- failcount, inst->hardfail, username);
- if (otp_incr_failcount(inst->syncdir, username) != 0) {
- otp_log(OTP_LOG_ERR,
- "auth: unable to increment failure count for "
- "locked out user [%s]", username);
- }
- return OTP_FC_FAIL_HARD;
- }
-
- /* Check against softfail setting. */
- if (inst->softfail && failcount >= inst->softfail) {
- time_t when;
- int fcount;
-
- /*
- * Determine the next time this user can authenticate.
- *
- * Once we hit softfail, we introduce a 1m delay before the user
- * can authenticate. For each successive failed authentication,
- * we double the delay time, up to a max of 32 minutes. While in
- * the "delay mode" of operation, all authentication ATTEMPTS are
- * considered failures (we don't test if the password is correct).
- * Also, each attempt during the delay period restarts the clock.
- *
- * The advantage of a delay instead of a simple lockout is that an
- * attacker can't lock out a user as easily; the user need only wait
- * a bit before he can authenticate.
- */
- fcount = failcount - inst->softfail;
- when = last_auth + (fcount > 5 ? 32 * 60 : (1 << fcount) * 60);
- if (time(NULL) < when) {
- otp_log(OTP_LOG_AUTH,
- "auth: user [%s] auth too soon while delayed, "
- "%d/%d failed/softfail authentications",
- username, failcount, inst->softfail);
- if (otp_incr_failcount(inst->syncdir, username) != 0) {
- otp_log(OTP_LOG_ERR,
- "auth: unable to increment failure count for "
- "delayed user [%s]", username);
- }
- return OTP_FC_FAIL_SOFT;
- }
- }
-
- return 0;
-}
-
-/*
- * Get the last auth window position for ewindow2.
- * Returns 0 on failure (caller cannot distinguish between failure and
- * a 0 position).
- */
-unsigned
-otp_get_last_auth_pos(const char *syncdir, const char *username)
-{
- int rc;
- char *lock;
- char challenge[OTP_MAX_CHALLENGE_LEN + 1];
- unsigned pos;
-
- if ((lock = otp_acquire_sd_lock(syncdir, username)) == NULL)
- return -1;
-
- rc = otp_get_sd(syncdir, username, challenge, NULL, NULL, &pos);
-
- otp_release_sd_lock(lock);
- return rc ? 0 : pos;
-}
-
-/*
- * Record the last auth window position (for ewindow2).
- */
-int
-otp_set_last_auth_pos(const char *syncdir, const char *username, unsigned pos)
-{
- int rc;
- char *lock;
- char challenge[OTP_MAX_CHALLENGE_LEN + 1];
- int failcount;
- time_t last_auth;
-
- if ((lock = otp_acquire_sd_lock(syncdir, username)) == NULL)
- return -1;
-
- rc = otp_get_sd(syncdir, username, challenge, &failcount, &last_auth, NULL);
- if (rc == 0)
- rc = otp_set_sd(syncdir, username, challenge, failcount, last_auth,
- pos);
-
- otp_release_sd_lock(lock);
- return rc;
-}
-
-
-/*
- * Return the failed login count for a user.
- * Returns 0 on success, non-zero otherwise.
- */
-static int
-otp_get_failcount(const char *syncdir, const char *username, int *failcount)
-{
- int rc;
- char *lock;
-
- if ((lock = otp_acquire_sd_lock(syncdir, username)) == NULL)
- return -1;
- rc = otp_get_sd(syncdir, username, NULL, failcount, NULL, NULL);
- otp_release_sd_lock(lock);
- return rc;
-}
-
-
-/*
- * Sync data is kept in a flat file[s], only because it's easy to implement.
- * It might be worth looking at Berkeley DB, but the flat file implementation
- * gives maximal concurrency with minimal complexity. Performance will be
- * better on filesystems like ext2fs, ffs w/ soft updates, etc, due to
- * the large number of ephemeral dot-files created/destroyed for locking.
- *
- * One file per user is created, and we typically expect that each thread
- * is handling a different user (even if a user is authenticating to
- * multiple NASs/ports, he can't really authenticate simultaneously to
- * each -- unless it's an attack), so this should give us maximal
- * concurrency.
- *
- * The file format is 'version:user:challenge:key:failures:last_auth:'.
- * Version is there to provide easy forward compatibility. The trailing
- * colon is there for the same reason. Future versions must add data to
- * the end. The current version is 1.
- *
- * For performance enhancements, it might be more worthwhile to look at
- * caching the inst->pwdfile data. Users who are disabled should also
- * be cached somehow, to reduce the impact of possible attacks.
- */
-
-
-/*
- * otp_acquire_sd_lock() returns NULL on failure, or a char *
- * which must be passed to otp_release_sd_lock() later.
- */
-static char *
-otp_acquire_sd_lock(const char *syncdir, const char *username)
-{
- char *lockfile;
- int i, fd = -1;
- struct stat st;
-
- /* Verify permissions first. */
- if (stat(syncdir, &st) != 0) {
- otp_log(OTP_LOG_ERR, "otp_acquire_sd_lock: syncdir %s error: %s",
- syncdir, strerror(errno));
- return NULL;
- }
- if (st.st_mode != (S_IFDIR|S_IRUSR|S_IWUSR|S_IXUSR)) {
- otp_log(OTP_LOG_ERR,
- "otp_acquire_sd_lock: syncdir %s has loose permissions",
- syncdir);
- return NULL;
- }
-
- /* We use dotfile locking. */
- lockfile = malloc(strlen(syncdir) + strlen(username) + 3);
- if (!lockfile) {
- otp_log(OTP_LOG_CRIT, "otp_acquire_sd_lock: out of memory");
- return NULL;
- }
- (void) sprintf(lockfile, "%s/.%s", syncdir, username);
-
- /*
- * Try to obtain exclusive access. 10 should be *plenty* of
- * iterations, we don't expect concurrent accesses to the same file,
- * and any accesses should be very quick. This is broken over NFS,
- * but you shouldn't have this data on NFS anyway.
- */
- for (i = 0; i < 10; ++i) {
- if ((fd = open(lockfile, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR)) != -1) {
- break;
- }
- /* break stale locks (older than 60s) */
- if (stat(lockfile, &st) == 0)
- if (st.st_ctime < time(NULL) - 60)
- (void) unlink(lockfile);
-
- usleep(500000); /* 0.5 second */
- }
- if (fd == -1) {
- otp_log(OTP_LOG_ERR,
- "otp_acquire_sd_lock: unable to acquire lock for [%s]",
- username);
- free(lockfile);
- return NULL;
- }
-
- (void) close(fd);
- return lockfile;
-}
-
-static void
-otp_release_sd_lock(char *lockfile)
-{
- (void) unlink(lockfile);
- free(lockfile);
-}
-
-
-/*
- * otp_get_sd() returns 0 on success, non-zero otherwise.
- * On successful returns, challenge, failures, last_auth, pos are filled in,
- * if non-NULL.
- * On unsuccessful returns, challenge, failures, last_auth, pos may be garbage.
- * challenge should be sized as indicated (if non-NULL).
- * The caller must have obtained an exclusive lock on the sync file.
- */
-static int
-otp_get_sd(const char *syncdir, const char *username,
- char challenge[OTP_MAX_CHALLENGE_LEN + 1], int *failures,
- time_t *last_auth, unsigned *pos)
-{
- char syncfile[PATH_MAX + 1];
- FILE *fp;
-
- char syncdata[BUFSIZ];
- char *p, *q;
- unsigned ver = UINT_MAX;
-
- (void) snprintf(syncfile, PATH_MAX, "%s/%s", syncdir, username);
- syncfile[PATH_MAX] = '\0';
-
- /* Open sync file. */
- if ((fp = fopen(syncfile, "r")) == NULL) {
- if (errno != ENOENT) {
- otp_log(OTP_LOG_ERR, "otp_get_sd: unable to open sync file %s: %s",
- syncfile, strerror(errno));
- return -1;
- }
- /*
- * Sync file did not exist. If we can create it, all is well.
- * Set the challenge to something "impossible".
- */
- if (failures)
- *failures = 0;
- return otp_set_sd(syncdir, username, "NEWSTATE", 0, 0, 0);
- }
-
- /* Read sync data. */
- if ((fgets(syncdata, sizeof(syncdata), fp) == NULL) || !strlen(syncdata)) {
- otp_log(OTP_LOG_ERR, "otp_get_sd: unable to read sync data from %s: %s",
- syncfile, strerror(errno));
- (void) fclose(fp);
- return -1;
- }
- (void) fclose(fp);
- p = syncdata;
-
- /* Now, parse the sync data. */
- /* Get the version. */
- if ((q = strchr(p, ':')) == NULL) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data for user %s", username);
- return -1;
- }
- *q++ = '\0';
- if ((sscanf(p, "%u", &ver) != 1) || (ver > 2)) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data (version) for user %s",
- username);
- return -1;
- }
- p = q;
-
- /* Sanity check the username. */
- if ((q = strchr(p, ':')) == NULL) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data (username) for user %s",
- username);
- return -1;
- }
- *q++ = '\0';
- if (strcmp(p, username)) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data (user mismatch) for user %s",
- username);
- return -1;
- }
- p = q;
-
- /* Get challenge. */
- if ((q = strchr(p, ':')) == NULL) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data (challenge) for user %s",
- username);
- return -1;
- }
- *q++ = '\0';
- if (strlen(p) > OTP_MAX_CHALLENGE_LEN) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data (challenge length) for user %s",
- username);
- return -1;
- }
- if (challenge)
- strcpy(challenge, p);
- p = q;
-
- /* Eat key. */
- if ((p = strchr(p, ':')) == NULL) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data (key) for user %s", username);
- return -1;
- }
- p++;
-
- /* Get failures. */
- if ((q = strchr(p, ':')) == NULL) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data (failures) for user %s",
- username);
- return -1;
- }
- *q++ = '\0';
- if (failures && (sscanf(p, "%d", failures) != 1)) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data (failures) for user %s",
- username);
- return -1;
- }
- p = q;
-
- /* Get last_auth. */
- if ((q = strchr(p, ':')) == NULL) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data (last_auth) for user %s",
- username);
- return -1;
- }
- *q++ = '\0';
- if (last_auth && (sscanf(p, "%ld", last_auth) != 1)) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data (last_auth) for user %s",
- username);
- return -1;
- }
- p = q;
-
- /* Get last auth position. */
- if (pos) {
- if (ver == 1) {
- *pos = 0;
- } else if (sscanf(p, "%u", pos) != 1) {
- otp_log(OTP_LOG_ERR,
- "otp_get_sd: invalid sync data (win. pos) for user %s",
- username);
- return -1;
- }
- }
- return 0;
-}
-
-
-/*
- * See otp_get_sd().
- * The caller must have obtained an exclusive lock on the sync file.
- */
-static int
-otp_set_sd(const char *syncdir, const char *username, const char *challenge,
- int failures, time_t last_auth, unsigned pos)
-{
- char syncfile[PATH_MAX + 1];
- FILE *fp;
-
- (void) snprintf(syncfile, PATH_MAX, "%s/%s", syncdir, username);
- syncfile[PATH_MAX] = '\0';
-
- if ((fp = fopen(syncfile, "w")) == NULL) {
- otp_log(OTP_LOG_ERR, "otp_set_sd: unable to open sync file %s: %s",
- syncfile, strerror(errno));
- return -1;
- }
-
- /* Write our (version 2) sync data. */
- (void) fprintf(fp, "2:%s:%s:%s:%d:%ld:%u:\n", username, challenge, "",
- failures, last_auth, pos);
- if (fclose(fp) != 0) {
- otp_log(OTP_LOG_ERR, "otp_set_sd: unable to write sync file %s: %s",
- syncfile, strerror(errno));
- return -1;
- }
-
- return 0;
-}
-
+++ /dev/null
-/*
- * otp_sync.h
- * $Id$
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- * Copyright 2001,2002 Google, Inc.
- * Copyright 2005 Frank Cusack
- */
-
-#ifndef OTP_SYNC_H
-#define OTP_SYNC_H
-
-static int otp_get_failcount(const char *syncdir, const char *username,
- int *failcount);
-static char *otp_acquire_sd_lock(const char *syncdir, const char *username);
-static void otp_release_sd_lock(char *lockfile);
-
-static int otp_get_sd(const char *syncdir, const char *username,
- char challenge[OTP_MAX_CHALLENGE_LEN + 1], int *failures,
- time_t *last_async, unsigned *pos);
-static int otp_set_sd(const char *syncdir, const char *username,
- const char *challenge, int failures, time_t last_async,
- unsigned pos);
-
-#endif /* OTP_SYNC_H */
*/
int
otp_x99_mac(const unsigned char *input, size_t len, unsigned char output[8],
- unsigned char keyblock[OTP_MAX_KEY_LEN])
+ const unsigned char keyblock[OTP_MAX_KEY_LEN])
{
des_key_schedule ks;
des_cblock ivec;