xref: /illumos-gate/usr/src/cmd/ccidadm/ccidadm.c (revision 20a7641f9918de8574b8b3b47dbe35c4bfc78df1)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2019, Joyent, Inc.
14  */
15 
16 /*
17  * Print out information about a CCID device.
18  */
19 
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 #include <err.h>
24 #include <stdlib.h>
25 #include <strings.h>
26 #include <unistd.h>
27 #include <ofmt.h>
28 #include <libgen.h>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <limits.h>
32 #include <libcmdutils.h>
33 #include <fts.h>
34 
35 #include <sys/usb/clients/ccid/uccid.h>
36 #include <atr.h>
37 
38 #define	EXIT_USAGE	2
39 
40 static const char *ccidadm_pname;
41 
42 #define	CCID_ROOT	"/dev/ccid/"
43 
44 typedef enum {
45 	CCIDADM_LIST_DEVICE,
46 	CCIDADM_LIST_PRODUCT,
47 	CCIDADM_LIST_STATE,
48 	CCIDADM_LIST_TRANSPORT,
49 	CCIDADM_LIST_SUPPORTED,
50 } ccidadm_list_index_t;
51 
52 typedef struct ccidadm_pair {
53 	uint32_t	ccp_val;
54 	const char	*ccp_name;
55 } ccidadm_pair_t;
56 
57 typedef struct ccid_list_ofmt_arg {
58 	const char		*cloa_name;
59 	uccid_cmd_status_t	*cloa_status;
60 } ccid_list_ofmt_arg_t;
61 
62 /*
63  * Attempt to open a CCID slot specified by a user. In general, we expect that
64  * users will use a path like "ccid0/slot0". However, they may also specify a
65  * full path. If the card boolean is set to true, that means that they may have
66  * just specified "ccid0", so we need to try to open up the default slot.
67  */
68 static int
69 ccidadm_open(const char *base, boolean_t card)
70 {
71 	int fd;
72 	char buf[PATH_MAX];
73 
74 	/*
75 	 * If it's an absolute path, just try to open it.
76 	 */
77 	if (base[0] == '/') {
78 		return (open(base, O_RDWR));
79 	}
80 
81 	/*
82 	 * For a card, try to append slot0 first.
83 	 */
84 	if (card) {
85 		if (snprintf(buf, sizeof (buf), "%s/%s/slot0", CCID_ROOT,
86 		    base) >= sizeof (buf)) {
87 			errno = ENAMETOOLONG;
88 			return (-1);
89 		}
90 
91 		if ((fd = open(buf, O_RDWR)) >= 0) {
92 			return (fd);
93 		}
94 
95 		if (errno != ENOENT && errno != ENOTDIR) {
96 			return (fd);
97 		}
98 	}
99 
100 	if (snprintf(buf, sizeof (buf), "%s/%s", CCID_ROOT, base) >=
101 	    sizeof (buf)) {
102 		errno = ENAMETOOLONG;
103 		return (-1);
104 	}
105 
106 	return (open(buf, O_RDWR));
107 }
108 
109 static void
110 ccidadm_iter(boolean_t readeronly, boolean_t newline,
111     void(*cb)(int, const char *, void *), void *arg)
112 {
113 	FTS *fts;
114 	FTSENT *ent;
115 	char *const paths[] = { CCID_ROOT, NULL };
116 	int fd;
117 	boolean_t first = B_TRUE;
118 
119 	fts = fts_open(paths, FTS_LOGICAL | FTS_NOCHDIR, NULL);
120 	if (fts == NULL) {
121 		err(EXIT_FAILURE, "failed to create directory stream");
122 	}
123 
124 	while ((ent = fts_read(fts)) != NULL) {
125 		const char *name;
126 
127 		/* Skip the root and post-order dirs */
128 		if (ent->fts_level == 0 || ent->fts_info == FTS_DP) {
129 			continue;
130 		}
131 		if (readeronly && ent->fts_level != 1) {
132 			continue;
133 		} else if (!readeronly && ent->fts_level != 2) {
134 			continue;
135 		}
136 
137 		if (ent->fts_info == FTS_ERR || ent->fts_info == FTS_NS) {
138 			warn("skipping %s, failed to get information: %s",
139 			    ent->fts_name, strerror(ent->fts_errno));
140 			continue;
141 		}
142 
143 		name = ent->fts_path + strlen(CCID_ROOT);
144 		if ((fd = ccidadm_open(name, readeronly)) < 0) {
145 			err(EXIT_FAILURE, "failed to open %s", name);
146 		}
147 
148 		if (!first && newline) {
149 			(void) printf("\n");
150 		}
151 		first = B_FALSE;
152 		cb(fd, name, arg);
153 		(void) close(fd);
154 	}
155 
156 	(void) fts_close(fts);
157 }
158 
159 static void
160 ccidadm_list_slot_status_str(uccid_cmd_status_t *ucs, char *buf, uint_t buflen)
161 {
162 	if (!(ucs->ucs_status & UCCID_STATUS_F_CARD_PRESENT)) {
163 		(void) snprintf(buf, buflen, "missing");
164 		return;
165 	}
166 
167 	if (ucs->ucs_status & UCCID_STATUS_F_CARD_ACTIVE) {
168 		(void) snprintf(buf, buflen, "activated");
169 		return;
170 	}
171 
172 	(void) snprintf(buf, buflen, "unactivated");
173 }
174 
175 static boolean_t
176 ccidadm_list_slot_transport_str(uccid_cmd_status_t *ucs, char *buf,
177     uint_t buflen)
178 {
179 	const char *prot;
180 	const char *tran;
181 	uint_t bits = CCID_CLASS_F_TPDU_XCHG | CCID_CLASS_F_SHORT_APDU_XCHG |
182 	    CCID_CLASS_F_EXT_APDU_XCHG;
183 
184 	switch (ucs->ucs_class.ccd_dwFeatures & bits) {
185 	case 0:
186 		tran = "character";
187 		break;
188 	case CCID_CLASS_F_TPDU_XCHG:
189 		tran = "TPDU";
190 		break;
191 	case CCID_CLASS_F_SHORT_APDU_XCHG:
192 	case CCID_CLASS_F_EXT_APDU_XCHG:
193 		tran = "APDU";
194 		break;
195 	default:
196 		tran = "unknown";
197 		break;
198 	}
199 
200 	if ((ucs->ucs_status & UCCID_STATUS_F_PARAMS_VALID) != 0) {
201 		switch (ucs->ucs_prot) {
202 		case UCCID_PROT_T0:
203 			prot = " (T=0)";
204 			break;
205 		case UCCID_PROT_T1:
206 			prot = " (T=1)";
207 			break;
208 		default:
209 			prot = "";
210 			break;
211 		}
212 	} else {
213 		prot = "";
214 	}
215 
216 	return (snprintf(buf, buflen, "%s%s", tran, prot) < buflen);
217 }
218 
219 static boolean_t
220 ccidadm_list_slot_usable_str(uccid_cmd_status_t *ucs, char *buf,
221     uint_t buflen)
222 {
223 	const char *un = "";
224 	ccid_class_features_t feat;
225 	uint_t prot = CCID_CLASS_F_SHORT_APDU_XCHG | CCID_CLASS_F_EXT_APDU_XCHG;
226 	uint_t param = CCID_CLASS_F_AUTO_PARAM_NEG | CCID_CLASS_F_AUTO_PPS;
227 	uint_t clock = CCID_CLASS_F_AUTO_BAUD | CCID_CLASS_F_AUTO_ICC_CLOCK;
228 
229 	feat = ucs->ucs_class.ccd_dwFeatures;
230 
231 	if ((feat & prot) == 0 ||
232 	    (feat & param) != param ||
233 	    (feat & clock) != clock) {
234 		un = "un";
235 	}
236 
237 	return (snprintf(buf, buflen, "%ssupported", un) < buflen);
238 }
239 
240 static boolean_t
241 ccidadm_list_ofmt_cb(ofmt_arg_t *ofmt, char *buf, uint_t buflen)
242 {
243 	ccid_list_ofmt_arg_t *cloa = ofmt->ofmt_cbarg;
244 
245 	switch (ofmt->ofmt_id) {
246 	case CCIDADM_LIST_DEVICE:
247 		if (snprintf(buf, buflen, "%s", cloa->cloa_name) >= buflen) {
248 			return (B_FALSE);
249 		}
250 		break;
251 	case CCIDADM_LIST_PRODUCT:
252 		if (snprintf(buf, buflen, "%s",
253 		    cloa->cloa_status->ucs_product) >= buflen) {
254 			return (B_FALSE);
255 		}
256 		break;
257 	case CCIDADM_LIST_STATE:
258 		ccidadm_list_slot_status_str(cloa->cloa_status, buf, buflen);
259 		break;
260 	case CCIDADM_LIST_TRANSPORT:
261 		return (ccidadm_list_slot_transport_str(cloa->cloa_status, buf,
262 		    buflen));
263 		break;
264 	case CCIDADM_LIST_SUPPORTED:
265 		return (ccidadm_list_slot_usable_str(cloa->cloa_status, buf,
266 		    buflen));
267 		break;
268 	default:
269 		return (B_FALSE);
270 	}
271 
272 	return (B_TRUE);
273 }
274 
275 static void
276 ccidadm_list_slot(int slotfd, const char *name, void *arg)
277 {
278 	uccid_cmd_status_t ucs;
279 	ofmt_handle_t ofmt = arg;
280 	ccid_list_ofmt_arg_t cloa;
281 
282 	bzero(&ucs, sizeof (ucs));
283 	ucs.ucs_version = UCCID_CURRENT_VERSION;
284 
285 	if (ioctl(slotfd, UCCID_CMD_STATUS, &ucs) != 0) {
286 		err(EXIT_FAILURE, "failed to issue status ioctl to %s", name);
287 	}
288 
289 	if ((ucs.ucs_status & UCCID_STATUS_F_PRODUCT_VALID) == 0) {
290 		(void) strlcpy(ucs.ucs_product, "<unknown>",
291 		    sizeof (ucs.ucs_product));
292 	}
293 
294 	cloa.cloa_name = name;
295 	cloa.cloa_status = &ucs;
296 	ofmt_print(ofmt, &cloa);
297 }
298 
299 static ofmt_field_t ccidadm_list_fields[] = {
300 	{ "PRODUCT",	24,	CCIDADM_LIST_PRODUCT,	ccidadm_list_ofmt_cb },
301 	{ "DEVICE",	16,	CCIDADM_LIST_DEVICE,	ccidadm_list_ofmt_cb },
302 	{ "CARD STATE",	12,	CCIDADM_LIST_STATE,	ccidadm_list_ofmt_cb },
303 	{ "TRANSPORT",	12,	CCIDADM_LIST_TRANSPORT,	ccidadm_list_ofmt_cb },
304 	{ "SUPPORTED",	12,	CCIDADM_LIST_SUPPORTED,	ccidadm_list_ofmt_cb },
305 	{ NULL,		0,	0,			NULL	}
306 };
307 
308 static void
309 ccidadm_do_list(int argc, char *argv[])
310 {
311 	ofmt_handle_t ofmt;
312 
313 	if (argc != 0) {
314 		errx(EXIT_USAGE, "list command does not take arguments\n");
315 	}
316 
317 	if (ofmt_open(NULL, ccidadm_list_fields, 0, 0, &ofmt) != OFMT_SUCCESS) {
318 		errx(EXIT_FAILURE, "failed to initialize ofmt state");
319 	}
320 
321 	ccidadm_iter(B_FALSE, B_FALSE, ccidadm_list_slot, ofmt);
322 	ofmt_close(ofmt);
323 }
324 
325 static void
326 ccidadm_list_usage(FILE *out)
327 {
328 	(void) fprintf(out, "\tlist\n");
329 }
330 
331 /*
332  * Print out logical information about the ICC's ATR. This includes information
333  * about what protocols it supports, required negotiation, etc.
334  */
335 static void
336 ccidadm_atr_props(uccid_cmd_status_t *ucs)
337 {
338 	int ret;
339 	atr_data_t *data;
340 	atr_protocol_t prots, defprot;
341 	boolean_t negotiate;
342 	atr_data_rate_choice_t rate;
343 	uint32_t bps;
344 
345 	if ((data = atr_data_alloc()) == NULL) {
346 		err(EXIT_FAILURE, "failed to allocate memory for "
347 		    "ATR data");
348 	}
349 
350 	ret = atr_parse(ucs->ucs_atr, ucs->ucs_atrlen, data);
351 	if (ret != ATR_CODE_OK) {
352 		errx(EXIT_FAILURE, "failed to parse ATR data: %s",
353 		    atr_strerror(ret));
354 	}
355 
356 	prots = atr_supported_protocols(data);
357 	(void) printf("ICC supports protocol(s): ");
358 	if (prots == ATR_P_NONE) {
359 		(void) printf("none\n");
360 		atr_data_free(data);
361 		return;
362 	}
363 
364 	(void) printf("%s\n", atr_protocol_to_string(prots));
365 
366 	negotiate = atr_params_negotiable(data);
367 	defprot = atr_default_protocol(data);
368 
369 	if (negotiate) {
370 		(void) printf("Card protocol is negotiable; starts with "
371 		    "default %s parameters\n", atr_protocol_to_string(defprot));
372 	} else {
373 		(void) printf("Card protocol is not negotiable; starts with "
374 		    "specific %s parameters\n",
375 		    atr_protocol_to_string(defprot));
376 	}
377 
378 	/*
379 	 * For each supported protocol, figure out parameters we would
380 	 * negotiate. We only need to warn about auto-negotiation if this
381 	 * is TPDU or character and specific bits are missing.
382 	 */
383 	if (((ucs->ucs_class.ccd_dwFeatures & (CCID_CLASS_F_SHORT_APDU_XCHG |
384 	    CCID_CLASS_F_EXT_APDU_XCHG)) == 0) &&
385 	    ((ucs->ucs_class.ccd_dwFeatures & (CCID_CLASS_F_AUTO_PARAM_NEG |
386 	    CCID_CLASS_F_AUTO_PPS)) == 0)) {
387 		(void) printf("CCID/ICC require explicit TPDU parameter/PPS "
388 		    "negotiation\n");
389 	}
390 
391 	/*
392 	 * Determine which set of Di/Fi values we should use and how we should
393 	 * get there (note a reader may not have to set them).
394 	 */
395 	rate = atr_data_rate(data, &ucs->ucs_class, NULL, 0, &bps);
396 	switch (rate) {
397 	case ATR_RATE_USEDEFAULT:
398 		(void) printf("Reader will run ICC at the default (Di=1/Fi=1) "
399 		    "speed\n");
400 		break;
401 	case ATR_RATE_USEATR:
402 		(void) printf("Reader will run ICC at ICC's Di/Fi values\n");
403 		break;
404 	case ATR_RATE_USEATR_SETRATE:
405 		(void) printf("Reader will run ICC at ICC's Di/Fi values, but "
406 		    "must set data rate to %u bps\n", bps);
407 		break;
408 	case ATR_RATE_UNSUPPORTED:
409 		(void) printf("Reader cannot run ICC due to Di/Fi mismatch\n");
410 		break;
411 	default:
412 		(void) printf("Cannot determine Di/Fi rate, unexpected "
413 		    "value: %u\n", rate);
414 		break;
415 	}
416 	if (prots & ATR_P_T0) {
417 		uint8_t fi, di;
418 		atr_convention_t conv;
419 		atr_clock_stop_t clock;
420 
421 		fi = atr_fi_index(data);
422 		di = atr_di_index(data);
423 		conv = atr_convention(data);
424 		clock = atr_clock_stop(data);
425 		(void) printf("T=0 properties that would be negotiated:\n");
426 		(void) printf("  + Fi/Fmax Index: %u (Fi %s/Fmax %s MHz)\n",
427 		    fi, atr_fi_index_to_string(fi),
428 		    atr_fmax_index_to_string(fi));
429 		(void) printf("  + Di Index: %u (Di %s)\n", di,
430 		    atr_di_index_to_string(di));
431 		(void) printf("  + Clock Convention: %u (%s)\n", conv,
432 		    atr_convention_to_string(conv));
433 		(void) printf("  + Extra Guardtime: %u\n",
434 		    atr_extra_guardtime(data));
435 		(void) printf("  + WI: %u\n", atr_t0_wi(data));
436 		(void) printf("  + Clock Stop: %u (%s)\n", clock,
437 		    atr_clock_stop_to_string(clock));
438 	}
439 
440 	if (prots & ATR_P_T1) {
441 		uint8_t fi, di;
442 		atr_clock_stop_t clock;
443 		atr_t1_checksum_t cksum;
444 
445 		fi = atr_fi_index(data);
446 		di = atr_di_index(data);
447 		clock = atr_clock_stop(data);
448 		cksum = atr_t1_checksum(data);
449 		(void) printf("T=1 properties that would be negotiated:\n");
450 		(void) printf("  + Fi/Fmax Index: %u (Fi %s/Fmax %s MHz)\n",
451 		    fi, atr_fi_index_to_string(fi),
452 		    atr_fmax_index_to_string(fi));
453 		(void) printf("  + Di Index: %u (Di %s)\n", di,
454 		    atr_di_index_to_string(di));
455 		(void) printf("  + Checksum: %s\n",
456 		    cksum == ATR_T1_CHECKSUM_CRC ? "CRC" : "LRC");
457 		(void) printf("  + Extra Guardtime: %u\n",
458 		    atr_extra_guardtime(data));
459 		(void) printf("  + BWI: %u\n", atr_t1_bwi(data));
460 		(void) printf("  + CWI: %u\n", atr_t1_cwi(data));
461 		(void) printf("  + Clock Stop: %u (%s)\n", clock,
462 		    atr_clock_stop_to_string(clock));
463 		(void) printf("  + IFSC: %u\n", atr_t1_ifsc(data));
464 		(void) printf("  + CCID Supports NAD: %s\n",
465 		    ucs->ucs_class.ccd_dwFeatures & CCID_CLASS_F_ALTNAD_SUP ?
466 		    "yes" : "no");
467 	}
468 
469 	atr_data_free(data);
470 }
471 
472 static void
473 ccidadm_atr_verbose(uccid_cmd_status_t *ucs)
474 {
475 	int ret;
476 	atr_data_t *data;
477 
478 	if ((data = atr_data_alloc()) == NULL) {
479 		err(EXIT_FAILURE, "failed to allocate memory for "
480 		    "ATR data");
481 	}
482 
483 	ret = atr_parse(ucs->ucs_atr, ucs->ucs_atrlen, data);
484 	if (ret != ATR_CODE_OK) {
485 		errx(EXIT_FAILURE, "failed to parse ATR data: %s",
486 		    atr_strerror(ret));
487 	}
488 	atr_data_dump(data, stdout);
489 	atr_data_free(data);
490 }
491 
492 typedef struct cciadm_atr_args {
493 	boolean_t caa_hex;
494 	boolean_t caa_props;
495 	boolean_t caa_verbose;
496 } ccidadm_atr_args_t;
497 
498 static void
499 ccidadm_atr_fetch(int fd, const char *name, void *arg)
500 {
501 	uccid_cmd_status_t ucs;
502 	ccidadm_atr_args_t *caa = arg;
503 
504 	bzero(&ucs, sizeof (ucs));
505 	ucs.ucs_version = UCCID_CURRENT_VERSION;
506 
507 	if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) {
508 		err(EXIT_FAILURE, "failed to issue status ioctl to %s",
509 		    name);
510 	}
511 
512 	if (ucs.ucs_atrlen == 0) {
513 		warnx("slot %s has no card inserted or activated", name);
514 		return;
515 	}
516 
517 	(void) printf("ATR for %s (%u bytes):\n", name, ucs.ucs_atrlen);
518 	if (caa->caa_props) {
519 		ccidadm_atr_props(&ucs);
520 	}
521 
522 	if (caa->caa_hex) {
523 		atr_data_hexdump(ucs.ucs_atr, ucs.ucs_atrlen, stdout);
524 	}
525 
526 	if (caa->caa_verbose) {
527 		ccidadm_atr_verbose(&ucs);
528 	}
529 }
530 
531 static void
532 ccidadm_do_atr(int argc, char *argv[])
533 {
534 	uint_t i;
535 	int c;
536 	ccidadm_atr_args_t caa;
537 
538 	bzero(&caa, sizeof (caa));
539 	optind = 0;
540 	while ((c = getopt(argc, argv, "vx")) != -1) {
541 		switch (c) {
542 		case 'v':
543 			caa.caa_verbose = B_TRUE;
544 			break;
545 		case 'x':
546 			caa.caa_hex = B_TRUE;
547 			break;
548 		case ':':
549 			errx(EXIT_USAGE, "Option -%c requires an argument\n",
550 			    optopt);
551 			break;
552 		case '?':
553 			errx(EXIT_USAGE, "Unknown option: -%c\n", optopt);
554 			break;
555 		}
556 	}
557 
558 	if (!caa.caa_verbose && !caa.caa_props && !caa.caa_hex) {
559 		caa.caa_props = B_TRUE;
560 	}
561 
562 	argc -= optind;
563 	argv += optind;
564 
565 	if (argc == 0) {
566 		ccidadm_iter(B_FALSE, B_TRUE, ccidadm_atr_fetch, &caa);
567 		return;
568 	}
569 
570 	for (i = 0; i < argc; i++) {
571 		int fd;
572 
573 		if ((fd = ccidadm_open(argv[i], B_FALSE)) < 0) {
574 			warn("failed to open %s", argv[i]);
575 			errx(EXIT_FAILURE, "valid CCID slot?");
576 		}
577 
578 		ccidadm_atr_fetch(fd, argv[i], &caa);
579 		(void) close(fd);
580 		if (i + 1 < argc) {
581 			(void) printf("\n");
582 		}
583 	}
584 }
585 
586 static void
587 ccidadm_atr_usage(FILE *out)
588 {
589 	(void) fprintf(out, "\tatr [-vx]\t[device] ...\n");
590 }
591 
592 static void
593 ccidadm_print_pairs(uint32_t val, ccidadm_pair_t *ccp)
594 {
595 	while (ccp->ccp_name != NULL) {
596 		if ((val & ccp->ccp_val) == ccp->ccp_val) {
597 			(void) printf("    + %s\n", ccp->ccp_name);
598 		}
599 		ccp++;
600 	}
601 }
602 
603 static ccidadm_pair_t ccidadm_p_protocols[] = {
604 	{ 0x01, "T=0" },
605 	{ 0x02, "T=1" },
606 	{ 0x0, NULL }
607 };
608 
609 static ccidadm_pair_t ccidadm_p_voltages[] = {
610 	{ CCID_CLASS_VOLT_5_0, "5.0 V" },
611 	{ CCID_CLASS_VOLT_3_0, "3.0 V" },
612 	{ CCID_CLASS_VOLT_1_8, "1.8 V" },
613 	{ 0x0, NULL }
614 };
615 
616 static ccidadm_pair_t ccidadm_p_syncprots[] = {
617 	{ 0x01, "2-Wire Support" },
618 	{ 0x02, "3-Wire Support" },
619 	{ 0x04, "I2C Support" },
620 	{ 0x0, NULL }
621 };
622 
623 static ccidadm_pair_t ccidadm_p_mechanical[] = {
624 	{ CCID_CLASS_MECH_CARD_ACCEPT, "Card Accept Mechanism" },
625 	{ CCID_CLASS_MECH_CARD_EJECT, "Card Eject Mechanism" },
626 	{ CCID_CLASS_MECH_CARD_CAPTURE, "Card Capture Mechanism" },
627 	{ CCID_CLASS_MECH_CARD_LOCK, "Card Lock/Unlock Mechanism" },
628 	{ 0x0, NULL }
629 };
630 
631 static ccidadm_pair_t ccidadm_p_features[] = {
632 	{ CCID_CLASS_F_AUTO_PARAM_ATR,
633 	    "Automatic parameter configuration based on ATR data" },
634 	{ CCID_CLASS_F_AUTO_ICC_ACTIVATE,
635 	    "Automatic activation on ICC insertion" },
636 	{ CCID_CLASS_F_AUTO_ICC_VOLTAGE, "Automatic ICC voltage selection" },
637 	{ CCID_CLASS_F_AUTO_ICC_CLOCK,
638 	    "Automatic ICC clock frequency change" },
639 	{ CCID_CLASS_F_AUTO_BAUD, "Automatic baud rate change" },
640 	{ CCID_CLASS_F_AUTO_PARAM_NEG,
641 	    "Automatic parameter negotiation by CCID" },
642 	{ CCID_CLASS_F_AUTO_PPS, "Automatic PPS made by CCID" },
643 	{ CCID_CLASS_F_ICC_CLOCK_STOP, "CCID can set ICC in clock stop mode" },
644 	{ CCID_CLASS_F_ALTNAD_SUP, "NAD value other than zero accepted" },
645 	{ CCID_CLASS_F_AUTO_IFSD, "Automatic IFSD exchange" },
646 	{ CCID_CLASS_F_TPDU_XCHG, "TPDU support" },
647 	{ CCID_CLASS_F_SHORT_APDU_XCHG, "Short APDU support" },
648 	{ CCID_CLASS_F_EXT_APDU_XCHG, "Short and Extended APDU support" },
649 	{ CCID_CLASS_F_WAKE_UP, "USB Wake Up signaling support" },
650 	{ 0x0, NULL }
651 };
652 
653 static ccidadm_pair_t ccidadm_p_pin[] = {
654 	{ CCID_CLASS_PIN_VERIFICATION, "PIN verification" },
655 	{ CCID_CLASS_PIN_MODIFICATION, "PIN modification" },
656 	{ 0x0, NULL }
657 };
658 
659 static void
660 ccidadm_reader_print(int fd, const char *name, void *unused __unused)
661 {
662 	uccid_cmd_status_t ucs;
663 	ccid_class_descr_t *cd;
664 	char nnbuf[NN_NUMBUF_SZ + 1];
665 
666 	bzero(&ucs, sizeof (uccid_cmd_status_t));
667 	ucs.ucs_version = UCCID_CURRENT_VERSION;
668 
669 	if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) {
670 		err(EXIT_FAILURE, "failed to issue status ioctl to %s",
671 		    name);
672 	}
673 
674 	cd = &ucs.ucs_class;
675 	(void) printf("Reader %s, CCID class v%u.%u device:\n", name,
676 	    CCID_VERSION_MAJOR(cd->ccd_bcdCCID),
677 	    CCID_VERSION_MINOR(cd->ccd_bcdCCID));
678 
679 	if ((ucs.ucs_status & UCCID_STATUS_F_PRODUCT_VALID) == 0) {
680 		(void) strlcpy(ucs.ucs_product, "<unknown>",
681 		    sizeof (ucs.ucs_product));
682 	}
683 
684 	if ((ucs.ucs_status & UCCID_STATUS_F_SERIAL_VALID) == 0) {
685 		(void) strlcpy(ucs.ucs_serial, "<unknown>",
686 		    sizeof (ucs.ucs_serial));
687 	}
688 
689 	(void) printf("  Product: %s\n", ucs.ucs_product);
690 	(void) printf("  Serial: %s\n", ucs.ucs_serial);
691 	(void) printf("  Slots Present: %u\n", cd->ccd_bMaxSlotIndex + 1);
692 	(void) printf("  Maximum Busy Slots: %u\n", cd->ccd_bMaxCCIDBusySlots);
693 	(void) printf("  Supported Voltages:\n");
694 	ccidadm_print_pairs(cd->ccd_bVoltageSupport, ccidadm_p_voltages);
695 	(void) printf("  Supported Protocols:\n");
696 	ccidadm_print_pairs(cd->ccd_dwProtocols, ccidadm_p_protocols);
697 	nicenum_scale(cd->ccd_dwDefaultClock, 1000, nnbuf,
698 	    sizeof (nnbuf), NN_DIVISOR_1000 | NN_UNIT_SPACE);
699 	(void) printf("  Default Clock: %sHz\n", nnbuf);
700 	nicenum_scale(cd->ccd_dwMaximumClock, 1000, nnbuf,
701 	    sizeof (nnbuf), NN_DIVISOR_1000 | NN_UNIT_SPACE);
702 	(void) printf("  Maximum Clock: %sHz\n", nnbuf);
703 	(void) printf("  Supported Clock Rates: %u\n",
704 	    cd->ccd_bNumClockSupported);
705 	nicenum_scale(cd->ccd_dwDataRate, 1, nnbuf, sizeof (nnbuf),
706 	    NN_DIVISOR_1000 | NN_UNIT_SPACE);
707 	(void) printf("  Default Data Rate: %sbps\n", nnbuf);
708 	nicenum_scale(cd->ccd_dwMaxDataRate, 1, nnbuf, sizeof (nnbuf),
709 	    NN_DIVISOR_1000 | NN_UNIT_SPACE);
710 	(void) printf("  Maximum Data Rate: %sbps\n", nnbuf);
711 	(void) printf("  Supported Data Rates: %u\n",
712 	    cd->ccd_bNumDataRatesSupported);
713 	(void) printf("  Maximum IFSD (T=1 only): %u\n", cd->ccd_dwMaxIFSD);
714 	if (cd->ccd_dwSyncProtocols != 0) {
715 		(void) printf("  Synchronous Protocols Supported:\n");
716 		ccidadm_print_pairs(cd->ccd_dwSyncProtocols,
717 		    ccidadm_p_syncprots);
718 	}
719 	if (cd->ccd_dwMechanical != 0) {
720 		(void) printf("  Mechanical Features:\n");
721 		ccidadm_print_pairs(cd->ccd_dwMechanical, ccidadm_p_mechanical);
722 	}
723 	if (cd->ccd_dwFeatures != 0) {
724 		(void) printf("  Device Features:\n");
725 		ccidadm_print_pairs(cd->ccd_dwFeatures, ccidadm_p_features);
726 	}
727 	(void) printf("  Maximum Message Length: %u bytes\n",
728 	    cd->ccd_dwMaxCCIDMessageLength);
729 	if (cd->ccd_dwFeatures & CCID_CLASS_F_EXT_APDU_XCHG) {
730 		if (cd->ccd_bClassGetResponse == 0xff) {
731 			(void) printf("  Default Get Response Class: echo\n");
732 		} else {
733 			(void) printf("  Default Get Response Class: %u\n",
734 			    cd->ccd_bClassGetResponse);
735 		}
736 		if (cd->ccd_bClassEnvelope == 0xff) {
737 			(void) printf("  Default Envelope Class: echo\n");
738 		} else {
739 			(void) printf("  Default Envelope Class: %u\n",
740 			    cd->ccd_bClassEnvelope);
741 		}
742 	}
743 	if (cd->ccd_wLcdLayout != 0) {
744 		(void) printf("  %2ux%2u LCD present\n",
745 		    cd->ccd_wLcdLayout >> 8, cd->ccd_wLcdLayout & 0xff);
746 	}
747 
748 	if (cd->ccd_bPinSupport) {
749 		(void) printf("  Pin Support:\n");
750 		ccidadm_print_pairs(cd->ccd_bPinSupport, ccidadm_p_pin);
751 	}
752 }
753 
754 static void
755 ccidadm_do_reader(int argc, char *argv[])
756 {
757 	int i;
758 
759 	if (argc == 0) {
760 		ccidadm_iter(B_TRUE, B_TRUE, ccidadm_reader_print, NULL);
761 		return;
762 	}
763 
764 	for (i = 0; i < argc; i++) {
765 		int fd;
766 
767 		if ((fd = ccidadm_open(argv[i], B_TRUE)) < 0) {
768 			warn("failed to open %s", argv[i]);
769 			errx(EXIT_FAILURE, "valid ccid reader");
770 		}
771 
772 		ccidadm_reader_print(fd, argv[i], NULL);
773 		(void) close(fd);
774 		if (i + 1 < argc) {
775 			(void) printf("\n");
776 		}
777 	}
778 }
779 
780 static void
781 ccidadm_reader_usage(FILE *out)
782 {
783 	(void) fprintf(out, "\treader\t\t[reader] ...\n");
784 }
785 
786 typedef struct ccidadm_cmdtab {
787 	const char *cc_name;
788 	void (*cc_op)(int, char *[]);
789 	void (*cc_usage)(FILE *);
790 } ccidadm_cmdtab_t;
791 
792 static ccidadm_cmdtab_t ccidadm_cmds[] = {
793 	{ "list", ccidadm_do_list, ccidadm_list_usage },
794 	{ "atr", ccidadm_do_atr, ccidadm_atr_usage },
795 	{ "reader", ccidadm_do_reader, ccidadm_reader_usage },
796 	{ NULL }
797 };
798 
799 static int
800 ccidadm_usage(const char *format, ...)
801 {
802 	ccidadm_cmdtab_t *tab;
803 
804 	if (format != NULL) {
805 		va_list ap;
806 
807 		va_start(ap, format);
808 		(void) fprintf(stderr, "%s: ", ccidadm_pname);
809 		(void) vfprintf(stderr, format, ap);
810 		(void) fprintf(stderr, "\n");
811 		va_end(ap);
812 	}
813 
814 	(void) fprintf(stderr, "usage:  %s <subcommand> <args> ...\n\n",
815 	    ccidadm_pname);
816 	(void) fprintf(stderr, "Subcommands:\n");
817 	for (tab = ccidadm_cmds; tab->cc_name != NULL; tab++) {
818 		tab->cc_usage(stderr);
819 	}
820 
821 	return (EXIT_USAGE);
822 }
823 
824 int
825 main(int argc, char *argv[])
826 {
827 	ccidadm_cmdtab_t *tab;
828 
829 	ccidadm_pname = basename(argv[0]);
830 	if (argc < 2) {
831 		return (ccidadm_usage("missing required subcommand"));
832 	}
833 
834 	for (tab = ccidadm_cmds; tab->cc_name != NULL; tab++) {
835 		if (strcmp(argv[1], tab->cc_name) == 0) {
836 			argc -= 2;
837 			argv += 2;
838 			tab->cc_op(argc, argv);
839 			return (EXIT_SUCCESS);
840 		}
841 	}
842 
843 	return (ccidadm_usage("unknown command: %s", argv[1]));
844 }
845