diff --git a/configure.ac b/configure.ac index 1c9cd72..36c469d 100644 --- a/configure.ac +++ b/configure.ac @@ -68,6 +68,14 @@ if test "$osmo_ac_build_client" = "yes"; then PKG_CHECK_MODULES(OSMOUSB, libosmousb) PKG_CHECK_MODULES(OSMOSIMTRACE2, libosmo-simtrace2) PKG_CHECK_MODULES(USB, libusb-1.0) + + serialconfdir=`pkg-config --variable=serialconfdir libpcsclite` + AC_SUBST(serialconfdir) + ifd_dropdir=`pkg-config --variable=usbdropdir libpcsclite` + # allow user to override the dropdir for the PCSC driver bundle + AC_ARG_WITH(usbdropdir, [--with-usbdropdir PCSC bundle location], + [usbdropdir="${withval}"], [usbdropdir="${ifd_dropdir}"]) + AC_SUBST(usbdropdir) fi AM_CONDITIONAL(BUILD_CLIENT, test "x$osmo_ac_build_client" = "xyes") AC_SUBST(BUILD_CLIENT) @@ -165,6 +173,7 @@ AC_OUTPUT( src/rspro/Makefile src/bankd/Makefile src/client/Makefile + src/client/osmo-remsim-client-reader_conf src/server/Makefile include/Makefile include/osmocom/Makefile diff --git a/debian/control b/debian/control index 14c5d7a..330b220 100644 --- a/debian/control +++ b/debian/control @@ -71,6 +71,17 @@ Description: Osmocom Remote SIM - Client hardware and forwards the SIM card communication to a remsim-bankd, under the control of remsim-server. +Package: libifd-osmo-remsim-client0 +Architecture: any +Multi-Arch: same +Depends: pcscd, ${shlibs:Depends}, ${misc:Depends} +Provides: pcsc-ifd-handler +Suggests: pcsc-tools +Description: Osmocom Remote SIM Client - PC/SC driver + This is an incarnation of osmo-remsim-client which can plug as ifd_handler + driver into pcscd. This means you can use remote smart cards managed + by osmo-remsim-server via normal PC/SC applications. + package: osmo-remsim-doc Architecture: all Section: doc diff --git a/debian/libifd-osmo-remsim-client0.install b/debian/libifd-osmo-remsim-client0.install new file mode 100644 index 0000000..4ceb02f --- /dev/null +++ b/debian/libifd-osmo-remsim-client0.install @@ -0,0 +1,2 @@ +usr/lib/pcsc/drivers/libifd-osmo-remsim-client.bundle/* +etc/reader.conf.d/* diff --git a/doc/manuals/chapters/remsim-client.adoc b/doc/manuals/chapters/remsim-client.adoc index 8e26f8e..e2c43d7 100644 --- a/doc/manuals/chapters/remsim-client.adoc +++ b/doc/manuals/chapters/remsim-client.adoc @@ -258,3 +258,123 @@ R-APDU: 9f 17 The program continues in this loop (read command APDU as hex-dump from stdin; provide response on stdout) until it is terminated by Ctrl+C or by other means. + +== libifd_remsim_client + +This is a remsim-client implemented as so-called `ifd_handler`, i.e. a card reader driver +that plugs into the bottom side of the PC/SC daemon of pcsc-lite. + +Using this library, you can use normal smart card application programs with remote smart +cards managed by osmo-remsim. The setup looks like this: + +[graphviz] +.Overall osmo-remsim architecture using libifd_remsim_client +---- +graph G { + rankdir = LR; + + subgraph cluster_0 { + label = "Client"; + application [label="Any application\nusing PC/SC"]; + pcscd [label="PC/SC Daemon\nlibifd_remsim_client driver"]; + application -- pcscd; + } + + subgraph cluster_2 { + label = "SIM Bank"; + bankd [label="remsim-bankd"]; + reader [label="Card Reader\ne.g. sysmoOCTSIM",shape="rectangle"]; + b_pcscd [label="PC/SC Daemon\nlibccid driver"]; + bankd -- b_pcscd; + b_pcscd -- reader [label = "USB CCID"]; + } + + subgraph cluster_1 { + label = "Server/Backend"; + server [label="remsim-server"]; + backend [label="Back-End Application"]; + server -- backend [label="REST Interface"]; + } + + pcscd -- bankd [label="RSPRO Data"]; + pcscd -- server [label="RSPRO Control"]; + bankd -- server [label="RSPRO Control"]; +} +---- + + +=== Configuration + +Like all non-USB PC/SC reader drivers, this is happening in `/etc/reader.conf` or, at +least on Debian GNU/Linux based systems via files in `/etc/reader.conf.d`. The +osmo-remsim software includes an example configuration file and installs it as +`osmo-remsim-client-reader_conf` in that directory. + +.contents of the configuration example provided by osmo-remsim-client +---- +#FRIENDLYNAME "osmo-remsim-client" +#DEVICENAME 0:0:192.168.11.10:9998 +#LIBPATH /usr/lib/pcsc/drivers/libifd-osmo-remsim-client.bundle/Contents/Linux/libifd_remsim_client.so +---- + +As you can see, all lines are commented out by default. In order to enable the +remsim-client virtual reader, you need to + +* remove the `#` character on all three lines +* configure the DEVICNAME according to your local configuration. It is a string with + fields separated by colons, in the form of CLIENT_ID:CLIENT_SLOT:SERVER_IP:SERVER_PORRT +** First part is the Client ID (default: 0) +** Second part is the Client SlotNumbera (default: 0) +** Third part is the IP address of the `osmo-resim-server` (default: localhost) +** Last part is the RSPRO TCP port of the `osmo-remsim-server` (default: 9998) + +Once the configuration file has been updated, you should re-start pcscd by issuing +`systemctl restart pcscd` or whatever command your Linux distribution uses for restarting +services. + +You can check if the driver is loaded by using the `pcsc_scan` tool included with `pcscd`: + +---- +$ pcsc_scan +Using reader plug'n play mechanism +Scanning present readers... +0: osmo-remsim-client 00 00 + +Wed Mar 4 13:31:42 2020 + Reader 0: osmo-remsim-client 00 00 + Event number: 0 + Card state: Card removed, + - +---- + +Once a proper slotmap to an existing SIM card in a remote bank daemon has been installed +in the server, you should see something like this: + +---- +$ pcsc_scan +Using reader plug'n play mechanism +Scanning present readers... +0: osmo-remsim-client 00 00 + +Wed Mar 4 13:35:18 2020 + Reader 0: osmo-remsim-client 00 00 + Event number: 1 + Card state: Card inserted, + ATR: 3B 7D 94 00 00 55 55 53 0A 74 86 93 0B 24 7C 4D 54 68 + +ATR: 3B 7D 94 00 00 55 55 53 0A 74 86 93 0B 24 7C 4D 54 68 ++ TS = 3B --> Direct Convention ++ T0 = 7D, Y(1): 0111, K: 13 (historical bytes) + TA(1) = 94 --> Fi=512, Di=8, 64 cycles/ETU + 62500 bits/s at 4 MHz, fMax for Fi = 5 MHz => 78125 bits/s + TB(1) = 00 --> VPP is not electrically connected + TC(1) = 00 --> Extra guard time: 0 ++ Historical bytes: 55 55 53 0A 74 86 93 0B 24 7C 4D 54 68 + Category indicator byte: 55 (proprietary format) + +Possibly identified card (using /home/laforge/.cache/smartcard_list.txt): + NONE +---- + +From now on, you can use any application using PC/SC, whether C, Python or Java with a +remote SIM card managed by osmo-remsim. diff --git a/src/client/Makefile.am b/src/client/Makefile.am index ac0d707..f37126a 100644 --- a/src/client/Makefile.am +++ b/src/client/Makefile.am @@ -8,11 +8,27 @@ bin_PROGRAMS = osmo-remsim-client-st2 osmo-remsim-client-shell osmo_remsim_client_shell_SOURCES = user_shell.c remsim_client_main.c \ remsim_client.c ../rspro_client_fsm.c ../debug.c +osmo_remsim_client_shell_CFLAGS = $(AM_CFLAGS) osmo_remsim_client_shell_LDADD = $(OSMOCORE_LIBS) $(OSMOGSM_LIBS) $(OSMOABIS_LIBS) \ $(top_builddir)/src/libosmo-rspro.la +EXTRA_DIST=PkgInfo osmo-remsim-client-reader_conf.in +serialconf_DATA=osmo-remsim-client-reader_conf +bundledir=$(usbdropdir)/libifd-osmo-remsim-client.bundle/Contents +bundle_DATA=PkgInfo +bundlelinuxdir=$(bundledir)/Linux +bundlelinux_LTLIBRARIES = libifd_remsim_client.la +libifd_remsim_client_la_SOURCES = user_ifdhandler.c \ + remsim_client.c ../rspro_client_fsm.c ../debug.c +libifd_remsim_client_la_CFLAGS = $(AM_CFLAGS) +libifd_remsim_client_la_CPPFLAGS = $(PCSC_CFLAGS) +libifd_remsim_client_la_LDFLAGS = -no-undefined +libifd_remsim_client_la_LIBADD = $(OSMOCORE_LIBS) $(OSMOGSM_LIBS) $(OSMOABIS_LIBS) \ + $(top_builddir)/src/libosmo-rspro.la + osmo_remsim_client_st2_SOURCES = simtrace2-remsim_client.c \ ../rspro_client_fsm.c ../debug.c +osmo_remsim_client_st2_CFLAGS = $(AM_CFLAGS) osmo_remsim_client_st2_LDADD = $(OSMOCORE_LIBS) $(OSMOGSM_LIBS) $(OSMOABIS_LIBS) \ $(OSMOUSB_LIBS) $(OSMOSIMTRACE2_LIBS) \ $(USB_LIBS) $(OSMOSIM_LIBS) \ diff --git a/src/client/PkgInfo b/src/client/PkgInfo new file mode 100644 index 0000000..43c9cb0 --- /dev/null +++ b/src/client/PkgInfo @@ -0,0 +1 @@ +BNDL???? diff --git a/src/client/client.h b/src/client/client.h index 80381fa..0761255 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -56,6 +56,7 @@ struct bankd_client { struct client_config *cfg; struct cardem_inst *cardem; + void *data; }; #define srvc2bankd_client(srvc) container_of(srvc, struct bankd_client, srv_conn) diff --git a/src/client/osmo-remsim-client-reader_conf.in b/src/client/osmo-remsim-client-reader_conf.in new file mode 100644 index 0000000..4b90116 --- /dev/null +++ b/src/client/osmo-remsim-client-reader_conf.in @@ -0,0 +1,3 @@ +#FRIENDLYNAME "osmo-remsim-client" +#DEVICENAME 0:0:192.168.11.10:9998 +#LIBPATH @usbdropdir@/libifd-osmo-remsim-client.bundle/Contents/Linux/libifd_remsim_client.so diff --git a/src/client/user_ifdhandler.c b/src/client/user_ifdhandler.c new file mode 100644 index 0000000..677b781 --- /dev/null +++ b/src/client/user_ifdhandler.c @@ -0,0 +1,936 @@ +/* (C) 2020 by Harald Welte + * + * 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. + * + * 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. + * + */ + +/* 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 +#include +#include + +#include +#include +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 runningi 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 *name; + const char *server_host; + int server_port; + int client_id; + int client_slot; + int it_sock_fd; +}; + +/* enqueue a msgb (containg '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; +} + +/*********************************************************************** + * Incoming RSPRO messages from bank-daemon (SIM card) + ***********************************************************************/ + +static int bankd_handle_tpduCardToModem(struct bankd_client *bc, const RsproPDU_t *pdu) +{ + const struct TpduCardToModem *card2modem; + struct client_thread *ct = bc->data; + struct msgb *msg; + + OSMO_ASSERT(pdu); + OSMO_ASSERT(RsproPDUchoice_PR_tpduCardToModem == pdu->msg.present); + + card2modem = &pdu->msg.choice.tpduCardToModem; + DEBUGP(DMAIN, "R-APDU: %s\n", osmo_hexdump(card2modem->data.buf, card2modem->data.size)); + /* enqueue towards IFD thread */ + msg = itmsg_alloc(ITMSG_TYPE_R_APDU_IND, 0, card2modem->data.buf, card2modem->data.size); + OSMO_ASSERT(msg); + enqueue_to_ifd(ct, msg); + + return 0; +} + +static int bankd_handle_setAtrReq(struct bankd_client *bc, const RsproPDU_t *pdu) +{ + struct client_thread *ct = bc->data; + RsproPDU_t *resp; + unsigned int atr_len; + + OSMO_ASSERT(pdu); + OSMO_ASSERT(RsproPDUchoice_PR_setAtrReq == pdu->msg.present); + + DEBUGP(DMAIN, "SET_ATR: %s\n", osmo_hexdump(pdu->msg.choice.setAtrReq.atr.buf, + pdu->msg.choice.setAtrReq.atr.size)); + + /* store ATR in local data structure until somebody needs it */ + atr_len = pdu->msg.choice.setAtrReq.atr.size; + if (atr_len > sizeof(ct->atr)) + atr_len = sizeof(ct->atr); + memcpy(ct->atr, pdu->msg.choice.setAtrReq.atr.buf, atr_len); + ct->atr_len = atr_len; + + resp = rspro_gen_SetAtrRes(ResultCode_ok); + if (!resp) + return -ENOMEM; + server_conn_send_rspro(&bc->bankd_conn, resp); + + return 0; +} + + +int client_user_bankd_handle_rx(struct rspro_server_conn *bankdc, const RsproPDU_t *pdu) +{ + struct bankd_client *bc = bankdc2bankd_client(bankdc); + + switch (pdu->msg.present) { + case RsproPDUchoice_PR_tpduCardToModem: + bankd_handle_tpduCardToModem(bc, pdu); + break; + case RsproPDUchoice_PR_setAtrReq: + bankd_handle_setAtrReq(bc, pdu); + break; + default: + OSMO_ASSERT(0); + } + return 0; +} + +/*********************************************************************** + * 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_thread *ct; + int rc; + + osmo_select_init(); + rc = osmo_ctx_init("client"); + OSMO_ASSERT(rc == 0); + + ct = talloc_zero(OTC_GLOBAL, struct client_thread); + OSMO_ASSERT(ct); + + if (!talloc_asn1_ctx) + talloc_asn1_ctx= talloc_named_const(ct, 0, "asn1"); + + ct->bc = remsim_client_create(ct, cfg->name, "remsim_ifdhandler"); + OSMO_ASSERT(ct->bc); + ct->bc->data = ct; + remsim_client_set_clslot(ct->bc, cfg->client_id, cfg->client_slot); + if (cfg->server_host) + ct->bc->srv_conn.server_host = (char *) cfg->server_host; + if (cfg->server_port >= 0) + ct->bc->srv_conn.server_port = cfg->server_port; + + 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 +#include + +#include +#include + +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 = { + .name = "fixme-name", + .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); +}