diff --git a/scd/ChangeLog b/scd/ChangeLog index 895aae966..9b04d5e78 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,3 +1,57 @@ +2004-04-20 Werner Koch + + * pcsc-wrapper.c: New. + * Makefile.am (pkglib_PROGRAMS): Install it here. + * apdu.c (writen, readn): New. + (open_pcsc_reader, pcsc_send_apdu, close_pcsc_reader): Use the + pcsc-wrapper if we are using Pth. + +2004-04-19 Werner Koch + + * ccid-driver.c (parse_ccid_descriptor): Store some of the reader + features away. New arg HANDLE + (read_device_info): New arg HANDLE. Changed caller. + (bulk_in): Handle time extension requests. + (ccid_get_atr): Setup parameters and the IFSD. + (compute_edc): New. Factored out code. + (ccid_transceive): Use default NADs when required. + +2004-04-14 Werner Koch + + * scdaemon.h (server_control_s): Add member READER_SLOT. + * scdaemon.c (scd_init_default_ctrl): Initialize READER_SLOT to -1. + * command.c (open_card): Reuse an open slot. + (reset_notify): Just reset the slot if supported by the reader. + (do_reset): Factored code from above out. + (scd_command_handler): Use it for cleanup. + + * apdu.h: New pseudo stati SW_HOST_NOT_SUPPORTED, + SW_HOST_LOCKING_FAILED and SW_HOST_BUSY. + * iso7816.c (map_sw): Map it. + + * ccid-driver.c (ccid_slot_status): Add arg STATUSBITS. + * apdu.c (apdu_get_status): New. + (ct_get_status, pcsc_get_status, ocsc_get_status): New stubs. + (get_status_ccid): New. + (apdu_reset): New. + (reset_ct_reader, reset_pcsc_reader, reset_osc_reader): New stubs. + (reset_ccid_reader): New. + (apdu_enum_reader): New. + + * apdu.c (lock_slot, trylock_slot, unlock_slot): New helpers. + (new_reader_slot) [USE_GNU_PTH]: Init mutex. + (apdu_reset, apdu_get_status, apdu_send_le): Run functions + in locked mode. + + * command.c (scd_update_reader_status_file): New. + * scdaemon.c (handle_tick): Call it. + +2004-04-13 Werner Koch + + * scdaemon.c: Convert to a Pth application. + (handle_signal, ticker_thread, handle_tick): New. + (main): Fire up the ticker thread in server mode. + 2004-03-23 Werner Koch * scdaemon.c (main) : Fixed output for pcsc_driver. diff --git a/scd/Makefile.am b/scd/Makefile.am index da59a1997..c3c603d28 100644 --- a/scd/Makefile.am +++ b/scd/Makefile.am @@ -19,13 +19,14 @@ ## Process this file with automake to produce Makefile.in bin_PROGRAMS = scdaemon sc-investigate sc-copykeys +pkglib_PROGRAMS = pcsc-wrapper AM_CPPFLAGS = -I$(top_srcdir)/intl -I$(top_srcdir)/common include $(top_srcdir)/am/cmacros.am AM_CFLAGS = $(OPENSC_CFLAGS) $(LIBGCRYPT_CFLAGS) \ - $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) + $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(PTH_CFLAGS) card_apps = app-openpgp.c app-nks.c app-dinsig.c @@ -43,7 +44,8 @@ scdaemon_SOURCES = \ scdaemon_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a \ - $(OPENSC_LIBS) $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) \ + $(OPENSC_LIBS) $(LIBGCRYPT_LIBS) $(PTH_LIBS) \ + $(KSBA_LIBS) $(LIBASSUAN_LIBS) \ $(LIBUSB_LIBS) -lgpg-error @INTLLIBS@ -ldl sc_investigate_SOURCES = \ @@ -57,7 +59,8 @@ sc_investigate_SOURCES = \ sc_investigate_LDADD = \ ../jnlib/libjnlib.a ../common/libcommon.a \ - $(OPENSC_LIBS) $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBUSB_LIBS) \ + $(OPENSC_LIBS) $(LIBGCRYPT_LIBS) $(PTH_LIBS) \ + $(KSBA_LIBS) $(LIBUSB_LIBS) \ @INTLLIBS@ -lgpg-error -ldl @@ -73,5 +76,10 @@ sc_copykeys_SOURCES = \ sc_copykeys_LDADD = \ ../jnlib/libjnlib.a ../common/libcommon.a \ ../common/libsimple-pwquery.a \ - $(OPENSC_LIBS) $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBUSB_LIBS) \ + $(OPENSC_LIBS) $(LIBGCRYPT_LIBS) $(PTH_LIBS) \ + $(KSBA_LIBS) $(LIBUSB_LIBS) \ -lgpg-error @INTLLIBS@ -ldl + +pcsc_wrapper_SOURCES = pcsc-wrapper.c +pcsc_wrapper_LDADD = -ldl +pcsc_wrapper_CFLAGS = \ No newline at end of file diff --git a/scd/apdu.c b/scd/apdu.c index 7843fd566..1b5ebc375 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -1,5 +1,5 @@ /* apdu.c - ISO 7816 APDU functions and low level I/O - * Copyright (C) 2003 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -24,6 +24,11 @@ #include #include #include +#ifdef USE_GNU_PTH +# include +# include +# include +#endif #ifdef HAVE_OPENSC # include #endif @@ -48,6 +53,11 @@ #include "dynload.h" #include "ccid-driver.h" +#ifdef USE_GNU_PTH +#define NEED_PCSC_WRAPPER 1 +#endif + + #define MAX_READER 4 /* Number of readers we support concurrently. */ #define CARD_CONNECT_TIMEOUT 1 /* Number of seconds to wait for insertion of the card (1 = don't wait). */ @@ -59,6 +69,12 @@ #define DLSTDCALL #endif +#ifdef _POSIX_OPEN_MAX +#define MAX_OPEN_FDS _POSIX_OPEN_MAX +#else +#define MAX_OPEN_FDS 20 +#endif + /* A structure to collect information pertaining to one reader slot. */ @@ -74,6 +90,11 @@ struct reader_table_s { unsigned long context; unsigned long card; unsigned long protocol; +#ifdef NEED_PCSC_WRAPPER + int req_fd; + int rsp_fd; + pid_t pid; +#endif /*NEED_PCSC_WRAPPER*/ } pcsc; #ifdef HAVE_OPENSC int is_osc; /* We are using the OpenSC driver layer. */ @@ -85,6 +106,11 @@ struct reader_table_s { int status; unsigned char atr[33]; size_t atrlen; + unsigned int change_counter; +#ifdef USE_GNU_PTH + int lock_initialized; + pth_mutex_t lock; +#endif }; typedef struct reader_table_s *reader_table_t; @@ -185,11 +211,27 @@ new_reader_slot (void) log_error ("new_reader_slot: out of slots\n"); return -1; } +#ifdef USE_GNU_PTH + if (!reader_table[reader].lock_initialized) + { + if (!pth_mutex_init (&reader_table[reader].lock)) + { + log_error ("error initializing mutex: %s\n", strerror (errno)); + return -1; + } + reader_table[reader].lock_initialized = 1; + } +#endif /*USE_GNU_PTH*/ reader_table[reader].used = 1; reader_table[reader].is_ccid = 0; reader_table[reader].is_ctapi = 0; #ifdef HAVE_OPENSC reader_table[reader].is_osc = 0; +#endif +#ifdef NEED_PCSC_WRAPPER + reader_table[reader].pcsc.req_fd = -1; + reader_table[reader].pcsc.rsp_fd = -1; + reader_table[reader].pcsc.pid = (pid_t)(-1); #endif return reader; } @@ -370,6 +412,18 @@ close_ct_reader (int slot) return 0; } +static int +reset_ct_reader (int slot) +{ + return SW_HOST_NOT_SUPPORTED; +} + + +static int +ct_get_status (int slot, unsigned int *status) +{ + return SW_HOST_NOT_SUPPORTED; +} /* Actually send the APDU of length APDULEN to SLOT and return a maximum of *BUFLEN data in BUFFER, the actual retruned size will be @@ -397,6 +451,66 @@ ct_send_apdu (int slot, unsigned char *apdu, size_t apdulen, +#ifdef NEED_PCSC_WRAPPER +static int +writen (int fd, const void *buf, size_t nbytes) +{ + size_t nleft = nbytes; + int nwritten; + +/* log_printhex (" writen:", buf, nbytes); */ + + while (nleft > 0) + { +#ifdef USE_GNU_PTH + nwritten = pth_write (fd, buf, nleft); +#else + nwritten = write (fd, buf, nleft); +#endif + if (nwritten < 0 && errno == EINTR) + continue; + if (nwritten < 0) + return -1; + nleft -= nwritten; + buf = (const char*)buf + nwritten; + } + return 0; +} + +/* Read up to BUFLEN bytes from FD and return the number of bytes + actually read in NREAD. Returns -1 on error or 0 on success. */ +static int +readn (int fd, void *buf, size_t buflen, size_t *nread) +{ + size_t nleft = buflen; + int n; +/* void *orig_buf = buf; */ + + while (nleft > 0) + { +#ifdef USE_GNU_PTH + n = pth_read (fd, buf, nleft); +#else + n = read (fd, buf, nleft); +#endif + if (n < 0 && errno == EINTR) + continue; + if (n < 0) + return -1; /* read error. */ + if (!n) + break; /* EOF */ + nleft -= n; + buf = (char*)buf + n; + } + if (nread) + *nread = buflen - nleft; + +/* log_printhex (" readn:", orig_buf, *nread); */ + + return 0; +} +#endif /*NEED_PCSC_WRAPPER*/ + static const char * pcsc_error_string (long err) { @@ -457,6 +571,172 @@ pcsc_error_string (long err) static int open_pcsc_reader (const char *portstr) { +#ifdef NEED_PCSC_WRAPPER +/* Open the PC/SC reader using the pcsc_wrapper program. This is + needed to cope with different thread models and other peculiarities + of libpcsclite. */ + int slot; + reader_table_t slotp; + int fd, rp[2], wp[2]; + int n, i; + pid_t pid; + size_t len; + unsigned char msgbuf[9]; + int err; + + slot = new_reader_slot (); + if (slot == -1) + return -1; + slotp = reader_table + slot; + + /* Fire up the pcsc wrapper. We don't use any fork/exec code from + the common directy but implement it direclty so that this file + may still be source copied. */ + + if (pipe (rp) == -1) + { + log_error ("error creating a pipe: %s\n", strerror (errno)); + slotp->used = 0; + return -1; + } + if (pipe (wp) == -1) + { + log_error ("error creating a pipe: %s\n", strerror (errno)); + close (rp[0]); + close (rp[1]); + slotp->used = 0; + return -1; + } + + pid = fork (); + if (pid == -1) + { + log_error ("error forking process: %s\n", strerror (errno)); + close (rp[0]); + close (rp[1]); + close (wp[0]); + close (wp[1]); + slotp->used = 0; + return -1; + } + slotp->pcsc.pid = pid; + + if (!pid) + { /* + === Child === + */ + + /* Double fork. */ + pid = fork (); + if (pid == -1) + _exit (31); + if (pid) + _exit (0); /* Immediate exit this parent, so that the child + gets cleaned up by the init process. */ + + /* Connect our pipes. */ + if (wp[0] != 0 && dup2 (wp[0], 0) == -1) + log_fatal ("dup2 stdin failed: %s\n", strerror (errno)); + if (rp[1] != 1 && dup2 (rp[1], 1) == -1) + log_fatal ("dup2 stdout failed: %s\n", strerror (errno)); + + /* Send stderr to the bit bucket. */ + fd = open ("/dev/null", O_WRONLY); + if (fd == -1) + log_fatal ("can't open `/dev/null': %s", strerror (errno)); + if (fd != 2 && dup2 (fd, 2) == -1) + log_fatal ("dup2 stderr failed: %s\n", strerror (errno)); + + /* Close all other files. */ + n = sysconf (_SC_OPEN_MAX); + if (n < 0) + n = MAX_OPEN_FDS; + for (i=3; i < n; i++) + close(i); + errno = 0; + + execl (GNUPG_LIBDIR "/pcsc-wrapper", + "pcsc-wrapper", + "--", + "1", /* API version */ + opt.pcsc_driver, /* Name of the PC/SC library. */ + NULL); + _exit (31); + } + + /* + === Parent === + */ + close (wp[0]); + close (rp[1]); + slotp->pcsc.req_fd = wp[1]; + slotp->pcsc.rsp_fd = rp[0]; + + /* Wait for the intermediate child to terminate. */ + while ( (i=pth_waitpid (pid, NULL, 0)) == -1 && errno == EINTR) + ; + + /* Now send the open request. */ + msgbuf[0] = 0x01; /* OPEN command. */ + len = portstr? strlen (portstr):0; + msgbuf[1] = (len >> 24); + msgbuf[2] = (len >> 16); + msgbuf[3] = (len >> 8); + msgbuf[4] = (len ); + if ( writen (slotp->pcsc.req_fd, msgbuf, 5) + || (portstr && writen (slotp->pcsc.req_fd, portstr, len))) + { + log_error ("error sending PC/SC OPEN request: %s\n", + strerror (errno)); + goto command_failed; + } + /* Read the response. */ + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9) + { + log_error ("error receiving PC/SC OPEN response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4]; + if (msgbuf[0] != 0x81 || len < 4) + { + log_error ("invalid response header from PC/SC received\n"); + goto command_failed; + } + len -= 4; /* Already read the error code. */ + if (len > DIM (slotp->atr)) + { + log_error ("PC/SC returned a too large ATR (len=%x)\n", len); + goto command_failed; + } + err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + if (err) + { + log_error ("PC/SC OPEN failed: %s\n", pcsc_error_string (err)); + goto command_failed; + } + n = len; + if ((i=readn (slotp->pcsc.rsp_fd, slotp->atr, n, &len)) || len != n) + { + log_error ("error receiving PC/SC OPEN response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + slotp->atrlen = len; + + dump_reader_status (slot); + return slot; + + command_failed: + close (slotp->pcsc.req_fd); + close (slotp->pcsc.rsp_fd); + slotp->pcsc.req_fd = -1; + slotp->pcsc.rsp_fd = -1; + kill (slotp->pcsc.pid, SIGTERM); + slotp->pcsc.pid = (pid_t)(-1); + slotp->used = 0; + return -1; +#else /*!NEED_PCSC_WRAPPER */ long err; int slot; char *list = NULL; @@ -559,9 +839,16 @@ open_pcsc_reader (const char *portstr) dump_reader_status (slot); return slot; +#endif /*!NEED_PCSC_WRAPPER */ } +static int +pcsc_get_status (int slot, unsigned int *status) +{ + return SW_HOST_NOT_SUPPORTED; +} + /* Actually send the APDU of length APDULEN to SLOT and return a maximum of *BUFLEN data in BUFFER, the actual returned size will be set to BUFLEN. Returns: CT API error code. */ @@ -569,6 +856,108 @@ static int pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, unsigned char *buffer, size_t *buflen) { +#ifdef NEED_PCSC_WRAPPER + long err; + reader_table_t slotp; + size_t len, full_len; + int i, n; + unsigned char msgbuf[9]; + + if (DBG_CARD_IO) + log_printhex (" PCSC_data:", apdu, apdulen); + + slotp = reader_table + slot; + + if (slotp->pcsc.req_fd == -1 + || slotp->pcsc.rsp_fd == -1 + || slotp->pcsc.pid == (pid_t)(-1) ) + { + log_error ("pcsc_send_apdu: pcsc-wrapper not running\n"); + return -1; + } + + msgbuf[0] = 0x03; /* TRANSMIT command. */ + len = apdulen; + msgbuf[1] = (len >> 24); + msgbuf[2] = (len >> 16); + msgbuf[3] = (len >> 8); + msgbuf[4] = (len ); + if ( writen (slotp->pcsc.req_fd, msgbuf, 5) + || writen (slotp->pcsc.req_fd, apdu, len)) + { + log_error ("error sending PC/SC TRANSMIT request: %s\n", + strerror (errno)); + goto command_failed; + } + + /* Read the response. */ + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9) + { + log_error ("error receiving PC/SC TRANSMIT response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4]; + if (msgbuf[0] != 0x81 || len < 4) + { + log_error ("invalid response header from PC/SC received\n"); + goto command_failed; + } + len -= 4; /* Already read the error code. */ + err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + if (err) + { + log_error ("pcsc_transmit failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return -1; + } + + full_len = len; + + n = *buflen < len ? *buflen : len; + if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) || len != n) + { + log_error ("error receiving PC/SC TRANSMIT response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + *buflen = n; + full_len -= len; + if (full_len) + { + log_error ("pcsc_send_apdu: provided buffer too short - truncated\n"); + err = -1; + } + /* We need to read any rest of the response, to keep the + protocol runnng. */ + while (full_len) + { + unsigned char dummybuf[128]; + + n = full_len < DIM (dummybuf) ? full_len : DIM (dummybuf); + if ((i=readn (slotp->pcsc.rsp_fd, dummybuf, n, &len)) || len != n) + { + log_error ("error receiving PC/SC TRANSMIT response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + full_len -= n; + } + + return err; + + command_failed: + close (slotp->pcsc.req_fd); + close (slotp->pcsc.rsp_fd); + slotp->pcsc.req_fd = -1; + slotp->pcsc.rsp_fd = -1; + kill (slotp->pcsc.pid, SIGTERM); + slotp->pcsc.pid = (pid_t)(-1); + slotp->used = 0; + return -1; + +#else /*!NEED_PCSC_WRAPPER*/ + long err; struct pcsc_io_request_s send_pci; unsigned long recv_len; @@ -591,14 +980,87 @@ pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, pcsc_error_string (err), err); return err? -1:0; /* FIXME: Return appropriate error code. */ +#endif /*!NEED_PCSC_WRAPPER*/ } + static int close_pcsc_reader (int slot) { +#ifdef NEED_PCSC_WRAPPER + long err; + reader_table_t slotp; + size_t len; + int i; + unsigned char msgbuf[9]; + + slotp = reader_table + slot; + + if (slotp->pcsc.req_fd == -1 + || slotp->pcsc.rsp_fd == -1 + || slotp->pcsc.pid == (pid_t)(-1) ) + { + log_error ("close_pcsc_reader: pcsc-wrapper not running\n"); + return 0; + } + + msgbuf[0] = 0x02; /* CLOSE command. */ + len = 0; + msgbuf[1] = (len >> 24); + msgbuf[2] = (len >> 16); + msgbuf[3] = (len >> 8); + msgbuf[4] = (len ); + if ( writen (slotp->pcsc.req_fd, msgbuf, 5) ) + { + log_error ("error sending PC/SC CLOSE request: %s\n", + strerror (errno)); + goto command_failed; + } + + /* Read the response. */ + if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9) + { + log_error ("error receiving PC/SC CLOSE response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4]; + if (msgbuf[0] != 0x81 || len < 4) + { + log_error ("invalid response header from PC/SC received\n"); + goto command_failed; + } + len -= 4; /* Already read the error code. */ + err = (msgbuf[5] << 24) | (msgbuf[6] << 16) | (msgbuf[7] << 8 ) | msgbuf[8]; + if (err) + log_error ("pcsc_close failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + + /* We will the wrapper in any case - errors are merely + informational. */ + + command_failed: + close (slotp->pcsc.req_fd); + close (slotp->pcsc.rsp_fd); + slotp->pcsc.req_fd = -1; + slotp->pcsc.rsp_fd = -1; + kill (slotp->pcsc.pid, SIGTERM); + slotp->pcsc.pid = (pid_t)(-1); + slotp->used = 0; + return 0; + +#else /*!NEED_PCSC_WRAPPER*/ + pcsc_release_context (reader_table[slot].pcsc.context); reader_table[slot].used = 0; return 0; +#endif /*!NEED_PCSC_WRAPPER*/ +} + +static int +reset_pcsc_reader (int slot) +{ + return SW_HOST_NOT_SUPPORTED; } @@ -661,6 +1123,46 @@ close_ccid_reader (int slot) } +static int +reset_ccid_reader (int slot) +{ + int err; + reader_table_t slotp = reader_table + slot; + unsigned char atr[33]; + size_t atrlen; + + err = ccid_get_atr (slotp->ccid.handle, atr, sizeof atr, &atrlen); + if (err) + return -1; + /* If the reset was successful, update the ATR. */ + assert (sizeof slotp->atr >= sizeof atr); + slotp->atrlen = atrlen; + memcpy (slotp->atr, atr, atrlen); + dump_reader_status (slot); + return 0; +} + + +static int +get_status_ccid (int slot, unsigned int *status) +{ + int rc; + int bits; + + rc = ccid_slot_status (reader_table[slot].ccid.handle, &bits); + if (rc) + return -1; + + if (bits == 0) + *status = 1|2|4; + else if (bits == 1) + *status = 2; + else + *status = 0; + + return 0; +} + /* Actually send the APDU of length APDULEN to SLOT and return a maximum of *BUFLEN data in BUFFER, the actual returned size will be @@ -798,6 +1300,18 @@ close_osc_reader (int slot) return 0; } +static int +reset_osc_reader (int slot) +{ + return SW_HOST_NOT_SUPPORTED; +} + + +static int +ocsc_get_status (int slot, unsigned int *status) +{ + return SW_HOST_NOT_SUPPORTED; +} /* Actually send the APDU of length APDULEN to SLOT and return a @@ -896,6 +1410,45 @@ osc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, Driver Access */ + +static int +lock_slot (int slot) +{ +#ifdef USE_GNU_PTH + if (!pth_mutex_acquire (&reader_table[slot].lock, 0, NULL)) + { + log_error ("failed to acquire apdu lock: %s\n", strerror (errno)); + return SW_HOST_LOCKING_FAILED; + } +#endif /*USE_GNU_PTH*/ + return 0; +} + +static int +trylock_slot (int slot) +{ +#ifdef USE_GNU_PTH + if (!pth_mutex_acquire (&reader_table[slot].lock, TRUE, NULL)) + { + if (errno == EBUSY) + return SW_HOST_BUSY; + log_error ("failed to acquire apdu lock: %s\n", strerror (errno)); + return SW_HOST_LOCKING_FAILED; + } +#endif /*USE_GNU_PTH*/ + return 0; +} + +static void +unlock_slot (int slot) +{ +#ifdef USE_GNU_PTH + if (!pth_mutex_release (&reader_table[slot].lock)) + log_error ("failed to release apdu lock: %s\n", strerror (errno)); +#endif /*USE_GNU_PTH*/ +} + + /* Open the reader and return an internal slot number or -1 on error. If PORTSTR is NULL we default to a suitable port (for ctAPI: the first USB reader. For PC/SC the first listed reader). If @@ -937,7 +1490,7 @@ apdu_open_reader (const char *portstr) handle = dlopen (opt.ctapi_driver, RTLD_LAZY); if (!handle) { - log_error ("apdu_open_reader: failed to open driver: %s", + log_error ("apdu_open_reader: failed to open driver: %s\n", dlerror ()); return -1; } @@ -959,12 +1512,13 @@ apdu_open_reader (const char *portstr) /* No ctAPI configured, so lets try the PC/SC API */ if (!pcsc_api_loaded) { +#ifndef NEED_PCSC_WRAPPER void *handle; handle = dlopen (opt.pcsc_driver, RTLD_LAZY); if (!handle) { - log_error ("apdu_open_reader: failed to open driver `%s': %s", + log_error ("apdu_open_reader: failed to open driver `%s': %s\n", opt.pcsc_driver, dlerror ()); return -1; } @@ -1020,9 +1574,10 @@ apdu_open_reader (const char *portstr) dlclose (handle); return -1; } +#endif /*!NEED_PCSC_WRAPPER*/ pcsc_api_loaded = 1; } - + return open_pcsc_reader (portstr); } @@ -1046,6 +1601,47 @@ apdu_close_reader (int slot) return close_pcsc_reader (slot); } +/* Enumerate all readers and return information on whether this reader + is in use. The caller should start with SLOT set to 0 and + increment it with each call until an error is returned. */ +int +apdu_enum_reader (int slot, int *used) +{ + if (slot < 0 || slot >= MAX_READER) + return SW_HOST_NO_DRIVER; + *used = reader_table[slot].used; + return 0; +} + +/* Do a reset for the card in reader at SLOT. */ +int +apdu_reset (int slot) +{ + int sw; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if ((sw = lock_slot (slot))) + return sw; + + if (reader_table[slot].is_ctapi) + sw = reset_ct_reader (slot); +#ifdef HAVE_LIBUSB + else if (reader_table[slot].is_ccid) + sw = reset_ccid_reader (slot); +#endif +#ifdef HAVE_OPENSC + else if (reader_table[slot].is_osc) + sw = reset_osc_reader (slot); +#endif + else + sw = reset_pcsc_reader (slot); + + unlock_slot (slot); + return sw; +} + unsigned char * apdu_get_atr (int slot, size_t *atrlen) @@ -1062,6 +1658,7 @@ apdu_get_atr (int slot, size_t *atrlen) *atrlen = reader_table[slot].atrlen; return buf; } + static const char * @@ -1084,7 +1681,62 @@ error_string (int slot, long rc) } -/* Dispatcher for the actual send_apdu fucntion. */ +/* Retrieve the status for SLOT. The function does obnly wait fot the + card to become available if HANG is set to true. On success the + bits in STATUS will be set to + + bit 0 = card present and usable + bit 1 = card present + bit 2 = card active + bit 3 = card access locked [not yet implemented] + + For must application, tetsing bit 0 is sufficient. + + CHANGED will receive the value of the counter tracking the number + of card insertions. This value may be used to detect a card + change. +*/ +int +apdu_get_status (int slot, int hang, + unsigned int *status, unsigned int *changed) +{ + int sw; + unsigned int s; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + + if ((sw = hang? lock_slot (slot) : trylock_slot (slot))) + return sw; + + if (reader_table[slot].is_ctapi) + sw = ct_get_status (slot, &s); +#ifdef HAVE_LIBUSB + else if (reader_table[slot].is_ccid) + sw = get_status_ccid (slot, &s); +#endif +#ifdef HAVE_OPENSC + else if (reader_table[slot].is_osc) + sw = osc_get_status (slot, &s); +#endif + else + sw = pcsc_get_status (slot, &s); + + unlock_slot (slot); + + if (sw) + return sw; + + if (status) + *status = s; + if (changed) + *changed = reader_table[slot].change_counter; + return 0; +} + + +/* Dispatcher for the actual send_apdu function. Note, that this + function should be called in locked state. */ static int send_apdu (int slot, unsigned char *apdu, size_t apdulen, unsigned char *buffer, size_t *buflen) @@ -1126,6 +1778,9 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, int sw; long rc; /* we need a long here due to PC/SC. */ + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return SW_HOST_NO_DRIVER; + if (DBG_CARD_IO) log_debug ("send apdu: c=%02X i=%02X p0=%02X p1=%02X lc=%d le=%d\n", class, ins, p0, p1, lc, le); @@ -1137,6 +1792,9 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, if ((!data && lc != -1) || (data && lc == -1)) return SW_HOST_INV_VALUE; + if ((sw = lock_slot (slot))) + return sw; + apdulen = 0; apdu[apdulen++] = class; apdu[apdulen++] = ins; @@ -1158,6 +1816,7 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, { log_error ("apdu_send_simple(%d) failed: %s\n", slot, error_string (slot, rc)); + unlock_slot (slot); return SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; @@ -1176,7 +1835,10 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, { *retbuf = xtrymalloc (resultlen? resultlen : 1); if (!*retbuf) - return SW_HOST_OUT_OF_CORE; + { + unlock_slot (slot); + return SW_HOST_OUT_OF_CORE; + } *retbuflen = resultlen; memcpy (*retbuf, result, resultlen); } @@ -1192,7 +1854,10 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, { *retbuf = p = xtrymalloc (bufsize); if (!*retbuf) - return SW_HOST_OUT_OF_CORE; + { + unlock_slot (slot); + return SW_HOST_OUT_OF_CORE; + } assert (resultlen < bufsize); memcpy (p, result, resultlen); p += resultlen; @@ -1216,6 +1881,7 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, { log_error ("apdu_send_simple(%d) for get response failed: %s\n", slot, error_string (slot, rc)); + unlock_slot (slot); return SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; @@ -1236,7 +1902,10 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, bufsize += resultlen > 4096? resultlen: 4096; tmp = xtryrealloc (*retbuf, bufsize); if (!tmp) - return SW_HOST_OUT_OF_CORE; + { + unlock_slot (slot); + return SW_HOST_OUT_OF_CORE; + } p = tmp + (p - *retbuf); *retbuf = tmp; } @@ -1259,6 +1928,9 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, *retbuf = tmp; } } + + unlock_slot (slot); + if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS) log_printhex (" dump: ", *retbuf, *retbuflen); diff --git a/scd/apdu.h b/scd/apdu.h index fd7634f13..f74bab7fe 100644 --- a/scd/apdu.h +++ b/scd/apdu.h @@ -48,9 +48,12 @@ enum { those values can't be issued by a card. */ SW_HOST_OUT_OF_CORE = 0x10001, /* No way yet to differentiate between errnos on a failed malloc. */ - SW_HOST_INV_VALUE = 0x10002, + SW_HOST_INV_VALUE = 0x10002, SW_HOST_INCOMPLETE_CARD_RESPONSE = 0x10003, - SW_HOST_NO_DRIVER = 0x10004 + SW_HOST_NO_DRIVER = 0x10004, + SW_HOST_NOT_SUPPORTED = 0x10005, + SW_HOST_LOCKING_FAILED= 0x10006, + SW_HOST_BUSY = 0x10007 }; @@ -58,10 +61,14 @@ enum { /* Note , that apdu_open_reader returns no status word but -1 on error. */ int apdu_open_reader (const char *portstr); int apdu_close_reader (int slot); +int apdu_enum_reader (int slot, int *used); unsigned char *apdu_get_atr (int slot, size_t *atrlen); /* The apdu send functions do return status words. */ +int apdu_reset (int slot); +int apdu_get_status (int slot, int hang, + unsigned int *status, unsigned int *changed); int apdu_send_simple (int slot, int class, int ins, int p0, int p1, int lc, const char *data); int apdu_send (int slot, int class, int ins, int p0, int p1, diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c index b398e3ce3..1c8167869 100644 --- a/scd/ccid-driver.c +++ b/scd/ccid-driver.c @@ -1,5 +1,5 @@ /* ccid-driver.c - USB ChipCardInterfaceDevices driver - * Copyright (C) 2003 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. * Written by Werner Koch. * * This file is part of GnuPG. @@ -108,7 +108,10 @@ /* Disable all debugging output for now. */ #undef DBG_CARD_IO -#define DBG_CARD_IO 0 +#define DBG_CARD_IO 1 + +/* Define to print information pertaining the T=1 protocol. */ +#undef DEBUG_T1 # define DEBUGOUT(t) do { if (DBG_CARD_IO) \ @@ -145,8 +148,6 @@ #endif /* This source not used by scdaemon. */ -/* Define to print information pertaining the T=1 protocol. */ -#undef DEBUG_T1 @@ -184,9 +185,15 @@ struct ccid_driver_s { int seqno; unsigned char t1_ns; unsigned char t1_nr; + int nonnull_nad; + int auto_ifsd; + int max_ifsd; + int ifsd; }; +static unsigned int compute_edc (const unsigned char *data, size_t datalen, + int use_crc); static int bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen); static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, size_t *nread, int expected_type, int seqno); @@ -220,13 +227,18 @@ set_msg_len (unsigned char *msg, unsigned int length) Note, that this code is based on the one in lsusb.c of the usb-utils package, I wrote on 2003-09-01. -wk. */ static int -parse_ccid_descriptor (const unsigned char *buf, size_t buflen) +parse_ccid_descriptor (ccid_driver_t handle, + const unsigned char *buf, size_t buflen) { unsigned int i; unsigned int us; int have_t1 = 0, have_tpdu=0, have_auto_conf = 0; + handle->nonnull_nad = 0; + handle->auto_ifsd = 0; + handle->max_ifsd = 32; + handle->ifsd = 0; if (buflen < 54 || buf[0] < 54) { DEBUGOUT ("CCID device descriptor is too short\n"); @@ -272,6 +284,7 @@ parse_ccid_descriptor (const unsigned char *buf, size_t buflen) us = convert_le_u32(buf+28); DEBUGOUT_1 (" dwMaxIFSD %5u\n", us); + handle->max_ifsd = us; us = convert_le_u32(buf+32); DEBUGOUT_1 (" dwSyncProtocols %08X ", us); @@ -320,9 +333,15 @@ parse_ccid_descriptor (const unsigned char *buf, size_t buflen) if ((us & 0x0100)) DEBUGOUT (" CCID can set ICC in clock stop mode\n"); if ((us & 0x0200)) - DEBUGOUT (" NAD value other than 0x00 accpeted\n"); + { + DEBUGOUT (" NAD value other than 0x00 accepted\n"); + handle->nonnull_nad = 1; + } if ((us & 0x0400)) - DEBUGOUT (" Auto IFSD exchange\n"); + { + DEBUGOUT (" Auto IFSD exchange\n"); + handle->auto_ifsd = 1; + } if ((us & 0x00010000)) { @@ -389,7 +408,7 @@ parse_ccid_descriptor (const unsigned char *buf, size_t buflen) that the device is usable for us. Returns 0 on success or an error code. */ static int -read_device_info (struct usb_device *dev) +read_device_info (ccid_driver_t handle, struct usb_device *dev) { int cfg_no; @@ -414,8 +433,9 @@ read_device_info (struct usb_device *dev) { if (ifcdesc->extra) { - if (!parse_ccid_descriptor (ifcdesc->extra, - ifcdesc->extralen)) + if (!parse_ccid_descriptor (handle, + ifcdesc->extra, + ifcdesc->extralen)) return 0; /* okay. we can use it. */ } } @@ -458,10 +478,22 @@ ccid_open_reader (ccid_driver_t *handle, int readerno) dev->descriptor->idVendor, dev->descriptor->idProduct); if (!readerno) { - rc = read_device_info (dev); + *handle = calloc (1, sizeof **handle); + if (!*handle) + { + DEBUGOUT ("out of memory\n"); + rc = -1; + free (*handle); + *handle = NULL; + goto leave; + } + + rc = read_device_info (*handle, dev); if (rc) { DEBUGOUT ("device not supported\n"); + free (*handle); + *handle = NULL; goto leave; } @@ -469,6 +501,8 @@ ccid_open_reader (ccid_driver_t *handle, int readerno) if (rc) { DEBUGOUT_1 ("usb_open failed: %d\n", rc); + free (*handle); + *handle = NULL; goto leave; } @@ -479,16 +513,11 @@ ccid_open_reader (ccid_driver_t *handle, int readerno) if (rc) { DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc); + free (*handle); + *handle = NULL; goto leave; } - *handle = calloc (1, sizeof **handle); - if (!*handle) - { - DEBUGOUT ("out of memory\n"); - rc = -1; - goto leave; - } (*handle)->idev = idev; idev = NULL; /* FIXME: Do we need to get the endpoint addresses from the @@ -508,7 +537,7 @@ ccid_open_reader (ccid_driver_t *handle, int readerno) usb_free_match (match); if (!rc && !*handle) - rc = -1; /* In case we didn't enter the while lool at all. */ + rc = -1; /* In case we didn't enter the while loop at all. */ return rc; } @@ -592,6 +621,7 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, int i, rc; size_t msglen; + retry: rc = usb_bulk_read (handle->idev, 0x82, buffer, length, @@ -628,6 +658,14 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length, return -1; } + if ( !(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80) + { + /* Card present and active, time extension requested. */ + DEBUGOUT_2 ("time extension requested (%02X,%02X)\n", + buffer[7], buffer[8]); + goto retry; + } + DEBUGOUT_3 ("status: %02X error: %02X octet[9]: %02X\n" " data:", buffer[7], buffer[8], buffer[9] ); for (i=10; i < msglen; i++) @@ -695,7 +733,7 @@ ccid_poll (ccid_driver_t handle) int -ccid_slot_status (ccid_driver_t handle) +ccid_slot_status (ccid_driver_t handle, int *statusbits) { int rc; unsigned char msg[100]; @@ -716,6 +754,7 @@ ccid_slot_status (ccid_driver_t handle) rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus, seqno); if (rc) return rc; + *statusbits = (msg[7] & 3); return 0; } @@ -727,8 +766,12 @@ ccid_get_atr (ccid_driver_t handle, { int rc; unsigned char msg[100]; - size_t msglen; + unsigned char *tpdu; + size_t msglen, tpdulen; unsigned char seqno; + int use_crc = 0; + unsigned int edc; + int i; msg[0] = PC_to_RDR_IccPowerOn; msg[5] = 0; /* slot */ @@ -756,11 +799,135 @@ ccid_get_atr (ccid_driver_t handle, *atrlen = n; } + /* Setup parameters to select T=1. */ + msg[0] = PC_to_RDR_SetParameters; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 1; /* Select T=1. */ + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + + /* FIXME: Get those values from the ATR. */ + msg[10]= 0x01; /* Fi/Di */ + msg[11]= 0x10; /* LRC, direct convention. */ + msg[12]= 0; /* Extra guardtime. */ + msg[13]= 0x41; /* BWI/CWI */ + msg[14]= 0; /* No clock stoppping. */ + msg[15]= 254; /* IFSC */ + msg[16]= 0; /* Does not support non default NAD values. */ + set_msg_len (msg, 7); + msglen = 10 + 7; + + DEBUGOUT ("sending"); + for (i=0; i < msglen; i++) + DEBUGOUT_CONT_1 (" %02X", msg[i]); + DEBUGOUT_LF (); + + rc = bulk_out (handle, msg, msglen); + if (rc) + return rc; + /* Note that we ignore the error code on purpose. */ + bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters, seqno); + + + /* Send an S-Block with our maximun IFSD to the CCID. */ + if (!handle->auto_ifsd) + { + tpdu = msg+10; + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; + tpdu[1] = (0xc0 | 0 | 1); /* S-block request: change IFSD */ + tpdu[2] = 1; + tpdu[3] = handle->max_ifsd? handle->max_ifsd : 32; + tpdulen = 4; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; + + msg[0] = PC_to_RDR_XfrBlock; + msg[5] = 0; /* slot */ + msg[6] = seqno = handle->seqno++; + msg[7] = 0; + msg[8] = 0; /* RFU */ + msg[9] = 0; /* RFU */ + set_msg_len (msg, tpdulen); + msglen = 10 + tpdulen; + + DEBUGOUT ("sending"); + for (i=0; i < msglen; i++) + DEBUGOUT_CONT_1 (" %02X", msg[i]); + DEBUGOUT_LF (); + +#ifdef DEBUG_T1 + fprintf (stderr, "T1: put %c-block seq=%d\n", + ((msg[11] & 0xc0) == 0x80)? 'R' : + (msg[11] & 0x80)? 'S' : 'I', + ((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40))); +#endif + + rc = bulk_out (handle, msg, msglen); + if (rc) + return rc; + + /* Fixme: The next line for the current Valgrid without support + for USB IOCTLs. */ + memset (msg, 0, sizeof msg); + + rc = bulk_in (handle, msg, sizeof msg, &msglen, + RDR_to_PC_DataBlock, seqno); + if (rc) + return rc; + + tpdu = msg + 10; + tpdulen = msglen - 10; + + if (tpdulen < 4) + { + DEBUGOUT ("cannot yet handle short blocks!\n"); + return -1; + } + +#ifdef DEBUG_T1 + fprintf (stderr, "T1: got %c-block seq=%d err=%d\n", + ((msg[11] & 0xc0) == 0x80)? 'R' : + (msg[11] & 0x80)? 'S' : 'I', + ((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40)), + ((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0 + ); +#endif + if ((tpdu[1] & 0xe0) != 0xe0 || tpdu[2] != 1) + { + DEBUGOUT ("invalid response for S-block (Change-IFSD)\n"); + return -1; + } + DEBUGOUT_1 ("IFSD has been set to %d\n", tpdu[3]); + } + return 0; } + +static unsigned int +compute_edc (const unsigned char *data, size_t datalen, int use_crc) +{ + if (use_crc) + { + return 0x42; /* Not yet implemented. */ + } + else + { + unsigned char crc = 0; + + for (; datalen; datalen--) + crc ^= *data++; + return crc; + } +} + + /* Protocol T=1 overview @@ -819,17 +986,19 @@ ccid_transceive (ccid_driver_t handle, unsigned char *resp, size_t maxresplen, size_t *nresp) { int rc; - unsigned char send_buffer[10+258], recv_buffer[10+258]; + unsigned char send_buffer[10+259], recv_buffer[10+259]; const unsigned char *apdu; size_t apdulen; unsigned char *msg, *tpdu, *p; - size_t msglen, tpdulen, n; + size_t msglen, tpdulen, last_tpdulen, n; unsigned char seqno; int i; - unsigned char crc; + unsigned int edc; + int use_crc = 0; size_t dummy_nresp; int next_chunk = 1; int sending = 1; + int retries = 0; if (!nresp) nresp = &dummy_nresp; @@ -852,7 +1021,8 @@ ccid_transceive (ccid_driver_t handle, return -1; /* Invalid length. */ tpdu = msg+10; - tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */ + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */ if (apdulen > 128 /* fixme: replace by ifsc */) { @@ -863,12 +1033,11 @@ ccid_transceive (ccid_driver_t handle, } tpdu[2] = apdulen; memcpy (tpdu+3, apdu, apdulen); - crc = 0; - for (i=0,p=tpdu; i < apdulen+3; i++) - crc ^= *p++; - tpdu[3+apdulen] = crc; - - tpdulen = apdulen + 4; + tpdulen = 3 + apdulen; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; } msg[0] = PC_to_RDR_XfrBlock; @@ -879,6 +1048,7 @@ ccid_transceive (ccid_driver_t handle, msg[9] = 0; /* RFU */ set_msg_len (msg, tpdulen); msglen = 10 + tpdulen; + last_tpdulen = tpdulen; DEBUGOUT ("sending"); for (i=0; i < msglen; i++) @@ -926,7 +1096,7 @@ ccid_transceive (ccid_driver_t handle, if (!(tpdu[1] & 0x80)) { /* This is an I-block. */ - + retries = 0; if (sending) { /* last block sent was successful. */ handle->t1_ns ^= 1; @@ -937,13 +1107,15 @@ ccid_transceive (ccid_driver_t handle, { /* Reponse does not match our sequence number. */ msg = send_buffer; tpdu = msg+10; - tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */ + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4 | 2); /* R-block */ tpdu[2] = 0; tpdulen = 3; - for (crc=0,i=0,p=tpdu; i < tpdulen; i++) - crc ^= *p++; - tpdu[tpdulen++] = crc; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; continue; } @@ -974,20 +1146,27 @@ ccid_transceive (ccid_driver_t handle, msg = send_buffer; tpdu = msg+10; - tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */ + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4); /* R-block */ tpdu[2] = 0; tpdulen = 3; - for (crc=0,i=0,p=tpdu; i < tpdulen; i++) - crc ^= *p++; - tpdu[tpdulen++] = crc; - + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; } else if ((tpdu[1] & 0xc0) == 0x80) { /* This is a R-block. */ if ( (tpdu[1] & 0x0f)) { /* Error: repeat last block */ + if (++retries > 3) + { + DEBUGOUT ("3 failed retries\n"); + return -1; + } msg = send_buffer; + tpdulen = last_tpdulen; } else if (sending && !!(tpdu[1] & 0x40) == handle->t1_ns) { /* Reponse does not match our sequence number. */ @@ -996,6 +1175,7 @@ ccid_transceive (ccid_driver_t handle, } else if (sending) { /* Send next chunk. */ + retries = 0; msg = send_buffer; next_chunk = 1; handle->t1_ns ^= 1; @@ -1008,6 +1188,7 @@ ccid_transceive (ccid_driver_t handle, } else { /* This is a S-block. */ + retries = 0; DEBUGOUT_2 ("T1 S-block %s received cmd=%d\n", (tpdu[1] & 0x20)? "response": "request", (tpdu[1] & 0x1f)); @@ -1016,14 +1197,16 @@ ccid_transceive (ccid_driver_t handle, unsigned char bwi = tpdu[3]; msg = send_buffer; tpdu = msg+10; - tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */ + /* NAD: DAD=1, SAD=0 */ + tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0; tpdu[1] = (0xc0 | 0x20 | 3); /* S-block response */ tpdu[2] = 1; tpdu[3] = bwi; tpdulen = 4; - for (crc=0,i=0,p=tpdu; i < tpdulen; i++) - crc ^= *p++; - tpdu[tpdulen++] = crc; + edc = compute_edc (tpdu, tpdulen, use_crc); + if (use_crc) + tpdu[tpdulen++] = (edc >> 8); + tpdu[tpdulen++] = edc; DEBUGOUT_1 ("T1 waittime extension of bwi=%d\n", bwi); } else @@ -1043,6 +1226,7 @@ main (int argc, char **argv) { int rc; ccid_driver_t ccid; + unsigned int slotstat; rc = ccid_open_reader (&ccid, 0); if (rc) @@ -1056,7 +1240,7 @@ main (int argc, char **argv) ccid_poll (ccid); fputs ("getting slot status ...\n", stderr); - rc = ccid_slot_status (ccid); + rc = ccid_slot_status (ccid, &slotstat); if (rc) return 1; diff --git a/scd/ccid-driver.h b/scd/ccid-driver.h index e33be55d9..8b86eb1a5 100644 --- a/scd/ccid-driver.h +++ b/scd/ccid-driver.h @@ -63,6 +63,7 @@ int ccid_open_reader (ccid_driver_t *handle, int readerno); int ccid_close_reader (ccid_driver_t handle); int ccid_get_atr (ccid_driver_t handle, unsigned char *atr, size_t maxatrlen, size_t *atrlen); +int ccid_slot_status (ccid_driver_t handle, int *statusbits); int ccid_transceive (ccid_driver_t handle, const unsigned char *apdu, size_t apdulen, unsigned char *resp, size_t maxresplen, size_t *nresp); diff --git a/scd/command.c b/scd/command.c index 4746e11b5..06ff6d2e2 100644 --- a/scd/command.c +++ b/scd/command.c @@ -56,14 +56,11 @@ has_option (const char *line, const char *name) } - - -/* Note, that this reset_notify is also used for cleanup purposes. */ +/* Reset the card and free the application context. With DO_CLOSE set + to true, close the reader and don't do just a reset. */ static void -reset_notify (ASSUAN_CONTEXT ctx) +do_reset (ctrl_t ctrl, int do_close) { - CTRL ctrl = assuan_get_pointer (ctx); - if (ctrl->card_ctx) { card_close (ctrl->card_ctx); @@ -73,11 +70,26 @@ reset_notify (ASSUAN_CONTEXT ctx) } if (ctrl->app_ctx) { - int slot = ctrl->app_ctx->slot; release_application (ctrl->app_ctx); ctrl->app_ctx = NULL; - apdu_close_reader (slot); } + if (ctrl->reader_slot != -1) + { + if (do_close || apdu_reset (ctrl->reader_slot)) + { + apdu_close_reader (ctrl->reader_slot); + ctrl->reader_slot = -1; + } + } +} + + +static void +reset_notify (ASSUAN_CONTEXT ctx) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + do_reset (ctrl, 0); } @@ -92,7 +104,7 @@ option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) function returns an Assuan error, so don't map the error a second time */ static AssuanError -open_card (CTRL ctrl, const char *apptype) +open_card (ctrl_t ctrl, const char *apptype) { int slot; @@ -101,13 +113,13 @@ open_card (CTRL ctrl, const char *apptype) if (ctrl->card_ctx) return 0; /* Already initialized using a card context. */ - slot = apdu_open_reader (opt.reader_port); + if (ctrl->reader_slot != -1) + slot = ctrl->reader_slot; + else + slot = apdu_open_reader (opt.reader_port); + ctrl->reader_slot = slot; if (slot != -1) - { - ctrl->app_ctx = select_application (ctrl, slot, apptype); - if (!ctrl->app_ctx) - apdu_close_reader (slot); - } + ctrl->app_ctx = select_application (ctrl, slot, apptype); if (!ctrl->app_ctx) { /* No application found - fall back to old mode. */ /* Note that we should rework the old code to use the @@ -1084,6 +1096,12 @@ scd_command_handler (int listen_fd) if (DBG_ASSUAN) assuan_set_log_stream (ctx, log_get_stream ()); + /* We open the reader right at startup so that the ticker is able to + update the status file. */ + if (ctrl.reader_slot == -1) + ctrl.reader_slot = apdu_open_reader (opt.reader_port); + + /* Command processing loop. */ for (;;) { rc = assuan_accept (ctx); @@ -1104,7 +1122,7 @@ scd_command_handler (int listen_fd) continue; } } - reset_notify (ctx); /* used for cleanup */ + do_reset (&ctrl, 1); /* Cleanup. */ assuan_deinit_server (ctx); } @@ -1156,3 +1174,22 @@ send_status_info (CTRL ctrl, const char *keyword, ...) va_end (arg_ptr); } + + +void +scd_update_reader_status_file (void) +{ + int slot; + int used; + unsigned int status, changed; + + /* Note, that we only try to get the status, becuase it does not + make sense to wait here for a operation to complete. If we are + so busy working with the card, delays in the status file updated + are should be acceptable. */ + for (slot=0; !apdu_enum_reader (slot, &used); slot++) + if (used && !apdu_get_status (slot, 0, &status, &changed)) + { + log_info ("status of slot %d is %u\n", slot, status); + } +} diff --git a/scd/iso7816.c b/scd/iso7816.c index 24361d148..fd3f0485c 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -82,6 +82,9 @@ map_sw (int sw) case SW_HOST_OUT_OF_CORE: ec = GPG_ERR_ENOMEM; break; case SW_HOST_INV_VALUE: ec = GPG_ERR_INV_VALUE; break; case SW_HOST_INCOMPLETE_CARD_RESPONSE: ec = GPG_ERR_CARD; break; + case SW_HOST_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_HOST_LOCKING_FAILED: ec = GPG_ERR_BUG; break; + case SW_HOST_BUSY: ec = GPG_ERR_EBUSY; break; default: if ((sw & 0x010000)) ec = GPG_ERR_GENERAL; /* Should not happen. */ diff --git a/scd/pcsc-wrapper.c b/scd/pcsc-wrapper.c new file mode 100644 index 000000000..4f47ee95c --- /dev/null +++ b/scd/pcsc-wrapper.c @@ -0,0 +1,631 @@ +/* pcsc-wrapper.c - Wrapper for ccessing the PC/SC service + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG 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. + * + * GnuPG 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* + This wrapper is required to handle problems with the libpscslite + library. That library assumes that pthreads are used and fails + badly if one tries to use it with a procerss using Pth. + + The operation model is pretty simple: It reads requests from stdin + and returns the answer on stdout. There is no direct mapping to the + pcsc interface but to a higher level one which resembles the code + used in scdaemon (apdu.c) when not using Pth or while running under + Windows. + + The interface is binary consisting of a command tag and the length + of the parameter list. The calling process needs to pass the + version number of the interface on the command line to make sure + that both agree on the same interface. For each port a separate + instance of this process needs to be started. + +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + + +#define PGM "pcsc-wrapper" + +/* Allow for a standalone build. */ +#ifdef VERSION +#define MYVERSION_LINE PGM " (GnuPG) " VERSION +#define BUGREPORT_LINE "\nReport bugs to .\n" +#else +#define MYVERSION_LINE PGM +#define BUGREPORT_LINE "" +#endif + +#define DEFAULT_PCSC_DRIVER "libpcsclite.so" + + +static int verbose; + + +/* PC/SC constants and function pointer. */ +#define PCSC_SCOPE_USER 0 +#define PCSC_SCOPE_TERMINAL 1 +#define PCSC_SCOPE_SYSTEM 2 +#define PCSC_SCOPE_GLOBAL 3 + +#define PCSC_PROTOCOL_T0 1 +#define PCSC_PROTOCOL_T1 2 +#define PCSC_PROTOCOL_RAW 4 + +#define PCSC_SHARE_EXCLUSIVE 1 +#define PCSC_SHARE_SHARED 2 +#define PCSC_SHARE_DIRECT 3 + +#define PCSC_LEAVE_CARD 0 +#define PCSC_RESET_CARD 1 +#define PCSC_UNPOWER_CARD 2 +#define PCSC_EJECT_CARD 3 + +struct pcsc_io_request_s { + unsigned long protocol; + unsigned long pci_len; +}; + +typedef struct pcsc_io_request_s *pcsc_io_request_t; + + +static int driver_is_open; /* True if the PC/SC driver has been + initialzied and is ready for + operations. The follwoing variables + are then valid. */ +static unsigned long pcsc_context; /* The current PC/CS context. */ +static unsigned long pcsc_card; +static unsigned long pcsc_protocol; +static unsigned char current_atr[33]; +static size_t current_atrlen; + +long (* pcsc_establish_context) (unsigned long scope, + const void *reserved1, + const void *reserved2, + unsigned long *r_context); +long (* pcsc_release_context) (unsigned long context); +long (* pcsc_list_readers) (unsigned long context, + const char *groups, + char *readers, unsigned long*readerslen); +long (* pcsc_connect) (unsigned long context, + const char *reader, + unsigned long share_mode, + unsigned long preferred_protocols, + unsigned long *r_card, + unsigned long *r_active_protocol); +long (* pcsc_disconnect) (unsigned long card, + unsigned long disposition); +long (* pcsc_status) (unsigned long card, + char *reader, unsigned long *readerlen, + unsigned long *r_state, + unsigned long *r_protocol, + unsigned char *atr, unsigned long *atrlen); +long (* pcsc_begin_transaction) (unsigned long card); +long (* pcsc_end_transaction) (unsigned long card); +long (* pcsc_transmit) (unsigned long card, + const pcsc_io_request_t send_pci, + const unsigned char *send_buffer, + unsigned long send_len, + pcsc_io_request_t recv_pci, + unsigned char *recv_buffer, + unsigned long *recv_len); +long (* pcsc_set_timeout) (unsigned long context, + unsigned long timeout); + + + +static void +bad_request (const char *type) +{ + fprintf (stderr, PGM ": bad `%s' request\n", type); + exit (1); +} + +static void +request_failed (int err) +{ + if (!err) + err = -1; + + putchar (0x81); /* Simple error/success response. */ + + putchar (0); + putchar (0); + putchar (0); + putchar (4); + + putchar ((err >> 24) & 0xff); + putchar ((err >> 16) & 0xff); + putchar ((err >> 8) & 0xff); + putchar ((err ) & 0xff); + + fflush (stdout); +} + + +static void +request_succeeded (const void *buffer, size_t buflen) +{ + size_t len; + + putchar (0x81); /* Simple error/success response. */ + + len = 4 + buflen; + putchar ((len >> 24) & 0xff); + putchar ((len >> 16) & 0xff); + putchar ((len >> 8) & 0xff); + putchar ((len ) & 0xff); + + /* Error code. */ + putchar (0); + putchar (0); + putchar (0); + putchar (0); + + /* Optional reponse string. */ + if (buffer) + fwrite (buffer, buflen, 1, stdout); + + fflush (stdout); +} + + + +static unsigned long +read_32 (FILE *fp) +{ + int c1, c2, c3, c4; + + c1 = getc (stdin); + c2 = getc (stdin); + c3 = getc (stdin); + c4 = getc (stdin); + if (c1 == EOF || c2 == EOF || c3 == EOF || c4 == EOF) + { + fprintf (stderr, PGM ": premature EOF while parsing request\n"); + exit (1); + } + return (c1 << 24) | (c2 << 16) | (c3 << 8) | c4; +} + + + +static const char * +pcsc_error_string (long err) +{ + const char *s; + + if (!err) + return "okay"; + if ((err & 0x80100000) != 0x80100000) + return "invalid PC/SC error code"; + err &= 0xffff; + switch (err) + { + case 0x0002: s = "cancelled"; break; + case 0x000e: s = "can't dispose"; break; + case 0x0008: s = "insufficient buffer"; break; + case 0x0015: s = "invalid ATR"; break; + case 0x0003: s = "invalid handle"; break; + case 0x0004: s = "invalid parameter"; break; + case 0x0005: s = "invalid target"; break; + case 0x0011: s = "invalid value"; break; + case 0x0006: s = "no memory"; break; + case 0x0013: s = "comm error"; break; + case 0x0001: s = "internal error"; break; + case 0x0014: s = "unknown error"; break; + case 0x0007: s = "waited too long"; break; + case 0x0009: s = "unknown reader"; break; + case 0x000a: s = "timeout"; break; + case 0x000b: s = "sharing violation"; break; + case 0x000c: s = "no smartcard"; break; + case 0x000d: s = "unknown card"; break; + case 0x000f: s = "proto mismatch"; break; + case 0x0010: s = "not ready"; break; + case 0x0012: s = "system cancelled"; break; + case 0x0016: s = "not transacted"; break; + case 0x0017: s = "reader unavailable"; break; + case 0x0065: s = "unsupported card"; break; + case 0x0066: s = "unresponsive card"; break; + case 0x0067: s = "unpowered card"; break; + case 0x0068: s = "reset card"; break; + case 0x0069: s = "removed card"; break; + case 0x006a: s = "inserted card"; break; + case 0x001f: s = "unsupported feature"; break; + case 0x0019: s = "PCI too small"; break; + case 0x001a: s = "reader unsupported"; break; + case 0x001b: s = "duplicate reader"; break; + case 0x001c: s = "card unsupported"; break; + case 0x001d: s = "no service"; break; + case 0x001e: s = "service stopped"; break; + default: s = "unknown PC/SC error code"; break; + } + return s; +} + +static void +load_pcsc_driver (const char *libname) +{ + void *handle; + + handle = dlopen (libname, RTLD_LAZY); + if (!handle) + { + fprintf (stderr, PGM ": failed to open driver `%s': %s", + libname, dlerror ()); + exit (1); + } + + pcsc_establish_context = dlsym (handle, "SCardEstablishContext"); + pcsc_release_context = dlsym (handle, "SCardReleaseContext"); + pcsc_list_readers = dlsym (handle, "SCardListReaders"); + pcsc_connect = dlsym (handle, "SCardConnect"); + pcsc_disconnect = dlsym (handle, "SCardDisconnect"); + pcsc_status = dlsym (handle, "SCardStatus"); + pcsc_begin_transaction = dlsym (handle, "SCardBeginTransaction"); + pcsc_end_transaction = dlsym (handle, "SCardEndTransaction"); + pcsc_transmit = dlsym (handle, "SCardTransmit"); + pcsc_set_timeout = dlsym (handle, "SCardSetTimeout"); + + if (!pcsc_establish_context + || !pcsc_release_context + || !pcsc_list_readers + || !pcsc_connect + || !pcsc_disconnect + || !pcsc_status + || !pcsc_begin_transaction + || !pcsc_end_transaction + || !pcsc_transmit + /* || !pcsc_set_timeout */) + { + /* Note that set_timeout is currently not used and also not + available under Windows. */ + fprintf (stderr, + "apdu_open_reader: invalid PC/SC driver " + "(%d%d%d%d%d%d%d%d%d%d)\n", + !!pcsc_establish_context, + !!pcsc_release_context, + !!pcsc_list_readers, + !!pcsc_connect, + !!pcsc_disconnect, + !!pcsc_status, + !!pcsc_begin_transaction, + !!pcsc_end_transaction, + !!pcsc_transmit, + !!pcsc_set_timeout ); + dlclose (handle); + exit (1); + } +} + + + + +/* Handle a open request. The argument is expected to be a string + with the port indentification. ARGBUF is always guaranteed to be + terminted by a 0 which is not counted in ARGLEN. We may modifiy + ARGBUF. */ +static void +handle_open (unsigned char *argbuf, size_t arglen) +{ + long err; + const char * portstr; + char *list = NULL; + unsigned long nreader, listlen, atrlen; + char *p; + unsigned long card_state, card_protocol; + unsigned char atr[33]; + + /* Make sure there is only the port string */ + if (arglen != strlen (argbuf)) + bad_request ("OPEN"); + portstr = argbuf; + + if (driver_is_open) + { + fprintf (stderr, PGM ": PC/SC has already been opened\n"); + request_failed (-1); + } + + err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL, &pcsc_context); + if (err) + { + fprintf (stderr, PGM": pcsc_establish_context failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + request_failed (err); + return; + } + + err = pcsc_list_readers (pcsc_context, NULL, NULL, &nreader); + if (!err) + { + list = malloc (nreader+1); /* Better add 1 for safety reasons. */ + if (!list) + { + fprintf (stderr, PGM": error allocating memory for reader list\n"); + exit (1); + } + err = pcsc_list_readers (pcsc_context, NULL, list, &nreader); + } + if (err) + { + fprintf (stderr, PGM": pcsc_list_readers failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + pcsc_release_context (pcsc_context); + free (list); + request_failed (err); + return; + } + + listlen = nreader; + p = list; + while (nreader) + { + if (!*p && !p[1]) + break; + fprintf (stderr, PGM": detected reader `%s'\n", p); + if (nreader < (strlen (p)+1)) + { + fprintf (stderr, PGM": invalid response from pcsc_list_readers\n"); + break; + } + nreader -= strlen (p)+1; + p += strlen (p) + 1; + } + + err = pcsc_connect (pcsc_context, + portstr && *portstr? portstr : list, + PCSC_SHARE_EXCLUSIVE, + PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, + &pcsc_card, + &pcsc_protocol); + if (err) + { + fprintf (stderr, PGM": pcsc_connect failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + pcsc_release_context (pcsc_context); + free (list); + request_failed (err); + return; + } + + atrlen = 32; + /* (We need to pass a dummy buffer. We use LIST because it ought to + be large enough.) */ + err = pcsc_status (pcsc_card, + list, &listlen, + &card_state, &card_protocol, + atr, &atrlen); + free (list); + if (err) + { + fprintf (stderr, PGM": pcsc_status failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + pcsc_release_context (pcsc_context); + request_failed (err); + return; + } + if (atrlen >= sizeof atr || atrlen >= sizeof current_atr) + { + fprintf (stderr, PGM": ATR returned by pcsc_status is too large\n"); + exit (4); + } + memcpy (current_atr, atr, atrlen); + current_atrlen = atrlen; + driver_is_open = 1; + request_succeeded (current_atr, current_atrlen); +} + + + +/* Handle a close request. We expect no arguments. We may modifiy + ARGBUF. */ +static void +handle_close (unsigned char *argbuf, size_t arglen) +{ + if (!driver_is_open) + { + fprintf (stderr, PGM ": PC/SC has not yet been opened\n"); + request_failed (-1); + } + + pcsc_release_context (pcsc_context); + + request_succeeded (NULL, 0); +} + + + +/* Handle a transmit request. The argument is expected to be a bufer + with the APDU. We may modifiy ARGBUF. */ +static void +handle_transmit (unsigned char *argbuf, size_t arglen) +{ + long err; + struct pcsc_io_request_s send_pci; + unsigned long recv_len; + unsigned char buffer[1024]; + + /* The apdu should at least be one byte. */ + if (!arglen) + bad_request ("TRANSMIT"); + + if (!driver_is_open) + { + fprintf (stderr, PGM ": PC/SC has not yet been opened\n"); + request_failed (-1); + } + + if ((pcsc_protocol & PCSC_PROTOCOL_T1)) + send_pci.protocol = PCSC_PROTOCOL_T1; + else + send_pci.protocol = PCSC_PROTOCOL_T0; + send_pci.pci_len = sizeof send_pci; + recv_len = sizeof (buffer); + err = pcsc_transmit (pcsc_card, &send_pci, argbuf, arglen, + NULL, buffer, &recv_len); + if (err) + { + if (verbose) + fprintf (stderr, PGM": pcsc_transmit failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + request_failed (err); + return; + } + request_succeeded (buffer, recv_len); +} + + + + + + + + + + + + +static void +print_version (int with_help) +{ + fputs (MYVERSION_LINE "\n" + "Copyright (C) 2004 Free Software Foundation, Inc.\n" + "This program comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it\n" + "under certain conditions. See the file COPYING for details.\n", + stdout); + + if (with_help) + fputs ("\n" + "Usage: " PGM " [OPTIONS] API-NUMBER [LIBNAME]\n" + "Helper to connect scdaemon to the PC/SC library\n" + "\n" + " --verbose enable extra informational output\n" + " --version print version of the program and exit\n" + " --help display this help and exit\n" + BUGREPORT_LINE, stdout ); + + exit (0); +} + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + int api_number = 0; + int c; + + if (argc) + { + argc--; argv++; + } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--version")) + print_version (0); + else if (!strcmp (*argv, "--help")) + print_version (1); + else if (!strcmp (*argv, "--verbose")) + { + verbose = 1; + argc--; argv++; + } + } + if (argc != 1 && argc != 2) + { + fprintf (stderr, "usage: " PGM " API-NUMBER [LIBNAME]\n"); + exit (1); + } + + api_number = atoi (*argv); + argv++; argc--; + if (api_number != 1) + { + fprintf (stderr, PGM ": api-number %d is not valid\n", api_number); + exit (1); + } + + load_pcsc_driver (argc? *argv : DEFAULT_PCSC_DRIVER); + + while ((c = getc (stdin)) != EOF) + { + size_t arglen; + unsigned char argbuffer[2048]; + + arglen = read_32 (stdin); + if (arglen >= sizeof argbuffer - 1) + { + fprintf (stderr, PGM ": request too long\n"); + exit (1); + } + if (arglen && fread (argbuffer, arglen, 1, stdin) != 1) + { + fprintf (stderr, PGM ": error reading request: %s\n", + strerror (errno)); + exit (1); + } + argbuffer[arglen] = 0; + switch (c) + { + case 1: + handle_open (argbuffer, arglen); + break; + + case 2: + handle_close (argbuffer, arglen); + exit (0); + break; + + case 3: + handle_transmit (argbuffer, arglen); + break; + + default: + fprintf (stderr, PGM ": invalid request 0x%02X\n", c); + exit (1); + } + free (argbuffer); + } + return 0; +} + + + +/* +Local Variables: +compile-command: "gcc -Wall -g -o pcsc-wrapper pcsc-wrapper.c -ldl" +End: +*/ diff --git a/scd/scdaemon.c b/scd/scdaemon.c index bc9d90b72..aabd38861 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -33,6 +33,9 @@ #include #include #include +#ifdef USE_GNU_PTH +# include +#endif #define JNLIB_NEED_LOG_LOGV #include "scdaemon.h" @@ -131,12 +134,24 @@ static ARGPARSE_OPTS opts[] = { static volatile int caught_fatal_sig = 0; +/* Flag to indicate that a shutdown was requested. */ +static int shutdown_pending; + /* It is possible that we are currently running under setuid permissions */ static int maybe_setuid = 1; /* Name of the communication socket */ static char socket_name[128]; + +#ifdef USE_GNU_PTH +/* Pth wrapper function definitions. */ +GCRY_THREAD_OPTION_PTH_IMPL; + +static void *ticker_thread (void *arg); +#endif /*USE_GNU_PTH*/ + + static const char * my_strusage (int level) { @@ -287,6 +302,7 @@ main (int argc, char **argv ) { ARGPARSE_ARGS pargs; int orig_argc; + gpg_error_t err; int may_coredump; char **orig_argv; FILE *configfp = NULL; @@ -318,7 +334,18 @@ main (int argc, char **argv ) i18n_init (); - /* check that the libraries are suitable. Do it here because + /* Libgcrypt requires us to register the threading model first. + Note that this will also do the pth_init. */ +#ifdef USE_GNU_PTH + err = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth); + if (err) + { + log_fatal ("can't register GNU Pth with Libgcrypt: %s\n", + gpg_strerror (err)); + } +#endif /*USE_GNU_PTH*/ + + /* Check that the libraries are suitable. Do it here because the option parsing may need services of the library */ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) { @@ -568,7 +595,21 @@ main (int argc, char **argv ) if (pipe_server) - { /* this is the simple pipe based server */ + { /* This is the simple pipe based server */ +#ifdef USE_GNU_PTH + pth_attr_t tattr; + + tattr = pth_attr_new(); + pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); + pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 64*1024); + pth_attr_set (tattr, PTH_ATTR_NAME, "ticker"); + + if (!pth_spawn (tattr, ticker_thread, NULL)) + { + log_error ("error spawning ticker thread: %s\n", strerror (errno)); + scd_exit (2); + } +#endif /*USE_GNU_PTH*/ scd_command_handler (-1); } else if (!is_daemon) @@ -780,6 +821,115 @@ scd_exit (int rc) void scd_init_default_ctrl (CTRL ctrl) { - + ctrl->reader_slot = -1; } + +#ifdef USE_GNU_PTH + +static void +handle_signal (int signo) +{ + switch (signo) + { + case SIGHUP: + log_info ("SIGHUP received - " + "re-reading configuration and resetting cards\n"); +/* reread_configuration (); */ + break; + + case SIGUSR1: + if (opt.verbose < 5) + opt.verbose++; + log_info ("SIGUSR1 received - verbosity set to %d\n", opt.verbose); + break; + + case SIGUSR2: + if (opt.verbose) + opt.verbose--; + log_info ("SIGUSR2 received - verbosity set to %d\n", opt.verbose ); + break; + + case SIGTERM: + if (!shutdown_pending) + log_info ("SIGTERM received - shutting down ...\n"); + else + log_info ("SIGTERM received - still %ld running threads\n", + pth_ctrl( PTH_CTRL_GETTHREADS )); + shutdown_pending++; + if (shutdown_pending > 2) + { + log_info ("shutdown forced\n"); + log_info ("%s %s stopped\n", strusage(11), strusage(13) ); + cleanup (); + scd_exit (0); + } + break; + + case SIGINT: + log_info ("SIGINT received - immediate shutdown\n"); + log_info( "%s %s stopped\n", strusage(11), strusage(13)); + cleanup (); + scd_exit (0); + break; + + default: + log_info ("signal %d received - no action defined\n", signo); + } +} + +static void +handle_tick (void) +{ + scd_update_reader_status_file (); +} + +static void * +ticker_thread (void *dummy_arg) +{ + pth_event_t sigs_ev, time_ev = NULL; + sigset_t sigs; + int signo; + + sigemptyset (&sigs ); + sigaddset (&sigs, SIGHUP); + sigaddset (&sigs, SIGUSR1); + sigaddset (&sigs, SIGUSR2); + sigaddset (&sigs, SIGINT); + sigaddset (&sigs, SIGTERM); + sigs_ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); + + for (;;) + { + if (!time_ev) + { + time_ev = pth_event (PTH_EVENT_TIME, pth_timeout (2, 0)); + if (time_ev) + pth_event_concat (sigs_ev, time_ev, NULL); + } + + if (pth_wait (sigs_ev) < 1) + continue; + + if ( +#ifdef PTH_STATUS_OCCURRED /* This is Pth 2 */ + pth_event_status (sigs_ev) == PTH_STATUS_OCCURRED +#else + pth_event_occurred (sigs_ev) +#endif + ) + handle_signal (signo); + + /* Always run the ticker. */ + if (!shutdown_pending) + { + pth_event_isolate (sigs_ev); + pth_event_free (time_ev, PTH_FREE_ALL); + time_ev = NULL; + handle_tick (); + } + } + + pth_event_free (sigs_ev, PTH_FREE_ALL); +} +#endif /*USE_GNU_PTH*/ diff --git a/scd/scdaemon.h b/scd/scdaemon.h index 098738508..1dd32ae90 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -79,6 +79,7 @@ struct app_ctx_s; struct server_control_s { struct server_local_s *server_local; + int reader_slot; /* Slot of the open reader or -1 if not open. */ struct card_ctx_s *card_ctx; struct app_ctx_s *app_ctx; struct { @@ -101,6 +102,7 @@ void scd_init_default_ctrl (CTRL ctrl); /*-- command.c --*/ void scd_command_handler (int); void send_status_info (CTRL ctrl, const char *keyword, ...); +void scd_update_reader_status_file (void); /*-- card.c --*/ int card_open (CARD *rcard);