diff options
Diffstat (limited to 'scd/apdu.c')
-rw-r--r-- | scd/apdu.c | 560 |
1 files changed, 309 insertions, 251 deletions
diff --git a/scd/apdu.c b/scd/apdu.c index 2df113c5e..f86a63897 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -55,7 +55,7 @@ #include "ccid-driver.h" struct dev_list { - struct ccid_dev_table *ccid_table; + void *table; const char *portstr; int idx; int idx_max; @@ -76,6 +76,19 @@ typedef unsigned int pcsc_dword_t; typedef unsigned long pcsc_dword_t; #endif +#ifdef HAVE_W32_SYSTEM +#define HANDLE uintptr_t +#else +#define HANDLE long +#endif + +/* PC/SC context to access readers. Shared among all readers. */ +static struct pcsc_global_data { + HANDLE context; + int count; + const char *rdrname[MAX_READER]; +} pcsc; + /* A structure to collect information pertaining to one reader slot. */ struct reader_table_s { @@ -101,8 +114,7 @@ struct reader_table_s { ccid_driver_t handle; } ccid; struct { - long context; - long card; + HANDLE card; pcsc_dword_t protocol; pcsc_dword_t verify_ioctl; pcsc_dword_t modify_ioctl; @@ -288,35 +300,35 @@ typedef struct pcsc_readerstate_s *pcsc_readerstate_t; long (* DLSTDCALL pcsc_establish_context) (pcsc_dword_t scope, const void *reserved1, const void *reserved2, - long *r_context); -long (* DLSTDCALL pcsc_release_context) (long context); -long (* DLSTDCALL pcsc_list_readers) (long context, + HANDLE *r_context); +long (* DLSTDCALL pcsc_release_context) (HANDLE context); +long (* DLSTDCALL pcsc_list_readers) (HANDLE context, const char *groups, char *readers, pcsc_dword_t*readerslen); -long (* DLSTDCALL pcsc_get_status_change) (long context, +long (* DLSTDCALL pcsc_get_status_change) (HANDLE context, pcsc_dword_t timeout, pcsc_readerstate_t readerstates, pcsc_dword_t nreaderstates); -long (* DLSTDCALL pcsc_connect) (long context, +long (* DLSTDCALL pcsc_connect) (HANDLE context, const char *reader, pcsc_dword_t share_mode, pcsc_dword_t preferred_protocols, - long *r_card, + HANDLE *r_card, pcsc_dword_t *r_active_protocol); -long (* DLSTDCALL pcsc_reconnect) (long card, +long (* DLSTDCALL pcsc_reconnect) (HANDLE card, pcsc_dword_t share_mode, pcsc_dword_t preferred_protocols, pcsc_dword_t initialization, pcsc_dword_t *r_active_protocol); -long (* DLSTDCALL pcsc_disconnect) (long card, +long (* DLSTDCALL pcsc_disconnect) (HANDLE card, pcsc_dword_t disposition); -long (* DLSTDCALL pcsc_status) (long card, +long (* DLSTDCALL pcsc_status) (HANDLE card, char *reader, pcsc_dword_t *readerlen, pcsc_dword_t *r_state, pcsc_dword_t *r_protocol, unsigned char *atr, pcsc_dword_t *atrlen); long (* DLSTDCALL pcsc_begin_transaction) (long card); -long (* DLSTDCALL pcsc_end_transaction) (long card, +long (* DLSTDCALL pcsc_end_transaction) (HANDLE card, pcsc_dword_t disposition); long (* DLSTDCALL pcsc_transmit) (long card, const pcsc_io_request_t send_pci, @@ -325,9 +337,9 @@ long (* DLSTDCALL pcsc_transmit) (long card, pcsc_io_request_t recv_pci, unsigned char *recv_buffer, pcsc_dword_t *recv_len); -long (* DLSTDCALL pcsc_set_timeout) (long context, +long (* DLSTDCALL pcsc_set_timeout) (HANDLE context, pcsc_dword_t timeout); -long (* DLSTDCALL pcsc_control) (long card, +long (* DLSTDCALL pcsc_control) (HANDLE card, pcsc_dword_t control_code, const void *send_buffer, pcsc_dword_t send_len, @@ -652,9 +664,7 @@ pcsc_get_status (int slot, unsigned int *status, int on_wire) memset (rdrstates, 0, sizeof *rdrstates); rdrstates[0].reader = reader_table[slot].rdrname; rdrstates[0].current_state = reader_table[slot].pcsc.current_state; - err = pcsc_get_status_change (reader_table[slot].pcsc.context, - 0, - rdrstates, 1); + err = pcsc_get_status_change (pcsc.context, 0, rdrstates, 1); if (err == PCSC_E_TIMEOUT) err = 0; /* Timeout is no error here. */ if (err) @@ -788,7 +798,16 @@ control_pcsc (int slot, pcsc_dword_t ioctl_code, static int close_pcsc_reader (int slot) { - pcsc_release_context (reader_table[slot].pcsc.context); + (void)slot; + if (--pcsc.count == 0) + { + int i; + + pcsc_release_context (pcsc.context); + pcsc.context = 0; + for (i = 0; i < MAX_READER; i++) + pcsc.rdrname[i] = NULL; + } return 0; } @@ -807,7 +826,7 @@ connect_pcsc_card (int slot) reader_table[slot].atrlen = 0; reader_table[slot].is_t0 = 0; - err = pcsc_connect (reader_table[slot].pcsc.context, + err = pcsc_connect (pcsc.context, reader_table[slot].rdrname, PCSC_SHARE_EXCLUSIVE, PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, @@ -829,7 +848,7 @@ connect_pcsc_card (int slot) pcsc_vendor_specific_init (slot); atrlen = DIM (reader_table[0].atr); - readerlen = sizeof reader -1 ; + readerlen = sizeof reader - 1; err = pcsc_status (reader_table[slot].pcsc.card, reader, &readerlen, &card_state, &card_protocol, @@ -1042,94 +1061,128 @@ pcsc_vendor_specific_init (int slot) return 0; } - -/* Open the PC/SC reader without using the wrapper. Returns -1 on - error or a slot number for the reader. */ static int -open_pcsc_reader (const char *portstr) +pcsc_init (void) { + static int pcsc_api_loaded; long err; - int slot; - char *list = NULL; - char *rdrname = NULL; - pcsc_dword_t nreader; - char *p; - - slot = new_reader_slot (); - if (slot == -1) - return -1; - /* Fixme: Allocating a context for each slot is not required. One - global context should be sufficient. */ - err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL, - &reader_table[slot].pcsc.context); - if (err) + /* Lets try the PC/SC API */ + if (!pcsc_api_loaded) { - log_error ("pcsc_establish_context failed: %s (0x%lx)\n", - pcsc_error_string (err), err); - reader_table[slot].used = 0; - unlock_slot (slot); - return -1; - } + void *handle; - err = pcsc_list_readers (reader_table[slot].pcsc.context, - NULL, NULL, &nreader); - if (!err) - { - list = xtrymalloc (nreader+1); /* Better add 1 for safety reasons. */ - if (!list) + handle = dlopen (opt.pcsc_driver, RTLD_LAZY); + if (!handle) { - log_error ("error allocating memory for reader list\n"); - pcsc_release_context (reader_table[slot].pcsc.context); - reader_table[slot].used = 0; - unlock_slot (slot); - return -1 /*SW_HOST_OUT_OF_CORE*/; + log_error ("apdu_open_reader: failed to open driver '%s': %s\n", + opt.pcsc_driver, dlerror ()); + return -1; + } + + pcsc_establish_context = dlsym (handle, "SCardEstablishContext"); + pcsc_release_context = dlsym (handle, "SCardReleaseContext"); + pcsc_list_readers = dlsym (handle, "SCardListReaders"); +#if defined(_WIN32) || defined(__CYGWIN__) + if (!pcsc_list_readers) + pcsc_list_readers = dlsym (handle, "SCardListReadersA"); +#endif + pcsc_get_status_change = dlsym (handle, "SCardGetStatusChange"); +#if defined(_WIN32) || defined(__CYGWIN__) + if (!pcsc_get_status_change) + pcsc_get_status_change = dlsym (handle, "SCardGetStatusChangeA"); +#endif + pcsc_connect = dlsym (handle, "SCardConnect"); +#if defined(_WIN32) || defined(__CYGWIN__) + if (!pcsc_connect) + pcsc_connect = dlsym (handle, "SCardConnectA"); +#endif + pcsc_reconnect = dlsym (handle, "SCardReconnect"); +#if defined(_WIN32) || defined(__CYGWIN__) + if (!pcsc_reconnect) + pcsc_reconnect = dlsym (handle, "SCardReconnectA"); +#endif + pcsc_disconnect = dlsym (handle, "SCardDisconnect"); + pcsc_status = dlsym (handle, "SCardStatus"); +#if defined(_WIN32) || defined(__CYGWIN__) + if (!pcsc_status) + pcsc_status = dlsym (handle, "SCardStatusA"); +#endif + pcsc_begin_transaction = dlsym (handle, "SCardBeginTransaction"); + pcsc_end_transaction = dlsym (handle, "SCardEndTransaction"); + pcsc_transmit = dlsym (handle, "SCardTransmit"); + pcsc_set_timeout = dlsym (handle, "SCardSetTimeout"); + pcsc_control = dlsym (handle, "SCardControl"); + + if (!pcsc_establish_context + || !pcsc_release_context + || !pcsc_list_readers + || !pcsc_get_status_change + || !pcsc_connect + || !pcsc_reconnect + || !pcsc_disconnect + || !pcsc_status + || !pcsc_begin_transaction + || !pcsc_end_transaction + || !pcsc_transmit + || !pcsc_control + /* || !pcsc_set_timeout */) + { + /* Note that set_timeout is currently not used and also not + available under Windows. */ + log_error ("apdu_open_reader: invalid PC/SC driver " + "(%d%d%d%d%d%d%d%d%d%d%d%d%d)\n", + !!pcsc_establish_context, + !!pcsc_release_context, + !!pcsc_list_readers, + !!pcsc_get_status_change, + !!pcsc_connect, + !!pcsc_reconnect, + !!pcsc_disconnect, + !!pcsc_status, + !!pcsc_begin_transaction, + !!pcsc_end_transaction, + !!pcsc_transmit, + !!pcsc_set_timeout, + !!pcsc_control ); + dlclose (handle); + return -1; } - err = pcsc_list_readers (reader_table[slot].pcsc.context, - NULL, list, &nreader); + pcsc_api_loaded = 1; } + + err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL, + &pcsc.context); if (err) { - log_error ("pcsc_list_readers failed: %s (0x%lx)\n", + log_error ("pcsc_establish_context failed: %s (0x%lx)\n", pcsc_error_string (err), err); - pcsc_release_context (reader_table[slot].pcsc.context); - reader_table[slot].used = 0; - xfree (list); - unlock_slot (slot); return -1; } - p = list; - while (nreader) - { - if (!*p && !p[1]) - break; - log_info ("detected reader '%s'\n", p); - if (nreader < (strlen (p)+1)) - { - log_error ("invalid response from pcsc_list_readers\n"); - break; - } - if (!rdrname && portstr && !strncmp (p, portstr, strlen (portstr))) - rdrname = p; - nreader -= strlen (p)+1; - p += strlen (p) + 1; - } + return 0; +} - if (!rdrname) - rdrname = list; +/* Open the PC/SC reader. Returns -1 on error or a slot number for + the reader. */ +static int +open_pcsc_reader (const char *rdrname) +{ + int slot; + + slot = new_reader_slot (); + if (slot == -1) + return -1; reader_table[slot].rdrname = xtrystrdup (rdrname); if (!reader_table[slot].rdrname) { log_error ("error allocating memory for reader name\n"); - pcsc_release_context (reader_table[slot].pcsc.context); + close_pcsc_reader (0); reader_table[slot].used = 0; unlock_slot (slot); return -1; } - xfree (list); - list = NULL; reader_table[slot].pcsc.card = 0; reader_table[slot].atrlen = 0; @@ -1142,6 +1195,7 @@ open_pcsc_reader (const char *portstr) reader_table[slot].send_apdu_reader = pcsc_send_apdu; reader_table[slot].dump_status_reader = dump_pcsc_reader_status; + pcsc.count++; dump_reader_status (slot); unlock_slot (slot); return slot; @@ -1520,7 +1574,7 @@ open_ccid_reader (struct dev_list *dl) return -1; slotp = reader_table + slot; - err = ccid_open_reader (dl->portstr, dl->idx, dl->ccid_table, + err = ccid_open_reader (dl->portstr, dl->idx, dl->table, &slotp->ccid.handle, &slotp->rdrname); if (!err) { @@ -1866,51 +1920,111 @@ gpg_error_t apdu_dev_list_start (const char *portstr, struct dev_list **l_p) { struct dev_list *dl = xtrymalloc (sizeof (struct dev_list)); + gpg_error_t err; *l_p = NULL; if (!dl) return gpg_error_from_syserror (); + dl->table = NULL; dl->portstr = portstr; dl->idx = 0; + dl->idx_max = 0; npth_mutex_lock (&reader_table_lock); #ifdef HAVE_LIBUSB - if (opt.disable_ccid) + if (!opt.disable_ccid) { - dl->ccid_table = NULL; - dl->idx_max = 1; + err = ccid_dev_scan (&dl->idx_max, &dl->table); + if (err) + { + npth_mutex_unlock (&reader_table_lock); + return err; + } + + if (dl->idx_max == 0) + { + if (DBG_READER) + log_debug ("leave: apdu_open_reader => slot=-1 (no ccid)\n"); + + xfree (dl); + npth_mutex_unlock (&reader_table_lock); + return gpg_error (GPG_ERR_ENODEV); + } } else - { - gpg_error_t err; +#endif + { /* PC/SC readers. */ + long r; + pcsc_dword_t nreader; + char *p = NULL; - err = ccid_dev_scan (&dl->idx_max, &dl->ccid_table); - if (err) - return err; + if (!pcsc.context) + if (pcsc_init () < 0) + { + npth_mutex_unlock (&reader_table_lock); + return gpg_error (GPG_ERR_NO_SERVICE); + } - if (dl->idx_max == 0) + r = pcsc_list_readers (pcsc.context, NULL, NULL, &nreader); + if (!r) { - /* If a CCID reader specification has been given, the user does - not want a fallback to other drivers. */ - if (portstr && strlen (portstr) > 5 && portstr[4] == ':') + p = xtrymalloc (nreader); + if (!p) { - if (DBG_READER) - log_debug ("leave: apdu_open_reader => slot=-1 (no ccid)\n"); + err = gpg_error_from_syserror (); - xfree (dl); + log_error ("error allocating memory for reader list\n"); + close_pcsc_reader (0); npth_mutex_unlock (&reader_table_lock); - return gpg_error (GPG_ERR_ENODEV); + return err; + } + r = pcsc_list_readers (pcsc.context, NULL, p, &nreader); + } + if (r) + { + log_error ("pcsc_list_readers failed: %s (0x%lx)\n", + pcsc_error_string (r), r); + xfree (p); + close_pcsc_reader (0); + npth_mutex_unlock (&reader_table_lock); + return gpg_error (GPG_ERR_NO_SERVICE); + } + + dl->table = p; + dl->idx_max = 0; + + while (nreader > 0) + { + size_t n; + + if (!*p) + break; + + for (n = 0; n < nreader; n++) + if (!p[n]) + break; + + if (n >= nreader) + { + log_error ("invalid response from pcsc_list_readers\n"); + break; + } + + log_info ("detected reader '%s'\n", p); + pcsc.rdrname[dl->idx_max] = p; + nreader -= n + 1; + p += n + 1; + dl->idx_max++; + if (dl->idx_max > MAX_READER) + { + log_error ("too many readers from pcsc_list_readers\n"); + dl->idx_max--; + break; } - else - dl->idx_max = 1; } } -#else - dl->ccid_table = NULL; - dl->idx_max = 1; -#endif /* HAVE_LIBUSB */ *l_p = dl; return 0; @@ -1920,161 +2034,65 @@ void apdu_dev_list_finish (struct dev_list *dl) { #ifdef HAVE_LIBUSB - if (dl->ccid_table) - ccid_dev_scan_finish (dl->ccid_table, dl->idx_max); + if (!opt.disable_ccid) + { + if (dl->table) + ccid_dev_scan_finish (dl->table, dl->idx_max); + } + else #endif + { /* PC/SC readers. */ + int i; + + xfree (dl->table); + for (i = 0; i < MAX_READER; i++) + pcsc.rdrname[i] = NULL; + } xfree (dl); npth_mutex_unlock (&reader_table_lock); } -/* 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). */ -static int -apdu_open_one_reader (const char *portstr) +int +apdu_open_reader (struct dev_list *dl) { - static int pcsc_api_loaded; int slot; + int readerno; - if (DBG_READER) - log_debug ("enter: apdu_open_reader: portstr=%s\n", portstr); + if (!dl->table) + return -1; - /* Lets try the PC/SC API */ - if (!pcsc_api_loaded) + /* See whether we want to use the reader ID string or a reader + number. A readerno of -1 indicates that the reader ID string is + to be used. */ + if (dl->portstr && strchr (dl->portstr, ':')) + readerno = -1; /* We want to use the readerid. */ + else if (dl->portstr) { - void *handle; - - handle = dlopen (opt.pcsc_driver, RTLD_LAZY); - if (!handle) - { - log_error ("apdu_open_reader: failed to open driver '%s': %s\n", - opt.pcsc_driver, dlerror ()); - return -1; - } + readerno = atoi (dl->portstr); + if (readerno < 0 || readerno >= dl->idx_max) + return -1; - pcsc_establish_context = dlsym (handle, "SCardEstablishContext"); - pcsc_release_context = dlsym (handle, "SCardReleaseContext"); - pcsc_list_readers = dlsym (handle, "SCardListReaders"); -#if defined(_WIN32) || defined(__CYGWIN__) - if (!pcsc_list_readers) - pcsc_list_readers = dlsym (handle, "SCardListReadersA"); -#endif - pcsc_get_status_change = dlsym (handle, "SCardGetStatusChange"); -#if defined(_WIN32) || defined(__CYGWIN__) - if (!pcsc_get_status_change) - pcsc_get_status_change = dlsym (handle, "SCardGetStatusChangeA"); -#endif - pcsc_connect = dlsym (handle, "SCardConnect"); -#if defined(_WIN32) || defined(__CYGWIN__) - if (!pcsc_connect) - pcsc_connect = dlsym (handle, "SCardConnectA"); -#endif - pcsc_reconnect = dlsym (handle, "SCardReconnect"); -#if defined(_WIN32) || defined(__CYGWIN__) - if (!pcsc_reconnect) - pcsc_reconnect = dlsym (handle, "SCardReconnectA"); -#endif - pcsc_disconnect = dlsym (handle, "SCardDisconnect"); - pcsc_status = dlsym (handle, "SCardStatus"); -#if defined(_WIN32) || defined(__CYGWIN__) - if (!pcsc_status) - pcsc_status = dlsym (handle, "SCardStatusA"); -#endif - pcsc_begin_transaction = dlsym (handle, "SCardBeginTransaction"); - pcsc_end_transaction = dlsym (handle, "SCardEndTransaction"); - pcsc_transmit = dlsym (handle, "SCardTransmit"); - pcsc_set_timeout = dlsym (handle, "SCardSetTimeout"); - pcsc_control = dlsym (handle, "SCardControl"); - - if (!pcsc_establish_context - || !pcsc_release_context - || !pcsc_list_readers - || !pcsc_get_status_change - || !pcsc_connect - || !pcsc_reconnect - || !pcsc_disconnect - || !pcsc_status - || !pcsc_begin_transaction - || !pcsc_end_transaction - || !pcsc_transmit - || !pcsc_control - /* || !pcsc_set_timeout */) - { - /* Note that set_timeout is currently not used and also not - available under Windows. */ - log_error ("apdu_open_reader: invalid PC/SC driver " - "(%d%d%d%d%d%d%d%d%d%d%d%d%d)\n", - !!pcsc_establish_context, - !!pcsc_release_context, - !!pcsc_list_readers, - !!pcsc_get_status_change, - !!pcsc_connect, - !!pcsc_reconnect, - !!pcsc_disconnect, - !!pcsc_status, - !!pcsc_begin_transaction, - !!pcsc_end_transaction, - !!pcsc_transmit, - !!pcsc_set_timeout, - !!pcsc_control ); - dlclose (handle); - return -1; - } - pcsc_api_loaded = 1; + dl->idx = readerno; + dl->portstr = NULL; } - - slot = open_pcsc_reader (portstr); - - if (DBG_READER) - log_debug ("leave: apdu_open_reader => slot=%d [pc/sc]\n", slot); - return slot; -} - -int -apdu_open_reader (struct dev_list *dl, int app_empty) -{ - int slot; + else + readerno = 0; /* Default. */ #ifdef HAVE_LIBUSB - if (dl->ccid_table) + if (!opt.disable_ccid) { /* CCID readers. */ - int readerno; - - /* See whether we want to use the reader ID string or a reader - number. A readerno of -1 indicates that the reader ID string is - to be used. */ - if (dl->portstr && strchr (dl->portstr, ':')) - readerno = -1; /* We want to use the readerid. */ - else if (dl->portstr) - { - readerno = atoi (dl->portstr); - if (readerno < 0) - { - return -1; - } - } - else - readerno = 0; /* Default. */ - if (readerno > 0) { /* Use single, the specific reader. */ - if (readerno >= dl->idx_max) - return -1; - - dl->idx = readerno; - dl->portstr = NULL; slot = open_ccid_reader (dl); + /* And stick the reader and no scan. */ dl->idx = dl->idx_max; - if (slot >= 0) - return slot; - else - return -1; + return slot; } while (dl->idx < dl->idx_max) { - unsigned int bai = ccid_get_BAI (dl->idx, dl->ccid_table); + unsigned int bai = ccid_get_BAI (dl->idx, dl->table); if (DBG_READER) log_debug ("apdu_open_reader: BAI=%x\n", bai); @@ -2107,25 +2125,60 @@ apdu_open_reader (struct dev_list *dl, int app_empty) dl->idx++; } - /* Not found. Try one for PC/SC, only when it's the initial scan. */ - if (app_empty && dl->idx == dl->idx_max) - { - dl->idx++; - slot = apdu_open_one_reader (dl->portstr); - } - else - slot = -1; + /* Not found. */ + slot = -1; } else #endif { /* PC/SC readers. */ - if (app_empty && dl->idx == 0) + if (readerno > 0) + { /* Use single, the specific reader. */ + slot = open_pcsc_reader (pcsc.rdrname[readerno]); + /* And stick the reader and no scan. */ + dl->idx = dl->idx_max; + return slot; + } + + while (dl->idx < dl->idx_max) { - dl->idx++; - slot = apdu_open_one_reader (dl->portstr); + const char *rdrname = pcsc.rdrname[dl->idx]; + + if (DBG_READER) + log_debug ("apdu_open_reader: %s\n", rdrname); + + /* Check the identity of reader against already opened one. */ + for (slot = 0; slot < MAX_READER; slot++) + if (reader_table[slot].used + && !strcmp (reader_table[slot].rdrname, rdrname)) + break; + + if (slot == MAX_READER) + { /* Found a new device. */ + if (DBG_READER) + log_debug ("apdu_open_reader: new device=%s\n", rdrname); + + /* When reader string is specified, check if it is the one. */ + if (readerno < 0 && strcmp (rdrname, dl->portstr) != 0) + continue; + + slot = open_pcsc_reader (rdrname); + + dl->idx++; + if (slot >= 0) + return slot; + else + { + /* Skip this reader. */ + log_error ("pcsc open error: skip\n"); + continue; + } + } + else + dl->idx++; } - else - slot = -1; + + /* Not found. */ + slot = -1; } return slot; @@ -3305,6 +3358,11 @@ apdu_init (void) gpg_error_t err; int i; + pcsc.count = 0; + pcsc.context = 0; + for (i = 0; i < MAX_READER; i++) + pcsc.rdrname[i] = NULL; + if (npth_mutex_init (&reader_table_lock, NULL)) goto leave; |