aboutsummaryrefslogtreecommitdiffstats
path: root/scd/pcsc-wrapper.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--scd/pcsc-wrapper.c631
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:
+*/