diff options
Diffstat (limited to 'usr/src/cmd/ccidadm/ccidadm.c')
| -rw-r--r-- | usr/src/cmd/ccidadm/ccidadm.c | 844 |
1 files changed, 844 insertions, 0 deletions
diff --git a/usr/src/cmd/ccidadm/ccidadm.c b/usr/src/cmd/ccidadm/ccidadm.c new file mode 100644 index 0000000000..1d3c2bcbfc --- /dev/null +++ b/usr/src/cmd/ccidadm/ccidadm.c @@ -0,0 +1,844 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2019, Joyent, Inc. + */ + +/* + * Print out information about a CCID device. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <err.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> +#include <ofmt.h> +#include <libgen.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <libcmdutils.h> +#include <fts.h> + +#include <sys/usb/clients/ccid/uccid.h> +#include <atr.h> + +#define EXIT_USAGE 2 + +static const char *ccidadm_pname; + +#define CCID_ROOT "/dev/ccid/" + +typedef enum { + CCIDADM_LIST_DEVICE, + CCIDADM_LIST_PRODUCT, + CCIDADM_LIST_STATE, + CCIDADM_LIST_TRANSPORT, + CCIDADM_LIST_SUPPORTED, +} ccidadm_list_index_t; + +typedef struct ccidadm_pair { + uint32_t ccp_val; + const char *ccp_name; +} ccidadm_pair_t; + +typedef struct ccid_list_ofmt_arg { + const char *cloa_name; + uccid_cmd_status_t *cloa_status; +} ccid_list_ofmt_arg_t; + +/* + * Attempt to open a CCID slot specified by a user. In general, we expect that + * users will use a path like "ccid0/slot0". However, they may also specify a + * full path. If the card boolean is set to true, that means that they may have + * just specified "ccid0", so we need to try to open up the default slot. + */ +static int +ccidadm_open(const char *base, boolean_t card) +{ + int fd; + char buf[PATH_MAX]; + + /* + * If it's an absolute path, just try to open it. + */ + if (base[0] == '/') { + return (open(base, O_RDWR)); + } + + /* + * For a card, try to append slot0 first. + */ + if (card) { + if (snprintf(buf, sizeof (buf), "%s/%s/slot0", CCID_ROOT, + base) >= sizeof (buf)) { + errno = ENAMETOOLONG; + return (-1); + } + + if ((fd = open(buf, O_RDWR)) >= 0) { + return (fd); + } + + if (errno != ENOENT && errno != ENOTDIR) { + return (fd); + } + } + + if (snprintf(buf, sizeof (buf), "%s/%s", CCID_ROOT, base) >= + sizeof (buf)) { + errno = ENAMETOOLONG; + return (-1); + } + + return (open(buf, O_RDWR)); +} + +static void +ccidadm_iter(boolean_t readeronly, boolean_t newline, + void(*cb)(int, const char *, void *), void *arg) +{ + FTS *fts; + FTSENT *ent; + char *const paths[] = { CCID_ROOT, NULL }; + int fd; + boolean_t first = B_TRUE; + + fts = fts_open(paths, FTS_LOGICAL | FTS_NOCHDIR, NULL); + if (fts == NULL) { + err(EXIT_FAILURE, "failed to create directory stream"); + } + + while ((ent = fts_read(fts)) != NULL) { + const char *name; + + /* Skip the root and post-order dirs */ + if (ent->fts_level == 0 || ent->fts_info == FTS_DP) { + continue; + } + if (readeronly && ent->fts_level != 1) { + continue; + } else if (!readeronly && ent->fts_level != 2) { + continue; + } + + if (ent->fts_info == FTS_ERR || ent->fts_info == FTS_NS) { + warn("skipping %s, failed to get information: %s", + ent->fts_name, strerror(ent->fts_errno)); + continue; + } + + name = ent->fts_path + strlen(CCID_ROOT); + if ((fd = ccidadm_open(name, readeronly)) < 0) { + err(EXIT_FAILURE, "failed to open %s", name); + } + + if (!first && newline) { + (void) printf("\n"); + } + first = B_FALSE; + cb(fd, name, arg); + (void) close(fd); + } + + (void) fts_close(fts); +} + +static void +ccidadm_list_slot_status_str(uccid_cmd_status_t *ucs, char *buf, uint_t buflen) +{ + if (!(ucs->ucs_status & UCCID_STATUS_F_CARD_PRESENT)) { + (void) snprintf(buf, buflen, "missing"); + return; + } + + if (ucs->ucs_status & UCCID_STATUS_F_CARD_ACTIVE) { + (void) snprintf(buf, buflen, "activated"); + return; + } + + (void) snprintf(buf, buflen, "unactivated"); +} + +static boolean_t +ccidadm_list_slot_transport_str(uccid_cmd_status_t *ucs, char *buf, + uint_t buflen) +{ + const char *prot; + const char *tran; + uint_t bits = CCID_CLASS_F_TPDU_XCHG | CCID_CLASS_F_SHORT_APDU_XCHG | + CCID_CLASS_F_EXT_APDU_XCHG; + + switch (ucs->ucs_class.ccd_dwFeatures & bits) { + case 0: + tran = "character"; + break; + case CCID_CLASS_F_TPDU_XCHG: + tran = "TPDU"; + break; + case CCID_CLASS_F_SHORT_APDU_XCHG: + case CCID_CLASS_F_EXT_APDU_XCHG: + tran = "APDU"; + break; + default: + tran = "unknown"; + break; + } + + if ((ucs->ucs_status & UCCID_STATUS_F_PARAMS_VALID) != 0) { + switch (ucs->ucs_prot) { + case UCCID_PROT_T0: + prot = " (T=0)"; + break; + case UCCID_PROT_T1: + prot = " (T=1)"; + break; + default: + prot = ""; + break; + } + } else { + prot = ""; + } + + return (snprintf(buf, buflen, "%s%s", tran, prot) < buflen); +} + +static boolean_t +ccidadm_list_slot_usable_str(uccid_cmd_status_t *ucs, char *buf, + uint_t buflen) +{ + char *un = ""; + ccid_class_features_t feat; + uint_t prot = CCID_CLASS_F_SHORT_APDU_XCHG | CCID_CLASS_F_EXT_APDU_XCHG; + uint_t param = CCID_CLASS_F_AUTO_PARAM_NEG | CCID_CLASS_F_AUTO_PPS; + uint_t clock = CCID_CLASS_F_AUTO_BAUD | CCID_CLASS_F_AUTO_ICC_CLOCK; + + feat = ucs->ucs_class.ccd_dwFeatures; + + if ((feat & prot) == 0 || + (feat & param) != param || + (feat & clock) != clock) { + un = "un"; + } + + return (snprintf(buf, buflen, "%ssupported", un) < buflen); +} + +static boolean_t +ccidadm_list_ofmt_cb(ofmt_arg_t *ofmt, char *buf, uint_t buflen) +{ + ccid_list_ofmt_arg_t *cloa = ofmt->ofmt_cbarg; + + switch (ofmt->ofmt_id) { + case CCIDADM_LIST_DEVICE: + if (snprintf(buf, buflen, "%s", cloa->cloa_name) >= buflen) { + return (B_FALSE); + } + break; + case CCIDADM_LIST_PRODUCT: + if (snprintf(buf, buflen, "%s", + cloa->cloa_status->ucs_product) >= buflen) { + return (B_FALSE); + } + break; + case CCIDADM_LIST_STATE: + ccidadm_list_slot_status_str(cloa->cloa_status, buf, buflen); + break; + case CCIDADM_LIST_TRANSPORT: + return (ccidadm_list_slot_transport_str(cloa->cloa_status, buf, + buflen)); + break; + case CCIDADM_LIST_SUPPORTED: + return (ccidadm_list_slot_usable_str(cloa->cloa_status, buf, + buflen)); + break; + default: + return (B_FALSE); + } + + return (B_TRUE); +} + +static void +ccidadm_list_slot(int slotfd, const char *name, void *arg) +{ + uccid_cmd_status_t ucs; + ofmt_handle_t ofmt = arg; + ccid_list_ofmt_arg_t cloa; + + bzero(&ucs, sizeof (ucs)); + ucs.ucs_version = UCCID_CURRENT_VERSION; + + if (ioctl(slotfd, UCCID_CMD_STATUS, &ucs) != 0) { + err(EXIT_FAILURE, "failed to issue status ioctl to %s", name); + } + + if ((ucs.ucs_status & UCCID_STATUS_F_PRODUCT_VALID) == 0) { + (void) strlcpy(ucs.ucs_product, "<unknown>", + sizeof (ucs.ucs_product)); + } + + cloa.cloa_name = name; + cloa.cloa_status = &ucs; + ofmt_print(ofmt, &cloa); +} + +static ofmt_field_t ccidadm_list_fields[] = { + { "PRODUCT", 24, CCIDADM_LIST_PRODUCT, ccidadm_list_ofmt_cb }, + { "DEVICE", 16, CCIDADM_LIST_DEVICE, ccidadm_list_ofmt_cb }, + { "CARD STATE", 12, CCIDADM_LIST_STATE, ccidadm_list_ofmt_cb }, + { "TRANSPORT", 12, CCIDADM_LIST_TRANSPORT, ccidadm_list_ofmt_cb }, + { "SUPPORTED", 12, CCIDADM_LIST_SUPPORTED, ccidadm_list_ofmt_cb }, + { NULL, 0, 0, NULL } +}; + +static void +ccidadm_do_list(int argc, char *argv[]) +{ + ofmt_handle_t ofmt; + + if (argc != 0) { + errx(EXIT_USAGE, "list command does not take arguments\n"); + } + + if (ofmt_open(NULL, ccidadm_list_fields, 0, 0, &ofmt) != OFMT_SUCCESS) { + errx(EXIT_FAILURE, "failed to initialize ofmt state"); + } + + ccidadm_iter(B_FALSE, B_FALSE, ccidadm_list_slot, ofmt); + ofmt_close(ofmt); +} + +static void +ccidadm_list_usage(FILE *out) +{ + (void) fprintf(out, "\tlist\n"); +} + +/* + * Print out logical information about the ICC's ATR. This includes information + * about what protocols it supports, required negotiation, etc. + */ +static void +ccidadm_atr_props(uccid_cmd_status_t *ucs) +{ + int ret; + atr_data_t *data; + atr_protocol_t prots, defprot; + boolean_t negotiate; + atr_data_rate_choice_t rate; + uint32_t bps; + + if ((data = atr_data_alloc()) == NULL) { + err(EXIT_FAILURE, "failed to allocate memory for " + "ATR data"); + } + + ret = atr_parse(ucs->ucs_atr, ucs->ucs_atrlen, data); + if (ret != ATR_CODE_OK) { + errx(EXIT_FAILURE, "failed to parse ATR data: %s", + atr_strerror(ret)); + } + + prots = atr_supported_protocols(data); + (void) printf("ICC supports protocol(s): "); + if (prots == ATR_P_NONE) { + (void) printf("none\n"); + atr_data_free(data); + return; + } + + (void) printf("%s\n", atr_protocol_to_string(prots)); + + negotiate = atr_params_negotiable(data); + defprot = atr_default_protocol(data); + + if (negotiate) { + (void) printf("Card protocol is negotiable; starts with " + "default %s parameters\n", atr_protocol_to_string(defprot)); + } else { + (void) printf("Card protocol is not negotiable; starts with " + "specific %s parameters\n", + atr_protocol_to_string(defprot)); + } + + /* + * For each supported protocol, figure out parameters we would + * negotiate. We only need to warn about auto-negotiation if this + * is TPDU or character and specific bits are missing. + */ + if (((ucs->ucs_class.ccd_dwFeatures & (CCID_CLASS_F_SHORT_APDU_XCHG | + CCID_CLASS_F_EXT_APDU_XCHG)) == 0) && + ((ucs->ucs_class.ccd_dwFeatures & (CCID_CLASS_F_AUTO_PARAM_NEG | + CCID_CLASS_F_AUTO_PPS)) == 0)) { + (void) printf("CCID/ICC require explicit TPDU parameter/PPS " + "negotiation\n"); + } + + /* + * Determine which set of Di/Fi values we should use and how we should + * get there (note a reader may not have to set them). + */ + rate = atr_data_rate(data, &ucs->ucs_class, NULL, 0, &bps); + switch (rate) { + case ATR_RATE_USEDEFAULT: + (void) printf("Reader will run ICC at the default (Di=1/Fi=1) " + "speed\n"); + break; + case ATR_RATE_USEATR: + (void) printf("Reader will run ICC at ICC's Di/Fi values\n"); + break; + case ATR_RATE_USEATR_SETRATE: + (void) printf("Reader will run ICC at ICC's Di/Fi values, but " + "must set data rate to %u bps\n", bps); + break; + case ATR_RATE_UNSUPPORTED: + (void) printf("Reader cannot run ICC due to Di/Fi mismatch\n"); + break; + default: + (void) printf("Cannot determine Di/Fi rate, unexpected " + "value: %u\n", rate); + break; + } + if (prots & ATR_P_T0) { + uint8_t fi, di; + atr_convention_t conv; + atr_clock_stop_t clock; + + fi = atr_fi_index(data); + di = atr_di_index(data); + conv = atr_convention(data); + clock = atr_clock_stop(data); + (void) printf("T=0 properties that would be negotiated:\n"); + (void) printf(" + Fi/Fmax Index: %u (Fi %s/Fmax %s MHz)\n", + fi, atr_fi_index_to_string(fi), + atr_fmax_index_to_string(fi)); + (void) printf(" + Di Index: %u (Di %s)\n", di, + atr_di_index_to_string(di)); + (void) printf(" + Clock Convention: %u (%s)\n", conv, + atr_convention_to_string(conv)); + (void) printf(" + Extra Guardtime: %u\n", + atr_extra_guardtime(data)); + (void) printf(" + WI: %u\n", atr_t0_wi(data)); + (void) printf(" + Clock Stop: %u (%s)\n", clock, + atr_clock_stop_to_string(clock)); + } + + if (prots & ATR_P_T1) { + uint8_t fi, di; + atr_clock_stop_t clock; + atr_t1_checksum_t cksum; + + fi = atr_fi_index(data); + di = atr_di_index(data); + clock = atr_clock_stop(data); + cksum = atr_t1_checksum(data); + (void) printf("T=1 properties that would be negotiated:\n"); + (void) printf(" + Fi/Fmax Index: %u (Fi %s/Fmax %s MHz)\n", + fi, atr_fi_index_to_string(fi), + atr_fmax_index_to_string(fi)); + (void) printf(" + Di Index: %u (Di %s)\n", di, + atr_di_index_to_string(di)); + (void) printf(" + Checksum: %s\n", + cksum == ATR_T1_CHECKSUM_CRC ? "CRC" : "LRC"); + (void) printf(" + Extra Guardtime: %u\n", + atr_extra_guardtime(data)); + (void) printf(" + BWI: %u\n", atr_t1_bwi(data)); + (void) printf(" + CWI: %u\n", atr_t1_cwi(data)); + (void) printf(" + Clock Stop: %u (%s)\n", clock, + atr_clock_stop_to_string(clock)); + (void) printf(" + IFSC: %u\n", atr_t1_ifsc(data)); + (void) printf(" + CCID Supports NAD: %s\n", + ucs->ucs_class.ccd_dwFeatures & CCID_CLASS_F_ALTNAD_SUP ? + "yes" : "no"); + } + + atr_data_free(data); +} + +static void +ccidadm_atr_verbose(uccid_cmd_status_t *ucs) +{ + int ret; + atr_data_t *data; + + if ((data = atr_data_alloc()) == NULL) { + err(EXIT_FAILURE, "failed to allocate memory for " + "ATR data"); + } + + ret = atr_parse(ucs->ucs_atr, ucs->ucs_atrlen, data); + if (ret != ATR_CODE_OK) { + errx(EXIT_FAILURE, "failed to parse ATR data: %s", + atr_strerror(ret)); + } + atr_data_dump(data, stdout); + atr_data_free(data); +} + +typedef struct cciadm_atr_args { + boolean_t caa_hex; + boolean_t caa_props; + boolean_t caa_verbose; +} ccidadm_atr_args_t; + +static void +ccidadm_atr_fetch(int fd, const char *name, void *arg) +{ + uccid_cmd_status_t ucs; + ccidadm_atr_args_t *caa = arg; + + bzero(&ucs, sizeof (ucs)); + ucs.ucs_version = UCCID_CURRENT_VERSION; + + if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) { + err(EXIT_FAILURE, "failed to issue status ioctl to %s", + name); + } + + if (ucs.ucs_atrlen == 0) { + warnx("slot %s has no card inserted or activated", name); + return; + } + + (void) printf("ATR for %s (%u bytes):\n", name, ucs.ucs_atrlen); + if (caa->caa_props) { + ccidadm_atr_props(&ucs); + } + + if (caa->caa_hex) { + atr_data_hexdump(ucs.ucs_atr, ucs.ucs_atrlen, stdout); + } + + if (caa->caa_verbose) { + ccidadm_atr_verbose(&ucs); + } +} + +static void +ccidadm_do_atr(int argc, char *argv[]) +{ + uint_t i; + int c; + ccidadm_atr_args_t caa; + + bzero(&caa, sizeof (caa)); + optind = 0; + while ((c = getopt(argc, argv, "vx")) != -1) { + switch (c) { + case 'v': + caa.caa_verbose = B_TRUE; + break; + case 'x': + caa.caa_hex = B_TRUE; + break; + case ':': + errx(EXIT_USAGE, "Option -%c requires an argument\n", + optopt); + break; + case '?': + errx(EXIT_USAGE, "Unknown option: -%c\n", optopt); + break; + } + } + + if (!caa.caa_verbose && !caa.caa_props && !caa.caa_hex) { + caa.caa_props = B_TRUE; + } + + argc -= optind; + argv += optind; + + if (argc == 0) { + ccidadm_iter(B_FALSE, B_TRUE, ccidadm_atr_fetch, &caa); + return; + } + + for (i = 0; i < argc; i++) { + int fd; + + if ((fd = ccidadm_open(argv[i], B_FALSE)) < 0) { + warn("failed to open %s", argv[i]); + errx(EXIT_FAILURE, "valid CCID slot?"); + } + + ccidadm_atr_fetch(fd, argv[i], &caa); + (void) close(fd); + if (i + 1 < argc) { + (void) printf("\n"); + } + } +} + +static void +ccidadm_atr_usage(FILE *out) +{ + (void) fprintf(out, "\tatr [-vx]\t[device] ...\n"); +} + +static void +ccidadm_print_pairs(uint32_t val, ccidadm_pair_t *ccp) +{ + while (ccp->ccp_name != NULL) { + if ((val & ccp->ccp_val) == ccp->ccp_val) { + (void) printf(" + %s\n", ccp->ccp_name); + } + ccp++; + } +} + +static ccidadm_pair_t ccidadm_p_protocols[] = { + { 0x01, "T=0" }, + { 0x02, "T=1" }, + { 0x0, NULL } +}; + +static ccidadm_pair_t ccidadm_p_voltages[] = { + { CCID_CLASS_VOLT_5_0, "5.0 V" }, + { CCID_CLASS_VOLT_3_0, "3.0 V" }, + { CCID_CLASS_VOLT_1_8, "1.8 V" }, + { 0x0, NULL } +}; + +static ccidadm_pair_t ccidadm_p_syncprots[] = { + { 0x01, "2-Wire Support" }, + { 0x02, "3-Wire Support" }, + { 0x04, "I2C Support" }, + { 0x0, NULL } +}; + +static ccidadm_pair_t ccidadm_p_mechanical[] = { + { CCID_CLASS_MECH_CARD_ACCEPT, "Card Accept Mechanism" }, + { CCID_CLASS_MECH_CARD_EJECT, "Card Eject Mechanism" }, + { CCID_CLASS_MECH_CARD_CAPTURE, "Card Capture Mechanism" }, + { CCID_CLASS_MECH_CARD_LOCK, "Card Lock/Unlock Mechanism" }, + { 0x0, NULL } +}; + +static ccidadm_pair_t ccidadm_p_features[] = { + { CCID_CLASS_F_AUTO_PARAM_ATR, + "Automatic parameter configuration based on ATR data" }, + { CCID_CLASS_F_AUTO_ICC_ACTIVATE, + "Automatic activation on ICC insertion" }, + { CCID_CLASS_F_AUTO_ICC_VOLTAGE, "Automatic ICC voltage selection" }, + { CCID_CLASS_F_AUTO_ICC_CLOCK, + "Automatic ICC clock frequency change" }, + { CCID_CLASS_F_AUTO_BAUD, "Automatic baud rate change" }, + { CCID_CLASS_F_AUTO_PARAM_NEG, + "Automatic parameter negotiation by CCID" }, + { CCID_CLASS_F_AUTO_PPS, "Automatic PPS made by CCID" }, + { CCID_CLASS_F_ICC_CLOCK_STOP, "CCID can set ICC in clock stop mode" }, + { CCID_CLASS_F_ALTNAD_SUP, "NAD value other than zero accepted" }, + { CCID_CLASS_F_AUTO_IFSD, "Automatic IFSD exchange" }, + { CCID_CLASS_F_TPDU_XCHG, "TPDU support" }, + { CCID_CLASS_F_SHORT_APDU_XCHG, "Short APDU support" }, + { CCID_CLASS_F_EXT_APDU_XCHG, "Short and Extended APDU support" }, + { CCID_CLASS_F_WAKE_UP, "USB Wake Up signaling support" }, + { 0x0, NULL } +}; + +static ccidadm_pair_t ccidadm_p_pin[] = { + { CCID_CLASS_PIN_VERIFICATION, "PIN verification" }, + { CCID_CLASS_PIN_MODIFICATION, "PIN modification" }, + { 0x0, NULL } +}; + +static void +ccidadm_reader_print(int fd, const char *name, void *unused __unused) +{ + uccid_cmd_status_t ucs; + ccid_class_descr_t *cd; + char nnbuf[NN_NUMBUF_SZ + 1]; + + bzero(&ucs, sizeof (uccid_cmd_status_t)); + ucs.ucs_version = UCCID_CURRENT_VERSION; + + if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) { + err(EXIT_FAILURE, "failed to issue status ioctl to %s", + name); + } + + cd = &ucs.ucs_class; + (void) printf("Reader %s, CCID class v%u.%u device:\n", name, + CCID_VERSION_MAJOR(cd->ccd_bcdCCID), + CCID_VERSION_MINOR(cd->ccd_bcdCCID)); + + if ((ucs.ucs_status & UCCID_STATUS_F_PRODUCT_VALID) == 0) { + (void) strlcpy(ucs.ucs_product, "<unknown>", + sizeof (ucs.ucs_product)); + } + + if ((ucs.ucs_status & UCCID_STATUS_F_SERIAL_VALID) == 0) { + (void) strlcpy(ucs.ucs_serial, "<unknown>", + sizeof (ucs.ucs_serial)); + } + + (void) printf(" Product: %s\n", ucs.ucs_product); + (void) printf(" Serial: %s\n", ucs.ucs_serial); + (void) printf(" Slots Present: %u\n", cd->ccd_bMaxSlotIndex + 1); + (void) printf(" Maximum Busy Slots: %u\n", cd->ccd_bMaxCCIDBusySlots); + (void) printf(" Supported Voltages:\n"); + ccidadm_print_pairs(cd->ccd_bVoltageSupport, ccidadm_p_voltages); + (void) printf(" Supported Protocols:\n"); + ccidadm_print_pairs(cd->ccd_dwProtocols, ccidadm_p_protocols); + nicenum_scale(cd->ccd_dwDefaultClock, 1000, nnbuf, + sizeof (nnbuf), NN_DIVISOR_1000 | NN_UNIT_SPACE); + (void) printf(" Default Clock: %sHz\n", nnbuf); + nicenum_scale(cd->ccd_dwMaximumClock, 1000, nnbuf, + sizeof (nnbuf), NN_DIVISOR_1000 | NN_UNIT_SPACE); + (void) printf(" Maximum Clock: %sHz\n", nnbuf); + (void) printf(" Supported Clock Rates: %u\n", + cd->ccd_bNumClockSupported); + nicenum_scale(cd->ccd_dwDataRate, 1, nnbuf, sizeof (nnbuf), + NN_DIVISOR_1000 | NN_UNIT_SPACE); + (void) printf(" Default Data Rate: %sbps\n", nnbuf); + nicenum_scale(cd->ccd_dwMaxDataRate, 1, nnbuf, sizeof (nnbuf), + NN_DIVISOR_1000 | NN_UNIT_SPACE); + (void) printf(" Maximum Data Rate: %sbps\n", nnbuf); + (void) printf(" Supported Data Rates: %u\n", + cd->ccd_bNumDataRatesSupported); + (void) printf(" Maximum IFSD (T=1 only): %u\n", cd->ccd_dwMaxIFSD); + if (cd->ccd_dwSyncProtocols != 0) { + (void) printf(" Synchronous Protocols Supported:\n"); + ccidadm_print_pairs(cd->ccd_dwSyncProtocols, + ccidadm_p_syncprots); + } + if (cd->ccd_dwMechanical != 0) { + (void) printf(" Mechanical Features:\n"); + ccidadm_print_pairs(cd->ccd_dwMechanical, ccidadm_p_mechanical); + } + if (cd->ccd_dwFeatures != 0) { + (void) printf(" Device Features:\n"); + ccidadm_print_pairs(cd->ccd_dwFeatures, ccidadm_p_features); + } + (void) printf(" Maximum Message Length: %u bytes\n", + cd->ccd_dwMaxCCIDMessageLength); + if (cd->ccd_dwFeatures & CCID_CLASS_F_EXT_APDU_XCHG) { + if (cd->ccd_bClassGetResponse == 0xff) { + (void) printf(" Default Get Response Class: echo\n"); + } else { + (void) printf(" Default Get Response Class: %u\n", + cd->ccd_bClassGetResponse); + } + if (cd->ccd_bClassEnvelope == 0xff) { + (void) printf(" Default Envelope Class: echo\n"); + } else { + (void) printf(" Default Envelope Class: %u\n", + cd->ccd_bClassEnvelope); + } + } + if (cd->ccd_wLcdLayout != 0) { + (void) printf(" %2ux%2u LCD present\n", + cd->ccd_wLcdLayout >> 8, cd->ccd_wLcdLayout & 0xff); + } + + if (cd->ccd_bPinSupport) { + (void) printf(" Pin Support:\n"); + ccidadm_print_pairs(cd->ccd_bPinSupport, ccidadm_p_pin); + } +} + +static void +ccidadm_do_reader(int argc, char *argv[]) +{ + int i; + + if (argc == 0) { + ccidadm_iter(B_TRUE, B_TRUE, ccidadm_reader_print, NULL); + return; + } + + for (i = 0; i < argc; i++) { + int fd; + + if ((fd = ccidadm_open(argv[i], B_TRUE)) < 0) { + warn("failed to open %s", argv[i]); + errx(EXIT_FAILURE, "valid ccid reader"); + } + + ccidadm_reader_print(fd, argv[i], NULL); + (void) close(fd); + if (i + 1 < argc) { + (void) printf("\n"); + } + } +} + +static void +ccidadm_reader_usage(FILE *out) +{ + (void) fprintf(out, "\treader\t\t[reader] ...\n"); +} + +typedef struct ccidadm_cmdtab { + const char *cc_name; + void (*cc_op)(int, char *[]); + void (*cc_usage)(FILE *); +} ccidadm_cmdtab_t; + +static ccidadm_cmdtab_t ccidadm_cmds[] = { + { "list", ccidadm_do_list, ccidadm_list_usage }, + { "atr", ccidadm_do_atr, ccidadm_atr_usage }, + { "reader", ccidadm_do_reader, ccidadm_reader_usage }, + { NULL } +}; + +static int +ccidadm_usage(const char *format, ...) +{ + ccidadm_cmdtab_t *tab; + + if (format != NULL) { + va_list ap; + + va_start(ap, format); + (void) fprintf(stderr, "%s: ", ccidadm_pname); + (void) vfprintf(stderr, format, ap); + (void) fprintf(stderr, "\n"); + va_end(ap); + } + + (void) fprintf(stderr, "usage: %s <subcommand> <args> ...\n\n", + ccidadm_pname); + (void) fprintf(stderr, "Subcommands:\n"); + for (tab = ccidadm_cmds; tab->cc_name != NULL; tab++) { + tab->cc_usage(stderr); + } + + return (EXIT_USAGE); +} + +int +main(int argc, char *argv[]) +{ + ccidadm_cmdtab_t *tab; + + ccidadm_pname = basename(argv[0]); + if (argc < 2) { + return (ccidadm_usage("missing required subcommand")); + } + + for (tab = ccidadm_cmds; tab->cc_name != NULL; tab++) { + if (strcmp(argv[1], tab->cc_name) == 0) { + argc -= 2; + argv += 2; + tab->cc_op(argc, argv); + return (EXIT_SUCCESS); + } + } + + return (ccidadm_usage("unknown command: %s", argv[1])); +} |
