643 lines
19 KiB
C
643 lines
19 KiB
C
/* Osmocom Visitor Location Register (VLR) Authentication FSM */
|
|
|
|
/* (C) 2016 by Harald Welte <laforge@gnumonks.org>
|
|
*
|
|
* All Rights Reserved
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation; either version 3 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
|
|
#include <osmocom/core/fsm.h>
|
|
#include <osmocom/core/utils.h>
|
|
#include <osmocom/gsm/gsup.h>
|
|
#include <osmocom/msc/vlr.h>
|
|
#include <osmocom/msc/debug.h>
|
|
|
|
#include "vlr_core.h"
|
|
#include "vlr_auth_fsm.h"
|
|
|
|
#define S(x) (1 << (x))
|
|
|
|
static const struct value_string fsm_auth_event_names[] = {
|
|
OSMO_VALUE_STRING(VLR_AUTH_E_START),
|
|
OSMO_VALUE_STRING(VLR_AUTH_E_HLR_SAI_ACK),
|
|
OSMO_VALUE_STRING(VLR_AUTH_E_HLR_SAI_NACK),
|
|
OSMO_VALUE_STRING(VLR_AUTH_E_HLR_SAI_ABORT),
|
|
OSMO_VALUE_STRING(VLR_AUTH_E_MS_AUTH_RESP),
|
|
OSMO_VALUE_STRING(VLR_AUTH_E_MS_AUTH_FAIL),
|
|
OSMO_VALUE_STRING(VLR_AUTH_E_MS_ID_IMSI),
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/* private state of the auth_fsm_instance */
|
|
struct auth_fsm_priv {
|
|
struct vlr_subscr *vsub;
|
|
bool by_imsi;
|
|
bool is_r99;
|
|
bool is_utran;
|
|
bool auth_requested;
|
|
|
|
int auth_tuple_max_reuse_count; /* see vlr->cfg instead */
|
|
};
|
|
|
|
/***********************************************************************
|
|
* Utility functions
|
|
***********************************************************************/
|
|
|
|
/* Always use either vlr_subscr_get_auth_tuple() or vlr_subscr_has_auth_tuple()
|
|
* instead, to ensure proper use count.
|
|
* Return an auth tuple with the lowest use_count among the auth tuples. If
|
|
* max_reuse_count >= 0, return NULL if all available auth tuples have a use
|
|
* count > max_reuse_count. If max_reuse_count is negative, return a currently
|
|
* least used auth tuple without enforcing a maximum use count. If there are
|
|
* no auth tuples, return NULL.
|
|
*/
|
|
static struct vlr_auth_tuple *
|
|
_vlr_subscr_next_auth_tuple(struct vlr_subscr *vsub, int max_reuse_count)
|
|
{
|
|
unsigned int count;
|
|
unsigned int idx;
|
|
struct vlr_auth_tuple *at = NULL;
|
|
unsigned int key_seq = VLR_KEY_SEQ_INVAL;
|
|
|
|
if (!vsub)
|
|
return NULL;
|
|
|
|
if (vsub->last_tuple)
|
|
key_seq = vsub->last_tuple->key_seq;
|
|
|
|
if (key_seq == VLR_KEY_SEQ_INVAL)
|
|
/* Start with 0 after increment modulo array size */
|
|
idx = ARRAY_SIZE(vsub->auth_tuples) - 1;
|
|
else
|
|
idx = key_seq;
|
|
|
|
for (count = ARRAY_SIZE(vsub->auth_tuples); count > 0; count--) {
|
|
idx = (idx + 1) % ARRAY_SIZE(vsub->auth_tuples);
|
|
|
|
if (vsub->auth_tuples[idx].key_seq == VLR_KEY_SEQ_INVAL)
|
|
continue;
|
|
|
|
if (!at || vsub->auth_tuples[idx].use_count < at->use_count)
|
|
at = &vsub->auth_tuples[idx];
|
|
}
|
|
|
|
if (!at || (max_reuse_count >= 0 && at->use_count > max_reuse_count))
|
|
return NULL;
|
|
|
|
return at;
|
|
}
|
|
|
|
/* Return an auth tuple and increment its use count. */
|
|
static struct vlr_auth_tuple *
|
|
vlr_subscr_get_auth_tuple(struct vlr_subscr *vsub, int max_reuse_count)
|
|
{
|
|
struct vlr_auth_tuple *at = _vlr_subscr_next_auth_tuple(vsub,
|
|
max_reuse_count);
|
|
if (!at)
|
|
return NULL;
|
|
at->use_count++;
|
|
return at;
|
|
}
|
|
|
|
/* Return whether an auth tuple with a matching use_count is available. */
|
|
static bool vlr_subscr_has_auth_tuple(struct vlr_subscr *vsub,
|
|
int max_reuse_count)
|
|
{
|
|
return _vlr_subscr_next_auth_tuple(vsub, max_reuse_count) != NULL;
|
|
}
|
|
|
|
static bool check_auth_resp(struct vlr_subscr *vsub, bool is_r99,
|
|
bool is_utran, const uint8_t *res,
|
|
uint8_t res_len)
|
|
{
|
|
struct vlr_auth_tuple *at = vsub->last_tuple;
|
|
struct osmo_auth_vector *vec = &at->vec;
|
|
bool check_umts;
|
|
bool res_is_umts_aka;
|
|
OSMO_ASSERT(at);
|
|
|
|
LOGVSUBP(LOGL_DEBUG, vsub, "AUTH on %s received %s: %s (%u bytes)\n",
|
|
is_utran ? "UTRAN" : "GERAN",
|
|
is_utran ? "RES" : "SRES/RES",
|
|
osmo_hexdump_nospc(res, res_len), res_len);
|
|
|
|
/* RES must be present and at least 32bit */
|
|
if (!res || !res_len) {
|
|
LOGVSUBP(LOGL_NOTICE, vsub, "AUTH SRES/RES missing\n");
|
|
goto out_false;
|
|
}
|
|
|
|
/* We're deciding the UMTS AKA-ness of the response by the RES size. So let's make sure we can't
|
|
* mix them up by size. On UTRAN, we expect full length RES always, no way to mix up there. */
|
|
if (!is_utran && vec->res_len == sizeof(vec->sres))
|
|
LOGVSUBP(LOGL_ERROR, vsub, "Unforeseen situation: UMTS AKA's RES length"
|
|
" equals the size of SRES: %u -- this code wants to differentiate"
|
|
" the two by their size, which won't work properly now.\n", vec->res_len);
|
|
|
|
/* RES must be either vec->res_len (UMTS AKA) or sizeof(sres) (GSM AKA) */
|
|
if (res_len == vec->res_len)
|
|
res_is_umts_aka = true;
|
|
else if (res_len == sizeof(vec->sres))
|
|
res_is_umts_aka = false;
|
|
else {
|
|
if (is_utran)
|
|
LOGVSUBP(LOGL_NOTICE, vsub, "AUTH RES has invalid length: %u."
|
|
" Expected %u (UMTS AKA)\n",
|
|
res_len, vec->res_len);
|
|
else
|
|
LOGVSUBP(LOGL_NOTICE, vsub, "AUTH SRES/RES has invalid length: %u."
|
|
" Expected either %zu (GSM AKA) or %u (UMTS AKA)\n",
|
|
res_len, sizeof(vec->sres), vec->res_len);
|
|
goto out_false;
|
|
}
|
|
|
|
check_umts = (is_r99
|
|
&& (vec->auth_types & OSMO_AUTH_TYPE_UMTS)
|
|
&& res_is_umts_aka);
|
|
|
|
/* Even on an R99 capable MS with a UMTS AKA capable USIM,
|
|
* the MS may still choose to only perform GSM AKA, as
|
|
* long as the bearer is GERAN -- never on UTRAN: */
|
|
if (is_utran && !check_umts) {
|
|
LOGVSUBP(LOGL_ERROR, vsub,
|
|
"AUTH via UTRAN, cannot allow GSM AKA"
|
|
" (MS is %sR99 capable, vec has %sUMTS AKA tokens, res_len=%u is %s)\n",
|
|
is_r99 ? "" : "NOT ",
|
|
(vec->auth_types & OSMO_AUTH_TYPE_UMTS) ? "" : "NO ",
|
|
res_len, (res_len == vec->res_len)? "valid" : "INVALID on UTRAN");
|
|
goto out_false;
|
|
}
|
|
|
|
if (check_umts) {
|
|
if (res_len != vec->res_len
|
|
|| memcmp(res, vec->res, res_len)) {
|
|
LOGVSUBP(LOGL_INFO, vsub, "UMTS AUTH failure:"
|
|
" mismatching res (expected res=%s)\n",
|
|
osmo_hexdump(vec->res, vec->res_len));
|
|
goto out_false;
|
|
}
|
|
|
|
LOGVSUBP(LOGL_INFO, vsub, "AUTH established UMTS security"
|
|
" context\n");
|
|
vsub->sec_ctx = VLR_SEC_CTX_UMTS;
|
|
return true;
|
|
} else {
|
|
if (res_len != sizeof(vec->sres)
|
|
|| memcmp(res, vec->sres, sizeof(vec->sres))) {
|
|
LOGVSUBP(LOGL_INFO, vsub, "GSM AUTH failure:"
|
|
" mismatching sres (expected sres=%s)\n",
|
|
osmo_hexdump(vec->sres, sizeof(vec->sres)));
|
|
goto out_false;
|
|
}
|
|
|
|
LOGVSUBP(LOGL_INFO, vsub, "AUTH established GSM security"
|
|
" context\n");
|
|
vsub->sec_ctx = VLR_SEC_CTX_GSM;
|
|
return true;
|
|
}
|
|
|
|
out_false:
|
|
vsub->sec_ctx = VLR_SEC_CTX_NONE;
|
|
return false;
|
|
}
|
|
|
|
static void auth_fsm_onenter_failed(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
|
{
|
|
struct auth_fsm_priv *afp = fi->priv;
|
|
struct vlr_subscr *vsub = afp->vsub;
|
|
|
|
/* If authentication hasn't even started, e.g. the HLR sent no auth
|
|
* info, then we also don't need to tell the HLR about an auth failure.
|
|
*/
|
|
if (afp->auth_requested) {
|
|
int rc = vlr_subscr_tx_auth_fail_rep(vsub);
|
|
if (rc < 0)
|
|
LOGVSUBP(LOGL_ERROR, vsub, "Failed to communicate AUTH failure to HLR\n");
|
|
}
|
|
}
|
|
|
|
static const char *vlr_auth_fsm_result_name(enum gsm48_reject_value result)
|
|
{
|
|
if (!result)
|
|
return "PASSED";
|
|
return get_value_string(gsm48_gmm_cause_names, result);
|
|
}
|
|
|
|
/* Terminate the Auth FSM Instance and notify parent */
|
|
static void auth_fsm_term(struct osmo_fsm_inst *fi, enum gsm48_reject_value result)
|
|
{
|
|
LOGPFSM(fi, "Authentication terminating with result %s\n",
|
|
vlr_auth_fsm_result_name(result));
|
|
|
|
/* Do one final state transition (mostly for logging purpose) */
|
|
if (!result)
|
|
osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTHENTICATED, 0, 0);
|
|
else
|
|
osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTH_FAILED, 0, 0);
|
|
|
|
/* return the result to the parent FSM */
|
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, &result);
|
|
}
|
|
|
|
static void auth_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
|
{
|
|
struct auth_fsm_priv *afp = fi->priv;
|
|
struct vlr_subscr *vsub = afp->vsub;
|
|
vsub->auth_fsm = NULL;
|
|
}
|
|
|
|
/* back-end function transmitting authentication. Caller ensures we have valid
|
|
* tuple */
|
|
static int _vlr_subscr_authenticate(struct osmo_fsm_inst *fi)
|
|
{
|
|
struct auth_fsm_priv *afp = fi->priv;
|
|
struct vlr_subscr *vsub = afp->vsub;
|
|
struct vlr_auth_tuple *at;
|
|
bool use_umts_aka;
|
|
|
|
/* Caller ensures we have vectors available */
|
|
at = vlr_subscr_get_auth_tuple(vsub, afp->auth_tuple_max_reuse_count);
|
|
if (!at) {
|
|
LOGPFSML(fi, LOGL_ERROR, "A previous check ensured that an"
|
|
" auth tuple was available, but now there is in fact"
|
|
" none.\n");
|
|
auth_fsm_term(fi, GSM48_REJECT_NETWORK_FAILURE);
|
|
return -1;
|
|
}
|
|
|
|
use_umts_aka = vlr_use_umts_aka(&at->vec, afp->is_r99);
|
|
LOGPFSM(fi, "got auth tuple: use_count=%d key_seq=%d"
|
|
" -- will use %s AKA (is_r99=%s, at->vec.auth_types=0x%x)\n",
|
|
at->use_count, at->key_seq,
|
|
use_umts_aka ? "UMTS" : "GSM", afp->is_r99 ? "yes" : "no", at->vec.auth_types);
|
|
|
|
/* Transmit auth req to subscriber */
|
|
afp->auth_requested = true;
|
|
vsub->last_tuple = at;
|
|
vsub->vlr->ops.tx_auth_req(vsub->msc_conn_ref, at, use_umts_aka);
|
|
return 0;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* FSM State Action functions
|
|
***********************************************************************/
|
|
|
|
/* Initial State of TS 23.018 AUT_VLR */
|
|
static void auth_fsm_needs_auth(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
|
{
|
|
struct auth_fsm_priv *afp = fi->priv;
|
|
struct vlr_subscr *vsub = afp->vsub;
|
|
|
|
OSMO_ASSERT(event == VLR_AUTH_E_START);
|
|
|
|
/* Start off with the default max_reuse_count, possibly change that if we
|
|
* need to re-use an old tuple. */
|
|
afp->auth_tuple_max_reuse_count = vsub->vlr->cfg.auth_tuple_max_reuse_count;
|
|
|
|
/* Check if we have vectors available */
|
|
if (!vlr_subscr_has_auth_tuple(vsub, afp->auth_tuple_max_reuse_count)) {
|
|
/* Obtain_Authentication_Sets_VLR */
|
|
int rc = vlr_subscr_req_sai(vsub, NULL, NULL);
|
|
if (rc < 0)
|
|
LOGPFSM(fi, "Failed to request Authentication Sets from VLR\n");
|
|
osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_NEEDS_AUTH_WAIT_AI,
|
|
GSM_29002_TIMER_M, 0);
|
|
} else {
|
|
/* go straight ahead with sending auth request */
|
|
osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP,
|
|
vlr_timer(vsub->vlr, 3260), 3260);
|
|
_vlr_subscr_authenticate(fi);
|
|
}
|
|
}
|
|
|
|
/* Waiting for Authentication Info from HLR */
|
|
static void auth_fsm_wait_ai(struct osmo_fsm_inst *fi, uint32_t event,
|
|
void *data)
|
|
{
|
|
struct auth_fsm_priv *afp = fi->priv;
|
|
struct vlr_subscr *vsub = afp->vsub;
|
|
struct osmo_gsup_message *gsup = data;
|
|
enum gsm48_reject_value gsm48_rej;
|
|
|
|
if (event == VLR_AUTH_E_HLR_SAI_NACK)
|
|
LOGPFSM(fi, "GSUP: rx Auth Info Error cause: %d: %s\n",
|
|
gsup->cause,
|
|
get_value_string(gsm48_gmm_cause_names, gsup->cause));
|
|
|
|
/* We are in what corresponds to the
|
|
* Wait_For_Authentication_Sets state of TS 23.018 OAS_VLR */
|
|
if ((event == VLR_AUTH_E_HLR_SAI_ACK && !gsup->num_auth_vectors)
|
|
|| (event == VLR_AUTH_E_HLR_SAI_NACK &&
|
|
gsup->cause != GMM_CAUSE_IMSI_UNKNOWN)
|
|
|| (event == VLR_AUTH_E_HLR_SAI_ABORT)) {
|
|
if (vsub->vlr->cfg.auth_reuse_old_sets_on_error
|
|
&& vlr_subscr_has_auth_tuple(vsub, -1)) {
|
|
/* To re-use an old tuple, disable the max_reuse_count
|
|
* constraint. */
|
|
afp->auth_tuple_max_reuse_count = -1;
|
|
goto pass;
|
|
}
|
|
}
|
|
|
|
switch (event) {
|
|
case VLR_AUTH_E_HLR_SAI_ACK:
|
|
if (!gsup->num_auth_vectors) {
|
|
auth_fsm_term(fi, GSM48_REJECT_NETWORK_FAILURE);
|
|
return;
|
|
}
|
|
vlr_subscr_update_tuples(vsub, gsup);
|
|
goto pass;
|
|
break;
|
|
case VLR_AUTH_E_HLR_SAI_NACK:
|
|
case VLR_AUTH_E_HLR_SAI_ABORT:
|
|
vlr_gmm_cause_to_mm_cause(gsup->cause, &gsm48_rej);
|
|
auth_fsm_term(fi, gsm48_rej);
|
|
break;
|
|
}
|
|
|
|
return;
|
|
pass:
|
|
osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP,
|
|
vlr_timer(vsub->vlr, 3260), 3260);
|
|
_vlr_subscr_authenticate(fi);
|
|
}
|
|
|
|
/* Waiting for Authentication Response from MS */
|
|
static void auth_fsm_wait_auth_resp(struct osmo_fsm_inst *fi, uint32_t event,
|
|
void *data)
|
|
{
|
|
struct auth_fsm_priv *afp = fi->priv;
|
|
struct vlr_subscr *vsub = afp->vsub;
|
|
struct vlr_instance *vlr = vsub->vlr;
|
|
struct vlr_auth_resp_par *par = data;
|
|
int rc;
|
|
|
|
switch (event) {
|
|
case VLR_AUTH_E_MS_AUTH_RESP:
|
|
rc = check_auth_resp(vsub, par->is_r99, par->is_utran,
|
|
par->res, par->res_len);
|
|
if (rc == false) {
|
|
if (!afp->by_imsi) {
|
|
vlr->ops.tx_id_req(vsub->msc_conn_ref,
|
|
GSM_MI_TYPE_IMSI);
|
|
osmo_fsm_inst_state_chg(fi,
|
|
VLR_SUB_AS_WAIT_ID_IMSI,
|
|
vlr_timer(vlr, 3270), 3270);
|
|
} else {
|
|
auth_fsm_term(fi, GSM48_REJECT_ILLEGAL_MS);
|
|
}
|
|
} else {
|
|
auth_fsm_term(fi, 0);
|
|
}
|
|
break;
|
|
case VLR_AUTH_E_MS_AUTH_FAIL:
|
|
if (par->auts) {
|
|
/* First failure, start re-sync attempt */
|
|
rc = vlr_subscr_req_sai(vsub, par->auts,
|
|
vsub->last_tuple->vec.rand);
|
|
osmo_fsm_inst_state_chg(fi,
|
|
VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC,
|
|
GSM_29002_TIMER_M, 0);
|
|
} else
|
|
auth_fsm_term(fi, GSM48_REJECT_ILLEGAL_MS);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Waiting for Authentication Info from HLR (resync case) */
|
|
static void auth_fsm_wait_ai_resync(struct osmo_fsm_inst *fi,
|
|
uint32_t event, void *data)
|
|
{
|
|
struct auth_fsm_priv *afp = fi->priv;
|
|
struct vlr_subscr *vsub = afp->vsub;
|
|
struct osmo_gsup_message *gsup = data;
|
|
|
|
/* We are in what corresponds to the
|
|
* Wait_For_Authentication_Sets state of TS 23.018 OAS_VLR */
|
|
if ((event == VLR_AUTH_E_HLR_SAI_ACK && !gsup->num_auth_vectors) ||
|
|
(event == VLR_AUTH_E_HLR_SAI_NACK &&
|
|
gsup->cause != GMM_CAUSE_IMSI_UNKNOWN) ||
|
|
(event == VLR_AUTH_E_HLR_SAI_ABORT)) {
|
|
/* result = procedure error */
|
|
auth_fsm_term(fi, GSM48_REJECT_NETWORK_FAILURE);
|
|
}
|
|
switch (event) {
|
|
case VLR_AUTH_E_HLR_SAI_ACK:
|
|
vlr_subscr_update_tuples(vsub, gsup);
|
|
osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP_RESYNC,
|
|
vlr_timer(vsub->vlr, 3260), 3260);
|
|
_vlr_subscr_authenticate(fi);
|
|
break;
|
|
case VLR_AUTH_E_HLR_SAI_NACK:
|
|
auth_fsm_term(fi,
|
|
gsup->cause == GMM_CAUSE_IMSI_UNKNOWN?
|
|
GSM48_REJECT_IMSI_UNKNOWN_IN_HLR
|
|
: GSM48_REJECT_NETWORK_FAILURE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Waiting for AUTH RESP from MS (re-sync case) */
|
|
static void auth_fsm_wait_auth_resp_resync(struct osmo_fsm_inst *fi,
|
|
uint32_t event, void *data)
|
|
{
|
|
struct auth_fsm_priv *afp = fi->priv;
|
|
struct vlr_subscr *vsub = afp->vsub;
|
|
struct vlr_auth_resp_par *par = data;
|
|
struct vlr_instance *vlr = vsub->vlr;
|
|
int rc;
|
|
|
|
switch (event) {
|
|
case VLR_AUTH_E_MS_AUTH_RESP:
|
|
rc = check_auth_resp(vsub, par->is_r99, par->is_utran,
|
|
par->res, par->res_len);
|
|
if (rc == false) {
|
|
if (!afp->by_imsi) {
|
|
vlr->ops.tx_id_req(vsub->msc_conn_ref,
|
|
GSM_MI_TYPE_IMSI);
|
|
osmo_fsm_inst_state_chg(fi,
|
|
VLR_SUB_AS_WAIT_ID_IMSI,
|
|
vlr_timer(vlr, 3270), 3270);
|
|
} else {
|
|
/* Result = Aborted */
|
|
auth_fsm_term(fi, GSM48_REJECT_SYNCH_FAILURE);
|
|
}
|
|
} else {
|
|
/* Result = Pass */
|
|
auth_fsm_term(fi, 0);
|
|
}
|
|
break;
|
|
case VLR_AUTH_E_MS_AUTH_FAIL:
|
|
/* Second failure: Result = Fail */
|
|
auth_fsm_term(fi, GSM48_REJECT_SYNCH_FAILURE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* AUT_VLR waiting for Obtain_IMSI_VLR result */
|
|
static void auth_fsm_wait_imsi(struct osmo_fsm_inst *fi, uint32_t event,
|
|
void *data)
|
|
{
|
|
struct auth_fsm_priv *afp = fi->priv;
|
|
struct vlr_subscr *vsub = afp->vsub;
|
|
const char *mi_string = data;
|
|
|
|
switch (event) {
|
|
case VLR_AUTH_E_MS_ID_IMSI:
|
|
if (vsub->imsi[0]
|
|
&& !vlr_subscr_matches_imsi(vsub, mi_string)) {
|
|
LOGVSUBP(LOGL_ERROR, vsub, "IMSI in ID RESP differs:"
|
|
" %s\n", mi_string);
|
|
} else {
|
|
strncpy(vsub->imsi, mi_string, sizeof(vsub->imsi));
|
|
vsub->imsi[sizeof(vsub->imsi)-1] = '\0';
|
|
}
|
|
/* retry with identity=IMSI */
|
|
afp->by_imsi = true;
|
|
osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_NEEDS_AUTH, 0, 0);
|
|
osmo_fsm_inst_dispatch(fi, VLR_AUTH_E_START, NULL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const struct osmo_fsm_state auth_fsm_states[] = {
|
|
[VLR_SUB_AS_NEEDS_AUTH] = {
|
|
.name = OSMO_STRINGIFY(VLR_SUB_AS_NEEDS_AUTH),
|
|
.in_event_mask = S(VLR_AUTH_E_START),
|
|
.out_state_mask = S(VLR_SUB_AS_NEEDS_AUTH_WAIT_AI) |
|
|
S(VLR_SUB_AS_WAIT_RESP),
|
|
.action = auth_fsm_needs_auth,
|
|
},
|
|
[VLR_SUB_AS_NEEDS_AUTH_WAIT_AI] = {
|
|
.name = OSMO_STRINGIFY(VLR_SUB_AS_NEEDS_AUTH_WAIT_AI),
|
|
.in_event_mask = S(VLR_AUTH_E_HLR_SAI_ACK) |
|
|
S(VLR_AUTH_E_HLR_SAI_NACK),
|
|
.out_state_mask = S(VLR_SUB_AS_AUTH_FAILED) |
|
|
S(VLR_SUB_AS_WAIT_RESP),
|
|
.action = auth_fsm_wait_ai,
|
|
},
|
|
[VLR_SUB_AS_WAIT_RESP] = {
|
|
.name = OSMO_STRINGIFY(VLR_SUB_AS_WAIT_RESP),
|
|
.in_event_mask = S(VLR_AUTH_E_MS_AUTH_RESP) |
|
|
S(VLR_AUTH_E_MS_AUTH_FAIL),
|
|
.out_state_mask = S(VLR_SUB_AS_WAIT_ID_IMSI) |
|
|
S(VLR_SUB_AS_AUTH_FAILED) |
|
|
S(VLR_SUB_AS_AUTHENTICATED) |
|
|
S(VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC),
|
|
.action = auth_fsm_wait_auth_resp,
|
|
},
|
|
[VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC] = {
|
|
.name = OSMO_STRINGIFY(VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC),
|
|
.in_event_mask = S(VLR_AUTH_E_HLR_SAI_ACK) |
|
|
S(VLR_AUTH_E_HLR_SAI_NACK),
|
|
.out_state_mask = S(VLR_SUB_AS_AUTH_FAILED) |
|
|
S(VLR_SUB_AS_WAIT_RESP_RESYNC),
|
|
.action = auth_fsm_wait_ai_resync,
|
|
},
|
|
[VLR_SUB_AS_WAIT_RESP_RESYNC] = {
|
|
.name = OSMO_STRINGIFY(VLR_SUB_AS_WAIT_RESP_RESYNC),
|
|
.in_event_mask = S(VLR_AUTH_E_MS_AUTH_RESP) |
|
|
S(VLR_AUTH_E_MS_AUTH_FAIL),
|
|
.out_state_mask = S(VLR_SUB_AS_AUTH_FAILED) |
|
|
S(VLR_SUB_AS_AUTHENTICATED),
|
|
.action = auth_fsm_wait_auth_resp_resync,
|
|
},
|
|
[VLR_SUB_AS_WAIT_ID_IMSI] = {
|
|
.name = OSMO_STRINGIFY(VLR_SUB_AS_WAIT_ID_IMSI),
|
|
.in_event_mask = S(VLR_AUTH_E_MS_ID_IMSI),
|
|
.out_state_mask = S(VLR_SUB_AS_NEEDS_AUTH),
|
|
.action = auth_fsm_wait_imsi,
|
|
},
|
|
[VLR_SUB_AS_AUTHENTICATED] = {
|
|
.name = OSMO_STRINGIFY(VLR_SUB_AS_AUTHENTICATED),
|
|
.in_event_mask = 0,
|
|
.out_state_mask = 0,
|
|
},
|
|
[VLR_SUB_AS_AUTH_FAILED] = {
|
|
.name = OSMO_STRINGIFY(VLR_SUB_AS_AUTH_FAILED),
|
|
.in_event_mask = 0,
|
|
.out_state_mask = 0,
|
|
.onenter = auth_fsm_onenter_failed,
|
|
},
|
|
};
|
|
|
|
struct osmo_fsm vlr_auth_fsm = {
|
|
.name = "VLR_Authenticate",
|
|
.states = auth_fsm_states,
|
|
.num_states = ARRAY_SIZE(auth_fsm_states),
|
|
.allstate_event_mask = 0,
|
|
.allstate_action = NULL,
|
|
.log_subsys = DVLR,
|
|
.event_names = fsm_auth_event_names,
|
|
.cleanup = auth_fsm_cleanup,
|
|
};
|
|
|
|
/***********************************************************************
|
|
* User API (for SGSN/MSC code)
|
|
***********************************************************************/
|
|
|
|
/* MSC->VLR: Start Procedure Authenticate_VLR (TS 23.012 Ch. 4.1.2.2) */
|
|
struct osmo_fsm_inst *auth_fsm_start(struct vlr_subscr *vsub,
|
|
struct osmo_fsm_inst *parent,
|
|
uint32_t parent_term_event,
|
|
bool is_r99,
|
|
bool is_utran)
|
|
{
|
|
struct osmo_fsm_inst *fi;
|
|
struct auth_fsm_priv *afp;
|
|
|
|
fi = osmo_fsm_inst_alloc_child(&vlr_auth_fsm, parent,
|
|
parent_term_event);
|
|
if (!fi) {
|
|
osmo_fsm_inst_dispatch(parent, parent_term_event, 0);
|
|
return NULL;
|
|
}
|
|
|
|
afp = talloc_zero(fi, struct auth_fsm_priv);
|
|
if (!afp) {
|
|
osmo_fsm_inst_dispatch(parent, parent_term_event, 0);
|
|
return NULL;
|
|
}
|
|
|
|
afp->vsub = vsub;
|
|
if (vsub->imsi[0])
|
|
afp->by_imsi = true;
|
|
afp->is_r99 = is_r99;
|
|
afp->is_utran = is_utran;
|
|
fi->priv = afp;
|
|
vsub->auth_fsm = fi;
|
|
|
|
osmo_fsm_inst_dispatch(fi, VLR_AUTH_E_START, NULL);
|
|
|
|
return fi;
|
|
}
|
|
|
|
bool auth_try_reuse_tuple(struct vlr_subscr *vsub, uint8_t key_seq)
|
|
{
|
|
int max_reuse_count = vsub->vlr->cfg.auth_tuple_max_reuse_count;
|
|
struct vlr_auth_tuple *at = vsub->last_tuple;
|
|
|
|
if (!at)
|
|
return false;
|
|
if ((max_reuse_count >= 0) && (at->use_count > max_reuse_count))
|
|
return false;
|
|
if (at->key_seq != key_seq)
|
|
return false;
|
|
at->use_count++;
|
|
return true;
|
|
}
|