1*53408464Skrw /*	$OpenBSD: usbhidaction.c,v 1.22 2016/03/17 19:40:43 krw Exp $ */
27517eab2Snate /*      $NetBSD: usbhidaction.c,v 1.7 2002/01/18 14:38:59 augustss Exp $ */
37517eab2Snate 
47517eab2Snate /*
57517eab2Snate  * Copyright (c) 2000, 2002 The NetBSD Foundation, Inc.
67517eab2Snate  * All rights reserved.
77517eab2Snate  *
87517eab2Snate  * This code is derived from software contributed to The NetBSD Foundation
97517eab2Snate  * by Lennart Augustsson <lennart@augustsson.net>.
107517eab2Snate  *
117517eab2Snate  * Redistribution and use in source and binary forms, with or without
127517eab2Snate  * modification, are permitted provided that the following conditions
137517eab2Snate  * are met:
147517eab2Snate  * 1. Redistributions of source code must retain the above copyright
157517eab2Snate  *    notice, this list of conditions and the following disclaimer.
167517eab2Snate  * 2. Redistributions in binary form must reproduce the above copyright
177517eab2Snate  *    notice, this list of conditions and the following disclaimer in the
187517eab2Snate  *    documentation and/or other materials provided with the distribution.
197517eab2Snate  *
207517eab2Snate  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
217517eab2Snate  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
227517eab2Snate  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
237517eab2Snate  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
247517eab2Snate  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
257517eab2Snate  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
267517eab2Snate  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
277517eab2Snate  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
287517eab2Snate  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
297517eab2Snate  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
307517eab2Snate  * POSSIBILITY OF SUCH DAMAGE.
317517eab2Snate  */
327517eab2Snate 
337517eab2Snate #include <stdio.h>
347517eab2Snate #include <stdlib.h>
357517eab2Snate #include <string.h>
367517eab2Snate #include <ctype.h>
377517eab2Snate #include <err.h>
387517eab2Snate #include <fcntl.h>
397517eab2Snate #include <limits.h>
407517eab2Snate #include <unistd.h>
417517eab2Snate #include <sys/types.h>
427517eab2Snate #include <sys/ioctl.h>
437517eab2Snate #include <dev/usb/usb.h>
447517eab2Snate #include <dev/usb/usbhid.h>
457517eab2Snate #include <usbhid.h>
467517eab2Snate #include <syslog.h>
477517eab2Snate #include <signal.h>
48a368f40fSjasper #include <paths.h>
497517eab2Snate 
507517eab2Snate int verbose = 0;
517517eab2Snate int isdemon = 0;
52cf57c52cSderaadt 
53cf57c52cSderaadt volatile sig_atomic_t reparse = 0;
547517eab2Snate 
557517eab2Snate struct command {
567517eab2Snate 	struct command *next;
577517eab2Snate 	int line;
587517eab2Snate 
597517eab2Snate 	struct hid_item item;
607517eab2Snate 	int value;
617517eab2Snate 	char anyvalue;
627517eab2Snate 	char *name;
637517eab2Snate 	char *action;
647517eab2Snate };
657517eab2Snate struct command *commands;
667517eab2Snate 
677517eab2Snate #define SIZE 4000
687517eab2Snate 
697517eab2Snate void usage(void);
707517eab2Snate struct command *parse_conf(const char *, report_desc_t, int, int);
717517eab2Snate void docmd(struct command *, int, const char *, int, char **);
727517eab2Snate void freecommands(struct command *);
737517eab2Snate 
741853465dSderaadt /* ARGSUSED */
757517eab2Snate static void
761853465dSderaadt sighup(int signo)
777517eab2Snate {
787517eab2Snate 	reparse = 1;
797517eab2Snate }
807517eab2Snate 
817517eab2Snate int
827517eab2Snate main(int argc, char **argv)
837517eab2Snate {
847517eab2Snate 	const char *conf = NULL;
857517eab2Snate 	const char *dev = NULL;
867517eab2Snate 	int fd, ch, sz, n, val, i;
877517eab2Snate 	int demon, ignore;
887517eab2Snate 	report_desc_t repd;
897517eab2Snate 	char buf[100];
907517eab2Snate 	char devnamebuf[PATH_MAX];
917517eab2Snate 	struct command *cmd;
927517eab2Snate 	int reportid;
937517eab2Snate 
947517eab2Snate 	demon = 1;
957517eab2Snate 	ignore = 0;
967517eab2Snate 	while ((ch = getopt(argc, argv, "c:df:iv")) != -1) {
977517eab2Snate 		switch(ch) {
987517eab2Snate 		case 'c':
997517eab2Snate 			conf = optarg;
1007517eab2Snate 			break;
1017517eab2Snate 		case 'd':
1027517eab2Snate 			demon ^= 1;
1037517eab2Snate 			break;
1047517eab2Snate 		case 'i':
1057517eab2Snate 			ignore++;
1067517eab2Snate 			break;
1077517eab2Snate 		case 'f':
1087517eab2Snate 			dev = optarg;
1097517eab2Snate 			break;
1107517eab2Snate 		case 'v':
1117517eab2Snate 			demon = 0;
1127517eab2Snate 			verbose++;
1137517eab2Snate 			break;
1147517eab2Snate 		case '?':
1157517eab2Snate 		default:
1167517eab2Snate 			usage();
1177517eab2Snate 		}
1187517eab2Snate 	}
1197517eab2Snate 	argc -= optind;
1207517eab2Snate 	argv += optind;
1217517eab2Snate 
1227517eab2Snate 	if (conf == NULL || dev == NULL)
1237517eab2Snate 		usage();
1247517eab2Snate 
12578e5c336Sderaadt 	if (hid_start(NULL) == -1)
12678e5c336Sderaadt 		errx(1, "hid_init");
1277517eab2Snate 
1287517eab2Snate 	if (dev[0] != '/') {
1297517eab2Snate 		snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s",
1300e71acc1Sderaadt 		    isdigit((unsigned char)dev[0]) ? "uhid" : "", dev);
1317517eab2Snate 		dev = devnamebuf;
1327517eab2Snate 	}
1337517eab2Snate 
134baecae6aSmk 	if (demon && conf[0] != '/')
135baecae6aSmk 		errx(1, "config file must have an absolute path, %s", conf);
136baecae6aSmk 
1370205f8e6Sguenther 	fd = open(dev, O_RDWR | O_CLOEXEC);
1387517eab2Snate 	if (fd < 0)
1397517eab2Snate 		err(1, "%s", dev);
140240ba166Sfgsch 
1417517eab2Snate 	if (ioctl(fd, USB_GET_REPORT_ID, &reportid) < 0)
1427517eab2Snate 		reportid = -1;
1437517eab2Snate 	repd = hid_get_report_desc(fd);
1447517eab2Snate 	if (repd == NULL)
145c20d9ad4Sjsyn 		err(1, "hid_get_report_desc() failed");
1467517eab2Snate 
1477517eab2Snate 	commands = parse_conf(conf, repd, reportid, ignore);
1487517eab2Snate 
1497517eab2Snate 	sz = hid_report_size(repd, hid_input, reportid);
1507517eab2Snate 
1517517eab2Snate 	if (verbose)
1527517eab2Snate 		printf("report size %d\n", sz);
1537517eab2Snate 	if (sz > sizeof buf)
1547517eab2Snate 		errx(1, "report too large");
1557517eab2Snate 
1567517eab2Snate 	(void)signal(SIGHUP, sighup);
1577517eab2Snate 
15812e427a9Srobert 	/* we do not care about the children, so ignore them */
15912e427a9Srobert 	(void)signal(SIGCHLD, SIG_IGN);
16012e427a9Srobert 
1617517eab2Snate 	if (demon) {
1627517eab2Snate 		if (daemon(0, 0) < 0)
1637517eab2Snate 			err(1, "daemon()");
1647517eab2Snate 		isdemon = 1;
1657517eab2Snate 	}
1667517eab2Snate 
1677517eab2Snate 	for(;;) {
1687517eab2Snate 		n = read(fd, buf, sz);
1697517eab2Snate 		if (verbose > 2) {
1707517eab2Snate 			printf("read %d bytes:", n);
1717517eab2Snate 			for (i = 0; i < n; i++)
1727517eab2Snate 				printf(" %02x", buf[i]);
1737517eab2Snate 			printf("\n");
1747517eab2Snate 		}
1757517eab2Snate 		if (n < 0) {
1767517eab2Snate 			if (verbose)
1777517eab2Snate 				err(1, "read");
1787517eab2Snate 			else
1797517eab2Snate 				exit(1);
1807517eab2Snate 		}
1817517eab2Snate 		if (n != sz) {
1827517eab2Snate 			err(2, "read size");
1837517eab2Snate 		}
1847517eab2Snate 		for (cmd = commands; cmd; cmd = cmd->next) {
1857517eab2Snate 			val = hid_get_data(buf, &cmd->item);
1867517eab2Snate 			if (cmd->value == val || cmd->anyvalue)
1877517eab2Snate 				docmd(cmd, val, dev, argc, argv);
1887517eab2Snate 		}
1897517eab2Snate 		if (reparse) {
1907517eab2Snate 			struct command *cmds =
1917517eab2Snate 			    parse_conf(conf, repd, reportid, ignore);
1927517eab2Snate 			if (cmds) {
1937517eab2Snate 				freecommands(commands);
1947517eab2Snate 				commands = cmds;
1957517eab2Snate 			}
1967517eab2Snate 			reparse = 0;
1977517eab2Snate 		}
1987517eab2Snate 	}
1997517eab2Snate 
2007517eab2Snate 	exit(0);
2017517eab2Snate }
2027517eab2Snate 
2037517eab2Snate void
2047517eab2Snate usage(void)
2057517eab2Snate {
2067517eab2Snate 	extern char *__progname;
2077517eab2Snate 
208a7876005Ssobrado 	fprintf(stderr, "usage: %s [-div] -c config-file -f device arg ...\n",
209a7876005Ssobrado 	    __progname);
2107517eab2Snate 	exit(1);
2117517eab2Snate }
2127517eab2Snate 
2137517eab2Snate static int
2147517eab2Snate peek(FILE *f)
2157517eab2Snate {
2167517eab2Snate 	int c;
2177517eab2Snate 
2187517eab2Snate 	c = getc(f);
2197517eab2Snate 	if (c != EOF)
2207517eab2Snate 		ungetc(c, f);
2217517eab2Snate 	return c;
2227517eab2Snate }
2237517eab2Snate 
2247517eab2Snate struct command *
2257517eab2Snate parse_conf(const char *conf, report_desc_t repd, int reportid, int ignore)
2267517eab2Snate {
2277517eab2Snate 	FILE *f;
2287517eab2Snate 	char *p;
2297517eab2Snate 	int line;
2307517eab2Snate 	char buf[SIZE], name[SIZE], value[SIZE], action[SIZE];
2317517eab2Snate 	char usage[SIZE], coll[SIZE];
2327517eab2Snate 	struct command *cmd, *cmds;
2337517eab2Snate 	struct hid_data *d;
2347517eab2Snate 	struct hid_item h;
2357517eab2Snate 	int u, lo, hi, range;
2367517eab2Snate 
2377517eab2Snate 	f = fopen(conf, "r");
2387517eab2Snate 	if (f == NULL)
2397517eab2Snate 		err(1, "%s", conf);
2407517eab2Snate 
2417517eab2Snate 	cmds = NULL;
2427517eab2Snate 	for (line = 1; ; line++) {
2437517eab2Snate 		if (fgets(buf, sizeof buf, f) == NULL)
2447517eab2Snate 			break;
2457517eab2Snate 		if (buf[0] == '#' || buf[0] == '\n')
2467517eab2Snate 			continue;
2477517eab2Snate 		p = strchr(buf, '\n');
2487517eab2Snate 		while (p && isspace(peek(f))) {
2497517eab2Snate 			if (fgets(p, sizeof buf - strlen(buf), f) == NULL)
2507517eab2Snate 				break;
2517517eab2Snate 			p = strchr(buf, '\n');
2527517eab2Snate 		}
2537517eab2Snate 		if (p)
2547517eab2Snate 			*p = 0;
2557517eab2Snate 		if (sscanf(buf, "%s %s %[^\n]", name, value, action) != 3) {
2567517eab2Snate 			if (isdemon) {
2577517eab2Snate 				syslog(LOG_WARNING, "config file `%s', line %d"
2587517eab2Snate 				    ", syntax error: %s", conf, line, buf);
2597517eab2Snate 				freecommands(cmds);
2605698d383Sguenther 				fclose(f);
2617517eab2Snate 				return (NULL);
2627517eab2Snate 			} else {
263ca8e7c81Sjaredy 				errx(1, "config file `%s', line %d"
2647517eab2Snate 				    ", syntax error: %s", conf, line, buf);
2657517eab2Snate 			}
2667517eab2Snate 		}
2677517eab2Snate 
2687517eab2Snate 		cmd = malloc(sizeof *cmd);
2697517eab2Snate 		if (cmd == NULL)
2707517eab2Snate 			err(1, "malloc failed");
2717517eab2Snate 		cmd->next = cmds;
2727517eab2Snate 		cmds = cmd;
2737517eab2Snate 		cmd->line = line;
2747517eab2Snate 
2757517eab2Snate 		if (strcmp(value, "*") == 0) {
2767517eab2Snate 			cmd->anyvalue = 1;
2777517eab2Snate 		} else {
2787517eab2Snate 			cmd->anyvalue = 0;
2797517eab2Snate 			if (sscanf(value, "%d", &cmd->value) != 1) {
2807517eab2Snate 				if (isdemon) {
2817517eab2Snate 					syslog(LOG_WARNING,
2827517eab2Snate 					    "config file `%s', line %d, "
283ca8e7c81Sjaredy 					    "bad value: %s",
2847517eab2Snate 					    conf, line, value);
2857517eab2Snate 					freecommands(cmds);
2865698d383Sguenther 					fclose(f);
2877517eab2Snate 					return (NULL);
2887517eab2Snate 				} else {
2897517eab2Snate 					errx(1, "config file `%s', line %d, "
290ca8e7c81Sjaredy 					    "bad value: %s",
2917517eab2Snate 					    conf, line, value);
2927517eab2Snate 				}
2937517eab2Snate 			}
2947517eab2Snate 		}
2957517eab2Snate 
2967517eab2Snate 		coll[0] = 0;
2975698d383Sguenther 		d = hid_start_parse(repd, 1 << hid_input, reportid);
2985698d383Sguenther 		if (d == NULL)
2995698d383Sguenther 			err(1, "hid_start_parse failed");
3005698d383Sguenther 		while (hid_get_item(d, &h)) {
3017517eab2Snate 			if (verbose > 2)
3027517eab2Snate 				printf("kind=%d usage=%x\n", h.kind, h.usage);
3037517eab2Snate 			if (h.flags & HIO_CONST)
3047517eab2Snate 				continue;
3057517eab2Snate 			switch (h.kind) {
3067517eab2Snate 			case hid_input:
3077517eab2Snate 				if (h.usage_minimum != 0 ||
3087517eab2Snate 				    h.usage_maximum != 0) {
3097517eab2Snate 					lo = h.usage_minimum;
3107517eab2Snate 					hi = h.usage_maximum;
3117517eab2Snate 					range = 1;
3127517eab2Snate 				} else {
3137517eab2Snate 					lo = h.usage;
3147517eab2Snate 					hi = h.usage;
3157517eab2Snate 					range = 0;
3167517eab2Snate 				}
3177517eab2Snate 				for (u = lo; u <= hi; u++) {
3187517eab2Snate 					snprintf(usage, sizeof usage,  "%s:%s",
3197517eab2Snate 						 hid_usage_page(HID_PAGE(u)),
3207517eab2Snate 						 hid_usage_in_page(u));
3217517eab2Snate 					if (verbose > 2)
3227517eab2Snate 						printf("usage %s\n", usage);
3237517eab2Snate 					if (!strcasecmp(usage, name))
3247517eab2Snate 						goto foundhid;
3257517eab2Snate 					if (coll[0]) {
3267517eab2Snate 						snprintf(usage, sizeof usage,
3277517eab2Snate 						  "%s.%s:%s", coll+1,
3287517eab2Snate 						  hid_usage_page(HID_PAGE(u)),
3297517eab2Snate 						  hid_usage_in_page(u));
3307517eab2Snate 						if (verbose > 2)
3317517eab2Snate 							printf("usage %s\n",
3327517eab2Snate 							    usage);
3337517eab2Snate 						if (!strcasecmp(usage, name))
3347517eab2Snate 							goto foundhid;
3357517eab2Snate 					}
3367517eab2Snate 				}
3377517eab2Snate 				break;
3387517eab2Snate 			case hid_collection:
3397517eab2Snate 				snprintf(coll + strlen(coll),
3407517eab2Snate 				    sizeof coll - strlen(coll),  ".%s:%s",
3417517eab2Snate 				    hid_usage_page(HID_PAGE(h.usage)),
3427517eab2Snate 				    hid_usage_in_page(h.usage));
3437517eab2Snate 				break;
3447517eab2Snate 			case hid_endcollection:
3457517eab2Snate 				if (coll[0])
3467517eab2Snate 					*strrchr(coll, '.') = 0;
3477517eab2Snate 				break;
3487517eab2Snate 			default:
3497517eab2Snate 				break;
3507517eab2Snate 			}
3517517eab2Snate 		}
3525698d383Sguenther 		hid_end_parse(d);
3537517eab2Snate 		if (ignore) {
3547517eab2Snate 			if (verbose)
355c20d9ad4Sjsyn 				warnx("ignore item '%s'", name);
3565698d383Sguenther 			/* pop and free this ignored item */
3575698d383Sguenther 			cmds = cmd->next;
3585698d383Sguenther 			free(cmd);
3597517eab2Snate 			continue;
3607517eab2Snate 		}
3617517eab2Snate 		if (isdemon) {
3627517eab2Snate 			syslog(LOG_WARNING, "config file `%s', line %d, HID "
363ca8e7c81Sjaredy 			    "item not found: `%s'", conf, line, name);
3647517eab2Snate 			freecommands(cmds);
3655698d383Sguenther 			fclose(f);
3667517eab2Snate 			return (NULL);
3677517eab2Snate 		} else {
3687517eab2Snate 			errx(1, "config file `%s', line %d, HID item "
369ca8e7c81Sjaredy 			    "not found: `%s'", conf, line, name);
3707517eab2Snate 		}
3717517eab2Snate 
3727517eab2Snate 	foundhid:
3737517eab2Snate 		hid_end_parse(d);
3747517eab2Snate 		cmd->item = h;
3757517eab2Snate 		cmd->name = strdup(name);
3767517eab2Snate 		cmd->action = strdup(action);
3777517eab2Snate 		if (range) {
3787517eab2Snate 			if (cmd->value == 1)
3797517eab2Snate 				cmd->value = u - lo;
3807517eab2Snate 			else
3817517eab2Snate 				cmd->value = -1;
3827517eab2Snate 		}
3837517eab2Snate 
3847517eab2Snate 		if (verbose)
3857517eab2Snate 			printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name,
3867517eab2Snate 			    cmd->value, cmd->action);
3877517eab2Snate 	}
3887517eab2Snate 	fclose(f);
3897517eab2Snate 	return (cmds);
3907517eab2Snate }
3917517eab2Snate 
3927517eab2Snate void
3937517eab2Snate docmd(struct command *cmd, int value, const char *hid, int argc, char **argv)
3947517eab2Snate {
3957517eab2Snate 	char cmdbuf[SIZE], *p, *q;
3967517eab2Snate 	size_t len;
3977517eab2Snate 	int n, r;
398a368f40fSjasper 	pid_t pid;
3997517eab2Snate 
400940c232bSckuethe 	if (cmd->action == NULL) {
401940c232bSckuethe 		if (verbose)
402940c232bSckuethe 			printf("no action for device %s value %d\n",
403940c232bSckuethe 			    hid, value);
404940c232bSckuethe 		return;
405940c232bSckuethe 	}
4067517eab2Snate 	for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) {
4077517eab2Snate 		if (*p == '$') {
4087517eab2Snate 			p++;
4097517eab2Snate 			len = &cmdbuf[SIZE-1] - q;
4100e71acc1Sderaadt 			if (isdigit((unsigned char)*p)) {
4117517eab2Snate 				n = strtol(p, &p, 10) - 1;
4127517eab2Snate 				if (n >= 0 && n < argc) {
4137517eab2Snate 					strncpy(q, argv[n], len);
4147517eab2Snate 					q += strlen(q);
4157517eab2Snate 				}
4167517eab2Snate 			} else if (*p == 'V') {
4177517eab2Snate 				p++;
4187517eab2Snate 				snprintf(q, len, "%d", value);
4197517eab2Snate 				q += strlen(q);
4207517eab2Snate 			} else if (*p == 'N') {
4217517eab2Snate 				p++;
4227517eab2Snate 				strncpy(q, cmd->name, len);
4237517eab2Snate 				q += strlen(q);
4247517eab2Snate 			} else if (*p == 'H') {
4257517eab2Snate 				p++;
4267517eab2Snate 				strncpy(q, hid, len);
4277517eab2Snate 				q += strlen(q);
4287517eab2Snate 			} else if (*p) {
4297517eab2Snate 				*q++ = *p++;
4307517eab2Snate 			}
4317517eab2Snate 		} else {
4327517eab2Snate 			*q++ = *p++;
4337517eab2Snate 		}
4347517eab2Snate 	}
4357517eab2Snate 	*q = 0;
4367517eab2Snate 
437a368f40fSjasper 	pid = fork();
438a368f40fSjasper 	if (pid == -1)
439a368f40fSjasper 		warn("fork failed");
440a368f40fSjasper 	else if (pid == 0) {
441a368f40fSjasper 		setpgid(0, 0);
4427517eab2Snate 		if (verbose)
443a368f40fSjasper 			printf("executing '%s'\n", cmdbuf);
444*53408464Skrw 		r = execl(_PATH_BSHELL, "sh", "-c", cmdbuf, (char *)NULL);
445a368f40fSjasper 		err(1, "execl");
446a368f40fSjasper 	}
4477517eab2Snate }
4487517eab2Snate 
4497517eab2Snate void
4507517eab2Snate freecommands(struct command *cmd)
4517517eab2Snate {
4527517eab2Snate 	struct command *next;
4537517eab2Snate 
4547517eab2Snate 	while (cmd) {
4557517eab2Snate 		next = cmd->next;
4567517eab2Snate 		free(cmd);
4577517eab2Snate 		cmd = next;
4587517eab2Snate 	}
4597517eab2Snate }
460