summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2009-04-17 20:40:32 +0200
committerWerner Koch <wk@gnupg.org>2009-04-17 20:40:32 +0200
commit5206a2deb3b80f46fdf47f8fcef181a3f24f5388 (patch)
tree9bca797fdcd1abaff3850422bff8a8c1968aa583 /tools
parent2009-04-14 Marcus Brinkmann <marcus@g10code.de> (diff)
downloadgnupg2-5206a2deb3b80f46fdf47f8fcef181a3f24f5388.tar.xz
gnupg2-5206a2deb3b80f46fdf47f8fcef181a3f24f5388.zip
Add a tool to analyze the CCID protocol on the USB bus.
Diffstat (limited to 'tools')
-rw-r--r--tools/ChangeLog4
-rw-r--r--tools/Makefile.am3
-rw-r--r--tools/ccidmon.c789
3 files changed, 795 insertions, 1 deletions
diff --git a/tools/ChangeLog b/tools/ChangeLog
index 6c0920e11..82328eeef 100644
--- a/tools/ChangeLog
+++ b/tools/ChangeLog
@@ -1,3 +1,7 @@
+2009-04-17 Werner Koch <wk@g10code.com>
+
+ * ccidmon.c: New.
+
2009-03-03 Werner Koch <wk@g10code.com>
* gpgconf.c: New command --reload.
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 2a63fa558..1d04964c0 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -19,7 +19,8 @@
EXTRA_DIST = \
Manifest watchgnupg.c \
addgnupghome applygnupgdefaults gpgsm-gencert.sh \
- lspgpot mail-signed-keys convert-from-106 sockprox.c
+ lspgpot mail-signed-keys convert-from-106 sockprox.c \
+ ccidmon.c
AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/common
diff --git a/tools/ccidmon.c b/tools/ccidmon.c
new file mode 100644
index 000000000..03bfc436b
--- /dev/null
+++ b/tools/ccidmon.c
@@ -0,0 +1,789 @@
+/* ccidmon.c - CCID monitor for use with the Linux usbmon facility.
+ * Copyright (C) 2009 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 <http://www.gnu.org/licenses/>.
+ */
+
+
+/* This utility takes the output of usbmon, filters out the bulk data
+ and prints the CCID messages in a human friendly way.
+
+*/
+
+#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 <unistd.h>
+#include <signal.h>
+
+
+#ifndef PACKAGE_VERSION
+# define PACKAGE_VERSION "[build on " __DATE__ " " __TIME__ "]"
+#endif
+#ifndef PACKAGE_BUGREPORT
+# define PACKAGE_BUGREPORT "devnull@example.org"
+#endif
+#define PGM "ccidmon"
+
+/* Option flags. */
+static int verbose;
+static int debug;
+static int skip_escape;
+static int usb_bus, usb_dev;
+
+/* Error counter. */
+static int any_error;
+
+/* Data storage. */
+struct
+{
+ int is_bi;
+ char address[50];
+ int count;
+ char data[2000];
+} databuffer;
+
+
+enum {
+ RDR_to_PC_NotifySlotChange= 0x50,
+ RDR_to_PC_HardwareError = 0x51,
+
+ PC_to_RDR_SetParameters = 0x61,
+ PC_to_RDR_IccPowerOn = 0x62,
+ PC_to_RDR_IccPowerOff = 0x63,
+ PC_to_RDR_GetSlotStatus = 0x65,
+ PC_to_RDR_Secure = 0x69,
+ PC_to_RDR_T0APDU = 0x6a,
+ PC_to_RDR_Escape = 0x6b,
+ PC_to_RDR_GetParameters = 0x6c,
+ PC_to_RDR_ResetParameters = 0x6d,
+ PC_to_RDR_IccClock = 0x6e,
+ PC_to_RDR_XfrBlock = 0x6f,
+ PC_to_RDR_Mechanical = 0x71,
+ PC_to_RDR_Abort = 0x72,
+ PC_to_RDR_SetDataRate = 0x73,
+
+ RDR_to_PC_DataBlock = 0x80,
+ RDR_to_PC_SlotStatus = 0x81,
+ RDR_to_PC_Parameters = 0x82,
+ RDR_to_PC_Escape = 0x83,
+ RDR_to_PC_DataRate = 0x84
+};
+
+
+#define digitp(p) ((p) >= '0' && (p) <= '9')
+#define hexdigitp(a) (digitp (a) \
+ || ((a) >= 'A' && (a) <= 'F') \
+ || ((a) >= 'a' && (a) <= 'f'))
+#define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
+#define xtoi_1(p) ((p) <= '9'? ((p)- '0'): \
+ (p) <= 'F'? ((p)-'A'+10):((p)-'a'+10))
+
+
+
+/* Print diagnostic message and exit with failure. */
+static void
+die (const char *format, ...)
+{
+ va_list arg_ptr;
+
+ fflush (stdout);
+ fprintf (stderr, "%s: ", PGM);
+
+ va_start (arg_ptr, format);
+ vfprintf (stderr, format, arg_ptr);
+ va_end (arg_ptr);
+ putc ('\n', stderr);
+
+ exit (1);
+}
+
+
+/* Print diagnostic message. */
+static void
+err (const char *format, ...)
+{
+ va_list arg_ptr;
+
+ any_error = 1;
+
+ fflush (stdout);
+ fprintf (stderr, "%s: ", PGM);
+
+ va_start (arg_ptr, format);
+ vfprintf (stderr, format, arg_ptr);
+ va_end (arg_ptr);
+ putc ('\n', stderr);
+}
+
+
+/* Convert a little endian stored 4 byte value into an unsigned
+ integer. */
+static unsigned int
+convert_le_u32 (const unsigned char *buf)
+{
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+}
+
+
+/* Convert a little endian stored 2 byte value into an unsigned
+ integer. */
+static unsigned int
+convert_le_u16 (const unsigned char *buf)
+{
+ return buf[0] | (buf[1] << 8);
+}
+
+
+
+
+static void
+print_pr_data (const unsigned char *data, size_t datalen, size_t off)
+{
+ int needlf = 0;
+ int first = 1;
+
+ for (; off < datalen; off++)
+ {
+ if (!(off % 16) || first)
+ {
+ if (needlf)
+ putchar ('\n');
+ printf (" [%04d] ", off);
+ }
+ printf (" %02X", data[off]);
+ needlf = 1;
+ first = 0;
+ }
+ if (needlf)
+ putchar ('\n');
+}
+
+
+static void
+print_p2r_header (const char *name, const unsigned char *msg, size_t msglen)
+{
+ printf ("%s:\n", name);
+ if (msglen < 7)
+ return;
+ printf (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
+ printf (" bSlot .............: %u\n", msg[5]);
+ printf (" bSeq ..............: %u\n", msg[6]);
+}
+
+
+static void
+print_p2r_iccpoweron (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_IccPowerOn", msg, msglen);
+ if (msglen < 10)
+ return;
+ printf (" bPowerSelect ......: 0x%02x (%s)\n", msg[7],
+ msg[7] == 0? "auto":
+ msg[7] == 1? "5.0 V":
+ msg[7] == 2? "3.0 V":
+ msg[7] == 3? "1.8 V":"");
+ print_pr_data (msg, msglen, 8);
+}
+
+
+static void
+print_p2r_iccpoweroff (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_IccPowerOff", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_getslotstatus (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_GetSlotStatus", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_xfrblock (const unsigned char *msg, size_t msglen)
+{
+ unsigned int val;
+
+ print_p2r_header ("PC_to_RDR_XfrBlock", msg, msglen);
+ if (msglen < 10)
+ return;
+ printf (" bBWI ..............: 0x%02x\n", msg[7]);
+ val = convert_le_u16 (msg+8);
+ printf (" wLevelParameter ...: 0x%04x%s\n", val,
+ val == 1? " (continued)":
+ val == 2? " (continues+ends)":
+ val == 3? " (continues+continued)":
+ val == 16? " (DataBlock-expected)":"");
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_p2r_getparameters (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_GetParameters", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_resetparameters (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_ResetParameters", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_setparameters (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_SetParameters", msg, msglen);
+ if (msglen < 10)
+ return;
+ printf (" bProtocolNum ......: 0x%02x\n", msg[7]);
+ print_pr_data (msg, msglen, 8);
+}
+
+
+static void
+print_p2r_escape (const unsigned char *msg, size_t msglen)
+{
+ if (skip_escape)
+ return;
+ print_p2r_header ("PC_to_RDR_Escape", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_iccclock (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_IccClock", msg, msglen);
+ if (msglen < 10)
+ return;
+ printf (" bClockCommand .....: 0x%02x\n", msg[7]);
+ print_pr_data (msg, msglen, 8);
+}
+
+
+static void
+print_p2r_to0apdu (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_T0APDU", msg, msglen);
+ if (msglen < 10)
+ return;
+ printf (" bmChanges .........: 0x%02x\n", msg[7]);
+ printf (" bClassGetResponse .: 0x%02x\n", msg[8]);
+ printf (" bClassEnvelope ....: 0x%02x\n", msg[9]);
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_p2r_secure (const unsigned char *msg, size_t msglen)
+{
+ unsigned int val;
+
+ print_p2r_header ("PC_to_RDR_Secure", msg, msglen);
+ if (msglen < 10)
+ return;
+ printf (" bBMI ..............: 0x%02x\n", msg[7]);
+ val = convert_le_u16 (msg+8);
+ printf (" wLevelParameter ...: 0x%04x%s\n", val,
+ val == 1? " (continued)":
+ val == 2? " (continues+ends)":
+ val == 3? " (continues+continued)":
+ val == 16? " (DataBlock-expected)":"");
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_p2r_mechanical (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_Mechanical", msg, msglen);
+ if (msglen < 10)
+ return;
+ printf (" bFunction .........: 0x%02x\n", msg[7]);
+ print_pr_data (msg, msglen, 8);
+}
+
+
+static void
+print_p2r_abort (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_Abort", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_setdatarate (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_SetDataRate", msg, msglen);
+ if (msglen < 10)
+ return;
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_unknown (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("Unknown PC_to_RDR command", msg, msglen);
+ if (msglen < 10)
+ return;
+ print_pr_data (msg, msglen, 0);
+}
+
+
+static void
+print_p2r (const unsigned char *msg, size_t msglen)
+{
+ switch (msglen? msg[0]:0)
+ {
+ case PC_to_RDR_IccPowerOn:
+ print_p2r_iccpoweron (msg, msglen);
+ break;
+ case PC_to_RDR_IccPowerOff:
+ print_p2r_iccpoweroff (msg, msglen);
+ break;
+ case PC_to_RDR_GetSlotStatus:
+ print_p2r_getslotstatus (msg, msglen);
+ break;
+ case PC_to_RDR_XfrBlock:
+ print_p2r_xfrblock (msg, msglen);
+ break;
+ case PC_to_RDR_GetParameters:
+ print_p2r_getparameters (msg, msglen);
+ break;
+ case PC_to_RDR_ResetParameters:
+ print_p2r_resetparameters (msg, msglen);
+ break;
+ case PC_to_RDR_SetParameters:
+ print_p2r_setparameters (msg, msglen);
+ break;
+ case PC_to_RDR_Escape:
+ print_p2r_escape (msg, msglen);
+ break;
+ case PC_to_RDR_IccClock:
+ print_p2r_iccclock (msg, msglen);
+ break;
+ case PC_to_RDR_T0APDU:
+ print_p2r_to0apdu (msg, msglen);
+ break;
+ case PC_to_RDR_Secure:
+ print_p2r_secure (msg, msglen);
+ break;
+ case PC_to_RDR_Mechanical:
+ print_p2r_mechanical (msg, msglen);
+ break;
+ case PC_to_RDR_Abort:
+ print_p2r_abort (msg, msglen);
+ break;
+ case PC_to_RDR_SetDataRate:
+ print_p2r_setdatarate (msg, msglen);
+ break;
+ default:
+ print_p2r_unknown (msg, msglen);
+ break;
+ }
+}
+
+
+static void
+print_r2p_header (const char *name, const unsigned char *msg, size_t msglen)
+{
+ printf ("%s:\n", name);
+ if (msglen < 9)
+ return;
+ printf (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
+ printf (" bSlot .............: %u\n", msg[5]);
+ printf (" bSeq ..............: %u\n", msg[6]);
+ printf (" bStatus ...........: %u\n", msg[7]);
+ if (msg[8])
+ printf (" bError ............: %u\n", msg[8]);
+}
+
+
+static void
+print_r2p_datablock (const unsigned char *msg, size_t msglen)
+{
+ print_r2p_header ("RDR_to_PC_DataBlock", msg, msglen);
+ if (msglen < 10)
+ return;
+ if (msg[9])
+ printf (" bChainParameter ...: 0x%02x%s\n", msg[9],
+ msg[9] == 1? " (continued)":
+ msg[9] == 2? " (continues+ends)":
+ msg[9] == 3? " (continues+continued)":
+ msg[9] == 16? " (XferBlock-expected)":"");
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_r2p_slotstatus (const unsigned char *msg, size_t msglen)
+{
+ print_r2p_header ("RDR_to_PC_SlotStatus", msg, msglen);
+ if (msglen < 10)
+ return;
+ printf (" bClockStatus ......: 0x%02x%s\n", msg[9],
+ msg[9] == 0? " (running)":
+ msg[9] == 1? " (stopped-L)":
+ msg[9] == 2? " (stopped-H)":
+ msg[9] == 3? " (stopped)":"");
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_r2p_parameters (const unsigned char *msg, size_t msglen)
+{
+ print_r2p_header ("RDR_to_PC_Parameters", msg, msglen);
+ if (msglen < 10)
+ return;
+
+ printf (" protocol ..........: T=%d\n", msg[9]);
+ if (msglen == 17 && msg[9] == 1)
+ {
+ /* Protocol T=1. */
+ printf (" bmFindexDindex ....: %02X\n", msg[10]);
+ printf (" bmTCCKST1 .........: %02X\n", msg[11]);
+ printf (" bGuardTimeT1 ......: %02X\n", msg[12]);
+ printf (" bmWaitingIntegersT1: %02X\n", msg[13]);
+ printf (" bClockStop ........: %02X\n", msg[14]);
+ printf (" bIFSC .............: %d\n", msg[15]);
+ printf (" bNadValue .........: %d\n", msg[16]);
+ }
+ else
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_r2p_escape (const unsigned char *msg, size_t msglen)
+{
+ if (skip_escape)
+ return;
+ print_r2p_header ("RDR_to_PC_Escape", msg, msglen);
+ if (msglen < 10)
+ return;
+ printf (" buffer[9] .........: %02X\n", msg[9]);
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_r2p_datarate (const unsigned char *msg, size_t msglen)
+{
+ print_r2p_header ("RDR_to_PC_DataRate", msg, msglen);
+ if (msglen < 10)
+ return;
+ if (msglen >= 18)
+ {
+ printf (" dwClockFrequency ..: %u\n", convert_le_u32 (msg+10));
+ printf (" dwDataRate ..... ..: %u\n", convert_le_u32 (msg+14));
+ print_pr_data (msg, msglen, 18);
+ }
+ else
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_r2p_unknown (const unsigned char *msg, size_t msglen)
+{
+ print_r2p_header ("Unknown RDR_to_PC command", msg, msglen);
+ if (msglen < 10)
+ return;
+ printf (" bMessageType ......: %02X\n", msg[0]);
+ printf (" buffer[9] .........: %02X\n", msg[9]);
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_r2p (const unsigned char *msg, size_t msglen)
+{
+ switch (msglen? msg[0]:0)
+ {
+ case RDR_to_PC_DataBlock:
+ print_r2p_datablock (msg, msglen);
+ break;
+ case RDR_to_PC_SlotStatus:
+ print_r2p_slotstatus (msg, msglen);
+ break;
+ case RDR_to_PC_Parameters:
+ print_r2p_parameters (msg, msglen);
+ break;
+ case RDR_to_PC_Escape:
+ print_r2p_escape (msg, msglen);
+ break;
+ case RDR_to_PC_DataRate:
+ print_r2p_datarate (msg, msglen);
+ break;
+ default:
+ print_r2p_unknown (msg, msglen);
+ break;
+ }
+
+}
+
+
+static void
+flush_data (void)
+{
+ if (!databuffer.count)
+ return;
+
+ if (verbose)
+ printf ("Address: %s\n", databuffer.address);
+ if (databuffer.is_bi)
+ {
+ print_r2p (databuffer.data, databuffer.count);
+ if (verbose)
+ putchar ('\n');
+ }
+ else
+ print_p2r (databuffer.data, databuffer.count);
+
+ databuffer.count = 0;
+}
+
+static void
+collect_data (char *hexdata, const char *address, unsigned int lineno)
+{
+ size_t length;
+ int is_bi;
+ char *s;
+ unsigned int value;
+
+ is_bi = (*address && address[1] == 'i');
+
+ if (databuffer.is_bi != is_bi || strcmp (databuffer.address, address))
+ flush_data ();
+ databuffer.is_bi = is_bi;
+ if (strlen (address) >= sizeof databuffer.address)
+ die ("address field too long");
+ strcpy (databuffer.address, address);
+
+ length = databuffer.count;
+ for (s=hexdata; *s; s++ )
+ {
+ if (ascii_isspace (*s))
+ continue;
+ if (!hexdigitp (*s))
+ {
+ err ("invalid hex digit in line %u - line skipped", lineno);
+ break;
+ }
+ value = xtoi_1 (*s) * 16;
+ s++;
+ if (!hexdigitp (*s))
+ {
+ err ("invalid hex digit in line %u - line skipped", lineno);
+ break;
+ }
+ value += xtoi_1 (*s);
+
+ if (length >= sizeof (databuffer.data))
+ {
+ err ("too much data at line %u - can handle only up to % bytes",
+ lineno, sizeof (databuffer.data));
+ break;
+ }
+ databuffer.data[length++] = value;
+ }
+ databuffer.count = length;
+}
+
+
+static void
+parse_line (char *line, unsigned int lineno)
+{
+ char *p;
+ char *event_type, *address, *data, *status, *datatag;
+
+ if (debug)
+ printf ("line[%u] =`%s'\n", lineno, line);
+
+ p = strtok (line, " ");
+ if (!p)
+ die ("invalid line %d (no URB)");
+ p = strtok (NULL, " ");
+ if (!p)
+ die ("invalid line %d (no timestamp)");
+ event_type = strtok (NULL, " ");
+ if (!event_type)
+ die ("invalid line %d (no event type)");
+ address = strtok (NULL, " ");
+ if (!address)
+ die ("invalid line %d (no address");
+ if (usb_bus || usb_dev)
+ {
+ int bus, dev;
+
+ p = strchr (address, ':');
+ if (!p)
+ die ("invalid line %d (invalid address");
+ p++;
+ bus = atoi (p);
+ p = strchr (p, ':');
+ if (!p)
+ die ("invalid line %d (invalid address");
+ p++;
+ dev = atoi (p);
+
+ if ((usb_bus && usb_bus != bus) || (usb_dev && usb_dev != dev))
+ return; /* We don't want that one. */
+ }
+ if (*address != 'B' || (address[1] != 'o' && address[1] != 'i'))
+ return; /* We only want block in and block out. */
+ status = strtok (NULL, " ");
+ if (!status)
+ return;
+ if (!strchr ("-0123456789", *status))
+ return; /* Setup packet. */
+ /* We don't support "Z[io]" types thus we don't need to check here. */
+ p = strtok (NULL, " ");
+ if (!p)
+ return; /* No data length. */
+
+ datatag = strtok (NULL, " ");
+ if (datatag && *datatag == '=')
+ {
+ data = strtok (NULL, "");
+ collect_data (data?data:"", address, lineno);
+ }
+}
+
+
+static void
+parse_input (FILE *fp)
+{
+ char line[2000];
+ size_t length;
+ unsigned int lineno = 0;
+
+ while (fgets (line, sizeof (line), fp))
+ {
+ lineno++;
+ length = strlen (line);
+ if (length && line[length - 1] == '\n')
+ line[--length] = 0;
+ else
+ err ("line number %u too long or last line not terminated", lineno);
+ if (length && line[length - 1] == '\r')
+ line[--length] = 0;
+ parse_line (line, lineno);
+ }
+ flush_data ();
+ if (ferror (fp))
+ err ("error reading input at line %u: %s", lineno, strerror (errno));
+}
+
+
+int
+main (int argc, char **argv)
+{
+ int last_argc = -1;
+
+ if (argc)
+ {
+ argc--; argv++;
+ }
+ while (argc && last_argc != argc )
+ {
+ last_argc = argc;
+ if (!strcmp (*argv, "--"))
+ {
+ argc--; argv++;
+ break;
+ }
+ else if (!strcmp (*argv, "--version"))
+ {
+ fputs (PGM " (GnuPG) " PACKAGE_VERSION "\n", stdout);
+ exit (0);
+ }
+ else if (!strcmp (*argv, "--help"))
+ {
+ puts ("Usage: " PGM " [BUS:DEV]\n"
+ "Parse the output of usbmod assuming it is CCID compliant.\n\n"
+ " --skip-escape do not show escape packets\n"
+ " --verbose enable extra informational output\n"
+ " --debug enable additional debug output\n"
+ " --help display this help and exit\n\n"
+ "Report bugs to " PACKAGE_BUGREPORT ".");
+ exit (0);
+ }
+ else if (!strcmp (*argv, "--verbose"))
+ {
+ verbose = 1;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "--debug"))
+ {
+ verbose = debug = 1;
+ argc--; argv++;
+ }
+ else if (!strcmp (*argv, "--skip-escape"))
+ {
+ skip_escape = 1;
+ argc--; argv++;
+ }
+ }
+
+ if (argc > 1)
+ die ("usage: " PGM " [BUS:DEV] (try --help for more information)\n");
+
+ if (argc == 1)
+ {
+ const char *s = strchr (argv[0], ':');
+
+ usb_bus = atoi (argv[0]);
+ if (s)
+ usb_dev = atoi (s+1);
+ if (usb_bus < 1 || usb_bus > 999 || usb_dev < 1 || usb_dev > 999)
+ die ("invalid bus:dev specified");
+ }
+
+
+ signal (SIGPIPE, SIG_IGN);
+
+ parse_input (stdin);
+
+ return any_error? 1:0;
+}
+
+
+/*
+Local Variables:
+compile-command: "gcc -Wall -Wno-pointer-sign -g -o ccidmon ccidmon.c"
+End:
+*/