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
cfga_ccid_error(cfga_err_t err,char ** errp,const char * fmt,...)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
cfga_ccid_modify(uccid_cmd_icc_modify_t * modify,const char * ap,struct cfga_confirm * confp,struct cfga_msg * msgp,char ** errp,boolean_t force)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
cfga_change_state(cfga_cmd_t cmd,const char * ap,const char * opts,struct cfga_confirm * confp,struct cfga_msg * msgp,char ** errp,cfga_flags_t flags)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
cfga_private_func(const char * function,const char * ap,const char * opts,struct cfga_confirm * confp,struct cfga_msg * msgp,char ** errp,cfga_flags_t flags)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
cfga_test(const char * ap,const char * opts,struct cfga_msg * msgp,char ** errp,cfga_flags_t flags)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
cfga_ccid_fill_info(const uccid_cmd_status_t * ucs,char * buf,size_t len)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
cfga_list_ext(const char * ap,struct cfga_list_data ** ap_list,int * nlist,const char * opts,const char * listopts,char ** errp,cfga_flags_t flags)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
cfga_help(struct cfga_msg * msgp,const char * opts,cfga_flags_t flags)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
cfga_ap_id_cmp(const cfga_ap_log_id_t ap_id1,const cfga_ap_log_id_t ap_id2)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