1*5b133f3fSguenther /* $OpenBSD: usbhidaction.c,v 1.27 2023/03/08 04:43:12 guenther 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
747517eab2Snate static void
sighup(int signo)751853465dSderaadt sighup(int signo)
767517eab2Snate {
777517eab2Snate reparse = 1;
787517eab2Snate }
797517eab2Snate
807517eab2Snate int
main(int argc,char ** argv)817517eab2Snate main(int argc, char **argv)
827517eab2Snate {
837517eab2Snate const char *conf = NULL;
847517eab2Snate const char *dev = NULL;
857517eab2Snate int fd, ch, sz, n, val, i;
867517eab2Snate int demon, ignore;
877517eab2Snate report_desc_t repd;
887517eab2Snate char buf[100];
897517eab2Snate char devnamebuf[PATH_MAX];
907517eab2Snate struct command *cmd;
917517eab2Snate int reportid;
927517eab2Snate
937517eab2Snate demon = 1;
947517eab2Snate ignore = 0;
957517eab2Snate while ((ch = getopt(argc, argv, "c:df:iv")) != -1) {
967517eab2Snate switch(ch) {
977517eab2Snate case 'c':
987517eab2Snate conf = optarg;
997517eab2Snate break;
1007517eab2Snate case 'd':
1017517eab2Snate demon ^= 1;
1027517eab2Snate break;
1037517eab2Snate case 'i':
1047517eab2Snate ignore++;
1057517eab2Snate break;
1067517eab2Snate case 'f':
1077517eab2Snate dev = optarg;
1087517eab2Snate break;
1097517eab2Snate case 'v':
1107517eab2Snate demon = 0;
1117517eab2Snate verbose++;
1127517eab2Snate break;
1137517eab2Snate default:
1147517eab2Snate usage();
1157517eab2Snate }
1167517eab2Snate }
1177517eab2Snate argc -= optind;
1187517eab2Snate argv += optind;
1197517eab2Snate
1207517eab2Snate if (conf == NULL || dev == NULL)
1217517eab2Snate usage();
1227517eab2Snate
12378e5c336Sderaadt if (hid_start(NULL) == -1)
12478e5c336Sderaadt errx(1, "hid_init");
1257517eab2Snate
1267517eab2Snate if (dev[0] != '/') {
1277517eab2Snate snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s",
1280e71acc1Sderaadt isdigit((unsigned char)dev[0]) ? "uhid" : "", dev);
1297517eab2Snate dev = devnamebuf;
1307517eab2Snate }
1317517eab2Snate
132baecae6aSmk if (demon && conf[0] != '/')
133baecae6aSmk errx(1, "config file must have an absolute path, %s", conf);
134baecae6aSmk
1350205f8e6Sguenther fd = open(dev, O_RDWR | O_CLOEXEC);
1363aaa63ebSderaadt if (fd == -1)
1377517eab2Snate err(1, "%s", dev);
138240ba166Sfgsch
1393aaa63ebSderaadt if (ioctl(fd, USB_GET_REPORT_ID, &reportid) == -1)
1407517eab2Snate reportid = -1;
1417517eab2Snate repd = hid_get_report_desc(fd);
1427517eab2Snate if (repd == NULL)
143c20d9ad4Sjsyn err(1, "hid_get_report_desc() failed");
1447517eab2Snate
1457517eab2Snate commands = parse_conf(conf, repd, reportid, ignore);
1467517eab2Snate
1477517eab2Snate sz = hid_report_size(repd, hid_input, reportid);
1487517eab2Snate
1497517eab2Snate if (verbose)
1507517eab2Snate printf("report size %d\n", sz);
1517517eab2Snate if (sz > sizeof buf)
1527517eab2Snate errx(1, "report too large");
1537517eab2Snate
1547517eab2Snate (void)signal(SIGHUP, sighup);
1557517eab2Snate
15612e427a9Srobert /* we do not care about the children, so ignore them */
15712e427a9Srobert (void)signal(SIGCHLD, SIG_IGN);
15812e427a9Srobert
1597517eab2Snate if (demon) {
1603aaa63ebSderaadt if (daemon(0, 0) == -1)
1617517eab2Snate err(1, "daemon()");
1627517eab2Snate isdemon = 1;
1637517eab2Snate }
1647517eab2Snate
16503319b45Smestre if (unveil(conf, "r") == -1)
16603319b45Smestre err(1, "unveil %s", conf);
167c29fbb98Smestre if (unveil(_PATH_BSHELL, "x") == -1)
168c29fbb98Smestre err(1, "unveil %s", _PATH_BSHELL);
16903319b45Smestre if (unveil(NULL, NULL) == -1)
17003319b45Smestre err(1, "unveil");
17103319b45Smestre
1727517eab2Snate for(;;) {
1737517eab2Snate n = read(fd, buf, sz);
1747517eab2Snate if (verbose > 2) {
1757517eab2Snate printf("read %d bytes:", n);
1767517eab2Snate for (i = 0; i < n; i++)
1777517eab2Snate printf(" %02x", buf[i]);
1787517eab2Snate printf("\n");
1797517eab2Snate }
1803aaa63ebSderaadt if (n == -1) {
1817517eab2Snate if (verbose)
1827517eab2Snate err(1, "read");
1837517eab2Snate else
1847517eab2Snate exit(1);
1857517eab2Snate }
1867517eab2Snate if (n != sz) {
1877517eab2Snate err(2, "read size");
1887517eab2Snate }
1897517eab2Snate for (cmd = commands; cmd; cmd = cmd->next) {
1907517eab2Snate val = hid_get_data(buf, &cmd->item);
1917517eab2Snate if (cmd->value == val || cmd->anyvalue)
1927517eab2Snate docmd(cmd, val, dev, argc, argv);
1937517eab2Snate }
1947517eab2Snate if (reparse) {
1957517eab2Snate struct command *cmds =
1967517eab2Snate parse_conf(conf, repd, reportid, ignore);
1977517eab2Snate if (cmds) {
1987517eab2Snate freecommands(commands);
1997517eab2Snate commands = cmds;
2007517eab2Snate }
2017517eab2Snate reparse = 0;
2027517eab2Snate }
2037517eab2Snate }
2047517eab2Snate
2057517eab2Snate exit(0);
2067517eab2Snate }
2077517eab2Snate
2087517eab2Snate void
usage(void)2097517eab2Snate usage(void)
2107517eab2Snate {
2117517eab2Snate extern char *__progname;
2127517eab2Snate
213a7876005Ssobrado fprintf(stderr, "usage: %s [-div] -c config-file -f device arg ...\n",
214a7876005Ssobrado __progname);
2157517eab2Snate exit(1);
2167517eab2Snate }
2177517eab2Snate
2187517eab2Snate static int
peek(FILE * f)2197517eab2Snate peek(FILE *f)
2207517eab2Snate {
2217517eab2Snate int c;
2227517eab2Snate
2237517eab2Snate c = getc(f);
2247517eab2Snate if (c != EOF)
2257517eab2Snate ungetc(c, f);
2267517eab2Snate return c;
2277517eab2Snate }
2287517eab2Snate
2297517eab2Snate struct command *
parse_conf(const char * conf,report_desc_t repd,int reportid,int ignore)2307517eab2Snate parse_conf(const char *conf, report_desc_t repd, int reportid, int ignore)
2317517eab2Snate {
2327517eab2Snate FILE *f;
2337517eab2Snate char *p;
2347517eab2Snate int line;
2357517eab2Snate char buf[SIZE], name[SIZE], value[SIZE], action[SIZE];
2367517eab2Snate char usage[SIZE], coll[SIZE];
2377517eab2Snate struct command *cmd, *cmds;
2387517eab2Snate struct hid_data *d;
2397517eab2Snate struct hid_item h;
2407517eab2Snate int u, lo, hi, range;
2417517eab2Snate
2427517eab2Snate f = fopen(conf, "r");
2437517eab2Snate if (f == NULL)
2447517eab2Snate err(1, "%s", conf);
2457517eab2Snate
2467517eab2Snate cmds = NULL;
2477517eab2Snate for (line = 1; ; line++) {
2487517eab2Snate if (fgets(buf, sizeof buf, f) == NULL)
2497517eab2Snate break;
2507517eab2Snate if (buf[0] == '#' || buf[0] == '\n')
2517517eab2Snate continue;
2527517eab2Snate p = strchr(buf, '\n');
2537517eab2Snate while (p && isspace(peek(f))) {
2547517eab2Snate if (fgets(p, sizeof buf - strlen(buf), f) == NULL)
2557517eab2Snate break;
2567517eab2Snate p = strchr(buf, '\n');
2577517eab2Snate }
2587517eab2Snate if (p)
2597517eab2Snate *p = 0;
2607517eab2Snate if (sscanf(buf, "%s %s %[^\n]", name, value, action) != 3) {
2617517eab2Snate if (isdemon) {
2627517eab2Snate syslog(LOG_WARNING, "config file `%s', line %d"
2637517eab2Snate ", syntax error: %s", conf, line, buf);
2647517eab2Snate freecommands(cmds);
2655698d383Sguenther fclose(f);
2667517eab2Snate return (NULL);
2677517eab2Snate } else {
268ca8e7c81Sjaredy errx(1, "config file `%s', line %d"
2697517eab2Snate ", syntax error: %s", conf, line, buf);
2707517eab2Snate }
2717517eab2Snate }
2727517eab2Snate
2737517eab2Snate cmd = malloc(sizeof *cmd);
2747517eab2Snate if (cmd == NULL)
2757517eab2Snate err(1, "malloc failed");
2767517eab2Snate cmd->next = cmds;
2777517eab2Snate cmds = cmd;
2787517eab2Snate cmd->line = line;
2797517eab2Snate
2807517eab2Snate if (strcmp(value, "*") == 0) {
2817517eab2Snate cmd->anyvalue = 1;
2827517eab2Snate } else {
2837517eab2Snate cmd->anyvalue = 0;
2847517eab2Snate if (sscanf(value, "%d", &cmd->value) != 1) {
2857517eab2Snate if (isdemon) {
2867517eab2Snate syslog(LOG_WARNING,
2877517eab2Snate "config file `%s', line %d, "
288ca8e7c81Sjaredy "bad value: %s",
2897517eab2Snate conf, line, value);
2907517eab2Snate freecommands(cmds);
2915698d383Sguenther fclose(f);
2927517eab2Snate return (NULL);
2937517eab2Snate } else {
2947517eab2Snate errx(1, "config file `%s', line %d, "
295ca8e7c81Sjaredy "bad value: %s",
2967517eab2Snate conf, line, value);
2977517eab2Snate }
2987517eab2Snate }
2997517eab2Snate }
3007517eab2Snate
3017517eab2Snate coll[0] = 0;
3025698d383Sguenther d = hid_start_parse(repd, 1 << hid_input, reportid);
3035698d383Sguenther if (d == NULL)
3045698d383Sguenther err(1, "hid_start_parse failed");
3055698d383Sguenther while (hid_get_item(d, &h)) {
3067517eab2Snate if (verbose > 2)
3077517eab2Snate printf("kind=%d usage=%x\n", h.kind, h.usage);
3087517eab2Snate if (h.flags & HIO_CONST)
3097517eab2Snate continue;
3107517eab2Snate switch (h.kind) {
3117517eab2Snate case hid_input:
3127517eab2Snate if (h.usage_minimum != 0 ||
3137517eab2Snate h.usage_maximum != 0) {
3147517eab2Snate lo = h.usage_minimum;
3157517eab2Snate hi = h.usage_maximum;
3167517eab2Snate range = 1;
3177517eab2Snate } else {
3187517eab2Snate lo = h.usage;
3197517eab2Snate hi = h.usage;
3207517eab2Snate range = 0;
3217517eab2Snate }
3227517eab2Snate for (u = lo; u <= hi; u++) {
3237517eab2Snate snprintf(usage, sizeof usage, "%s:%s",
3247517eab2Snate hid_usage_page(HID_PAGE(u)),
3257517eab2Snate hid_usage_in_page(u));
3267517eab2Snate if (verbose > 2)
3277517eab2Snate printf("usage %s\n", usage);
3287517eab2Snate if (!strcasecmp(usage, name))
3297517eab2Snate goto foundhid;
3307517eab2Snate if (coll[0]) {
3317517eab2Snate snprintf(usage, sizeof usage,
3327517eab2Snate "%s.%s:%s", coll+1,
3337517eab2Snate hid_usage_page(HID_PAGE(u)),
3347517eab2Snate hid_usage_in_page(u));
3357517eab2Snate if (verbose > 2)
3367517eab2Snate printf("usage %s\n",
3377517eab2Snate usage);
3387517eab2Snate if (!strcasecmp(usage, name))
3397517eab2Snate goto foundhid;
3407517eab2Snate }
3417517eab2Snate }
3427517eab2Snate break;
3437517eab2Snate case hid_collection:
3447517eab2Snate snprintf(coll + strlen(coll),
3457517eab2Snate sizeof coll - strlen(coll), ".%s:%s",
3467517eab2Snate hid_usage_page(HID_PAGE(h.usage)),
3477517eab2Snate hid_usage_in_page(h.usage));
3487517eab2Snate break;
3497517eab2Snate case hid_endcollection:
3507517eab2Snate if (coll[0])
3517517eab2Snate *strrchr(coll, '.') = 0;
3527517eab2Snate break;
3537517eab2Snate default:
3547517eab2Snate break;
3557517eab2Snate }
3567517eab2Snate }
3575698d383Sguenther hid_end_parse(d);
3587517eab2Snate if (ignore) {
3597517eab2Snate if (verbose)
360c20d9ad4Sjsyn warnx("ignore item '%s'", name);
3615698d383Sguenther /* pop and free this ignored item */
3625698d383Sguenther cmds = cmd->next;
3635698d383Sguenther free(cmd);
3647517eab2Snate continue;
3657517eab2Snate }
3667517eab2Snate if (isdemon) {
3677517eab2Snate syslog(LOG_WARNING, "config file `%s', line %d, HID "
368ca8e7c81Sjaredy "item not found: `%s'", conf, line, name);
3697517eab2Snate freecommands(cmds);
3705698d383Sguenther fclose(f);
3717517eab2Snate return (NULL);
3727517eab2Snate } else {
3737517eab2Snate errx(1, "config file `%s', line %d, HID item "
374ca8e7c81Sjaredy "not found: `%s'", conf, line, name);
3757517eab2Snate }
3767517eab2Snate
3777517eab2Snate foundhid:
3787517eab2Snate hid_end_parse(d);
3797517eab2Snate cmd->item = h;
3807517eab2Snate cmd->name = strdup(name);
3817517eab2Snate cmd->action = strdup(action);
3827517eab2Snate if (range) {
3837517eab2Snate if (cmd->value == 1)
3847517eab2Snate cmd->value = u - lo;
3857517eab2Snate else
3867517eab2Snate cmd->value = -1;
3877517eab2Snate }
3887517eab2Snate
3897517eab2Snate if (verbose)
3907517eab2Snate printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name,
3917517eab2Snate cmd->value, cmd->action);
3927517eab2Snate }
3937517eab2Snate fclose(f);
3947517eab2Snate return (cmds);
3957517eab2Snate }
3967517eab2Snate
3977517eab2Snate void
docmd(struct command * cmd,int value,const char * hid,int argc,char ** argv)3987517eab2Snate docmd(struct command *cmd, int value, const char *hid, int argc, char **argv)
3997517eab2Snate {
4007517eab2Snate char cmdbuf[SIZE], *p, *q;
4017517eab2Snate size_t len;
4027517eab2Snate int n, r;
403a368f40fSjasper pid_t pid;
4047517eab2Snate
405940c232bSckuethe if (cmd->action == NULL) {
406940c232bSckuethe if (verbose)
407940c232bSckuethe printf("no action for device %s value %d\n",
408940c232bSckuethe hid, value);
409940c232bSckuethe return;
410940c232bSckuethe }
4117517eab2Snate for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) {
4127517eab2Snate if (*p == '$') {
4137517eab2Snate p++;
4147517eab2Snate len = &cmdbuf[SIZE-1] - q;
4150e71acc1Sderaadt if (isdigit((unsigned char)*p)) {
4167517eab2Snate n = strtol(p, &p, 10) - 1;
4177517eab2Snate if (n >= 0 && n < argc) {
4187517eab2Snate strncpy(q, argv[n], len);
4197517eab2Snate q += strlen(q);
4207517eab2Snate }
4217517eab2Snate } else if (*p == 'V') {
4227517eab2Snate p++;
4237517eab2Snate snprintf(q, len, "%d", value);
4247517eab2Snate q += strlen(q);
4257517eab2Snate } else if (*p == 'N') {
4267517eab2Snate p++;
4277517eab2Snate strncpy(q, cmd->name, len);
4287517eab2Snate q += strlen(q);
4297517eab2Snate } else if (*p == 'H') {
4307517eab2Snate p++;
4317517eab2Snate strncpy(q, hid, len);
4327517eab2Snate q += strlen(q);
4337517eab2Snate } else if (*p) {
4347517eab2Snate *q++ = *p++;
4357517eab2Snate }
4367517eab2Snate } else {
4377517eab2Snate *q++ = *p++;
4387517eab2Snate }
4397517eab2Snate }
4407517eab2Snate *q = 0;
4417517eab2Snate
442a368f40fSjasper pid = fork();
443a368f40fSjasper if (pid == -1)
444a368f40fSjasper warn("fork failed");
445a368f40fSjasper else if (pid == 0) {
446a368f40fSjasper setpgid(0, 0);
4477517eab2Snate if (verbose)
448a368f40fSjasper printf("executing '%s'\n", cmdbuf);
44953408464Skrw r = execl(_PATH_BSHELL, "sh", "-c", cmdbuf, (char *)NULL);
450a368f40fSjasper err(1, "execl");
451a368f40fSjasper }
4527517eab2Snate }
4537517eab2Snate
4547517eab2Snate void
freecommands(struct command * cmd)4557517eab2Snate freecommands(struct command *cmd)
4567517eab2Snate {
4577517eab2Snate struct command *next;
4587517eab2Snate
4597517eab2Snate while (cmd) {
4607517eab2Snate next = cmd->next;
4617517eab2Snate free(cmd);
4627517eab2Snate cmd = next;
4637517eab2Snate }
4647517eab2Snate }
465