From 283daffc1aba6d4147b308e6475c74804d2a4392 Mon Sep 17 00:00:00 2001 From: Alexey Dotsenko Date: Wed, 29 May 2019 15:51:09 +0300 Subject: [PATCH] radius: add access-challenge (multifactor) authentication skip banning each next OTP for modules with allows_retries option: sec_mod_auth: add check - the repeated password or the password of the following factor is entered radius: passwd_count incremention is related to a auth-message change sec-mod-auth: set more descriptive name for password-retries indicator Signed-off-by: Alexey Dotsenko --- src/auth/radius.c | 53 +++++++++++++++++++++++++++++++++++++++++++++- src/auth/radius.h | 3 +++ src/sec-mod-auth.c | 8 +++++-- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/auth/radius.c b/src/auth/radius.c index a4400f25..10e12daf 100644 --- a/src/auth/radius.c +++ b/src/auth/radius.c @@ -30,6 +30,7 @@ #include "radius.h" #include "auth/common.h" #include "str.h" +#include #ifdef HAVE_RADIUS @@ -124,6 +125,7 @@ static int radius_auth_init(void **ctx, void *pool, void *_vctx, const common_au pctx->pass_msg[0] = 0; pctx->vctx = vctx; + pctx->passwd_counter = 0; default_realm = rc_conf_str(pctx->vctx->rh, "default_realm"); @@ -331,6 +333,18 @@ static int radius_auth_pass(void *ctx, const char *pass, unsigned pass_len) goto cleanup; } + if (pctx->state != NULL) { + if (rc_avpair_add(pctx->vctx->rh, &send, PW_STATE, pctx->state, -1, 0) == NULL) { + syslog(LOG_ERR, + "%s:%u: error in constructing radius message for user '%s'", __func__, __LINE__, + pctx->username); + ret = ERR_AUTH_FAIL; + goto cleanup; + } + talloc_free(pctx->state); + pctx->state = NULL; + } + pctx->pass_msg[0] = 0; ret = rc_aaa(pctx->vctx->rh, pctx->id, send, &recvd, pctx->pass_msg, 1, PW_ACCESS_REQUEST); @@ -421,12 +435,36 @@ static int radius_auth_pass(void *ctx, const char *pass, unsigned pass_len) ret = 0; goto cleanup; + } else if (ret == CHALLENGE_RC) { + + vp = recvd; + + while(vp != NULL) { + if (vp->attribute == PW_STATE && vp->type == PW_TYPE_STRING) { + /* State */ + if (vp->lvalue > 0) + pctx->state = talloc_strdup(pctx, vp->strvalue); + + pctx->id++; + syslog(LOG_DEBUG, "radius-auth: Access-Challenge response stage %u, State %s", pctx->passwd_counter, vp->strvalue); + ret = ERR_AUTH_CONTINUE; + } + vp = vp->next; + } + + /* PW_STATE or PW_REPLY_MESSAGE is empty*/ + if ((pctx->pass_msg[0] == 0) || (pctx->state == NULL)) { + strlcpy(pctx->pass_msg, pass_msg_failed, sizeof(pctx->pass_msg)); + syslog(LOG_ERR, "radius-auth: Access-Challenge with invalid State or Reply-Message"); + ret = ERR_AUTH_FAIL; + } + goto cleanup; } else { fail: if (pctx->pass_msg[0] == 0) strlcpy(pctx->pass_msg, pass_msg_failed, sizeof(pctx->pass_msg)); - if (pctx->retries++ < MAX_PASSWORD_TRIES-1) { + if (pctx->retries++ < MAX_PASSWORD_TRIES-1 && pctx->passwd_counter == 0) { ret = ERR_AUTH_CONTINUE; goto cleanup; } @@ -449,10 +487,23 @@ static int radius_auth_pass(void *ctx, const char *pass, unsigned pass_len) static int radius_auth_msg(void *ctx, void *pool, passwd_msg_st *pst) { struct radius_ctx_st *pctx = ctx; + size_t prompt_hash = 0; if (pctx->pass_msg[0] != 0) pst->msg_str = talloc_strdup(pool, pctx->pass_msg); + if (pctx->state != NULL) { + + /* differentiate password prompts, if the hash of the prompt + * is different. + */ + prompt_hash = hash_any(pctx->pass_msg, strlen(pctx->pass_msg), 0); + if (pctx->prev_prompt_hash != prompt_hash) + pctx->passwd_counter++; + pctx->prev_prompt_hash = prompt_hash; + pst->counter = pctx->passwd_counter; + } + /* use default prompt */ return 0; } diff --git a/src/auth/radius.h b/src/auth/radius.h index bf69e8c8..f8586e62 100644 --- a/src/auth/radius.h +++ b/src/auth/radius.h @@ -68,6 +68,9 @@ struct radius_ctx_st { unsigned id; struct radius_vhost_ctx *vctx; + char *state; + unsigned passwd_counter; + size_t prev_prompt_hash; }; extern const struct auth_mod_st radius_auth_funcs; diff --git a/src/sec-mod-auth.c b/src/sec-mod-auth.c index 73d87138..0e037c5d 100644 --- a/src/sec-mod-auth.c +++ b/src/sec-mod-auth.c @@ -328,6 +328,7 @@ int handle_sec_auth_res(int cfd, sec_mod_st * sec, client_entry_st * e, int resu { int ret; passwd_msg_st pst; + int passwd_retries = 1; if ((result == ERR_AUTH_CONTINUE || result == 0) && e->module) { memset(&pst, 0, sizeof(pst)); @@ -338,12 +339,15 @@ int handle_sec_auth_res(int cfd, sec_mod_st * sec, client_entry_st * e, int resu return ret; } e->msg_str = pst.msg_str; + + /* password requested for the next stage in multifactor auth OR password is requested again at the same stage */ + passwd_retries = (e->passwd_counter < pst.counter) ? 0 : 1; e->passwd_counter = pst.counter; } if (result == ERR_AUTH_CONTINUE) { - /* if the module allows multiple retries for the password */ - if (e->status != PS_AUTH_INIT && e->module && e->module->allows_retries) { + /* if the module allows multiple retries for the password and the password refers to the same stage */ + if (e->status != PS_AUTH_INIT && e->module && e->module->allows_retries && passwd_retries == 1) { sec_mod_add_score_to_ip(sec, e, e->acct_info.remote_ip, e->vhost->perm_config.config->ban_points_wrong_password); }