/* BTSAP socket interface of layer2/3 stack */ /* (C) 2010 by Holger Hans Peter Freyther * (C) 2010,2018 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 #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 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) 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 0 /* FIXME: ARRAY_SIZE() is not applicable to * extern const struct value_string[] */ if(param->value[0] > ARRAY_SIZE(sap_result_names)){ return -1; } #endif 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; sap_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; 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) { LOGP(DSAP, LOGL_ERROR, "Failed to parse SAP message\n"); return; } switch(msg->id){ case SAP_CONNECT_RESP: LOGP(DSAP, LOGL_INFO, "Status: %s\n", get_value_string(sap_conn_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_card_status_names, msg->params[0].value[0])); if(msg->params[0].value[0] != SAP_CARD_STATUS_RESET){ /* 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); break; } 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); break; } if(msg->num_params != 2){ LOGP(DSAP, LOGL_ERROR, "wrong number of parameters %u in APDU response\n", msg->num_params); break; } 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; } sap_msg_free(msg); } 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){ LOGP(DSAP, LOGL_ERROR, "Failed to allocate memory\n"); return -ENOMEM; } rc = read(fd->fd, sap_buffer, ms->sap_entity.max_msg_size - 1); if (rc < 0) { LOGP(DSAP, LOGL_ERROR,"SAP socket failed\n"); msgb_free(msg); sap_close(ms); return rc; } if(rc == 0) { LOGP(DSAP, LOGL_NOTICE, "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; sap_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; sap_send(ms, msg); ms->sap_entity.sap_state = SAP_NOT_CONNECTED; } static int sap_apdu(struct osmocom_ms *ms, uint8_t *data, uint16_t len) { struct msgb *msg; struct sap_param params[1]; int rc; 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 -EIO; } msg = sap_create_msg(SAP_TRANSFER_APDU_REQ, 1, params); if(!msg) return -ENOMEM; rc = sap_send(ms, msg); if (rc) return rc; ms->sap_entity.sap_state = SAP_PROCESSING_APDU_REQUEST; return 0; } int sap_open(struct osmocom_ms *ms) { int rc; rc = osmo_sock_unix_init_ofd(&ms->sap_wq.bfd, SOCK_STREAM, 0, ms->settings.sap_socket_path, OSMO_SOCK_F_CONNECT); if (rc < 0) { LOGP(DSAP, LOGL_ERROR, "Failed to create unix domain socket %s: %s\n", ms->settings.sap_socket_path, strerror(-rc)); ms->sap_entity.sap_state = SAP_SOCKET_ERROR; return rc; } osmo_wqueue_init(&ms->sap_wq, 100); ms->sap_wq.bfd.data = ms; ms->sap_wq.read_cb = sap_read; ms->sap_wq.write_cb = sap_write; 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 sap_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)); return sap_apdu(ms, data, length); } /* register message handler for messages that are sent from L2->L3 */ int sap_register_handler(struct osmocom_ms *ms, sap_cb_t cb) { ms->sap_entity.msg_handler = cb; return 0; } /* init */ int sap_init(struct osmocom_ms *ms) { struct osmosap_entity *sap = &ms->sap_entity; LOGP(DSAP, LOGL_INFO, "init SAP client\n"); sap->sap_state = SAP_NOT_CONNECTED; sap->max_msg_size = GSM_SAP_LENGTH; return 0; }