1281 lines
32 KiB
C
1281 lines
32 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.
|
|
*
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
#include <arpa/inet.h>
|
|
#include <osmocom/core/talloc.h>
|
|
#include <osmocom/core/utils.h>
|
|
#include <osmocom/core/gsmtap_util.h>
|
|
#include <osmocom/core/gsmtap.h>
|
|
|
|
#include <osmocom/bb/common/logging.h>
|
|
#include <osmocom/bb/common/osmocom_data.h>
|
|
#include <osmocom/bb/common/l1ctl.h>
|
|
|
|
extern struct gsmtap_inst *gsmtap_inst;
|
|
|
|
static int sim_process_job(struct osmocom_ms *ms);
|
|
|
|
/*
|
|
* support
|
|
*/
|
|
|
|
uint32_t new_handle = 1;
|
|
|
|
static struct gsm1111_df_name {
|
|
uint16_t file;
|
|
const char *name;
|
|
} gsm1111_df_name[] = {
|
|
{ 0x3f00, "MF" },
|
|
{ 0x7f20, "DFgsm" },
|
|
{ 0x7f10, "DFtelecom" },
|
|
{ 0x7f22, "DFis-41" },
|
|
{ 0x7f23, "DFfp-cts" },
|
|
{ 0x5f50, "DFgraphics" },
|
|
{ 0x5f30, "DFiridium" },
|
|
{ 0x5f31, "DFglobst" },
|
|
{ 0x5f32, "DFico" },
|
|
{ 0x5f33, "DFaces" },
|
|
{ 0x5f40, "DFeia/tia-553" },
|
|
{ 0x5f60, "DFcts" },
|
|
{ 0x5f70, "DFsolsa" },
|
|
{ 0x5f3c, "DFmexe" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
static const char *get_df_name(uint16_t fid)
|
|
{
|
|
int i;
|
|
static char text[7];
|
|
|
|
for (i = 0; gsm1111_df_name[i].file; i++)
|
|
if (gsm1111_df_name[i].file == fid)
|
|
break;
|
|
if (gsm1111_df_name[i].file)
|
|
return gsm1111_df_name[i].name;
|
|
|
|
sprintf(text, "0x%04x", fid);
|
|
return text;
|
|
}
|
|
|
|
static struct gsm_sim_handler *sim_get_handler(struct gsm_sim *sim,
|
|
uint32_t handle)
|
|
{
|
|
struct gsm_sim_handler *handler;
|
|
|
|
llist_for_each_entry(handler, &sim->handlers, entry)
|
|
if (handler->handle == handle)
|
|
return handler;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* messages
|
|
*/
|
|
|
|
static const struct value_string sim_job_names[] = {
|
|
{ SIM_JOB_READ_BINARY, "SIM_JOB_READ_BINARY" },
|
|
{ SIM_JOB_UPDATE_BINARY, "SIM_JOB_UPDATE_BINARY" },
|
|
{ SIM_JOB_READ_RECORD, "SIM_JOB_READ_RECORD" },
|
|
{ SIM_JOB_UPDATE_RECORD, "SIM_JOB_UPDATE_RECORD" },
|
|
{ SIM_JOB_SEEK_RECORD, "SIM_JOB_SEEK_RECORD" },
|
|
{ SIM_JOB_INCREASE, "SIM_JOB_INCREASE" },
|
|
{ SIM_JOB_INVALIDATE, "SIM_JOB_INVALIDATE" },
|
|
{ SIM_JOB_REHABILITATE, "SIM_JOB_REHABILITATE" },
|
|
{ SIM_JOB_RUN_GSM_ALGO, "SIM_JOB_RUN_GSM_ALGO" },
|
|
{ SIM_JOB_PIN1_UNLOCK, "SIM_JOB_PIN1_UNLOCK" },
|
|
{ SIM_JOB_PIN1_CHANGE, "SIM_JOB_PIN1_CHANGE" },
|
|
{ SIM_JOB_PIN1_DISABLE, "SIM_JOB_PIN1_DISABLE" },
|
|
{ SIM_JOB_PIN1_ENABLE, "SIM_JOB_PIN1_ENABLE" },
|
|
{ SIM_JOB_PIN1_UNBLOCK, "SIM_JOB_PIN1_UNBLOCK" },
|
|
{ SIM_JOB_PIN2_UNLOCK, "SIM_JOB_PIN2_UNLOCK" },
|
|
{ SIM_JOB_PIN2_CHANGE, "SIM_JOB_PIN2_CHANGE" },
|
|
{ SIM_JOB_PIN2_UNBLOCK, "SIM_JOB_PIN2_UNBLOCK" },
|
|
{ SIM_JOB_OK, "SIM_JOB_OK" },
|
|
{ SIM_JOB_ERROR, "SIM_JOB_ERROR" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
static const char *get_job_name(int value)
|
|
{
|
|
return get_value_string(sim_job_names, value);
|
|
}
|
|
|
|
/* allocate sim client message (upper layer) */
|
|
struct msgb *gsm_sim_msgb_alloc(uint32_t handle, uint8_t job_type)
|
|
{
|
|
struct msgb *msg;
|
|
struct sim_hdr *nsh;
|
|
|
|
msg = msgb_alloc_headroom(SIM_ALLOC_SIZE+SIM_ALLOC_HEADROOM,
|
|
SIM_ALLOC_HEADROOM, "SIM");
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
nsh = (struct sim_hdr *) msgb_put(msg, sizeof(*nsh));
|
|
nsh->handle = handle;
|
|
nsh->job_type = job_type;
|
|
|
|
return msg;
|
|
}
|
|
|
|
/* reply to job, after it is done. reuse the msgb in the job */
|
|
void gsm_sim_reply(struct osmocom_ms *ms, uint8_t result_type, uint8_t *result,
|
|
uint16_t result_len)
|
|
{
|
|
struct gsm_sim *sim = &ms->sim;
|
|
struct msgb *msg = sim->job_msg;
|
|
struct sim_hdr *sh;
|
|
struct gsm_sim_handler *handler;
|
|
|
|
LOGP(DSIM, LOGL_INFO, "sending result to callback function "
|
|
"(type=%d)\n", result_type);
|
|
|
|
/* if no handler, or no callback, just free the job */
|
|
sh = (struct sim_hdr *)msg->data;
|
|
handler = sim_get_handler(sim, sh->handle);
|
|
if (!handler || !handler->cb) {
|
|
LOGP(DSIM, LOGL_INFO, "no callback or no handler, "
|
|
"dropping result\n");
|
|
msgb_free(sim->job_msg);
|
|
sim->job_msg = NULL;
|
|
sim->job_state = SIM_JST_IDLE;
|
|
return;
|
|
}
|
|
|
|
/* remove data */
|
|
msgb_get(msg, msg->len - sizeof(*sh));
|
|
|
|
/* add reply data */
|
|
sh->job_type = result_type;
|
|
if (result_len)
|
|
memcpy(msgb_put(msg, result_len), result, result_len);
|
|
|
|
/* callback */
|
|
sim->job_state = SIM_JST_IDLE;
|
|
sim->job_msg = NULL;
|
|
handler->cb(ms, msg);
|
|
}
|
|
|
|
/* send APDU to card reader */
|
|
static int sim_apdu_send(struct osmocom_ms *ms, uint8_t *data, uint16_t length)
|
|
{
|
|
int rc;
|
|
|
|
LOGP(DSIM, LOGL_INFO, "sending APDU (class 0x%02x, ins 0x%02x)\n",
|
|
data[0], data[1]);
|
|
|
|
/* Cache this APDU, so it can be sent to GSMTAP on response */
|
|
if (length <= sizeof(ms->sim.apdu_data)) {
|
|
memcpy(ms->sim.apdu_data, data, length);
|
|
ms->sim.apdu_len = length;
|
|
} else {
|
|
LOGP(DSIM, LOGL_NOTICE, "Cannot cache SIM APDU "
|
|
"(len=%u), so it won't be sent to GSMTAP\n", length);
|
|
ms->sim.apdu_len = 0;
|
|
}
|
|
|
|
/* adding SAP client support
|
|
* it makes more sense to do it here then in L1CTL */
|
|
if (ms->subscr.sim_type == GSM_SIM_TYPE_SAP) {
|
|
LOGP(DSIM, LOGL_INFO, "Using SAP backend\n");
|
|
rc = sap_send_apdu(ms, data, length);
|
|
} else {
|
|
LOGP(DSIM, LOGL_INFO, "Using built-in SIM reader\n");
|
|
rc = l1ctl_tx_sim_req(ms, data, length);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* dequeue messages (RSL-SAP) */
|
|
int gsm_sim_job_dequeue(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_sim *sim = &ms->sim;
|
|
struct sim_hdr *sh;
|
|
struct msgb *msg;
|
|
struct gsm_sim_handler *handler;
|
|
|
|
/* already have a job */
|
|
if (sim->job_msg)
|
|
return 0;
|
|
|
|
/* get next job */
|
|
while ((msg = msgb_dequeue(&sim->jobs))) {
|
|
/* resolve handler */
|
|
sh = (struct sim_hdr *) msg->data;
|
|
LOGP(DSIM, LOGL_INFO, "got new job: %s (handle=%08x)\n",
|
|
get_job_name(sh->job_type), sh->handle);
|
|
handler = sim_get_handler(sim, sh->handle);
|
|
if (!handler) {
|
|
LOGP(DSIM, LOGL_INFO, "no handler, ignoring job\n");
|
|
/* does not exist anymore */
|
|
msgb_free(msg);
|
|
continue;
|
|
}
|
|
|
|
/* init job */
|
|
sim->job_state = SIM_JST_IDLE;
|
|
sim->job_msg = msg;
|
|
sim->job_handle = sh->handle;
|
|
|
|
/* process current job, message is freed there */
|
|
sim_process_job(ms);
|
|
return 1; /* work done */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* SIM commands
|
|
*/
|
|
|
|
/* 9.2.1 */
|
|
static int gsm1111_tx_select(struct osmocom_ms *ms, uint16_t fid)
|
|
{
|
|
uint8_t buffer[5 + 2];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "SELECT (file=0x%04x)\n", fid);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_SELECT;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = 2;
|
|
buffer[5] = fid >> 8;
|
|
buffer[6] = fid;
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + 2);
|
|
}
|
|
|
|
#if 0
|
|
/* 9.2.2 */
|
|
static int gsm1111_tx_status(struct osmocom_ms *ms)
|
|
{
|
|
uint8_t buffer[5];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "STATUS\n");
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_STATUS;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = 0;
|
|
|
|
return sim_apdu_send(ms, buffer, 5);
|
|
}
|
|
#endif
|
|
|
|
/* 9.2.3 */
|
|
static int gsm1111_tx_read_binary(struct osmocom_ms *ms, uint16_t offset,
|
|
uint8_t length)
|
|
{
|
|
uint8_t buffer[5];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "READ BINARY (offset=%d len=%d)\n", offset,
|
|
length);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_READ_BINARY;
|
|
buffer[2] = offset >> 8;
|
|
buffer[3] = offset;
|
|
buffer[4] = length;
|
|
|
|
return sim_apdu_send(ms, buffer, 5);
|
|
}
|
|
|
|
/* 9.2.4 */
|
|
static int gsm1111_tx_update_binary(struct osmocom_ms *ms, uint16_t offset,
|
|
uint8_t *data, uint8_t length)
|
|
{
|
|
uint8_t buffer[5 + length];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "UPDATE BINARY (offset=%d len=%d)\n", offset,
|
|
length);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_UPDATE_BINARY;
|
|
buffer[2] = offset >> 8;
|
|
buffer[3] = offset;
|
|
buffer[4] = length;
|
|
memcpy(buffer + 5, data, length);
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + length);
|
|
}
|
|
|
|
/* 9.2.5 */
|
|
static int gsm1111_tx_read_record(struct osmocom_ms *ms, uint8_t rec_no,
|
|
uint8_t mode, uint8_t length)
|
|
{
|
|
uint8_t buffer[5];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "READ RECORD (rec_no=%d mode=%d len=%d)\n",
|
|
rec_no, mode, length);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_READ_RECORD;
|
|
buffer[2] = rec_no;
|
|
buffer[3] = mode;
|
|
buffer[4] = length;
|
|
|
|
return sim_apdu_send(ms, buffer, 5);
|
|
}
|
|
|
|
/* 9.2.6 */
|
|
static int gsm1111_tx_update_record(struct osmocom_ms *ms, uint8_t rec_no,
|
|
uint8_t mode, uint8_t *data, uint8_t length)
|
|
{
|
|
uint8_t buffer[5 + length];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "UPDATE RECORD (rec_no=%d mode=%d len=%d)\n",
|
|
rec_no, mode, length);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_UPDATE_RECORD;
|
|
buffer[2] = rec_no;
|
|
buffer[3] = mode;
|
|
buffer[4] = length;
|
|
memcpy(buffer + 5, data, length);
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + length);
|
|
}
|
|
|
|
/* 9.2.7 */
|
|
static int gsm1111_tx_seek(struct osmocom_ms *ms, uint8_t type_mode,
|
|
uint8_t *pattern, uint8_t length)
|
|
{
|
|
uint8_t buffer[5 + length];
|
|
uint8_t type = type_mode >> 4;
|
|
uint8_t mode = type_mode & 0x0f;
|
|
|
|
LOGP(DSIM, LOGL_INFO, "SEEK (type=%d mode=%d len=%d)\n", type, mode,
|
|
length);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_SEEK;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = type_mode;
|
|
buffer[4] = length;
|
|
memcpy(buffer + 5, pattern, length);
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + length);
|
|
}
|
|
|
|
/* 9.2.8 */
|
|
static int gsm1111_tx_increase(struct osmocom_ms *ms, uint32_t value)
|
|
{
|
|
uint8_t buffer[5 + 3];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "INCREASE (value=%d)\n", value);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_INCREASE;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = 3;
|
|
buffer[5] = value >> 16;
|
|
buffer[6] = value >> 8;
|
|
buffer[7] = value;
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + 3);
|
|
}
|
|
|
|
/* 9.2.9 */
|
|
static int gsm1111_tx_verify_chv(struct osmocom_ms *ms, uint8_t chv_no,
|
|
uint8_t *chv, uint8_t length)
|
|
{
|
|
uint8_t buffer[5 + 8];
|
|
int i;
|
|
|
|
LOGP(DSIM, LOGL_INFO, "VERIFY CHV (CHV%d)\n", chv_no);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_VERIFY_CHV;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = chv_no;
|
|
buffer[4] = 8;
|
|
for (i = 0; i < 8; i++) {
|
|
if (i < length)
|
|
buffer[5 + i] = chv[i];
|
|
else
|
|
buffer[5 + i] = 0xff;
|
|
}
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + 8);
|
|
}
|
|
|
|
/* 9.2.10 */
|
|
static int gsm1111_tx_change_chv(struct osmocom_ms *ms, uint8_t chv_no,
|
|
uint8_t *chv_old, uint8_t length_old, uint8_t *chv_new,
|
|
uint8_t length_new)
|
|
{
|
|
uint8_t buffer[5 + 16];
|
|
int i;
|
|
|
|
LOGP(DSIM, LOGL_INFO, "CHANGE CHV (CHV%d)\n", chv_no);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_CHANGE_CHV;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = chv_no;
|
|
buffer[4] = 16;
|
|
for (i = 0; i < 8; i++) {
|
|
if (i < length_old)
|
|
buffer[5 + i] = chv_old[i];
|
|
else
|
|
buffer[5 + i] = 0xff;
|
|
if (i < length_new)
|
|
buffer[13 + i] = chv_new[i];
|
|
else
|
|
buffer[13 + i] = 0xff;
|
|
}
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + 16);
|
|
}
|
|
|
|
/* 9.2.11 */
|
|
static int gsm1111_tx_disable_chv(struct osmocom_ms *ms, uint8_t *chv,
|
|
uint8_t length)
|
|
{
|
|
uint8_t buffer[5 + 8];
|
|
int i;
|
|
|
|
LOGP(DSIM, LOGL_INFO, "DISABLE CHV (CHV1)\n");
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_DISABLE_CHV;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x01;
|
|
buffer[4] = 8;
|
|
for (i = 0; i < 8; i++) {
|
|
if (i < length)
|
|
buffer[5 + i] = chv[i];
|
|
else
|
|
buffer[5 + i] = 0xff;
|
|
}
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + 8);
|
|
}
|
|
|
|
/* 9.2.12 */
|
|
static int gsm1111_tx_enable_chv(struct osmocom_ms *ms, uint8_t *chv,
|
|
uint8_t length)
|
|
{
|
|
uint8_t buffer[5 + 8];
|
|
int i;
|
|
|
|
LOGP(DSIM, LOGL_INFO, "ENABLE CHV (CHV1)\n");
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_ENABLE_CHV;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x01;
|
|
buffer[4] = 8;
|
|
for (i = 0; i < 8; i++) {
|
|
if (i < length)
|
|
buffer[5 + i] = chv[i];
|
|
else
|
|
buffer[5 + i] = 0xff;
|
|
}
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + 8);
|
|
}
|
|
|
|
/* 9.2.13 */
|
|
static int gsm1111_tx_unblock_chv(struct osmocom_ms *ms, uint8_t chv_no,
|
|
uint8_t *chv_unblk, uint8_t length_unblk, uint8_t *chv_new,
|
|
uint8_t length_new)
|
|
{
|
|
uint8_t buffer[5 + 16];
|
|
int i;
|
|
|
|
LOGP(DSIM, LOGL_INFO, "UNBLOCK CHV (CHV%d)\n", (chv_no == 2) ? 2 : 1);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_UNBLOCK_CHV;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = (chv_no == 1) ? 0 : chv_no;
|
|
buffer[4] = 16;
|
|
for (i = 0; i < 8; i++) {
|
|
if (i < length_unblk)
|
|
buffer[5 + i] = chv_unblk[i];
|
|
else
|
|
buffer[5 + i] = 0xff;
|
|
if (i < length_new)
|
|
buffer[13 + i] = chv_new[i];
|
|
else
|
|
buffer[13 + i] = 0xff;
|
|
}
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + 16);
|
|
}
|
|
|
|
/* 9.2.14 */
|
|
static int gsm1111_tx_invalidate(struct osmocom_ms *ms)
|
|
{
|
|
uint8_t buffer[5];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "INVALIDATE\n");
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_INVALIDATE;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = 0;
|
|
|
|
return sim_apdu_send(ms, buffer, 5);
|
|
}
|
|
|
|
/* 9.2.15 */
|
|
static int gsm1111_tx_rehabilitate(struct osmocom_ms *ms)
|
|
{
|
|
uint8_t buffer[5];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "REHABILITATE\n");
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_REHABLILITATE;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = 0;
|
|
|
|
return sim_apdu_send(ms, buffer, 5);
|
|
}
|
|
|
|
/* 9.2.16 */
|
|
static int gsm1111_tx_run_gsm_algo(struct osmocom_ms *ms, uint8_t *rand)
|
|
{
|
|
uint8_t buffer[5 + 16];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "RUN GSM ALGORITHM\n");
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_RUN_GSM_ALGO;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = 16;
|
|
memcpy(buffer + 5, rand, 16);
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + 16);
|
|
}
|
|
|
|
#if 0
|
|
/* 9.2.17 */
|
|
static int gsm1111_tx_sleep(struct osmocom_ms *ms)
|
|
{
|
|
uint8_t buffer[5];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "\n");
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_SLEEP;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = 0;
|
|
|
|
return sim_apdu_send(ms, buffer, 5);
|
|
}
|
|
#endif
|
|
|
|
/* 9.2.18 */
|
|
static int gsm1111_tx_get_response(struct osmocom_ms *ms, uint8_t length)
|
|
{
|
|
uint8_t buffer[5];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "GET RESPONSE (len=%d)\n", length);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_GET_RESPONSE;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = length;
|
|
|
|
return sim_apdu_send(ms, buffer, 5);
|
|
}
|
|
|
|
#if 0
|
|
/* 9.2.19 */
|
|
static int gsm1111_tx_terminal_profile(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
uint8_t buffer[5 + length];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "TERMINAL PROFILE (len=%d)\n", length);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_TERMINAL_PROFILE;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = length;
|
|
memcpy(buffer + 5, data, length);
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + length);
|
|
}
|
|
|
|
/* 9.2.20 */
|
|
static int gsm1111_tx_envelope(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
uint8_t buffer[5 + length];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "ENVELOPE (len=%d)\n", length);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_ENVELOPE;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = length;
|
|
memcpy(buffer + 5, data, length);
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + length);
|
|
}
|
|
|
|
/* 9.2.21 */
|
|
static int gsm1111_tx_fetch(struct osmocom_ms *ms, uint8_t length)
|
|
{
|
|
uint8_t buffer[5];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "FETCH (len=%d)\n", length);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_FETCH;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = length;
|
|
|
|
return sim_apdu_send(ms, buffer, 5);
|
|
}
|
|
|
|
/* 9.2.22 */
|
|
static int gsm1111_tx_terminal_response(struct osmocom_ms *ms, uint8_t *data,
|
|
uint8_t length)
|
|
{
|
|
uint8_t buffer[5 + length];
|
|
|
|
LOGP(DSIM, LOGL_INFO, "TERMINAL RESPONSE (len=%d)\n", length);
|
|
buffer[0] = GSM1111_CLASS_GSM;
|
|
buffer[1] = GSM1111_INST_TERMINAL_RESPONSE;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = length;
|
|
memcpy(buffer + 5, data, length);
|
|
|
|
return sim_apdu_send(ms, buffer, 5 + length);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* SIM state machine
|
|
*/
|
|
|
|
/* process job */
|
|
static int sim_process_job(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_sim *sim = &ms->sim;
|
|
uint8_t *payload, *payload2;
|
|
uint16_t payload_len, payload_len2;
|
|
struct sim_hdr *sh;
|
|
uint8_t cause;
|
|
int i;
|
|
|
|
/* no current */
|
|
if (!sim->job_msg)
|
|
return 0;
|
|
|
|
sh = (struct sim_hdr *)sim->job_msg->data;
|
|
payload = sim->job_msg->data + sizeof(*sh);
|
|
payload_len = sim->job_msg->len - sizeof(*sh);
|
|
|
|
/* do reset before sim reading */
|
|
if (!sim->reset) {
|
|
sim->reset = 1;
|
|
// FIXME: send reset command to L1
|
|
}
|
|
|
|
/* navigate to right DF */
|
|
switch (sh->job_type) {
|
|
case SIM_JOB_READ_BINARY:
|
|
case SIM_JOB_UPDATE_BINARY:
|
|
case SIM_JOB_READ_RECORD:
|
|
case SIM_JOB_UPDATE_RECORD:
|
|
case SIM_JOB_SEEK_RECORD:
|
|
case SIM_JOB_INCREASE:
|
|
case SIM_JOB_INVALIDATE:
|
|
case SIM_JOB_REHABILITATE:
|
|
case SIM_JOB_RUN_GSM_ALGO:
|
|
/* check MF / DF */
|
|
i = 0;
|
|
while (sh->path[i] && sim->path[i]) {
|
|
if (sh->path[i] != sim->path[i])
|
|
break;
|
|
i++;
|
|
}
|
|
/* if path in message is shorter or if paths are different */
|
|
if (sim->path[i]) {
|
|
LOGP(DSIM, LOGL_INFO, "go MF\n");
|
|
sim->job_state = SIM_JST_SELECT_MFDF;
|
|
/* go MF */
|
|
sim->path[0] = 0;
|
|
return gsm1111_tx_select(ms, 0x3f00);
|
|
}
|
|
/* if path in message is longer */
|
|
if (sh->path[i]) {
|
|
LOGP(DSIM, LOGL_INFO, "requested path is longer, go "
|
|
"child %s\n", get_df_name(sh->path[i]));
|
|
sim->job_state = SIM_JST_SELECT_MFDF;
|
|
/* select child */
|
|
sim->path[i] = sh->path[i];
|
|
sim->path[i + 1] = 0;
|
|
return gsm1111_tx_select(ms, sh->path[i]);
|
|
}
|
|
/* if paths are equal, continue */
|
|
}
|
|
|
|
/* set state and trigger SIM process */
|
|
switch (sh->job_type) {
|
|
case SIM_JOB_READ_BINARY:
|
|
case SIM_JOB_UPDATE_BINARY:
|
|
case SIM_JOB_READ_RECORD:
|
|
case SIM_JOB_UPDATE_RECORD:
|
|
case SIM_JOB_SEEK_RECORD:
|
|
case SIM_JOB_INCREASE:
|
|
case SIM_JOB_INVALIDATE:
|
|
case SIM_JOB_REHABILITATE:
|
|
sim->job_state = SIM_JST_SELECT_EF;
|
|
sim->file = sh->file;
|
|
return gsm1111_tx_select(ms, sh->file);
|
|
case SIM_JOB_RUN_GSM_ALGO:
|
|
if (payload_len != 16) {
|
|
LOGP(DSIM, LOGL_ERROR, "random not 16 bytes\n");
|
|
break;
|
|
}
|
|
sim->job_state = SIM_JST_RUN_GSM_ALGO;
|
|
return gsm1111_tx_run_gsm_algo(ms, payload);
|
|
case SIM_JOB_PIN1_UNLOCK:
|
|
payload_len = strlen((char *)payload);
|
|
if (payload_len < 4 || payload_len > 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n");
|
|
break;
|
|
}
|
|
sim->job_state = SIM_JST_PIN1_UNLOCK;
|
|
return gsm1111_tx_verify_chv(ms, 0x01, payload, payload_len);
|
|
case SIM_JOB_PIN2_UNLOCK:
|
|
payload_len = strlen((char *)payload);
|
|
if (payload_len < 4 || payload_len > 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n");
|
|
break;
|
|
}
|
|
sim->job_state = SIM_JST_PIN2_UNLOCK;
|
|
return gsm1111_tx_verify_chv(ms, 0x02, payload, payload_len);
|
|
case SIM_JOB_PIN1_CHANGE:
|
|
payload_len = strlen((char *)payload);
|
|
payload2 = payload + payload_len + 1;
|
|
payload_len2 = strlen((char *)payload2);
|
|
if (payload_len < 4 || payload_len > 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key1 not in range 4..8\n");
|
|
break;
|
|
}
|
|
if (payload_len2 < 4 || payload_len2 > 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key2 not in range 4..8\n");
|
|
break;
|
|
}
|
|
sim->job_state = SIM_JST_PIN1_CHANGE;
|
|
return gsm1111_tx_change_chv(ms, 0x01, payload, payload_len,
|
|
payload2, payload_len2);
|
|
case SIM_JOB_PIN2_CHANGE:
|
|
payload_len = strlen((char *)payload);
|
|
payload2 = payload + payload_len + 1;
|
|
payload_len2 = strlen((char *)payload2);
|
|
if (payload_len < 4 || payload_len > 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key1 not in range 4..8\n");
|
|
break;
|
|
}
|
|
if (payload_len2 < 4 || payload_len2 > 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key2 not in range 4..8\n");
|
|
break;
|
|
}
|
|
sim->job_state = SIM_JST_PIN2_CHANGE;
|
|
return gsm1111_tx_change_chv(ms, 0x02, payload, payload_len,
|
|
payload2, payload_len2);
|
|
case SIM_JOB_PIN1_DISABLE:
|
|
payload_len = strlen((char *)payload);
|
|
if (payload_len < 4 || payload_len > 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n");
|
|
break;
|
|
}
|
|
sim->job_state = SIM_JST_PIN1_DISABLE;
|
|
return gsm1111_tx_disable_chv(ms, payload, payload_len);
|
|
case SIM_JOB_PIN1_ENABLE:
|
|
payload_len = strlen((char *)payload);
|
|
if (payload_len < 4 || payload_len > 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key not in range 4..8\n");
|
|
break;
|
|
}
|
|
sim->job_state = SIM_JST_PIN1_ENABLE;
|
|
return gsm1111_tx_enable_chv(ms, payload, payload_len);
|
|
case SIM_JOB_PIN1_UNBLOCK:
|
|
payload_len = strlen((char *)payload);
|
|
payload2 = payload + payload_len + 1;
|
|
payload_len2 = strlen((char *)payload2);
|
|
if (payload_len != 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key1 not 8 digits\n");
|
|
break;
|
|
}
|
|
if (payload_len2 < 4 || payload_len2 > 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key2 not in range 4..8\n");
|
|
break;
|
|
}
|
|
sim->job_state = SIM_JST_PIN1_UNBLOCK;
|
|
/* NOTE: CHV1 is coded 0x00 here */
|
|
return gsm1111_tx_unblock_chv(ms, 0x00, payload, payload_len,
|
|
payload2, payload_len2);
|
|
case SIM_JOB_PIN2_UNBLOCK:
|
|
payload_len = strlen((char *)payload);
|
|
payload2 = payload + payload_len + 1;
|
|
payload_len2 = strlen((char *)payload2);
|
|
if (payload_len != 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key1 not 8 digits\n");
|
|
break;
|
|
}
|
|
if (payload_len2 < 4 || payload_len2 > 8) {
|
|
LOGP(DSIM, LOGL_ERROR, "key2 not in range 4..8\n");
|
|
break;
|
|
}
|
|
sim->job_state = SIM_JST_PIN2_UNBLOCK;
|
|
return gsm1111_tx_unblock_chv(ms, 0x02, payload, payload_len,
|
|
payload2, payload_len2);
|
|
}
|
|
|
|
LOGP(DSIM, LOGL_ERROR, "unknown job %x, please fix\n", sh->job_type);
|
|
cause = SIM_CAUSE_REQUEST_ERROR;
|
|
gsm_sim_reply(ms, SIM_JOB_ERROR, &cause, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* receive SIM response */
|
|
int sim_apdu_resp(struct osmocom_ms *ms, struct msgb *msg)
|
|
{
|
|
struct gsm_sim *sim = &ms->sim;
|
|
uint8_t *payload;
|
|
uint16_t payload_len;
|
|
uint8_t *data = msg->data;
|
|
int length = msg->len, ef_len;
|
|
uint8_t sw1, sw2;
|
|
uint8_t cause;
|
|
uint8_t pin_cause[2];
|
|
struct sim_hdr *sh;
|
|
struct gsm1111_response_ef *ef;
|
|
struct gsm1111_response_mfdf *mfdf;
|
|
struct gsm1111_response_mfdf_gsm *mfdf_gsm;
|
|
int i;
|
|
|
|
/* If there is cached APDU */
|
|
if (ms->sim.apdu_len) {
|
|
/* ... and APDU buffer has enough space, send it to GSMTAP */
|
|
if ((ms->sim.apdu_len + length) <= sizeof(ms->sim.apdu_data)) {
|
|
memcpy(ms->sim.apdu_data + ms->sim.apdu_len, data, length);
|
|
ms->sim.apdu_len += length;
|
|
gsmtap_send_ex(gsmtap_inst, GSMTAP_TYPE_SIM,
|
|
0, 0, 0, 0, 0, 0, 0, ms->sim.apdu_data, ms->sim.apdu_len);
|
|
}
|
|
}
|
|
|
|
/* ignore, if current job already gone */
|
|
if (!sim->job_msg) {
|
|
LOGP(DSIM, LOGL_ERROR, "received APDU but no job, "
|
|
"please fix!\n");
|
|
msgb_free(msg);
|
|
return 0;
|
|
}
|
|
|
|
sh = (struct sim_hdr *)sim->job_msg->data;
|
|
payload = sim->job_msg->data + sizeof(*sh);
|
|
payload_len = sim->job_msg->len - sizeof(*sh);
|
|
|
|
/* process status */
|
|
if (length < 2) {
|
|
msgb_free(msg);
|
|
return 0;
|
|
}
|
|
sw1 = data[length - 2];
|
|
sw2 = data[length - 1];
|
|
length -= 2;
|
|
LOGP(DSIM, LOGL_INFO, "received APDU (len=%d sw1=0x%02x sw2=0x%02x)\n",
|
|
length, sw1, sw2);
|
|
|
|
switch (sw1) {
|
|
case GSM1111_STAT_SECURITY:
|
|
LOGP(DSIM, LOGL_NOTICE, "SIM Security\n");
|
|
/* error */
|
|
if (sw2 != GSM1111_SEC_NO_ACCESS && sw2 != GSM1111_SEC_BLOCKED)
|
|
goto sim_error;
|
|
|
|
/* select the right remaining counter an cause */
|
|
// FIXME: read status to replace "*_remain"-counters
|
|
switch (sim->job_state) {
|
|
case SIM_JST_PIN1_UNBLOCK:
|
|
if (sw2 == GSM1111_SEC_NO_ACCESS) {
|
|
pin_cause[0] = SIM_CAUSE_PIN1_BLOCKED;
|
|
pin_cause[1] = --sim->unblk1_remain;
|
|
} else {
|
|
pin_cause[0] = SIM_CAUSE_PUC_BLOCKED;
|
|
pin_cause[1] = 0;
|
|
}
|
|
break;
|
|
case SIM_JST_PIN2_UNLOCK:
|
|
case SIM_JST_PIN2_CHANGE:
|
|
if (sw2 == GSM1111_SEC_NO_ACCESS && sim->chv2_remain) {
|
|
pin_cause[0] = SIM_CAUSE_PIN2_REQUIRED;
|
|
pin_cause[1] = sim->chv2_remain--;
|
|
} else {
|
|
pin_cause[0] = SIM_CAUSE_PIN2_BLOCKED;
|
|
pin_cause[1] = sim->unblk2_remain;
|
|
}
|
|
break;
|
|
case SIM_JST_PIN2_UNBLOCK:
|
|
if (sw2 == GSM1111_SEC_NO_ACCESS) {
|
|
pin_cause[0] = SIM_CAUSE_PIN2_BLOCKED;
|
|
pin_cause[1] = --sim->unblk2_remain;
|
|
} else {
|
|
pin_cause[0] = SIM_CAUSE_PUC_BLOCKED;
|
|
pin_cause[1] = 0;
|
|
}
|
|
break;
|
|
case SIM_JST_PIN1_UNLOCK:
|
|
case SIM_JST_PIN1_CHANGE:
|
|
case SIM_JST_PIN1_DISABLE:
|
|
case SIM_JST_PIN1_ENABLE:
|
|
default:
|
|
if (sw2 == GSM1111_SEC_NO_ACCESS && sim->chv1_remain) {
|
|
pin_cause[0] = SIM_CAUSE_PIN1_REQUIRED;
|
|
pin_cause[1] = sim->chv1_remain--;
|
|
} else {
|
|
pin_cause[0] = SIM_CAUSE_PIN1_BLOCKED;
|
|
pin_cause[1] = sim->unblk1_remain;
|
|
}
|
|
break;
|
|
}
|
|
gsm_sim_reply(ms, SIM_JOB_ERROR, pin_cause, 2);
|
|
msgb_free(msg);
|
|
return 0;
|
|
case GSM1111_STAT_MEM_PROBLEM:
|
|
if (sw2 >= 0x40) {
|
|
LOGP(DSIM, LOGL_NOTICE, "memory of SIM failed\n");
|
|
sim_error:
|
|
cause = SIM_CAUSE_SIM_ERROR;
|
|
gsm_sim_reply(ms, SIM_JOB_ERROR, &cause, 1);
|
|
msgb_free(msg);
|
|
return 0;
|
|
}
|
|
LOGP(DSIM, LOGL_NOTICE, "memory of SIM is bad (write took %d "
|
|
"times to succeed)\n", sw2);
|
|
/* fall through */
|
|
case GSM1111_STAT_NORMAL:
|
|
case GSM1111_STAT_PROACTIVE:
|
|
case GSM1111_STAT_DL_ERROR:
|
|
case GSM1111_STAT_RESPONSE:
|
|
case GSM1111_STAT_RESPONSE_TOO:
|
|
LOGP(DSIM, LOGL_INFO, "command successful\n");
|
|
break;
|
|
default:
|
|
LOGP(DSIM, LOGL_INFO, "command failed\n");
|
|
request_error:
|
|
cause = SIM_CAUSE_REQUEST_ERROR;
|
|
gsm_sim_reply(ms, SIM_JOB_ERROR, &cause, 1);
|
|
msgb_free(msg);
|
|
return 0;
|
|
}
|
|
|
|
|
|
switch (sim->job_state) {
|
|
/* step 1: after selecting MF / DF, request the response */
|
|
case SIM_JST_SELECT_MFDF:
|
|
/* not enough data */
|
|
if (sw2 < 22) {
|
|
LOGP(DSIM, LOGL_NOTICE, "expecting minimum 22 bytes\n");
|
|
goto sim_error;
|
|
}
|
|
/* request response */
|
|
sim->job_state = SIM_JST_SELECT_MFDF_RESP;
|
|
gsm1111_tx_get_response(ms, sw2);
|
|
msgb_free(msg);
|
|
return 0;
|
|
/* step 2: after getting response of selecting MF / DF, continue
|
|
* to "process_job".
|
|
*/
|
|
case SIM_JST_SELECT_MFDF_RESP:
|
|
if (length < 22) {
|
|
LOGP(DSIM, LOGL_NOTICE, "expecting minimum 22 bytes\n");
|
|
goto sim_error;
|
|
}
|
|
mfdf = (struct gsm1111_response_mfdf *)data;
|
|
mfdf_gsm = (struct gsm1111_response_mfdf_gsm *)(data + 13);
|
|
sim->chv1_remain = mfdf_gsm->chv1_remain;
|
|
sim->chv2_remain = mfdf_gsm->chv2_remain;
|
|
sim->unblk1_remain = mfdf_gsm->unblk1_remain;
|
|
sim->unblk2_remain = mfdf_gsm->unblk2_remain;
|
|
/* if MF was selected */
|
|
if (sim->path[0] == 0) {
|
|
/* if MF was selected, but MF is not indicated */
|
|
if (ntohs(mfdf->file_id) != 0x3f00) {
|
|
LOGP(DSIM, LOGL_NOTICE, "Not MF\n");
|
|
goto sim_error;
|
|
}
|
|
/* if MF was selected, but type is not indicated */
|
|
if (mfdf->tof != GSM1111_TOF_MF) {
|
|
LOGP(DSIM, LOGL_NOTICE, "MF %02x != %02x "
|
|
"%04x\n", mfdf->tof, GSM1111_TOF_MF,
|
|
sim->path[0]);
|
|
goto sim_error;
|
|
}
|
|
/* now continue */
|
|
msgb_free(msg);
|
|
return sim_process_job(ms);
|
|
}
|
|
/* if DF was selected, but this DF is not indicated */
|
|
i = 0;
|
|
while (sim->path[i + 1])
|
|
i++;
|
|
if (ntohs(mfdf->file_id) != sim->path[i]) {
|
|
LOGP(DSIM, LOGL_NOTICE, "Path %04x != %04x\n",
|
|
ntohs(mfdf->file_id), sim->path[i]);
|
|
goto sim_error;
|
|
}
|
|
/* if DF was selected, but type is not indicated */
|
|
if (mfdf->tof != GSM1111_TOF_DF) {
|
|
LOGP(DSIM, LOGL_NOTICE, "TOF error\n");
|
|
goto sim_error;
|
|
}
|
|
/* now continue */
|
|
msgb_free(msg);
|
|
return sim_process_job(ms);
|
|
/* step 1: after selecting EF, request response of SELECT */
|
|
case SIM_JST_SELECT_EF:
|
|
/* not enough data */
|
|
if (sw2 < 14) {
|
|
LOGP(DSIM, LOGL_NOTICE, "expecting minimum 14 bytes\n");
|
|
goto sim_error;
|
|
}
|
|
/* request response */
|
|
sim->job_state = SIM_JST_SELECT_EF_RESP;
|
|
gsm1111_tx_get_response(ms, sw2);
|
|
msgb_free(msg);
|
|
return 0;
|
|
/* step 2: after getting response of selecting EF, do file command */
|
|
case SIM_JST_SELECT_EF_RESP:
|
|
if (length < 14) {
|
|
LOGP(DSIM, LOGL_NOTICE, "expecting minimum 14 bytes\n");
|
|
goto sim_error;
|
|
}
|
|
ef = (struct gsm1111_response_ef *)data;
|
|
/* if EF was selected, but type is not indicated */
|
|
if (ntohs(ef->file_id) != sim->file) {
|
|
LOGP(DSIM, LOGL_NOTICE, "EF ID %04x != %04x\n",
|
|
ntohs(ef->file_id), sim->file);
|
|
goto sim_error;
|
|
}
|
|
/* check for record */
|
|
if (length >= 15 && ef->length >= 2 && ef->structure != 0x00) {
|
|
/* get length of record */
|
|
ef_len = ntohs(ef->file_size);
|
|
if (ef_len < data[14]) {
|
|
LOGP(DSIM, LOGL_NOTICE, "total length is "
|
|
"smaller (%d) than record size (%d)\n",
|
|
ef_len, data[14]);
|
|
goto request_error;
|
|
}
|
|
ef_len = data[14];
|
|
LOGP(DSIM, LOGL_NOTICE, "selected record (len %d "
|
|
"structure %d)\n", ef_len, ef->structure);
|
|
} else {
|
|
/* get length of file */
|
|
ef_len = ntohs(ef->file_size);
|
|
LOGP(DSIM, LOGL_NOTICE, "selected file (len %d)\n",
|
|
ef_len);
|
|
}
|
|
/* do file command */
|
|
sim->job_state = SIM_JST_WAIT_FILE;
|
|
switch (sh->job_type) {
|
|
case SIM_JOB_READ_BINARY:
|
|
// FIXME: do chunks when greater or equal 256 bytes */
|
|
gsm1111_tx_read_binary(ms, 0, ef_len);
|
|
break;
|
|
case SIM_JOB_UPDATE_BINARY:
|
|
// FIXME: do chunks when greater or equal 256 bytes */
|
|
if (ef_len < payload_len) {
|
|
LOGP(DSIM, LOGL_NOTICE, "selected file is "
|
|
"smaller (%d) than data to update "
|
|
"(%d)\n", ef_len, payload_len);
|
|
goto request_error;
|
|
}
|
|
gsm1111_tx_update_binary(ms, 0, payload, payload_len);
|
|
break;
|
|
case SIM_JOB_READ_RECORD:
|
|
gsm1111_tx_read_record(ms, sh->rec_no, sh->rec_mode,
|
|
ef_len);
|
|
break;
|
|
case SIM_JOB_UPDATE_RECORD:
|
|
if (ef_len != payload_len) {
|
|
LOGP(DSIM, LOGL_NOTICE, "selected file length "
|
|
"(%d) does not equal record to update "
|
|
"(%d)\n", ef_len, payload_len);
|
|
goto request_error;
|
|
}
|
|
gsm1111_tx_update_record(ms, sh->rec_no, sh->rec_mode,
|
|
payload, payload_len);
|
|
break;
|
|
case SIM_JOB_SEEK_RECORD:
|
|
gsm1111_tx_seek(ms, sh->seek_type_mode, data, length);
|
|
break;
|
|
case SIM_JOB_INCREASE:
|
|
if (length != 4) {
|
|
LOGP(DSIM, LOGL_ERROR, "expecting uint32_t as "
|
|
"value length, but got %d bytes\n",
|
|
length);
|
|
goto request_error;
|
|
}
|
|
gsm1111_tx_increase(ms, *((uint32_t *)data));
|
|
break;
|
|
case SIM_JOB_INVALIDATE:
|
|
gsm1111_tx_invalidate(ms);
|
|
break;
|
|
case SIM_JOB_REHABILITATE:
|
|
gsm1111_tx_rehabilitate(ms);
|
|
break;
|
|
}
|
|
msgb_free(msg);
|
|
return 0;
|
|
/* step 3: after processing file command, job is done */
|
|
case SIM_JST_WAIT_FILE:
|
|
/* reply job with data */
|
|
gsm_sim_reply(ms, SIM_JOB_OK, data, length);
|
|
msgb_free(msg);
|
|
return 0;
|
|
/* step 1: after running GSM algorithm, request response */
|
|
case SIM_JST_RUN_GSM_ALGO:
|
|
/* not enough data */
|
|
if (sw2 < 12) {
|
|
LOGP(DSIM, LOGL_NOTICE, "expecting minimum 12 bytes\n");
|
|
goto sim_error;
|
|
}
|
|
/* request response */
|
|
sim->job_state = SIM_JST_RUN_GSM_ALGO_RESP;
|
|
gsm1111_tx_get_response(ms, sw2);
|
|
msgb_free(msg);
|
|
return 0;
|
|
/* step 2: after processing GSM command, job is done */
|
|
case SIM_JST_RUN_GSM_ALGO_RESP:
|
|
/* reply job with data */
|
|
gsm_sim_reply(ms, SIM_JOB_OK, data, length);
|
|
msgb_free(msg);
|
|
return 0;
|
|
case SIM_JST_PIN1_UNLOCK:
|
|
case SIM_JST_PIN1_CHANGE:
|
|
case SIM_JST_PIN1_DISABLE:
|
|
case SIM_JST_PIN1_ENABLE:
|
|
case SIM_JST_PIN1_UNBLOCK:
|
|
case SIM_JST_PIN2_UNLOCK:
|
|
case SIM_JST_PIN2_CHANGE:
|
|
case SIM_JST_PIN2_UNBLOCK:
|
|
/* reply job with data */
|
|
gsm_sim_reply(ms, SIM_JOB_OK, data, length);
|
|
msgb_free(msg);
|
|
return 0;
|
|
}
|
|
|
|
LOGP(DSIM, LOGL_ERROR, "unknown state %u, please fix!\n",
|
|
sim->job_state);
|
|
goto request_error;
|
|
}
|
|
|
|
/*
|
|
* API
|
|
*/
|
|
|
|
/* open access to sim */
|
|
uint32_t sim_open(struct osmocom_ms *ms,
|
|
void (*cb)(struct osmocom_ms *ms, struct msgb *msg))
|
|
{
|
|
struct gsm_sim *sim = &ms->sim;
|
|
struct gsm_sim_handler *handler;
|
|
|
|
/* create handler and attach */
|
|
handler = talloc_zero(ms, struct gsm_sim_handler);
|
|
if (!handler)
|
|
return 0;
|
|
handler->handle = new_handle++;
|
|
handler->cb = cb;
|
|
llist_add_tail(&handler->entry, &sim->handlers);
|
|
|
|
return handler->handle;
|
|
}
|
|
|
|
/* close access to sim */
|
|
void sim_close(struct osmocom_ms *ms, uint32_t handle)
|
|
{
|
|
struct gsm_sim *sim = &ms->sim;
|
|
struct gsm_sim_handler *handler;
|
|
|
|
handler = sim_get_handler(sim, handle);
|
|
if (!handle)
|
|
return;
|
|
|
|
/* kill ourself */
|
|
llist_del(&handler->entry);
|
|
talloc_free(handler);
|
|
}
|
|
|
|
/* send job */
|
|
void sim_job(struct osmocom_ms *ms, struct msgb *msg)
|
|
{
|
|
struct gsm_sim *sim = &ms->sim;
|
|
|
|
msgb_enqueue(&sim->jobs, msg);
|
|
}
|
|
|
|
/*
|
|
* init
|
|
*/
|
|
|
|
int gsm_sim_init(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_sim *sim = &ms->sim;
|
|
|
|
/* current path is undefined, forching MF */
|
|
sim->path[0] = 0x0bad;
|
|
sim->path[1] = 0;
|
|
sim->file = 0;
|
|
|
|
INIT_LLIST_HEAD(&sim->handlers);
|
|
INIT_LLIST_HEAD(&sim->jobs);
|
|
|
|
LOGP(DSIM, LOGL_INFO, "init SIM client\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gsm_sim_exit(struct osmocom_ms *ms)
|
|
{
|
|
struct gsm_sim *sim = &ms->sim;
|
|
struct gsm_sim_handler *handler, *handler2;
|
|
struct msgb *msg;
|
|
|
|
LOGP(DSIM, LOGL_INFO, "exit SIM client\n");
|
|
|
|
/* remove pending job msg */
|
|
if (sim->job_msg) {
|
|
msgb_free(sim->job_msg);
|
|
sim->job_msg = NULL;
|
|
}
|
|
/* flush handlers */
|
|
llist_for_each_entry_safe(handler, handler2, &sim->handlers, entry)
|
|
sim_close(ms, handler->handle);
|
|
/* flush jobs */
|
|
while ((msg = msgb_dequeue(&sim->jobs)))
|
|
msgb_free(msg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|