identd.c revision 1.11
1/*	$OpenBSD: identd.c,v 1.11 2013/04/23 01:46:39 dlg Exp $ */
2
3/*
4 * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20#include <sys/ioctl.h>
21#include <sys/socket.h>
22#include <sys/socketvar.h>
23#include <sys/sysctl.h>
24#include <sys/uio.h>
25
26#include <netinet/in.h>
27#include <netinet/ip_var.h>
28#include <netinet/tcp.h>
29#include <netinet/tcp_timer.h>
30#include <netinet/tcp_var.h>
31
32#include <netdb.h>
33
34#include <err.h>
35#include <ctype.h>
36#include <errno.h>
37#include <event.h>
38#include <fcntl.h>
39#include <pwd.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <syslog.h>
44#include <unistd.h>
45
46#define IDENTD_USER "_identd"
47
48#define DOTNOIDENT ".noident"
49
50#define TIMEOUT_MIN 4
51#define TIMEOUT_MAX 240
52#define TIMEOUT_DEFAULT 120
53#define INPUT_MAX 256
54
55enum ident_client_state {
56	S_BEGINNING = 0,
57	S_SERVER_PORT,
58	S_PRE_COMMA,
59	S_POST_COMMA,
60	S_CLIENT_PORT,
61	S_PRE_EOL,
62	S_EOL,
63
64	S_DEAD,
65	S_QUEUED
66};
67
68#define E_NONE		0
69#define E_NOUSER	1
70#define E_UNKNOWN	2
71#define E_HIDDEN	3
72
73struct ident_client {
74	struct {
75		/* from the socket */
76		struct sockaddr_storage ss;
77		socklen_t len;
78
79		/* from the request */
80		u_int port;
81	} client, server;
82	SIMPLEQ_ENTRY(ident_client) entry;
83	enum ident_client_state state;
84	struct event ev;
85	struct event tmo;
86	size_t rxbytes;
87
88	char *buf;
89	size_t buflen;
90	size_t bufoff;
91	uid_t uid;
92};
93
94struct ident_resolver {
95	SIMPLEQ_ENTRY(ident_resolver) entry;
96	char *buf;
97	size_t buflen;
98	u_int error;
99};
100
101struct identd_listener {
102	struct event ev, pause;
103};
104
105void	parent_rd(int, short, void *);
106void	parent_wr(int, short, void *);
107void	parent_noident(struct ident_resolver *, struct passwd *);
108
109void	child_rd(int, short, void *);
110void	child_wr(int, short, void *);
111
112void	identd_listen(const char *, const char *, int);
113void	identd_paused(int, short, void *);
114void	identd_accept(int, short, void *);
115int	identd_error(struct ident_client *, const char *);
116void	identd_close(struct ident_client *);
117void	identd_timeout(int, short, void *);
118void	identd_request(int, short, void *);
119enum ident_client_state
120	identd_parse(struct ident_client *, int);
121void	identd_resolving(int, short, void *);
122void	identd_response(int, short, void *);
123int	fetchuid(struct ident_client *);
124
125const char *gethost(struct sockaddr_storage *);
126const char *getport(struct sockaddr_storage *);
127
128struct loggers {
129	void (*err)(int, const char *, ...);
130	void (*errx)(int, const char *, ...);
131	void (*warn)(const char *, ...);
132	void (*warnx)(const char *, ...);
133	void (*info)(const char *, ...);
134	void (*debug)(const char *, ...);
135};
136
137const struct loggers conslogger = {
138	err,
139	errx,
140	warn,
141	warnx,
142	warnx, /* info */
143	warnx /* debug */
144};
145
146void	syslog_err(int, const char *, ...);
147void	syslog_errx(int, const char *, ...);
148void	syslog_warn(const char *, ...);
149void	syslog_warnx(const char *, ...);
150void	syslog_info(const char *, ...);
151void	syslog_debug(const char *, ...);
152void	syslog_vstrerror(int, int, const char *, va_list);
153
154const struct loggers syslogger = {
155	syslog_err,
156	syslog_errx,
157	syslog_warn,
158	syslog_warnx,
159	syslog_info,
160	syslog_debug
161};
162
163const struct loggers *logger = &conslogger;
164
165#define lerr(_e, _f...) logger->err((_e), _f)
166#define lerrx(_e, _f...) logger->errx((_e), _f)
167#define lwarn(_f...) logger->warn(_f)
168#define lwarnx(_f...) logger->warnx(_f)
169#define linfo(_f...) logger->info(_f)
170#define ldebug(_f...) logger->debug(_f)
171
172#define sa(_ss) ((struct sockaddr *)(_ss))
173
174__dead void
175usage(void)
176{
177	extern char *__progname;
178	fprintf(stderr, "usage: %s [-46d] [-l address] [-p port] "
179	    "[-t timeout]\n", __progname);
180	exit(1);
181}
182
183struct timeval timeout = { TIMEOUT_DEFAULT, 0 };
184int debug = 0;
185int noident = 0;
186int on = 1;
187
188struct event proc_rd, proc_wr;
189union {
190	struct {
191		SIMPLEQ_HEAD(, ident_resolver) replies;
192	} parent;
193	struct {
194		SIMPLEQ_HEAD(, ident_client) pushing, popping;
195	} child;
196} sc;
197
198int
199main(int argc, char *argv[])
200{
201	extern char *__progname;
202	const char *errstr = NULL;
203
204	int		 c;
205	struct passwd	*pw;
206
207	char *addr = NULL;
208	char *port = "auth";
209	int family = AF_UNSPEC;
210
211	int pair[2];
212	pid_t parent;
213	int sibling;
214
215	while ((c = getopt(argc, argv, "46dl:Np:t:")) != -1) {
216		switch (c) {
217		case '4':
218			family = AF_INET;
219			break;
220		case '6':
221			family = AF_INET6;
222			break;
223		case 'd':
224			debug = 1;
225			break;
226		case 'l':
227			addr = optarg;
228			break;
229		case 'N':
230			noident = 1;
231			break;
232		case 'p':
233			port = optarg;
234			break;
235		case 't':
236			timeout.tv_sec = strtonum(optarg,
237			    TIMEOUT_MIN, TIMEOUT_MAX, &errstr);
238			if (errstr != NULL)
239				errx(1, "timeout %s is %s", optarg, errstr);
240			break;
241		default:
242			usage();
243			/* NOTREACHED */
244		}
245	}
246
247	argc -= optind;
248	argv += optind;
249
250	if (argc != 0)
251		usage();
252
253	if (geteuid() != 0)
254		errx(1, "need root privileges");
255
256	if (socketpair(AF_UNIX, SOCK_SEQPACKET, PF_UNSPEC, pair) == -1)
257		err(1, "socketpair");
258
259	pw = getpwnam(IDENTD_USER);
260	if (pw == NULL)
261		err(1, "no %s user", IDENTD_USER);
262
263	if (!debug && daemon(1, 0) == -1)
264		err(1, "daemon");
265
266	parent = fork();
267	switch (parent) {
268	case -1:
269		lerr(1, "fork");
270
271	case 0:
272		/* child */
273		setproctitle("listener");
274		close(pair[1]);
275		sibling = pair[0];
276		break;
277
278	default:
279		/* parent */
280		setproctitle("resolver");
281		close(pair[0]);
282		sibling = pair[1];
283		break;
284	}
285
286	if (!debug) {
287		openlog(__progname, LOG_PID|LOG_NDELAY, LOG_DAEMON);
288		tzset();
289		logger = &syslogger;
290	}
291
292	event_init();
293
294	if (ioctl(sibling, FIONBIO, &on) == -1)
295		lerr(1, "sibling ioctl(FIONBIO)");
296
297	if (parent) {
298		SIMPLEQ_INIT(&sc.parent.replies);
299
300		event_set(&proc_rd, sibling, EV_READ | EV_PERSIST,
301		    parent_rd, NULL);
302		event_set(&proc_wr, sibling, EV_WRITE,
303		    parent_wr, NULL);
304	} else {
305		SIMPLEQ_INIT(&sc.child.pushing);
306		SIMPLEQ_INIT(&sc.child.popping);
307
308		identd_listen(addr, port, family);
309
310		if (chroot(pw->pw_dir) == -1)
311			lerr(1, "chroot(%s)", pw->pw_dir);
312
313		if (chdir("/") == -1)
314			lerr(1, "chdir(%s)", pw->pw_dir);
315
316		event_set(&proc_rd, sibling, EV_READ | EV_PERSIST,
317		    child_rd, NULL);
318		event_set(&proc_wr, sibling, EV_WRITE,
319		    child_wr, NULL);
320	}
321
322	if (setgroups(1, &pw->pw_gid) ||
323	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
324	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
325		lerr(1, "unable to revoke privs");
326
327	event_add(&proc_rd, NULL);
328	event_dispatch();
329	return (0);
330}
331
332void
333parent_rd(int fd, short events, void *arg)
334{
335	struct ident_resolver *r;
336	struct passwd *pw;
337	ssize_t n;
338	uid_t uid;
339
340	n = read(fd, &uid, sizeof(uid));
341	switch (n) {
342	case -1:
343		switch (errno) {
344		case EAGAIN:
345		case EINTR:
346			return;
347		default:
348			lerr(1, "parent read");
349		}
350		break;
351	case 0:
352		lerrx(1, "child has gone");
353	case sizeof(uid):
354		break;
355	default:
356		lerrx(1, "unexpected %zd data from child", n);
357	}
358
359	r = calloc(1, sizeof(*r));
360	if (r == NULL)
361		lerr(1, "resolver alloc");
362
363	pw = getpwuid(uid);
364	if (pw == NULL) {
365		r->error = E_NOUSER;
366		goto done;
367	}
368
369	if (noident) {
370		parent_noident(r, pw);
371		if (r->error != E_NONE)
372			goto done;
373	}
374
375	n = asprintf(&r->buf, "%s", pw->pw_name);
376	if (n == -1) {
377		r->error = E_UNKNOWN;
378		goto done;
379	}
380
381	r->buflen = n;
382
383done:
384	SIMPLEQ_INSERT_TAIL(&sc.parent.replies, r, entry);
385	event_add(&proc_wr, NULL);
386}
387
388void
389parent_noident(struct ident_resolver *r, struct passwd *pw)
390{
391	char path[MAXPATHLEN];
392	int fd;
393	int rv;
394
395	rv = snprintf(path, sizeof(path), "%s/%s", pw->pw_dir, DOTNOIDENT);
396	if (rv == -1 || rv >= sizeof(path)) {
397		r->error = E_UNKNOWN;
398		return;
399	}
400
401	fd = open(path, O_RDONLY, 0);
402	if (fd == -1) {
403		switch (errno) {
404		case ENOENT:
405		case EACCES:
406			return; /* not an error */
407		default:
408			r->error = E_UNKNOWN;
409			return;
410		}
411	}
412
413	close(fd);
414
415	r->error = E_HIDDEN;
416}
417
418void
419parent_wr(int fd, short events, void *arg)
420{
421	struct ident_resolver *r = SIMPLEQ_FIRST(&sc.parent.replies);
422	struct iovec iov[2];
423	int iovcnt = 0;
424	ssize_t n;
425
426	iov[iovcnt].iov_base = &r->error;
427	iov[iovcnt].iov_len = sizeof(r->error);
428	iovcnt++;
429
430	if (r->buflen > 0) {
431		iov[iovcnt].iov_base = r->buf;
432		iov[iovcnt].iov_len = r->buflen;
433		iovcnt++;
434	}
435
436	n = writev(fd, iov, iovcnt);
437	if (n == -1) {
438		if (errno == EAGAIN) {
439			event_add(&proc_wr, NULL);
440			return;
441		}
442		lerr(1, "parent write");
443	}
444
445	if (n != sizeof(r->error) + r->buflen)
446		lerrx(1, "unexpected parent write length %zd", n);
447
448	SIMPLEQ_REMOVE_HEAD(&sc.parent.replies, entry);
449
450	if (r->buflen > 0)
451		free(r->buf);
452
453	free(r);
454}
455
456void
457child_rd(int fd, short events, void *arg)
458{
459	struct ident_client *c;
460	struct {
461		u_int error;
462		char buf[512];
463	} reply;
464	ssize_t n;
465
466	n = read(fd, &reply, sizeof(reply));
467	switch (n) {
468	case -1:
469		switch (errno) {
470		case EAGAIN:
471		case EINTR:
472			return;
473		default:
474			lerr(1, "child read");
475		}
476		break;
477	case 0:
478		lerrx(1, "parent has gone");
479	default:
480		break;
481	}
482
483	c = SIMPLEQ_FIRST(&sc.child.popping);
484	if (c == NULL)
485		lerrx(1, "unsolicited data from parent");
486
487	SIMPLEQ_REMOVE_HEAD(&sc.child.popping, entry);
488
489	if (n < sizeof(reply.error))
490		lerrx(1, "short data from parent");
491
492	/* check if something went wrong while the parent was working */
493	if (c->state == S_DEAD) {
494		free(c);
495		return;
496	}
497	c->state = S_DEAD;
498
499	switch (reply.error) {
500	case E_NONE:
501		n = asprintf(&c->buf, "%u , %u : USERID : UNIX : %s\r\n",
502		    c->server.port, c->client.port, reply.buf);
503		break;
504	case E_NOUSER:
505		n = asprintf(&c->buf, "%u , %u : ERROR : NO-USER\r\n",
506		    c->server.port, c->client.port);
507		break;
508	case E_UNKNOWN:
509		n = asprintf(&c->buf, "%u , %u : ERROR : UNKNOWN-ERROR\r\n",
510		    c->server.port, c->client.port);
511		break;
512	case E_HIDDEN:
513		n = asprintf(&c->buf, "%u , %u : ERROR : HIDDEN-USER\r\n",
514		    c->server.port, c->client.port);
515		break;
516	default:
517		lerrx(1, "unexpected error from parent %u", reply.error);
518	}
519	if (n == -1)
520		goto fail;
521
522	c->buflen = n;
523
524	fd = EVENT_FD(&c->ev);
525	event_del(&c->ev);
526	event_set(&c->ev, fd, EV_READ | EV_WRITE | EV_PERSIST,
527	    identd_response, c);
528	event_add(&c->ev, NULL);
529	return;
530
531fail:
532	identd_close(c);
533}
534
535void
536child_wr(int fd, short events, void *arg)
537{
538	struct ident_client *c = SIMPLEQ_FIRST(&sc.child.pushing);
539	const char *errstr = NULL;
540	ssize_t n;
541
542	n = write(fd, &c->uid, sizeof(c->uid));
543	switch (n) {
544	case -1:
545		switch (errno) {
546		case EAGAIN:
547			event_add(&proc_wr, NULL);
548			return;
549		case ENOBUFS: /* parent has a backlog of requests */
550			errstr = "UNKNOWN-ERROR";
551			break;
552		default:
553			lerr(1, "child write");
554		}
555		break;
556	case sizeof(c->uid):
557		break;
558	default:
559		lerrx(1, "unexpected child write length %zd", n);
560	}
561
562	SIMPLEQ_REMOVE_HEAD(&sc.child.pushing, entry);
563	if (errstr == NULL)
564		SIMPLEQ_INSERT_TAIL(&sc.child.popping, c, entry);
565	else if (identd_error(c, errstr) == -1)
566		identd_close(c);
567
568	if (!SIMPLEQ_EMPTY(&sc.child.pushing))
569		event_add(&proc_wr, NULL);
570}
571
572void
573identd_listen(const char *addr, const char *port, int family)
574{
575	struct identd_listener *l = NULL;
576
577	struct addrinfo hints, *res, *res0;
578	int error, s;
579	const char *cause = NULL;
580
581	memset(&hints, 0, sizeof(hints));
582	hints.ai_family = family;
583	hints.ai_socktype = SOCK_STREAM;
584	hints.ai_flags = AI_PASSIVE;
585
586	error = getaddrinfo(addr, port, &hints, &res0);
587	if (error)
588		lerrx(1, "%s/%s: %s", addr, port, gai_strerror(error));
589
590	for (res = res0; res != NULL; res = res->ai_next) {
591		s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
592		if (s == -1) {
593			cause = "socket";
594			continue;
595		}
596
597		if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
598		    &on, sizeof(on)) == -1)
599			err(1, "listener setsockopt(SO_REUSEADDR)");
600
601		if (bind(s, res->ai_addr, res->ai_addrlen) == -1) {
602			int serrno = errno;
603
604			cause = "bind";
605			close(s);
606			errno = serrno;
607			continue;
608		}
609
610		if (ioctl(s, FIONBIO, &on) == -1)
611			err(1, "listener ioctl(FIONBIO)");
612
613		if (listen(s, 5) == -1)
614			err(1, "listen");
615
616		l = calloc(1, sizeof(*l));
617		if (l == NULL)
618			err(1, "listener ev alloc");
619
620		event_set(&l->ev, s, EV_READ | EV_PERSIST, identd_accept, l);
621		event_add(&l->ev, NULL);
622		evtimer_set(&l->pause, identd_paused, l);
623	}
624	if (l == NULL)
625		err(1, "%s", cause);
626
627	freeaddrinfo(res0);
628}
629
630void
631identd_paused(int fd, short events, void *arg)
632{
633	struct identd_listener *l = arg;
634	event_add(&l->ev, NULL);
635}
636
637void
638identd_accept(int fd, short events, void *arg)
639{
640	struct identd_listener *l = arg;
641	struct sockaddr_storage ss;
642	struct timeval pause = { 1, 0 };
643	struct ident_client *c = NULL;
644	socklen_t len;
645	int s;
646
647	len = sizeof(ss);
648	s = accept(fd, sa(&ss), &len);
649	if (s == -1) {
650		switch (errno) {
651		case EINTR:
652		case EWOULDBLOCK:
653		case ECONNABORTED:
654			return;
655		case EMFILE:
656		case ENFILE:
657			event_del(&l->ev);
658			evtimer_add(&l->pause, &pause);
659			return;
660		default:
661			lerr(1, "accept");
662		}
663	}
664
665	if (ioctl(s, FIONBIO, &on) == -1)
666		lerr(1, "client ioctl(FIONBIO)");
667
668	c = calloc(1, sizeof(*c));
669	if (c == NULL) {
670		lwarn("client alloc");
671		close(fd);
672		return;
673	}
674
675	memcpy(&c->client.ss, &ss, len);
676	c->client.len = len;
677	ldebug("client: %s", gethost(&ss));
678
679	/* lookup the local ip it connected to */
680	c->server.len = sizeof(c->server.ss);
681	if (getsockname(s, sa(&c->server.ss), &c->server.len) == -1)
682		lerr(1, "getsockname");
683
684	event_set(&c->ev, s, EV_READ | EV_PERSIST, identd_request, c);
685	event_add(&c->ev, NULL);
686
687	event_set(&c->tmo, s, 0, identd_timeout, c);
688	event_add(&c->tmo, &timeout);
689}
690
691void
692identd_timeout(int fd, short events, void *arg)
693{
694	struct ident_client *c = arg;
695
696	event_del(&c->ev);
697	close(fd);
698	free(c->buf);
699
700	if (c->state == S_QUEUED) /* it is queued for resolving */
701		c->state = S_DEAD;
702	else
703		free(c);
704}
705
706void
707identd_request(int fd, short events, void *arg)
708{
709	struct ident_client *c = arg;
710	char buf[64];
711	ssize_t n, i;
712	char *errstr = "INVALID-PORT";
713
714	n = read(fd, buf, sizeof(buf));
715	switch (n) {
716	case -1:
717		switch (errno) {
718		case EINTR:
719		case EAGAIN:
720			return;
721		default:
722			lwarn("%s read", gethost(&c->client.ss));
723			goto fail;
724		}
725		break;
726
727	case 0:
728		ldebug("%s closed connection", gethost(&c->client.ss));
729		goto fail;
730	default:
731		break;
732	}
733
734	c->rxbytes += n;
735	if (c->rxbytes >= INPUT_MAX)
736		goto fail;
737
738	for (i = 0; c->state < S_EOL && i < n; i++)
739		c->state = identd_parse(c, buf[i]);
740
741	if (c->state == S_DEAD)
742		goto error;
743	if (c->state != S_EOL)
744		return;
745
746	if (c->server.port < 1 || c->client.port < 1)
747		goto error;
748
749	if (fetchuid(c) == -1) {
750		errstr = "NO-USER";
751		goto error;
752	}
753
754	SIMPLEQ_INSERT_TAIL(&sc.child.pushing, c, entry);
755	c->state = S_QUEUED;
756
757	event_del(&c->ev);
758	event_set(&c->ev, fd, EV_READ | EV_PERSIST, identd_resolving, c);
759	event_add(&c->ev, NULL);
760
761	event_add(&proc_wr, NULL);
762	return;
763
764error:
765	if (identd_error(c, errstr) == -1)
766		goto fail;
767
768	return;
769
770fail:
771	identd_close(c);
772}
773
774int
775identd_error(struct ident_client *c, const char *errstr)
776{
777	int fd = EVENT_FD(&c->ev);
778	ssize_t n;
779
780	n = asprintf(&c->buf, "%u , %u : ERROR : %s\r\n",
781	    c->server.port, c->client.port, errstr);
782	if (n == -1)
783		return (-1);
784
785	c->buflen = n;
786
787	event_del(&c->ev);
788	event_set(&c->ev, fd, EV_READ | EV_WRITE | EV_PERSIST,
789	    identd_response, c);
790	event_add(&c->ev, NULL);
791
792	return (0);
793}
794
795void
796identd_close(struct ident_client *c)
797{
798	int fd = EVENT_FD(&c->ev);
799
800	evtimer_del(&c->tmo);
801	event_del(&c->ev);
802	close(fd);
803	free(c->buf);
804	free(c);
805}
806
807void
808identd_resolving(int fd, short events, void *arg)
809{
810	struct ident_client *c = arg;
811	char buf[64];
812	ssize_t n;
813
814	/*
815	 * something happened while we're waiting for the parent to lookup
816	 * the user.
817	 */
818
819	n = read(fd, buf, sizeof(buf));
820	switch (n) {
821	case -1:
822		switch (errno) {
823		case EINTR:
824		case EAGAIN:
825			return;
826		default:
827			lerrx(1, "resolving read");
828		}
829		/* NOTREACHED */
830	case 0:
831		ldebug("%s closed connection during resolving",
832		    gethost(&c->client.ss));
833		break;
834	default:
835		c->rxbytes += n;
836		if (c->rxbytes >= INPUT_MAX)
837			break;
838
839		/* ignore extra input */
840		return;
841	}
842
843	evtimer_del(&c->tmo);
844	event_del(&c->ev);
845	close(fd);
846	c->state = S_DEAD; /* on the resolving queue */
847}
848
849enum ident_client_state
850identd_parse(struct ident_client *c, int ch)
851{
852	enum ident_client_state s = c->state;
853
854	switch (s) {
855	case S_BEGINNING:
856		/* ignore leading space */
857		if (ch == '\t' || ch == ' ')
858			return (s);
859
860		if (ch == '0' || !isdigit(ch))
861			return (S_DEAD);
862
863		c->server.port = ch - '0';
864		return (S_SERVER_PORT);
865
866	case S_SERVER_PORT:
867		if (ch == '\t' || ch == ' ')
868			return (S_PRE_COMMA);
869		if (ch == ',')
870			return (S_POST_COMMA);
871
872		if (!isdigit(ch))
873			return (S_DEAD);
874
875		c->server.port *= 10;
876		c->server.port += ch - '0';
877		if (c->server.port > 65535)
878			return (S_DEAD);
879
880		return (s);
881
882	case S_PRE_COMMA:
883		if (ch == '\t' || ch == ' ')
884			return (s);
885		if (ch == ',')
886			return (S_POST_COMMA);
887
888		return (S_DEAD);
889
890	case S_POST_COMMA:
891		if (ch == '\t' || ch == ' ')
892			return (s);
893
894		if (ch == '0' || !isdigit(ch))
895			return (S_DEAD);
896
897		c->client.port = ch - '0';
898		return (S_CLIENT_PORT);
899
900	case S_CLIENT_PORT:
901		if (ch == '\t' || ch == ' ')
902			return (S_PRE_EOL);
903		if (ch == '\r' || ch == '\n')
904			return (S_EOL);
905
906		if (!isdigit(ch))
907			return (S_DEAD);
908
909		c->client.port *= 10;
910		c->client.port += ch - '0';
911		if (c->client.port > 65535)
912			return (S_DEAD);
913
914		return (s);
915
916	case S_PRE_EOL:
917		if (ch == '\t' || ch == ' ')
918			return (s);
919		if (ch == '\r' || ch == '\n')
920			return (S_EOL);
921
922		return (S_DEAD);
923
924	case S_EOL:
925		/* ignore trailing garbage */
926		return (s);
927
928	default:
929		return (S_DEAD);
930	}
931}
932
933void
934identd_response(int fd, short events, void *arg)
935{
936	struct ident_client *c = arg;
937	char buf[64];
938	ssize_t n;
939
940	if (events & EV_READ) {
941		n = read(fd, buf, sizeof(buf));
942		switch (n) {
943		case -1:
944			switch (errno) {
945			case EINTR:
946			case EAGAIN:
947				/* meh, try a write */
948				break;
949			default:
950				lerrx(1, "response read");
951			}
952			break;
953		case 0:
954			ldebug("%s closed connection during response",
955			    gethost(&c->client.ss));
956			goto done;
957		default:
958			c->rxbytes += n;
959			if (c->rxbytes >= INPUT_MAX)
960				goto done;
961
962			/* ignore extra input */
963			break;
964		}
965	}
966
967	if (!(events & EV_WRITE))
968		return; /* try again later */
969
970	n = write(fd, c->buf + c->bufoff, c->buflen - c->bufoff);
971	if (n == -1) {
972		switch (errno) {
973		case EAGAIN:
974			return; /* try again later */
975		default:
976			lerr(1, "response write");
977		}
978	}
979
980	c->bufoff += n;
981	if (c->bufoff != c->buflen)
982		return; /* try again later */
983
984done:
985	identd_close(c);
986}
987
988void
989syslog_vstrerror(int e, int priority, const char *fmt, va_list ap)
990{
991	char *s;
992
993	if (vasprintf(&s, fmt, ap) == -1) {
994		syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror");
995		exit(1);
996	}
997	syslog(priority, "%s: %s", s, strerror(e));
998	free(s);
999}
1000
1001void
1002syslog_err(int ecode, const char *fmt, ...)
1003{
1004	va_list ap;
1005
1006	va_start(ap, fmt);
1007	syslog_vstrerror(errno, LOG_EMERG, fmt, ap);
1008	va_end(ap);
1009	exit(ecode);
1010}
1011
1012void
1013syslog_errx(int ecode, const char *fmt, ...)
1014{
1015	va_list ap;
1016
1017	va_start(ap, fmt);
1018	vsyslog(LOG_WARNING, fmt, ap);
1019	va_end(ap);
1020	exit(ecode);
1021}
1022
1023void
1024syslog_warn(const char *fmt, ...)
1025{
1026	va_list ap;
1027
1028	va_start(ap, fmt);
1029	syslog_vstrerror(errno, LOG_WARNING, fmt, ap);
1030	va_end(ap);
1031}
1032
1033void
1034syslog_warnx(const char *fmt, ...)
1035{
1036	va_list ap;
1037
1038	va_start(ap, fmt);
1039	vsyslog(LOG_WARNING, fmt, ap);
1040	va_end(ap);
1041}
1042
1043void
1044syslog_info(const char *fmt, ...)
1045{
1046	va_list ap;
1047
1048	va_start(ap, fmt);
1049	vsyslog(LOG_INFO, fmt, ap);
1050	va_end(ap);
1051}
1052
1053void
1054syslog_debug(const char *fmt, ...)
1055{
1056	va_list ap;
1057
1058	if (!debug)
1059		return;
1060
1061	va_start(ap, fmt);
1062	vsyslog(LOG_DEBUG, fmt, ap);
1063	va_end(ap);
1064}
1065
1066const char *
1067gethost(struct sockaddr_storage *ss)
1068{
1069	struct sockaddr *sa = (struct sockaddr *)ss;
1070	static char buf[NI_MAXHOST];
1071
1072	if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf),
1073	    NULL, 0, NI_NUMERICHOST) != 0)
1074		return ("(unknown)");
1075
1076	return (buf);
1077}
1078
1079const char *
1080getport(struct sockaddr_storage *ss)
1081{
1082	struct sockaddr *sa = (struct sockaddr *)ss;
1083	static char buf[NI_MAXSERV];
1084
1085	if (getnameinfo(sa, sa->sa_len, NULL, 0, buf, sizeof(buf),
1086	    NI_NUMERICSERV) != 0)
1087		return ("(unknown)");
1088
1089	return (buf);
1090}
1091
1092int
1093fetchuid(struct ident_client *c)
1094{
1095	struct tcp_ident_mapping tir;
1096	int mib[] = { CTL_NET, PF_INET, IPPROTO_TCP, TCPCTL_IDENT };
1097	struct sockaddr_in *s4;
1098	struct sockaddr_in6 *s6;
1099	int err = 0;
1100	size_t len;
1101
1102	memset(&tir, 0, sizeof(tir));
1103	memcpy(&tir.faddr, &c->client.ss, sizeof(&tir.faddr));
1104	memcpy(&tir.laddr, &c->server.ss, sizeof(&tir.laddr));
1105
1106	switch (c->server.ss.ss_family) {
1107	case AF_INET:
1108		s4 = (struct sockaddr_in *)&tir.faddr;
1109		s4->sin_port = htons(c->client.port);
1110
1111		s4 = (struct sockaddr_in *)&tir.laddr;
1112		s4->sin_port = htons(c->server.port);
1113		break;
1114	case AF_INET6:
1115		s6 = (struct sockaddr_in6 *)&tir.faddr;
1116		s6->sin6_port = htons(c->client.port);
1117
1118		s6 = (struct sockaddr_in6 *)&tir.laddr;
1119		s6->sin6_port = htons(c->server.port);
1120		break;
1121	default:
1122		lerrx(1, "unexpected family %d", c->server.ss.ss_family);
1123	}
1124
1125	len = sizeof(tir);
1126	err = sysctl(mib, sizeof(mib) / sizeof(mib[0]), &tir, &len, NULL, 0);
1127	if (err == -1)
1128		lerr(1, "sysctl");
1129
1130	if (tir.ruid == -1)
1131		return (-1);
1132
1133	c->uid = tir.ruid;
1134	return (0);
1135}
1136