diff options
author | Werner Koch <[email protected]> | 2004-04-20 14:17:10 +0000 |
---|---|---|
committer | Werner Koch <[email protected]> | 2004-04-20 14:17:10 +0000 |
commit | e950b01ff56c86d8e04b75a7bac4234afc939199 (patch) | |
tree | df584bfcd6ea6348371a037e2983c393c3f02a0d /scd/pcsc-wrapper.c | |
parent | Include jnlib/types.h and remove our own (diff) | |
download | gnupg-e950b01ff56c86d8e04b75a7bac4234afc939199.tar.gz gnupg-e950b01ff56c86d8e04b75a7bac4234afc939199.zip |
* pcsc-wrapper.c: New.
* Makefile.am (pkglib_PROGRAMS): Install it here.
* apdu.c (writen, readn): New.
(open_pcsc_reader, pcsc_send_apdu, close_pcsc_reader): Use the
pcsc-wrapper if we are using Pth.
Diffstat (limited to '')
-rw-r--r-- | scd/pcsc-wrapper.c | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/scd/pcsc-wrapper.c b/scd/pcsc-wrapper.c new file mode 100644 index 000000000..4f47ee95c --- /dev/null +++ b/scd/pcsc-wrapper.c @@ -0,0 +1,631 @@ +/* pcsc-wrapper.c - Wrapper for ccessing the PC/SC service + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* + This wrapper is required to handle problems with the libpscslite + library. That library assumes that pthreads are used and fails + badly if one tries to use it with a procerss using Pth. + + The operation model is pretty simple: It reads requests from stdin + and returns the answer on stdout. There is no direct mapping to the + pcsc interface but to a higher level one which resembles the code + used in scdaemon (apdu.c) when not using Pth or while running under + Windows. + + The interface is binary consisting of a command tag and the length + of the parameter list. The calling process needs to pass the + version number of the interface on the command line to make sure + that both agree on the same interface. For each port a separate + instance of this process needs to be started. + +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> +#include <assert.h> +#include <dlfcn.h> + + +#define PGM "pcsc-wrapper" + +/* Allow for a standalone build. */ +#ifdef VERSION +#define MYVERSION_LINE PGM " (GnuPG) " VERSION +#define BUGREPORT_LINE "\nReport bugs to <[email protected]>.\n" +#else +#define MYVERSION_LINE PGM +#define BUGREPORT_LINE "" +#endif + +#define DEFAULT_PCSC_DRIVER "libpcsclite.so" + + +static int verbose; + + +/* PC/SC constants and function pointer. */ +#define PCSC_SCOPE_USER 0 +#define PCSC_SCOPE_TERMINAL 1 +#define PCSC_SCOPE_SYSTEM 2 +#define PCSC_SCOPE_GLOBAL 3 + +#define PCSC_PROTOCOL_T0 1 +#define PCSC_PROTOCOL_T1 2 +#define PCSC_PROTOCOL_RAW 4 + +#define PCSC_SHARE_EXCLUSIVE 1 +#define PCSC_SHARE_SHARED 2 +#define PCSC_SHARE_DIRECT 3 + +#define PCSC_LEAVE_CARD 0 +#define PCSC_RESET_CARD 1 +#define PCSC_UNPOWER_CARD 2 +#define PCSC_EJECT_CARD 3 + +struct pcsc_io_request_s { + unsigned long protocol; + unsigned long pci_len; +}; + +typedef struct pcsc_io_request_s *pcsc_io_request_t; + + +static int driver_is_open; /* True if the PC/SC driver has been + initialzied and is ready for + operations. The follwoing variables + are then valid. */ +static unsigned long pcsc_context; /* The current PC/CS context. */ +static unsigned long pcsc_card; +static unsigned long pcsc_protocol; +static unsigned char current_atr[33]; +static size_t current_atrlen; + +long (* pcsc_establish_context) (unsigned long scope, + const void *reserved1, + const void *reserved2, + unsigned long *r_context); +long (* pcsc_release_context) (unsigned long context); +long (* pcsc_list_readers) (unsigned long context, + const char *groups, + char *readers, unsigned long*readerslen); +long (* pcsc_connect) (unsigned long context, + const char *reader, + unsigned long share_mode, + unsigned long preferred_protocols, + unsigned long *r_card, + unsigned long *r_active_protocol); +long (* pcsc_disconnect) (unsigned long card, + unsigned long disposition); +long (* pcsc_status) (unsigned long card, + char *reader, unsigned long *readerlen, + unsigned long *r_state, + unsigned long *r_protocol, + unsigned char *atr, unsigned long *atrlen); +long (* pcsc_begin_transaction) (unsigned long card); +long (* pcsc_end_transaction) (unsigned long card); +long (* pcsc_transmit) (unsigned long card, + const pcsc_io_request_t send_pci, + const unsigned char *send_buffer, + unsigned long send_len, + pcsc_io_request_t recv_pci, + unsigned char *recv_buffer, + unsigned long *recv_len); +long (* pcsc_set_timeout) (unsigned long context, + unsigned long timeout); + + + +static void +bad_request (const char *type) +{ + fprintf (stderr, PGM ": bad `%s' request\n", type); + exit (1); +} + +static void +request_failed (int err) +{ + if (!err) + err = -1; + + putchar (0x81); /* Simple error/success response. */ + + putchar (0); + putchar (0); + putchar (0); + putchar (4); + + putchar ((err >> 24) & 0xff); + putchar ((err >> 16) & 0xff); + putchar ((err >> 8) & 0xff); + putchar ((err ) & 0xff); + + fflush (stdout); +} + + +static void +request_succeeded (const void *buffer, size_t buflen) +{ + size_t len; + + putchar (0x81); /* Simple error/success response. */ + + len = 4 + buflen; + putchar ((len >> 24) & 0xff); + putchar ((len >> 16) & 0xff); + putchar ((len >> 8) & 0xff); + putchar ((len ) & 0xff); + + /* Error code. */ + putchar (0); + putchar (0); + putchar (0); + putchar (0); + + /* Optional reponse string. */ + if (buffer) + fwrite (buffer, buflen, 1, stdout); + + fflush (stdout); +} + + + +static unsigned long +read_32 (FILE *fp) +{ + int c1, c2, c3, c4; + + c1 = getc (stdin); + c2 = getc (stdin); + c3 = getc (stdin); + c4 = getc (stdin); + if (c1 == EOF || c2 == EOF || c3 == EOF || c4 == EOF) + { + fprintf (stderr, PGM ": premature EOF while parsing request\n"); + exit (1); + } + return (c1 << 24) | (c2 << 16) | (c3 << 8) | c4; +} + + + +static const char * +pcsc_error_string (long err) +{ + const char *s; + + if (!err) + return "okay"; + if ((err & 0x80100000) != 0x80100000) + return "invalid PC/SC error code"; + err &= 0xffff; + switch (err) + { + case 0x0002: s = "cancelled"; break; + case 0x000e: s = "can't dispose"; break; + case 0x0008: s = "insufficient buffer"; break; + case 0x0015: s = "invalid ATR"; break; + case 0x0003: s = "invalid handle"; break; + case 0x0004: s = "invalid parameter"; break; + case 0x0005: s = "invalid target"; break; + case 0x0011: s = "invalid value"; break; + case 0x0006: s = "no memory"; break; + case 0x0013: s = "comm error"; break; + case 0x0001: s = "internal error"; break; + case 0x0014: s = "unknown error"; break; + case 0x0007: s = "waited too long"; break; + case 0x0009: s = "unknown reader"; break; + case 0x000a: s = "timeout"; break; + case 0x000b: s = "sharing violation"; break; + case 0x000c: s = "no smartcard"; break; + case 0x000d: s = "unknown card"; break; + case 0x000f: s = "proto mismatch"; break; + case 0x0010: s = "not ready"; break; + case 0x0012: s = "system cancelled"; break; + case 0x0016: s = "not transacted"; break; + case 0x0017: s = "reader unavailable"; break; + case 0x0065: s = "unsupported card"; break; + case 0x0066: s = "unresponsive card"; break; + case 0x0067: s = "unpowered card"; break; + case 0x0068: s = "reset card"; break; + case 0x0069: s = "removed card"; break; + case 0x006a: s = "inserted card"; break; + case 0x001f: s = "unsupported feature"; break; + case 0x0019: s = "PCI too small"; break; + case 0x001a: s = "reader unsupported"; break; + case 0x001b: s = "duplicate reader"; break; + case 0x001c: s = "card unsupported"; break; + case 0x001d: s = "no service"; break; + case 0x001e: s = "service stopped"; break; + default: s = "unknown PC/SC error code"; break; + } + return s; +} + +static void +load_pcsc_driver (const char *libname) +{ + void *handle; + + handle = dlopen (libname, RTLD_LAZY); + if (!handle) + { + fprintf (stderr, PGM ": failed to open driver `%s': %s", + libname, dlerror ()); + exit (1); + } + + pcsc_establish_context = dlsym (handle, "SCardEstablishContext"); + pcsc_release_context = dlsym (handle, "SCardReleaseContext"); + pcsc_list_readers = dlsym (handle, "SCardListReaders"); + pcsc_connect = dlsym (handle, "SCardConnect"); + pcsc_disconnect = dlsym (handle, "SCardDisconnect"); + pcsc_status = dlsym (handle, "SCardStatus"); + pcsc_begin_transaction = dlsym (handle, "SCardBeginTransaction"); + pcsc_end_transaction = dlsym (handle, "SCardEndTransaction"); + pcsc_transmit = dlsym (handle, "SCardTransmit"); + pcsc_set_timeout = dlsym (handle, "SCardSetTimeout"); + + if (!pcsc_establish_context + || !pcsc_release_context + || !pcsc_list_readers + || !pcsc_connect + || !pcsc_disconnect + || !pcsc_status + || !pcsc_begin_transaction + || !pcsc_end_transaction + || !pcsc_transmit + /* || !pcsc_set_timeout */) + { + /* Note that set_timeout is currently not used and also not + available under Windows. */ + fprintf (stderr, + "apdu_open_reader: invalid PC/SC driver " + "(%d%d%d%d%d%d%d%d%d%d)\n", + !!pcsc_establish_context, + !!pcsc_release_context, + !!pcsc_list_readers, + !!pcsc_connect, + !!pcsc_disconnect, + !!pcsc_status, + !!pcsc_begin_transaction, + !!pcsc_end_transaction, + !!pcsc_transmit, + !!pcsc_set_timeout ); + dlclose (handle); + exit (1); + } +} + + + + +/* Handle a open request. The argument is expected to be a string + with the port indentification. ARGBUF is always guaranteed to be + terminted by a 0 which is not counted in ARGLEN. We may modifiy + ARGBUF. */ +static void +handle_open (unsigned char *argbuf, size_t arglen) +{ + long err; + const char * portstr; + char *list = NULL; + unsigned long nreader, listlen, atrlen; + char *p; + unsigned long card_state, card_protocol; + unsigned char atr[33]; + + /* Make sure there is only the port string */ + if (arglen != strlen (argbuf)) + bad_request ("OPEN"); + portstr = argbuf; + + if (driver_is_open) + { + fprintf (stderr, PGM ": PC/SC has already been opened\n"); + request_failed (-1); + } + + err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL, &pcsc_context); + if (err) + { + fprintf (stderr, PGM": pcsc_establish_context failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + request_failed (err); + return; + } + + err = pcsc_list_readers (pcsc_context, NULL, NULL, &nreader); + if (!err) + { + list = malloc (nreader+1); /* Better add 1 for safety reasons. */ + if (!list) + { + fprintf (stderr, PGM": error allocating memory for reader list\n"); + exit (1); + } + err = pcsc_list_readers (pcsc_context, NULL, list, &nreader); + } + if (err) + { + fprintf (stderr, PGM": pcsc_list_readers failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + pcsc_release_context (pcsc_context); + free (list); + request_failed (err); + return; + } + + listlen = nreader; + p = list; + while (nreader) + { + if (!*p && !p[1]) + break; + fprintf (stderr, PGM": detected reader `%s'\n", p); + if (nreader < (strlen (p)+1)) + { + fprintf (stderr, PGM": invalid response from pcsc_list_readers\n"); + break; + } + nreader -= strlen (p)+1; + p += strlen (p) + 1; + } + + err = pcsc_connect (pcsc_context, + portstr && *portstr? portstr : list, + PCSC_SHARE_EXCLUSIVE, + PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, + &pcsc_card, + &pcsc_protocol); + if (err) + { + fprintf (stderr, PGM": pcsc_connect failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + pcsc_release_context (pcsc_context); + free (list); + request_failed (err); + return; + } + + atrlen = 32; + /* (We need to pass a dummy buffer. We use LIST because it ought to + be large enough.) */ + err = pcsc_status (pcsc_card, + list, &listlen, + &card_state, &card_protocol, + atr, &atrlen); + free (list); + if (err) + { + fprintf (stderr, PGM": pcsc_status failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + pcsc_release_context (pcsc_context); + request_failed (err); + return; + } + if (atrlen >= sizeof atr || atrlen >= sizeof current_atr) + { + fprintf (stderr, PGM": ATR returned by pcsc_status is too large\n"); + exit (4); + } + memcpy (current_atr, atr, atrlen); + current_atrlen = atrlen; + driver_is_open = 1; + request_succeeded (current_atr, current_atrlen); +} + + + +/* Handle a close request. We expect no arguments. We may modifiy + ARGBUF. */ +static void +handle_close (unsigned char *argbuf, size_t arglen) +{ + if (!driver_is_open) + { + fprintf (stderr, PGM ": PC/SC has not yet been opened\n"); + request_failed (-1); + } + + pcsc_release_context (pcsc_context); + + request_succeeded (NULL, 0); +} + + + +/* Handle a transmit request. The argument is expected to be a bufer + with the APDU. We may modifiy ARGBUF. */ +static void +handle_transmit (unsigned char *argbuf, size_t arglen) +{ + long err; + struct pcsc_io_request_s send_pci; + unsigned long recv_len; + unsigned char buffer[1024]; + + /* The apdu should at least be one byte. */ + if (!arglen) + bad_request ("TRANSMIT"); + + if (!driver_is_open) + { + fprintf (stderr, PGM ": PC/SC has not yet been opened\n"); + request_failed (-1); + } + + if ((pcsc_protocol & PCSC_PROTOCOL_T1)) + send_pci.protocol = PCSC_PROTOCOL_T1; + else + send_pci.protocol = PCSC_PROTOCOL_T0; + send_pci.pci_len = sizeof send_pci; + recv_len = sizeof (buffer); + err = pcsc_transmit (pcsc_card, &send_pci, argbuf, arglen, + NULL, buffer, &recv_len); + if (err) + { + if (verbose) + fprintf (stderr, PGM": pcsc_transmit failed: %s (0x%lx)\n", + pcsc_error_string (err), err); + request_failed (err); + return; + } + request_succeeded (buffer, recv_len); +} + + + + + + + + + + + + +static void +print_version (int with_help) +{ + fputs (MYVERSION_LINE "\n" + "Copyright (C) 2004 Free Software Foundation, Inc.\n" + "This program comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it\n" + "under certain conditions. See the file COPYING for details.\n", + stdout); + + if (with_help) + fputs ("\n" + "Usage: " PGM " [OPTIONS] API-NUMBER [LIBNAME]\n" + "Helper to connect scdaemon to the PC/SC library\n" + "\n" + " --verbose enable extra informational output\n" + " --version print version of the program and exit\n" + " --help display this help and exit\n" + BUGREPORT_LINE, stdout ); + + exit (0); +} + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + int api_number = 0; + int c; + + if (argc) + { + argc--; argv++; + } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--version")) + print_version (0); + else if (!strcmp (*argv, "--help")) + print_version (1); + else if (!strcmp (*argv, "--verbose")) + { + verbose = 1; + argc--; argv++; + } + } + if (argc != 1 && argc != 2) + { + fprintf (stderr, "usage: " PGM " API-NUMBER [LIBNAME]\n"); + exit (1); + } + + api_number = atoi (*argv); + argv++; argc--; + if (api_number != 1) + { + fprintf (stderr, PGM ": api-number %d is not valid\n", api_number); + exit (1); + } + + load_pcsc_driver (argc? *argv : DEFAULT_PCSC_DRIVER); + + while ((c = getc (stdin)) != EOF) + { + size_t arglen; + unsigned char argbuffer[2048]; + + arglen = read_32 (stdin); + if (arglen >= sizeof argbuffer - 1) + { + fprintf (stderr, PGM ": request too long\n"); + exit (1); + } + if (arglen && fread (argbuffer, arglen, 1, stdin) != 1) + { + fprintf (stderr, PGM ": error reading request: %s\n", + strerror (errno)); + exit (1); + } + argbuffer[arglen] = 0; + switch (c) + { + case 1: + handle_open (argbuffer, arglen); + break; + + case 2: + handle_close (argbuffer, arglen); + exit (0); + break; + + case 3: + handle_transmit (argbuffer, arglen); + break; + + default: + fprintf (stderr, PGM ": invalid request 0x%02X\n", c); + exit (1); + } + free (argbuffer); + } + return 0; +} + + + +/* +Local Variables: +compile-command: "gcc -Wall -g -o pcsc-wrapper pcsc-wrapper.c -ldl" +End: +*/ |