1358 lines
33 KiB
C
1358 lines
33 KiB
C
/*
|
|
* (C) 2010 by Andreas Eversberg <jolly@eversberg.eu>
|
|
*
|
|
* All Rights Reserved
|
|
*
|
|
* 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.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <arpa/inet.h>
|
|
#include <osmocom/core/talloc.h>
|
|
#include <osmocom/crypt/auth.h>
|
|
|
|
#include <osmocom/bb/common/logging.h>
|
|
#include <osmocom/bb/common/osmocom_data.h>
|
|
#include <osmocom/bb/common/sap_interface.h>
|
|
#include <osmocom/bb/common/sap_proto.h>
|
|
#include <osmocom/bb/common/networks.h>
|
|
#include <osmocom/bb/mobile/vty.h>
|
|
|
|
/* enable to get an empty list of forbidden PLMNs, even if stored on SIM.
|
|
* if list is changed, the result is not written back to SIM */
|
|
//#define TEST_EMPTY_FPLMN
|
|
|
|
static void subscr_sim_query_cb(struct osmocom_ms *ms, struct msgb *msg);
|
|
static void subscr_sim_update_cb(struct osmocom_ms *ms, struct msgb *msg);
|
|
static void subscr_sim_key_cb(struct osmocom_ms *ms, struct msgb *msg);
|
|
|
|
/*
|
|
* support
|
|
*/
|
|
|
|
char *gsm_check_imsi(const char *imsi)
|
|
{
|
|
int i;
|
|
|
|
if (!imsi || strlen(imsi) != 15)
|
|
return "IMSI must have 15 digits!";
|
|
|
|
for (i = 0; i < strlen(imsi); i++) {
|
|
if (imsi[i] < '0' || imsi[i] > '9')
|
|
return "IMSI must have digits 0 to 9 only!";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static char *sim_decode_bcd(uint8_t *data, uint8_t length)
|
|
{
|
|
int i, j = 0;
|
|
static char result[32], c;
|
|
|
|
for (i = 0; i < (length << 1); i++) {
|
|
if ((i & 1))
|
|
c = (data[i >> 1] >> 4);
|
|
else
|
|
c = (data[i >> 1] & 0xf);
|
|
if (c == 0xf)
|
|
break;
|
|
result[j++] = c + '0';
|
|
if (j == sizeof(result) - 1)
|
|
break;
|
|
}
|
|
result[j] = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* init/exit
|
|
*/
|
|
|
|
int gsm_subscr_init(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
|
|
memset(subscr, 0, sizeof(*subscr));
|
|
subscr->ms = ms;
|
|
|
|
/* set TMSI / LAC invalid */
|
|
subscr->tmsi = 0xffffffff;
|
|
subscr->lac = 0x0000;
|
|
|
|
/* set key invalid */
|
|
subscr->key_seq = 7;
|
|
|
|
/* any cell selection timer timeout */
|
|
subscr->any_timeout = ms->settings.any_timeout;
|
|
|
|
/* init lists */
|
|
INIT_LLIST_HEAD(&subscr->plmn_list);
|
|
INIT_LLIST_HEAD(&subscr->plmn_na);
|
|
|
|
/* open SIM */
|
|
subscr->sim_handle_query = sim_open(ms, subscr_sim_query_cb);
|
|
subscr->sim_handle_update = sim_open(ms, subscr_sim_update_cb);
|
|
subscr->sim_handle_key = sim_open(ms, subscr_sim_key_cb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gsm_subscr_exit(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct llist_head *lh, *lh2;
|
|
|
|
if (subscr->sim_handle_query) {
|
|
sim_close(ms, subscr->sim_handle_query);
|
|
subscr->sim_handle_query = 0;
|
|
}
|
|
if (subscr->sim_handle_update) {
|
|
sim_close(ms, subscr->sim_handle_update);
|
|
subscr->sim_handle_update = 0;
|
|
}
|
|
if (subscr->sim_handle_key) {
|
|
sim_close(ms, subscr->sim_handle_key);
|
|
subscr->sim_handle_key = 0;
|
|
}
|
|
|
|
/* flush lists */
|
|
llist_for_each_safe(lh, lh2, &subscr->plmn_list) {
|
|
llist_del(lh);
|
|
talloc_free(lh);
|
|
}
|
|
llist_for_each_safe(lh, lh2, &subscr->plmn_na) {
|
|
llist_del(lh);
|
|
talloc_free(lh);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* test card
|
|
*/
|
|
|
|
/* Attach test card, no SIM must be currently attached */
|
|
int gsm_subscr_testcard(struct osmocom_ms *ms, uint16_t mcc, uint16_t mnc,
|
|
uint16_t lac, uint32_t tmsi, uint8_t imsi_attached)
|
|
{
|
|
struct gsm_settings *set = &ms->settings;
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct msgb *nmsg;
|
|
char *error;
|
|
|
|
if (subscr->sim_valid) {
|
|
LOGP(DMM, LOGL_ERROR, "Cannot insert card, until current card "
|
|
"is detached.\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
error = gsm_check_imsi(set->test_imsi);
|
|
if (error) {
|
|
LOGP(DMM, LOGL_ERROR, "%s\n", error);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* reset subscriber */
|
|
gsm_subscr_exit(ms);
|
|
gsm_subscr_init(ms);
|
|
|
|
subscr->sim_type = GSM_SIM_TYPE_TEST;
|
|
sprintf(subscr->sim_name, "test");
|
|
subscr->sim_valid = 1;
|
|
if (imsi_attached && set->test_rplmn_valid) {
|
|
subscr->imsi_attached = imsi_attached;
|
|
subscr->ustate = GSM_SIM_U1_UPDATED;
|
|
} else
|
|
subscr->ustate = GSM_SIM_U2_NOT_UPDATED;
|
|
subscr->acc_barr = set->test_barr; /* we may access barred cell */
|
|
subscr->acc_class = 0xffff; /* we have any access class */
|
|
subscr->plmn_valid = set->test_rplmn_valid;
|
|
subscr->plmn_mcc = mcc;
|
|
subscr->plmn_mnc = mnc;
|
|
subscr->mcc = mcc;
|
|
subscr->mnc = mnc;
|
|
subscr->lac = lac;
|
|
subscr->tmsi = tmsi;
|
|
subscr->always_search_hplmn = set->test_always;
|
|
subscr->t6m_hplmn = 1; /* try to find home network every 6 min */
|
|
strcpy(subscr->imsi, set->test_imsi);
|
|
|
|
LOGP(DMM, LOGL_INFO, "(ms %s) Inserting test card (IMSI=%s %s, %s)\n",
|
|
ms->name, subscr->imsi, gsm_imsi_mcc(subscr->imsi),
|
|
gsm_imsi_mnc(subscr->imsi));
|
|
|
|
if (subscr->plmn_valid)
|
|
LOGP(DMM, LOGL_INFO, "-> Test card registered to %s %s 0x%04x"
|
|
"(%s, %s)\n", gsm_print_mcc(mcc),
|
|
gsm_print_mnc(mnc), lac, gsm_get_mcc(mcc),
|
|
gsm_get_mnc(mcc, mnc));
|
|
else
|
|
LOGP(DMM, LOGL_INFO, "-> Test card not registered\n");
|
|
if (subscr->imsi_attached)
|
|
LOGP(DMM, LOGL_INFO, "-> Test card attached\n");
|
|
|
|
/* insert card */
|
|
nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_REG_REQ);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
gsm48_mmr_downmsg(ms, nmsg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sim card
|
|
*/
|
|
|
|
static int subscr_sim_iccid(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
|
|
strcpy(subscr->iccid, sim_decode_bcd(data, length));
|
|
sprintf(subscr->sim_name, "sim-%s", subscr->iccid);
|
|
LOGP(DMM, LOGL_INFO, "received ICCID %s from SIM\n", subscr->iccid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int subscr_sim_imsi(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
char *imsi;
|
|
|
|
/* get actual length */
|
|
if (length < 1)
|
|
return -EINVAL;
|
|
if (data[0] + 1 < length) {
|
|
LOGP(DMM, LOGL_NOTICE, "invalid length = %d\n", length);
|
|
return -EINVAL;
|
|
}
|
|
length = data[0];
|
|
|
|
/* decode IMSI, skip first digit (parity) */
|
|
imsi = sim_decode_bcd(data + 1, length);
|
|
if (strlen(imsi) - 1 > GSM_IMSI_LENGTH - 1 || strlen(imsi) - 1 < 6) {
|
|
LOGP(DMM, LOGL_NOTICE, "IMSI invalid length = %zu\n",
|
|
strlen(imsi) - 1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
OSMO_STRLCPY_ARRAY(subscr->imsi, imsi + 1);
|
|
|
|
LOGP(DMM, LOGL_INFO, "received IMSI %s from SIM\n", subscr->imsi);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int subscr_sim_loci(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct gsm1111_ef_loci *loci;
|
|
|
|
if (length < 11)
|
|
return -EINVAL;
|
|
loci = (struct gsm1111_ef_loci *) data;
|
|
|
|
/* TMSI */
|
|
subscr->tmsi = ntohl(loci->tmsi);
|
|
|
|
/* LAI */
|
|
gsm48_decode_lai_hex(&loci->lai, &subscr->mcc, &subscr->mnc,
|
|
&subscr->lac);
|
|
|
|
/* location update status */
|
|
switch (loci->lupd_status & 0x07) {
|
|
case 0x00:
|
|
subscr->ustate = GSM_SIM_U1_UPDATED;
|
|
break;
|
|
case 0x02:
|
|
case 0x03:
|
|
subscr->ustate = GSM_SIM_U3_ROAMING_NA;
|
|
break;
|
|
default:
|
|
subscr->ustate = GSM_SIM_U2_NOT_UPDATED;
|
|
}
|
|
|
|
LOGP(DMM, LOGL_INFO, "received LOCI from SIM (mcc=%s mnc=%s lac=0x%04x "
|
|
"U%d)\n", gsm_print_mcc(subscr->mcc),
|
|
gsm_print_mnc(subscr->mnc), subscr->lac, subscr->ustate);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int subscr_sim_msisdn(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct gsm1111_ef_adn *adn;
|
|
|
|
if (length < sizeof(*adn))
|
|
return -EINVAL;
|
|
adn = (struct gsm1111_ef_adn *) (data + length - sizeof(*adn));
|
|
|
|
/* empty */
|
|
subscr->msisdn[0] = '\0';
|
|
if (adn->len_bcd <= 1)
|
|
return 0;
|
|
|
|
/* number */
|
|
if (((adn->ton_npi & 0x70) >> 4) == 1)
|
|
strcpy(subscr->msisdn, "+");
|
|
if (((adn->ton_npi & 0x70) >> 4) == 2)
|
|
strcpy(subscr->msisdn, "0");
|
|
strncat(subscr->msisdn, sim_decode_bcd(adn->number, adn->len_bcd - 1),
|
|
sizeof(subscr->msisdn) - 2);
|
|
|
|
LOGP(DMM, LOGL_INFO, "received MSISDN %s from SIM\n", subscr->msisdn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int subscr_sim_smsp(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct gsm1111_ef_smsp *smsp;
|
|
|
|
if (length < sizeof(*smsp))
|
|
return -EINVAL;
|
|
smsp = (struct gsm1111_ef_smsp *) (data + length - sizeof(*smsp));
|
|
|
|
/* empty */
|
|
subscr->sms_sca[0] = '\0';
|
|
|
|
/* TS-Service Centre Address */
|
|
if (!(smsp->par_ind & 0x02) && smsp->ts_sca[0] <= 11) {
|
|
if (((smsp->ts_sca[1] & 0x70) >> 4) == 1)
|
|
strcpy(subscr->sms_sca, "+");
|
|
if (((smsp->ts_sca[1] & 0x70) >> 4) == 2)
|
|
strcpy(subscr->sms_sca, "0");
|
|
gsm48_decode_bcd_number(subscr->sms_sca +
|
|
strlen(subscr->sms_sca), sizeof(subscr->sms_sca)
|
|
- strlen(subscr->sms_sca), smsp->ts_sca, 1);
|
|
}
|
|
|
|
LOGP(DMM, LOGL_INFO, "received SMSP from SIM (sca=%s)\n",
|
|
subscr->sms_sca);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int subscr_sim_kc(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
|
|
if (length < 9)
|
|
return -EINVAL;
|
|
|
|
/* key */
|
|
memcpy(subscr->key, data, 8);
|
|
|
|
/* key sequence */
|
|
subscr->key_seq = data[8] & 0x07;
|
|
|
|
LOGP(DMM, LOGL_INFO, "received KEY from SIM\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int subscr_sim_plmnsel(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct gsm_sub_plmn_list *plmn;
|
|
struct llist_head *lh, *lh2;
|
|
uint8_t lai[5];
|
|
uint16_t dummy_lac;
|
|
|
|
/* flush list */
|
|
llist_for_each_safe(lh, lh2, &subscr->plmn_list) {
|
|
llist_del(lh);
|
|
talloc_free(lh);
|
|
}
|
|
|
|
while(length >= 3) {
|
|
/* end of list inside mandatory fields */
|
|
if (data[0] == 0xff && data[1] == 0xff && data[2] == 0x0ff)
|
|
break;
|
|
|
|
/* add to list */
|
|
plmn = talloc_zero(ms, struct gsm_sub_plmn_list);
|
|
if (!plmn)
|
|
return -ENOMEM;
|
|
lai[0] = data[0];
|
|
lai[1] = data[1];
|
|
lai[2] = data[2];
|
|
gsm48_decode_lai_hex((struct gsm48_loc_area_id *)lai,
|
|
&plmn->mcc, &plmn->mnc, &dummy_lac);
|
|
llist_add_tail(&plmn->entry, &subscr->plmn_list);
|
|
|
|
LOGP(DMM, LOGL_INFO, "received PLMN selector (mcc=%s mnc=%s) "
|
|
"from SIM\n",
|
|
gsm_print_mcc(plmn->mcc), gsm_print_mnc(plmn->mnc));
|
|
|
|
data += 3;
|
|
length -= 3;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int subscr_sim_hpplmn(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
|
|
if (length < 1)
|
|
return -EINVAL;
|
|
|
|
/* HPLMN search interval */
|
|
subscr->t6m_hplmn = *data; /* multiple of 6 minutes */
|
|
|
|
LOGP(DMM, LOGL_INFO, "received HPPLMN %d (%d mins) from SIM\n",
|
|
subscr->t6m_hplmn, subscr->t6m_hplmn * 6);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int subscr_sim_spn(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
int i;
|
|
|
|
/* UCS2 code not supported */
|
|
if (length < 17 || data[1] >= 0x80)
|
|
return -ENOTSUP;
|
|
|
|
data++;
|
|
for (i = 0; i < 16; i++) {
|
|
if (*data == 0xff)
|
|
break;
|
|
subscr->sim_spn[i] = *data++;
|
|
}
|
|
subscr->sim_spn[i] = '\0';
|
|
|
|
LOGP(DMM, LOGL_INFO, "received SPN %s from SIM\n", subscr->sim_spn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int subscr_sim_acc(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
uint16_t ac;
|
|
|
|
if (length < 2)
|
|
return -EINVAL;
|
|
|
|
/* cell access */
|
|
memcpy(&ac, data, sizeof(ac));
|
|
subscr->acc_class = ntohs(ac);
|
|
|
|
LOGP(DMM, LOGL_INFO, "received ACC %04x from SIM\n", subscr->acc_class);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int subscr_sim_fplmn(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct gsm_sub_plmn_na *na;
|
|
struct llist_head *lh, *lh2;
|
|
uint8_t lai[5];
|
|
uint16_t dummy_lac;
|
|
|
|
#ifdef TEST_EMPTY_FPLMN
|
|
return 0;
|
|
#endif
|
|
|
|
/* flush list */
|
|
llist_for_each_safe(lh, lh2, &subscr->plmn_na) {
|
|
llist_del(lh);
|
|
talloc_free(lh);
|
|
}
|
|
|
|
while (length >= 3) {
|
|
/* end of list inside mandatory fields */
|
|
if (data[0] == 0xff && data[1] == 0xff && data[2] == 0x0ff)
|
|
break;
|
|
|
|
/* add to list */
|
|
na = talloc_zero(ms, struct gsm_sub_plmn_na);
|
|
if (!na)
|
|
return -ENOMEM;
|
|
lai[0] = data[0];
|
|
lai[1] = data[1];
|
|
lai[2] = data[2];
|
|
gsm48_decode_lai_hex((struct gsm48_loc_area_id *)lai, &na->mcc,
|
|
&na->mnc, &dummy_lac);
|
|
LOGP(DMM, LOGL_INFO, "received Forbidden PLMN %s %s from SIM\n",
|
|
gsm_print_mcc(na->mcc), gsm_print_mnc(na->mnc));
|
|
na->cause = -1; /* must have a value, but SIM stores no cause */
|
|
llist_add_tail(&na->entry, &subscr->plmn_na);
|
|
|
|
data += 3;
|
|
length -= 3;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct subscr_sim_file {
|
|
uint8_t mandatory;
|
|
uint16_t path[MAX_SIM_PATH_LENGTH];
|
|
uint16_t file;
|
|
uint8_t sim_job;
|
|
int (*func)(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length);
|
|
} subscr_sim_files[] = {
|
|
{ 1, { 0 }, 0x2fe2, SIM_JOB_READ_BINARY, subscr_sim_iccid },
|
|
{ 1, { 0x7f20, 0 }, 0x6f07, SIM_JOB_READ_BINARY, subscr_sim_imsi },
|
|
{ 1, { 0x7f20, 0 }, 0x6f7e, SIM_JOB_READ_BINARY, subscr_sim_loci },
|
|
{ 0, { 0x7f20, 0 }, 0x6f20, SIM_JOB_READ_BINARY, subscr_sim_kc },
|
|
{ 0, { 0x7f20, 0 }, 0x6f30, SIM_JOB_READ_BINARY, subscr_sim_plmnsel },
|
|
{ 0, { 0x7f20, 0 }, 0x6f31, SIM_JOB_READ_BINARY, subscr_sim_hpplmn },
|
|
{ 0, { 0x7f20, 0 }, 0x6f46, SIM_JOB_READ_BINARY, subscr_sim_spn },
|
|
{ 0, { 0x7f20, 0 }, 0x6f78, SIM_JOB_READ_BINARY, subscr_sim_acc },
|
|
{ 0, { 0x7f20, 0 }, 0x6f7b, SIM_JOB_READ_BINARY, subscr_sim_fplmn },
|
|
{ 0, { 0x7f10, 0 }, 0x6f40, SIM_JOB_READ_RECORD, subscr_sim_msisdn },
|
|
{ 0, { 0x7f10, 0 }, 0x6f42, SIM_JOB_READ_RECORD, subscr_sim_smsp },
|
|
{ 0, { 0 }, 0, 0, NULL }
|
|
};
|
|
|
|
/* request file from SIM */
|
|
static int subscr_sim_request(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct subscr_sim_file *sf = &subscr_sim_files[subscr->sim_file_index];
|
|
struct msgb *nmsg;
|
|
struct sim_hdr *nsh;
|
|
int i;
|
|
|
|
/* we are done, fire up PLMN and cell selection process */
|
|
if (!sf->func) {
|
|
LOGP(DMM, LOGL_INFO, "(ms %s) Done reading SIM card "
|
|
"(IMSI=%s %s, %s)\n", ms->name, subscr->imsi,
|
|
gsm_imsi_mcc(subscr->imsi), gsm_imsi_mnc(subscr->imsi));
|
|
|
|
/* if LAI is valid, set RPLMN */
|
|
if (subscr->lac > 0x0000 && subscr->lac < 0xfffe) {
|
|
subscr->plmn_valid = 1;
|
|
subscr->plmn_mcc = subscr->mcc;
|
|
subscr->plmn_mnc = subscr->mnc;
|
|
LOGP(DMM, LOGL_INFO, "-> SIM card registered to %s %s "
|
|
"(%s, %s)\n", gsm_print_mcc(subscr->plmn_mcc),
|
|
gsm_print_mnc(subscr->plmn_mnc),
|
|
gsm_get_mcc(subscr->plmn_mcc),
|
|
gsm_get_mnc(subscr->plmn_mcc,
|
|
subscr->plmn_mnc));
|
|
} else
|
|
LOGP(DMM, LOGL_INFO, "-> SIM card not registered\n");
|
|
|
|
/* insert card */
|
|
nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_REG_REQ);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
gsm48_mmr_downmsg(ms, nmsg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* trigger SIM reading */
|
|
nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_query,
|
|
sf->sim_job);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
nsh = (struct sim_hdr *) nmsg->data;
|
|
i = 0;
|
|
while (sf->path[i]) {
|
|
nsh->path[i] = sf->path[i];
|
|
i++;
|
|
}
|
|
nsh->path[i] = 0; /* end of path */
|
|
nsh->file = sf->file;
|
|
nsh->rec_no = 1;
|
|
nsh->rec_mode = 0x04;
|
|
LOGP(DMM, LOGL_INFO, "Requesting SIM file 0x%04x\n", nsh->file);
|
|
sim_job(ms, nmsg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void subscr_sim_query_cb(struct osmocom_ms *ms, struct msgb *msg)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct sim_hdr *sh = (struct sim_hdr *) msg->data;
|
|
uint8_t *payload = msg->data + sizeof(*sh);
|
|
uint16_t payload_len = msg->len - sizeof(*sh);
|
|
int rc;
|
|
struct subscr_sim_file *sf = &subscr_sim_files[subscr->sim_file_index];
|
|
struct msgb *nmsg;
|
|
|
|
/* error handling */
|
|
if (sh->job_type == SIM_JOB_ERROR) {
|
|
uint8_t cause = payload[0];
|
|
|
|
switch (cause) {
|
|
/* unlocking required */
|
|
case SIM_CAUSE_PIN1_REQUIRED:
|
|
LOGP(DMM, LOGL_INFO, "PIN is required, %d tries left\n",
|
|
payload[1]);
|
|
|
|
vty_notify(ms, NULL);
|
|
vty_notify(ms, "Please give PIN for ICCID %s (you have "
|
|
"%d tries left)\n", subscr->iccid, payload[1]);
|
|
subscr->sim_pin_required = 1;
|
|
break;
|
|
case SIM_CAUSE_PIN1_BLOCKED:
|
|
LOGP(DMM, LOGL_NOTICE, "PIN is blocked\n");
|
|
|
|
vty_notify(ms, NULL);
|
|
vty_notify(ms, "PIN is blocked\n");
|
|
if (payload[1]) {
|
|
vty_notify(ms, "Please give PUC for ICCID %s "
|
|
"(you have %d tries left)\n",
|
|
subscr->iccid, payload[1]);
|
|
}
|
|
subscr->sim_pin_required = 1;
|
|
break;
|
|
case SIM_CAUSE_PUC_BLOCKED:
|
|
LOGP(DMM, LOGL_NOTICE, "PUC is blocked\n");
|
|
|
|
vty_notify(ms, NULL);
|
|
vty_notify(ms, "PUC is blocked\n");
|
|
subscr->sim_pin_required = 1;
|
|
break;
|
|
default:
|
|
if (sf->func && !sf->mandatory) {
|
|
LOGP(DMM, LOGL_NOTICE, "SIM reading failed, "
|
|
"ignoring!\n");
|
|
goto ignore;
|
|
}
|
|
LOGP(DMM, LOGL_NOTICE, "SIM reading failed\n");
|
|
|
|
vty_notify(ms, NULL);
|
|
vty_notify(ms, "SIM failed, replace SIM!\n");
|
|
|
|
/* detach simcard */
|
|
subscr->sim_valid = 0;
|
|
nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_NREG_REQ);
|
|
if (!nmsg)
|
|
return;
|
|
gsm48_mmr_downmsg(ms, nmsg);
|
|
}
|
|
msgb_free(msg);
|
|
|
|
return;
|
|
}
|
|
|
|
/* if pin was successfully unlocked, then resend request */
|
|
if (subscr->sim_pin_required) {
|
|
subscr->sim_pin_required = 0;
|
|
subscr_sim_request(ms);
|
|
return;
|
|
}
|
|
|
|
/* done when nothing more to read. this happens on PIN requests */
|
|
if (!sf->func)
|
|
return;
|
|
|
|
/* call function do decode SIM reply */
|
|
rc = sf->func(ms, payload, payload_len);
|
|
if (rc) {
|
|
LOGP(DMM, LOGL_NOTICE, "SIM reading failed, file invalid\n");
|
|
if (subscr_sim_files[subscr->sim_file_index].mandatory) {
|
|
vty_notify(ms, NULL);
|
|
vty_notify(ms, "SIM failed, data invalid, replace "
|
|
"SIM!\n");
|
|
msgb_free(msg);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
ignore:
|
|
msgb_free(msg);
|
|
|
|
/* trigger next file */
|
|
subscr->sim_file_index++;
|
|
subscr_sim_request(ms);
|
|
}
|
|
|
|
/* enter PIN */
|
|
void gsm_subscr_sim_pin(struct osmocom_ms *ms, char *pin1, char *pin2,
|
|
int8_t mode)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct msgb *nmsg;
|
|
uint8_t job;
|
|
|
|
/* skip, if no real valid SIM */
|
|
if (!GSM_SIM_IS_READER(subscr->sim_type))
|
|
return;
|
|
|
|
switch (mode) {
|
|
case -1:
|
|
job = SIM_JOB_PIN1_DISABLE;
|
|
LOGP(DMM, LOGL_INFO, "disabling PIN %s\n", pin1);
|
|
break;
|
|
case 1:
|
|
job = SIM_JOB_PIN1_ENABLE;
|
|
LOGP(DMM, LOGL_INFO, "enabling PIN %s\n", pin1);
|
|
break;
|
|
case 2:
|
|
job = SIM_JOB_PIN1_CHANGE;
|
|
LOGP(DMM, LOGL_INFO, "changing PIN %s to %s\n", pin1, pin2);
|
|
break;
|
|
case 99:
|
|
job = SIM_JOB_PIN1_UNBLOCK;
|
|
LOGP(DMM, LOGL_INFO, "unblocking PIN %s with PUC %s\n", pin1,
|
|
pin2);
|
|
break;
|
|
default:
|
|
if (!subscr->sim_pin_required) {
|
|
LOGP(DMM, LOGL_ERROR, "No PIN required now\n");
|
|
return;
|
|
}
|
|
LOGP(DMM, LOGL_INFO, "entering PIN %s\n", pin1);
|
|
job = SIM_JOB_PIN1_UNLOCK;
|
|
}
|
|
|
|
nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_query, job);
|
|
if (!nmsg)
|
|
return;
|
|
memcpy(msgb_put(nmsg, strlen(pin1) + 1), pin1, strlen(pin1) + 1);
|
|
memcpy(msgb_put(nmsg, strlen(pin2) + 1), pin2, strlen(pin2) + 1);
|
|
sim_job(ms, nmsg);
|
|
}
|
|
|
|
/* Attach SIM reader, no SIM must be currently attached */
|
|
int gsm_subscr_simcard(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
|
|
if (subscr->sim_valid) {
|
|
LOGP(DMM, LOGL_ERROR, "Cannot attach card, until current card "
|
|
"is detached.\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* reset subscriber */
|
|
gsm_subscr_exit(ms);
|
|
gsm_subscr_init(ms);
|
|
|
|
subscr->sim_type = GSM_SIM_TYPE_L1PHY;
|
|
sprintf(subscr->sim_name, "sim");
|
|
subscr->sim_valid = 1;
|
|
subscr->ustate = GSM_SIM_U2_NOT_UPDATED;
|
|
|
|
/* start with first index */
|
|
subscr->sim_file_index = 0;
|
|
return subscr_sim_request(ms);
|
|
}
|
|
|
|
/* update plmn not allowed list on SIM */
|
|
static int subscr_write_plmn_na(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct msgb *nmsg;
|
|
struct sim_hdr *nsh;
|
|
struct gsm_sub_plmn_na *na, *nas[4] = { NULL, NULL, NULL, NULL };
|
|
int count = 0, i;
|
|
uint8_t *data;
|
|
uint8_t lai[5];
|
|
|
|
#ifdef TEST_EMPTY_FPLMN
|
|
return 0;
|
|
#endif
|
|
|
|
/* skip, if no real valid SIM */
|
|
if (!GSM_SIM_IS_READER(subscr->sim_type) || !subscr->sim_valid)
|
|
return 0;
|
|
|
|
/* get tail list from "PLMN not allowed" */
|
|
llist_for_each_entry(na, &subscr->plmn_na, entry) {
|
|
if (count < 4)
|
|
nas[count] = na;
|
|
else {
|
|
nas[0] = nas[1];
|
|
nas[1] = nas[2];
|
|
nas[2] = nas[3];
|
|
nas[3] = na;
|
|
}
|
|
count++;
|
|
}
|
|
|
|
/* write to SIM */
|
|
LOGP(DMM, LOGL_INFO, "Updating FPLMN on SIM\n");
|
|
nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_update,
|
|
SIM_JOB_UPDATE_BINARY);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
nsh = (struct sim_hdr *) nmsg->data;
|
|
data = msgb_put(nmsg, 12);
|
|
nsh->path[0] = 0x7f20;
|
|
nsh->path[1] = 0;
|
|
nsh->file = 0x6f7b;
|
|
for (i = 0; i < 4; i++) {
|
|
if (nas[i]) {
|
|
gsm48_encode_lai_hex((struct gsm48_loc_area_id *)lai,
|
|
nas[i]->mcc, nas[i]->mnc, 0);
|
|
*data++ = lai[0];
|
|
*data++ = lai[1];
|
|
*data++ = lai[2];
|
|
} else {
|
|
*data++ = 0xff;
|
|
*data++ = 0xff;
|
|
*data++ = 0xff;
|
|
}
|
|
}
|
|
sim_job(ms, nmsg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* update LOCI on SIM */
|
|
int gsm_subscr_write_loci(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct msgb *nmsg;
|
|
struct sim_hdr *nsh;
|
|
struct gsm1111_ef_loci *loci;
|
|
|
|
/* skip, if no real valid SIM */
|
|
if (!GSM_SIM_IS_READER(subscr->sim_type) || !subscr->sim_valid)
|
|
return 0;
|
|
|
|
LOGP(DMM, LOGL_INFO, "Updating LOCI on SIM\n");
|
|
|
|
/* write to SIM */
|
|
nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_update,
|
|
SIM_JOB_UPDATE_BINARY);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
nsh = (struct sim_hdr *) nmsg->data;
|
|
nsh->path[0] = 0x7f20;
|
|
nsh->path[1] = 0;
|
|
nsh->file = 0x6f7e;
|
|
loci = (struct gsm1111_ef_loci *)msgb_put(nmsg, sizeof(*loci));
|
|
|
|
/* TMSI */
|
|
loci->tmsi = htonl(subscr->tmsi);
|
|
|
|
/* LAI */
|
|
gsm48_encode_lai_hex(&loci->lai, subscr->mcc, subscr->mnc, subscr->lac);
|
|
|
|
/* TMSI time */
|
|
loci->tmsi_time = 0xff;
|
|
|
|
/* location update status */
|
|
switch (subscr->ustate) {
|
|
case GSM_SIM_U1_UPDATED:
|
|
loci->lupd_status = 0x00;
|
|
break;
|
|
case GSM_SIM_U3_ROAMING_NA:
|
|
loci->lupd_status = 0x03;
|
|
break;
|
|
default:
|
|
loci->lupd_status = 0x01;
|
|
}
|
|
|
|
sim_job(ms, nmsg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void subscr_sim_update_cb(struct osmocom_ms *ms, struct msgb *msg)
|
|
{
|
|
struct sim_hdr *sh = (struct sim_hdr *) msg->data;
|
|
uint8_t *payload = msg->data + sizeof(*sh);
|
|
|
|
/* error handling */
|
|
if (sh->job_type == SIM_JOB_ERROR)
|
|
LOGP(DMM, LOGL_NOTICE, "SIM update failed (cause %d)\n",
|
|
*payload);
|
|
|
|
msgb_free(msg);
|
|
}
|
|
|
|
int gsm_subscr_generate_kc(struct osmocom_ms *ms, uint8_t key_seq,
|
|
uint8_t *rand, uint8_t no_sim)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct msgb *nmsg;
|
|
struct sim_hdr *nsh;
|
|
|
|
/* not a SIM */
|
|
if (!GSM_SIM_IS_READER(subscr->sim_type)
|
|
|| !subscr->sim_valid || no_sim) {
|
|
struct gsm48_mm_event *nmme;
|
|
|
|
LOGP(DMM, LOGL_INFO, "Sending dummy authentication response\n");
|
|
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_AUTH_RESPONSE);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
nmme = (struct gsm48_mm_event *) nmsg->data;
|
|
nmme->sres[0] = 0x12;
|
|
nmme->sres[1] = 0x34;
|
|
nmme->sres[2] = 0x56;
|
|
nmme->sres[3] = 0x78;
|
|
gsm48_mmevent_msg(ms, nmsg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* test SIM */
|
|
if (subscr->sim_type == GSM_SIM_TYPE_TEST) {
|
|
struct gsm48_mm_event *nmme;
|
|
struct gsm_settings *set = &ms->settings;
|
|
static struct osmo_sub_auth_data auth = {
|
|
.type = OSMO_AUTH_TYPE_GSM
|
|
};
|
|
struct osmo_auth_vector _vec;
|
|
struct osmo_auth_vector *vec = &_vec;
|
|
|
|
auth.algo = set->test_ki_type;
|
|
memcpy(auth.u.gsm.ki, set->test_ki, sizeof(auth.u.gsm.ki));
|
|
int ret = osmo_auth_gen_vec(vec, &auth, rand);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* store sequence */
|
|
subscr->key_seq = key_seq;
|
|
memcpy(subscr->key, vec->kc, 8);
|
|
|
|
LOGP(DMM, LOGL_INFO, "Sending authentication response\n");
|
|
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_AUTH_RESPONSE);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
nmme = (struct gsm48_mm_event *) nmsg->data;
|
|
memcpy(nmme->sres, vec->sres, 4);
|
|
gsm48_mmevent_msg(ms, nmsg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
LOGP(DMM, LOGL_INFO, "Generating KEY at SIM\n");
|
|
|
|
/* command to SIM */
|
|
nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_key, SIM_JOB_RUN_GSM_ALGO);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
nsh = (struct sim_hdr *) nmsg->data;
|
|
nsh->path[0] = 0x7f20;
|
|
nsh->path[1] = 0;
|
|
|
|
/* random */
|
|
memcpy(msgb_put(nmsg, 16), rand, 16);
|
|
|
|
/* store sequence */
|
|
subscr->key_seq = key_seq;
|
|
|
|
sim_job(ms, nmsg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void subscr_sim_key_cb(struct osmocom_ms *ms, struct msgb *msg)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct sim_hdr *sh = (struct sim_hdr *) msg->data;
|
|
uint8_t *payload = msg->data + sizeof(*sh);
|
|
uint16_t payload_len = msg->len - sizeof(*sh);
|
|
struct msgb *nmsg;
|
|
struct sim_hdr *nsh;
|
|
struct gsm48_mm_event *nmme;
|
|
uint8_t *data;
|
|
|
|
/* error handling */
|
|
if (sh->job_type == SIM_JOB_ERROR) {
|
|
LOGP(DMM, LOGL_NOTICE, "key generation on SIM failed "
|
|
"(cause %d)\n", *payload);
|
|
|
|
msgb_free(msg);
|
|
|
|
return;
|
|
}
|
|
|
|
if (payload_len < 12) {
|
|
LOGP(DMM, LOGL_NOTICE, "response from SIM too short\n");
|
|
return;
|
|
}
|
|
|
|
/* store key */
|
|
memcpy(subscr->key, payload + 4, 8);
|
|
|
|
/* write to SIM */
|
|
LOGP(DMM, LOGL_INFO, "Updating KC on SIM\n");
|
|
nmsg = gsm_sim_msgb_alloc(subscr->sim_handle_update,
|
|
SIM_JOB_UPDATE_BINARY);
|
|
if (!nmsg)
|
|
return;
|
|
nsh = (struct sim_hdr *) nmsg->data;
|
|
nsh->path[0] = 0x7f20;
|
|
nsh->path[1] = 0;
|
|
nsh->file = 0x6f20;
|
|
data = msgb_put(nmsg, 9);
|
|
memcpy(data, subscr->key, 8);
|
|
data[8] = subscr->key_seq;
|
|
sim_job(ms, nmsg);
|
|
|
|
/* return signed response */
|
|
nmsg = gsm48_mmevent_msgb_alloc(GSM48_MM_EVENT_AUTH_RESPONSE);
|
|
if (!nmsg)
|
|
return;
|
|
nmme = (struct gsm48_mm_event *) nmsg->data;
|
|
memcpy(nmme->sres, payload, 4);
|
|
gsm48_mmevent_msg(ms, nmsg);
|
|
|
|
msgb_free(msg);
|
|
}
|
|
|
|
/*
|
|
* detach
|
|
*/
|
|
|
|
/* Detach card */
|
|
int gsm_subscr_remove(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct msgb *nmsg;
|
|
|
|
if (!subscr->sim_valid) {
|
|
LOGP(DMM, LOGL_ERROR, "Cannot remove card, no card present\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* remove card */
|
|
nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_NREG_REQ);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
gsm48_mmr_downmsg(ms, nmsg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* state and lists
|
|
*/
|
|
|
|
static const char *subscr_ustate_names[] = {
|
|
"U0_NULL",
|
|
"U1_UPDATED",
|
|
"U2_NOT_UPDATED",
|
|
"U3_ROAMING_NA"
|
|
};
|
|
|
|
/* change to new U state */
|
|
void new_sim_ustate(struct gsm_subscriber *subscr, int state)
|
|
{
|
|
LOGP(DMM, LOGL_INFO, "(ms %s) new state %s -> %s\n", subscr->ms->name,
|
|
subscr_ustate_names[subscr->ustate],
|
|
subscr_ustate_names[state]);
|
|
|
|
subscr->ustate = state;
|
|
}
|
|
|
|
/* del forbidden PLMN. if MCC==0, flush complete list */
|
|
int gsm_subscr_del_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc,
|
|
uint16_t mnc)
|
|
{
|
|
struct gsm_sub_plmn_na *na, *na2;
|
|
int deleted = 0;
|
|
|
|
llist_for_each_entry_safe(na, na2, &subscr->plmn_na, entry) {
|
|
if (!mcc || (na->mcc == mcc && na->mnc == mnc)) {
|
|
LOGP(DPLMN, LOGL_INFO, "Delete from list of forbidden "
|
|
"PLMNs (mcc=%s, mnc=%s)\n",
|
|
gsm_print_mcc(mcc), gsm_print_mnc(mnc));
|
|
llist_del(&na->entry);
|
|
talloc_free(na);
|
|
deleted = 1;
|
|
if (mcc)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (deleted) {
|
|
/* update plmn not allowed list on SIM */
|
|
subscr_write_plmn_na(subscr->ms);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* add forbidden PLMN */
|
|
int gsm_subscr_add_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc,
|
|
uint16_t mnc, uint8_t cause)
|
|
{
|
|
struct gsm_sub_plmn_na *na;
|
|
|
|
/* if already in the list, remove and add to tail */
|
|
gsm_subscr_del_forbidden_plmn(subscr, mcc, mnc);
|
|
|
|
LOGP(DPLMN, LOGL_INFO, "Add to list of forbidden PLMNs "
|
|
"(mcc=%s, mnc=%s)\n", gsm_print_mcc(mcc), gsm_print_mnc(mnc));
|
|
na = talloc_zero(subscr->ms, struct gsm_sub_plmn_na);
|
|
if (!na)
|
|
return -ENOMEM;
|
|
na->mcc = mcc;
|
|
na->mnc = mnc;
|
|
na->cause = cause ? : -1; /* cause 0 is not allowed */
|
|
llist_add_tail(&na->entry, &subscr->plmn_na);
|
|
|
|
/* don't add Home PLMN to SIM */
|
|
if (subscr->sim_valid && gsm_match_mnc(mcc, mnc, subscr->imsi))
|
|
return -EINVAL;
|
|
|
|
/* update plmn not allowed list on SIM */
|
|
subscr_write_plmn_na(subscr->ms);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* search forbidden PLMN */
|
|
int gsm_subscr_is_forbidden_plmn(struct gsm_subscriber *subscr, uint16_t mcc,
|
|
uint16_t mnc)
|
|
{
|
|
struct gsm_sub_plmn_na *na;
|
|
|
|
llist_for_each_entry(na, &subscr->plmn_na, entry) {
|
|
if (na->mcc == mcc && na->mnc == mnc)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gsm_subscr_get_key_seq(struct osmocom_ms *ms, struct gsm_subscriber *subscr)
|
|
{
|
|
if (ms->settings.force_rekey)
|
|
return 7;
|
|
else
|
|
return subscr->key_seq;
|
|
}
|
|
|
|
int gsm_subscr_dump_forbidden_plmn(struct osmocom_ms *ms,
|
|
void (*print)(void *, const char *, ...), void *priv)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct gsm_sub_plmn_na *temp;
|
|
|
|
print(priv, "MCC |MNC |cause\n");
|
|
print(priv, "-------+-------+-------\n");
|
|
llist_for_each_entry(temp, &subscr->plmn_na, entry)
|
|
print(priv, "%s |%s%s |#%d\n",
|
|
gsm_print_mcc(temp->mcc), gsm_print_mnc(temp->mnc),
|
|
((temp->mnc & 0x00f) == 0x00f) ? " ":"", temp->cause);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* dump subscriber */
|
|
void gsm_subscr_dump(struct gsm_subscriber *subscr,
|
|
void (*print)(void *, const char *, ...), void *priv)
|
|
{
|
|
int i;
|
|
struct gsm_sub_plmn_list *plmn_list;
|
|
struct gsm_sub_plmn_na *plmn_na;
|
|
|
|
print(priv, "Mobile Subscriber of MS '%s':\n", subscr->ms->name);
|
|
|
|
if (!subscr->sim_valid) {
|
|
print(priv, " No SIM present.\n");
|
|
return;
|
|
}
|
|
|
|
print(priv, " IMSI: %s\n", subscr->imsi);
|
|
if (subscr->iccid[0])
|
|
print(priv, " ICCID: %s\n", subscr->iccid);
|
|
if (subscr->sim_spn[0])
|
|
print(priv, " Service Provider Name: %s\n", subscr->sim_spn);
|
|
if (subscr->msisdn[0])
|
|
print(priv, " MSISDN: %s\n", subscr->msisdn);
|
|
if (subscr->sms_sca[0])
|
|
print(priv, " SMS Service Center Address: %s\n",
|
|
subscr->sms_sca);
|
|
print(priv, " Status: %s IMSI %s", subscr_ustate_names[subscr->ustate],
|
|
(subscr->imsi_attached) ? "attached" : "detached");
|
|
if (subscr->tmsi != 0xffffffff)
|
|
print(priv, " TMSI 0x%08x", subscr->tmsi);
|
|
if (subscr->lac > 0x0000 && subscr->lac < 0xfffe) {
|
|
print(priv, "\n");
|
|
print(priv, " LAI: MCC %s MNC %s LAC 0x%04x "
|
|
"(%s, %s)\n", gsm_print_mcc(subscr->mcc),
|
|
gsm_print_mnc(subscr->mnc), subscr->lac,
|
|
gsm_get_mcc(subscr->mcc),
|
|
gsm_get_mnc(subscr->mcc, subscr->mnc));
|
|
} else
|
|
print(priv, " LAI: invalid\n");
|
|
if (subscr->key_seq != 7) {
|
|
print(priv, " Key: sequence %d ", subscr->key_seq);
|
|
for (i = 0; i < sizeof(subscr->key); i++)
|
|
print(priv, " %02x", subscr->key[i]);
|
|
print(priv, "\n");
|
|
}
|
|
if (subscr->plmn_valid)
|
|
print(priv, " Registered PLMN: MCC %s MNC %s (%s, %s)\n",
|
|
gsm_print_mcc(subscr->plmn_mcc),
|
|
gsm_print_mnc(subscr->plmn_mnc),
|
|
gsm_get_mcc(subscr->plmn_mcc),
|
|
gsm_get_mnc(subscr->plmn_mcc, subscr->plmn_mnc));
|
|
print(priv, " Access barred cells: %s\n",
|
|
(subscr->acc_barr) ? "yes" : "no");
|
|
print(priv, " Access classes:");
|
|
for (i = 0; i < 16; i++)
|
|
if ((subscr->acc_class & (1 << i)))
|
|
print(priv, " C%d", i);
|
|
print(priv, "\n");
|
|
if (!llist_empty(&subscr->plmn_list)) {
|
|
print(priv, " List of preferred PLMNs:\n");
|
|
print(priv, " MCC |MNC\n");
|
|
print(priv, " -------+-------\n");
|
|
llist_for_each_entry(plmn_list, &subscr->plmn_list, entry)
|
|
print(priv, " %s |%s (%s, %s)\n",
|
|
gsm_print_mcc(plmn_list->mcc),
|
|
gsm_print_mnc(plmn_list->mnc),
|
|
gsm_get_mcc(plmn_list->mcc),
|
|
gsm_get_mnc(plmn_list->mcc, plmn_list->mnc));
|
|
}
|
|
if (!llist_empty(&subscr->plmn_na)) {
|
|
print(priv, " List of forbidden PLMNs:\n");
|
|
print(priv, " MCC |MNC |cause\n");
|
|
print(priv, " -------+-------+-------\n");
|
|
llist_for_each_entry(plmn_na, &subscr->plmn_na, entry)
|
|
print(priv, " %s |%s%s |#%d "
|
|
"(%s, %s)\n", gsm_print_mcc(plmn_na->mcc),
|
|
gsm_print_mnc(plmn_na->mnc),
|
|
((plmn_na->mnc & 0x00f) == 0x00f) ? " ":"",
|
|
plmn_na->cause, gsm_get_mcc(plmn_na->mcc),
|
|
gsm_get_mnc(plmn_na->mcc, plmn_na->mnc));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* SAP interface integration
|
|
*/
|
|
|
|
/* Attach SIM card over SAP */
|
|
int gsm_subscr_sapcard(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_subscriber *subscr = &ms->subscr;
|
|
struct msgb *nmsg;
|
|
int rc;
|
|
|
|
if (subscr->sim_valid) {
|
|
LOGP(DMM, LOGL_ERROR, "Cannot insert card, until current card "
|
|
"is detached.\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* reset subscriber */
|
|
gsm_subscr_exit(ms);
|
|
gsm_subscr_init(ms);
|
|
|
|
subscr->sim_type = GSM_SIM_TYPE_SAP;
|
|
sprintf(subscr->sim_name, "sap");
|
|
subscr->sim_valid = 1;
|
|
|
|
/* Try to connect to the SAP interface */
|
|
vty_notify(ms, NULL);
|
|
vty_notify(ms, "Connecting to the SAP interface...\n");
|
|
rc = sap_open(ms);
|
|
if (rc < 0) {
|
|
LOGP(DSAP, LOGL_ERROR, "Failed during sap_open(), no SAP based SIM reader\n");
|
|
vty_notify(ms, "SAP connection error!\n");
|
|
ms->sap_wq.bfd.fd = -1;
|
|
|
|
/* Detach SIM */
|
|
subscr->sim_valid = 0;
|
|
nmsg = gsm48_mmr_msgb_alloc(GSM48_MMR_NREG_REQ);
|
|
if (!nmsg)
|
|
return -ENOMEM;
|
|
gsm48_mmr_downmsg(ms, nmsg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Deattach sapcard */
|
|
int gsm_subscr_remove_sapcard(struct osmocom_ms *ms)
|
|
{
|
|
return sap_close(ms);
|
|
}
|
|
|
|
int gsm_subscr_sap_rsp_cb(struct osmocom_ms *ms, int res_code,
|
|
uint8_t res_type, uint16_t param_len, const uint8_t *param_val)
|
|
{
|
|
struct msgb *msg;
|
|
int rc = 0;
|
|
|
|
/* Response parameter is not encoded in case of error */
|
|
if (res_code != SAP_RESULT_OK_REQ_PROC_CORR)
|
|
goto ignore_rsp;
|
|
|
|
switch (res_type) {
|
|
case SAP_TRANSFER_APDU_RESP:
|
|
/* Prevent NULL-pointer dereference */
|
|
if (!param_len || !param_val) {
|
|
rc = -EINVAL;
|
|
goto ignore_rsp;
|
|
}
|
|
|
|
/* FIXME: why do we use this length? */
|
|
msg = msgb_alloc(GSM_SAP_LENGTH, "sap_apdu");
|
|
if (!msg) {
|
|
rc = -ENOMEM;
|
|
goto ignore_rsp;
|
|
}
|
|
|
|
msg->data = msgb_put(msg, param_len);
|
|
memcpy(msg->data, param_val, param_len);
|
|
|
|
return sim_apdu_resp(ms, msg);
|
|
|
|
case SAP_TRANSFER_ATR_RESP:
|
|
/* TODO: don't read SIM again (if already) */
|
|
LOGP(DSAP, LOGL_INFO, "SAP card is ready, start reading...\n");
|
|
return subscr_sim_request(ms);
|
|
|
|
default:
|
|
rc = -ENOTSUP;
|
|
goto ignore_rsp;
|
|
}
|
|
|
|
return 0;
|
|
|
|
ignore_rsp:
|
|
LOGP(DSAP, LOGL_NOTICE, "Ignored SAP response '%s' (code=%d)\n",
|
|
get_value_string(sap_msg_names, res_type), res_code);
|
|
return rc;
|
|
}
|