diff --git a/scd/ChangeLog b/scd/ChangeLog index 970335aef..3e8292dee 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,3 +1,30 @@ +2005-09-05 Werner Koch + + * iso7816.c (iso7816_select_path): New. + * app-p15.c (select_ef_by_path): Allow for direct path selection. + (app_select_p15): Try using the beigian variant of pkcs#15. + (read_home_df): New. + (read_ef_odf): Generalized. + (read_ef_tokeninfo): New. + (read_p15_info): Set serialnumber from TokenInfo. + (app_select_p15): Don't munge serialNumber - that must be done + only once. + + * iso7816.c (iso7816_read_binary): Use Le=0 when reading all + data. Handle 6C00 error and take 6B00 as indication for EOF. + * apdu.h (SW_EXACT_LENGTH_P): New. + * apdu.c (new_reader_slot, reset_pcsc_reader, pcsc_get_status) + (open_pcsc_reader): Set new reader state IS_T0. + (apdu_send_le): When doing T=0 make sure not to send Lc and Le. + Problem reported by Carl Meijer. + (apdu_send_direct): Initialize RESULTLEN. + * pcsc-wrapper.c (handle_status): Return the current protocol as + a new third word. + +2005-08-05 Werner Koch + + * apdu.c (open_rapdu_reader): Set the reader number. + 2005-07-05 Werner Koch * app-openpgp.c (do_readkey): Return a mallcoed copy of the key as diff --git a/scd/apdu.c b/scd/apdu.c index 79022f21b..678ea12d3 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -15,7 +15,8 @@ * * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. * * $Id$ */ @@ -126,6 +127,7 @@ struct reader_table_s { char *rdrname; /* Name of the connected reader or NULL if unknown. */ int last_status; int status; + int is_t0; /* True if we know that we are running T=0. */ unsigned char atr[33]; size_t atrlen; /* A zero length indicates that the ATR has not yet been read; i.e. the card is not @@ -275,6 +277,9 @@ long (* DLSTDCALL pcsc_set_timeout) (unsigned long context, unsigned long timeout); +/* Prototypes. */ +static int pcsc_get_status (int slot, unsigned int *status); + /* @@ -319,6 +324,7 @@ new_reader_slot (void) reader_table[reader].used = 1; reader_table[reader].last_status = 0; + reader_table[reader].is_t0 = 1; #ifdef NEED_PCSC_WRAPPER reader_table[reader].pcsc.req_fd = -1; reader_table[reader].pcsc.rsp_fd = -1; @@ -768,6 +774,7 @@ reset_pcsc_reader (int slot) size_t len; int i, n; unsigned char msgbuf[9]; + unsigned int dummy_status; int sw = SW_HOST_CARD_IO_ERROR; slotp = reader_table + slot; @@ -841,6 +848,9 @@ reset_pcsc_reader (int slot) } slotp->atrlen = len; + /* Read the status so that IS_T0 will be set. */ + pcsc_get_status (slot, &dummy_status); + return 0; command_failed: @@ -902,6 +912,7 @@ reset_pcsc_reader (int slot) 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].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0); return 0; #endif /* !NEED_PCSC_WRAPPER */ @@ -917,7 +928,7 @@ pcsc_get_status (int slot, unsigned int *status) size_t len, full_len; int i, n; unsigned char msgbuf[9]; - unsigned char buffer[12]; + unsigned char buffer[16]; int sw = SW_HOST_CARD_IO_ERROR; slotp = reader_table + slot; @@ -968,14 +979,20 @@ pcsc_get_status (int slot, unsigned int *status) full_len = len; - n = 8 < len ? 8 : len; - if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) || len != 8) + /* The current version returns 3 words but we allow also for old + versions returning only 2 words. */ + n = 12 < len ? 12 : len; + if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) + || (len != 8 && len != 12)) { log_error ("error receiving PC/SC STATUS response: %s\n", i? strerror (errno) : "premature EOF"); goto command_failed; } + slotp->is_t0 = (len == 12 && !!(buffer[11] & PCSC_PROTOCOL_T0)); + + full_len -= len; /* Newer versions of the wrapper might send more status bytes. Read them. */ @@ -1296,6 +1313,7 @@ open_pcsc_reader (const char *portstr) size_t len; unsigned char msgbuf[9]; int err; + unsigned int dummy_status; int sw = SW_HOST_CARD_IO_ERROR; slot = new_reader_slot (); @@ -1440,7 +1458,7 @@ open_pcsc_reader (const char *portstr) slotp->last_status = 0; - /* The open fucntion may return a zero for the ATR length to + /* The open request may return a zero for the ATR length to indicate that no card is present. */ n = len; if (n) @@ -1463,6 +1481,9 @@ 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; + /* Read the status so that IS_T0 will be set. */ + pcsc_get_status (slot, &dummy_status); + dump_reader_status (slot); return slot; @@ -1596,6 +1617,7 @@ open_pcsc_reader (const char *portstr) /* 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].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0); } } @@ -1997,6 +2019,7 @@ open_rapdu_reader (int portno, return -1; } + rapdu_set_reader (slotp->rapdu.handle, portno); rapdu_set_iofunc (slotp->rapdu.handle, readfnc, readfnc_value, @@ -2518,7 +2541,7 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, if (lc != -1 && (lc > 255 || lc < 0)) return SW_WRONG_LENGTH; - if (le != -1 && (le > 256 || le < 1)) + if (le != -1 && (le > 256 || le < 0)) return SW_WRONG_LENGTH; if ((!data && lc != -1) || (data && lc == -1)) return SW_HOST_INV_VALUE; @@ -2536,9 +2559,13 @@ apdu_send_le(int slot, int class, int ins, int p0, int p1, apdu[apdulen++] = lc; memcpy (apdu+apdulen, data, lc); apdulen += lc; + /* T=0 does not allow the use of Lc together with Le; thus + disable Le in this case. */ + if (reader_table[slot].is_t0) + le = -1; } if (le != -1) - apdu[apdulen++] = le; /* Truncation is okay becuase 0 means 256. */ + apdu[apdulen++] = le; /* Truncation is okay because 0 means 256. */ assert (sizeof (apdu) >= apdulen); /* As safeguard don't pass any garbage from the stack to the driver. */ memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); @@ -2736,14 +2763,14 @@ apdu_send_direct (int slot, const unsigned char *apdudata, size_t apdudatalen, if ((sw = trylock_slot (slot))) return sw; - /* We simply trucntate a too long APDU. */ + /* We simply trunctate a too long APDU. */ if (apdudatalen > sizeof apdu) apdudatalen = sizeof apdu; apdulen = apdudatalen; memcpy (apdu, apdudata, apdudatalen); class = apdulen? *apdu : 0; - + resultlen = RESULTLEN; rc = send_apdu (slot, apdu, apdulen, result, &resultlen); if (rc || resultlen < 2) { diff --git a/scd/apdu.h b/scd/apdu.h index e0f50b72b..45388fdd1 100644 --- a/scd/apdu.h +++ b/scd/apdu.h @@ -15,7 +15,8 @@ * * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. * * $Id$ */ @@ -41,6 +42,7 @@ enum { SW_RECORD_NOT_FOUND = 0x6a83, SW_REF_NOT_FOUND = 0x6a88, SW_BAD_P0_P1 = 0x6b00, + SW_EXACT_LENGTH = 0x6c00, SW_INS_NOT_SUP = 0x6d00, SW_CLA_NOT_SUP = 0x6e00, SW_SUCCESS = 0x9000, @@ -65,6 +67,8 @@ enum { }; +#define SW_EXACT_LENGTH_P(a) (((a)&~0xff) == SW_EXACT_LENGTH) + /* Note , that apdu_open_reader returns no status word but -1 on error. */ int apdu_open_reader (const char *portstr); diff --git a/scd/app-p15.c b/scd/app-p15.c index f03e5d5f0..c8d38850b 100644 --- a/scd/app-p15.c +++ b/scd/app-p15.c @@ -74,7 +74,17 @@ static struct #undef X -/* The Pin Types as defined in pkcs#15 v1.1 */ +/* The AID of PKCS15. */ +static char const pkcs15_aid[] = { 0xA0, 0, 0, 0, 0x63, + 0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 }; + +/* The Belgian eID variant - they didn't understood why a shared AID + is useful for a standard. Oh well. */ +static char const pkcs15be_aid[] = { 0xA0, 0, 0, 0x01, 0x77, + 0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 }; + + +/* The PIN types as defined in pkcs#15 v1.1 */ typedef enum { PIN_TYPE_BCD = 0, @@ -261,6 +271,9 @@ struct app_local_s /* The type of the card. */ card_type_t card_type; + /* Flag indicating whether we may use direct path selection. */ + int direct_path_selection; + /* Structure with the EFIDs of the objects described in the ODF file. */ struct @@ -276,6 +289,10 @@ struct app_local_s unsigned short auth_objects; } odf; + /* The PKCS#15 serialnumber from EF(TokeiNFo) or NULL. Malloced. */ + unsigned char *serialno; + size_t serialnolen; + /* Information on all certificates. */ cdf_object_t certificate_info; /* Information on all trusted certificates. */ @@ -363,6 +380,7 @@ do_deinit (app_t app) release_cdflist (app->app_local->useful_certificate_info); release_prkdflist (app->app_local->private_key_info); release_aodflist (app->app_local->auth_object_info); + xfree (app->app_local->serialno); xfree (app->app_local); app->app_local = NULL; } @@ -406,26 +424,44 @@ select_ef_by_path (app_t app, const unsigned short *path, size_t pathlen) gpg_error_t err; int i, j; - /* FIXME: Need code to remember the last PATH so that we can decide - what select commands to send in case the path does not start off - with 3F00. We might also want to use direct path selection if - supported by the card. */ + if (!pathlen) + return gpg_error (GPG_ERR_INV_VALUE); + if (pathlen && *path != 0x3f00 ) log_debug ("WARNING: relative path selection not yet implemented\n"); - - for (i=0; i < pathlen; i++) + + if (app->app_local->direct_path_selection) { - err = iso7816_select_file (app->slot, path[i], - !(i+1 == pathlen), NULL, NULL); + err = iso7816_select_path (app->slot, path+1, pathlen-1, NULL, NULL); if (err) { - log_error ("error selecting part %d from path ", i); + log_error ("error selecting path "); for (j=0; j < pathlen; j++) log_printf ("%04hX", path[j]); log_printf (": %s\n", gpg_strerror (err)); return err; } } + else + { + /* FIXME: Need code to remember the last PATH so that we can decide + what select commands to send in case the path does not start off + with 3F00. We might also want to use direct path selection if + supported by the card. */ + for (i=0; i < pathlen; i++) + { + err = iso7816_select_file (app->slot, path[i], + !(i+1 == pathlen), NULL, NULL); + if (err) + { + log_error ("error selecting part %d from path ", i); + for (j=0; j < pathlen; j++) + log_printf ("%04hX", path[j]); + log_printf (": %s\n", gpg_strerror (err)); + return err; + } + } + } return 0; } @@ -586,12 +622,13 @@ read_ef_odf (app_t app, unsigned short odf_fid) } else if ( buflen >= 12 && (p[0] & 0xf0) == 0xA0 - && !memcmp (p+1, "\x0a\x30\x08\x04\x06\x3F\x00\x50\x15", 9) - && app->app_local->home_df == 0x5015 ) + && !memcmp (p+1, "\x0a\x30\x08\x04\x06\x3F\x00", 7) + && app->app_local->home_df == ((p[8]<<8)|p[9]) ) { - /* This format using a full path is used by a self-created - test card of mine. I have not checked whether this is - legal. We assume a home DF of 0x5015 here. */ + /* We only allow a full path if all files are at the same + level and below the home directory. The extend this we + would need to make use of new data type capable of + keeping a full path. */ offset = 10; } else @@ -2158,12 +2195,81 @@ TokenFlags ::= BIT STRING { */ -/* static gpg_error_t */ -/* read_ef_tokeninfo (app_t app) */ -/* { */ -/* unsigned short efid = 0x5032; */ -/* return 0; */ -/* } */ +static gpg_error_t +read_ef_tokeninfo (app_t app) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + size_t buflen; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, constructed, ndef; + unsigned long ul; + + err = select_and_read_binary (app->slot, 0x5032, "TokenInfo", + &buffer, &buflen); + if (err) + return err; + + p = buffer; + n = buflen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + { + log_error ("error parsing TokenInfo: %s\n", gpg_strerror (err)); + goto leave; + } + + n = objlen; + + /* Version. */ + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != TAG_INTEGER)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*p++) & 0xff; + n--; + } + if (ul) + { + log_error ("invalid version %lu in TokenInfo\n", ul); + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + + /* serialNumber. */ + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != TAG_OCTET_STRING || !objlen)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto leave; + + xfree (app->app_local->serialno); + app->app_local->serialno = xtrymalloc (objlen); + if (!app->app_local->serialno) + { + err = gpg_error_from_errno (errno); + goto leave; + } + memcpy (app->app_local->serialno, p, objlen); + app->app_local->serialnolen = objlen; + log_printhex ("Serialnumber from EF(TokenInfo) is:", p, objlen); + + leave: + xfree (buffer); + return err; +} /* Get all the basic information from the pkcs#15 card, check the @@ -2174,11 +2280,25 @@ read_p15_info (app_t app) { gpg_error_t err; - /* Fixme: We might need to read the tokeninfo to get a non-standard - ODF FID. */ - + if (!read_ef_tokeninfo (app)) + { + /* If we don't have a serial number yet but the TokenInfo provides + one, use that. */ + if (!app->serialno && app->app_local->serialno) + { + app->serialno = app->app_local->serialno; + app->serialnolen = app->app_local->serialnolen; + app->app_local->serialno = NULL; + app->app_local->serialnolen = 0; + err = app_munge_serialno (app); + if (err) + return err; + } + } + /* Read the ODF so that we know the location of all directory files. */ + /* Fixme: We might need to get a non-standard ODF FID from TokenInfo. */ err = read_ef_odf (app, 0x5031); if (err) return err; @@ -2895,22 +3015,88 @@ do_sign (app_t app, const char *keyidstr, int hashalgo, } + +/* Assume that EF(DIR) has been selected. Read its content and figure + out the home EF of pkcs#15. Return that home DF or 0 if not + found. */ +static unsigned short +read_home_df (int slot) +{ + gpg_error_t err; + unsigned char *buffer; + const unsigned char *p, *pp; + size_t buflen, n, nn; + unsigned short result = 0; + + err = iso7816_read_binary (slot, 0, 0, &buffer, &buflen); + if (err) + { + log_error ("error reading EF{DIR}: %s\n", gpg_strerror (err)); + return 0; + } + + /* FIXME: We need to scan all records. */ + p = find_tlv (buffer, buflen, 0x61, &n); + if (p && n) + { + pp = find_tlv (p, n, 0x4f, &nn); + if (pp + && ((nn == sizeof pkcs15_aid && !memcmp (pp, pkcs15_aid, nn)) + ||(nn == sizeof pkcs15be_aid && !memcmp (pp, pkcs15be_aid, nn)))) + { + pp = find_tlv (p, n, 0x50, &nn); + if (pp) /* fixme: Filter log value? */ + log_info ("pkcs#15 application label from EF(DIR) is `%.*s'\n", + (int)nn, pp); + pp = find_tlv (p, n, 0x51, &nn); + if (pp && nn == 4 && *pp == 0x3f && !pp[1]) + { + result = ((pp[2] << 8) | pp[3]); + log_info ("pkcs#15 application directory is 0x%04hX\n", result); + } + } + } + xfree (buffer); + return result; +} -/* Select the PKCS#15 application on the card in SLOT. */ +/* + Select the PKCS#15 application on the card in SLOT. + */ gpg_error_t app_select_p15 (app_t app) { - static char const aid[] = { 0xA0, 0, 0, 0, 0x63, - 0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 }; int slot = app->slot; int rc; unsigned short def_home_df = 0; card_type_t card_type = CARD_TYPE_UNKNOWN; + int direct = 0; - rc = iso7816_select_application (slot, aid, sizeof aid); + rc = iso7816_select_application (slot, pkcs15_aid, sizeof pkcs15_aid); if (rc) - { + rc = iso7816_select_application (slot, pkcs15be_aid, sizeof pkcs15be_aid); + if (rc) + { /* Not found: Try to locate it from 2F00. We use direct path + selection here because it seems that the Belgian eID card + does only allow for that. Many other cards supports this + selection method too. */ + unsigned short path[1] = { 0x2f00 }; + + rc = iso7816_select_path (app->slot, path, 1, NULL, NULL); + if (!rc) + { + direct = 1; + def_home_df = read_home_df (slot); + if (def_home_df) + { + path[0] = def_home_df; + rc = iso7816_select_path (app->slot, path, 1, NULL, NULL); + } + } + } + if (rc) + { /* Still not found: Try the default DF. */ def_home_df = 0x5015; rc = iso7816_select_file (slot, def_home_df, 1, NULL, NULL); } @@ -2958,6 +3144,9 @@ app_select_p15 (app_t app) the common APP structure. */ app->app_local->card_type = card_type; + /* Store whether we may and should use direct path selection. */ + app->app_local->direct_path_selection = direct; + /* Read basic information and thus check whether this is a real card. */ rc = read_p15_info (app); @@ -2989,8 +3178,6 @@ app_select_p15 (app_t app) app->serialno = p; } } - else /* Use standard munging code. */ - rc = app_munge_serialno (app); app->fnc.deinit = do_deinit; app->fnc.learn_status = do_learn_status; diff --git a/scd/iso7816.c b/scd/iso7816.c index 742ed9433..484906251 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -77,6 +77,7 @@ map_sw (int sw) case SW_RECORD_NOT_FOUND:ec= GPG_ERR_NOT_FOUND; break; case SW_REF_NOT_FOUND: ec = GPG_ERR_NO_OBJ; break; case SW_BAD_P0_P1: ec = GPG_ERR_INV_VALUE; break; + case SW_EXACT_LENGTH: ec = GPG_ERR_INV_VALUE; break; case SW_INS_NOT_SUP: ec = GPG_ERR_CARD; break; case SW_CLA_NOT_SUP: ec = GPG_ERR_CARD; break; case SW_SUCCESS: ec = 0; break; @@ -161,6 +162,39 @@ iso7816_select_file (int slot, int tag, int is_dir, } +/* Do a select file command with a direct path. */ +gpg_error_t +iso7816_select_path (int slot, const unsigned short *path, size_t pathlen, + unsigned char **result, size_t *resultlen) +{ + int sw, p0, p1; + unsigned char buffer[100]; + int buflen; + + if (result || resultlen) + { + *result = NULL; + *resultlen = 0; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + + if (pathlen/2 >= sizeof buffer) + return gpg_error (GPG_ERR_TOO_LARGE); + + for (buflen = 0; pathlen; pathlen--, path++) + { + buffer[buflen++] = (*path >> 8); + buffer[buflen++] = *path; + } + + p0 = 0x08; + p1 = 0x0c; /* No FC return. */ + sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, + p0, p1, buflen, (char*)buffer ); + return map_sw (sw); +} + + /* This is a private command currently only working for TCOS cards. */ gpg_error_t iso7816_list_directory (int slot, int list_dirs, @@ -524,8 +558,10 @@ iso7816_read_binary (int slot, size_t offset, size_t nmax, { buffer = NULL; bufferlen = 0; - /* Fixme: Either the ccid driver or the TCOS cards have problems - with an Le of 0. */ + /* Note, that we to set N to 254 due to problems either with the + ccid driver or some TCOS cards. It actually should be 0 + which is the official ISO value to read a variable length + object. */ if (read_all || nmax > 254) n = 254; else @@ -533,6 +569,21 @@ iso7816_read_binary (int slot, size_t offset, size_t nmax, sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY, ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, n, &buffer, &bufferlen); + if ( SW_EXACT_LENGTH_P(sw) ) + { + n = (sw & 0x00ff); + sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY, + ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, + n, &buffer, &bufferlen); + } + + if (*result && sw == SW_BAD_P0_P1) + { + /* Bad Parameter means that the offset is outside of the + EF. When reading all data we take this as an indication + for EOF. */ + break; + } if (sw != SW_SUCCESS && sw != SW_EOF_REACHED) { diff --git a/scd/iso7816.h b/scd/iso7816.h index b9ba1800b..828fabb01 100644 --- a/scd/iso7816.h +++ b/scd/iso7816.h @@ -33,6 +33,9 @@ gpg_error_t iso7816_select_application (int slot, const char *aid, size_t aidlen); gpg_error_t iso7816_select_file (int slot, int tag, int is_dir, unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_select_path (int slot, + const unsigned short *path, size_t pathlen, + unsigned char **result, size_t *resultlen); gpg_error_t iso7816_list_directory (int slot, int list_dirs, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_verify (int slot, diff --git a/scd/pcsc-wrapper.c b/scd/pcsc-wrapper.c index 21af16fba..f149e785a 100644 --- a/scd/pcsc-wrapper.c +++ b/scd/pcsc-wrapper.c @@ -15,7 +15,8 @@ * * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. */ /* @@ -587,6 +588,11 @@ handle_status (unsigned char *argbuf, size_t arglen) buf[5] = (rdrstates[0].event_state >> 16); buf[6] = (rdrstates[0].event_state >> 8); buf[7] = (rdrstates[0].event_state >> 0); + /* The third word is the protocol. */ + buf[8] = (pcsc_protocol >> 24); + buf[9] = (pcsc_protocol >> 16); + buf[10] = (pcsc_protocol >> 8); + buf[11] = (pcsc_protocol); request_succeeded (buf, 8); }