| /* (C) 2020 by Harald Welte <laforge@gnumonks.org> |
| * |
| * All Rights Reserved |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| * |
| * 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. |
| * |
| */ |
| |
| /* This is a remsim-client that provides an IFD_Handler (reader driver) |
| * towards the PC/SC services. This effectively allows any local PC/SC client |
| * application to use a remote smartcard via osmo-remsim. |
| * |
| * In order to use this, you will need an /etc/reader.conf.d/osmo-remsim-client |
| * file with the following content: |
| * |
| * FRIENDLYNAME "osmo-remsim-client" |
| * DEVICENAME 0:0:192.168.11.10:9998 |
| * LIBPATH /usr/lib/pcsc/drivers/serial/libifd_remsim_client.so |
| * |
| * Where DEVICENAME has the following format: |
| * [ClientID:[SlotNr:[ServerIp:[ServerPort]]]] |
| * |
| */ |
| |
| #include <errno.h> |
| #include <unistd.h> |
| #include <pthread.h> |
| |
| #include <osmocom/core/select.h> |
| #include <osmocom/core/application.h> |
| extern int osmo_ctx_init(const char *id); |
| |
| #include "client.h" |
| |
| /* ensure this current thread has an osmo_ctx and hence can use OTC_GLOBAL and friends */ |
| static void ensure_osmo_ctx(void) |
| { |
| if (!osmo_ctx) |
| osmo_ctx_init(""); |
| } |
| |
| /* inter-thread messages between IFD thread and remsim-client thread */ |
| enum itmsg_type { |
| ITMSG_TYPE_NONE, |
| |
| /* card present? */ |
| ITMSG_TYPE_CARD_PRES_REQ, |
| ITMSG_TYPE_CARD_PRES_RESP, |
| |
| /* obtain ATR */ |
| ITMSG_TYPE_ATR_REQ, |
| ITMSG_TYPE_ATR_RESP, |
| |
| /* transceive APDU: Send C-APDU, receive R-APDU */ |
| ITMSG_TYPE_C_APDU_REQ, |
| ITMSG_TYPE_R_APDU_IND, |
| |
| /* power off the card */ |
| ITMSG_TYPE_POWER_OFF_REQ, |
| ITMSG_TYPE_POWER_OFF_RESP, |
| |
| /* power on the card */ |
| ITMSG_TYPE_POWER_ON_REQ, |
| ITMSG_TYPE_POWER_ON_RESP, |
| |
| /* reset the card */ |
| ITMSG_TYPE_RESET_REQ, |
| ITMSG_TYPE_RESET_RESP, |
| }; |
| |
| struct itmsg { |
| enum itmsg_type type; |
| uint16_t status; /* 0 == success */ |
| uint16_t len; /* length of 'data' */ |
| uint8_t data[0]; |
| }; |
| |
| /* allocate + initialize msgb-wrapped inter-thread message (struct itmsg) */ |
| struct msgb *itmsg_alloc(enum itmsg_type type, uint16_t status, const uint8_t *data, uint16_t len) |
| { |
| struct msgb *msg = msgb_alloc_c(OTC_GLOBAL, sizeof(struct itmsg)+len, "Tx itmsg"); |
| struct itmsg *im; |
| |
| if (!msg) |
| return NULL; |
| |
| im = (struct itmsg *) msgb_put(msg, sizeof(struct itmsg) + len); |
| im->type = type; |
| im->status = status; |
| im->len = len; |
| if (len) |
| memcpy(im->data, data, len); |
| |
| return msg; |
| } |
| |
| /*********************************************************************** |
| * remsim_client thread |
| ***********************************************************************/ |
| |
| void __thread *talloc_asn1_ctx; |
| |
| struct client_thread { |
| /* bankd client running inside this thread */ |
| struct bankd_client *bc; |
| |
| /* inter-thread osmo-fd; communication with IFD/PCSC thread */ |
| struct osmo_fd it_ofd; |
| struct llist_head it_msgq; |
| |
| /* ATR as received from remsim-bankd */ |
| uint8_t atr[ATR_SIZE_MAX]; |
| uint8_t atr_len; |
| }; |
| |
| /* configuration of client thread; passed in from IFD thread */ |
| struct client_thread_cfg { |
| const char *server_host; |
| int server_port; |
| int client_id; |
| int client_slot; |
| int it_sock_fd; |
| }; |
| |
| /* enqueue a msgb (containing 'struct itmsg') towards the IFD-handler thread */ |
| static void enqueue_to_ifd(struct client_thread *ct, struct msgb *msg) |
| { |
| if (!msg) |
| return; |
| |
| msgb_enqueue(&ct->it_msgq, msg); |
| ct->it_ofd.when |= OSMO_FD_WRITE; |
| } |
| |
| /*********************************************************************** |
| * frontend to remsim-client main FSM code |
| ***********************************************************************/ |
| |
| int frontend_request_card_insert(struct bankd_client *bc) |
| { |
| return 0; |
| } |
| |
| int frontend_request_card_remove(struct bankd_client *bc) |
| { |
| return 0; |
| } |
| |
| int frontend_request_sim_remote(struct bankd_client *bc) |
| { |
| return 0; |
| } |
| |
| int frontend_request_sim_local(struct bankd_client *bc) |
| { |
| return 0; |
| } |
| |
| int frontend_request_modem_reset(struct bankd_client *bc) |
| { |
| return 0; |
| } |
| |
| int frontend_handle_card2modem(struct bankd_client *bc, const uint8_t *data, size_t len) |
| { |
| struct client_thread *ct = bc->data; |
| struct msgb *msg; |
| |
| OSMO_ASSERT(data); |
| |
| DEBUGP(DMAIN, "R-APDU: %s\n", osmo_hexdump(data, len)); |
| /* enqueue towards IFD thread */ |
| msg = itmsg_alloc(ITMSG_TYPE_R_APDU_IND, 0, data, len); |
| OSMO_ASSERT(msg); |
| enqueue_to_ifd(ct, msg); |
| |
| return 0; |
| } |
| |
| int frontend_handle_set_atr(struct bankd_client *bc, const uint8_t *data, size_t len) |
| { |
| struct client_thread *ct = bc->data; |
| unsigned int atr_len; |
| |
| OSMO_ASSERT(data); |
| |
| DEBUGP(DMAIN, "SET_ATR: %s\n", osmo_hexdump(data, len)); |
| |
| /* store ATR in local data structure until somebody needs it */ |
| atr_len = len; |
| if (atr_len > sizeof(ct->atr)) |
| atr_len = sizeof(ct->atr); |
| memcpy(ct->atr, data, atr_len); |
| ct->atr_len = atr_len; |
| |
| return 0; |
| } |
| |
| int frontend_handle_slot_status(struct bankd_client *bc, const SlotPhysStatus_t *sts) |
| { |
| return 0; |
| } |
| |
| int frontend_append_script_env(struct bankd_client *bc, char **env, int idx, size_t max_env) |
| { |
| return idx; |
| } |
| |
| /*********************************************************************** |
| * Incoming command from the user application |
| ***********************************************************************/ |
| |
| /* handle a single msgb-wrapped 'struct itmsg' from the IFD-handler thread */ |
| static void handle_it_msg(struct client_thread *ct, struct itmsg *itmsg) |
| { |
| struct bankd_client *bc = ct->bc; |
| struct msgb *tx = NULL; |
| RsproPDU_t *pdu; |
| BankSlot_t bslot; |
| |
| bank_slot2rspro(&bslot, &ct->bc->bankd_slot); |
| |
| switch (itmsg->type) { |
| case ITMSG_TYPE_CARD_PRES_REQ: |
| if (bc->bankd_conn.fi->state == 2 /*SRVC_ST_CONNECTED*/) |
| tx = itmsg_alloc(ITMSG_TYPE_CARD_PRES_RESP, 0, NULL, 0); |
| else |
| tx = itmsg_alloc(ITMSG_TYPE_CARD_PRES_RESP, 0xffff, NULL, 0); |
| OSMO_ASSERT(tx); |
| break; |
| |
| case ITMSG_TYPE_ATR_REQ: |
| /* respond to IFD */ |
| tx = itmsg_alloc(ITMSG_TYPE_ATR_RESP, 0, ct->atr, ct->atr_len); |
| OSMO_ASSERT(tx); |
| break; |
| |
| case ITMSG_TYPE_POWER_OFF_REQ: |
| pdu = rspro_gen_ClientSlotStatusInd(bc->srv_conn.clslot, &bslot, |
| true, false, false, true); |
| server_conn_send_rspro(&bc->bankd_conn, pdu); |
| /* respond to IFD */ |
| tx = itmsg_alloc(ITMSG_TYPE_POWER_OFF_RESP, 0, NULL, 0); |
| OSMO_ASSERT(tx); |
| break; |
| |
| case ITMSG_TYPE_POWER_ON_REQ: |
| pdu = rspro_gen_ClientSlotStatusInd(bc->srv_conn.clslot, &bslot, |
| false, true, true, true); |
| server_conn_send_rspro(&bc->bankd_conn, pdu); |
| /* respond to IFD */ |
| tx = itmsg_alloc(ITMSG_TYPE_POWER_ON_RESP, 0, NULL, 0); |
| OSMO_ASSERT(tx); |
| break; |
| |
| case ITMSG_TYPE_RESET_REQ: |
| /* reset the [remote] card */ |
| pdu = rspro_gen_ClientSlotStatusInd(bc->srv_conn.clslot, &bslot, |
| true, true, true, true); |
| server_conn_send_rspro(&bc->bankd_conn, pdu); |
| /* and take it out of reset again */ |
| pdu = rspro_gen_ClientSlotStatusInd(bc->srv_conn.clslot, &bslot, |
| false, true, true, true); |
| server_conn_send_rspro(&bc->bankd_conn, pdu); |
| /* respond to IFD */ |
| tx = itmsg_alloc(ITMSG_TYPE_RESET_RESP, 0, NULL, 0); |
| OSMO_ASSERT(tx); |
| break; |
| case ITMSG_TYPE_C_APDU_REQ: |
| if (!bc->srv_conn.clslot) { |
| LOGP(DMAIN, LOGL_ERROR, "Cannot send command; no client slot\n"); |
| /* FIXME: Response? */ |
| return; |
| } |
| |
| /* Send CMD APDU to [remote] card */ |
| pdu = rspro_gen_TpduModem2Card(bc->srv_conn.clslot, &bslot, itmsg->data, itmsg->len); |
| server_conn_send_rspro(&bc->bankd_conn, pdu); |
| /* response will come in asynchronously */ |
| break; |
| default: |
| LOGP(DMAIN, LOGL_ERROR, "Unknown inter-thread msg type %u\n", itmsg->type); |
| break; |
| } |
| |
| if (tx) |
| enqueue_to_ifd(ct, tx); |
| |
| } |
| |
| /* call-back function for inter-thread socket */ |
| static int it_sock_fd_cb(struct osmo_fd *ofd, unsigned int what) |
| { |
| struct client_thread *ct = ofd->data; |
| int rc; |
| |
| if (what & OSMO_FD_READ) { |
| struct msgb *msg = msgb_alloc_c(OTC_GLOBAL, 1024, "Rx it_fd"); |
| struct itmsg *itmsg; |
| |
| OSMO_ASSERT(msg); |
| rc = read(ofd->fd, msg->tail, msgb_tailroom(msg)); |
| if (rc <= 0) { |
| LOGP(DMAIN, LOGL_ERROR, "Error reading from inter-thread fd: %d\n", rc); |
| pthread_exit(NULL); |
| } |
| msgb_put(msg, rc); |
| itmsg = (struct itmsg *) msgb_data(msg); |
| if (msgb_length(msg) < sizeof(*itmsg) || |
| msgb_length(msg) < sizeof(*itmsg) + itmsg->len) { |
| LOGP(DMAIN, LOGL_ERROR, "Dropping short inter-thread message\n"); |
| } else { |
| handle_it_msg(ct, itmsg); |
| } |
| msgb_free(msg); |
| } |
| |
| if (what & OSMO_FD_WRITE) { |
| struct msgb *msg = msgb_dequeue(&ct->it_msgq); |
| if (!msg) { |
| /* last message: disable write events */ |
| ofd->when &= ~OSMO_FD_WRITE; |
| } else { |
| unsigned int len = msgb_length(msg); |
| rc = write(ofd->fd, msgb_data(msg), len); |
| msgb_free(msg); |
| if (rc < len) { |
| LOGP(DMAIN, LOGL_ERROR, "Short write on inter-thread fd: %d < %d\n", |
| rc, len); |
| } |
| } |
| } |
| |
| |
| return 0; |
| } |
| |
| /* release all resources allocated by thread */ |
| static void client_pthread_cleanup(void *arg) |
| { |
| struct client_thread *ct = arg; |
| |
| LOGP(DMAIN, LOGL_INFO, "Cleaning up remsim-client thread\n"); |
| //FIXME remsim_client_destroy(ct->bc); |
| ct->bc = NULL; |
| msgb_queue_free(&ct->it_msgq); |
| osmo_fd_unregister(&ct->it_ofd); |
| close(ct->it_ofd.fd); |
| ct->it_ofd.fd = -1; |
| talloc_free(ct); |
| } |
| |
| /* main function of remsim-client pthread */ |
| static void *client_pthread_main(void *arg) |
| { |
| struct client_thread_cfg *cfg = arg; |
| struct client_config *ccfg; |
| struct client_thread *ct; |
| char hostname[256]; |
| int rc; |
| |
| if (gethostname(hostname, sizeof(hostname)) < 0) |
| OSMO_STRLCPY_ARRAY(hostname, "unknown"); |
| |
| osmo_select_init(); |
| rc = osmo_ctx_init("client"); |
| OSMO_ASSERT(rc == 0); |
| |
| ct = talloc_zero(OTC_GLOBAL, struct client_thread); |
| OSMO_ASSERT(ct); |
| |
| ccfg = client_config_init(ct); |
| OSMO_ASSERT(ccfg); |
| osmo_talloc_replace_string(ccfg, &ccfg->server_host, cfg->server_host); |
| if (cfg->server_port >= 0) |
| ccfg->server_port = cfg->server_port; |
| ccfg->client_id = cfg->client_id; |
| ccfg->client_slot = cfg->client_slot; |
| |
| if (!talloc_asn1_ctx) |
| talloc_asn1_ctx= talloc_named_const(ct, 0, "asn1"); |
| |
| ct->bc = remsim_client_create(ct, hostname, "remsim_ifdhandler", ccfg); |
| OSMO_ASSERT(ct->bc); |
| ct->bc->data = ct; |
| |
| INIT_LLIST_HEAD(&ct->it_msgq); |
| osmo_fd_setup(&ct->it_ofd, cfg->it_sock_fd, OSMO_FD_READ, &it_sock_fd_cb, ct, 0); |
| osmo_fd_register(&ct->it_ofd); |
| |
| /* ensure we get properly cleaned up if cancelled */ |
| pthread_cleanup_push(client_pthread_cleanup, ct); |
| |
| osmo_fsm_inst_dispatch(ct->bc->srv_conn.fi, SRVC_E_ESTABLISH, NULL); |
| |
| while (1) { |
| osmo_select_main(0); |
| } |
| |
| pthread_cleanup_pop(1); |
| return NULL; |
| } |
| |
| /*********************************************************************** |
| * PC/SC ifd_handler API functions |
| ***********************************************************************/ |
| |
| #include <ifdhandler.h> |
| #include <debuglog.h> |
| |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| |
| static const struct value_string ifd_status_names[] = { |
| OSMO_VALUE_STRING(IFD_SUCCESS), |
| OSMO_VALUE_STRING(IFD_ERROR_TAG), |
| OSMO_VALUE_STRING(IFD_ERROR_SET_FAILURE), |
| OSMO_VALUE_STRING(IFD_ERROR_VALUE_READ_ONLY), |
| OSMO_VALUE_STRING(IFD_ERROR_PTS_FAILURE), |
| OSMO_VALUE_STRING(IFD_ERROR_NOT_SUPPORTED), |
| OSMO_VALUE_STRING(IFD_PROTOCOL_NOT_SUPPORTED), |
| OSMO_VALUE_STRING(IFD_ERROR_POWER_ACTION), |
| OSMO_VALUE_STRING(IFD_ERROR_SWALLOW), |
| OSMO_VALUE_STRING(IFD_ERROR_EJECT), |
| OSMO_VALUE_STRING(IFD_ERROR_CONFISCATE), |
| OSMO_VALUE_STRING(IFD_COMMUNICATION_ERROR), |
| OSMO_VALUE_STRING(IFD_RESPONSE_TIMEOUT), |
| OSMO_VALUE_STRING(IFD_NOT_SUPPORTED), |
| OSMO_VALUE_STRING(IFD_ICC_PRESENT), |
| OSMO_VALUE_STRING(IFD_ICC_NOT_PRESENT), |
| OSMO_VALUE_STRING(IFD_NO_SUCH_DEVICE), |
| OSMO_VALUE_STRING(IFD_ERROR_INSUFFICIENT_BUFFER), |
| { 0, NULL } |
| }; |
| |
| static const struct value_string ifd_tag_names[] = { |
| OSMO_VALUE_STRING(TAG_IFD_ATR), |
| OSMO_VALUE_STRING(TAG_IFD_SLOTNUM), |
| OSMO_VALUE_STRING(TAG_IFD_SLOT_THREAD_SAFE), |
| OSMO_VALUE_STRING(TAG_IFD_THREAD_SAFE), |
| OSMO_VALUE_STRING(TAG_IFD_SLOTS_NUMBER), |
| OSMO_VALUE_STRING(TAG_IFD_SIMULTANEOUS_ACCESS), |
| OSMO_VALUE_STRING(TAG_IFD_POLLING_THREAD), |
| OSMO_VALUE_STRING(TAG_IFD_POLLING_THREAD_KILLABLE), |
| OSMO_VALUE_STRING(TAG_IFD_STOP_POLLING_THREAD), |
| OSMO_VALUE_STRING(TAG_IFD_POLLING_THREAD_WITH_TIMEOUT), |
| { 0, NULL } |
| }; |
| |
| #define LOG_EXIT(Lun, r) \ |
| Log4(r == IFD_SUCCESS || r == IFD_ICC_NOT_PRESENT ? PCSC_LOG_DEBUG : PCSC_LOG_ERROR, \ |
| "%s(0x%08lx) => %s\n", __func__, Lun, get_value_string(ifd_status_names, r)) |
| |
| #define LOG_EXITF(Lun, r, fmt, args...) \ |
| Log5(r == IFD_SUCCESS ? PCSC_LOG_DEBUG : PCSC_LOG_ERROR, \ |
| "%s(0x%08lx) "fmt" => %s\n", __func__, Lun, ## args, get_value_string(ifd_status_names, r)) |
| |
| /* IFD side handle for a remsim-client [thread] */ |
| struct ifd_client { |
| /* the client pthread itself */ |
| pthread_t pthread; |
| /* socket to talk to thread */ |
| int it_fd; |
| /* configuration passed into the thread */ |
| struct client_thread_cfg cfg; |
| }; |
| |
| static struct msgb *ifd_xceive_client(struct ifd_client *ic, struct msgb *tx) |
| { |
| struct msgb *rx = msgb_alloc_c(OTC_GLOBAL, 1024, "ifd_rx itmsg"); |
| struct itmsg *rx_it; |
| int rc; |
| |
| rc = write(ic->it_fd, msgb_data(tx), msgb_length(tx)); |
| msgb_free(tx); |
| if (rc < msgb_length(tx)) { |
| Log2(PCSC_LOG_ERROR, "Short write IFD->client thread: %d\n", rc); |
| msgb_free(rx); |
| return NULL; |
| } |
| rc = read(ic->it_fd, rx->tail, msgb_tailroom(rx)); |
| if (rc <= 0) { |
| Log2(PCSC_LOG_ERROR, "Short read IFD<-client thread: %d\n", rc); |
| msgb_free(rx); |
| return NULL; |
| } |
| msgb_put(rx, rc); |
| rx_it = (struct itmsg *) msgb_data(rx); |
| if (msgb_length(rx) < sizeof(*rx_it) + rx_it->len) { |
| Log2(PCSC_LOG_ERROR, "Short itmsg IFD<-client thread: %d\n", msgb_length(rx)); |
| msgb_free(rx); |
| return NULL; |
| } |
| return rx; |
| } |
| |
| /* function called on IFD side to create socketpair + start remsim-client thread */ |
| static struct ifd_client *create_ifd_client(const struct client_thread_cfg *cfg) |
| { |
| struct ifd_client *ic = talloc_zero(OTC_GLOBAL, struct ifd_client); |
| int sp[2]; |
| int rc; |
| |
| /* copy over configuration */ |
| ic->cfg = *cfg; |
| |
| /* create socket pair for communication between threads */ |
| rc = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sp); |
| if (rc != 0) { |
| talloc_free(ic); |
| return NULL; |
| } |
| |
| ic->it_fd = sp[0]; |
| ic->cfg.it_sock_fd = sp[1]; |
| |
| /* start the thread */ |
| rc = pthread_create(&ic->pthread, NULL, client_pthread_main, &ic->cfg); |
| if (rc != 0) { |
| Log1(PCSC_LOG_ERROR, "Error creating remsim-client pthread\n"); |
| close(sp[0]); |
| close(sp[1]); |
| talloc_free(ic); |
| return NULL; |
| } |
| |
| return ic; |
| } |
| |
| /* function called on IFD side to destroy (terminate) remsim-client thread */ |
| static void destroy_ifd_client(struct ifd_client *ic) |
| { |
| if (!ic) |
| return; |
| |
| pthread_cancel(ic->pthread); |
| pthread_join(ic->pthread, NULL); |
| } |
| |
| #define MAX_SLOTS 256 |
| static struct ifd_client *ifd_client[MAX_SLOTS]; |
| |
| #define LUN2SLOT(lun) ((lun) & 0xffff) |
| #define LUN2RDR(lun) ((lun) >> 16) |
| |
| |
| RESPONSECODE IFDHCreateChannel(DWORD Lun, DWORD Channel) |
| { |
| return IFD_COMMUNICATION_ERROR; |
| } |
| |
| RESPONSECODE IFDHCreateChannelByName(DWORD Lun, LPSTR DeviceName) |
| { |
| struct ifd_client *ic; |
| struct client_thread_cfg cfg = { |
| .server_host = "127.0.0.1", |
| .server_port = -1, |
| .client_id = 0, |
| .client_slot = 0, |
| }; |
| char *r, *client_id, *slot_nr, *host, *port; |
| |
| if (LUN2RDR(Lun) != 0) |
| return IFD_NO_SUCH_DEVICE; |
| |
| if (LUN2SLOT(Lun) >= ARRAY_SIZE(ifd_client)) |
| return IFD_NO_SUCH_DEVICE; |
| |
| ensure_osmo_ctx(); |
| |
| client_id = strtok_r(DeviceName, ":", &r); |
| if (!client_id) |
| goto end_parse; |
| cfg.client_id = atoi(client_id); |
| |
| slot_nr = strtok_r(NULL, ":", &r); |
| if (!slot_nr) |
| goto end_parse; |
| cfg.client_slot = atoi(slot_nr); |
| |
| host = strtok_r(NULL, ":", &r); |
| if (!host) |
| goto end_parse; |
| cfg.server_host = strdup(host); |
| |
| port = strtok_r(NULL, ":", &r); |
| cfg.server_port = atoi(port); |
| |
| |
| end_parse: |
| LOGP(DMAIN, LOGL_NOTICE, "remsim-client C%d:%d bankd=%s:%d\n", |
| cfg.client_id, cfg.client_slot, cfg.server_host, cfg.server_port); |
| |
| ic = create_ifd_client(&cfg); |
| if (ic) { |
| ifd_client[LUN2SLOT(Lun)] = ic; |
| return IFD_SUCCESS; |
| } else |
| return IFD_COMMUNICATION_ERROR; |
| } |
| |
| RESPONSECODE IFDHControl(DWORD Lun, DWORD dwControlCode, PUCHAR TxBuffer, DWORD TxLength, |
| PUCHAR RxBuffer, DWORD RxLength, LPDWORD pdwBytesReturned) |
| { |
| RESPONSECODE r = IFD_COMMUNICATION_ERROR; |
| |
| ensure_osmo_ctx(); |
| |
| if (LUN2RDR(Lun) != 0) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| if (LUN2SLOT(Lun) >= ARRAY_SIZE(ifd_client)) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| if (pdwBytesReturned) |
| *pdwBytesReturned = 0; |
| |
| r = IFD_ERROR_NOT_SUPPORTED; |
| err: |
| LOG_EXIT(Lun, r); |
| return r; |
| } |
| |
| RESPONSECODE IFDHCloseChannel(DWORD Lun) |
| { |
| RESPONSECODE r = IFD_COMMUNICATION_ERROR; |
| |
| ensure_osmo_ctx(); |
| |
| if (LUN2RDR(Lun) != 0) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| if (LUN2SLOT(Lun) >= ARRAY_SIZE(ifd_client)) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| destroy_ifd_client(ifd_client[LUN2SLOT(Lun)]); |
| ifd_client[LUN2SLOT(Lun)] = NULL; |
| |
| r = IFD_SUCCESS; |
| err: |
| LOG_EXIT(Lun, r); |
| return r; |
| } |
| |
| RESPONSECODE IFDHGetCapabilities(DWORD Lun, DWORD Tag, PDWORD Length, PUCHAR Value) |
| { |
| RESPONSECODE r = IFD_COMMUNICATION_ERROR; |
| struct ifd_client *ic; |
| struct msgb *rx, *tx; |
| struct itmsg *rx_it; |
| |
| ensure_osmo_ctx(); |
| |
| if (LUN2RDR(Lun) != 0) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| if (LUN2SLOT(Lun) >= ARRAY_SIZE(ifd_client)) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| ic = ifd_client[LUN2SLOT(Lun)]; |
| if (!ic) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| if (!Length || !Value) |
| goto err; |
| |
| switch (Tag) { |
| case TAG_IFD_ATR: |
| /* Return the ATR and its size */ |
| tx = itmsg_alloc(ITMSG_TYPE_ATR_REQ, 0, NULL, 0); |
| OSMO_ASSERT(tx); |
| rx = ifd_xceive_client(ic, tx); |
| if (!rx) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| rx_it = (struct itmsg *)msgb_data(rx); |
| if (*Length > rx_it->len) |
| *Length = rx_it->len; |
| memcpy(Value, rx_it->data, *Length); |
| msgb_free(rx); |
| break; |
| case TAG_IFD_SIMULTANEOUS_ACCESS: |
| /* Return the number of sessions (readers) the driver |
| * can handle in Value[0]. This is used for multiple |
| * readers sharing the same driver. */ |
| if (*Length < 1) |
| goto err; |
| *Value = 1; |
| *Length = 1; |
| break; |
| case TAG_IFD_SLOTS_NUMBER: |
| /* Return the number of slots in this reader in Value[0] */ |
| if (*Length < 1) |
| goto err; |
| *Value = 1; |
| *Length = 1; |
| break; |
| case TAG_IFD_THREAD_SAFE: |
| /* If the driver supports more than one reader (see |
| * TAG_IFD_SIMULTANEOUS_ACCESS above) this tag indicates |
| * if the driver supports access to multiple readers at |
| * the same time. */ |
| if (*Length < 1) |
| goto err; |
| *Value = 0; |
| *Length = 1; |
| break; |
| case TAG_IFD_SLOT_THREAD_SAFE: |
| /* If the reader has more than one slot (see |
| * TAG_IFD_SLOTS_NUMBER above) this tag indicates if the |
| * driver supports access to multiple slots of the same |
| * reader at the same time. */ |
| if (*Length < 1) |
| goto err; |
| *Value = 0; |
| *Length = 1; |
| break; |
| default: |
| r = IFD_ERROR_TAG; |
| goto err; |
| } |
| |
| r = IFD_SUCCESS; |
| |
| err: |
| if (r != IFD_SUCCESS && Length) |
| *Length = 0; |
| |
| LOG_EXITF(Lun, r, "%s", get_value_string(ifd_tag_names, Tag)); |
| return r; |
| } |
| |
| RESPONSECODE IFDHSetCapabilities(DWORD Lun, DWORD Tag, DWORD Length, PUCHAR Value) |
| { |
| ensure_osmo_ctx(); |
| |
| if (LUN2RDR(Lun) != 0) |
| return IFD_NO_SUCH_DEVICE; |
| |
| if (LUN2SLOT(Lun) >= ARRAY_SIZE(ifd_client)) |
| return IFD_NO_SUCH_DEVICE; |
| |
| |
| LOG_EXIT(Lun, IFD_NOT_SUPPORTED); |
| return IFD_NOT_SUPPORTED; |
| } |
| |
| RESPONSECODE IFDHSetProtocolParameters(DWORD Lun, DWORD Protocol, UCHAR Flags, UCHAR PTS1, |
| UCHAR PTS2, UCHAR PTS3) |
| { |
| ensure_osmo_ctx(); |
| |
| if (LUN2RDR(Lun) != 0) |
| return IFD_NO_SUCH_DEVICE; |
| |
| if (LUN2SLOT(Lun) >= ARRAY_SIZE(ifd_client)) |
| return IFD_NO_SUCH_DEVICE; |
| |
| LOG_EXIT(Lun, IFD_SUCCESS); |
| return IFD_SUCCESS; |
| } |
| |
| RESPONSECODE IFDHPowerICC(DWORD Lun, DWORD Action, PUCHAR Atr, PDWORD AtrLength) |
| { |
| RESPONSECODE r = IFD_COMMUNICATION_ERROR; |
| struct ifd_client *ic; |
| struct msgb *rx, *tx; |
| |
| ensure_osmo_ctx(); |
| |
| if (LUN2RDR(Lun) != 0) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| if (LUN2SLOT(Lun) >= ARRAY_SIZE(ifd_client)) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| ic = ifd_client[LUN2SLOT(Lun)]; |
| if (!ic) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| switch (Action) { |
| case IFD_POWER_DOWN: |
| tx = itmsg_alloc(ITMSG_TYPE_POWER_OFF_REQ, 0, NULL, 0); |
| break; |
| case IFD_POWER_UP: |
| tx = itmsg_alloc(ITMSG_TYPE_POWER_ON_REQ, 0, NULL, 0); |
| break; |
| case IFD_RESET: |
| tx = itmsg_alloc(ITMSG_TYPE_RESET_REQ, 0, NULL, 0); |
| break; |
| default: |
| r = IFD_NOT_SUPPORTED; |
| goto err; |
| } |
| |
| rx = ifd_xceive_client(ic, tx); |
| if (!rx) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| r = IFD_SUCCESS; |
| msgb_free(rx); |
| |
| err: |
| if (r != IFD_SUCCESS && AtrLength) |
| *AtrLength = 0; |
| else |
| r = IFDHGetCapabilities(Lun, TAG_IFD_ATR, AtrLength, Atr); |
| |
| LOG_EXIT(Lun, r); |
| return r; |
| } |
| |
| RESPONSECODE IFDHTransmitToICC(DWORD Lun, SCARD_IO_HEADER SendPci, PUCHAR TxBuffer, |
| DWORD TxLength, PUCHAR RxBuffer, PDWORD RxLength, |
| PSCARD_IO_HEADER RecvPci) |
| { |
| RESPONSECODE r = IFD_COMMUNICATION_ERROR; |
| struct ifd_client *ic; |
| struct msgb *rx, *tx; |
| struct itmsg *rx_it; |
| |
| ensure_osmo_ctx(); |
| |
| if (LUN2RDR(Lun) != 0) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| if (LUN2SLOT(Lun) >= ARRAY_SIZE(ifd_client)) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| ic = ifd_client[LUN2SLOT(Lun)]; |
| if (!ic) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| tx = itmsg_alloc(ITMSG_TYPE_C_APDU_REQ, 0, TxBuffer, TxLength); |
| OSMO_ASSERT(tx); |
| /* transmit C-APDU to remote reader + blocking wait for response from peer */ |
| rx = ifd_xceive_client(ic, tx); |
| if (!rx) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| rx_it = (struct itmsg *) msgb_data(rx); |
| if (*RxLength > rx_it->len) |
| *RxLength = rx_it->len; |
| memcpy(RxBuffer, rx_it->data, *RxLength); |
| msgb_free(rx); |
| |
| r = IFD_SUCCESS; |
| err: |
| if (r != IFD_SUCCESS && RxLength) |
| *RxLength = 0; |
| |
| LOG_EXIT(Lun, r); |
| return r; |
| } |
| |
| RESPONSECODE IFDHICCPresence(DWORD Lun) |
| { |
| RESPONSECODE r = IFD_COMMUNICATION_ERROR; |
| struct ifd_client *ic; |
| struct msgb *rx, *tx; |
| struct itmsg *rx_it; |
| |
| ensure_osmo_ctx(); |
| |
| if (LUN2RDR(Lun) != 0) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| if (LUN2SLOT(Lun) >= ARRAY_SIZE(ifd_client)) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| ic = ifd_client[LUN2SLOT(Lun)]; |
| if (!ic) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| |
| tx = itmsg_alloc(ITMSG_TYPE_CARD_PRES_REQ, 0, NULL, 0); |
| OSMO_ASSERT(tx); |
| rx = ifd_xceive_client(ic, tx); |
| if (!rx) { |
| r = IFD_NO_SUCH_DEVICE; |
| goto err; |
| } |
| rx_it = (struct itmsg *) msgb_data(rx); |
| if (rx_it->status == 0) |
| r = IFD_SUCCESS; |
| else |
| r = IFD_ICC_NOT_PRESENT; |
| |
| err: |
| LOG_EXIT(Lun, r); |
| return r; |
| } |
| |
| static __attribute__((constructor)) void on_dso_load_ifd(void) |
| { |
| void *g_tall_ctx = NULL; |
| ensure_osmo_ctx(); |
| osmo_init_logging2(g_tall_ctx, &log_info); |
| } |