1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-11-11 21:48:50 +01:00
gnupg/tools/ccidmon.c
Werner Koch b008274afd Nuked almost all trailing white space.
We better do this once and for all instead of cluttering all future
commits with diffs of trailing white spaces.  In the majority of cases
blank or single lines are affected and thus this change won't disturb
a git blame too much.  For future commits the pre-commit scripts
checks that this won't happen again.
2011-02-04 12:57:53 +01:00

880 lines
21 KiB
C

/* 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;
static int sniffusb;
/* 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)
{
char buf[100];
snprintf (buf, sizeof buf, "Unknown PC_to_RDR command 0x%02X",
msglen? msg[0]:0);
print_p2r_header (buf, 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)
{
char buf[100];
snprintf (buf, sizeof buf, "Unknown RDR_to_PC command 0x%02X",
msglen? msg[0]:0);
print_r2p_header (buf, 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_line_sniffusb (char *line, unsigned int lineno)
{
char *p;
if (debug)
printf ("line[%u] =`%s'\n", lineno, line);
p = strtok (line, " \t");
if (!p)
return;
p = strtok (NULL, " \t");
if (!p)
return;
p = strtok (NULL, " \t");
if (!p)
return;
if (hexdigitp (p[0]) && hexdigitp (p[1])
&& hexdigitp (p[2]) && hexdigitp (p[3])
&& p[4] == ':' && !p[5])
{
size_t length;
unsigned int value;
length = databuffer.count;
while ((p=strtok (NULL, " \t")))
{
if (!hexdigitp (p[0]) || !hexdigitp (p[1]))
{
err ("invalid hex digit in line %u (%s)", lineno,p);
break;
}
value = xtoi_1 (p[0]) * 16 + xtoi_1 (p[1]);
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;
}
else if (!strcmp (p, "TransferFlags"))
{
flush_data ();
*databuffer.address = 0;
while ((p=strtok (NULL, " \t(,)")))
{
if (!strcmp (p, "USBD_TRANSFER_DIRECTION_IN"))
{
databuffer.is_bi = 1;
break;
}
else if (!strcmp (p, "USBD_TRANSFER_DIRECTION_OUT"))
{
databuffer.is_bi = 0;
break;
}
}
}
}
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;
if (sniffusb)
parse_line_sniffusb (line, lineno);
else
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"
" --sniffusb Assume output from Sniffusb.exe\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++;
}
else if (!strcmp (*argv, "--sniffusb"))
{
sniffusb = 1;
argc--; argv++;
}
}
if (argc && sniffusb)
die ("no arguments expected when using --sniffusb\n");
else 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:
*/