xref: /openbsd/usr.bin/sndiod/sndiod.c (revision 9b7c3dbb)
1 /*	$OpenBSD: sndiod.c,v 1.31 2016/03/23 06:16:35 ratchov Exp $	*/
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>
20 #include <sys/socket.h>
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"
38 #include "fdpass.h"
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 /*
54  * privileged user name
55  */
56 #ifndef SNDIO_PRIV_USER
57 #define SNDIO_PRIV_USER	"_sndiop"
58 #endif
59 
60 /*
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
85 #define DEFAULT_BUFSZ	7680
86 #endif
87 
88 /*
89  * default device in server mode
90  */
91 #ifndef DEFAULT_DEV
92 #define DEFAULT_DEV "rsnd/0"
93 #endif
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);
102 void getbasepath(char *);
103 void setsig(void);
104 void unsetsig(void);
105 struct dev *mkdev(char *, struct aparams *,
106     int, int, int, int, int, int);
107 struct port *mkport(char *, int);
108 struct opt *mkopt(char *, struct dev *,
109     int, int, int, int, int, int, int, int);
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 
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 
202 unsigned int
203 opt_mode(void)
204 {
205 	unsigned int mode = 0;
206 	char *p = optarg;
207 
208 	for (;;) {
209 		if (getword("play", &p)) {
210 			mode |= MODE_PLAY;
211 		} else if (getword("rec", &p)) {
212 			mode |= MODE_REC;
213 		} else if (getword("mon", &p)) {
214 			mode |= MODE_MON;
215 		} else if (getword("midi", &p)) {
216 			mode |= MODE_MIDIMASK;
217 		} else
218 			errx(1, "%s: bad mode", optarg);
219 		if (*p == '\0')
220 			break;
221 		p++;
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;
237 	if (sigaction(SIGINT, &sa, NULL) < 0)
238 		err(1, "sigaction(int) failed");
239 	if (sigaction(SIGTERM, &sa, NULL) < 0)
240 		err(1, "sigaction(term) failed");
241 	if (sigaction(SIGHUP, &sa, NULL) < 0)
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;
253 	if (sigaction(SIGHUP, &sa, NULL) < 0)
254 		err(1, "unsetsig(hup): sigaction failed");
255 	if (sigaction(SIGTERM, &sa, NULL) < 0)
256 		err(1, "unsetsig(term): sigaction failed");
257 	if (sigaction(SIGINT, &sa, NULL) < 0)
258 		err(1, "unsetsig(int): sigaction failed");
259 }
260 
261 void
262 getbasepath(char *base)
263 {
264 	uid_t uid;
265 	struct stat sb;
266 	mode_t mask, omask;
267 
268 	uid = geteuid();
269 	if (uid == 0) {
270 		mask = 022;
271 		snprintf(base, SOCKPATH_MAX, SOCKPATH_DIR);
272 	} else {
273 		mask = 077;
274 		snprintf(base, SOCKPATH_MAX, SOCKPATH_DIR "-%u", uid);
275 	}
276 	omask = umask(mask);
277 	if (mkdir(base, 0777) < 0) {
278 		if (errno != EEXIST)
279 			err(1, "mkdir(\"%s\")", base);
280 	}
281 	umask(omask);
282 	if (stat(base, &sb) < 0)
283 		err(1, "stat(\"%s\")", base);
284 	if (!S_ISDIR(sb.st_mode))
285 		errx(1, "%s is not a directory", base);
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 
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 
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 
335 	o = opt_new(path, d, pmin, pmax, rmin, rmax,
336 	    MIDI_TO_ADATA(vol), mmc, dup, mode);
337 	if (o == NULL)
338 		return NULL;
339 	dev_adjpar(d, o->mode, o->pmax, o->rmax);
340 	return o;
341 }
342 
343 int
344 main(int argc, char **argv)
345 {
346 	int c, background, unit;
347 	int pmin, pmax, rmin, rmax;
348 	char base[SOCKPATH_MAX], path[SOCKPATH_MAX];
349 	unsigned int mode, dup, mmc, vol;
350 	unsigned int hold, autovol, bufsz, round, rate;
351 	const char *str;
352 	struct aparams par;
353 	struct dev *d;
354 	struct port *p;
355 	struct listen *l;
356 	struct passwd *pw;
357 	struct tcpaddr {
358 		char *host;
359 		struct tcpaddr *next;
360 	} *tcpaddr_list, *ta;
361 	int s[2];
362 	pid_t pid;
363 	uid_t euid, hpw_uid, wpw_uid;
364 	gid_t hpw_gid, wpw_gid;
365 	char *wpw_dir;
366 
367 	atexit(log_flush);
368 
369 	/*
370 	 * global options defaults
371 	 */
372 	vol = 118;
373 	dup = 1;
374 	mmc = 0;
375 	hold = 0;
376 	autovol = 1;
377 	bufsz = 0;
378 	round = 0;
379 	rate = DEFAULT_RATE;
380 	unit = 0;
381 	background = 1;
382 	pmin = 0;
383 	pmax = 1;
384 	rmin = 0;
385 	rmax = 1;
386 	aparams_init(&par);
387 	mode = MODE_PLAY | MODE_REC;
388 	tcpaddr_list = NULL;
389 
390 	while ((c = getopt(argc, argv, "a:b:c:C:de:f:j:L:m:q:r:s:t:U:v:w:x:z:")) != -1) {
391 		switch (c) {
392 		case 'd':
393 			log_level++;
394 			background = 0;
395 			break;
396 		case 'U':
397 			unit = strtonum(optarg, 0, 15, &str);
398 			if (str)
399 				errx(1, "%s: unit number is %s", optarg, str);
400 			break;
401 		case 'L':
402 			ta = xmalloc(sizeof(struct tcpaddr));
403 			ta->host = optarg;
404 			ta->next = tcpaddr_list;
405 			tcpaddr_list = ta;
406 			break;
407 		case 'm':
408 			mode = opt_mode();
409 			break;
410 		case 'j':
411 			dup = opt_onoff();
412 			break;
413 		case 't':
414 			mmc = opt_mmc();
415 			break;
416 		case 'c':
417 			opt_ch(&pmin, &pmax);
418 			break;
419 		case 'C':
420 			opt_ch(&rmin, &rmax);
421 			break;
422 		case 'e':
423 			opt_enc(&par);
424 			break;
425 		case 'r':
426 			rate = strtonum(optarg, RATE_MIN, RATE_MAX, &str);
427 			if (str)
428 				errx(1, "%s: rate is %s", optarg, str);
429 			break;
430 		case 'v':
431 			vol = strtonum(optarg, 0, MIDI_MAXCTL, &str);
432 			if (str)
433 				errx(1, "%s: volume is %s", optarg, str);
434 			break;
435 		case 's':
436 			if ((d = dev_list) == NULL) {
437 				d = mkdev(DEFAULT_DEV, &par, 0, bufsz, round,
438 				    rate, hold, autovol);
439 			}
440 			if (mkopt(optarg, d, pmin, pmax, rmin, rmax,
441 				mode, vol, mmc, dup) == NULL)
442 				return 1;
443 			break;
444 		case 'q':
445 			mkport(optarg, hold);
446 			break;
447 		case 'a':
448 			hold = opt_onoff();
449 			break;
450 		case 'w':
451 			autovol = opt_onoff();
452 			break;
453 		case 'b':
454 			bufsz = strtonum(optarg, 1, RATE_MAX, &str);
455 			if (str)
456 				errx(1, "%s: buffer size is %s", optarg, str);
457 			break;
458 		case 'z':
459 			round = strtonum(optarg, 1, SHRT_MAX, &str);
460 			if (str)
461 				errx(1, "%s: block size is %s", optarg, str);
462 			break;
463 		case 'f':
464 			mkdev(optarg, &par, 0, bufsz, round,
465 			    rate, hold, autovol);
466 			break;
467 		default:
468 			fputs(usagestr, stderr);
469 			return 1;
470 		}
471 	}
472 	argc -= optind;
473 	argv += optind;
474 	if (argc > 0) {
475 		fputs(usagestr, stderr);
476 		return 1;
477 	}
478 	if (dev_list == NULL)
479 		mkdev(DEFAULT_DEV, &par, 0, bufsz, round, rate, hold, autovol);
480 	for (d = dev_list; d != NULL; d = d->next) {
481 		if (opt_byname("default", d->num))
482 			continue;
483 		if (mkopt("default", d, pmin, pmax, rmin, rmax,
484 			mode, vol, mmc, dup) == NULL)
485 			return 1;
486 	}
487 
488 	setsig();
489 	filelist_init();
490 
491 	euid = geteuid();
492 	if (euid == 0) {
493 		if ((pw = getpwnam(SNDIO_PRIV_USER)) == NULL)
494 			errx(1, "unknown user %s", SNDIO_PRIV_USER);
495 		hpw_uid = pw->pw_uid;
496 		hpw_gid = pw->pw_gid;
497 		if ((pw = getpwnam(SNDIO_USER)) == NULL)
498 			errx(1, "unknown user %s", SNDIO_USER);
499 		wpw_uid = pw->pw_uid;
500 		wpw_gid = pw->pw_gid;
501 		wpw_dir = xstrdup(pw->pw_dir);
502 	} else {
503 		hpw_uid = wpw_uid = hpw_gid = wpw_gid = 0xdeadbeef;
504 		wpw_dir = NULL;
505 	}
506 
507 	/* start subprocesses */
508 
509 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) < 0) {
510 		perror("socketpair");
511 		return 1;
512 	}
513 	pid = fork();
514 	if (pid	== -1) {
515 		log_puts("can't fork\n");
516 		return 1;
517 	}
518 	if (pid == 0) {
519 		setproctitle("helper");
520 		close(s[0]);
521 		if (fdpass_new(s[1], &helper_fileops) == NULL)
522 			return 1;
523 		if (background) {
524 			log_flush();
525 			log_level = 0;
526 			if (daemon(0, 0) < 0)
527 				err(1, "daemon");
528 		}
529 		if (euid == 0) {
530 			if (setgroups(1, &hpw_gid) ||
531 			    setresgid(hpw_gid, hpw_gid, hpw_gid) ||
532 			    setresuid(hpw_uid, hpw_uid, hpw_uid))
533 				err(1, "cannot drop privileges");
534 		}
535 		if (pledge("stdio sendfd rpath wpath", NULL) < 0)
536 			err(1, "pledge");
537 		while (file_poll())
538 			; /* nothing */
539 	} else {
540 		close(s[1]);
541 		if (fdpass_new(s[0], &worker_fileops) == NULL)
542 			return 1;
543 
544 		getbasepath(base);
545 		snprintf(path,
546 		    SOCKPATH_MAX, "%s/" SOCKPATH_FILE "%u",
547 		    base, unit);
548 		if (!listen_new_un(path))
549 			return 1;
550 		for (ta = tcpaddr_list; ta != NULL; ta = ta->next) {
551 			if (!listen_new_tcp(ta->host, AUCAT_PORT + unit))
552 				return 1;
553 		}
554 		for (l = listen_list; l != NULL; l = l->next) {
555 			if (!listen_init(l))
556 				return 1;
557 		}
558 
559 		midi_init();
560 		for (p = port_list; p != NULL; p = p->next) {
561 			if (!port_init(p))
562 				return 1;
563 		}
564 		for (d = dev_list; d != NULL; d = d->next) {
565 			if (!dev_init(d))
566 				return 1;
567 		}
568 		if (background) {
569 			log_flush();
570 			log_level = 0;
571 			if (daemon(0, 0) < 0)
572 				err(1, "daemon");
573 		}
574 		if (euid == 0) {
575 			if (setpriority(PRIO_PROCESS, 0, SNDIO_PRIO) < 0)
576 				err(1, "setpriority");
577 			if (chroot(wpw_dir) != 0 || chdir("/") != 0)
578 				err(1, "cannot chroot to %s", wpw_dir);
579 			if (setgroups(1, &wpw_gid) ||
580 			    setresgid(wpw_gid, wpw_gid, wpw_gid) ||
581 			    setresuid(wpw_uid, wpw_uid, wpw_uid))
582 				err(1, "cannot drop privileges");
583 		}
584 		if (tcpaddr_list) {
585 			if (pledge("stdio audio recvfd unix inet", NULL) == -1)
586 				err(1, "pledge");
587 		} else {
588 			if (pledge("stdio audio recvfd unix", NULL) == -1)
589 				err(1, "pledge");
590 		}
591 		for (;;) {
592 			if (quit_flag)
593 				break;
594 			if (!fdpass_peer)
595 				break;
596 			if (!file_poll())
597 				break;
598 		}
599 		if (fdpass_peer)
600 			fdpass_close(fdpass_peer);
601 		while (listen_list != NULL)
602 			listen_close(listen_list);
603 		while (sock_list != NULL)
604 			sock_close(sock_list);
605 		for (d = dev_list; d != NULL; d = d->next)
606 			dev_done(d);
607 		for (p = port_list; p != NULL; p = p->next)
608 			port_done(p);
609 		while (file_poll())
610 			; /* nothing */
611 		midi_done();
612 	}
613 	while (opt_list != NULL)
614 		opt_del(opt_list);
615 	while (dev_list)
616 		dev_del(dev_list);
617 	while (port_list)
618 		port_del(port_list);
619 	while (tcpaddr_list) {
620 		ta = tcpaddr_list;
621 		tcpaddr_list = ta->next;
622 		xfree(ta);
623 	}
624 	filelist_done();
625 	unsetsig();
626 	return 0;
627 }
628