/* BTSAP socket interface of layer2/3 stack */ /* (C) 2010 by Holger Hans Peter Freyther * (C) 2010 by Harald Welte * (C) 2010 by Andreas Eversberg * (C) 2011 by Nico Golde * * 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 #include #include #include #include #include #include #include #define _GNU_SOURCE #include #include #include #include #define GSM_SAP_LENGTH 300 #define GSM_SAP_HEADROOM 32 static void sap_connect(struct osmocom_ms *ms); static const struct value_string sap_param_names[] = { {SAP_MAX_MSG_SIZE, "MaxMsgSize"}, {SAP_CONNECTION_STATUS, "ConnectionStatus"}, {SAP_RESULT_CODE, "ResultCode"}, {SAP_DISCONNECTION_TYPE, "DisconnectionType"}, {SAP_COMMAND_APDU, "CommandAPDU"}, {SAP_COMMAND_APDU_7816, "CommandAPDU7816"}, {SAP_RESPONSE_APDU, "ResponseAPDU"}, {SAP_ATR, "ATR"}, {SAP_CARD_READER_STATUS, "CardReaderStatus"}, {SAP_STATUS_CHANGE, "StatusChange"}, {SAP_TRANSPORT_PROTOCOL, "TransportProtocol"} }; static const struct value_string sap_msg_names[] = { {SAP_CONNECT_REQ, "CONNECT_REQ"}, {SAP_CONNECT_RESP, "CONNECT_RESP"}, {SAP_DISCONNECT_REQ, "DISCONNECT_REQ"}, {SAP_DISCONNECT_RESP, "DISCONNECT_RESP"}, {SAP_DISCONNECT_IND, "DISCONNECT_IND"}, {SAP_TRANSFER_APDU_REQ, "TRANSFER_APDU_REQ"}, {SAP_TRANSFER_APDU_RESP, "TRANSFER_APDU_RESP"}, {SAP_TRANSFER_ATR_REQ, "TRANSFER_ATR_REQ"}, {SAP_TRANSFER_ATR_RESP, "TRANSFER_ATR_RESP"}, {SAP_POWER_SIM_OFF_REQ, "POWER_SIM_OFF_REQ"}, {SAP_POWER_SIM_OFF_RESP, "POWER_SIM_OFF_RESP"}, {SAP_POWER_SIM_ON_REQ, "POWER_SIM_ON_REQ"}, {SAP_POWER_SIM_ON_RESP, "POWER_SIM_ON_RESP"}, {SAP_RESET_SIM_REQ, "RESET_SIM_REQ"}, {SAP_RESET_SIM_RESP, "RESET_SIM_RESP"}, {SAP_TRANSFER_CARD_READER_STATUS_REQ, "TRANSFER_CARD_READER_STATUS_REQ"}, {SAP_TRANSFER_CARD_READER_STATUS_RESP, "TRANSFER_CARD_READER_STATUS_RESP"}, {SAP_STATUS_IND, "STATUS_IND"}, {SAP_ERROR_RESP, "ERROR_RESP"}, {SAP_SET_TRANSPORT_PROTOCOL_REQ, "SET_TRANSPORT_PROTOCOL_REQ"}, {SAP_SET_TRANSPORT_PROTOCOL_RESP, "SET_TRANSPORT_PROTOCOL_RESP"} }; /* BTSAP table 5.18 */ static const struct value_string sap_result_names[] = { {0, "OK, request processed correctly"}, {1, "Error, no reason defined"}, {2, "Error, card not accessible"}, {3, "Error, card (already) powered off"}, {4, "Error, card removed"}, {5, "Error, card already powered on"}, {6, "Error, data not available"}, {7, "Error, not supported"} }; static const struct value_string sap_status_change_names[] = { {0, "Unknown Error"}, {1, "Card reset"}, {2, "Card not accessible"}, {3, "Card removed"}, {4, "Card inserted"}, {5, "Card recovered"}, }; static const struct value_string sap_status_names[] = { {0, "OK, Server can fulfill requirements"}, {1, "Error, Server unable to establish connection"}, {2, "Error, Server does not support maximum message size"}, {3, "Error, maximum message size by Client is too small"}, {4, "OK, ongoing call"} }; static struct msgb *sap_create_msg(uint8_t id, uint8_t num_params, struct sap_param *params) { struct msgb *msg; uint8_t *msgp; uint8_t i, plen, padding = 0; msg = msgb_alloc(GSM_SAP_LENGTH, "osmosap"); if (!msg) { LOGP(DSAP, LOGL_ERROR, "Failed to allocate msg.\n"); return NULL; } /* BTSAP 5.1 */ msgb_put_u8(msg, id); msgb_put_u8(msg, num_params); msgb_put_u16(msg, 0); for(i=0; isap_entity.sap_state == SAP_NOT_CONNECTED && !ms->sap_entity.sap_state == SAP_CONNECTION_UNDER_NEGOTIATION) sap_connect(ms); if (ms->sap_wq.bfd.fd <= 0) return -EINVAL; if (osmo_wqueue_enqueue(&ms->sap_wq, msg) != 0) { LOGP(DSAP, LOGL_ERROR, "Failed to enqueue msg.\n"); msgb_free(msg); return -1; } return 0; } static int sap_parse_result(struct sap_param *param) { if(param->id != SAP_RESULT_CODE){ LOGP(DSAP, LOGL_INFO, "> Parameter id: %u no valid result type\n", param->id); return -1; } else { LOGP(DSAP, LOGL_INFO, "> RESULT CODE: %s\n", get_value_string(sap_result_names, param->value[0])); } if(param->value[0] > sizeof(sap_result_names)/sizeof(struct value_string)){ return -1; } return 0; } static uint8_t *sap_get_param(uint8_t *data, struct sap_param *param) { uint8_t *dptr = data; uint8_t padlen; param->id = *dptr++; /* skip reserved byte */ dptr++; param->len = *dptr << 8; dptr++; param->len |= *dptr++; param->value = talloc_zero_size(NULL, param->len); memcpy(param->value, dptr, param->len); /* skip parameter and padding and return pointer to next parameter */ dptr += param->len; if(param->len % 4){ padlen = (4 - param->len % 4); } else { padlen = 0; } dptr += padlen; return dptr; } static void sap_msg_free(struct sap_msg *msg) { uint8_t i; for(i=0; inum_params; i++){ talloc_free(msg->params[i].value); talloc_free(msg->params); } talloc_free(msg); } static struct sap_msg *sap_parse_msg(uint8_t *data) { struct sap_msg *msg = talloc_zero(NULL, struct sap_msg); uint8_t *ptr = data; uint8_t i; if(!msg){ return NULL; } msg->id = *ptr++; LOGP(DSAP, LOGL_INFO, "> %s \n", get_value_string(sap_msg_names, msg->id)); msg->num_params = *ptr++; /* skip two reserved null bytes, BTSAP 5.1 */ ptr += 2; msg->params = talloc_zero_size(NULL, sizeof(struct sap_param) * msg->num_params); for(i=0; inum_params; i++){ ptr = sap_get_param(ptr, &msg->params[i]); LOGP(DSAP, LOGL_INFO, "> %s %s\n", get_value_string(sap_param_names, msg->params[i].id), osmo_hexdump(msg->params[i].value, msg->params[i].len)); } return msg; } static void sap_apdu_resp(struct osmocom_ms *ms, uint8_t *data, uint16_t len) { struct msgb *msg; uint8_t *apdu; msg = msgb_alloc(GSM_SAP_LENGTH, "osmosap"); if(!msg){ LOGP(DSAP, LOGL_ERROR, "Failed to allocate memory.\n"); return; } apdu = msgb_put(msg, len); memcpy(apdu, data, len); LOGP(DSAP, LOGL_DEBUG, "Forwarding APDU to SIM handler.\n"); sim_apdu_resp(ms, msg); } static int sap_adapt_msg_size(struct osmocom_ms *ms, struct sap_param *param) { uint16_t size; size = (param->value[0] << 8) | param->value[1]; if(size != ms->sap_entity.max_msg_size && size > 0){ LOGP(DSAP, LOGL_NOTICE, "Server can not handle max_msg_size, adapting.\n"); ms->sap_entity.max_msg_size = size; return -1; } return 0; } static void sap_atr(struct osmocom_ms *ms) { struct msgb *msg; if(ms->sap_entity.sap_state != SAP_IDLE){ LOGP(DSAP, LOGL_ERROR, "Attempting to send ATR request while not being idle.\n"); return; } msg = sap_create_msg(SAP_TRANSFER_ATR_REQ, 0, NULL); if(!msg) return; osmosap_send(ms, msg); ms->sap_entity.sap_state = SAP_PROCESSING_ATR_REQUEST; } static void sap_parse_resp(struct osmocom_ms *ms, uint8_t *data, uint16_t len) { struct sap_msg *msg = NULL; if(len > ms->sap_entity.max_msg_size){ LOGP(DSAP, LOGL_ERROR, "Read more data than allowed by max_msg_size, ignoring.\n"); return; } msg = sap_parse_msg(data); if(!msg){ sap_msg_free(msg); return; } switch(msg->id){ case SAP_CONNECT_RESP: LOGP(DSAP, LOGL_INFO, "Status: %s\n", get_value_string(sap_status_names, msg->params[0].value[0])); if(msg->params[0].value[0] == 0){ ms->sap_entity.sap_state = SAP_IDLE; } if(msg->num_params == 2 && msg->params[1].len == 2){ if(sap_adapt_msg_size(ms, &msg->params[1]) < 0) { ms->sap_entity.sap_state = SAP_NOT_CONNECTED; } else { sap_atr(ms); } } break; case SAP_DISCONNECT_RESP: ms->sap_entity.sap_state = SAP_NOT_CONNECTED; break; case SAP_STATUS_IND: LOGP(DSAP, LOGL_INFO, "New card state: %s\n", get_value_string(sap_status_change_names, msg->params[0].value[0])); if(msg->params[0].value[0] != 1){ /* TODO: handle case in which the card is not ready yet */ } break; case SAP_TRANSFER_ATR_RESP: if(ms->sap_entity.sap_state != SAP_PROCESSING_ATR_REQUEST){ LOGP(DSAP, LOGL_ERROR, "got ATR resp in state: %u\n", ms->sap_entity.sap_state); return; } if(msg->num_params >= 2){ LOGP(DSAP, LOGL_INFO, "ATR: %s\n", osmo_hexdump(msg->params[1].value, msg->params[1].len)); } ms->sap_entity.sap_state = SAP_IDLE; break; case SAP_TRANSFER_APDU_RESP: if(ms->sap_entity.sap_state != SAP_PROCESSING_APDU_REQUEST){ LOGP(DSAP, LOGL_ERROR, "got APDU resp in state: %u\n", ms->sap_entity.sap_state); return; } if(msg->num_params != 2){ LOGP(DSAP, LOGL_ERROR, "wrong number of parameters %u in APDU response\n", msg->num_params); return; } ms->sap_entity.sap_state = SAP_IDLE; if(sap_parse_result(&msg->params[0]) == 0){ /* back apdu resp to layer23 */ sap_apdu_resp(ms, msg->params[1].value, msg->params[1].len); LOGP(DSAP, LOGL_INFO, "sap_apdu_resp called, sending data back to layer23\n"); } break; case SAP_ERROR_RESP: if(ms->sap_entity.sap_state == SAP_CONNECTION_UNDER_NEGOTIATION){ ms->sap_entity.sap_state = SAP_NOT_CONNECTED; } else { ms->sap_entity.sap_state = SAP_IDLE; } break; default: LOGP(DSAP, LOGL_ERROR, "got unknown or not implemented SAP msgid: %u\n", msg->id); break; } } static int sap_read(struct osmo_fd *fd) { struct msgb *msg = NULL; struct osmocom_ms *ms = (struct osmocom_ms *) fd->data; uint8_t *sap_buffer; ssize_t rc; sap_buffer = talloc_zero_size(NULL, ms->sap_entity.max_msg_size); if(!sap_buffer){ fprintf(stderr, "Failed to allocate memory\n"); return -ENOMEM; } rc = read(fd->fd, sap_buffer, ms->sap_entity.max_msg_size - 1); if (rc < 0) { fprintf(stderr, "SAP socket failed\n"); msgb_free(msg); sap_close(ms); return rc; } if(rc == 0) { fprintf(stderr, "SAP socket closed by server\n"); msgb_free(msg); sap_close(ms); return -ECONNREFUSED; } sap_buffer[rc] = 0; LOGP(DSAP, LOGL_INFO, "Received %zd bytes: %s\n", rc, osmo_hexdump(sap_buffer, rc)); sap_parse_resp(ms, sap_buffer, rc); talloc_free(sap_buffer); if (ms->sap_entity.msg_handler){ ms->sap_entity.msg_handler(msg, ms); } return 0; } static int sap_write(struct osmo_fd *fd, struct msgb *msg) { ssize_t rc; if (fd->fd <= 0) return -EINVAL; LOGP(DSAP, LOGL_INFO, "< %s\n", osmo_hexdump(msg->data, msg->len)); rc = write(fd->fd, msg->data, msg->len); if (rc != msg->len) { LOGP(DSAP, LOGL_ERROR, "Failed to write data: rc: %zd\n", rc); return rc; } return 0; } static void sap_connect(struct osmocom_ms *ms) { uint8_t buffer[3]; struct msgb *msg; uint16_t size = ms->sap_entity.max_msg_size; struct sap_param params[1]; params[0].id = SAP_MAX_MSG_SIZE; params[0].len = 2; if(ms->sap_entity.sap_state != SAP_NOT_CONNECTED) { LOGP(DSAP, LOGL_ERROR, "Attempting to connect while there is an active connection.\n"); return; } buffer[0] = (size >> 8) & 0xFF; buffer[1] = size & 0xFF; buffer[2] = 0; params[0].value = buffer; msg = sap_create_msg(SAP_CONNECT_REQ, 1, params); if(!msg) return; osmosap_send(ms, msg); ms->sap_entity.sap_state = SAP_CONNECTION_UNDER_NEGOTIATION; } static void sap_disconnect(struct osmocom_ms *ms) { struct msgb *msg; if(ms->sap_entity.sap_state != SAP_NOT_CONNECTED && ms->sap_entity.sap_state != SAP_CONNECTION_UNDER_NEGOTIATION){ LOGP(DSAP, LOGL_ERROR, "Attempting to disconnect while no active connection.\n"); return; } msg = sap_create_msg(SAP_DISCONNECT_REQ, 0, NULL); if(!msg) return; osmosap_send(ms, msg); ms->sap_entity.sap_state = SAP_NOT_CONNECTED; } static void sap_apdu(struct osmocom_ms *ms, uint8_t *data, uint16_t len) { struct msgb *msg; struct sap_param params[1]; params[0].id = SAP_COMMAND_APDU; params[0].len = len; params[0].value = data; if(ms->sap_entity.sap_state != SAP_IDLE){ LOGP(DSAP, LOGL_ERROR, "Attempting to send APDU request while not being idle.\n"); return; } msg = sap_create_msg(SAP_TRANSFER_APDU_REQ, 1, params); if(!msg) return; osmosap_send(ms, msg); ms->sap_entity.sap_state = SAP_PROCESSING_APDU_REQUEST; } int sap_open(struct osmocom_ms *ms, const char *socket_path) { ssize_t rc; struct sockaddr_un local; struct gsm_settings *set = &ms->settings; ms->sap_wq.bfd.fd = socket(AF_UNIX, SOCK_STREAM, 0); if (ms->sap_wq.bfd.fd < 0) { fprintf(stderr, "Failed to create unix domain socket.\n"); return ms->sap_wq.bfd.fd; } local.sun_family = AF_UNIX; strncpy(local.sun_path, socket_path, sizeof(local.sun_path)); local.sun_path[sizeof(local.sun_path) - 1] = '\0'; rc = connect(ms->sap_wq.bfd.fd, (struct sockaddr *) &local, sizeof(local)); if (rc < 0) { fprintf(stderr, "Failed to connect to '%s'\n", local.sun_path); set->sap_socket_path[0] = 0; close(ms->sap_wq.bfd.fd); return rc; } osmo_wqueue_init(&ms->sap_wq, 100); ms->sap_wq.bfd.data = ms; ms->sap_wq.bfd.when = BSC_FD_READ; ms->sap_wq.read_cb = sap_read; ms->sap_wq.write_cb = sap_write; rc = osmo_fd_register(&ms->sap_wq.bfd); if (rc != 0) { fprintf(stderr, "Failed to register fd.\n"); return rc; } sap_connect(ms); return 0; } int sap_close(struct osmocom_ms *ms) { if (ms->sap_wq.bfd.fd <= 0) return -EINVAL; sap_disconnect(ms); close(ms->sap_wq.bfd.fd); ms->sap_wq.bfd.fd = -1; osmo_fd_unregister(&ms->sap_wq.bfd); osmo_wqueue_clear(&ms->sap_wq); return 0; } /* same signature as in L1CTL, so it can be called from sim.c */ int osmosap_send_apdu(struct osmocom_ms *ms, uint8_t *data, uint16_t length) { //LOGP(DSAP, LOGL_ERROR, "Received the following APDU from sim.c: %s\n" , // osmo_hexdump(data, length)); sap_apdu(ms, data, length); return 0; } /* register message handler for messages that are sent from L2->L3 */ int osmosap_register_handler(struct osmocom_ms *ms, osmosap_cb_t cb) { ms->sap_entity.msg_handler = cb; return 0; } int osmosap_sapsocket(struct osmocom_ms *ms, const char *path) { struct gsm_settings *set = &ms->settings; memset(set->sap_socket_path, 0, sizeof(set->sap_socket_path)); strncpy(set->sap_socket_path, path, sizeof(set->sap_socket_path) - 1); return 0; } /* init */ int osmosap_init(struct osmocom_ms *ms) { struct osmosap_entity *sap = &ms->sap_entity; int rc; sap->sap_state = SAP_NOT_CONNECTED; sap->max_msg_size = GSM_SAP_LENGTH; LOGP(DSAP, LOGL_INFO, "init SAP client\n"); if(ms->settings.sap_socket_path){ rc = sap_open(ms, ms->settings.sap_socket_path); if (rc < 0) { fprintf(stderr, "Failed during sap_open(), no SAP based SIM reader\n"); ms->sap_wq.bfd.fd = -1; return rc; } } return 0; }