Add a tool to analyze the CCID protocol on the USB bus.
authorWerner Koch <wk@gnupg.org>
Fri, 17 Apr 2009 18:40:32 +0000 (18:40 +0000)
committerWerner Koch <wk@gnupg.org>
Fri, 17 Apr 2009 18:40:32 +0000 (18:40 +0000)
tools/ChangeLog
tools/Makefile.am
tools/ccidmon.c [new file with mode: 0644]

index 6c0920e..82328ee 100644 (file)
@@ -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.
index 2a63fa5..1d04964 100644 (file)
@@ -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 (file)
index 0000000..03bfc43
--- /dev/null
@@ -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:
+*/