xref: /openbsd-src/lib/libsndio/aucat.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /*	$OpenBSD: aucat.c,v 1.61 2013/12/20 08:51:28 ratchov Exp $	*/
2 /*
3  * Copyright (c) 2008 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 
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <sys/stat.h>
21 #include <sys/un.h>
22 
23 #include <netinet/in.h>
24 #include <netinet/tcp.h>
25 #include <netdb.h>
26 
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <limits.h>
30 #include <poll.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 
36 #include "aucat.h"
37 #include "debug.h"
38 
39 
40 /*
41  * read a message, return 0 if not completed
42  */
43 int
44 _aucat_rmsg(struct aucat *hdl, int *eof)
45 {
46 	ssize_t n;
47 	unsigned char *data;
48 
49 	if (hdl->rstate != RSTATE_MSG) {
50 		DPRINTF("_aucat_rmsg: bad state\n");
51 		abort();
52 	}
53 	while (hdl->rtodo > 0) {
54 		data = (unsigned char *)&hdl->rmsg;
55 		data += sizeof(struct amsg) - hdl->rtodo;
56 		while ((n = read(hdl->fd, data, hdl->rtodo)) < 0) {
57 			if (errno == EINTR)
58 				continue;
59 			if (errno != EAGAIN) {
60 				*eof = 1;
61 				DPERROR("_aucat_rmsg: read");
62 			}
63 			return 0;
64 		}
65 		if (n == 0) {
66 			DPRINTF("_aucat_rmsg: eof\n");
67 			*eof = 1;
68 			return 0;
69 		}
70 		hdl->rtodo -= n;
71 	}
72 	if (ntohl(hdl->rmsg.cmd) == AMSG_DATA) {
73 		hdl->rtodo = ntohl(hdl->rmsg.u.data.size);
74 		hdl->rstate = RSTATE_DATA;
75 	} else {
76 		hdl->rtodo = sizeof(struct amsg);
77 		hdl->rstate = RSTATE_MSG;
78 	}
79 	return 1;
80 }
81 
82 /*
83  * write a message, return 0 if not completed
84  */
85 int
86 _aucat_wmsg(struct aucat *hdl, int *eof)
87 {
88 	ssize_t n;
89 	unsigned char *data;
90 
91 	if (hdl->wstate == WSTATE_IDLE)
92 		hdl->wstate = WSTATE_MSG;
93 		hdl->wtodo = sizeof(struct amsg);
94 	if (hdl->wstate != WSTATE_MSG) {
95 		DPRINTF("_aucat_wmsg: bad state\n");
96 		abort();
97 	}
98 	while (hdl->wtodo > 0) {
99 		data = (unsigned char *)&hdl->wmsg;
100 		data += sizeof(struct amsg) - hdl->wtodo;
101 		while ((n = write(hdl->fd, data, hdl->wtodo)) < 0) {
102 			if (errno == EINTR)
103 				continue;
104 			if (errno != EAGAIN) {
105 				*eof = 1;
106 				DPERROR("_aucat_wmsg: write");
107 			}
108 			return 0;
109 		}
110 		hdl->wtodo -= n;
111 	}
112 	if (ntohl(hdl->wmsg.cmd) == AMSG_DATA) {
113 		hdl->wtodo = ntohl(hdl->wmsg.u.data.size);
114 		hdl->wstate = WSTATE_DATA;
115 	} else {
116 		hdl->wtodo = 0xdeadbeef;
117 		hdl->wstate = WSTATE_IDLE;
118 	}
119 	return 1;
120 }
121 
122 size_t
123 _aucat_rdata(struct aucat *hdl, void *buf, size_t len, int *eof)
124 {
125 	ssize_t n;
126 
127 	if (hdl->rstate != RSTATE_DATA) {
128 		DPRINTF("_aucat_rdata: bad state\n");
129 		abort();
130 	}
131 	if (len > hdl->rtodo)
132 		len = hdl->rtodo;
133 	while ((n = read(hdl->fd, buf, len)) < 0) {
134 		if (errno == EINTR)
135 			continue;
136 		if (errno != EAGAIN) {
137 			*eof = 1;
138 			DPERROR("_aucat_rdata: read");
139 		}
140 		return 0;
141 	}
142 	if (n == 0) {
143 		DPRINTF("_aucat_rdata: eof\n");
144 		*eof = 1;
145 		return 0;
146 	}
147 	hdl->rtodo -= n;
148 	if (hdl->rtodo == 0) {
149 		hdl->rstate = RSTATE_MSG;
150 		hdl->rtodo = sizeof(struct amsg);
151 	}
152 	DPRINTFN(2, "_aucat_rdata: read: n = %zd\n", n);
153 	return n;
154 }
155 
156 size_t
157 _aucat_wdata(struct aucat *hdl, const void *buf, size_t len,
158    unsigned int wbpf, int *eof)
159 {
160 	ssize_t n;
161 	size_t datasize;
162 
163 	switch (hdl->wstate) {
164 	case WSTATE_IDLE:
165 		datasize = len;
166 		if (datasize > AMSG_DATAMAX)
167 			datasize = AMSG_DATAMAX;
168 		datasize -= datasize % wbpf;
169 		if (datasize == 0)
170 			datasize = wbpf;
171 		hdl->wmsg.cmd = htonl(AMSG_DATA);
172 		hdl->wmsg.u.data.size = htonl(datasize);
173 		hdl->wtodo = sizeof(struct amsg);
174 		hdl->wstate = WSTATE_MSG;
175 		/* FALLTHROUGH */
176 	case WSTATE_MSG:
177 		if (!_aucat_wmsg(hdl, eof))
178 			return 0;
179 	}
180 	if (len > hdl->wtodo)
181 		len = hdl->wtodo;
182 	if (len == 0) {
183 		DPRINTF("_aucat_wdata: len == 0\n");
184 		abort();
185 	}
186 	while ((n = write(hdl->fd, buf, len)) < 0) {
187 		if (errno == EINTR)
188 			continue;
189 		if (errno != EAGAIN) {
190 			*eof = 1;
191 			DPERROR("_aucat_wdata: write");
192 		}
193 		return 0;
194 	}
195 	DPRINTFN(2, "_aucat_wdata: write: n = %zd\n", n);
196 	hdl->wtodo -= n;
197 	if (hdl->wtodo == 0) {
198 		hdl->wstate = WSTATE_IDLE;
199 		hdl->wtodo = 0xdeadbeef;
200 	}
201 	return n;
202 }
203 
204 static int
205 aucat_mkcookie(unsigned char *cookie)
206 {
207 	struct stat sb;
208 	char buf[PATH_MAX], tmp[PATH_MAX], *path;
209 	ssize_t len;
210 	int fd;
211 
212 	/*
213 	 * try to load the cookie
214 	 */
215 	path = issetugid() ? NULL : getenv("AUCAT_COOKIE");
216 	if (path == NULL) {
217 		path = issetugid() ? NULL : getenv("HOME");
218 		if (path == NULL)
219 			goto bad_gen;
220 		snprintf(buf, PATH_MAX, "%s/.aucat_cookie", path);
221 		path = buf;
222 	}
223 	fd = open(path, O_RDONLY);
224 	if (fd < 0) {
225 		if (errno != ENOENT)
226 			DPERROR(path);
227 		goto bad_gen;
228 	}
229 	if (fstat(fd, &sb) < 0) {
230 		DPERROR(path);
231 		goto bad_close;
232 	}
233 	if (sb.st_mode & 0077) {
234 		DPRINTF("%s has wrong permissions\n", path);
235 		goto bad_close;
236 	}
237 	len = read(fd, cookie, AMSG_COOKIELEN);
238 	if (len < 0) {
239 		DPERROR(path);
240 		goto bad_close;
241 	}
242 	if (len != AMSG_COOKIELEN) {
243 		DPRINTF("%s: short read\n", path);
244 		goto bad_close;
245 	}
246 	close(fd);
247 	return 1;
248 bad_close:
249 	close(fd);
250 bad_gen:
251 	/*
252 	 * generate a new cookie
253 	 */
254 	arc4random_buf(cookie, AMSG_COOKIELEN);
255 
256 	/*
257 	 * try to save the cookie
258 	 */
259 	if (path == NULL)
260 		return 1;
261 	if (strlcpy(tmp, path, PATH_MAX) >= PATH_MAX ||
262 	    strlcat(tmp, ".XXXXXXXX", PATH_MAX) >= PATH_MAX) {
263 		DPRINTF("%s: too long\n", path);
264 		return 1;
265 	}
266 	fd = mkstemp(tmp);
267 	if (fd < 0) {
268 		DPERROR(tmp);
269 		return 1;
270 	}
271 	if (write(fd, cookie, AMSG_COOKIELEN) < 0) {
272 		DPERROR(tmp);
273 		unlink(tmp);
274 		close(fd);
275 		return 1;
276 	}
277 	close(fd);
278 	if (rename(tmp, path) < 0) {
279 		DPERROR(tmp);
280 		unlink(tmp);
281 	}
282 	return 1;
283 }
284 
285 static int
286 aucat_connect_tcp(struct aucat *hdl, char *host, unsigned int unit)
287 {
288 	int s, error, opt;
289 	struct addrinfo *ailist, *ai, aihints;
290 	char serv[NI_MAXSERV];
291 
292 	snprintf(serv, sizeof(serv), "%u", unit + AUCAT_PORT);
293 	memset(&aihints, 0, sizeof(struct addrinfo));
294 	aihints.ai_socktype = SOCK_STREAM;
295 	aihints.ai_protocol = IPPROTO_TCP;
296 	error = getaddrinfo(host, serv, &aihints, &ailist);
297 	if (error) {
298 		DPRINTF("%s: %s\n", host, gai_strerror(error));
299 		return 0;
300 	}
301 	s = -1;
302 	for (ai = ailist; ai != NULL; ai = ai->ai_next) {
303 		s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
304 		if (s < 0) {
305 			DPERROR("socket");
306 			continue;
307 		}
308 	restart:
309 		if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
310 			if (errno == EINTR)
311 				goto restart;
312 			DPERROR("connect");
313 			close(s);
314 			s = -1;
315 			continue;
316 		}
317 		break;
318 	}
319 	freeaddrinfo(ailist);
320 	if (s < 0)
321 		return 0;
322 	opt = 1;
323 	if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(int)) < 0) {
324 		DPERROR("setsockopt");
325 		close(s);
326 		return 0;
327 	}
328 	hdl->fd = s;
329 	return 1;
330 }
331 
332 static int
333 aucat_connect_un(struct aucat *hdl, unsigned int unit)
334 {
335 	struct sockaddr_un ca;
336 	socklen_t len = sizeof(struct sockaddr_un);
337 	uid_t uid;
338 	int s;
339 
340 	uid = geteuid();
341 	snprintf(ca.sun_path, sizeof(ca.sun_path),
342 	    "/tmp/aucat-%u/%s%u", uid, AUCAT_PATH, unit);
343 	ca.sun_family = AF_UNIX;
344 	s = socket(AF_UNIX, SOCK_STREAM, 0);
345 	if (s < 0)
346 		return 0;
347 	while (connect(s, (struct sockaddr *)&ca, len) < 0) {
348 		if (errno == EINTR)
349 			continue;
350 		DPERROR(ca.sun_path);
351 		/* try shared server */
352 		snprintf(ca.sun_path, sizeof(ca.sun_path),
353 		    "/tmp/aucat/%s%u", AUCAT_PATH, unit);
354 		while (connect(s, (struct sockaddr *)&ca, len) < 0) {
355 			if (errno == EINTR)
356 				continue;
357 			DPERROR(ca.sun_path);
358 			close(s);
359 			return 0;
360 		}
361 		break;
362 	}
363 	hdl->fd = s;
364 	DPRINTFN(2, "%s: connected\n", ca.sun_path);
365 	return 1;
366 }
367 
368 static const char *
369 parsedev(const char *str, unsigned int *rval)
370 {
371 	const char *p = str;
372 	unsigned int val;
373 
374 	for (val = 0; *p >= '0' && *p <= '9'; p++) {
375 		val = 10 * val + (*p - '0');
376 		if (val >= 16) {
377 			DPRINTF("%s: number too large\n", str);
378 			return NULL;
379 		}
380 	}
381 	if (p == str) {
382 		DPRINTF("%s: number expected\n", str);
383 		return NULL;
384 	}
385 	*rval = val;
386 	return p;
387 }
388 
389 static const char *
390 parsestr(const char *str, char *rstr, unsigned int max)
391 {
392 	const char *p = str;
393 
394 	while (*p != '\0' && *p != ',' && *p != '/') {
395 		if (--max == 0) {
396 			DPRINTF("%s: string too long\n", str);
397 			return NULL;
398 		}
399 		*rstr++ = *p++;
400 	}
401 	if (str == p) {
402 		DPRINTF("%s: string expected\n", str);
403 		return NULL;
404 	}
405 	*rstr = '\0';
406 	return p;
407 }
408 
409 int
410 _aucat_open(struct aucat *hdl, const char *str, unsigned int mode,
411     unsigned int type)
412 {
413 	extern char *__progname;
414 	int eof;
415 	char host[NI_MAXHOST], opt[AMSG_OPTMAX];
416 	const char *p = str;
417 	unsigned int unit, devnum;
418 
419 	if (*p == '@') {
420 		p = parsestr(++p, host, NI_MAXHOST);
421 		if (p == NULL)
422 			return 0;
423 	} else
424 		*host = '\0';
425 	if (*p == ',') {
426 		p = parsedev(++p, &unit);
427 		if (p == NULL)
428 			return 0;
429 	} else
430 		unit = 0;
431 	if (*p != '/' && *p != ':') {
432 		DPRINTF("%s: '/' expected\n", str);
433 		return 0;
434 	}
435 	p = parsedev(++p, &devnum);
436 	if (p == NULL)
437 		return 0;
438 	if (*p == '.') {
439 		p = parsestr(++p, opt, AMSG_OPTMAX);
440 		if (p == NULL)
441 			return 0;
442 	} else
443 		strlcpy(opt, "default", AMSG_OPTMAX);
444 	if (*p != '\0') {
445 		DPRINTF("%s: junk at end of dev name\n", p);
446 		return 0;
447 	}
448 	devnum += type * 16; /* XXX */
449 	DPRINTFN(2, "_aucat_open: host=%s unit=%u devnum=%u opt=%s\n",
450 	    host, unit, devnum, opt);
451 	if (host[0] != '\0') {
452 		if (!aucat_connect_tcp(hdl, host, unit))
453 			return 0;
454 	} else {
455 		if (!aucat_connect_un(hdl, unit))
456 			return 0;
457 	}
458 	if (fcntl(hdl->fd, F_SETFD, FD_CLOEXEC) < 0) {
459 		DPERROR("FD_CLOEXEC");
460 		goto bad_connect;
461 	}
462 	hdl->rstate = RSTATE_MSG;
463 	hdl->rtodo = sizeof(struct amsg);
464 	hdl->wstate = WSTATE_IDLE;
465 	hdl->wtodo = 0xdeadbeef;
466 	hdl->maxwrite = 0;
467 
468 	/*
469 	 * say hello to server
470 	 */
471 	AMSG_INIT(&hdl->wmsg);
472 	hdl->wmsg.cmd = htonl(AMSG_AUTH);
473 	if (!aucat_mkcookie(hdl->wmsg.u.auth.cookie))
474 		goto bad_connect;
475 	hdl->wtodo = sizeof(struct amsg);
476 	if (!_aucat_wmsg(hdl, &eof))
477 		goto bad_connect;
478 	AMSG_INIT(&hdl->wmsg);
479 	hdl->wmsg.cmd = htonl(AMSG_HELLO);
480 	hdl->wmsg.u.hello.version = AMSG_VERSION;
481 	hdl->wmsg.u.hello.mode = htons(mode);
482 	hdl->wmsg.u.hello.devnum = devnum;
483 	strlcpy(hdl->wmsg.u.hello.who, __progname,
484 	    sizeof(hdl->wmsg.u.hello.who));
485 	strlcpy(hdl->wmsg.u.hello.opt, opt,
486 	    sizeof(hdl->wmsg.u.hello.opt));
487 	hdl->wtodo = sizeof(struct amsg);
488 	if (!_aucat_wmsg(hdl, &eof))
489 		goto bad_connect;
490 	hdl->rtodo = sizeof(struct amsg);
491 	if (!_aucat_rmsg(hdl, &eof)) {
492 		DPRINTF("aucat_init: mode refused\n");
493 		goto bad_connect;
494 	}
495 	if (ntohl(hdl->rmsg.cmd) != AMSG_ACK) {
496 		DPRINTF("aucat_init: protocol err\n");
497 		goto bad_connect;
498 	}
499 	return 1;
500  bad_connect:
501 	while (close(hdl->fd) < 0 && errno == EINTR)
502 		; /* retry */
503 	return 0;
504 }
505 
506 void
507 _aucat_close(struct aucat *hdl, int eof)
508 {
509 	char dummy[1];
510 
511 	if (!eof) {
512 		AMSG_INIT(&hdl->wmsg);
513 		hdl->wmsg.cmd = htonl(AMSG_BYE);
514 		hdl->wtodo = sizeof(struct amsg);
515 		if (!_aucat_wmsg(hdl, &eof))
516 			goto bad_close;
517 		while (read(hdl->fd, dummy, 1) < 0 && errno == EINTR)
518 			; /* nothing */
519 	}
520  bad_close:
521 	while (close(hdl->fd) < 0 && errno == EINTR)
522 		; /* nothing */
523 }
524 
525 int
526 _aucat_setfl(struct aucat *hdl, int nbio, int *eof)
527 {
528 	if (fcntl(hdl->fd, F_SETFL, nbio ? O_NONBLOCK : 0) < 0) {
529 		DPERROR("_aucat_setfl: fcntl");
530 		*eof = 1;
531 		return 0;
532 	}
533 	return 1;
534 }
535 
536 int
537 _aucat_pollfd(struct aucat *hdl, struct pollfd *pfd, int events)
538 {
539 	if (hdl->rstate == RSTATE_MSG)
540 		events |= POLLIN;
541 	pfd->fd = hdl->fd;
542 	pfd->events = events;
543 	return 1;
544 }
545 
546 int
547 _aucat_revents(struct aucat *hdl, struct pollfd *pfd)
548 {
549 	int revents = pfd->revents;
550 
551 	DPRINTFN(2, "_aucat_revents: revents: %x\n", revents);
552 	return revents;
553 }
554