/* * * oFono - Open Source Telephony * * Copyright (C) 2011 ST-Ericsson AB. * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "simutil.h" #include "isimodem.h" #include "isiutil.h" #include "sim.h" #include "uicc.h" #include "uicc-util.h" #include "debug.h" /* File info parameters */ #define FCP_TEMPLATE 0x62 #define FCP_FILE_SIZE 0x80 #define FCP_FILE_DESC 0x82 #define FCP_FILE_ID 0x83 #define FCP_FILE_LIFECYCLE 0x8A #define FCP_FILE_SECURITY_ARR 0x8B #define FCP_FILE_SECURITY_COMPACT 0x8C #define FCP_FILE_SECURITY_EXPANDED 0xAB #define FCP_PIN_STATUS 0xC6 #define SIM_EFARR_FILEID 0x6f06 #define MAX_SIM_APPS 10 #define MAX_IMSI_LENGTH 15 enum uicc_flag { UICC_FLAG_APP_STARTED = 1 << 0, UICC_FLAG_PIN_STATE_RECEIVED = 1 << 1, UICC_FLAG_PASSWD_REQUIRED = 1 << 2, }; static GHashTable *g_modems; struct file_info { int fileid; int length; int structure; int record_length; uint8_t access[3]; uint8_t file_status; }; static const struct file_info static_file_info[] = { { SIM_EFSPN_FILEID, 17, 0, 0, { 0x0e, 0xff, 0xee }, 1 }, { SIM_EF_ICCID_FILEID, 10, 0, 10, { 0x0f, 0xff, 0xee }, 1 }, { SIM_EFPL_FILEID, 1, 0, 1, { 0x0f, 0xff, 0xff }, 1 }, { SIM_EFLI_FILEID, 1, 0, 1, { 0x0f, 0xff, 0xff }, 1 }, { SIM_EFMSISDN_FILEID, 28, 1, 28, { 0x01, 0xff, 0xee }, 1 }, { SIM_EFAD_FILEID, 20, 0, 20, { 0x0e, 0xff, 0xee }, 1 }, { SIM_EFPHASE_FILEID, 1, 0, 1, { 0x0e, 0xff, 0xee }, 1 }, { SIM_EFPNN_FILEID, 4 * 18, 1, 18, { 0x0e, 0xff, 0xee }, 1 }, { SIM_EFOPL_FILEID, 4 * 24, 1, 24, { 0x0e, 0xff, 0xee }, 1 }, { SIM_EFMBI_FILEID, 5, 1, 5, { 0x0e, 0xff, 0xee }, 1 }, { SIM_EFMWIS_FILEID, 6, 1, 6, { 0x01, 0xff, 0xee }, 1 }, { SIM_EFSPDI_FILEID, 64, 0, 64, { 0x0e, 0xff, 0xee }, 1 }, { SIM_EFECC_FILEID, 5 * 3, 0, 3, { 0x0e, 0xff, 0xee }, 1 }, { SIM_EFCBMIR_FILEID, 8 * 4, 0, 4, { 0x01, 0xff, 0xee }, 1 }, { SIM_EFCBMI_FILEID, 8 * 2, 0, 2, { 0x01, 0xff, 0xee }, 1 }, { SIM_EFCBMID_FILEID, 8 * 2, 0, 2, { 0x01, 0xff, 0x11 }, 1 }, { SIM_EFSMSP_FILEID, 56, 1, 56, { 0x01, 0xff, 0xee }, 1 }, { SIM_EFIMSI_FILEID, 9, 0, 9, { 0x0e, 0xff, 0xee }, 1 }, }; static gboolean check_resp(const GIsiMessage *msg, uint8_t msgid, uint8_t service) { uint8_t type; uint8_t cause; if (g_isi_msg_error(msg) < 0) { DBG("Error: %s", g_isi_msg_strerror(msg)); return FALSE; } if (g_isi_msg_id(msg) != msgid) { DBG("Unexpected msg: %s", sim_message_id_name(g_isi_msg_id(msg))); return FALSE; } if (!g_isi_msg_data_get_byte(msg, 1, &cause) || cause != UICC_STATUS_OK) { DBG("Request failed: %s", uicc_status_name(cause)); return FALSE; } if (!g_isi_msg_data_get_byte(msg, 0, &type) || type != service) { DBG("Unexpected service: 0x%02X (0x%02X)", type, service); return FALSE; } return TRUE; } struct uicc_file_info_cb_data { void *cb; void *data; void *user; struct ofono_sim *sim; }; static gboolean decode_uicc_usim_type(GIsiSubBlockIter *iter, uint16_t *length, uint16_t *file_id, uint16_t *record_length, uint8_t *records, uint8_t *structure) { uint8_t fcp = 0; uint8_t desc = 0; uint8_t coding = 0; uint8_t fcp_len = 0; uint8_t read = 0; uint8_t item_len = 0; if (!g_isi_sb_iter_get_byte(iter, &fcp, 8)) return FALSE; if (fcp != FCP_TEMPLATE) return FALSE; if (!g_isi_sb_iter_get_byte(iter, &fcp_len, 9)) return FALSE; for (read = 0; read < fcp_len; read += item_len + 2) { uint8_t id; if (!g_isi_sb_iter_get_byte(iter, &id, read + 10)) return FALSE; if (!g_isi_sb_iter_get_byte(iter, &item_len, read + 11)) return FALSE; switch (id) { case FCP_FILE_SIZE: if (item_len != 2) return FALSE; if (!g_isi_sb_iter_get_word(iter, length, read + 10 + 2)) return FALSE; break; case FCP_FILE_ID: if (item_len != 2) return FALSE; if (!g_isi_sb_iter_get_word(iter, file_id, read + 10 + 2)) return FALSE; break; case FCP_FILE_DESC: if (item_len < 2) return FALSE; if (!g_isi_sb_iter_get_byte(iter, &desc, read + 10 + 2)) return FALSE; if (!g_isi_sb_iter_get_byte(iter, &coding, read + 10 + 3)) return FALSE; if (item_len < 4) break; if (!g_isi_sb_iter_get_word(iter, record_length, read + 10 + 4)) return FALSE; if (!g_isi_sb_iter_get_byte(iter, records, read + 10 + 6)) return FALSE; break; /* * Not implemented, using static access rules * as these are used only for cacheing See * ETSI TS 102 221, ch 11.1.1.4.7 and Annexes * E, F and G. */ case FCP_FILE_SECURITY_ARR: case FCP_FILE_SECURITY_COMPACT: case FCP_FILE_SECURITY_EXPANDED: case FCP_FILE_LIFECYCLE: default: DBG("FCP id %02X not supported", id); break; } } if ((desc & 7) == 1) *structure = OFONO_SIM_FILE_STRUCTURE_TRANSPARENT; else if ((desc & 7) == 2) *structure = OFONO_SIM_FILE_STRUCTURE_FIXED; else if ((desc & 7) == 6) *structure = OFONO_SIM_FILE_STRUCTURE_CYCLIC; return TRUE; } static void uicc_file_info_resp_cb(const GIsiMessage *msg, void *opaque) { struct uicc_file_info_cb_data *cbd = opaque; struct uicc_sim_data *sd = ofono_sim_get_data(cbd->sim); struct file_info const *info = cbd->user; ofono_sim_file_info_cb_t cb = cbd->cb; GIsiSubBlockIter iter; uint16_t length = 0; uint16_t record_length = 0; uint8_t structure = 0xFF; uint8_t records = 0; uint16_t file_id = 0; uint8_t access[3] = {0, 0, 0}; uint8_t item_len = 0; uint8_t message_id = 0; uint8_t service_type = 0; uint8_t status = 0; uint8_t details = 0; uint8_t num_subblocks = 0; uint8_t file_status = 1; message_id = g_isi_msg_id(msg); DBG("uicc_file_info_resp_cb: msg_id=%d, msg len=%zu", message_id, g_isi_msg_data_len(msg)); if (message_id != UICC_APPL_CMD_RESP) goto error; if (!g_isi_msg_data_get_byte(msg, 0, &service_type) || !g_isi_msg_data_get_byte(msg, 1, &status) || !g_isi_msg_data_get_byte(msg, 2, &details) || !g_isi_msg_data_get_byte(msg, 5, &num_subblocks)) goto error; DBG("%s, service %s, status %s, details %s, nm_sb %d", uicc_message_id_name(message_id), uicc_service_type_name(service_type), uicc_status_name(status), uicc_details_name(details), num_subblocks); if (info) { access[0] = info->access[0]; access[1] = info->access[1]; access[2] = info->access[2]; file_status = info->file_status; } for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_subblocks); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { uint8_t sb_id = g_isi_sb_iter_get_id(&iter); DBG("Subblock %s", uicc_subblock_name(sb_id)); if (sb_id != UICC_SB_FCI) continue; DBG("Decoding UICC_SB_FCI"); switch (sd->app_type) { case UICC_APPL_TYPE_UICC_USIM: DBG("UICC_APPL_TYPE_UICC_USIM"); if (!decode_uicc_usim_type(&iter, &length, &file_id, &record_length, &records, &structure)) goto error; break; case UICC_APPL_TYPE_ICC_SIM: DBG("UICC_APPL_TYPE_ICC_SIM"); if (!g_isi_sb_iter_get_word(&iter, &length, 10)) goto error; if (!g_isi_sb_iter_get_word(&iter, &file_id, 12)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &access[0], 16)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &access[0], 17)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &access[0], 18)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &item_len, 20)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &structure, 21)) goto error; if (item_len == 2) { uint8_t byte; if (!g_isi_sb_iter_get_byte(&iter, &byte, 22)) goto error; record_length = byte; } break; default: DBG("Application type %d not supported", sd->app_type); break; } DBG("fileid=%04X, filelen=%d, records=%d, reclen=%d, structure=%d", file_id, length, records, record_length, structure); CALLBACK_WITH_SUCCESS(cb, length, structure, record_length, access, file_status, cbd->data); return; } error: DBG("Error reading file info"); CALLBACK_WITH_FAILURE(cb, -1, -1, -1, NULL, 0, cbd->data); } static gboolean send_uicc_read_file_info(GIsiClient *client, uint8_t app_id, int fileid, uint8_t df_len, int mf_path, int df1_path, int df2_path, GIsiNotifyFunc notify, void *data, GDestroyNotify destroy) { const uint8_t msg[] = { UICC_APPL_CMD_REQ, UICC_APPL_FILE_INFO, /* Service type */ app_id, UICC_SESSION_ID_NOT_USED, 0, 0, /* Filler */ 1, /* Number of subblocks */ ISI_16BIT(UICC_SB_APPL_PATH), ISI_16BIT(16), /* Subblock length */ ISI_16BIT(fileid), uicc_get_sfi(fileid), /* Elementary file short file id */ 0, /* Filler */ df_len, 0, /* Filler */ ISI_16BIT(mf_path), ISI_16BIT(df1_path), ISI_16BIT(df2_path), }; return g_isi_client_send(client, msg, sizeof(msg), notify, data, destroy); } static void uicc_read_file_info(struct ofono_sim *sim, int fileid, const unsigned char *path, unsigned int path_len, ofono_sim_file_info_cb_t cb, void *data) { struct uicc_sim_data *sd = ofono_sim_get_data(sim); struct uicc_file_info_cb_data *cbd; /* Prepare for static file info used for access rights */ int i; int N = sizeof(static_file_info) / sizeof(static_file_info[0]); int mf_path = 0; int df1_path = 0; int df2_path = 0; uint8_t df_len = 0; cbd = g_try_new0(struct uicc_file_info_cb_data, 1); if (!cbd) goto error; cbd->cb = cb; cbd->data = data; cbd->sim = sim; cbd->user = NULL; DBG("File info for ID=%04X app id %d", fileid, sd->app_id); for (i = 0; i < N; i++) { if (fileid == static_file_info[i].fileid) { cbd->user = (void *) &static_file_info[i]; break; } } DBG("File info for ID=%04X: %p", fileid, cbd->user); if (!uicc_get_fileid_path(sd, &mf_path, &df1_path, &df2_path, &df_len, fileid)) goto error; if (send_uicc_read_file_info(sd->client, sd->app_id, fileid, df_len, mf_path, df1_path, df2_path, uicc_file_info_resp_cb, cbd, g_free)) return; error: CALLBACK_WITH_FAILURE(cb, -1, -1, -1, NULL, 0, data); g_free(cbd); } static void uicc_read_file_transp_resp_cb(const GIsiMessage *msg, void *opaque) { struct isi_cb_data *cbd = opaque; ofono_sim_read_cb_t cb = cbd->cb; GIsiSubBlockIter iter; uint32_t filelen = 0; uint8_t *filedata = NULL; uint8_t num_sb = 0; DBG(""); if (!check_resp(msg, UICC_APPL_CMD_RESP, UICC_APPL_READ_TRANSPARENT)) goto error; if (!g_isi_msg_data_get_byte(msg, 5, &num_sb)) goto error; for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { int sb_id = g_isi_sb_iter_get_id(&iter); DBG("Subblock %s", uicc_subblock_name(sb_id)); if (sb_id != UICC_SB_FILE_DATA) continue; if (!g_isi_sb_iter_get_dword(&iter, &filelen, 4)) goto error; if (!g_isi_sb_iter_get_struct(&iter, (void **) &filedata, filelen, 8)) goto error; DBG("Transparent EF read: 1st byte %02x, len %d", filedata[0], filelen); CALLBACK_WITH_SUCCESS(cb, filedata, filelen, cbd->data); return; } error: DBG("Error reading transparent EF"); CALLBACK_WITH_FAILURE(cb, NULL, 0, cbd->data); } static gboolean send_uicc_read_file_transparent(GIsiClient *client, uint8_t app_id, uint8_t client_id, int fileid, uint8_t df_len, int mf_path, int df1_path, int df2_path, GIsiNotifyFunc notify, void *data, GDestroyNotify destroy) { const uint8_t msg[] = { UICC_APPL_CMD_REQ, UICC_APPL_READ_TRANSPARENT, app_id, UICC_SESSION_ID_NOT_USED, 0, 0, /* Filler */ 3, /* Number of subblocks */ ISI_16BIT(UICC_SB_CLIENT), ISI_16BIT(8), /* Subblock length*/ 0, 0, 0, /* Filler */ client_id, ISI_16BIT(UICC_SB_TRANSPARENT), ISI_16BIT(8), /* Subblock length */ ISI_16BIT(0), /* File offset */ ISI_16BIT(0), /* Data amount (0=all) */ ISI_16BIT(UICC_SB_APPL_PATH), ISI_16BIT(16), /* Subblock length */ ISI_16BIT(fileid), uicc_get_sfi(fileid), /* Elementary file short file id */ 0, /* Filler */ df_len, 0, ISI_16BIT(mf_path), ISI_16BIT(df1_path), ISI_16BIT(df2_path), }; return g_isi_client_send(client, msg, sizeof(msg), notify, data, destroy); } static void uicc_read_file_transparent(struct ofono_sim *sim, int fileid, int start, int length, const unsigned char *path, unsigned int path_len, ofono_sim_read_cb_t cb, void *data) { struct uicc_sim_data *sd = ofono_sim_get_data(sim); struct isi_cb_data *cbd = isi_cb_data_new(sim, cb, data); int mf_path = 0; int df1_path = 0; int df2_path = 0; uint8_t df_len = 0; if (!cbd || !sd) goto error; DBG("File ID=%04X, client %d, AID %d", fileid, sd->client_id, sd->app_id); if (!uicc_get_fileid_path(sd, &mf_path, &df1_path, &df2_path, &df_len, fileid)) goto error; if (send_uicc_read_file_transparent(sd->client, sd->app_id, sd->client_id, fileid, df_len, mf_path, df1_path, df2_path, uicc_read_file_transp_resp_cb, cbd, g_free)) return; error: DBG("Read file transparent failed"); CALLBACK_WITH_FAILURE(cb, NULL, 0, data); g_free(cbd); } static void read_file_linear_resp(const GIsiMessage *msg, void *opaque) { struct isi_cb_data *cbd = opaque; ofono_sim_read_cb_t cb = cbd->cb; GIsiSubBlockIter iter; uint8_t num_sb = 0; uint8_t *filedata = NULL; uint32_t filelen = 0; DBG(""); if (!check_resp(msg, UICC_APPL_CMD_RESP, UICC_APPL_READ_LINEAR_FIXED)) goto error; if (!g_isi_msg_data_get_byte(msg, 5, &num_sb)) goto error; for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { uint8_t sb_id = g_isi_sb_iter_get_id(&iter); DBG("Subblock %s", uicc_subblock_name(sb_id)); if (sb_id != UICC_SB_FILE_DATA) continue; if (!g_isi_sb_iter_get_dword(&iter, &filelen, 4)) goto error; if (!g_isi_sb_iter_get_struct(&iter, (void **) &filedata, filelen, 8)) goto error; DBG("Linear fixed EF read: 1st byte %02x, len %d", filedata[0], filelen); CALLBACK_WITH_SUCCESS(cb, filedata, filelen, cbd->data); return; } error: CALLBACK_WITH_FAILURE(cb, NULL, 0, cbd->data); } static gboolean send_uicc_read_file_linear(GIsiClient *client, uint8_t app_id, uint8_t client_id, int fileid, int record, int rec_length, unsigned char df_len, int mf_path, int df1_path, int df2_path, GIsiNotifyFunc notify, void *data, GDestroyNotify destroy) { const uint8_t msg[] = { UICC_APPL_CMD_REQ, UICC_APPL_READ_LINEAR_FIXED, app_id, UICC_SESSION_ID_NOT_USED, 0, 0, /* Filler */ 3, /* Number of subblocks */ ISI_16BIT(UICC_SB_CLIENT), ISI_16BIT(8), /*Subblock length */ 0, 0, 0, /* Filler */ client_id, ISI_16BIT(UICC_SB_LINEAR_FIXED), ISI_16BIT(8), /*Subblock length */ record, 0, /* Record offset */ rec_length & 0xff, /*Data amount (0=all)*/ 0, ISI_16BIT(UICC_SB_APPL_PATH), ISI_16BIT(16), /* Subblock length */ ISI_16BIT(fileid), uicc_get_sfi(fileid), /* Elementary file short file id */ 0, /* Filler */ df_len, 0, ISI_16BIT(mf_path), ISI_16BIT(df1_path), ISI_16BIT(df2_path), }; return g_isi_client_send(client, msg, sizeof(msg), notify, data, destroy); } static void uicc_read_file_linear(struct ofono_sim *sim, int fileid, int record, int rec_length, const unsigned char *path, unsigned int path_len, ofono_sim_read_cb_t cb, void *data) { struct uicc_sim_data *sd = ofono_sim_get_data(sim); struct isi_cb_data *cbd = isi_cb_data_new(sim, cb, data); int mf_path = 0; int df1_path = 0; int df2_path = 0; uint8_t df_len = 0; if (!sd || !cbd) goto error; DBG("File ID=%04X, record %d, client %d AID %d", fileid, record, sd->client_id, sd->app_id); if (!uicc_get_fileid_path(sd, &mf_path, &df1_path, &df2_path, &df_len, fileid)) goto error; if (send_uicc_read_file_linear(sd->client, sd->app_id, sd->client_id, fileid, record, rec_length, df_len, mf_path, df1_path, df2_path, read_file_linear_resp, cbd, g_free)) return; error: CALLBACK_WITH_FAILURE(cb, NULL, 0, data); g_free(cbd); } static void uicc_read_file_cyclic(struct ofono_sim *sim, int fileid, int record, int length, const unsigned char *path, unsigned int path_len, ofono_sim_read_cb_t cb, void *data) { DBG("Not implemented"); CALLBACK_WITH_FAILURE(cb, NULL, 0, data); } static void uicc_write_file_transparent(struct ofono_sim *sim, int fileid, int start, int length, const unsigned char *value, const unsigned char *path, unsigned int path_len, ofono_sim_write_cb_t cb, void *data) { DBG("Not implemented"); CALLBACK_WITH_FAILURE(cb, data); } static void uicc_write_file_linear(struct ofono_sim *sim, int fileid, int record, int length, const unsigned char *value, const unsigned char *path, unsigned int path_len, ofono_sim_write_cb_t cb, void *data) { DBG("Not implemented"); CALLBACK_WITH_FAILURE(cb, data); } static void uicc_write_file_cyclic(struct ofono_sim *sim, int fileid, int length, const unsigned char *value, const unsigned char *path, unsigned int path_len, ofono_sim_write_cb_t cb, void *data) { DBG("Not implemented"); CALLBACK_WITH_FAILURE(cb, data); } static gboolean decode_imsi(uint8_t *data, int len, char *imsi) { int i = 1; /* Skip first byte, the length field */ int j = 0; if (data == NULL || len == 0) return FALSE; if (data[0] != 8 || data[0] > len) return FALSE; /* Ignore low-order semi-octet of the first byte */ imsi[j] = ((data[i] & 0xF0) >> 4) + '0'; for (i++, j++; i - 1 < data[0] && j < MAX_IMSI_LENGTH; i++) { char nibble; imsi[j++] = (data[i] & 0x0F) + '0'; nibble = (data[i] & 0xF0) >> 4; if (nibble != 0x0F) imsi[j++] = nibble + '0'; } imsi[j] = '\0'; return TRUE; } static void uicc_read_imsi_resp(const GIsiMessage *msg, void *opaque) { struct isi_cb_data *cbd = opaque; ofono_sim_imsi_cb_t cb = cbd->cb; GIsiSubBlockIter iter; uint32_t filelen = 0; uint8_t *filedata = NULL; uint8_t num_sb = 0; char imsi[MAX_IMSI_LENGTH + 1] = { 0 }; DBG(""); if (!check_resp(msg, UICC_APPL_CMD_RESP, UICC_APPL_READ_TRANSPARENT)) goto error; if (!g_isi_msg_data_get_byte(msg, 5, &num_sb)) goto error; for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { int sb_id = g_isi_sb_iter_get_id(&iter); DBG("Subblock %s", uicc_subblock_name(sb_id)); if (sb_id != UICC_SB_FILE_DATA) continue; if (!g_isi_sb_iter_get_dword(&iter, &filelen, 4)) goto error; if (!g_isi_sb_iter_get_struct(&iter, (void **) &filedata, filelen, 8)) goto error; DBG("Transparent EF read: 1st byte %02x, len %d", filedata[0], filelen); if (!decode_imsi(filedata, filelen, imsi)) goto error; DBG("IMSI %s", imsi); CALLBACK_WITH_SUCCESS(cb, imsi, cbd->data); return; } error: CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); } static void uicc_read_imsi(struct ofono_sim *sim, ofono_sim_imsi_cb_t cb, void *data) { struct uicc_sim_data *sd = ofono_sim_get_data(sim); struct isi_cb_data *cbd = isi_cb_data_new(sim, cb, data); int mf_path = 0; int df1_path = 0; int df2_path = 0; uint8_t df_len = 0; if (!cbd) goto error; DBG("Client %d, AID %d", sd->client_id, sd->app_id); if (!uicc_get_fileid_path(sd, &mf_path, &df1_path, &df2_path, &df_len, SIM_EFIMSI_FILEID)) goto error; if (send_uicc_read_file_transparent(sd->client, sd->app_id, sd->client_id, SIM_EFIMSI_FILEID, df_len, mf_path, df1_path, df2_path, uicc_read_imsi_resp, cbd, g_free)) return; error: CALLBACK_WITH_FAILURE(cb, NULL, data); g_free(cbd); } static void uicc_query_passwd_state_resp(const GIsiMessage *msg, void *opaque) { struct isi_cb_data *cbd = opaque; ofono_sim_passwd_cb_t cb = cbd->cb; uint8_t type; uint8_t cause; DBG(""); if (g_isi_msg_error(msg) < 0) { DBG("Error: %s", g_isi_msg_strerror(msg)); goto error; } if (g_isi_msg_id(msg) != UICC_PIN_RESP) { DBG("Unexpected msg: %s", sim_message_id_name(g_isi_msg_id(msg))); goto error; } if (!g_isi_msg_data_get_byte(msg, 0, &type) || type != UICC_PIN_PROMPT_VERIFY) { DBG("Unexpected service: 0x%02X (0x%02X)", type, UICC_PIN_PROMPT_VERIFY); goto error; } if (!g_isi_msg_data_get_byte(msg, 1, &cause)) goto error; DBG("Status: %d %s", cause, uicc_status_name(cause)); if (cause == UICC_STATUS_PIN_DISABLED) { CALLBACK_WITH_SUCCESS(cb, OFONO_SIM_PASSWORD_NONE, cbd->data); return; } DBG("Request failed or not implemented: %s", uicc_status_name(cause)); error: CALLBACK_WITH_FAILURE(cb, -1, cbd->data); } static void uicc_query_passwd_state(struct ofono_sim *sim, ofono_sim_passwd_cb_t cb, void *data) { struct uicc_sim_data *sd = ofono_sim_get_data(sim); struct isi_cb_data *cbd = isi_cb_data_new(sim, cb, data); const uint8_t req[] = { UICC_PIN_REQ, UICC_PIN_PROMPT_VERIFY, sd->app_id, 0, 0, 0, /* Filler */ 1, /* Number of subblocks */ ISI_16BIT(UICC_SB_PIN_REF), ISI_16BIT(8), /*Sub block length*/ sd->pin1_id, /* Pin ID */ 0, 0, 0, /* Filler */ }; DBG(""); if (g_isi_client_send(sd->client, req, sizeof(req), uicc_query_passwd_state_resp, cbd, g_free)) return; CALLBACK_WITH_FAILURE(cb, -1, data); g_free(cbd); } static void uicc_send_passwd(struct ofono_sim *sim, const char *passwd, ofono_sim_lock_unlock_cb_t cb, void *data) { DBG("Not implemented"); CALLBACK_WITH_FAILURE(cb, data); } static void uicc_query_pin_retries_resp(const GIsiMessage *msg, void *opaque) { struct isi_cb_data *cbd = opaque; ofono_sim_pin_retries_cb_t cb = cbd->cb; int retries[OFONO_SIM_PASSWORD_INVALID]; GIsiSubBlockIter iter; uint8_t num_sb = 0; uint8_t pins = 0; uint8_t pina = 0; uint8_t puka = 0; DBG(""); if (!check_resp(msg, UICC_PIN_RESP, UICC_PIN_INFO)) goto error; if (!g_isi_msg_data_get_byte(msg, 5, &num_sb)) goto error; DBG("Subblock count %d", num_sb); for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { uint8_t sb_id = g_isi_sb_iter_get_id(&iter); DBG("Sub-block %s", uicc_subblock_name(sb_id)); if (sb_id != UICC_SB_PIN_INFO) continue; if (!g_isi_sb_iter_get_byte(&iter, &pins, 4)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &pina, 5)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &puka, 6)) goto error; DBG("PIN status %X PIN Attrib %d PUK attrib %d", pins, pina, puka); retries[OFONO_SIM_PASSWORD_SIM_PIN] = pina; retries[OFONO_SIM_PASSWORD_SIM_PUK] = puka; CALLBACK_WITH_SUCCESS(cb, retries, cbd->data); return; } error: CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); } static void uicc_query_pin_retries(struct ofono_sim *sim, ofono_sim_pin_retries_cb_t cb, void *data) { struct uicc_sim_data *sd = ofono_sim_get_data(sim); struct isi_cb_data *cbd = isi_cb_data_new(sim, cb, data); const uint8_t req[] = { UICC_PIN_REQ, UICC_PIN_INFO, sd->app_id, 0, 0, 0, /* Filler */ 1, /* Number of subblocks */ ISI_16BIT(UICC_SB_PIN_REF), ISI_16BIT(8), /* Subblock length */ sd->pin1_id, /* Pin ID */ 0, 0, 0, /* Filler */ }; DBG(""); if (g_isi_client_send(sd->client, req, sizeof(req), uicc_query_pin_retries_resp, cbd, g_free)) return; CALLBACK_WITH_FAILURE(cb, NULL, data); g_free(cbd); } static void uicc_reset_passwd(struct ofono_sim *sim, const char *puk, const char *passwd, ofono_sim_lock_unlock_cb_t cb, void *data) { DBG("Not implemented"); CALLBACK_WITH_FAILURE(cb, data); } static void uicc_change_passwd(struct ofono_sim *sim, enum ofono_sim_password_type passwd_type, const char *old, const char *new, ofono_sim_lock_unlock_cb_t cb, void *data) { DBG("Not implemented"); CALLBACK_WITH_FAILURE(cb, data); } static void uicc_lock(struct ofono_sim *sim, enum ofono_sim_password_type type, int enable, const char *passwd, ofono_sim_lock_unlock_cb_t cb, void *data) { DBG("Not implemented"); CALLBACK_WITH_FAILURE(cb, data); } static gboolean decode_fcp_pin_status(const GIsiSubBlockIter *iter, uint8_t read, uint8_t *pin1, uint8_t *pin2) { uint8_t do_len; uint8_t len; uint8_t tag; uint8_t id; uint8_t tag_pos; DBG("Decoding PIN status"); if (!g_isi_sb_iter_get_byte(iter, &do_len, read)) return FALSE; tag_pos = read + 1 + do_len; if (!g_isi_sb_iter_get_byte(iter, &tag, tag_pos)) return FALSE; while (tag == 0x83) { if (!g_isi_sb_iter_get_byte(iter, &len, tag_pos + 1)) return FALSE; if (!g_isi_sb_iter_get_byte(iter, &id, tag_pos + 2)) return FALSE; tag_pos += 2 + len; if (!g_isi_sb_iter_get_byte(iter, &tag, tag_pos)) return FALSE; DBG("PIN_len %d, PIN id %02x, PIN tag %02x", len, id, tag); if (id >= 0x01 && id <= 0x08) *pin1 = id; else if (id >= 0x81 && id <= 0x88) *pin2 = id; } return TRUE; } static gboolean decode_fci_sb(const GIsiSubBlockIter *iter, int app_type, uint8_t *pin1, uint8_t *pin2) { uint8_t fcp = 0; uint8_t fcp_len = 0; uint8_t read = 0; uint8_t item_len = 0; DBG("Decoding UICC_SB_FCI"); if (app_type != UICC_APPL_TYPE_UICC_USIM) return FALSE; if (!g_isi_sb_iter_get_byte(iter, &fcp, 8)) return FALSE; if (fcp != FCP_TEMPLATE) return FALSE; if (!g_isi_sb_iter_get_byte(iter, &fcp_len, 9)) return FALSE; for (read = 0; read < fcp_len; read += item_len + 2) { uint8_t id; if (!g_isi_sb_iter_get_byte(iter, &id, read + 10)) return FALSE; if (!g_isi_sb_iter_get_byte(iter, &item_len, read + 11)) return FALSE; if (id != FCP_PIN_STATUS) continue; if (!decode_fcp_pin_status(iter, read + 13, pin1, pin2)) return FALSE; } return TRUE; } static gboolean decode_chv_sb(const GIsiSubBlockIter *iter, int app_type, uint8_t *pin1, uint8_t *pin2) { uint8_t chv_id = 0; uint8_t pin_id = 0; DBG("Decoding UICC_SB_CHV"); if (app_type != UICC_APPL_TYPE_ICC_SIM) return FALSE; if (!g_isi_sb_iter_get_byte(iter, &chv_id, 4)) return FALSE; if (!g_isi_sb_iter_get_byte(iter, &pin_id, 5)) return FALSE; switch (chv_id) { case 1: *pin1 = pin_id; break; case 2: *pin2 = pin_id; break; default: return FALSE; } DBG("CHV=%d, pin_id=%2x, PIN1 %02x, PIN2 %02x", chv_id, pin_id, *pin1, *pin2); return TRUE; } static void uicc_application_activate_resp(const GIsiMessage *msg, void *opaque) { struct ofono_sim *sim = opaque; struct uicc_sim_data *sd = ofono_sim_get_data(sim); GIsiSubBlockIter iter; uint8_t cause, num_sb; DBG(""); if (g_isi_msg_error(msg) < 0) { DBG("Error: %s", g_isi_msg_strerror(msg)); return; } if (g_isi_msg_id(msg) != UICC_APPLICATION_RESP) { DBG("Unexpected msg: %s", sim_message_id_name(g_isi_msg_id(msg))); return; } if (!g_isi_msg_data_get_byte(msg, 1, &cause)) return; if (cause != UICC_STATUS_OK && cause != UICC_STATUS_APPL_ACTIVE) { DBG("TODO: handle application activation"); return; } if (!sd->uicc_app_started) { sd->app_id = sd->trying_app_id; sd->app_type = sd->trying_app_type; sd->uicc_app_started = TRUE; DBG("UICC application activated"); ofono_sim_inserted_notify(sim, TRUE); ofono_sim_register(sim); g_hash_table_remove_all(sd->app_table); } if (!g_isi_msg_data_get_byte(msg, 5, &num_sb)) return; for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { uint8_t sb_id = g_isi_sb_iter_get_id(&iter); DBG("Subblock %s", uicc_subblock_name(sb_id)); switch (sb_id) { case UICC_SB_CLIENT: if (!g_isi_sb_iter_get_byte(&iter, &sd->client_id, 7)) return; DBG("Client id %d", sd->client_id); break; case UICC_SB_FCI: if (!decode_fci_sb(&iter, sd->app_type, &sd->pin1_id, &sd->pin2_id)) return; DBG("PIN1 %02x, PIN2 %02x", sd->pin1_id, sd->pin2_id); break; case UICC_SB_CHV: if (!decode_chv_sb(&iter, sd->app_type, &sd->pin1_id, &sd->pin2_id)) return; DBG("PIN1 %02x, PIN2 %02x", sd->pin1_id, sd->pin2_id); break; default: DBG("Skipping sub-block: %s (%zu bytes)", uicc_subblock_name(g_isi_sb_iter_get_id(&iter)), g_isi_sb_iter_get_len(&iter)); break; } } } static gboolean send_application_activate_req(GIsiClient *client, uint8_t app_type, uint8_t app_id, GIsiNotifyFunc notify, void *data, GDestroyNotify destroy) { const uint8_t msg[] = { UICC_APPLICATION_REQ, UICC_APPL_HOST_ACTIVATE, 2, /* Number of subblocks */ ISI_16BIT(UICC_SB_APPLICATION), ISI_16BIT(8), /* Subblock length */ 0, 0, /* Filler */ app_type, app_id, ISI_16BIT(UICC_SB_APPL_INFO), ISI_16BIT(8), /* Subblock length */ 0, 0, 0, /* Filler */ /* * Next field indicates whether the application * initialization procedure will follow the activation * or not */ UICC_APPL_START_UP_INIT_PROC, }; DBG("App type %d, AID %d", app_type, app_id); return g_isi_client_send(client, msg, sizeof(msg), notify, data, destroy); } static void uicc_application_list_resp(const GIsiMessage *msg, void *data) { struct ofono_sim *sim = data; struct uicc_sim_data *sd = ofono_sim_get_data(sim); GIsiSubBlockIter iter; uint8_t num_sb; struct uicc_sim_application *sim_app; /* Throw away old app table */ g_hash_table_remove_all(sd->app_table); if (!check_resp(msg, UICC_APPLICATION_RESP, UICC_APPL_LIST)) goto error; if (!g_isi_msg_data_get_byte(msg, 5, &num_sb)) goto error; /* Iterate through the application list */ for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { uint8_t app_type; uint8_t app_id; uint8_t app_status; uint8_t app_len; if (g_isi_sb_iter_get_id(&iter) != UICC_SB_APPL_DATA_OBJECT) continue; if (!g_isi_sb_iter_get_byte(&iter, &app_type, 6)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &app_id, 7)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &app_status, 8)) goto error; if (!g_isi_sb_iter_get_byte(&iter, &app_len, 9)) goto error; if (app_type != UICC_APPL_TYPE_ICC_SIM && app_type != UICC_APPL_TYPE_UICC_USIM) continue; sim_app = g_try_new0(struct uicc_sim_application, 1); if (!sim_app) { DBG("out of memory!"); goto error; } sim_app->type = app_type; sim_app->id = app_id; sim_app->status = app_status; sim_app->length = app_len; sim_app->sim = sd; g_hash_table_replace(sd->app_table, &sim_app->id, sim_app); } if (!sd->uicc_app_started) { GHashTableIter app_iter; struct uicc_sim_application *app; gpointer key; gpointer value; g_hash_table_iter_init(&app_iter, sd->app_table); if (!g_hash_table_iter_next(&app_iter, &key, &value)) return; app = value; sd->trying_app_type = app->type; sd->trying_app_id = app->id; g_hash_table_remove(sd->app_table, &app->id); if (!send_application_activate_req(sd->client, app->type, app->id, uicc_application_activate_resp, data, NULL)) { DBG("Failed to activate: 0x%02X (type=0x%02X)", app->id, app->type); return; } } return; error: DBG("Decoding application list failed"); g_isi_client_destroy(sd->client); sd->client = NULL; ofono_sim_remove(sim); } static void uicc_card_status_resp(const GIsiMessage *msg, void *data) { struct ofono_sim *sim = data; struct uicc_sim_data *sd = ofono_sim_get_data(sim); GIsiSubBlockIter iter; uint8_t card_status = 0; uint8_t num_sb = 0; DBG(""); if (!sd->server_running) return; if (!check_resp(msg, UICC_CARD_RESP, UICC_CARD_STATUS_GET)) goto error; if (!g_isi_msg_data_get_byte(msg, 1, &card_status)) goto error; if (!g_isi_msg_data_get_byte(msg, 5, &num_sb)) goto error; DBG("Subblock count %d", num_sb); for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { if (g_isi_sb_iter_get_id(&iter) != UICC_SB_CARD_STATUS) continue; if (!g_isi_sb_iter_get_byte(&iter, &card_status, 7)) goto error; DBG("card_status = 0x%X", card_status); /* Check if card is ready */ if (card_status == 0x21) { const uint8_t req[] = { UICC_APPLICATION_REQ, UICC_APPL_LIST, 0, /* Number of subblocks */ }; DBG("card is ready"); ofono_sim_inserted_notify(sim, TRUE); if (g_isi_client_send(sd->client, req, sizeof(req), uicc_application_list_resp, data, NULL)) return; DBG("Failed to query application list"); goto error; } else { DBG("card not ready"); ofono_sim_inserted_notify(sim, FALSE); return; } } error: g_isi_client_destroy(sd->client); sd->client = NULL; ofono_sim_remove(sim); } static void uicc_card_status_req(struct ofono_sim *sim, struct uicc_sim_data *sd) { const uint8_t req[] = { UICC_CARD_REQ, UICC_CARD_STATUS_GET, 0, }; DBG(""); if (g_isi_client_send(sd->client, req, sizeof(req), uicc_card_status_resp, sim, NULL)) return; g_isi_client_destroy(sd->client); sd->client = NULL; ofono_sim_remove(sim); } static void uicc_card_ind_cb(const GIsiMessage *msg, void *data) { struct ofono_sim *sim = data; struct uicc_sim_data *sd = ofono_sim_get_data(sim); DBG(""); if (g_isi_msg_id(msg) != UICC_CARD_IND) return; /* We're not interested in card indications if server isn't running */ if (!sd->server_running) return; /* Request card status */ uicc_card_status_req(sim, sd); } static void uicc_status_resp(const GIsiMessage *msg, void *data) { struct ofono_sim *sim = data; struct uicc_sim_data *sd = ofono_sim_get_data(sim); uint8_t status = 0, server_status = 0; gboolean server_running = FALSE; if (!check_resp(msg, UICC_RESP, UICC_STATUS_GET)) goto error; if (g_isi_msg_error(msg) < 0) goto error; if (!g_isi_msg_data_get_byte(msg, 1, &status) || !g_isi_msg_data_get_byte(msg, 3, &server_status)) goto error; DBG("status=0x%X, server_status=0x%X", status, server_status); if (status == UICC_STATUS_OK && server_status == UICC_STATUS_START_UP_COMPLETED) { DBG("server is up!"); server_running = TRUE; } if (!server_running) { sd->server_running = FALSE; /* TODO: Remove SIM etc... */ return; } if (sd->server_running && server_running) { DBG("Server status didn't change..."); return; } /* Server is running */ sd->server_running = TRUE; /* Request card status */ uicc_card_status_req(sim, sd); return; error: g_isi_client_destroy(sd->client); sd->client = NULL; ofono_sim_remove(sim); } static void uicc_ind_cb(const GIsiMessage *msg, void *data) { struct ofono_sim *sim = data; struct uicc_sim_data *sd = ofono_sim_get_data(sim); const uint8_t req[] = { UICC_REQ, UICC_STATUS_GET, 0 }; int msg_id = g_isi_msg_id(msg); DBG("%s", uicc_message_id_name(msg_id)); if (msg_id != UICC_IND) return; /* Request status */ if (g_isi_client_send(sd->client, req, sizeof(req), uicc_status_resp, data, NULL)) return; DBG("status request failed!"); g_isi_client_destroy(sd->client); sd->client = NULL; ofono_sim_remove(sim); } static void uicc_reachable_cb(const GIsiMessage *msg, void *data) { struct ofono_sim *sim = data; struct uicc_sim_data *sd = ofono_sim_get_data(sim); const uint8_t req[] = { UICC_REQ, UICC_STATUS_GET, 0, /* Number of Sub Blocks (only from version 4.0) */ }; ISI_RESOURCE_DBG(msg); if (g_isi_msg_error(msg) < 0) goto error; sd->version.major = g_isi_msg_version_major(msg); sd->version.minor = g_isi_msg_version_minor(msg); /* UICC server is reachable: request indications */ g_isi_client_ind_subscribe(sd->client, UICC_IND, uicc_ind_cb, sim); g_isi_client_ind_subscribe(sd->client, UICC_CARD_IND, uicc_card_ind_cb, sim); /* Update status */ if (g_isi_client_send(sd->client, req, sizeof(req) - ((sd->version.major < 4) ? 1 : 0), uicc_status_resp, data, NULL)) return; error: g_isi_client_destroy(sd->client); sd->client = NULL; ofono_sim_remove(sim); } static void sim_app_destroy(gpointer p) { struct uicc_sim_application *app = p; if (!app) return; g_free(app); } static int uicc_sim_probe(struct ofono_sim *sim, unsigned int vendor, void *user) { GIsiModem *modem = user; struct uicc_sim_data *sd; sd = g_try_new0(struct uicc_sim_data, 1); if (sd == NULL) return -ENOMEM; /* Create hash table for the UICC applications */ sd->app_table = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, sim_app_destroy); if (sd->app_table == NULL) { g_free(sd); return -ENOMEM; } sd->client = g_isi_client_create(modem, PN_UICC); if (sd->client == NULL) { g_hash_table_destroy(sd->app_table); g_free(sd); return -ENOMEM; } g_hash_table_insert(g_modems, g_isi_client_modem(sd->client), sim); sd->server_running = FALSE; sd->uicc_app_started = FALSE; sd->pin_state_received = FALSE; sd->passwd_required = TRUE; ofono_sim_set_data(sim, sd); g_isi_client_verify(sd->client, uicc_reachable_cb, sim, NULL); return 0; } static void uicc_sim_remove(struct ofono_sim *sim) { struct uicc_sim_data *data = ofono_sim_get_data(sim); ofono_sim_set_data(sim, NULL); if (data == NULL) return; g_hash_table_remove(g_modems, g_isi_client_modem(data->client)); g_hash_table_destroy(data->app_table); g_isi_client_destroy(data->client); g_free(data); } static const struct ofono_sim_driver driver = { .name = "wgmodem2.5", .probe = uicc_sim_probe, .remove = uicc_sim_remove, .read_file_info = uicc_read_file_info, .read_file_transparent = uicc_read_file_transparent, .read_file_linear = uicc_read_file_linear, .read_file_cyclic = uicc_read_file_cyclic, .write_file_transparent = uicc_write_file_transparent, .write_file_linear = uicc_write_file_linear, .write_file_cyclic = uicc_write_file_cyclic, .read_imsi = uicc_read_imsi, .query_passwd_state = uicc_query_passwd_state, .send_passwd = uicc_send_passwd, .query_pin_retries = uicc_query_pin_retries, .reset_passwd = uicc_reset_passwd, .change_passwd = uicc_change_passwd, .lock = uicc_lock, }; void isi_uicc_init(void) { g_modems = g_hash_table_new(g_direct_hash, g_direct_equal); ofono_sim_driver_register(&driver); } void isi_uicc_exit(void) { g_hash_table_destroy(g_modems); ofono_sim_driver_unregister(&driver); } gboolean isi_uicc_properties(GIsiModem *modem, int *app_id, int *app_type, int *client_id) { struct ofono_sim *sim; struct uicc_sim_data *sd; sim = g_hash_table_lookup(g_modems, modem); if (sim == NULL) return FALSE; sd = ofono_sim_get_data(sim); if (sd == NULL) return FALSE; if (app_id != NULL) *app_id = sd->app_id; if (app_type != NULL) *app_type = sd->app_type; if (client_id != NULL) *client_id = sd->client_id; return TRUE; }