#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include "extern.h"
#include "otp.h"
static unsigned char hmac_key[16];
static int ninstance = 0;
static const CONF_PARSER module_config[] = {
{ "otpd_rp", PW_TYPE_STRING_PTR, offsetof(otp_option_t, otpd_rp),
NULL, OTP_OTPD_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, challenge_len),
NULL, "6" },
{ "challenge_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, challenge_delay),
NULL, "30" },
{ "allow_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_sync),
NULL, "yes" },
{ "allow_async", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_async),
NULL, "no" },
{ "mschapv2_mppe", PW_TYPE_INTEGER,
offsetof(otp_option_t, mschapv2_mppe_policy), NULL, "2" },
{ "mschapv2_mppe_bits", PW_TYPE_INTEGER,
offsetof(otp_option_t, mschapv2_mppe_types), NULL, "2" },
{ "mschap_mppe", PW_TYPE_INTEGER,
offsetof(otp_option_t, mschap_mppe_policy), NULL, "2" },
{ "mschap_mppe_bits", PW_TYPE_INTEGER,
offsetof(otp_option_t, mschap_mppe_types), NULL, "2" },
{ NULL, -1, 0, NULL, NULL }
};
static int
otp_instantiate(CONF_SECTION *conf, void **instance)
{
otp_option_t *opt;
char *p;
opt = rad_malloc(sizeof(*opt));
(void) memset(opt, 0, sizeof(*opt));
if (cf_section_parse(conf, opt, module_config) < 0) {
free(opt);
return -1;
}
if (!ninstance) {
otp_get_random(hmac_key, sizeof(hmac_key));
otp_pwe_init();
ninstance++;
}
if ((opt->challenge_len < 5) ||
(opt->challenge_len > OTP_MAX_CHALLENGE_LEN)) {
opt->challenge_len = 6;
(void) radlog(L_ERR, "rlm_otp: %s: invalid challenge_length, range 5-%d, "
"using default of 6",
__func__, OTP_MAX_CHALLENGE_LEN);
}
p = strchr(opt->chal_prompt, '%');
if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
strncmp(p,"%s",2)) {
free(opt->chal_prompt);
opt->chal_prompt = strdup(OTP_CHALLENGE_PROMPT);
(void) radlog(L_ERR, "rlm_otp: %s: invalid challenge_prompt, "
"using default of \"%s\"",
__func__, OTP_CHALLENGE_PROMPT);
}
if (!opt->allow_sync && !opt->allow_async) {
(void) radlog(L_ERR, "rlm_otp: %s: at least one of "
"{allow_async, allow_sync} must be set",
__func__);
free(opt);
return -1;
}
if ((opt->mschapv2_mppe_policy > 2) || (opt->mschapv2_mppe_policy < 0)) {
opt->mschapv2_mppe_policy = 2;
(void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschapv2_mppe, "
"using default of 2",
__func__);
}
if ((opt->mschapv2_mppe_types > 2) || (opt->mschapv2_mppe_types < 0)) {
opt->mschapv2_mppe_types = 2;
(void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschapv2_mppe_bits, "
"using default of 2",
__func__);
}
if ((opt->mschap_mppe_policy > 2) || (opt->mschap_mppe_policy < 0)) {
opt->mschap_mppe_policy = 2;
(void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschap_mppe, "
"using default of 2",
__func__);
}
if (opt->mschap_mppe_types != 2) {
opt->mschap_mppe_types = 2;
(void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschap_mppe_bits, "
"using default of 2",
__func__);
}
opt->name = cf_section_name2(conf);
if (!opt->name)
opt->name = cf_section_name1(conf);
if (!opt->name) {
(void) radlog(L_ERR|L_CONS,
"rlm_otp: %s: no instance name (this can't happen)",
__func__);
free(opt);
return -1;
}
*instance = opt;
return 0;
}
static int
otp_authorize(void *instance, REQUEST *request)
{
otp_option_t *inst = (otp_option_t *) instance;
char challenge[OTP_MAX_CHALLENGE_LEN + 1];
int auth_type_found;
{
VALUE_PAIR *vp;
auth_type_found = 0;
if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
auth_type_found = 1;
if (strcmp(vp->vp_strvalue, inst->name))
return RLM_MODULE_NOOP;
}
}
if (pairfind(request->packet->vps, PW_STATE) != NULL) {
DEBUG("rlm_otp: autz: Found response to Access-Challenge");
return RLM_MODULE_OK;
}
if (!request->username) {
(void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
"for authentication.",
__func__);
return RLM_MODULE_INVALID;
}
if (otp_pwe_present(request) == 0) {
(void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Password\" "
"or equivalent required for authentication.",
__func__);
return RLM_MODULE_INVALID;
}
if (inst->allow_sync && !inst->allow_async) {
if (!auth_type_found)
pairadd(&request->config_items,
pairmake("Auth-Type", inst->name, T_OP_EQ));
return RLM_MODULE_OK;
}
otp_async_challenge(challenge, inst->challenge_len);
{
int32_t now = htonl(time(NULL));
char state[OTP_MAX_RADSTATE_LEN];
if (otp_gen_state(state, NULL, challenge, inst->challenge_len, 0,
now, hmac_key) != 0) {
(void) radlog(L_ERR, "rlm_otp: %s: failed to generate radstate",__func__);
return RLM_MODULE_FAIL;
}
pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
}
{
char *u_challenge;
u_challenge = rad_malloc(strlen(inst->chal_prompt) +
OTP_MAX_CHALLENGE_LEN + 1);
(void) sprintf(u_challenge, inst->chal_prompt, challenge);
pairadd(&request->reply->vps,
pairmake("Reply-Message", u_challenge, T_OP_EQ));
free(u_challenge);
}
request->reply->code = PW_ACCESS_CHALLENGE;
DEBUG("rlm_otp: Sending Access-Challenge.");
if (!auth_type_found)
pairadd(&request->config_items, pairmake("Auth-Type", inst->name, T_OP_EQ));
return RLM_MODULE_HANDLED;
}
static int
otp_authenticate(void *instance, REQUEST *request)
{
otp_option_t *inst = (otp_option_t *) instance;
char *username;
int rc;
otp_pwe_t pwe;
VALUE_PAIR *vp;
unsigned char challenge[OTP_MAX_CHALLENGE_LEN];
char passcode[OTP_MAX_PASSCODE_LEN + 1];
challenge[0] = '\0';
if (!request->username) {
(void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
"for authentication.",
__func__);
return RLM_MODULE_INVALID;
}
username = request->username->vp_strvalue;
if ((pwe = otp_pwe_present(request)) == 0) {
(void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Password\" "
"or equivalent required for authentication.",
__func__);
return RLM_MODULE_INVALID;
}
pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
"rlm_otp", T_OP_EQ));
pairadd(&request->packet->vps, pairmake("Module-Success-Message",
"rlm_otp", T_OP_EQ));
if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
unsigned char state[OTP_MAX_RADSTATE_LEN];
unsigned char raw_state[OTP_MAX_RADSTATE_LEN];
unsigned char rad_state[OTP_MAX_RADSTATE_LEN];
int32_t then;
int e_length;
e_length = inst->challenge_len * 2 + 8 + 8 + 32;
if (vp->length != e_length) {
(void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: length",
__func__, username);
return RLM_MODULE_INVALID;
}
(void) memcpy(rad_state, vp->vp_strvalue, vp->length);
rad_state[e_length] = '\0';
if (otp_a2x(rad_state, raw_state) == -1) {
(void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: not hex",
__func__, username);
return RLM_MODULE_INVALID;
}
(void) memcpy(challenge, raw_state, inst->challenge_len);
(void) memcpy(&then, raw_state + inst->challenge_len + 4, 4);
if (otp_gen_state(NULL, state, challenge, inst->challenge_len, 0,
then, hmac_key) != 0) {
(void) radlog(L_ERR, "rlm_otp: %s: failed to generate radstate",
__func__);
return RLM_MODULE_FAIL;
}
if (memcmp(state, vp->vp_strvalue, vp->length)) {
(void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: hmac",
__func__, username);
return RLM_MODULE_REJECT;
}
then = ntohl(then);
if (time(NULL) - then > inst->challenge_delay) {
(void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: expired",
__func__, username);
return RLM_MODULE_REJECT;
}
}
rc = otp_pw_valid(request, pwe, challenge, inst, passcode);
if (rc == RLM_MODULE_OK)
otp_mppe(request, pwe, inst, passcode);
return rc;
}
static int
otp_detach(void *instance)
{
free(instance);
if (--ninstance == 0)
(void) memset(hmac_key, 0, sizeof(hmac_key));
return 0;
}
module_t rlm_otp = {
RLM_MODULE_INIT,
"otp",
RLM_TYPE_THREAD_SAFE,
otp_instantiate,
otp_detach,
{
otp_authenticate,
otp_authorize,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
},
};