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