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