Annotation of src/usr.bin/sndiod/sndiod.c, Revision 1.35
1.35 ! ratchov 1: /* $OpenBSD: sndiod.c,v 1.34 2018/08/08 22:31:43 ratchov Exp $ */
1.1 ratchov 2: /*
3: * Copyright (c) 2008-2012 Alexandre Ratchov <alex@caoua.org>
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: */
17: #include <sys/stat.h>
18: #include <sys/types.h>
19: #include <sys/resource.h>
1.18 ratchov 20: #include <sys/socket.h>
1.1 ratchov 21:
22: #include <err.h>
23: #include <errno.h>
24: #include <fcntl.h>
25: #include <grp.h>
26: #include <limits.h>
27: #include <pwd.h>
28: #include <signal.h>
29: #include <sndio.h>
30: #include <stdio.h>
31: #include <stdlib.h>
32: #include <string.h>
33: #include <unistd.h>
34:
35: #include "amsg.h"
36: #include "defs.h"
37: #include "dev.h"
1.18 ratchov 38: #include "fdpass.h"
1.1 ratchov 39: #include "file.h"
40: #include "listen.h"
41: #include "midi.h"
42: #include "opt.h"
43: #include "sock.h"
44: #include "utils.h"
45:
46: /*
47: * unprivileged user name
48: */
49: #ifndef SNDIO_USER
50: #define SNDIO_USER "_sndio"
51: #endif
52:
53: /*
1.18 ratchov 54: * privileged user name
55: */
56: #ifndef SNDIO_PRIV_USER
57: #define SNDIO_PRIV_USER "_sndiop"
58: #endif
59:
60: /*
1.1 ratchov 61: * priority when run as root
62: */
63: #ifndef SNDIO_PRIO
64: #define SNDIO_PRIO (-20)
65: #endif
66:
67: /*
68: * sample rate if no ``-r'' is used
69: */
70: #ifndef DEFAULT_RATE
71: #define DEFAULT_RATE 48000
72: #endif
73:
74: /*
75: * block size if neither ``-z'' nor ``-b'' is used
76: */
77: #ifndef DEFAULT_ROUND
78: #define DEFAULT_ROUND 960
79: #endif
80:
81: /*
82: * buffer size if neither ``-z'' nor ``-b'' is used
83: */
84: #ifndef DEFAULT_BUFSZ
1.8 dcoppa 85: #define DEFAULT_BUFSZ 7680
1.1 ratchov 86: #endif
87:
88: /*
89: * default device in server mode
90: */
91: #ifndef DEFAULT_DEV
92: #define DEFAULT_DEV "rsnd/0"
93: #endif
1.5 ratchov 94:
95: void sigint(int);
96: void opt_ch(int *, int *);
97: void opt_enc(struct aparams *);
98: int opt_mmc(void);
99: int opt_onoff(void);
100: int getword(char *, char **);
101: unsigned int opt_mode(void);
1.31 ratchov 102: void getbasepath(char *);
1.5 ratchov 103: void setsig(void);
104: void unsetsig(void);
105: struct dev *mkdev(char *, struct aparams *,
106: int, int, int, int, int, int);
1.27 ratchov 107: struct port *mkport(char *, int);
1.5 ratchov 108: struct opt *mkopt(char *, struct dev *,
109: int, int, int, int, int, int, int, int);
1.1 ratchov 110:
111: unsigned int log_level = 0;
112: volatile sig_atomic_t quit_flag = 0;
113:
114: char usagestr[] = "usage: sndiod [-d] [-a flag] [-b nframes] "
115: "[-C min:max] [-c min:max] [-e enc]\n\t"
116: "[-f device] [-j flag] [-L addr] [-m mode] [-q port] [-r rate]\n\t"
117: "[-s name] [-t mode] [-U unit] [-v volume] [-w flag] [-z nframes]\n";
118:
119: /*
120: * SIGINT handler, it raises the quit flag. If the flag is already set,
121: * that means that the last SIGINT was not handled, because the process
122: * is blocked somewhere, so exit.
123: */
124: void
125: sigint(int s)
126: {
127: if (quit_flag)
128: _exit(1);
129: quit_flag = 1;
130: }
131:
132: void
133: opt_ch(int *rcmin, int *rcmax)
134: {
135: char *next, *end;
136: long cmin, cmax;
137:
138: errno = 0;
139: cmin = strtol(optarg, &next, 10);
140: if (next == optarg || *next != ':')
141: goto failed;
142: cmax = strtol(++next, &end, 10);
143: if (end == next || *end != '\0')
144: goto failed;
145: if (cmin < 0 || cmax < cmin || cmax >= NCHAN_MAX)
146: goto failed;
147: *rcmin = cmin;
148: *rcmax = cmax;
149: return;
150: failed:
151: errx(1, "%s: bad channel range", optarg);
152: }
153:
154: void
155: opt_enc(struct aparams *par)
156: {
157: int len;
158:
159: len = aparams_strtoenc(par, optarg);
160: if (len == 0 || optarg[len] != '\0')
161: errx(1, "%s: bad encoding", optarg);
162: }
163:
164: int
165: opt_mmc(void)
166: {
167: if (strcmp("off", optarg) == 0)
168: return 0;
169: if (strcmp("slave", optarg) == 0)
170: return 1;
171: errx(1, "%s: off/slave expected", optarg);
172: }
173:
174: int
175: opt_onoff(void)
176: {
177: if (strcmp("off", optarg) == 0)
178: return 0;
179: if (strcmp("on", optarg) == 0)
180: return 1;
181: errx(1, "%s: on/off expected", optarg);
182: }
183:
1.4 ratchov 184: int
185: getword(char *word, char **str)
186: {
187: char *p = *str;
188:
189: for (;;) {
190: if (*word == '\0')
191: break;
192: if (*word++ != *p++)
193: return 0;
194: }
195: if (*p == ',' || *p == '\0') {
196: *str = p;
197: return 1;
198: }
199: return 0;
200: }
201:
1.1 ratchov 202: unsigned int
203: opt_mode(void)
204: {
205: unsigned int mode = 0;
206: char *p = optarg;
207:
1.4 ratchov 208: for (;;) {
209: if (getword("play", &p)) {
1.1 ratchov 210: mode |= MODE_PLAY;
1.4 ratchov 211: } else if (getword("rec", &p)) {
1.1 ratchov 212: mode |= MODE_REC;
1.4 ratchov 213: } else if (getword("mon", &p)) {
1.1 ratchov 214: mode |= MODE_MON;
1.4 ratchov 215: } else if (getword("midi", &p)) {
1.1 ratchov 216: mode |= MODE_MIDIMASK;
1.4 ratchov 217: } else
1.1 ratchov 218: errx(1, "%s: bad mode", optarg);
219: if (*p == '\0')
220: break;
1.4 ratchov 221: p++;
1.1 ratchov 222: }
223: if (mode == 0)
224: errx(1, "empty mode");
225: return mode;
226: }
227:
228: void
229: setsig(void)
230: {
231: struct sigaction sa;
232:
233: quit_flag = 0;
234: sigfillset(&sa.sa_mask);
235: sa.sa_flags = SA_RESTART;
236: sa.sa_handler = sigint;
1.35 ! ratchov 237: if (sigaction(SIGINT, &sa, NULL) == -1)
1.1 ratchov 238: err(1, "sigaction(int) failed");
1.35 ! ratchov 239: if (sigaction(SIGTERM, &sa, NULL) == -1)
1.1 ratchov 240: err(1, "sigaction(term) failed");
1.35 ! ratchov 241: if (sigaction(SIGHUP, &sa, NULL) == -1)
1.1 ratchov 242: err(1, "sigaction(hup) failed");
243: }
244:
245: void
246: unsetsig(void)
247: {
248: struct sigaction sa;
249:
250: sigfillset(&sa.sa_mask);
251: sa.sa_flags = SA_RESTART;
252: sa.sa_handler = SIG_DFL;
1.35 ! ratchov 253: if (sigaction(SIGHUP, &sa, NULL) == -1)
1.29 ratchov 254: err(1, "unsetsig(hup): sigaction failed");
1.35 ! ratchov 255: if (sigaction(SIGTERM, &sa, NULL) == -1)
1.29 ratchov 256: err(1, "unsetsig(term): sigaction failed");
1.35 ! ratchov 257: if (sigaction(SIGINT, &sa, NULL) == -1)
1.29 ratchov 258: err(1, "unsetsig(int): sigaction failed");
1.1 ratchov 259: }
260:
261: void
1.31 ratchov 262: getbasepath(char *base)
1.1 ratchov 263: {
264: uid_t uid;
265: struct stat sb;
1.14 ratchov 266: mode_t mask, omask;
1.1 ratchov 267:
268: uid = geteuid();
269: if (uid == 0) {
270: mask = 022;
1.10 ratchov 271: snprintf(base, SOCKPATH_MAX, SOCKPATH_DIR);
1.1 ratchov 272: } else {
273: mask = 077;
1.10 ratchov 274: snprintf(base, SOCKPATH_MAX, SOCKPATH_DIR "-%u", uid);
1.1 ratchov 275: }
1.14 ratchov 276: omask = umask(mask);
1.35 ! ratchov 277: if (mkdir(base, 0777) == -1) {
1.1 ratchov 278: if (errno != EEXIST)
279: err(1, "mkdir(\"%s\")", base);
280: }
1.28 ratchov 281: umask(omask);
1.35 ! ratchov 282: if (stat(base, &sb) == -1)
1.1 ratchov 283: err(1, "stat(\"%s\")", base);
1.30 ratchov 284: if (!S_ISDIR(sb.st_mode))
285: errx(1, "%s is not a directory", base);
1.1 ratchov 286: if (sb.st_uid != uid || (sb.st_mode & mask) != 0)
287: errx(1, "%s has wrong permissions", base);
288: }
289:
290: struct dev *
291: mkdev(char *path, struct aparams *par,
292: int mode, int bufsz, int round, int rate, int hold, int autovol)
293: {
294: struct dev *d;
295:
296: for (d = dev_list; d != NULL; d = d->next) {
297: if (strcmp(d->path, path) == 0)
298: return d;
299: }
300: if (!bufsz && !round) {
301: round = DEFAULT_ROUND;
302: bufsz = DEFAULT_BUFSZ;
303: } else if (!bufsz) {
304: bufsz = round * 2;
305: } else if (!round)
306: round = bufsz / 2;
307: d = dev_new(path, par, mode, bufsz, round, rate, hold, autovol);
308: if (d == NULL)
309: exit(1);
310: return d;
311: }
312:
1.27 ratchov 313: struct port *
314: mkport(char *path, int hold)
315: {
316: struct port *c;
317:
318: for (c = port_list; c != NULL; c = c->next) {
319: if (strcmp(c->path, path) == 0)
320: return c;
321: }
322: c = port_new(path, MODE_MIDIMASK, hold);
323: if (c == NULL)
324: exit(1);
325: return c;
326: }
327:
1.1 ratchov 328: struct opt *
329: mkopt(char *path, struct dev *d,
330: int pmin, int pmax, int rmin, int rmax,
331: int mode, int vol, int mmc, int dup)
332: {
333: struct opt *o;
334:
1.33 ratchov 335: o = opt_new(d, path, pmin, pmax, rmin, rmax,
1.1 ratchov 336: MIDI_TO_ADATA(vol), mmc, dup, mode);
337: if (o == NULL)
1.26 ratchov 338: return NULL;
1.31 ratchov 339: dev_adjpar(d, o->mode, o->pmax, o->rmax);
1.1 ratchov 340: return o;
341: }
342:
1.34 ratchov 343: static void
344: dounveil(char *name, char *prefix, char *path_prefix)
345: {
346: size_t prefix_len;
347: char path[PATH_MAX];
348:
349: prefix_len = strlen(prefix);
350:
351: if (strncmp(name, prefix, prefix_len) != 0)
352: errx(1, "%s: unsupported device or port format", name);
353: snprintf(path, sizeof(path), "%s%s", path_prefix, name + prefix_len);
1.35 ! ratchov 354: if (unveil(path, "rw") == -1)
1.34 ratchov 355: err(1, "unveil");
356: }
357:
1.32 ratchov 358: static int
359: start_helper(int background)
360: {
1.34 ratchov 361: struct dev *d;
362: struct port *p;
1.32 ratchov 363: struct passwd *pw;
364: int s[2];
365: pid_t pid;
366:
367: if (geteuid() == 0) {
368: if ((pw = getpwnam(SNDIO_PRIV_USER)) == NULL)
369: errx(1, "unknown user %s", SNDIO_PRIV_USER);
370: } else
371: pw = NULL;
1.35 ! ratchov 372: if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == -1) {
1.32 ratchov 373: perror("socketpair");
374: return 0;
375: }
376: pid = fork();
377: if (pid == -1) {
378: log_puts("can't fork\n");
379: return 0;
380: }
381: if (pid == 0) {
382: setproctitle("helper");
383: close(s[0]);
384: if (fdpass_new(s[1], &helper_fileops) == NULL)
385: return 0;
386: if (background) {
387: log_flush();
388: log_level = 0;
1.35 ! ratchov 389: if (daemon(0, 0) == -1)
1.32 ratchov 390: err(1, "daemon");
391: }
392: if (pw != NULL) {
393: if (setgroups(1, &pw->pw_gid) ||
394: setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
395: setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
396: err(1, "cannot drop privileges");
397: }
1.34 ratchov 398: for (d = dev_list; d != NULL; d = d->next)
399: dounveil(d->path, "rsnd/", "/dev/audio");
400: for (p = port_list; p != NULL; p = p->next)
401: dounveil(p->path, "rmidi/", "/dev/rmidi");
1.35 ! ratchov 402: if (pledge("stdio sendfd rpath wpath", NULL) == -1)
1.32 ratchov 403: err(1, "pledge");
404: while (file_poll())
405: ; /* nothing */
406: exit(0);
407: } else {
408: close(s[1]);
409: if (fdpass_new(s[0], &worker_fileops) == NULL)
410: return 0;
411: }
412: return 1;
413: }
414:
415: static void
416: stop_helper(void)
417: {
418: if (fdpass_peer)
419: fdpass_close(fdpass_peer);
420: }
421:
1.1 ratchov 422: int
423: main(int argc, char **argv)
424: {
425: int c, background, unit;
426: int pmin, pmax, rmin, rmax;
1.25 ratchov 427: char base[SOCKPATH_MAX], path[SOCKPATH_MAX];
1.1 ratchov 428: unsigned int mode, dup, mmc, vol;
429: unsigned int hold, autovol, bufsz, round, rate;
430: const char *str;
431: struct aparams par;
432: struct dev *d;
433: struct port *p;
434: struct listen *l;
1.17 ratchov 435: struct passwd *pw;
1.25 ratchov 436: struct tcpaddr {
437: char *host;
438: struct tcpaddr *next;
439: } *tcpaddr_list, *ta;
1.1 ratchov 440:
441: atexit(log_flush);
442:
443: /*
444: * global options defaults
445: */
446: vol = 118;
447: dup = 1;
448: mmc = 0;
449: hold = 0;
450: autovol = 1;
451: bufsz = 0;
452: round = 0;
453: rate = DEFAULT_RATE;
454: unit = 0;
455: background = 1;
456: pmin = 0;
457: pmax = 1;
458: rmin = 0;
459: rmax = 1;
460: aparams_init(&par);
461: mode = MODE_PLAY | MODE_REC;
1.25 ratchov 462: tcpaddr_list = NULL;
1.1 ratchov 463:
1.15 ratchov 464: while ((c = getopt(argc, argv, "a:b:c:C:de:f:j:L:m:q:r:s:t:U:v:w:x:z:")) != -1) {
1.1 ratchov 465: switch (c) {
466: case 'd':
467: log_level++;
468: background = 0;
469: break;
470: case 'U':
471: unit = strtonum(optarg, 0, 15, &str);
472: if (str)
473: errx(1, "%s: unit number is %s", optarg, str);
474: break;
475: case 'L':
1.25 ratchov 476: ta = xmalloc(sizeof(struct tcpaddr));
477: ta->host = optarg;
478: ta->next = tcpaddr_list;
479: tcpaddr_list = ta;
1.1 ratchov 480: break;
481: case 'm':
482: mode = opt_mode();
483: break;
484: case 'j':
485: dup = opt_onoff();
486: break;
487: case 't':
488: mmc = opt_mmc();
489: break;
490: case 'c':
491: opt_ch(&pmin, &pmax);
492: break;
493: case 'C':
494: opt_ch(&rmin, &rmax);
495: break;
496: case 'e':
497: opt_enc(&par);
498: break;
499: case 'r':
500: rate = strtonum(optarg, RATE_MIN, RATE_MAX, &str);
501: if (str)
502: errx(1, "%s: rate is %s", optarg, str);
503: break;
504: case 'v':
505: vol = strtonum(optarg, 0, MIDI_MAXCTL, &str);
506: if (str)
507: errx(1, "%s: volume is %s", optarg, str);
508: break;
509: case 's':
510: if ((d = dev_list) == NULL) {
1.28 ratchov 511: d = mkdev(DEFAULT_DEV, &par, 0, bufsz, round,
512: rate, hold, autovol);
1.1 ratchov 513: }
1.26 ratchov 514: if (mkopt(optarg, d, pmin, pmax, rmin, rmax,
515: mode, vol, mmc, dup) == NULL)
516: return 1;
1.1 ratchov 517: break;
518: case 'q':
1.27 ratchov 519: mkport(optarg, hold);
1.1 ratchov 520: break;
521: case 'a':
522: hold = opt_onoff();
523: break;
524: case 'w':
525: autovol = opt_onoff();
526: break;
527: case 'b':
528: bufsz = strtonum(optarg, 1, RATE_MAX, &str);
529: if (str)
530: errx(1, "%s: buffer size is %s", optarg, str);
531: break;
532: case 'z':
533: round = strtonum(optarg, 1, SHRT_MAX, &str);
534: if (str)
535: errx(1, "%s: block size is %s", optarg, str);
536: break;
537: case 'f':
1.28 ratchov 538: mkdev(optarg, &par, 0, bufsz, round,
539: rate, hold, autovol);
1.1 ratchov 540: break;
541: default:
542: fputs(usagestr, stderr);
543: return 1;
544: }
545: }
546: argc -= optind;
547: argv += optind;
548: if (argc > 0) {
549: fputs(usagestr, stderr);
550: return 1;
551: }
552: if (dev_list == NULL)
553: mkdev(DEFAULT_DEV, &par, 0, bufsz, round, rate, hold, autovol);
554: for (d = dev_list; d != NULL; d = d->next) {
1.33 ratchov 555: if (opt_byname(d, "default"))
1.1 ratchov 556: continue;
1.26 ratchov 557: if (mkopt("default", d, pmin, pmax, rmin, rmax,
558: mode, vol, mmc, dup) == NULL)
559: return 1;
1.1 ratchov 560: }
1.17 ratchov 561:
562: setsig();
563: filelist_init();
564:
1.32 ratchov 565: if (!start_helper(background))
566: return 1;
567:
568: if (geteuid() == 0) {
1.20 ratchov 569: if ((pw = getpwnam(SNDIO_USER)) == NULL)
570: errx(1, "unknown user %s", SNDIO_USER);
1.32 ratchov 571: } else
572: pw = NULL;
573: getbasepath(base);
574: snprintf(path, SOCKPATH_MAX, "%s/" SOCKPATH_FILE "%u", base, unit);
575: if (!listen_new_un(path))
576: return 1;
577: for (ta = tcpaddr_list; ta != NULL; ta = ta->next) {
578: if (!listen_new_tcp(ta->host, AUCAT_PORT + unit))
579: return 1;
1.20 ratchov 580: }
1.32 ratchov 581: for (l = listen_list; l != NULL; l = l->next) {
582: if (!listen_init(l))
583: return 1;
1.13 ratchov 584: }
1.32 ratchov 585: midi_init();
586: for (p = port_list; p != NULL; p = p->next) {
587: if (!port_init(p))
588: return 1;
1.1 ratchov 589: }
1.32 ratchov 590: for (d = dev_list; d != NULL; d = d->next) {
591: if (!dev_init(d))
1.1 ratchov 592: return 1;
1.32 ratchov 593: }
594: if (background) {
595: log_flush();
596: log_level = 0;
1.35 ! ratchov 597: if (daemon(0, 0) == -1)
1.32 ratchov 598: err(1, "daemon");
599: }
600: if (pw != NULL) {
1.35 ! ratchov 601: if (setpriority(PRIO_PROCESS, 0, SNDIO_PRIO) == -1)
1.32 ratchov 602: err(1, "setpriority");
1.35 ! ratchov 603: if (chroot(pw->pw_dir) == -1 || chdir("/") == -1)
1.32 ratchov 604: err(1, "cannot chroot to %s", pw->pw_dir);
1.35 ! ratchov 605: if (setgroups(1, &pw->pw_gid) == -1 ||
! 606: setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
! 607: setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1 )
1.32 ratchov 608: err(1, "cannot drop privileges");
609: }
610: if (tcpaddr_list) {
611: if (pledge("stdio audio recvfd unix inet", NULL) == -1)
1.22 ratchov 612: err(1, "pledge");
1.18 ratchov 613: } else {
1.32 ratchov 614: if (pledge("stdio audio recvfd unix", NULL) == -1)
615: err(1, "pledge");
616: }
617: for (;;) {
618: if (quit_flag)
619: break;
620: if (!fdpass_peer)
621: break;
622: if (!file_poll())
623: break;
624: }
625: stop_helper();
626: while (listen_list != NULL)
627: listen_close(listen_list);
628: while (sock_list != NULL)
629: sock_close(sock_list);
630: for (d = dev_list; d != NULL; d = d->next)
631: dev_done(d);
632: for (p = port_list; p != NULL; p = p->next)
633: port_done(p);
634: while (file_poll())
635: ; /* nothing */
636: midi_done();
1.18 ratchov 637:
1.1 ratchov 638: while (dev_list)
639: dev_del(dev_list);
640: while (port_list)
641: port_del(port_list);
1.25 ratchov 642: while (tcpaddr_list) {
643: ta = tcpaddr_list;
644: tcpaddr_list = ta->next;
645: xfree(ta);
646: }
1.1 ratchov 647: filelist_done();
648: unsetsig();
649: return 0;
650: }