diff options
Diffstat (limited to 'scd/apdu.c')
-rw-r--r-- | scd/apdu.c | 488 |
1 files changed, 453 insertions, 35 deletions
diff --git a/scd/apdu.c b/scd/apdu.c index 0d9ef3d0c..f4b32d141 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -125,6 +125,8 @@ struct reader_table_s { rapdu_t handle; } rapdu; #endif /*USE_G10CODE_RAPDU*/ + char *rdrname; /* Name of the connected reader or NULL if unknown. */ + int last_status; int status; unsigned char atr[33]; size_t atrlen; /* A zero length indicates that the ATR has @@ -169,13 +171,47 @@ static char (* DLSTDCALL CT_close) (unsigned short ctn); #define PCSC_UNPOWER_CARD 2 #define PCSC_EJECT_CARD 3 -struct pcsc_io_request_s { +#define PCSC_UNKNOWN 0x0001 +#define PCSC_ABSENT 0x0002 /* Card is absent. */ +#define PCSC_PRESENT 0x0004 /* Card is present. */ +#define PCSC_SWALLOWED 0x0008 /* Card is present and electrical connected. */ +#define PCSC_POWERED 0x0010 /* Card is powered. */ +#define PCSC_NEGOTIABLE 0x0020 /* Card is awaiting PTS. */ +#define PCSC_SPECIFIC 0x0040 /* Card is ready for use. */ + +#define PCSC_STATE_UNAWARE 0x0000 /* Want status. */ +#define PCSC_STATE_IGNORE 0x0001 /* Ignore this reader. */ +#define PCSC_STATE_CHANGED 0x0002 /* State has changed. */ +#define PCSC_STATE_UNKNOWN 0x0004 /* Reader unknown. */ +#define PCSC_STATE_UNAVAILABLE 0x0008 /* Status unavailable. */ +#define PCSC_STATE_EMPTY 0x0010 /* Card removed. */ +#define PCSC_STATE_PRESENT 0x0020 /* Card inserted. */ +#define PCSC_STATE_ATRMATCH 0x0040 /* ATR matches card. */ +#define PCSC_STATE_EXCLUSIVE 0x0080 /* Exclusive Mode. */ +#define PCSC_STATE_INUSE 0x0100 /* Shared mode. */ +#define PCSC_STATE_MUTE 0x0200 /* Unresponsive card. */ + + +struct pcsc_io_request_s +{ unsigned long protocol; unsigned long pci_len; }; typedef struct pcsc_io_request_s *pcsc_io_request_t; +struct pcsc_readerstate_s +{ + const char *reader; + void *user_data; + unsigned long current_state; + unsigned long event_state; + unsigned long atrlen; + unsigned char atr[33]; +}; + +typedef struct pcsc_readerstate_s *pcsc_readerstate_t; + long (* DLSTDCALL pcsc_establish_context) (unsigned long scope, const void *reserved1, const void *reserved2, @@ -184,12 +220,21 @@ long (* DLSTDCALL pcsc_release_context) (unsigned long context); long (* DLSTDCALL pcsc_list_readers) (unsigned long context, const char *groups, char *readers, unsigned long*readerslen); +long (* DLSTDCALL pcsc_get_status_change) (unsigned long context, + unsigned long timeout, + pcsc_readerstate_t readerstates, + unsigned long nreaderstates); long (* DLSTDCALL 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 (* DLSTDCALL pcsc_reconnect) (unsigned long card, + unsigned long share_mode, + unsigned long preferred_protocols, + unsigned long initialization, + unsigned long *r_active_protocol); long (* DLSTDCALL pcsc_disconnect) (unsigned long card, unsigned long disposition); long (* DLSTDCALL pcsc_status) (unsigned long card, @@ -211,7 +256,6 @@ long (* DLSTDCALL pcsc_set_timeout) (unsigned long context, - /* Helper @@ -254,11 +298,13 @@ new_reader_slot (void) reader_table[reader].dump_status_reader = NULL; reader_table[reader].used = 1; + reader_table[reader].last_status = 0; #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; } @@ -662,18 +708,293 @@ dump_pcsc_reader_status (int slot) } - static int -pcsc_get_status (int slot, unsigned int *status) +reset_pcsc_reader (int slot) { - *status = 1|2|4; /* FIXME!!!! */ +#ifdef NEED_PCSC_WRAPPER + long err; + reader_table_t slotp; + size_t len; + int i, n; + 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 ("pcsc_get_status: pcsc-wrapper not running\n"); + return SW_HOST_CARD_IO_ERROR; + } + + msgbuf[0] = 0x05; /* RESET 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 RESET 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 RESET 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 RESET failed: %s\n", pcsc_error_string (err)); + goto command_failed; + } + + /* The open fucntion may return a zero for the ATR length to + indicate that no card is present. */ + n = len; + if (n) + { + if ((i=readn (slotp->pcsc.rsp_fd, slotp->atr, n, &len)) || len != n) + { + log_error ("error receiving PC/SC RESET response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + } + slotp->atrlen = len; + + return 0; + + 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; + char reader[250]; + unsigned long nreader, atrlen; + unsigned long card_state, card_protocol; + + if (reader_table[slot].pcsc.card) + { + err = pcsc_disconnect (reader_table[slot].pcsc.card, PCSC_LEAVE_CARD); + if (err) + { + log_error ("pcsc_disconnect failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return SW_HOST_CARD_IO_ERROR; + } + reader_table[slot].pcsc.card = 0; + } + + err = pcsc_connect (reader_table[slot].pcsc.context, + reader_table[slot].rdrname, + PCSC_SHARE_EXCLUSIVE, + PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, + &reader_table[slot].pcsc.card, + &reader_table[slot].pcsc.protocol); + if (err) + { + log_error ("pcsc_connect failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + reader_table[slot].pcsc.card = 0; + return SW_HOST_CARD_IO_ERROR; + } + + + atrlen = 33; + nreader = sizeof reader - 1; + err = pcsc_status (reader_table[slot].pcsc.card, + reader, &nreader, + &card_state, &card_protocol, + reader_table[slot].atr, &atrlen); + if (err) + { + log_error ("pcsc_status failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + reader_table[slot].atrlen = 0; + return SW_HOST_CARD_IO_ERROR; + } + if (atrlen >= DIM (reader_table[0].atr)) + log_bug ("ATR returned by pcsc_status is too large\n"); + reader_table[slot].atrlen = atrlen; + return 0; +#endif /* !NEED_PCSC_WRAPPER */ } + static int -reset_pcsc_reader (int slot) +pcsc_get_status (int slot, unsigned int *status) { - return SW_HOST_NOT_SUPPORTED; +#ifdef NEED_PCSC_WRAPPER + long err; + reader_table_t slotp; + size_t len, full_len; + int i, n; + unsigned char msgbuf[9]; + unsigned char buffer[12]; + + slotp = reader_table + slot; + + if (slotp->pcsc.req_fd == -1 + || slotp->pcsc.rsp_fd == -1 + || slotp->pcsc.pid == (pid_t)(-1) ) + { + log_error ("pcsc_get_status: pcsc-wrapper not running\n"); + return SW_HOST_CARD_IO_ERROR; + } + + msgbuf[0] = 0x04; /* STATUS 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 STATUS 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 STATUS 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_status failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return SW_HOST_CARD_IO_ERROR; + } + + full_len = len; + + n = 8 < len ? 8 : len; + if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) || len != 8) + { + log_error ("error receiving PC/SC STATUS response: %s\n", + i? strerror (errno) : "premature EOF"); + goto command_failed; + } + + full_len -= len; + /* Newer versions of the wrapper might send more status bytes. + Read them. */ + 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; + } + + /* We are lucky: The wrapper already returns the data in the + required format. */ + *status = buffer[3]; + + return 0; + + 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_readerstate_s rdrstates[1]; + + memset (rdrstates, 0, sizeof *rdrstates); + rdrstates[0].reader = reader_table[slot].rdrname; + rdrstates[0].current_state = PCSC_STATE_UNAWARE; + err = pcsc_get_status_change (reader_table[slot].pcsc.context, + 0, + rdrstates, 1); + if (err == 0x8010000a) /* Timeout. */ + err = 0; + if (err) + { + log_error ("pcsc_get_status_change failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + return SW_HOST_CARD_IO_ERROR; + } + + + /* log_debug */ + /* ("pcsc_get_status_change: %s%s%s%s%s%s%s%s%s%s\n", */ + /* (rdrstates[0].event_state & PCSC_STATE_IGNORE)? " ignore":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_CHANGED)? " changed":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_UNKNOWN)? " unknown":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_UNAVAILABLE)?" unavail":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_EMPTY)? " empty":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_PRESENT)? " present":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_ATRMATCH)? " atr":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_EXCLUSIVE)? " excl":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_INUSE)? " unuse":"", */ + /* (rdrstates[0].event_state & PCSC_STATE_MUTE)? " mute":"" ); */ + + *status = 0; + if ( (rdrstates[0].event_state & PCSC_STATE_PRESENT) ) + *status |= 2; + if ( !(rdrstates[0].event_state & PCSC_STATE_MUTE) ) + *status |= 4; + /* We indicate a useful card if it is not in use by another + application. This is because we only use exclusive access + mode. */ + if ( (*status & 6) == 6 + && !(rdrstates[0].event_state & PCSC_STATE_INUSE) ) + *status |= 1; + + return 0; +#endif /*!NEED_PCSC_WRAPPER*/ } @@ -889,6 +1210,8 @@ close_pcsc_reader (int slot) #else /*!NEED_PCSC_WRAPPER*/ pcsc_release_context (reader_table[slot].pcsc.context); + xfree (reader_table[slot].rdrname); + reader_table[slot].rdrname = NULL; reader_table[slot].used = 0; return 0; #endif /*!NEED_PCSC_WRAPPER*/ @@ -999,8 +1322,14 @@ open_pcsc_reader (const char *portstr) slotp->pcsc.rsp_fd = rp[0]; /* Wait for the intermediate child to terminate. */ - while ( (i=pth_waitpid (pid, NULL, 0)) == -1 && errno == EINTR) +#ifdef USE_GNU_PTH +#define WAIT pth_waitpid +#else +#define WAIT waitpid +#endif + while ( (i=WAIT (pid, NULL, 0)) == -1 && errno == EINTR) ; +#undef X /* Now send the open request. */ msgbuf[0] = 0x01; /* OPEN command. */ @@ -1041,12 +1370,23 @@ open_pcsc_reader (const char *portstr) log_error ("PC/SC OPEN failed: %s\n", pcsc_error_string (err)); goto command_failed; } + + slotp->last_status = 0; + + /* The open fucntion may return a zero for the ATR length to + indicate that no card is present. */ n = len; - if ((i=readn (slotp->pcsc.rsp_fd, slotp->atr, n, &len)) || len != n) + if (n) { - log_error ("error receiving PC/SC OPEN response: %s\n", - i? strerror (errno) : "premature EOF"); - goto command_failed; + 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; + } + /* If we got to here we know that a card is present + and usable. Thus remember this. */ + slotp->last_status = (1|2|4| 0x8000); } slotp->atrlen = len; @@ -1132,41 +1472,63 @@ open_pcsc_reader (const char *portstr) p += strlen (p) + 1; } + reader_table[slot].rdrname = xtrymalloc (strlen (portstr? portstr : list)+1); + if (!reader_table[slot].rdrname) + { + log_error ("error allocating memory for reader name\n"); + pcsc_release_context (reader_table[slot].pcsc.context); + reader_table[slot].used = 0; + return -1; + } + strcpy (reader_table[slot].rdrname, portstr? portstr : list); + xfree (list); + err = pcsc_connect (reader_table[slot].pcsc.context, - portstr? portstr : list, + reader_table[slot].rdrname, PCSC_SHARE_EXCLUSIVE, PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, &reader_table[slot].pcsc.card, &reader_table[slot].pcsc.protocol); - if (err) + if (err == 0x8010000c) /* No smartcard. */ + reader_table[slot].pcsc.card = 0; + else if (err) { log_error ("pcsc_connect failed: %s (0x%lx)\n", pcsc_error_string (err), err); pcsc_release_context (reader_table[slot].pcsc.context); + xfree (reader_table[slot].rdrname); + reader_table[slot].rdrname = NULL; reader_table[slot].used = 0; xfree (list); return -1; } - - atrlen = 32; - /* (We need to pass a dummy buffer. We use LIST because it ought to - be large enough.) */ - err = pcsc_status (reader_table[slot].pcsc.card, - list, &listlen, - &card_state, &card_protocol, - reader_table[slot].atr, &atrlen); - xfree (list); - if (err) + + reader_table[slot].atrlen = 0; + reader_table[slot].last_status = 0; + if (!err) { - log_error ("pcsc_status failed: %s (0x%lx)\n", - pcsc_error_string (err), err); - pcsc_release_context (reader_table[slot].pcsc.context); - reader_table[slot].used = 0; - return -1; + char reader[250]; + unsigned long readerlen; + + atrlen = 32; + readerlen = sizeof reader -1 ; + err = pcsc_status (reader_table[slot].pcsc.card, + reader, &readerlen, + &card_state, &card_protocol, + reader_table[slot].atr, &atrlen); + if (err) + log_error ("pcsc_status failed: %s (0x%lx) %lu\n", + pcsc_error_string (err), err, readerlen); + else + { + if (atrlen >= DIM (reader_table[0].atr)) + log_bug ("ATR returned by pcsc_status is too large\n"); + reader_table[slot].atrlen = atrlen; + /* If we got to here we know that a card is present + and usable. Thus remember this. */ + reader_table[slot].last_status = (1|2|4| 0x8000); + } } - if (atrlen >= DIM (reader_table[0].atr)) - log_bug ("ATR returned by pcsc_status is too large\n"); - reader_table[slot].atrlen = atrlen; reader_table[slot].close_reader = close_pcsc_reader; reader_table[slot].reset_reader = reset_pcsc_reader; @@ -1311,6 +1673,12 @@ open_ccid_reader (const char *portstr) slotp->atrlen = 0; err = 0; } + else + { + /* If we got to here we know that a card is present + and usable. Thus remember this. */ + reader_table[slot].last_status = (1|2|4| 0x8000); + } reader_table[slot].close_reader = close_ccid_reader; reader_table[slot].shutdown_reader = shutdown_ccid_reader; @@ -1971,11 +2339,21 @@ apdu_open_reader (const char *portstr) if (!pcsc_list_readers) pcsc_list_readers = dlsym (handle, "SCardListReadersA"); #endif + pcsc_get_status_change = dlsym (handle, "SCardGetStatusChange"); +#ifdef _WIN32 + if (!pcsc_get_status_change) + pcsc_get_status_change = dlsym (handle, "SCardGetStatusChangeA"); +#endif pcsc_connect = dlsym (handle, "SCardConnect"); #ifdef _WIN32 if (!pcsc_connect) pcsc_connect = dlsym (handle, "SCardConnectA"); #endif + pcsc_reconnect = dlsym (handle, "SCardReconnect"); +#ifdef _WIN32 + if (!pcsc_reconnect) + pcsc_reconnect = dlsym (handle, "SCardReconnectA"); +#endif pcsc_disconnect = dlsym (handle, "SCardDisconnect"); pcsc_status = dlsym (handle, "SCardStatus"); #ifdef _WIN32 @@ -1990,7 +2368,9 @@ apdu_open_reader (const char *portstr) 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 @@ -2001,11 +2381,13 @@ apdu_open_reader (const char *portstr) /* 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)\n", + "(%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, @@ -2049,7 +2431,11 @@ apdu_open_remote_reader (const char *portstr, writefnc, writefnc_value, closefnc, closefnc_value); #else +#ifdef _WIN32 + errno = ENOENT; +#else errno = ENOSYS; +#endif return -1; #endif } @@ -2102,9 +2488,17 @@ apdu_reset (int slot) if ((sw = lock_slot (slot))) return sw; + reader_table[slot].last_status = 0; if (reader_table[slot].reset_reader) sw = reader_table[slot].reset_reader (slot); + if (!sw) + { + /* If we got to here we know that a card is present + and usable. Thus remember this. */ + reader_table[slot].last_status = (1|2|4| 0x8000); + } + unlock_slot (slot); return sw; } @@ -2139,7 +2533,16 @@ apdu_activate (int slot) /* We don't have an ATR or a card is present though inactive: do a reset now. */ if (reader_table[slot].reset_reader) - sw = reader_table[slot].reset_reader (slot); + { + reader_table[slot].last_status = 0; + sw = reader_table[slot].reset_reader (slot); + if (!sw) + { + /* If we got to here we know that a card is present + and usable. Thus remember this. */ + reader_table[slot].last_status = (1|2|4| 0x8000); + } + } } } @@ -2201,7 +2604,22 @@ apdu_get_status (int slot, int hang, unlock_slot (slot); if (sw) - return sw; + { + reader_table[slot].last_status = 0; + return sw; + } + + /* Keep track of changes. We use one extra bit to test whether we + have checked the status at least once. */ + if ( s != (reader_table[slot].last_status & 0x07ff) + || !reader_table[slot].last_status ) + { + reader_table[slot].change_counter++; + /* Make sure that the ATR is invalid so that a reset will be by + activate. */ + reader_table[slot].atrlen = 0; + } + reader_table[slot].last_status = (s | 0x8000); if (status) *status = s; |