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