summaryrefslogtreecommitdiffstats
path: root/scd/apdu.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
commiteee068778cb28ecf3c14e1bf843a95547d72c42d (patch)
tree0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /scd/apdu.c
parentInitial commit. (diff)
downloadgnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.tar.xz
gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.zip
Adding upstream version 2.2.40.upstream/2.2.40upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--scd/apdu.c3523
1 files changed, 3523 insertions, 0 deletions
diff --git a/scd/apdu.c b/scd/apdu.c
new file mode 100644
index 0000000..9568d25
--- /dev/null
+++ b/scd/apdu.c
@@ -0,0 +1,3523 @@
+/* apdu.c - ISO 7816 APDU functions and low level I/O
+ * Copyright (C) 2003, 2004, 2008, 2009, 2010,
+ * 2011 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+/* NOTE: This module is also used by other software, thus the use of
+ the macro USE_NPTH is mandatory. For GnuPG this macro is
+ guaranteed to be defined true. */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#ifdef USE_NPTH
+# include <unistd.h>
+# include <fcntl.h>
+# include <npth.h>
+#endif
+
+
+/* If requested include the definitions for the remote APDU protocol
+ code. */
+#ifdef USE_G10CODE_RAPDU
+#include "rapdu.h"
+#endif /*USE_G10CODE_RAPDU*/
+
+#if defined(GNUPG_SCD_MAIN_HEADER)
+#include GNUPG_SCD_MAIN_HEADER
+#elif GNUPG_MAJOR_VERSION == 1
+/* This is used with GnuPG version < 1.9. The code has been source
+ copied from the current GnuPG >= 1.9 and is maintained over
+ there. */
+#include "../common/options.h"
+#include "errors.h"
+#include "memory.h"
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "dynload.h"
+#include "cardglue.h"
+#else /* GNUPG_MAJOR_VERSION != 1 */
+#include "scdaemon.h"
+#include "../common/exechelp.h"
+#endif /* GNUPG_MAJOR_VERSION != 1 */
+#include "../common/host2net.h"
+#include "../common/membuf.h"
+
+#include "iso7816.h"
+#include "apdu.h"
+#define CCID_DRIVER_INCLUDE_USB_IDS 1
+#include "ccid-driver.h"
+
+struct dev_list {
+ void *table;
+ const char *portstr;
+ int idx;
+ int idx_max;
+};
+
+#define MAX_READER 4 /* Number of readers we support concurrently. */
+
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+#define DLSTDCALL __stdcall
+#else
+#define DLSTDCALL
+#endif
+
+#if defined(__APPLE__) || defined(_WIN32) || defined(__CYGWIN__)
+typedef unsigned int pcsc_dword_t;
+#else
+typedef unsigned long pcsc_dword_t;
+#endif
+
+/* PC/SC context to access readers. Shared among all readers. */
+static struct pcsc {
+ unsigned int context_valid:1;
+ long context;
+ char *reader_list; /* List of detected readers. */
+} pcsc;
+
+/* A structure to collect information pertaining to one reader
+ slot. */
+struct reader_table_s {
+ int used; /* True if slot is used. */
+ unsigned short port; /* Port number: 0 = unused, 1 - dev/tty */
+
+ /* Function pointers initialized to the various backends. */
+ int (*connect_card)(int);
+ int (*disconnect_card)(int);
+ int (*close_reader)(int);
+ int (*reset_reader)(int);
+ int (*get_status_reader)(int, unsigned int *, int);
+ int (*send_apdu_reader)(int,unsigned char *,size_t,
+ unsigned char *, size_t *, pininfo_t *);
+ int (*check_pinpad)(int, int, pininfo_t *);
+ void (*dump_status_reader)(int);
+ int (*set_progress_cb)(int, gcry_handler_progress_t, void*);
+ int (*set_prompt_cb)(int, void (*) (void *, int), void*);
+ int (*pinpad_verify)(int, int, int, int, int, pininfo_t *);
+ int (*pinpad_modify)(int, int, int, int, int, pininfo_t *);
+
+ struct {
+ ccid_driver_t handle;
+ } ccid;
+ struct {
+ long card;
+ pcsc_dword_t protocol;
+ pcsc_dword_t verify_ioctl;
+ pcsc_dword_t modify_ioctl;
+ int pinmin;
+ int pinmax;
+ pcsc_dword_t current_state;
+ } pcsc;
+#ifdef USE_G10CODE_RAPDU
+ struct {
+ rapdu_t handle;
+ } rapdu;
+#endif /*USE_G10CODE_RAPDU*/
+ char *rdrname; /* Name of the connected reader or NULL if unknown. */
+ unsigned int is_t0:1; /* True if we know that we are running T=0. */
+ unsigned int is_spr532:1; /* True if we know that the reader is a SPR532. */
+ unsigned int pinpad_varlen_supported:1; /* True if we know that the reader
+ supports variable length pinpad
+ input. */
+ unsigned int require_get_status:1;
+ 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
+ ready for use. */
+#ifdef USE_NPTH
+ npth_mutex_t lock;
+#endif
+};
+typedef struct reader_table_s *reader_table_t;
+
+/* A global table to keep track of active readers. */
+static struct reader_table_s reader_table[MAX_READER];
+
+#ifdef USE_NPTH
+static npth_mutex_t reader_table_lock;
+#endif
+
+
+/* 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
+#ifdef HAVE_W32_SYSTEM
+# define PCSC_PROTOCOL_RAW 0x00010000 /* The active protocol. */
+#else
+# define PCSC_PROTOCOL_RAW 4
+#endif
+
+#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
+
+#ifdef HAVE_W32_SYSTEM
+# define PCSC_UNKNOWN 0x0000 /* The driver is not aware of the status. */
+# define PCSC_ABSENT 0x0001 /* Card is absent. */
+# define PCSC_PRESENT 0x0002 /* Card is present. */
+# define PCSC_SWALLOWED 0x0003 /* Card is present and electrical connected. */
+# define PCSC_POWERED 0x0004 /* Card is powered. */
+# define PCSC_NEGOTIABLE 0x0005 /* Card is awaiting PTS. */
+# define PCSC_SPECIFIC 0x0006 /* Card is ready for use. */
+#else
+# 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. */
+#endif
+
+#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. */
+#ifdef HAVE_W32_SYSTEM
+# define PCSC_STATE_UNPOWERED 0x0400 /* Card not powerred up. */
+#endif
+
+/* Some PC/SC error codes. */
+#define PCSC_E_CANCELLED 0x80100002
+#define PCSC_E_CANT_DISPOSE 0x8010000E
+#define PCSC_E_INSUFFICIENT_BUFFER 0x80100008
+#define PCSC_E_INVALID_ATR 0x80100015
+#define PCSC_E_INVALID_HANDLE 0x80100003
+#define PCSC_E_INVALID_PARAMETER 0x80100004
+#define PCSC_E_INVALID_TARGET 0x80100005
+#define PCSC_E_INVALID_VALUE 0x80100011
+#define PCSC_E_NO_MEMORY 0x80100006
+#define PCSC_E_UNKNOWN_READER 0x80100009
+#define PCSC_E_TIMEOUT 0x8010000A
+#define PCSC_E_SHARING_VIOLATION 0x8010000B
+#define PCSC_E_NO_SMARTCARD 0x8010000C
+#define PCSC_E_UNKNOWN_CARD 0x8010000D
+#define PCSC_E_PROTO_MISMATCH 0x8010000F
+#define PCSC_E_NOT_READY 0x80100010
+#define PCSC_E_SYSTEM_CANCELLED 0x80100012
+#define PCSC_E_NOT_TRANSACTED 0x80100016
+#define PCSC_E_READER_UNAVAILABLE 0x80100017
+#define PCSC_E_NO_SERVICE 0x8010001D
+#define PCSC_E_NO_READERS_AVAILABLE 0x8010002E
+#define PCSC_E_SERVICE_STOPPED 0x8010001E
+#define PCSC_W_RESET_CARD 0x80100068
+#define PCSC_W_REMOVED_CARD 0x80100069
+
+/* Fix pcsc-lite ABI incompatibility. */
+#ifndef SCARD_CTL_CODE
+#ifdef _WIN32
+#include <winioctl.h>
+#define SCARD_CTL_CODE(code) CTL_CODE(FILE_DEVICE_SMARTCARD, (code), \
+ METHOD_BUFFERED, FILE_ANY_ACCESS)
+#else
+#define SCARD_CTL_CODE(code) (0x42000000 + (code))
+#endif
+#endif
+
+#define CM_IOCTL_GET_FEATURE_REQUEST SCARD_CTL_CODE(3400)
+#define CM_IOCTL_VENDOR_IFD_EXCHANGE SCARD_CTL_CODE(1)
+#define FEATURE_VERIFY_PIN_DIRECT 0x06
+#define FEATURE_MODIFY_PIN_DIRECT 0x07
+#define FEATURE_GET_TLV_PROPERTIES 0x12
+
+#define PCSCv2_PART10_PROPERTY_bEntryValidationCondition 2
+#define PCSCv2_PART10_PROPERTY_bTimeOut2 3
+#define PCSCv2_PART10_PROPERTY_bMinPINSize 6
+#define PCSCv2_PART10_PROPERTY_bMaxPINSize 7
+#define PCSCv2_PART10_PROPERTY_wIdVendor 11
+#define PCSCv2_PART10_PROPERTY_wIdProduct 12
+
+
+/* The PC/SC error is defined as a long as per specs. Due to left
+ shifts bit 31 will get sign extended. We use this mask to fix
+ it. */
+#define PCSC_ERR_MASK(a) ((a) & 0xffffffff)
+
+
+struct pcsc_io_request_s
+{
+ unsigned long protocol;
+ unsigned long pci_len;
+};
+
+typedef struct pcsc_io_request_s *pcsc_io_request_t;
+
+#ifdef __APPLE__
+#pragma pack(1)
+#endif
+
+struct pcsc_readerstate_s
+{
+ const char *reader;
+ void *user_data;
+ pcsc_dword_t current_state;
+ pcsc_dword_t event_state;
+ pcsc_dword_t atrlen;
+ unsigned char atr[33];
+};
+
+#ifdef __APPLE__
+#pragma pack()
+#endif
+
+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_cancel) (long context);
+long (* DLSTDCALL pcsc_list_readers) (long context,
+ const char *groups,
+ char *readers, pcsc_dword_t*readerslen);
+long (* DLSTDCALL pcsc_get_status_change) (long context,
+ pcsc_dword_t timeout,
+ pcsc_readerstate_t readerstates,
+ pcsc_dword_t nreaderstates);
+long (* DLSTDCALL pcsc_connect) (long context,
+ const char *reader,
+ pcsc_dword_t share_mode,
+ pcsc_dword_t preferred_protocols,
+ long *r_card,
+ pcsc_dword_t *r_active_protocol);
+long (* DLSTDCALL pcsc_reconnect) (long 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,
+ pcsc_dword_t disposition);
+long (* DLSTDCALL pcsc_status) (long 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,
+ pcsc_dword_t disposition);
+long (* DLSTDCALL pcsc_transmit) (long card,
+ const pcsc_io_request_t send_pci,
+ const unsigned char *send_buffer,
+ pcsc_dword_t send_len,
+ pcsc_io_request_t recv_pci,
+ unsigned char *recv_buffer,
+ pcsc_dword_t *recv_len);
+long (* DLSTDCALL pcsc_set_timeout) (long context,
+ pcsc_dword_t timeout);
+long (* DLSTDCALL pcsc_control) (long card,
+ pcsc_dword_t control_code,
+ const void *send_buffer,
+ pcsc_dword_t send_len,
+ void *recv_buffer,
+ pcsc_dword_t recv_len,
+ pcsc_dword_t *bytes_returned);
+
+
+/* Prototypes. */
+static int pcsc_vendor_specific_init (int slot);
+static int pcsc_get_status (int slot, unsigned int *status, int on_wire);
+static int reset_pcsc_reader (int slot);
+static int apdu_get_status_internal (int slot, int hang, unsigned int *status,
+ int on_wire);
+static int check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo);
+static int pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
+ pininfo_t *pininfo);
+static int pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1,
+ pininfo_t *pininfo);
+
+
+
+/*
+ * Helper
+ */
+
+/* Return true if (BUFFER,LENGTH) consists of only binary zeroes. */
+static int
+all_zero_p (const void *buffer, size_t length)
+{
+ const unsigned char *p;
+
+ for (p=buffer; length; p++, length--)
+ if (*p)
+ return 0;
+ return 1;
+}
+
+
+
+static int
+lock_slot (int slot)
+{
+#ifdef USE_NPTH
+ int err;
+
+ err = npth_mutex_lock (&reader_table[slot].lock);
+ if (err)
+ {
+ log_error ("failed to acquire apdu lock: %s\n", strerror (err));
+ return SW_HOST_LOCKING_FAILED;
+ }
+#endif /*USE_NPTH*/
+ return 0;
+}
+
+static int
+trylock_slot (int slot)
+{
+#ifdef USE_NPTH
+ int err;
+
+ err = npth_mutex_trylock (&reader_table[slot].lock);
+ if (err == EBUSY)
+ return SW_HOST_BUSY;
+ else if (err)
+ {
+ log_error ("failed to acquire apdu lock: %s\n", strerror (err));
+ return SW_HOST_LOCKING_FAILED;
+ }
+#endif /*USE_NPTH*/
+ return 0;
+}
+
+static void
+unlock_slot (int slot)
+{
+#ifdef USE_NPTH
+ int err;
+
+ err = npth_mutex_unlock (&reader_table[slot].lock);
+ if (err)
+ log_error ("failed to release apdu lock: %s\n", strerror (errno));
+#endif /*USE_NPTH*/
+}
+
+
+/* Find an unused reader slot for PORTSTR and put it into the reader
+ table. Return -1 on error or the index into the reader table.
+ Acquire slot's lock on successful return. Caller needs to unlock it. */
+static int
+new_reader_slot (void)
+{
+ int i, reader = -1;
+
+ for (i=0; i < MAX_READER; i++)
+ if (!reader_table[i].used)
+ {
+ reader = i;
+ reader_table[reader].used = 1;
+ break;
+ }
+
+ if (reader == -1)
+ {
+ log_error ("new_reader_slot: out of slots\n");
+ return -1;
+ }
+
+ if (lock_slot (reader))
+ {
+ reader_table[reader].used = 0;
+ return -1;
+ }
+
+ reader_table[reader].connect_card = NULL;
+ reader_table[reader].disconnect_card = NULL;
+ reader_table[reader].close_reader = NULL;
+ reader_table[reader].reset_reader = NULL;
+ reader_table[reader].get_status_reader = NULL;
+ reader_table[reader].send_apdu_reader = NULL;
+ reader_table[reader].check_pinpad = check_pcsc_pinpad;
+ reader_table[reader].dump_status_reader = NULL;
+ reader_table[reader].set_progress_cb = NULL;
+ reader_table[reader].set_prompt_cb = NULL;
+ reader_table[reader].pinpad_verify = pcsc_pinpad_verify;
+ reader_table[reader].pinpad_modify = pcsc_pinpad_modify;
+
+ reader_table[reader].is_t0 = 1;
+ reader_table[reader].is_spr532 = 0;
+ reader_table[reader].pinpad_varlen_supported = 0;
+ reader_table[reader].require_get_status = 1;
+ reader_table[reader].pcsc.verify_ioctl = 0;
+ reader_table[reader].pcsc.modify_ioctl = 0;
+ reader_table[reader].pcsc.pinmin = -1;
+ reader_table[reader].pcsc.pinmax = -1;
+ reader_table[reader].pcsc.current_state = PCSC_STATE_UNAWARE;
+
+ return reader;
+}
+
+
+static void
+dump_reader_status (int slot)
+{
+ if (!opt.verbose)
+ return;
+
+ if (reader_table[slot].dump_status_reader)
+ reader_table[slot].dump_status_reader (slot);
+
+ if (reader_table[slot].atrlen)
+ {
+ log_info ("slot %d: ATR=", slot);
+ log_printhex (reader_table[slot].atr, reader_table[slot].atrlen, "");
+ }
+}
+
+
+
+static const char *
+host_sw_string (long err)
+{
+ switch (err)
+ {
+ case 0: return "okay";
+ case SW_HOST_OUT_OF_CORE: return "out of core";
+ case SW_HOST_INV_VALUE: return "invalid value";
+ case SW_HOST_NO_DRIVER: return "no driver";
+ case SW_HOST_NOT_SUPPORTED: return "not supported";
+ case SW_HOST_LOCKING_FAILED: return "locking failed";
+ case SW_HOST_BUSY: return "busy";
+ case SW_HOST_NO_CARD: return "no card";
+ case SW_HOST_CARD_INACTIVE: return "card inactive";
+ case SW_HOST_CARD_IO_ERROR: return "card I/O error";
+ case SW_HOST_GENERAL_ERROR: return "general error";
+ case SW_HOST_NO_READER: return "no reader";
+ case SW_HOST_ABORTED: return "aborted";
+ case SW_HOST_NO_PINPAD: return "no pinpad";
+ case SW_HOST_ALREADY_CONNECTED: return "already connected";
+ case SW_HOST_CANCELLED: return "cancelled";
+ case SW_HOST_USB_OTHER: return "USB general error";
+ case SW_HOST_USB_IO: return "USB I/O error";
+ case SW_HOST_USB_ACCESS: return "USB permission denied";
+ case SW_HOST_USB_NO_DEVICE:return "USB no device";
+ case SW_HOST_USB_BUSY: return "USB busy";
+ case SW_HOST_USB_TIMEOUT: return "USB timeout";
+ case SW_HOST_USB_OVERFLOW: return "USB overflow";
+ default: return "unknown host status error";
+ }
+}
+
+
+const char *
+apdu_strerror (int rc)
+{
+ switch (rc)
+ {
+ case SW_EOF_REACHED : return "eof reached";
+ case SW_EEPROM_FAILURE : return "eeprom failure";
+ case SW_WRONG_LENGTH : return "wrong length";
+ case SW_SM_NOT_SUP : return "secure messaging not supported";
+ case SW_CC_NOT_SUP : return "command chaining not supported";
+ case SW_FILE_STRUCT : return "command can't be used for file structure.";
+ case SW_CHV_WRONG : return "CHV wrong";
+ case SW_CHV_BLOCKED : return "CHV blocked";
+ case SW_REF_DATA_INV : return "referenced data invalidated";
+ case SW_USE_CONDITIONS : return "use conditions not satisfied";
+ case SW_NO_CURRENT_EF : return "no current EF selected";
+ case SW_BAD_PARAMETER : return "bad parameter";
+ case SW_NOT_SUPPORTED : return "not supported";
+ case SW_FILE_NOT_FOUND : return "file not found";
+ case SW_RECORD_NOT_FOUND:return "record not found";
+ case SW_REF_NOT_FOUND : return "reference not found";
+ case SW_NOT_ENOUGH_MEMORY: return "not enough memory space in the file";
+ case SW_INCONSISTENT_LC: return "Lc inconsistent with TLV structure.";
+ case SW_INCORRECT_P0_P1: return "incorrect parameters P0,P1";
+ case SW_BAD_LC : return "Lc inconsistent with P0,P1";
+ case SW_BAD_P0_P1 : return "bad P0,P1";
+ case SW_INS_NOT_SUP : return "instruction not supported";
+ case SW_CLA_NOT_SUP : return "class not supported";
+ case SW_SUCCESS : return "success";
+ default:
+ if ((rc & ~0x00ff) == SW_MORE_DATA)
+ return "more data available";
+ if ( (rc & 0x10000) )
+ return host_sw_string (rc);
+ return "unknown status error";
+ }
+}
+
+/*
+ PC/SC Interface
+ */
+
+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;
+ case 0x002e: s = "no readers available"; break;
+ default: s = "unknown PC/SC error code"; break;
+ }
+ return s;
+}
+
+/* Map PC/SC error codes to our special host status words. */
+static int
+pcsc_error_to_sw (long ec)
+{
+ int rc;
+
+ switch ( PCSC_ERR_MASK (ec) )
+ {
+ case 0: rc = 0; break;
+
+ case PCSC_E_CANCELLED: rc = SW_HOST_CANCELLED; break;
+ case PCSC_E_NO_MEMORY: rc = SW_HOST_OUT_OF_CORE; break;
+ case PCSC_E_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break;
+ case PCSC_E_NO_SERVICE:
+ case PCSC_E_SERVICE_STOPPED:
+ case PCSC_E_UNKNOWN_READER: rc = SW_HOST_NO_READER; break;
+ case PCSC_E_SHARING_VIOLATION: rc = SW_HOST_LOCKING_FAILED; break;
+ case PCSC_E_NO_SMARTCARD: rc = SW_HOST_NO_CARD; break;
+ case PCSC_W_REMOVED_CARD: rc = SW_HOST_NO_CARD; break;
+
+ case PCSC_E_INVALID_TARGET:
+ case PCSC_E_INVALID_VALUE:
+ case PCSC_E_INVALID_HANDLE:
+ case PCSC_E_INVALID_PARAMETER:
+ case PCSC_E_INSUFFICIENT_BUFFER: rc = SW_HOST_INV_VALUE; break;
+
+ default: rc = SW_HOST_GENERAL_ERROR; break;
+ }
+
+ return rc;
+}
+
+static void
+dump_pcsc_reader_status (int slot)
+{
+ if (reader_table[slot].pcsc.card)
+ {
+ log_info ("reader slot %d: active protocol:", slot);
+ if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T0))
+ log_printf (" T0");
+ else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1))
+ log_printf (" T1");
+ else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_RAW))
+ log_printf (" raw");
+ log_printf ("\n");
+ }
+ else
+ log_info ("reader slot %d: not connected\n", slot);
+}
+
+
+static int
+pcsc_get_status (int slot, unsigned int *status, int on_wire)
+{
+ long err;
+ struct pcsc_readerstate_s rdrstates[1];
+
+ (void)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 (pcsc.context, 0, rdrstates, 1);
+ if (err == PCSC_E_TIMEOUT)
+ err = 0; /* Timeout is no error here. */
+ if (err)
+ {
+ log_error ("pcsc_get_status_change failed: %s (0x%lx)\n",
+ pcsc_error_string (err), err);
+ return pcsc_error_to_sw (err);
+ }
+
+ if ((rdrstates[0].event_state & PCSC_STATE_CHANGED))
+ reader_table[slot].pcsc.current_state =
+ (rdrstates[0].event_state & ~PCSC_STATE_CHANGED);
+
+ if (DBG_READER)
+ 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)? " inuse":"",
+ (rdrstates[0].event_state & PCSC_STATE_MUTE)? " mute":"" );
+
+ *status = 0;
+ if ( (reader_table[slot].pcsc.current_state & PCSC_STATE_PRESENT) )
+ {
+ *status |= APDU_CARD_PRESENT;
+ if ( !(reader_table[slot].pcsc.current_state & PCSC_STATE_MUTE) )
+ *status |= APDU_CARD_ACTIVE;
+ }
+#ifndef HAVE_W32_SYSTEM
+ /* 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 & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
+ == (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)
+ && (opt.pcsc_shared
+ || !(reader_table[slot].pcsc.current_state & PCSC_STATE_INUSE)))
+ *status |= APDU_CARD_USABLE;
+#else
+ /* Some winscard drivers may set EXCLUSIVE and INUSE at the same
+ time when we are the only user (SCM SCR335) under Windows. */
+ if ((*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
+ == (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
+ *status |= APDU_CARD_USABLE;
+#endif
+
+ if (!on_wire && (rdrstates[0].event_state & PCSC_STATE_CHANGED))
+ /* Event like sleep/resume occurs, which requires RESET. */
+ return SW_HOST_NO_READER;
+ else
+ return 0;
+}
+
+
+/* Send the APDU of length APDULEN to SLOT and return a maximum of
+ *BUFLEN data in BUFFER, the actual returned size will be stored at
+ BUFLEN. Returns: A status word. */
+static int
+pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
+ unsigned char *buffer, size_t *buflen,
+ pininfo_t *pininfo)
+{
+ long err;
+ struct pcsc_io_request_s send_pci;
+ pcsc_dword_t recv_len;
+
+ (void)pininfo;
+
+ if (!reader_table[slot].atrlen
+ && (err = reset_pcsc_reader (slot)))
+ return err;
+
+ if (DBG_CARD_IO)
+ log_printhex (apdu, apdulen, " PCSC_data:");
+
+ if ((reader_table[slot].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 = *buflen;
+ err = pcsc_transmit (reader_table[slot].pcsc.card,
+ &send_pci, apdu, apdulen,
+ NULL, buffer, &recv_len);
+ *buflen = recv_len;
+ if (err)
+ log_error ("pcsc_transmit failed: %s (0x%lx)\n",
+ pcsc_error_string (err), err);
+
+ /* Handle fatal errors which require shutdown of reader. */
+ if (err == PCSC_E_NOT_TRANSACTED || err == PCSC_W_RESET_CARD
+ || err == PCSC_W_REMOVED_CARD)
+ {
+ reader_table[slot].pcsc.current_state = PCSC_STATE_UNAWARE;
+ scd_kick_the_loop ();
+ }
+
+ return pcsc_error_to_sw (err);
+}
+
+
+/* Do some control with the value of IOCTL_CODE to the card inserted
+ to SLOT. Input buffer is specified by CNTLBUF of length LEN.
+ Output buffer is specified by BUFFER of length *BUFLEN, and the
+ actual output size will be stored at BUFLEN. Returns: A status word.
+ This routine is used for PIN pad input support. */
+static int
+control_pcsc (int slot, pcsc_dword_t ioctl_code,
+ const unsigned char *cntlbuf, size_t len,
+ unsigned char *buffer, pcsc_dword_t *buflen)
+{
+ long err;
+
+ err = pcsc_control (reader_table[slot].pcsc.card, ioctl_code,
+ cntlbuf, len, buffer, buflen? *buflen:0, buflen);
+ if (err)
+ {
+ log_error ("pcsc_control failed: %s (0x%lx)\n",
+ pcsc_error_string (err), err);
+ return pcsc_error_to_sw (err);
+ }
+
+ return 0;
+}
+
+
+static int
+close_pcsc_reader (int slot)
+{
+ (void)slot;
+
+ if (pcsc.context_valid)
+ {
+ pcsc_release_context (pcsc.context);
+ pcsc.context_valid = 0;
+ }
+ return 0;
+}
+
+
+/* Connect a PC/SC card. */
+static int
+connect_pcsc_card (int slot)
+{
+ long err;
+
+ log_assert (slot >= 0 && slot < MAX_READER);
+
+ if (reader_table[slot].pcsc.card)
+ return SW_HOST_ALREADY_CONNECTED;
+
+ reader_table[slot].atrlen = 0;
+ reader_table[slot].is_t0 = 0;
+
+ err = pcsc_connect (pcsc.context,
+ reader_table[slot].rdrname,
+ opt.pcsc_shared? PCSC_SHARE_SHARED:PCSC_SHARE_EXCLUSIVE,
+ PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1,
+ &reader_table[slot].pcsc.card,
+ &reader_table[slot].pcsc.protocol);
+ if (err)
+ {
+ reader_table[slot].pcsc.card = 0;
+ if (err != PCSC_E_NO_SMARTCARD)
+ log_error ("pcsc_connect failed: %s (0x%lx)\n",
+ pcsc_error_string (err), err);
+ if (err == PCSC_W_REMOVED_CARD && pcsc_cancel)
+ {
+ long err2;
+ err2 = pcsc_cancel (pcsc.context);
+ if (err2)
+ log_error ("pcsc_cancel failed: %s (0x%lx)\n",
+ pcsc_error_string (err2), err2);
+ else if (opt.verbose)
+ log_error ("pcsc_cancel succeeded\n");
+ }
+ }
+ else
+ {
+ char reader[250];
+ pcsc_dword_t readerlen, atrlen;
+ pcsc_dword_t card_state, card_protocol;
+
+ pcsc_vendor_specific_init (slot);
+
+ atrlen = DIM (reader_table[0].atr);
+ 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, (long unsigned int)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;
+ reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0);
+ }
+ }
+
+ dump_reader_status (slot);
+ return pcsc_error_to_sw (err);
+}
+
+
+static int
+disconnect_pcsc_card (int slot)
+{
+ long err;
+
+ log_assert (slot >= 0 && slot < MAX_READER);
+
+ if (!reader_table[slot].pcsc.card)
+ return 0;
+
+ 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;
+ return 0;
+}
+
+
+/* Send an PC/SC reset command and return a status word on error or 0
+ on success. */
+static int
+reset_pcsc_reader (int slot)
+{
+ int sw;
+
+ sw = disconnect_pcsc_card (slot);
+ if (!sw)
+ sw = connect_pcsc_card (slot);
+
+ return sw;
+}
+
+
+/* Examine reader specific parameters and initialize. This is mostly
+ for pinpad input. Called at opening the connection to the reader. */
+static int
+pcsc_vendor_specific_init (int slot)
+{
+ unsigned char buf[256];
+ pcsc_dword_t len;
+ int sw;
+ int vendor = 0;
+ int product = 0;
+ pcsc_dword_t get_tlv_ioctl = (pcsc_dword_t)-1;
+ unsigned char *p;
+
+ len = sizeof (buf);
+ sw = control_pcsc (slot, CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0, buf, &len);
+ if (sw)
+ {
+ log_error ("pcsc_vendor_specific_init: GET_FEATURE_REQUEST failed: %d\n",
+ sw);
+ return SW_NOT_SUPPORTED;
+ }
+ else
+ {
+ p = buf;
+ while (p < buf + len)
+ {
+ unsigned char code = *p++;
+ int l = *p++;
+ unsigned int v = 0;
+
+ if (l == 1)
+ v = p[0];
+ else if (l == 2)
+ v = buf16_to_uint (p);
+ else if (l == 4)
+ v = buf32_to_uint (p);
+
+ if (code == FEATURE_VERIFY_PIN_DIRECT)
+ reader_table[slot].pcsc.verify_ioctl = v;
+ else if (code == FEATURE_MODIFY_PIN_DIRECT)
+ reader_table[slot].pcsc.modify_ioctl = v;
+ else if (code == FEATURE_GET_TLV_PROPERTIES)
+ get_tlv_ioctl = v;
+
+ if (DBG_CARD_IO)
+ log_debug ("feature: code=%02X, len=%d, v=%02X\n", code, l, v);
+
+ p += l;
+ }
+ }
+
+ if (get_tlv_ioctl == (pcsc_dword_t)-1)
+ {
+ /*
+ * For system which doesn't support GET_TLV_PROPERTIES,
+ * we put some heuristics here.
+ */
+ if (reader_table[slot].rdrname)
+ {
+ if (strstr (reader_table[slot].rdrname, "SPRx32"))
+ {
+ reader_table[slot].is_spr532 = 1;
+ reader_table[slot].pinpad_varlen_supported = 1;
+ }
+ else if (strstr (reader_table[slot].rdrname, "ST-2xxx"))
+ {
+ reader_table[slot].pcsc.pinmax = 15;
+ reader_table[slot].pinpad_varlen_supported = 1;
+ }
+ else if (strstr (reader_table[slot].rdrname, "cyberJack")
+ || strstr (reader_table[slot].rdrname, "DIGIPASS")
+ || strstr (reader_table[slot].rdrname, "Gnuk")
+ || strstr (reader_table[slot].rdrname, "KAAN")
+ || strstr (reader_table[slot].rdrname, "Trustica"))
+ reader_table[slot].pinpad_varlen_supported = 1;
+ }
+
+ return 0;
+ }
+
+ len = sizeof (buf);
+ sw = control_pcsc (slot, get_tlv_ioctl, NULL, 0, buf, &len);
+ if (sw)
+ {
+ log_error ("pcsc_vendor_specific_init: GET_TLV_IOCTL failed: %d\n", sw);
+ return SW_NOT_SUPPORTED;
+ }
+
+ p = buf;
+ while (p < buf + len)
+ {
+ unsigned char tag = *p++;
+ int l = *p++;
+ unsigned int v = 0;
+
+ /* Umm... here is little endian, while the encoding above is big. */
+ if (l == 1)
+ v = p[0];
+ else if (l == 2)
+ v = (((unsigned int)p[1] << 8) | p[0]);
+ else if (l == 4)
+ v = (((unsigned int)p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]);
+
+ if (tag == PCSCv2_PART10_PROPERTY_bMinPINSize)
+ reader_table[slot].pcsc.pinmin = v;
+ else if (tag == PCSCv2_PART10_PROPERTY_bMaxPINSize)
+ reader_table[slot].pcsc.pinmax = v;
+ else if (tag == PCSCv2_PART10_PROPERTY_wIdVendor)
+ vendor = v;
+ else if (tag == PCSCv2_PART10_PROPERTY_wIdProduct)
+ product = v;
+
+ if (DBG_CARD_IO)
+ log_debug ("TLV properties: tag=%02X, len=%d, v=%08X\n", tag, l, v);
+
+ p += l;
+ }
+
+ if (vendor == VENDOR_VEGA && product == VEGA_ALPHA)
+ {
+ /*
+ * Please read the comment of ccid_vendor_specific_init in
+ * ccid-driver.c.
+ */
+ const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' };
+ sw = control_pcsc (slot, CM_IOCTL_VENDOR_IFD_EXCHANGE,
+ cmd, sizeof (cmd), NULL, 0);
+ if (sw)
+ return SW_NOT_SUPPORTED;
+ }
+ else if (vendor == VENDOR_SCM && product == SCM_SPR532) /* SCM SPR532 */
+ {
+ reader_table[slot].is_spr532 = 1;
+ reader_table[slot].pinpad_varlen_supported = 1;
+ }
+ else if (vendor == 0x046a)
+ {
+ /* Cherry ST-2xxx (product == 0x003e) supports TPDU level
+ * exchange. Other products which only support short APDU level
+ * exchange only work with shorter keys like RSA 1024.
+ */
+ reader_table[slot].pcsc.pinmax = 15;
+ reader_table[slot].pinpad_varlen_supported = 1;
+ }
+ else if (vendor == 0x0c4b /* Tested with Reiner cyberJack GO */
+ || vendor == 0x1a44 /* Tested with Vasco DIGIPASS 920 */
+ || vendor == 0x234b /* Tested with FSIJ Gnuk Token */
+ || vendor == 0x0d46 /* Tested with KAAN Advanced??? */
+ || (vendor == 0x1fc9 && product == 0x81e6) /* Tested with Trustica Cryptoucan */)
+ reader_table[slot].pinpad_varlen_supported = 1;
+
+ return 0;
+}
+
+static int
+pcsc_init (void)
+{
+ static int pcsc_api_loaded;
+ long err;
+
+ /* Load the PC/SC API */
+ if (!pcsc_api_loaded)
+ {
+ void *handle;
+
+ handle = dlopen (opt.pcsc_driver, RTLD_LAZY);
+ if (!handle)
+ {
+ log_error ("pscd_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_cancel = dlsym (handle, "SCardCancel");
+ 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 ("pcsc_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;
+ }
+
+ pcsc.context_valid = 0;
+ err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL, &pcsc.context);
+ if (err)
+ {
+ log_error ("pcsc_establish_context failed: %s (0x%lx)\n",
+ pcsc_error_string (err), err);
+ return -1;
+ }
+ pcsc.context_valid = 1;
+
+ return 0;
+}
+
+
+/* Select a reader from list of readers available. */
+static const char *
+select_a_reader (const char *list, unsigned int len)
+{
+ const char *black_list_to_skip[] = {
+ /* We do left match by strncmp(3). */
+ "Windows Hello"
+ };
+ const char *white_list_to_prefer[] = {
+ /* We do substring match by strstr(3). */
+ "SPRx32",
+ "Yubico"
+ };
+ const char *p = list;
+ const char *candidate = NULL;
+ unsigned int n;
+
+ /*
+ * (1) If one in the white list is found in LIST, that one is
+ * selected.
+ * (2) Otherwise, if one not in the black list is found in LIST,
+ * that is a candidate.
+ * (3) Select the first candidate, or in case of no candidate,
+ * return the first entry even if it's in the black list.
+ */
+ while (len)
+ {
+ int i;
+ int is_bad;
+
+ if (!*p)
+ break;
+
+ for (n=0; n < len; n++)
+ if (!p[n])
+ break;
+
+ /* Something wrong in the LIST. */
+ if (n >= len)
+ break;
+
+ for (i = 0; i < DIM (white_list_to_prefer); i++)
+ if (strstr (p, white_list_to_prefer[i]))
+ return p;
+
+ is_bad = 0;
+ for (i = 0; i < DIM (black_list_to_skip); i++)
+ if (!strncmp (p, black_list_to_skip[i],
+ strlen (black_list_to_skip[i])))
+ is_bad = 1;
+
+ if (!is_bad && !candidate)
+ candidate = p;
+
+ len -= n + 1;
+ p += n + 1;
+ }
+
+ if (candidate)
+ return candidate;
+
+ return list;
+}
+
+
+/* Open the PC/SC reader. If PORTSTR is NULL we default to a suitable
+ port. Returns -1 on error or a slot number for the reader. */
+static int
+open_pcsc_reader (const char *portstr)
+{
+ long err;
+ int slot;
+ char *list = NULL;
+ const char *rdrname = NULL;
+ pcsc_dword_t nreader = 0;
+ const char *p;
+ size_t n;
+ membuf_t reader_mb;
+
+ xfree (pcsc.reader_list);
+ pcsc.reader_list = NULL;
+
+ if (!pcsc.context_valid)
+ if (pcsc_init () < 0)
+ return -1;
+
+ if (DBG_READER)
+ log_debug ("open_pcsc_reader(portstr=%s)\n", portstr);
+
+
+ slot = new_reader_slot ();
+ if (slot == -1)
+ return -1; /* No need to cleanup here. */
+
+ err = pcsc_list_readers (pcsc.context, NULL, NULL, &nreader);
+ if (!err)
+ {
+ list = xtrymalloc (nreader+1); /* Better add 1 for safety reasons. */
+ if (!list)
+ {
+ log_error ("error allocating memory for reader list\n");
+ close_pcsc_reader (slot);
+ reader_table[slot].used = 0;
+ unlock_slot (slot);
+ slot = -1 /*SW_HOST_OUT_OF_CORE*/;
+ goto leave;
+ }
+ err = pcsc_list_readers (pcsc.context, NULL, list, &nreader);
+ }
+ if (err)
+ {
+ log_error ("pcsc_list_readers failed: %s (0x%lx)\n",
+ pcsc_error_string (err), err);
+ close_pcsc_reader (slot);
+ reader_table[slot].used = 0;
+ unlock_slot (slot);
+ xfree (list);
+ slot = -1;
+ goto leave;
+ }
+
+ init_membuf (&reader_mb, 256);
+
+ p = list;
+ while (nreader > 0)
+ {
+ 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");
+ xfree (get_membuf (&reader_mb, NULL));
+ close_pcsc_reader (slot);
+ reader_table[slot].used = 0;
+ unlock_slot (slot);
+ xfree (list);
+ slot = -1;
+ goto leave;
+ }
+
+ log_info ("detected reader '%s'\n", p);
+ put_membuf_str (&reader_mb, p);
+ put_membuf (&reader_mb, "\n", 1);
+ if (!rdrname && portstr && !strncmp (p, portstr, strlen (portstr)))
+ rdrname = p;
+ nreader -= n + 1;
+ p += n + 1;
+ }
+ put_membuf (&reader_mb, "", 1);
+ pcsc.reader_list = get_membuf (&reader_mb, NULL);
+ if (!pcsc.reader_list)
+ log_error ("error allocating memory for reader list\n");
+
+ if (!rdrname)
+ rdrname = select_a_reader (list, nreader);
+
+ reader_table[slot].rdrname = xtrystrdup (rdrname);
+ if (!reader_table[slot].rdrname)
+ {
+ log_error ("error allocating memory for reader name\n");
+ close_pcsc_reader (slot);
+ reader_table[slot].used = 0;
+ unlock_slot (slot);
+ slot = -1;
+ xfree (list);
+ goto leave;
+ }
+ xfree (list);
+ list = NULL;
+
+ reader_table[slot].pcsc.card = 0;
+ reader_table[slot].atrlen = 0;
+
+ reader_table[slot].connect_card = connect_pcsc_card;
+ reader_table[slot].disconnect_card = disconnect_pcsc_card;
+ reader_table[slot].close_reader = close_pcsc_reader;
+ reader_table[slot].reset_reader = reset_pcsc_reader;
+ reader_table[slot].get_status_reader = pcsc_get_status;
+ reader_table[slot].send_apdu_reader = pcsc_send_apdu;
+ reader_table[slot].dump_status_reader = dump_pcsc_reader_status;
+
+ dump_reader_status (slot);
+ unlock_slot (slot);
+
+ leave:
+ if (DBG_READER)
+ log_debug ("open_pcsc_reader => slot=%d\n", slot);
+ return slot;
+}
+
+
+/* Check whether the reader supports the ISO command code COMMAND
+ on the pinpad. Return 0 on success. */
+static int
+check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo)
+{
+ int r;
+
+ if (reader_table[slot].pcsc.pinmin >= 0)
+ pininfo->minlen = reader_table[slot].pcsc.pinmin;
+
+ if (reader_table[slot].pcsc.pinmax >= 0)
+ pininfo->maxlen = reader_table[slot].pcsc.pinmax;
+
+ if (!pininfo->minlen)
+ pininfo->minlen = 1;
+ if (!pininfo->maxlen)
+ pininfo->maxlen = 15;
+
+ if ((command == ISO7816_VERIFY && reader_table[slot].pcsc.verify_ioctl != 0)
+ || (command == ISO7816_CHANGE_REFERENCE_DATA
+ && reader_table[slot].pcsc.modify_ioctl != 0))
+ r = 0; /* Success */
+ else
+ r = SW_NOT_SUPPORTED;
+
+ if (DBG_CARD_IO)
+ log_debug ("check_pcsc_pinpad: command=%02X, r=%d\n",
+ (unsigned int)command, r);
+
+ if (reader_table[slot].pinpad_varlen_supported)
+ pininfo->fixedlen = 0;
+
+ return r;
+}
+
+#define PIN_VERIFY_STRUCTURE_SIZE 24
+static int
+pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
+ pininfo_t *pininfo)
+{
+ int sw;
+ unsigned char *pin_verify;
+ int len = PIN_VERIFY_STRUCTURE_SIZE + pininfo->fixedlen;
+ /*
+ * The result buffer is only expected to have two-byte result on
+ * return. However, some implementation uses this buffer for lower
+ * layer too and it assumes that there is enough space for lower
+ * layer communication. Such an implementation fails for TPDU
+ * readers with "insufficient buffer", as it needs header and
+ * trailer. Six is the number for header + result + trailer (TPDU).
+ */
+ unsigned char result[6];
+ pcsc_dword_t resultlen = 6;
+ int no_lc;
+
+ if (!reader_table[slot].atrlen
+ && (sw = reset_pcsc_reader (slot)))
+ return sw;
+
+ if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
+ return SW_NOT_SUPPORTED;
+
+ pin_verify = xtrymalloc (len);
+ if (!pin_verify)
+ return SW_HOST_OUT_OF_CORE;
+
+ no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532);
+
+ pin_verify[0] = 0x00; /* bTimeOut */
+ pin_verify[1] = 0x00; /* bTimeOut2 */
+ pin_verify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
+ pin_verify[3] = pininfo->fixedlen; /* bmPINBlockString */
+ pin_verify[4] = 0x00; /* bmPINLengthFormat */
+ pin_verify[5] = pininfo->maxlen; /* wPINMaxExtraDigit */
+ pin_verify[6] = pininfo->minlen; /* wPINMaxExtraDigit */
+ pin_verify[7] = 0x02; /* bEntryValidationCondition: Validation key pressed */
+ if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
+ pin_verify[7] |= 0x01; /* Max size reached. */
+ pin_verify[8] = 0x01; /* bNumberMessage: One message */
+ pin_verify[9] = 0x09; /* wLangId: 0x0409: US English */
+ pin_verify[10] = 0x04; /* wLangId: 0x0409: US English */
+ pin_verify[11] = 0x00; /* bMsgIndex */
+ pin_verify[12] = 0x00; /* bTeoPrologue[0] */
+ pin_verify[13] = 0x00; /* bTeoPrologue[1] */
+ pin_verify[14] = pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */
+ pin_verify[15] = pininfo->fixedlen + 0x05 - no_lc; /* ulDataLength */
+ pin_verify[16] = 0x00; /* ulDataLength */
+ pin_verify[17] = 0x00; /* ulDataLength */
+ pin_verify[18] = 0x00; /* ulDataLength */
+ pin_verify[19] = class; /* abData[0] */
+ pin_verify[20] = ins; /* abData[1] */
+ pin_verify[21] = p0; /* abData[2] */
+ pin_verify[22] = p1; /* abData[3] */
+ pin_verify[23] = pininfo->fixedlen; /* abData[4] */
+ if (pininfo->fixedlen)
+ memset (&pin_verify[24], 0xff, pininfo->fixedlen);
+ else if (no_lc)
+ len--;
+
+ if (DBG_CARD_IO)
+ log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n",
+ class, ins, p0, p1, len, pininfo->maxlen);
+
+ sw = control_pcsc (slot, reader_table[slot].pcsc.verify_ioctl,
+ pin_verify, len, result, &resultlen);
+ xfree (pin_verify);
+ if (sw || resultlen < 2)
+ {
+ log_error ("control_pcsc failed: %d\n", sw);
+ return sw? sw: SW_HOST_INCOMPLETE_CARD_RESPONSE;
+ }
+ sw = (result[resultlen-2] << 8) | result[resultlen-1];
+ if (DBG_CARD_IO)
+ log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen);
+ return sw;
+}
+
+
+#define PIN_MODIFY_STRUCTURE_SIZE 29
+static int
+pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1,
+ pininfo_t *pininfo)
+{
+ int sw;
+ unsigned char *pin_modify;
+ int len = PIN_MODIFY_STRUCTURE_SIZE + 2 * pininfo->fixedlen;
+ unsigned char result[6]; /* See the comment at pinpad_verify. */
+ pcsc_dword_t resultlen = 6;
+ int no_lc;
+
+ if (!reader_table[slot].atrlen
+ && (sw = reset_pcsc_reader (slot)))
+ return sw;
+
+ if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
+ return SW_NOT_SUPPORTED;
+
+ pin_modify = xtrymalloc (len);
+ if (!pin_modify)
+ return SW_HOST_OUT_OF_CORE;
+
+ no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532);
+
+ pin_modify[0] = 0x00; /* bTimeOut */
+ pin_modify[1] = 0x00; /* bTimeOut2 */
+ pin_modify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
+ pin_modify[3] = pininfo->fixedlen; /* bmPINBlockString */
+ pin_modify[4] = 0x00; /* bmPINLengthFormat */
+ pin_modify[5] = 0x00; /* bInsertionOffsetOld */
+ pin_modify[6] = pininfo->fixedlen; /* bInsertionOffsetNew */
+ pin_modify[7] = pininfo->maxlen; /* wPINMaxExtraDigit */
+ pin_modify[8] = pininfo->minlen; /* wPINMaxExtraDigit */
+ pin_modify[9] = (p0 == 0 ? 0x03 : 0x01);
+ /* bConfirmPIN
+ * 0x00: new PIN once
+ * 0x01: new PIN twice (confirmation)
+ * 0x02: old PIN and new PIN once
+ * 0x03: old PIN and new PIN twice (confirmation)
+ */
+ pin_modify[10] = 0x02; /* bEntryValidationCondition: Validation key pressed */
+ if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
+ pin_modify[10] |= 0x01; /* Max size reached. */
+ pin_modify[11] = 0x03; /* bNumberMessage: Three messages */
+ pin_modify[12] = 0x09; /* wLangId: 0x0409: US English */
+ pin_modify[13] = 0x04; /* wLangId: 0x0409: US English */
+ pin_modify[14] = 0x00; /* bMsgIndex1 */
+ pin_modify[15] = 0x01; /* bMsgIndex2 */
+ pin_modify[16] = 0x02; /* bMsgIndex3 */
+ pin_modify[17] = 0x00; /* bTeoPrologue[0] */
+ pin_modify[18] = 0x00; /* bTeoPrologue[1] */
+ pin_modify[19] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */
+ pin_modify[20] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* ulDataLength */
+ pin_modify[21] = 0x00; /* ulDataLength */
+ pin_modify[22] = 0x00; /* ulDataLength */
+ pin_modify[23] = 0x00; /* ulDataLength */
+ pin_modify[24] = class; /* abData[0] */
+ pin_modify[25] = ins; /* abData[1] */
+ pin_modify[26] = p0; /* abData[2] */
+ pin_modify[27] = p1; /* abData[3] */
+ pin_modify[28] = 2 * pininfo->fixedlen; /* abData[4] */
+ if (pininfo->fixedlen)
+ memset (&pin_modify[29], 0xff, 2 * pininfo->fixedlen);
+ else if (no_lc)
+ len--;
+
+ if (DBG_CARD_IO)
+ log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n",
+ class, ins, p0, p1, len, (int)pininfo->maxlen);
+
+ sw = control_pcsc (slot, reader_table[slot].pcsc.modify_ioctl,
+ pin_modify, len, result, &resultlen);
+ xfree (pin_modify);
+ if (sw || resultlen < 2)
+ {
+ log_error ("control_pcsc failed: %d\n", sw);
+ return sw? sw : SW_HOST_INCOMPLETE_CARD_RESPONSE;
+ }
+ sw = (result[resultlen-2] << 8) | result[resultlen-1];
+ if (DBG_CARD_IO)
+ log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen);
+ return sw;
+}
+
+#ifdef HAVE_LIBUSB
+/*
+ Internal CCID driver interface.
+ */
+
+
+static void
+dump_ccid_reader_status (int slot)
+{
+ log_info ("reader slot %d: using ccid driver\n", slot);
+}
+
+static int
+close_ccid_reader (int slot)
+{
+ ccid_close_reader (reader_table[slot].ccid.handle);
+ return 0;
+}
+
+
+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 err;
+ /* If the reset was successful, update the ATR. */
+ log_assert (sizeof slotp->atr >= sizeof atr);
+ slotp->atrlen = atrlen;
+ memcpy (slotp->atr, atr, atrlen);
+ dump_reader_status (slot);
+ return 0;
+}
+
+
+static int
+set_progress_cb_ccid_reader (int slot, gcry_handler_progress_t cb, void *cb_arg)
+{
+ reader_table_t slotp = reader_table + slot;
+
+ return ccid_set_progress_cb (slotp->ccid.handle, cb, cb_arg);
+}
+
+static int
+set_prompt_cb_ccid_reader (int slot, void (*cb) (void *, int ), void *cb_arg)
+{
+ reader_table_t slotp = reader_table + slot;
+
+ return ccid_set_prompt_cb (slotp->ccid.handle, cb, cb_arg);
+}
+
+
+static int
+get_status_ccid (int slot, unsigned int *status, int on_wire)
+{
+ int rc;
+ int bits;
+
+ rc = ccid_slot_status (reader_table[slot].ccid.handle, &bits, on_wire);
+ if (rc)
+ return rc;
+
+ if (bits == 0)
+ *status = (APDU_CARD_USABLE|APDU_CARD_PRESENT|APDU_CARD_ACTIVE);
+ else if (bits == 1)
+ *status = APDU_CARD_PRESENT;
+ 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
+ set to BUFLEN. Returns: Internal CCID driver error code. */
+static int
+send_apdu_ccid (int slot, unsigned char *apdu, size_t apdulen,
+ unsigned char *buffer, size_t *buflen,
+ pininfo_t *pininfo)
+{
+ long err;
+ size_t maxbuflen;
+
+ /* If we don't have an ATR, we need to reset the reader first. */
+ if (!reader_table[slot].atrlen
+ && (err = reset_ccid_reader (slot)))
+ return err;
+
+ if (DBG_CARD_IO)
+ log_printhex (apdu, apdulen, " raw apdu:");
+
+ maxbuflen = *buflen;
+ if (pininfo)
+ err = ccid_transceive_secure (reader_table[slot].ccid.handle,
+ apdu, apdulen, pininfo,
+ buffer, maxbuflen, buflen);
+ else
+ err = ccid_transceive (reader_table[slot].ccid.handle,
+ apdu, apdulen,
+ buffer, maxbuflen, buflen);
+ if (err)
+ log_error ("ccid_transceive failed: (0x%lx)\n",
+ err);
+
+ return err;
+}
+
+
+/* Check whether the CCID reader supports the ISO command code COMMAND
+ on the pinpad. Return 0 on success. For a description of the pin
+ parameters, see ccid-driver.c */
+static int
+check_ccid_pinpad (int slot, int command, pininfo_t *pininfo)
+{
+ unsigned char apdu[] = { 0, 0, 0, 0x81 };
+
+ apdu[1] = command;
+ return ccid_transceive_secure (reader_table[slot].ccid.handle, apdu,
+ sizeof apdu, pininfo, NULL, 0, NULL);
+}
+
+
+static int
+ccid_pinpad_operation (int slot, int class, int ins, int p0, int p1,
+ pininfo_t *pininfo)
+{
+ unsigned char apdu[4];
+ int err, sw;
+ unsigned char result[2];
+ size_t resultlen = 2;
+
+ apdu[0] = class;
+ apdu[1] = ins;
+ apdu[2] = p0;
+ apdu[3] = p1;
+ err = ccid_transceive_secure (reader_table[slot].ccid.handle,
+ apdu, sizeof apdu, pininfo,
+ result, 2, &resultlen);
+ if (err)
+ return err;
+
+ if (resultlen < 2)
+ return SW_HOST_INCOMPLETE_CARD_RESPONSE;
+
+ sw = (result[resultlen-2] << 8) | result[resultlen-1];
+ return sw;
+}
+
+
+/* Open the reader and try to read an ATR. */
+static int
+open_ccid_reader (struct dev_list *dl)
+{
+ int err;
+ int slot;
+ int require_get_status;
+ reader_table_t slotp;
+
+ slot = new_reader_slot ();
+ if (slot == -1)
+ return -1;
+ slotp = reader_table + slot;
+
+ err = ccid_open_reader (dl->portstr, dl->idx, dl->table,
+ &slotp->ccid.handle, &slotp->rdrname);
+ if (!err)
+ {
+ err = ccid_get_atr (slotp->ccid.handle,
+ slotp->atr, sizeof slotp->atr, &slotp->atrlen);
+ if (err)
+ ccid_close_reader (slotp->ccid.handle);
+ }
+
+ if (err)
+ {
+ slotp->used = 0;
+ unlock_slot (slot);
+ return -1;
+ }
+
+ require_get_status = ccid_require_get_status (slotp->ccid.handle);
+
+ reader_table[slot].close_reader = close_ccid_reader;
+ reader_table[slot].reset_reader = reset_ccid_reader;
+ reader_table[slot].get_status_reader = get_status_ccid;
+ reader_table[slot].send_apdu_reader = send_apdu_ccid;
+ reader_table[slot].check_pinpad = check_ccid_pinpad;
+ reader_table[slot].dump_status_reader = dump_ccid_reader_status;
+ reader_table[slot].set_progress_cb = set_progress_cb_ccid_reader;
+ reader_table[slot].set_prompt_cb = set_prompt_cb_ccid_reader;
+ reader_table[slot].pinpad_verify = ccid_pinpad_operation;
+ reader_table[slot].pinpad_modify = ccid_pinpad_operation;
+ /* Our CCID reader code does not support T=0 at all, thus reset the
+ flag. */
+ reader_table[slot].is_t0 = 0;
+ reader_table[slot].require_get_status = require_get_status;
+
+ dump_reader_status (slot);
+ unlock_slot (slot);
+ return slot;
+}
+#endif /* HAVE_LIBUSB */
+
+#ifdef USE_G10CODE_RAPDU
+/*
+ The Remote APDU Interface.
+
+ This uses the Remote APDU protocol to contact a reader.
+
+ The port number is actually an index into the list of ports as
+ returned via the protocol.
+ */
+
+
+static int
+rapdu_status_to_sw (int status)
+{
+ int rc;
+
+ switch (status)
+ {
+ case RAPDU_STATUS_SUCCESS: rc = 0; break;
+
+ case RAPDU_STATUS_INVCMD:
+ case RAPDU_STATUS_INVPROT:
+ case RAPDU_STATUS_INVSEQ:
+ case RAPDU_STATUS_INVCOOKIE:
+ case RAPDU_STATUS_INVREADER: rc = SW_HOST_INV_VALUE; break;
+
+ case RAPDU_STATUS_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break;
+ case RAPDU_STATUS_CARDIO: rc = SW_HOST_CARD_IO_ERROR; break;
+ case RAPDU_STATUS_NOCARD: rc = SW_HOST_NO_CARD; break;
+ case RAPDU_STATUS_CARDCHG: rc = SW_HOST_NO_CARD; break;
+ case RAPDU_STATUS_BUSY: rc = SW_HOST_BUSY; break;
+ case RAPDU_STATUS_NEEDRESET: rc = SW_HOST_CARD_INACTIVE; break;
+
+ default: rc = SW_HOST_GENERAL_ERROR; break;
+ }
+
+ return rc;
+}
+
+
+
+static int
+close_rapdu_reader (int slot)
+{
+ rapdu_release (reader_table[slot].rapdu.handle);
+ return 0;
+}
+
+
+static int
+reset_rapdu_reader (int slot)
+{
+ int err;
+ reader_table_t slotp;
+ rapdu_msg_t msg = NULL;
+
+ slotp = reader_table + slot;
+
+ err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET);
+ if (err)
+ {
+ log_error ("sending rapdu command RESET failed: %s\n",
+ err < 0 ? strerror (errno): rapdu_strerror (err));
+ rapdu_msg_release (msg);
+ return rapdu_status_to_sw (err);
+ }
+ err = rapdu_read_msg (slotp->rapdu.handle, &msg);
+ if (err)
+ {
+ log_error ("receiving rapdu message failed: %s\n",
+ err < 0 ? strerror (errno): rapdu_strerror (err));
+ rapdu_msg_release (msg);
+ return rapdu_status_to_sw (err);
+ }
+ if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
+ {
+ int sw = rapdu_status_to_sw (msg->cmd);
+ log_error ("rapdu command RESET failed: %s\n",
+ rapdu_strerror (msg->cmd));
+ rapdu_msg_release (msg);
+ return sw;
+ }
+ if (msg->datalen > DIM (slotp->atr))
+ {
+ log_error ("ATR returned by the RAPDU layer is too large\n");
+ rapdu_msg_release (msg);
+ return SW_HOST_INV_VALUE;
+ }
+ slotp->atrlen = msg->datalen;
+ memcpy (slotp->atr, msg->data, msg->datalen);
+
+ rapdu_msg_release (msg);
+ return 0;
+}
+
+
+static int
+my_rapdu_get_status (int slot, unsigned int *status, int on_wire)
+{
+ int err;
+ reader_table_t slotp;
+ rapdu_msg_t msg = NULL;
+ int oldslot;
+
+ (void)on_wire;
+ slotp = reader_table + slot;
+
+ oldslot = rapdu_set_reader (slotp->rapdu.handle, slot);
+ err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_STATUS);
+ rapdu_set_reader (slotp->rapdu.handle, oldslot);
+ if (err)
+ {
+ log_error ("sending rapdu command GET_STATUS failed: %s\n",
+ err < 0 ? strerror (errno): rapdu_strerror (err));
+ return rapdu_status_to_sw (err);
+ }
+ err = rapdu_read_msg (slotp->rapdu.handle, &msg);
+ if (err)
+ {
+ log_error ("receiving rapdu message failed: %s\n",
+ err < 0 ? strerror (errno): rapdu_strerror (err));
+ rapdu_msg_release (msg);
+ return rapdu_status_to_sw (err);
+ }
+ if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
+ {
+ int sw = rapdu_status_to_sw (msg->cmd);
+ log_error ("rapdu command GET_STATUS failed: %s\n",
+ rapdu_strerror (msg->cmd));
+ rapdu_msg_release (msg);
+ return sw;
+ }
+ *status = msg->data[0];
+
+ rapdu_msg_release (msg);
+ 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
+ set to BUFLEN. Returns: APDU error code. */
+static int
+my_rapdu_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
+ unsigned char *buffer, size_t *buflen,
+ pininfo_t *pininfo)
+{
+ int err;
+ reader_table_t slotp;
+ rapdu_msg_t msg = NULL;
+ size_t maxlen = *buflen;
+
+ slotp = reader_table + slot;
+
+ *buflen = 0;
+ if (DBG_CARD_IO)
+ log_printhex (apdu, apdulen, " APDU_data:");
+
+ if (apdulen < 4)
+ {
+ log_error ("rapdu_send_apdu: APDU is too short\n");
+ return SW_HOST_INV_VALUE;
+ }
+
+ err = rapdu_send_apdu (slotp->rapdu.handle, apdu, apdulen);
+ if (err)
+ {
+ log_error ("sending rapdu command APDU failed: %s\n",
+ err < 0 ? strerror (errno): rapdu_strerror (err));
+ rapdu_msg_release (msg);
+ return rapdu_status_to_sw (err);
+ }
+ err = rapdu_read_msg (slotp->rapdu.handle, &msg);
+ if (err)
+ {
+ log_error ("receiving rapdu message failed: %s\n",
+ err < 0 ? strerror (errno): rapdu_strerror (err));
+ rapdu_msg_release (msg);
+ return rapdu_status_to_sw (err);
+ }
+ if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
+ {
+ int sw = rapdu_status_to_sw (msg->cmd);
+ log_error ("rapdu command APDU failed: %s\n",
+ rapdu_strerror (msg->cmd));
+ rapdu_msg_release (msg);
+ return sw;
+ }
+
+ if (msg->datalen > maxlen)
+ {
+ log_error ("rapdu response apdu too large\n");
+ rapdu_msg_release (msg);
+ return SW_HOST_INV_VALUE;
+ }
+
+ *buflen = msg->datalen;
+ memcpy (buffer, msg->data, msg->datalen);
+
+ rapdu_msg_release (msg);
+ return 0;
+}
+
+static int
+open_rapdu_reader (int portno,
+ const unsigned char *cookie, size_t length,
+ int (*readfnc) (void *opaque,
+ void *buffer, size_t size),
+ void *readfnc_value,
+ int (*writefnc) (void *opaque,
+ const void *buffer, size_t size),
+ void *writefnc_value,
+ void (*closefnc) (void *opaque),
+ void *closefnc_value)
+{
+ int err;
+ int slot;
+ reader_table_t slotp;
+ rapdu_msg_t msg = NULL;
+
+ slot = new_reader_slot ();
+ if (slot == -1)
+ return -1;
+ slotp = reader_table + slot;
+
+ slotp->rapdu.handle = rapdu_new ();
+ if (!slotp->rapdu.handle)
+ {
+ slotp->used = 0;
+ unlock_slot (slot);
+ return -1;
+ }
+
+ rapdu_set_reader (slotp->rapdu.handle, portno);
+
+ rapdu_set_iofunc (slotp->rapdu.handle,
+ readfnc, readfnc_value,
+ writefnc, writefnc_value,
+ closefnc, closefnc_value);
+ rapdu_set_cookie (slotp->rapdu.handle, cookie, length);
+
+ /* First try to get the current ATR, but if the card is inactive
+ issue a reset instead. */
+ err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_ATR);
+ if (err == RAPDU_STATUS_NEEDRESET)
+ err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET);
+ if (err)
+ {
+ log_info ("sending rapdu command GET_ATR/RESET failed: %s\n",
+ err < 0 ? strerror (errno): rapdu_strerror (err));
+ goto failure;
+ }
+ err = rapdu_read_msg (slotp->rapdu.handle, &msg);
+ if (err)
+ {
+ log_info ("receiving rapdu message failed: %s\n",
+ err < 0 ? strerror (errno): rapdu_strerror (err));
+ goto failure;
+ }
+ if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
+ {
+ log_info ("rapdu command GET ATR failed: %s\n",
+ rapdu_strerror (msg->cmd));
+ goto failure;
+ }
+ if (msg->datalen > DIM (slotp->atr))
+ {
+ log_error ("ATR returned by the RAPDU layer is too large\n");
+ goto failure;
+ }
+ slotp->atrlen = msg->datalen;
+ memcpy (slotp->atr, msg->data, msg->datalen);
+
+ reader_table[slot].close_reader = close_rapdu_reader;
+ reader_table[slot].reset_reader = reset_rapdu_reader;
+ reader_table[slot].get_status_reader = my_rapdu_get_status;
+ reader_table[slot].send_apdu_reader = my_rapdu_send_apdu;
+ reader_table[slot].check_pinpad = NULL;
+ reader_table[slot].dump_status_reader = NULL;
+ reader_table[slot].pinpad_verify = NULL;
+ reader_table[slot].pinpad_modify = NULL;
+
+ dump_reader_status (slot);
+ rapdu_msg_release (msg);
+ unlock_slot (slot);
+ return slot;
+
+ failure:
+ rapdu_msg_release (msg);
+ rapdu_release (slotp->rapdu.handle);
+ slotp->used = 0;
+ unlock_slot (slot);
+ return -1;
+}
+
+#endif /*USE_G10CODE_RAPDU*/
+
+
+
+/*
+ Driver Access
+ */
+gpg_error_t
+apdu_dev_list_start (const char *portstr, struct dev_list **l_p)
+{
+ struct dev_list *dl = xtrymalloc (sizeof (struct dev_list));
+
+ *l_p = NULL;
+ if (!dl)
+ return gpg_error_from_syserror ();
+
+ dl->portstr = portstr;
+ dl->idx = 0;
+
+ npth_mutex_lock (&reader_table_lock);
+
+#ifdef HAVE_LIBUSB
+ if (opt.disable_ccid)
+ {
+ dl->table = NULL;
+ dl->idx_max = 1;
+ }
+ else
+ {
+ gpg_error_t err;
+
+ err = ccid_dev_scan (&dl->idx_max, &dl->table);
+ if (err)
+ return err;
+
+ if (dl->idx_max == 0)
+ {
+ /* 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] == ':')
+ {
+ 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
+ dl->idx_max = 1;
+ }
+ }
+#else
+ dl->table = NULL;
+ dl->idx_max = 1;
+#endif /* HAVE_LIBUSB */
+
+ *l_p = dl;
+ return 0;
+}
+
+void
+apdu_dev_list_finish (struct dev_list *dl)
+{
+#ifdef HAVE_LIBUSB
+ if (dl->table)
+ ccid_dev_scan_finish (dl->table, dl->idx_max);
+#endif
+ xfree (dl);
+ npth_mutex_unlock (&reader_table_lock);
+}
+
+
+int
+apdu_open_reader (struct dev_list *dl, int app_empty)
+{
+ int slot;
+
+#ifdef HAVE_LIBUSB
+ if (dl->table)
+ { /* 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);
+ dl->idx = dl->idx_max;
+ if (slot >= 0)
+ return slot;
+ else
+ return -1;
+ }
+
+ while (dl->idx < dl->idx_max)
+ {
+ unsigned int bai = ccid_get_BAI (dl->idx, dl->table);
+
+ if (DBG_READER)
+ log_debug ("apdu_open_reader: BAI=%x\n", bai);
+
+ /* Check identity by BAI against already opened HANDLEs. */
+ for (slot = 0; slot < MAX_READER; slot++)
+ if (reader_table[slot].used
+ && reader_table[slot].ccid.handle
+ && ccid_compare_BAI (reader_table[slot].ccid.handle, bai))
+ break;
+
+ if (slot == MAX_READER)
+ { /* Found a new device. */
+ if (DBG_READER)
+ log_debug ("apdu_open_reader: new device=%x\n", bai);
+
+ slot = open_ccid_reader (dl);
+
+ dl->idx++;
+ if (slot >= 0)
+ return slot;
+ else
+ {
+ /* Skip this reader. */
+ log_error ("ccid open error: skip\n");
+ continue;
+ }
+ }
+ else
+ 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 = open_pcsc_reader (dl->portstr);
+ }
+ else
+ slot = -1;
+ }
+ else
+#endif
+ { /* PC/SC readers. */
+
+ if (app_empty && dl->idx == 0)
+ {
+ dl->idx++;
+ slot = open_pcsc_reader (dl->portstr);
+ }
+ else
+ slot = -1;
+ }
+
+ return slot;
+}
+
+
+/* Open an remote reader and return an internal slot number or -1 on
+ error. This function is an alternative to apdu_open_reader and used
+ with remote readers only. Note that the supplied CLOSEFNC will
+ only be called once and the slot will not be valid afther this.
+
+ If PORTSTR is NULL we default to the first available port.
+*/
+int
+apdu_open_remote_reader (const char *portstr,
+ const unsigned char *cookie, size_t length,
+ int (*readfnc) (void *opaque,
+ void *buffer, size_t size),
+ void *readfnc_value,
+ int (*writefnc) (void *opaque,
+ const void *buffer, size_t size),
+ void *writefnc_value,
+ void (*closefnc) (void *opaque),
+ void *closefnc_value)
+{
+#ifdef USE_G10CODE_RAPDU
+ return open_rapdu_reader (portstr? atoi (portstr) : 0,
+ cookie, length,
+ readfnc, readfnc_value,
+ writefnc, writefnc_value,
+ closefnc, closefnc_value);
+#else
+ (void)portstr;
+ (void)cookie;
+ (void)length;
+ (void)readfnc;
+ (void)readfnc_value;
+ (void)writefnc;
+ (void)writefnc_value;
+ (void)closefnc;
+ (void)closefnc_value;
+#ifdef _WIN32
+ errno = ENOENT;
+#else
+ errno = ENOSYS;
+#endif
+ return -1;
+#endif
+}
+
+
+int
+apdu_close_reader (int slot)
+{
+ int sw;
+
+ if (DBG_READER)
+ log_debug ("enter: apdu_close_reader: slot=%d\n", slot);
+
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ {
+ if (DBG_READER)
+ log_debug ("leave: apdu_close_reader => SW_HOST_NO_DRIVER\n");
+ return SW_HOST_NO_DRIVER;
+ }
+ sw = apdu_disconnect (slot);
+ if (sw)
+ {
+ /*
+ * When the reader/token was removed it might come here.
+ * It should go through to call CLOSE_READER even if we got an error.
+ */
+ if (DBG_READER)
+ log_debug ("apdu_close_reader => 0x%x (apdu_disconnect)\n", sw);
+ }
+ if (reader_table[slot].close_reader)
+ {
+ sw = reader_table[slot].close_reader (slot);
+ xfree (reader_table[slot].rdrname);
+ reader_table[slot].rdrname = NULL;
+ reader_table[slot].used = 0;
+ if (DBG_READER)
+ log_debug ("leave: apdu_close_reader => 0x%x (close_reader)\n", sw);
+ return sw;
+ }
+ xfree (reader_table[slot].rdrname);
+ reader_table[slot].rdrname = NULL;
+ reader_table[slot].used = 0;
+ if (DBG_READER)
+ log_debug ("leave: apdu_close_reader => SW_HOST_NOT_SUPPORTED\n");
+ return SW_HOST_NOT_SUPPORTED;
+}
+
+
+/* Function suitable for a cleanup function to close all reader. It
+ should not be used if the reader will be opened again. The reason
+ for implementing this to properly close USB devices so that they
+ will startup the next time without error. */
+void
+apdu_prepare_exit (void)
+{
+ static int sentinel;
+ int slot;
+
+ if (!sentinel)
+ {
+ sentinel = 1;
+ npth_mutex_lock (&reader_table_lock);
+ for (slot = 0; slot < MAX_READER; slot++)
+ if (reader_table[slot].used)
+ {
+ apdu_disconnect (slot);
+ if (reader_table[slot].close_reader)
+ reader_table[slot].close_reader (slot);
+ xfree (reader_table[slot].rdrname);
+ reader_table[slot].rdrname = NULL;
+ reader_table[slot].used = 0;
+ }
+ npth_mutex_unlock (&reader_table_lock);
+ sentinel = 0;
+ }
+}
+
+
+/* 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;
+}
+
+
+/* Connect a card. This is used to power up the card and make sure
+ that an ATR is available. Depending on the reader backend it may
+ return an error for an inactive card or if no card is available.
+ Return -1 on error. Return 1 if reader requires get_status to
+ watch card removal. Return 0 if it's a token (always with a card),
+ or it supports INTERRUPT endpoint to watch card removal.
+ */
+int
+apdu_connect (int slot)
+{
+ int sw = 0;
+ unsigned int status = 0;
+
+ if (DBG_READER)
+ log_debug ("enter: apdu_connect: slot=%d\n", slot);
+
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ {
+ if (DBG_READER)
+ log_debug ("leave: apdu_connect => SW_HOST_NO_DRIVER\n");
+ return -1;
+ }
+
+ /* Only if the access method provides a connect function we use it.
+ If not, we expect that the card has been implicitly connected by
+ apdu_open_reader. */
+ if (reader_table[slot].connect_card)
+ {
+ sw = lock_slot (slot);
+ if (!sw)
+ {
+ sw = reader_table[slot].connect_card (slot);
+ unlock_slot (slot);
+ }
+ }
+
+ /* We need to call apdu_get_status_internal, so that the last-status
+ machinery gets setup properly even if a card is inserted while
+ scdaemon is fired up and apdu_get_status has not yet been called.
+ Without that we would force a reset of the card with the next
+ call to apdu_get_status. */
+ if (!sw)
+ sw = apdu_get_status_internal (slot, 1, &status, 1);
+
+ if (sw)
+ ;
+ else if (!(status & APDU_CARD_PRESENT))
+ sw = SW_HOST_NO_CARD;
+ else if ((status & APDU_CARD_PRESENT) && !(status & APDU_CARD_ACTIVE))
+ sw = SW_HOST_CARD_INACTIVE;
+
+ if (sw == SW_HOST_CARD_INACTIVE)
+ {
+ /* Try power it up again. */
+ sw = apdu_reset (slot);
+ }
+
+ if (DBG_READER)
+ log_debug ("leave: apdu_connect => sw=0x%x\n", sw);
+
+ if (sw)
+ return -1;
+
+ return reader_table[slot].require_get_status;
+}
+
+
+int
+apdu_disconnect (int slot)
+{
+ int sw;
+
+ if (DBG_READER)
+ log_debug ("enter: apdu_disconnect: slot=%d\n", slot);
+
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ {
+ if (DBG_READER)
+ log_debug ("leave: apdu_disconnect => SW_HOST_NO_DRIVER\n");
+ return SW_HOST_NO_DRIVER;
+ }
+
+ if (reader_table[slot].disconnect_card)
+ {
+ sw = lock_slot (slot);
+ if (!sw)
+ {
+ sw = reader_table[slot].disconnect_card (slot);
+ unlock_slot (slot);
+ }
+ }
+ else
+ sw = 0;
+
+ if (DBG_READER)
+ log_debug ("leave: apdu_disconnect => sw=0x%x\n", sw);
+ return sw;
+}
+
+
+/* Set the progress callback of SLOT to CB and its args to CB_ARG. If
+ CB is NULL the progress callback is removed. */
+int
+apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg)
+{
+ int sw;
+
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ return SW_HOST_NO_DRIVER;
+
+ if (reader_table[slot].set_progress_cb)
+ {
+ sw = lock_slot (slot);
+ if (!sw)
+ {
+ sw = reader_table[slot].set_progress_cb (slot, cb, cb_arg);
+ unlock_slot (slot);
+ }
+ }
+ else
+ sw = 0;
+ return sw;
+}
+
+
+int
+apdu_set_prompt_cb (int slot, void (*cb) (void *, int), void *cb_arg)
+{
+ int sw;
+
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ return SW_HOST_NO_DRIVER;
+
+ if (reader_table[slot].set_prompt_cb)
+ {
+ sw = lock_slot (slot);
+ if (!sw)
+ {
+ sw = reader_table[slot].set_prompt_cb (slot, cb, cb_arg);
+ unlock_slot (slot);
+ }
+ }
+ else
+ sw = 0;
+ return sw;
+}
+
+
+/* Do a reset for the card in reader at SLOT. */
+int
+apdu_reset (int slot)
+{
+ int sw;
+
+ if (DBG_READER)
+ log_debug ("enter: apdu_reset: slot=%d\n", slot);
+
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ {
+ if (DBG_READER)
+ log_debug ("leave: apdu_reset => SW_HOST_NO_DRIVER\n");
+ return SW_HOST_NO_DRIVER;
+ }
+
+ if ((sw = lock_slot (slot)))
+ {
+ if (DBG_READER)
+ log_debug ("leave: apdu_reset => sw=0x%x (lock_slot)\n", sw);
+ return sw;
+ }
+
+ if (reader_table[slot].reset_reader)
+ sw = reader_table[slot].reset_reader (slot);
+
+ unlock_slot (slot);
+ if (DBG_READER)
+ log_debug ("leave: apdu_reset => sw=0x%x\n", sw);
+ return sw;
+}
+
+
+/* Return the ATR or NULL if none is available. On success the length
+ of the ATR is stored at ATRLEN. The caller must free the returned
+ value. */
+unsigned char *
+apdu_get_atr (int slot, size_t *atrlen)
+{
+ unsigned char *buf;
+
+ if (DBG_READER)
+ log_debug ("enter: apdu_get_atr: slot=%d\n", slot);
+
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ {
+ if (DBG_READER)
+ log_debug ("leave: apdu_get_atr => NULL (bad slot)\n");
+ return NULL;
+ }
+ if (!reader_table[slot].atrlen)
+ {
+ if (DBG_READER)
+ log_debug ("leave: apdu_get_atr => NULL (no ATR)\n");
+ return NULL;
+ }
+
+ buf = xtrymalloc (reader_table[slot].atrlen);
+ if (!buf)
+ {
+ if (DBG_READER)
+ log_debug ("leave: apdu_get_atr => NULL (out of core)\n");
+ return NULL;
+ }
+ memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen);
+ *atrlen = reader_table[slot].atrlen;
+ if (DBG_READER)
+ log_debug ("leave: apdu_get_atr => atrlen=%zu\n", *atrlen);
+ return buf;
+}
+
+
+
+/* Retrieve the status for SLOT. The function does only wait for the
+ card to become available if HANG is set to true. On success the
+ bits in STATUS will be set to
+
+ APDU_CARD_USABLE (bit 0) = card present and usable
+ APDU_CARD_PRESENT (bit 1) = card present
+ APDU_CARD_ACTIVE (bit 2) = card active
+ (bit 3) = card access locked [not yet implemented]
+
+ For most applications, testing bit 0 is sufficient.
+*/
+static int
+apdu_get_status_internal (int slot, int hang, unsigned int *status, int on_wire)
+{
+ int sw;
+ unsigned int s = 0;
+
+ 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].get_status_reader)
+ sw = reader_table[slot].get_status_reader (slot, &s, on_wire);
+
+ unlock_slot (slot);
+
+ if (sw)
+ {
+ if (on_wire)
+ reader_table[slot].atrlen = 0;
+ s = 0;
+ }
+
+ if (status)
+ *status = s;
+ return sw;
+}
+
+
+/* See above for a description. */
+int
+apdu_get_status (int slot, int hang, unsigned int *status)
+{
+ int sw;
+
+ if (DBG_READER)
+ log_debug ("enter: apdu_get_status: slot=%d hang=%d\n", slot, hang);
+ sw = apdu_get_status_internal (slot, hang, status, 0);
+ if (DBG_READER)
+ {
+ if (status)
+ log_debug ("leave: apdu_get_status => sw=0x%x status=%u\n",
+ sw, *status);
+ else
+ log_debug ("leave: apdu_get_status => sw=0x%x\n", sw);
+ }
+ return sw;
+}
+
+
+/* Check whether the reader supports the ISO command code COMMAND on
+ the pinpad. Return 0 on success. For a description of the pin
+ parameters, see ccid-driver.c */
+int
+apdu_check_pinpad (int slot, int command, pininfo_t *pininfo)
+{
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ return SW_HOST_NO_DRIVER;
+
+ if (opt.enable_pinpad_varlen)
+ pininfo->fixedlen = 0;
+
+ if (reader_table[slot].check_pinpad)
+ {
+ int sw;
+
+ if ((sw = lock_slot (slot)))
+ return sw;
+
+ sw = reader_table[slot].check_pinpad (slot, command, pininfo);
+ unlock_slot (slot);
+ return sw;
+ }
+ else
+ return SW_HOST_NOT_SUPPORTED;
+}
+
+
+int
+apdu_pinpad_verify (int slot, int class, int ins, int p0, int p1,
+ pininfo_t *pininfo)
+{
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ return SW_HOST_NO_DRIVER;
+
+ if (reader_table[slot].pinpad_verify)
+ {
+ int sw;
+
+ if ((sw = lock_slot (slot)))
+ return sw;
+
+ sw = reader_table[slot].pinpad_verify (slot, class, ins, p0, p1,
+ pininfo);
+ unlock_slot (slot);
+ return sw;
+ }
+ else
+ return SW_HOST_NOT_SUPPORTED;
+}
+
+
+int
+apdu_pinpad_modify (int slot, int class, int ins, int p0, int p1,
+ pininfo_t *pininfo)
+{
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ return SW_HOST_NO_DRIVER;
+
+ if (reader_table[slot].pinpad_modify)
+ {
+ int sw;
+
+ if ((sw = lock_slot (slot)))
+ return sw;
+
+ sw = reader_table[slot].pinpad_modify (slot, class, ins, p0, p1,
+ pininfo);
+ unlock_slot (slot);
+ return sw;
+ }
+ else
+ return SW_HOST_NOT_SUPPORTED;
+}
+
+
+/* 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, pininfo_t *pininfo)
+{
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ return SW_HOST_NO_DRIVER;
+
+ if (reader_table[slot].send_apdu_reader)
+ return reader_table[slot].send_apdu_reader (slot,
+ apdu, apdulen,
+ buffer, buflen,
+ pininfo);
+ else
+ return SW_HOST_NOT_SUPPORTED;
+}
+
+
+/* Core APDU tranceiver function. Parameters are described at
+ apdu_send_le with the exception of PININFO which indicates pinpad
+ related operations if not NULL. If EXTENDED_MODE is not 0
+ command chaining or extended length will be used according to these
+ values:
+ n < 0 := Use command chaining with the data part limited to -n
+ in each chunk. If -1 is used a default value is used.
+ n == 0 := No extended mode or command chaining.
+ n == 1 := Use extended length for input and output without a
+ length limit.
+ n > 1 := Use extended length with up to N bytes.
+
+*/
+static int
+send_le (int slot, int class, int ins, int p0, int p1,
+ int lc, const char *data, int le,
+ unsigned char **retbuf, size_t *retbuflen,
+ pininfo_t *pininfo, int extended_mode)
+{
+#define SHORT_RESULT_BUFFER_SIZE 258
+ /* We allocate 8 extra bytes as a safety margin towards a driver bug. */
+ unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10];
+ unsigned char *result_buffer = NULL;
+ size_t result_buffer_size;
+ unsigned char *result;
+ size_t resultlen;
+ unsigned char short_apdu_buffer[5+256+1];
+ unsigned char *apdu_buffer = NULL;
+ size_t apdu_buffer_size;
+ unsigned char *apdu;
+ size_t apdulen;
+ int sw;
+ long rc; /* We need a long here due to PC/SC. */
+ int did_exact_length_hack = 0;
+ int use_chaining = 0;
+ int use_extended_length = 0;
+ int lc_chunk;
+
+ 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 p1=%02X p2=%02X lc=%d le=%d em=%d\n",
+ class, ins, p0, p1, lc, le, extended_mode);
+
+ if (lc != -1 && (lc > 255 || lc < 0))
+ {
+ /* Data does not fit into an APDU. What we do now depends on
+ the EXTENDED_MODE parameter. */
+ if (!extended_mode)
+ return SW_WRONG_LENGTH; /* No way to send such an APDU. */
+ else if (extended_mode > 0)
+ use_extended_length = 1;
+ else if (extended_mode < 0)
+ {
+ /* Send APDU using chaining mode. */
+ if (lc > 16384)
+ return SW_WRONG_LENGTH; /* Sanity check. */
+ if ((class&0xf0) != 0)
+ return SW_HOST_INV_VALUE; /* Upper 4 bits need to be 0. */
+ use_chaining = extended_mode == -1? 255 : -extended_mode;
+ use_chaining &= 0xff;
+ }
+ else
+ return SW_HOST_INV_VALUE;
+ }
+ else if (lc == -1 && extended_mode > 0)
+ use_extended_length = 1;
+
+ if (le != -1 && (le > (extended_mode > 0? 255:256) || le < 0))
+ {
+ /* Expected Data does not fit into an APDU. What we do now
+ depends on the EXTENDED_MODE parameter. Note that a check
+ for command chaining does not make sense because we are
+ looking at Le. */
+ if (!extended_mode)
+ return SW_WRONG_LENGTH; /* No way to send such an APDU. */
+ else if (use_extended_length)
+ ; /* We are already using extended length. */
+ else if (extended_mode > 0)
+ use_extended_length = 1;
+ else
+ return SW_HOST_INV_VALUE;
+ }
+
+ if ((!data && lc != -1) || (data && lc == -1))
+ return SW_HOST_INV_VALUE;
+
+ if (use_extended_length)
+ {
+ if (reader_table[slot].is_t0)
+ return SW_HOST_NOT_SUPPORTED;
+
+ /* Space for: cls/ins/p1/p2+Z+2_byte_Lc+Lc+2_byte_Le. */
+ apdu_buffer_size = 4 + 1 + (lc >= 0? (2+lc):0) + 2;
+ apdu_buffer = xtrymalloc (apdu_buffer_size + 10);
+ if (!apdu_buffer)
+ return SW_HOST_OUT_OF_CORE;
+ apdu = apdu_buffer;
+ }
+ else
+ {
+ apdu_buffer_size = sizeof short_apdu_buffer;
+ apdu = short_apdu_buffer;
+ }
+
+ if (use_extended_length && (le > 256 || le < 0))
+ {
+ /* Two more bytes are needed for status bytes. */
+ result_buffer_size = le < 0? 4096 : (le + 2);
+ result_buffer = xtrymalloc (result_buffer_size);
+ if (!result_buffer)
+ {
+ xfree (apdu_buffer);
+ return SW_HOST_OUT_OF_CORE;
+ }
+ result = result_buffer;
+ }
+ else
+ {
+ result_buffer_size = SHORT_RESULT_BUFFER_SIZE;
+ result = short_result_buffer;
+ }
+#undef SHORT_RESULT_BUFFER_SIZE
+
+ if ((sw = lock_slot (slot)))
+ {
+ xfree (apdu_buffer);
+ xfree (result_buffer);
+ return sw;
+ }
+
+ do
+ {
+ if (use_extended_length)
+ {
+ use_chaining = 0;
+ apdulen = 0;
+ apdu[apdulen++] = class;
+ apdu[apdulen++] = ins;
+ apdu[apdulen++] = p0;
+ apdu[apdulen++] = p1;
+ if (lc > 0)
+ {
+ apdu[apdulen++] = 0; /* Z byte: Extended length marker. */
+ apdu[apdulen++] = ((lc >> 8) & 0xff);
+ apdu[apdulen++] = (lc & 0xff);
+ memcpy (apdu+apdulen, data, lc);
+ data += lc;
+ apdulen += lc;
+ }
+ if (le != -1)
+ {
+ if (lc <= 0)
+ apdu[apdulen++] = 0; /* Z byte: Extended length marker. */
+ apdu[apdulen++] = ((le >> 8) & 0xff);
+ apdu[apdulen++] = (le & 0xff);
+ }
+ }
+ else
+ {
+ apdulen = 0;
+ apdu[apdulen] = class;
+ if (use_chaining && lc > 255)
+ {
+ apdu[apdulen] |= 0x10;
+ log_assert (use_chaining < 256);
+ lc_chunk = use_chaining;
+ lc -= use_chaining;
+ }
+ else
+ {
+ use_chaining = 0;
+ lc_chunk = lc;
+ }
+ apdulen++;
+ apdu[apdulen++] = ins;
+ apdu[apdulen++] = p0;
+ apdu[apdulen++] = p1;
+ if (lc_chunk != -1)
+ {
+ apdu[apdulen++] = lc_chunk;
+ memcpy (apdu+apdulen, data, lc_chunk);
+ data += lc_chunk;
+ apdulen += lc_chunk;
+ /* 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 && !use_chaining)
+ apdu[apdulen++] = le; /* Truncation is okay (0 means 256). */
+ }
+
+ exact_length_hack:
+ /* As a safeguard don't pass any garbage to the driver. */
+ log_assert (apdulen <= apdu_buffer_size);
+ memset (apdu+apdulen, 0, apdu_buffer_size - apdulen);
+ resultlen = result_buffer_size;
+ rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo);
+ if (rc || resultlen < 2)
+ {
+ log_info ("apdu_send_simple(%d) failed: %s\n",
+ slot, apdu_strerror (rc));
+ unlock_slot (slot);
+ xfree (apdu_buffer);
+ xfree (result_buffer);
+ return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
+ }
+ sw = (result[resultlen-2] << 8) | result[resultlen-1];
+ if (!use_extended_length
+ && !did_exact_length_hack && SW_EXACT_LENGTH_P (sw))
+ {
+ apdu[apdulen-1] = (sw & 0x00ff);
+ did_exact_length_hack = 1;
+ goto exact_length_hack;
+ }
+ }
+ while (use_chaining && sw == SW_SUCCESS);
+
+ if (apdu_buffer)
+ {
+ xfree (apdu_buffer);
+ apdu_buffer = NULL;
+ }
+
+ /* Store away the returned data but strip the statusword. */
+ resultlen -= 2;
+ if (DBG_CARD_IO)
+ {
+ log_debug (" response: sw=%04X datalen=%d\n",
+ sw, (unsigned int)resultlen);
+ if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
+ {
+ if (all_zero_p (result, resultlen))
+ log_debug (" dump: [all zero]\n");
+ else
+ log_printhex (result, resultlen, " dump:");
+ }
+ }
+
+ if (sw == SW_SUCCESS || sw == SW_EOF_REACHED)
+ {
+ if (retbuf)
+ {
+ *retbuf = xtrymalloc (resultlen? resultlen : 1);
+ if (!*retbuf)
+ {
+ unlock_slot (slot);
+ xfree (result_buffer);
+ return SW_HOST_OUT_OF_CORE;
+ }
+ *retbuflen = resultlen;
+ memcpy (*retbuf, result, resultlen);
+ }
+ }
+ else if ((sw & 0xff00) == SW_MORE_DATA)
+ {
+ unsigned char *p = NULL, *tmp;
+ size_t bufsize = 4096;
+
+ /* It is likely that we need to return much more data, so we
+ start off with a large buffer. */
+ if (retbuf)
+ {
+ *retbuf = p = xtrymalloc (bufsize);
+ if (!*retbuf)
+ {
+ unlock_slot (slot);
+ xfree (result_buffer);
+ return SW_HOST_OUT_OF_CORE;
+ }
+ log_assert (resultlen < bufsize);
+ memcpy (p, result, resultlen);
+ p += resultlen;
+ }
+
+ do
+ {
+ int len = (sw & 0x00ff);
+
+ if (DBG_CARD_IO)
+ log_debug ("apdu_send_simple(%d): %d more bytes available\n",
+ slot, len);
+ apdu_buffer_size = sizeof short_apdu_buffer;
+ apdu = short_apdu_buffer;
+ apdulen = 0;
+ apdu[apdulen++] = class;
+ apdu[apdulen++] = 0xC0;
+ apdu[apdulen++] = 0;
+ apdu[apdulen++] = 0;
+ apdu[apdulen++] = len;
+ log_assert (apdulen <= apdu_buffer_size);
+ memset (apdu+apdulen, 0, apdu_buffer_size - apdulen);
+ resultlen = result_buffer_size;
+ rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
+ if (rc || resultlen < 2)
+ {
+ log_error ("apdu_send_simple(%d) for get response failed: %s\n",
+ slot, apdu_strerror (rc));
+ unlock_slot (slot);
+ xfree (result_buffer);
+ return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
+ }
+ sw = (result[resultlen-2] << 8) | result[resultlen-1];
+ resultlen -= 2;
+ if (DBG_CARD_IO)
+ {
+ log_debug (" more: sw=%04X datalen=%d\n",
+ sw, (unsigned int)resultlen);
+ if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
+ {
+ if (all_zero_p (result, resultlen))
+ log_debug ( " dump: [all zero]\n");
+ else
+ log_printhex (result, resultlen, " dump:");
+ }
+ }
+
+ if ((sw & 0xff00) == SW_MORE_DATA
+ || sw == SW_SUCCESS
+ || sw == SW_EOF_REACHED )
+ {
+ if (retbuf && resultlen)
+ {
+ if (p - *retbuf + resultlen > bufsize)
+ {
+ bufsize += resultlen > 4096? resultlen: 4096;
+ tmp = xtryrealloc (*retbuf, bufsize);
+ if (!tmp)
+ {
+ unlock_slot (slot);
+ xfree (result_buffer);
+ return SW_HOST_OUT_OF_CORE;
+ }
+ p = tmp + (p - *retbuf);
+ *retbuf = tmp;
+ }
+ memcpy (p, result, resultlen);
+ p += resultlen;
+ }
+ }
+ else
+ log_info ("apdu_send_simple(%d) "
+ "got unexpected status %04X from get response\n",
+ slot, sw);
+ }
+ while ((sw & 0xff00) == SW_MORE_DATA);
+
+ if (retbuf)
+ {
+ *retbuflen = p - *retbuf;
+ tmp = xtryrealloc (*retbuf, *retbuflen);
+ if (tmp)
+ *retbuf = tmp;
+ }
+ }
+
+ unlock_slot (slot);
+ xfree (result_buffer);
+
+ if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS)
+ {
+ if (all_zero_p (*retbuf, *retbuflen))
+ log_debug (" dump: [all zero]\n");
+ else
+ log_printhex (*retbuf, *retbuflen, " dump:");
+ }
+
+ return sw;
+}
+
+/* Send an APDU to the card in SLOT. The APDU is created from all
+ given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1
+ for LC won't sent this field and the data field; in this case DATA
+ must also be passed as NULL. If EXTENDED_MODE is not 0 command
+ chaining or extended length will be used; see send_le for details.
+ The return value is the status word or -1 for an invalid SLOT or
+ other non card related error. If RETBUF is not NULL, it will
+ receive an allocated buffer with the returned data. The length of
+ that data will be put into *RETBUFLEN. The caller is responsible
+ for releasing the buffer even in case of errors. */
+int
+apdu_send_le(int slot, int extended_mode,
+ int class, int ins, int p0, int p1,
+ int lc, const char *data, int le,
+ unsigned char **retbuf, size_t *retbuflen)
+{
+ return send_le (slot, class, ins, p0, p1,
+ lc, data, le,
+ retbuf, retbuflen,
+ NULL, extended_mode);
+}
+
+
+/* Send an APDU to the card in SLOT. The APDU is created from all
+ given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
+ LC won't sent this field and the data field; in this case DATA must
+ also be passed as NULL. If EXTENDED_MODE is not 0 command chaining
+ or extended length will be used; see send_le for details. The
+ return value is the status word or -1 for an invalid SLOT or other
+ non card related error. If RETBUF is not NULL, it will receive an
+ allocated buffer with the returned data. The length of that data
+ will be put into *RETBUFLEN. The caller is responsible for
+ releasing the buffer even in case of errors. */
+int
+apdu_send (int slot, int extended_mode,
+ int class, int ins, int p0, int p1,
+ int lc, const char *data, unsigned char **retbuf, size_t *retbuflen)
+{
+ return send_le (slot, class, ins, p0, p1, lc, data, 256,
+ retbuf, retbuflen, NULL, extended_mode);
+}
+
+/* Send an APDU to the card in SLOT. The APDU is created from all
+ given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
+ LC won't sent this field and the data field; in this case DATA must
+ also be passed as NULL. If EXTENDED_MODE is not 0 command chaining
+ or extended length will be used; see send_le for details. The
+ return value is the status word or -1 for an invalid SLOT or other
+ non card related error. No data will be returned. */
+int
+apdu_send_simple (int slot, int extended_mode,
+ int class, int ins, int p0, int p1,
+ int lc, const char *data)
+{
+ return send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL, NULL,
+ extended_mode);
+}
+
+
+/* This is a more generic version of the apdu sending routine. It
+ * takes an already formatted APDU in APDUDATA or length APDUDATALEN
+ * and returns with an APDU including the status word. With
+ * HANDLE_MORE set to true this function will handle the MORE DATA
+ * status and return all APDUs concatenated with one status word at
+ * the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed
+ * with a max. result data length of EXTENDED_LENGTH bytes. The
+ * function does not return a regular status word but 0 on success.
+ * If the slot is locked, the function returns immediately with an
+ * error.
+ *
+ * Out of historical reasons the function returns 0 on success and
+ * outs the status word at the end of the result to be able to get the
+ * status word in the case of a not provided RETBUF, R_SW can be used
+ * to store the SW. But note that R_SW qill only be set if the
+ * function returns 0. */
+int
+apdu_send_direct (int slot, size_t extended_length,
+ const unsigned char *apdudata, size_t apdudatalen,
+ int handle_more, unsigned int *r_sw,
+ unsigned char **retbuf, size_t *retbuflen)
+{
+#define SHORT_RESULT_BUFFER_SIZE 258
+ unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10];
+ unsigned char *result_buffer = NULL;
+ size_t result_buffer_size;
+ unsigned char *result;
+ size_t resultlen;
+ unsigned char short_apdu_buffer[5+256+10];
+ unsigned char *apdu_buffer = NULL;
+ unsigned char *apdu;
+ size_t apdulen;
+ int sw;
+ long rc; /* we need a long here due to PC/SC. */
+ int class;
+
+ if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
+ return SW_HOST_NO_DRIVER;
+
+ if (apdudatalen > 65535)
+ return SW_HOST_INV_VALUE;
+
+ if (apdudatalen > sizeof short_apdu_buffer - 5)
+ {
+ apdu_buffer = xtrymalloc (apdudatalen + 5);
+ if (!apdu_buffer)
+ return SW_HOST_OUT_OF_CORE;
+ apdu = apdu_buffer;
+ }
+ else
+ {
+ apdu = short_apdu_buffer;
+ }
+ apdulen = apdudatalen;
+ memcpy (apdu, apdudata, apdudatalen);
+ class = apdulen? *apdu : 0;
+
+ if (extended_length >= 256 && extended_length <= 65536)
+ {
+ result_buffer_size = extended_length;
+ result_buffer = xtrymalloc (result_buffer_size + 10);
+ if (!result_buffer)
+ {
+ xfree (apdu_buffer);
+ return SW_HOST_OUT_OF_CORE;
+ }
+ result = result_buffer;
+ }
+ else
+ {
+ result_buffer_size = SHORT_RESULT_BUFFER_SIZE;
+ result = short_result_buffer;
+ }
+#undef SHORT_RESULT_BUFFER_SIZE
+
+ if ((sw = lock_slot (slot)))
+ {
+ xfree (apdu_buffer);
+ xfree (result_buffer);
+ return sw;
+ }
+
+ resultlen = result_buffer_size;
+ rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
+ xfree (apdu_buffer);
+ apdu_buffer = NULL;
+ if (rc || resultlen < 2)
+ {
+ log_error ("apdu_send_direct(%d) failed: %s\n",
+ slot, apdu_strerror (rc));
+ unlock_slot (slot);
+ xfree (result_buffer);
+ return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
+ }
+ sw = (result[resultlen-2] << 8) | result[resultlen-1];
+ /* Store away the returned data but strip the statusword. */
+ resultlen -= 2;
+ if (DBG_CARD_IO)
+ {
+ log_debug (" response: sw=%04X datalen=%d\n",
+ sw, (unsigned int)resultlen);
+ if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
+ log_printhex (result, resultlen, " dump: ");
+ }
+
+ if (handle_more && (sw & 0xff00) == SW_MORE_DATA)
+ {
+ unsigned char *p = NULL, *tmp;
+ size_t bufsize = 4096;
+
+ /* It is likely that we need to return much more data, so we
+ start off with a large buffer. */
+ if (retbuf)
+ {
+ *retbuf = p = xtrymalloc (bufsize + 2);
+ if (!*retbuf)
+ {
+ unlock_slot (slot);
+ xfree (result_buffer);
+ return SW_HOST_OUT_OF_CORE;
+ }
+ log_assert (resultlen < bufsize);
+ memcpy (p, result, resultlen);
+ p += resultlen;
+ }
+
+ do
+ {
+ int len = (sw & 0x00ff);
+
+ if (DBG_CARD_IO)
+ log_debug ("apdu_send_direct(%d): %d more bytes available\n",
+ slot, len);
+ apdu = short_apdu_buffer;
+ apdulen = 0;
+ apdu[apdulen++] = class;
+ apdu[apdulen++] = 0xC0;
+ apdu[apdulen++] = 0;
+ apdu[apdulen++] = 0;
+ apdu[apdulen++] = len;
+ memset (apdu+apdulen, 0, sizeof (short_apdu_buffer) - apdulen);
+ resultlen = result_buffer_size;
+ rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
+ if (rc || resultlen < 2)
+ {
+ log_error ("apdu_send_direct(%d) for get response failed: %s\n",
+ slot, apdu_strerror (rc));
+ unlock_slot (slot);
+ xfree (result_buffer);
+ return rc ? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
+ }
+ sw = (result[resultlen-2] << 8) | result[resultlen-1];
+ resultlen -= 2;
+ if (DBG_CARD_IO)
+ {
+ log_debug (" more: sw=%04X datalen=%d\n",
+ sw, (unsigned int)resultlen);
+ if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
+ log_printhex (result, resultlen, " dump: ");
+ }
+
+ if ((sw & 0xff00) == SW_MORE_DATA
+ || sw == SW_SUCCESS
+ || sw == SW_EOF_REACHED )
+ {
+ if (retbuf && resultlen)
+ {
+ if (p - *retbuf + resultlen > bufsize)
+ {
+ bufsize += resultlen > 4096? resultlen: 4096;
+ tmp = xtryrealloc (*retbuf, bufsize + 2);
+ if (!tmp)
+ {
+ unlock_slot (slot);
+ xfree (result_buffer);
+ return SW_HOST_OUT_OF_CORE;
+ }
+ p = tmp + (p - *retbuf);
+ *retbuf = tmp;
+ }
+ memcpy (p, result, resultlen);
+ p += resultlen;
+ }
+ }
+ else
+ log_info ("apdu_send_direct(%d) "
+ "got unexpected status %04X from get response\n",
+ slot, sw);
+ }
+ while ((sw & 0xff00) == SW_MORE_DATA);
+
+ if (retbuf)
+ {
+ *retbuflen = p - *retbuf;
+ tmp = xtryrealloc (*retbuf, *retbuflen + 2);
+ if (tmp)
+ *retbuf = tmp;
+ }
+ }
+ else
+ {
+ if (retbuf)
+ {
+ *retbuf = xtrymalloc ((resultlen? resultlen : 1)+2);
+ if (!*retbuf)
+ {
+ unlock_slot (slot);
+ xfree (result_buffer);
+ return SW_HOST_OUT_OF_CORE;
+ }
+ *retbuflen = resultlen;
+ memcpy (*retbuf, result, resultlen);
+ }
+ }
+
+ unlock_slot (slot);
+ xfree (result_buffer);
+
+ /* Append the status word. Note that we reserved the two extra
+ bytes while allocating the buffer. */
+ if (retbuf)
+ {
+ (*retbuf)[(*retbuflen)++] = (sw >> 8);
+ (*retbuf)[(*retbuflen)++] = sw;
+ }
+
+ if (r_sw)
+ *r_sw = sw;
+
+ if (DBG_CARD_IO && retbuf)
+ log_printhex (*retbuf, *retbuflen, " dump: ");
+
+
+ return 0;
+}
+
+
+const char *
+apdu_get_reader_name (int slot)
+{
+ return reader_table[slot].rdrname;
+}
+
+
+/* Return the list of currently known readers. Caller must free the
+ * returned value. Might return NULL. */
+char *
+apdu_get_reader_list (void)
+{
+ membuf_t mb;
+ char *ccidlist = NULL;
+
+ init_membuf (&mb, 256);
+#ifdef HAVE_LIBUSB
+ ccidlist = ccid_get_reader_list ();
+#endif
+
+ if (ccidlist && *ccidlist)
+ put_membuf_str (&mb, ccidlist);
+ if (pcsc.reader_list && *pcsc.reader_list)
+ {
+ if (ccidlist && *ccidlist)
+ put_membuf (&mb, "\n", 1);
+ put_membuf_str (&mb, pcsc.reader_list);
+ }
+ xfree (ccidlist);
+ put_membuf (&mb, "", 1);
+
+ return get_membuf (&mb, NULL);
+}
+
+
+gpg_error_t
+apdu_init (void)
+{
+#ifdef USE_NPTH
+ gpg_error_t err;
+ int i;
+
+ pcsc.context = -1;
+ pcsc.context_valid = 0;
+ pcsc.reader_list = NULL;
+
+ if (npth_mutex_init (&reader_table_lock, NULL))
+ goto leave;
+
+ for (i = 0; i < MAX_READER; i++)
+ if (npth_mutex_init (&reader_table[i].lock, NULL))
+ goto leave;
+
+ /* All done well. */
+ return 0;
+
+ leave:
+ err = gpg_error_from_syserror ();
+ log_error ("apdu: error initializing mutex: %s\n", gpg_strerror (err));
+ return err;
+#endif /*USE_NPTH*/
+ return 0;
+}