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 * CCID cfgadm plugin 18 */ 19 20 #include <sys/types.h> 21 #include <sys/stat.h> 22 #include <fcntl.h> 23 #include <unistd.h> 24 #include <stdio.h> 25 #include <stdarg.h> 26 #include <errno.h> 27 #include <string.h> 28 #include <strings.h> 29 #include <stdlib.h> 30 31 #include <sys/usb/clients/ccid/uccid.h> 32 33 #define CFGA_PLUGIN_LIB 34 #include <config_admin.h> 35 36 int cfga_version = CFGA_HSL_V2; 37 38 static cfga_err_t 39 cfga_ccid_error(cfga_err_t err, char **errp, const char *fmt, ...) 40 { 41 va_list ap; 42 43 if (errp == NULL) 44 return (err); 45 46 /* 47 * Try to format a string. However because we have to return allocated 48 * memory, if this fails, then we have no error. 49 */ 50 va_start(ap, fmt); 51 (void) vasprintf(errp, fmt, ap); 52 va_end(ap); 53 54 return (err); 55 } 56 57 cfga_err_t 58 cfga_ccid_modify(uccid_cmd_icc_modify_t *modify, const char *ap, 59 struct cfga_confirm *confp, struct cfga_msg *msgp, char **errp, 60 boolean_t force) 61 { 62 int fd; 63 uccid_cmd_status_t ucs; 64 uccid_cmd_txn_begin_t begin; 65 boolean_t held = B_FALSE; 66 67 /* 68 * Check ap is valid by doing a status request. 69 */ 70 if ((fd = open(ap, O_RDWR)) < 0) { 71 return (cfga_ccid_error(CFGA_LIB_ERROR, errp, 72 "failed to open %s: %s", ap, strerror(errno))); 73 } 74 75 bzero(&ucs, sizeof (ucs)); 76 ucs.ucs_version = UCCID_CURRENT_VERSION; 77 78 if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) { 79 int e = errno; 80 if (errno == ENODEV) { 81 (void) close(fd); 82 return (cfga_ccid_error(CFGA_LIB_ERROR, errp, 83 "ap %s going away", ap)); 84 } 85 (void) close(fd); 86 return (cfga_ccid_error(CFGA_ERROR, errp, 87 "ioctl on ap %s failed: %s", ap, strerror(e))); 88 } 89 90 /* 91 * Attempt to get a hold. If we cannot obtain a hold, we will not 92 * perform this unless the user has said we should force this. 93 */ 94 bzero(&begin, sizeof (begin)); 95 begin.uct_version = UCCID_CURRENT_VERSION; 96 begin.uct_flags = UCCID_TXN_DONT_BLOCK; 97 if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) { 98 if (errno != EBUSY) { 99 int e = errno; 100 (void) close(fd); 101 return (cfga_ccid_error(CFGA_ERROR, errp, "failed to " 102 "begin ccid transaction on ap %s: %s", ap, 103 strerror(e))); 104 } 105 106 /* 107 * If the user didn't force this operation, prompt if we would 108 * interfere. 109 */ 110 if (!force) { 111 int confirm = 0; 112 const char *prompt = "CCID slot is held exclusively " 113 "by another program. Proceeding may interrupt " 114 "their functionality. Continue?"; 115 if (confp != NULL && confp->appdata_ptr != NULL) { 116 confirm = (*confp->confirm)(confp->appdata_ptr, 117 prompt); 118 } 119 120 if (confirm == 0) { 121 (void) close(fd); 122 return (CFGA_NACK); 123 } 124 } 125 } else { 126 held = B_TRUE; 127 } 128 129 if (ioctl(fd, UCCID_CMD_ICC_MODIFY, modify) != 0) { 130 int e = errno; 131 (void) close(fd); 132 return (cfga_ccid_error(CFGA_ERROR, errp, 133 "failed to modify state on ap %s: %s", ap, 134 strerror(e))); 135 } 136 137 if (held) { 138 uccid_cmd_txn_end_t end; 139 140 bzero(&end, sizeof (end)); 141 end.uct_version = UCCID_CURRENT_VERSION; 142 end.uct_flags = UCCID_TXN_END_RELEASE; 143 144 if (ioctl(fd, UCCID_CMD_TXN_END, &end) != 0) { 145 int e = errno; 146 (void) close(fd); 147 return (cfga_ccid_error(CFGA_ERROR, errp, "failed to " 148 "end transaction on ap %s: %s", ap, 149 strerror(e))); 150 } 151 } 152 153 (void) close(fd); 154 return (CFGA_OK); 155 156 } 157 158 cfga_err_t 159 cfga_change_state(cfga_cmd_t cmd, const char *ap, const char *opts, 160 struct cfga_confirm *confp, struct cfga_msg *msgp, char **errp, 161 cfga_flags_t flags) 162 { 163 uccid_cmd_icc_modify_t modify; 164 165 if (errp != NULL) { 166 *errp = NULL; 167 } 168 169 if (ap == NULL) { 170 return (cfga_ccid_error(CFGA_APID_NOEXIST, errp, NULL)); 171 } 172 173 if (opts != NULL) { 174 return (cfga_ccid_error(CFGA_ERROR, errp, 175 "hardware specific options are not supported")); 176 } 177 178 bzero(&modify, sizeof (modify)); 179 modify.uci_version = UCCID_CURRENT_VERSION; 180 switch (cmd) { 181 case CFGA_CMD_CONFIGURE: 182 modify.uci_action = UCCID_ICC_POWER_ON; 183 break; 184 case CFGA_CMD_UNCONFIGURE: 185 modify.uci_action = UCCID_ICC_POWER_OFF; 186 break; 187 default: 188 (void) cfga_help(msgp, opts, flags); 189 return (CFGA_OPNOTSUPP); 190 } 191 192 return (cfga_ccid_modify(&modify, ap, confp, msgp, errp, 193 (flags & CFGA_FLAG_FORCE) != 0)); 194 } 195 196 cfga_err_t 197 cfga_private_func(const char *function, const char *ap, const char *opts, 198 struct cfga_confirm *confp, struct cfga_msg *msgp, char **errp, 199 cfga_flags_t flags) 200 { 201 uccid_cmd_icc_modify_t modify; 202 203 if (errp != NULL) { 204 *errp = NULL; 205 } 206 207 if (function == NULL) { 208 return (CFGA_ERROR); 209 } 210 211 if (ap == NULL) { 212 return (cfga_ccid_error(CFGA_APID_NOEXIST, errp, NULL)); 213 } 214 215 if (opts != NULL) { 216 return (cfga_ccid_error(CFGA_ERROR, errp, 217 "hardware specific options are not supported")); 218 } 219 220 if (strcmp(function, "warm_reset") != 0) { 221 return (CFGA_OPNOTSUPP); 222 } 223 224 bzero(&modify, sizeof (modify)); 225 modify.uci_version = UCCID_CURRENT_VERSION; 226 modify.uci_action = UCCID_ICC_WARM_RESET; 227 228 return (cfga_ccid_modify(&modify, ap, confp, msgp, errp, 229 (flags & CFGA_FLAG_FORCE) != 0)); 230 } 231 232 /* 233 * We don't support the test entry point for CCID. 234 */ 235 cfga_err_t 236 cfga_test(const char *ap, const char *opts, struct cfga_msg *msgp, char **errp, 237 cfga_flags_t flags) 238 { 239 (void) cfga_help(msgp, opts, flags); 240 return (CFGA_OPNOTSUPP); 241 } 242 243 static void 244 cfga_ccid_fill_info(const uccid_cmd_status_t *ucs, char *buf, size_t len) 245 { 246 const char *product, *serial, *tran, *prot; 247 uint_t bits = CCID_CLASS_F_TPDU_XCHG | CCID_CLASS_F_SHORT_APDU_XCHG | 248 CCID_CLASS_F_EXT_APDU_XCHG; 249 250 if ((ucs->ucs_status & UCCID_STATUS_F_PRODUCT_VALID) != 0) { 251 product = ucs->ucs_product; 252 } else { 253 product = "<unknown>"; 254 } 255 256 if ((ucs->ucs_status & UCCID_STATUS_F_SERIAL_VALID) != 0) { 257 serial = ucs->ucs_serial; 258 } else { 259 serial = "<unknown>"; 260 } 261 262 switch (ucs->ucs_class.ccd_dwFeatures & bits) { 263 case 0: 264 tran = "Character"; 265 break; 266 case CCID_CLASS_F_TPDU_XCHG: 267 tran = "TPDU"; 268 break; 269 case CCID_CLASS_F_SHORT_APDU_XCHG: 270 case CCID_CLASS_F_EXT_APDU_XCHG: 271 tran = "APDU"; 272 break; 273 default: 274 tran = "Unknown"; 275 break; 276 } 277 278 if ((ucs->ucs_status & UCCID_STATUS_F_PARAMS_VALID) != 0) { 279 switch (ucs->ucs_prot) { 280 case UCCID_PROT_T0: 281 prot = " (T=0)"; 282 break; 283 case UCCID_PROT_T1: 284 prot = " (T=1)"; 285 break; 286 default: 287 prot = "<unknown>"; 288 break; 289 } 290 } else { 291 prot = "<unknown>"; 292 } 293 294 if ((ucs->ucs_status & UCCID_STATUS_F_CARD_ACTIVE) != 0) { 295 (void) snprintf(buf, len, "Product: %s Serial: %s " 296 "Transport: %s Protocol: %s", product, serial, 297 tran, prot); 298 } else { 299 (void) snprintf(buf, len, "Product: %s Serial: %s ", 300 product, serial); 301 } 302 } 303 304 cfga_err_t 305 cfga_list_ext(const char *ap, struct cfga_list_data **ap_list, int *nlist, 306 const char *opts, const char *listopts, char **errp, cfga_flags_t flags) 307 { 308 int fd; 309 uccid_cmd_status_t ucs; 310 struct cfga_list_data *cld; 311 312 if (errp != NULL) { 313 *errp = NULL; 314 } 315 316 if (ap == NULL) { 317 return (cfga_ccid_error(CFGA_APID_NOEXIST, errp, NULL)); 318 } 319 320 if (opts != NULL) { 321 return (cfga_ccid_error(CFGA_ERROR, errp, 322 "hardware specific options are not supported")); 323 } 324 325 if ((fd = open(ap, O_RDWR)) < 0) { 326 return (cfga_ccid_error(CFGA_LIB_ERROR, errp, 327 "failed to open %s: %s", ap, strerror(errno))); 328 } 329 330 bzero(&ucs, sizeof (ucs)); 331 ucs.ucs_version = UCCID_CURRENT_VERSION; 332 333 if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) { 334 int e = errno; 335 (void) close(fd); 336 if (e == ENODEV) { 337 return (cfga_ccid_error(CFGA_LIB_ERROR, errp, 338 "ap %s going away", ap)); 339 } 340 return (cfga_ccid_error(CFGA_ERROR, errp, 341 "ioctl on ap %s failed: %s", ap, strerror(e))); 342 } 343 (void) close(fd); 344 345 if ((cld = calloc(1, sizeof (*cld))) == NULL) { 346 return (cfga_ccid_error(CFGA_LIB_ERROR, errp, "failed to " 347 "allocate memory for list entry")); 348 } 349 350 if (snprintf(cld->ap_log_id, sizeof (cld->ap_log_id), "ccid%d/slot%u", 351 ucs.ucs_instance, ucs.ucs_slot) >= sizeof (cld->ap_log_id)) { 352 free(cld); 353 return (cfga_ccid_error(CFGA_LIB_ERROR, errp, "ap %s logical id" 354 " was too large", ap)); 355 } 356 357 if (strlcpy(cld->ap_phys_id, ap, sizeof (cld->ap_phys_id)) >= 358 sizeof (cld->ap_phys_id)) { 359 free(cld); 360 return (cfga_ccid_error(CFGA_LIB_ERROR, errp, 361 "ap %s physical id was too long", ap)); 362 } 363 364 cld->ap_class[0] = '\0'; 365 366 if ((ucs.ucs_status & UCCID_STATUS_F_CARD_PRESENT) != 0) { 367 cld->ap_r_state = CFGA_STAT_CONNECTED; 368 if ((ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE) != 0) { 369 cld->ap_o_state = CFGA_STAT_CONFIGURED; 370 } else { 371 cld->ap_o_state = CFGA_STAT_UNCONFIGURED; 372 } 373 } else { 374 cld->ap_r_state = CFGA_STAT_EMPTY; 375 cld->ap_o_state = CFGA_STAT_UNCONFIGURED; 376 } 377 378 /* 379 * We should probably have a way to indicate that there's an error when 380 * the ICC is basically foobar'd. We should also allow the status ioctl 381 * to know that the slot is resetting or something else is going on. 382 */ 383 if ((ucs.ucs_class.ccd_dwFeatures & 384 (CCID_CLASS_F_SHORT_APDU_XCHG | CCID_CLASS_F_EXT_APDU_XCHG)) == 0) { 385 cld->ap_cond = CFGA_COND_UNUSABLE; 386 } else { 387 cld->ap_cond = CFGA_COND_OK; 388 } 389 cld->ap_busy = 0; 390 cld->ap_status_time = (time_t)-1; 391 cfga_ccid_fill_info(&ucs, cld->ap_info, sizeof (cld->ap_info)); 392 if (strlcpy(cld->ap_type, "icc", sizeof (cld->ap_type)) >= 393 sizeof (cld->ap_type)) { 394 free(cld); 395 return (cfga_ccid_error(CFGA_LIB_ERROR, errp, 396 "ap %s type overflowed ICC field", ap)); 397 } 398 399 *ap_list = cld; 400 *nlist = 1; 401 return (CFGA_OK); 402 } 403 404 cfga_err_t 405 cfga_help(struct cfga_msg *msgp, const char *opts, cfga_flags_t flags) 406 { 407 (void) (*msgp->message_routine)(msgp, "CCID specific commands:\n"); 408 (void) (*msgp->message_routine)(msgp, 409 " cfgadm -c [configure|unconfigure] ap_id [ap_id...]\n"); 410 (void) (*msgp->message_routine)(msgp, 411 " cfgadm -x warm_reset ap_id [ap_id...]\n"); 412 413 return (CFGA_OK); 414 } 415 416 int 417 cfga_ap_id_cmp(const cfga_ap_log_id_t ap_id1, const cfga_ap_log_id_t ap_id2) 418 { 419 return (strcmp(ap_id1, ap_id2)); 420 } 421