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