diff options
Diffstat (limited to 'usr.sbin/identd')
-rw-r--r-- | usr.sbin/identd/Makefile | 11 | ||||
-rw-r--r-- | usr.sbin/identd/identd.8 | 79 | ||||
-rw-r--r-- | usr.sbin/identd/identd.c | 1063 |
3 files changed, 1153 insertions, 0 deletions
diff --git a/usr.sbin/identd/Makefile b/usr.sbin/identd/Makefile new file mode 100644 index 00000000000..37ec17bd781 --- /dev/null +++ b/usr.sbin/identd/Makefile @@ -0,0 +1,11 @@ +# $OpenBSD: Makefile,v 1.1 2013/03/18 00:34:48 dlg Exp $ + +PROG= identd +SRCS= identd.c +LDADD= -levent +DPADD= ${LIBEVENT} +CFLAGS+= -Wall -Werror + +MAN= identd.8 + +.include <bsd.prog.mk> diff --git a/usr.sbin/identd/identd.8 b/usr.sbin/identd/identd.8 new file mode 100644 index 00000000000..899ae630b06 --- /dev/null +++ b/usr.sbin/identd/identd.8 @@ -0,0 +1,79 @@ +.\" $OpenBSD: identd.8,v 1.1 2013/03/18 00:34:48 dlg Exp $ +.\" +.\" Copyright (c) YYYY YOUR NAME HERE <user@your.dom.ain> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: March 18 2013 $ +.Dt IDENTD 8 +.Os +.Sh NAME +.Nm identd +.Nd Identification Protocol daemon +.Sh SYNOPSIS +.Nm +.Op Fl 46d +.Op Fl l Ar address +.Op Fl p Ar port +.Op Fl t Ar timeout +.Ar directory +.Sh DESCRIPTION +.Nm +is a server which implements the Identification Protocol as specified in +RFC 1413. +.Pp +.Nm +operates by looking up specific TCP/IP connections and returning +the name of the user running the process responsible for the connection. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl 4 +Forces +.Nm +to use IPv4 addresses only. +.It Fl 6 +Forces +.Nm +to use IPv6 addresses only. +.It Fl d +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to stderr. +.It Fl l Ar address +Listen on the specified address. +By default +.Nm +listens on wildcard addresses. +.It Fl p Ar port +Listen on the specified port. +By default +.Nm +listens on the port indicated in the +.Ql auth +service description; see +.Xr services 5 . +.It Fl t Ar seconds +Specifies the idle timeout for client connections. +The default timeout is 120 seconds. +.El +.\" .Sh SEE ALSO +.Sh HISTORY +The +.Nm +command was originally a process run via +.Xr inetd 8 . +It was rewritten for +.Ox 5.4 +as a persistent non-blocking daemon. diff --git a/usr.sbin/identd/identd.c b/usr.sbin/identd/identd.c new file mode 100644 index 00000000000..5e511622449 --- /dev/null +++ b/usr.sbin/identd/identd.c @@ -0,0 +1,1063 @@ +/* $OpenBSD: identd.c,v 1.1 2013/03/18 00:34:48 dlg Exp $ */ + +/* + * Copyright (c) 2013 David Gwynne <dlg@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sysctl.h> +#include <sys/uio.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip_var.h> +#include <netinet/tcp.h> +#include <netinet/tcp_timer.h> +#include <netinet/tcp_var.h> + +#include <arpa/inet.h> +#include <netdb.h> + +#include <err.h> +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <syslog.h> +#include <unistd.h> + +#define IDENTD_USER "_identd" + +#define TIMEOUT_MIN 4 +#define TIMEOUT_MAX 240 +#define TIMEOUT_DEFAULT 120 + +enum ident_client_state { + S_BEGINNING = 0, + S_SERVER_PORT, + S_PRE_COMMA, + S_POST_COMMA, + S_CLIENT_PORT, + S_PRE_EOL, + S_EOL, + + S_DEAD, + S_DYING +}; + +#define E_NONE 0 +#define E_NOUSER 1 +#define E_UNKNOWN 2 +#define E_HIDDEN 3 + +struct ident_client { + struct { + /* from the socket */ + struct sockaddr_storage ss; + socklen_t len; + + /* from the request */ + u_int port; + } client, server; + SIMPLEQ_ENTRY(ident_client) entry; + enum ident_client_state state; + struct event ev; + + char *buf; + size_t buflen; + size_t bufoff; + uid_t uid; +}; + +struct ident_resolver { + SIMPLEQ_ENTRY(ident_resolver) entry; + char *buf; + size_t buflen; + u_int error; +}; + +void parent_rd(int, short, void *); +void parent_wr(int, short, void *); + +void child_rd(int, short, void *); +void child_wr(int, short, void *); + +void identd_listen(const char *, const char *, int); +void identd_accept(int, short, void *); +void identd_request(int, short, void *); +enum ident_client_state + identd_parse(struct ident_client *, int); +void identd_resolving(int, short, void *); +void identd_response(int, short, void *); +int fetchuid(struct ident_client *); + +const char *gethost(struct sockaddr_storage *); +const char *getport(struct sockaddr_storage *); + +struct loggers { + void (*err)(int, const char *, ...); + void (*errx)(int, const char *, ...); + void (*warn)(const char *, ...); + void (*warnx)(const char *, ...); + void (*info)(const char *, ...); + void (*debug)(const char *, ...); +}; + +const struct loggers conslogger = { + err, + errx, + warn, + warnx, + warnx, /* info */ + warnx /* debug */ +}; + +void syslog_err(int, const char *, ...); +void syslog_errx(int, const char *, ...); +void syslog_warn(const char *, ...); +void syslog_warnx(const char *, ...); +void syslog_info(const char *, ...); +void syslog_debug(const char *, ...); +void syslog_vstrerror(int, int, const char *, va_list); + +const struct loggers syslogger = { + syslog_err, + syslog_errx, + syslog_warn, + syslog_warnx, + syslog_info, + syslog_debug +}; + +const struct loggers *logger = &conslogger; + +#define lerr(_e, _f...) logger->err((_e), _f) +#define lerrx(_e, _f...) logger->errx((_e), _f) +#define lwarn(_f...) logger->warn(_f) +#define lwarnx(_f...) logger->warnx(_f) +#define linfo(_f...) logger->info(_f) +#define ldebug(_f...) logger->debug(_f) + +const char *gethost(struct sockaddr_storage *); +#define sa(_ss) ((struct sockaddr *)(_ss)) + +__dead void +usage(void) +{ + extern char *__progname; + fprintf(stderr, "usage: %s [-46d] [-l address] [-p port] " + "[-t timeout]\n", __progname); + exit(1); +} + +struct timeval timeout = { TIMEOUT_DEFAULT, 0 }; +int debug = 0; +int on = 1; + +struct event proc_rd, proc_wr; +union { + struct { + SIMPLEQ_HEAD(, ident_resolver) replies; + } parent; + struct { + SIMPLEQ_HEAD(, ident_client) pushing, popping; + } child; +} sc; + +int +main(int argc, char *argv[]) +{ + extern char *__progname; + const char *errstr = NULL; + + int c; + struct passwd *pw; + + char *addr = NULL; + char *port = "auth"; + int family = AF_UNSPEC; + + int pair[2]; + pid_t parent; + int sibling; + + while ((c = getopt(argc, argv, "46dl:p:t:")) != -1) { + switch (c) { + case '4': + family = AF_INET; + break; + case '6': + family = AF_INET6; + break; + case 'd': + debug = 1; + break; + case 'l': + addr = optarg; + break; + case 'p': + port = optarg; + break; + case 't': + timeout.tv_sec = strtonum(optarg, + TIMEOUT_MIN, TIMEOUT_MAX, &errstr); + if (errstr != NULL) + errx(1, "timeout %s is %s", optarg, errstr); + break; + default: + usage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (argc != 0) + usage(); + + if (geteuid() != 0) + errx(1, "need root privileges"); + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, PF_UNSPEC, pair) == -1) + err(1, "socketpair"); + + pw = getpwnam(IDENTD_USER); + if (pw == NULL) + err(1, "no %s user", IDENTD_USER); + + if (!debug && daemon(1, 0) == -1) + err(1, "daemon"); + + parent = fork(); + switch (parent) { + case -1: + lerr(1, "fork"); + + case 0: + /* child */ + setproctitle("listener"); + close(pair[1]); + sibling = pair[0]; + break; + + default: + /* parent */ + setproctitle("resolver"); + close(pair[0]); + sibling = pair[1]; + break; + } + + if (!debug) { + openlog(__progname, LOG_PID|LOG_NDELAY, LOG_DAEMON); + tzset(); + logger = &syslogger; + } + + event_init(); + + if (ioctl(sibling, FIONBIO, &on) == -1) + lerr(1, "sibling ioctl(FIONBIO)"); + + if (parent) { + SIMPLEQ_INIT(&sc.parent.replies); + + event_set(&proc_rd, sibling, EV_READ | EV_PERSIST, + parent_rd, NULL); + event_set(&proc_wr, sibling, EV_WRITE, + parent_wr, NULL); + } else { + SIMPLEQ_INIT(&sc.child.pushing); + SIMPLEQ_INIT(&sc.child.popping); + + identd_listen(addr, port, family); + + if (chroot(pw->pw_dir) == -1) + lerr(1, "chroot(%s)", pw->pw_dir); + + if (chdir("/") == -1) + lerr(1, "chdir(%s)", pw->pw_dir); + + event_set(&proc_rd, sibling, EV_READ | EV_PERSIST, + child_rd, NULL); + event_set(&proc_wr, sibling, EV_WRITE, + child_wr, NULL); + } + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + lerr(1, "unable to revoke privs"); + + event_add(&proc_rd, NULL); + + event_dispatch(); + + return (0); +} + +void +parent_rd(int fd, short events, void *arg) +{ + struct ident_resolver *r; + struct passwd *pw; + ssize_t n; + uid_t uid; + + n = read(fd, &uid, sizeof(uid)); + switch (n) { + case -1: + switch (errno) { + case EAGAIN: + case EINTR: + return; + default: + lerr(1, "parent read"); + } + break; + case 0: + lerrx(1, "child has gone"); + case sizeof(uid): + break; + default: + lerrx(1, "unexpected %zd data from child", n); + } + + event_add(&proc_wr, NULL); + + r = calloc(1, sizeof(*r)); + if (r == NULL) + lerr(1, "resolver alloc"); + + pw = getpwuid(uid); + if (pw == NULL) { + r->error = E_NOUSER; + } else { + n = asprintf(&r->buf, "%s", pw->pw_name); + if (n == -1) + r->error = E_UNKNOWN; + else { + r->error = E_NONE; + r->buflen = n; + } + } + + SIMPLEQ_INSERT_TAIL(&sc.parent.replies, r, entry); + event_add(&proc_wr, NULL); +} + +void +parent_wr(int fd, short events, void *arg) +{ + struct ident_resolver *r = SIMPLEQ_FIRST(&sc.parent.replies); + struct iovec iov[2]; + int iovcnt = 0; + ssize_t n; + + iov[iovcnt].iov_base = &r->error; + iov[iovcnt].iov_len = sizeof(r->error); + iovcnt++; + + if (r->buflen > 0) { + iov[iovcnt].iov_base = r->buf; + iov[iovcnt].iov_len = r->buflen; + iovcnt++; + } + + n = writev(fd, iov, iovcnt); + if (n == -1) { + if (errno == EAGAIN) { + event_add(&proc_wr, NULL); + return; + } + lerr(1, "parent write"); + } + + if (n != sizeof(r->error) + r->buflen) + lerrx(1, "unexpected parent write length %zd", n); + + SIMPLEQ_REMOVE_HEAD(&sc.parent.replies, entry); + + if (r->buflen > 0) + free(r->buf); + + free(r); +} + +void +child_rd(int fd, short events, void *arg) +{ + struct ident_client *c; + struct { + u_int error; + char buf[512]; + } reply; + ssize_t n; + + n = read(fd, &reply, sizeof(reply)); + switch (n) { + case -1: + switch (errno) { + case EAGAIN: + case EINTR: + return; + default: + lerr(1, "child read"); + } + break; + case 0: + lerrx(1, "parent has gone"); + default: + break; + } + + c = SIMPLEQ_FIRST(&sc.child.popping); + if (c == NULL) + lerrx(1, "unsolicited data from parent"); + + SIMPLEQ_REMOVE_HEAD(&sc.child.popping, entry); + + if (n < sizeof(reply.error)) + lerrx(1, "short data from parent"); + + /* check if something went wrong while the parent was working */ + switch (c->state) { + case S_DYING: /* we're sending an error to the client */ + c->state = S_DEAD; + return; + case S_DEAD: /* we finished sending an error to the client */ + free(c); + return; + default: + break; + } + + switch (reply.error) { + case E_NONE: + n = asprintf(&c->buf, "%u , %u : USERID : UNIX : %s\r\n", + c->server.port, c->client.port, reply.buf); + break; + case E_NOUSER: + n = asprintf(&c->buf, "%u , %u : ERROR : NO-USER\r\n", + c->server.port, c->client.port); + break; + case E_UNKNOWN: + n = asprintf(&c->buf, "%u , %u : ERROR : UNKNOWN-ERROR\r\n", + c->server.port, c->client.port); + break; + case E_HIDDEN: + n = asprintf(&c->buf, "%u , %u : ERROR : HIDDEN-USER\r\n", + c->server.port, c->client.port); + break; + default: + lerrx(1, "unexpected error from parent %u", reply.error); + } + if (n == -1) + goto fail; + + c->buflen = n; + + fd = EVENT_FD(&c->ev); + event_del(&c->ev); + event_set(&c->ev, fd, EV_READ | EV_WRITE | EV_PERSIST, + identd_response, c); + event_add(&c->ev, &timeout); + return; + +fail: + event_del(&c->ev); + close(fd); + free(c); +} + +void +child_wr(int fd, short events, void *arg) +{ + struct ident_client *c = SIMPLEQ_FIRST(&sc.child.pushing); + ssize_t n; + + n = write(fd, &c->uid, sizeof(c->uid)); + switch (n) { + case -1: + if (errno == EAGAIN) { + event_add(&proc_wr, NULL); + return; + } + lerr(1, "child write"); + case sizeof(c->uid): + break; + default: + lerrx(1, "unexpected child write length %zd", n); + } + + SIMPLEQ_REMOVE_HEAD(&sc.child.pushing, entry); + SIMPLEQ_INSERT_TAIL(&sc.child.popping, c, entry); + + if (!SIMPLEQ_EMPTY(&sc.child.pushing)) + event_add(&proc_wr, NULL); +} + +void +identd_listen(const char *addr, const char *port, int family) +{ + struct event *ev = NULL; + + struct addrinfo hints, *res, *res0; + int error; + int s; + int serrno; + const char *cause = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + error = getaddrinfo(addr, port, &hints, &res0); + if (error) + lerrx(1, "%s/%s: %s", addr, port, gai_strerror(error)); + + for (res = res0; res != NULL; res = res->ai_next) { + s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (s == -1) { + cause = "socket"; + continue; + } + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + &on, sizeof(on)) == -1) + err(1, "listener setsockopt(SO_REUSEADDR)"); + + if (bind(s, res->ai_addr, res->ai_addrlen) == -1) { + cause = "bind"; + serrno = errno; + close(s); + errno = serrno; + continue; + } + + if (ioctl(s, FIONBIO, &on) == -1) + err(1, "listener ioctl(FIONBIO)"); + + if (listen(s, 5) == -1) + err(1, "listen"); + + ev = calloc(1, sizeof(*ev)); + if (ev == NULL) + err(1, "listener ev alloc"); + + event_set(ev, s, EV_READ | EV_PERSIST, identd_accept, NULL); + event_add(ev, NULL); + } + if (ev == NULL) + err(1, "%s", cause); + + freeaddrinfo(res0); +} + +void +identd_accept(int fd, short events, void *arg) +{ + struct sockaddr_storage ss; + struct ident_client *c = NULL; + socklen_t len; + int s; + + len = sizeof(ss); + s = accept(fd, sa(&ss), &len); + if (s == -1) { + switch (errno) { + case EINTR: + case EWOULDBLOCK: + case ECONNABORTED: + return; + default: + lerr(1, "accept"); + } + } + + if (ioctl(s, FIONBIO, &on) == -1) + lerr(1, "client ioctl(FIONBIO)"); + + c = calloc(1, sizeof(*c)); + if (c == NULL) { + lwarn("client alloc"); + close(fd); + return; + } + + memcpy(&c->client.ss, &ss, len); + c->client.len = len; + ldebug("client: %s", gethost(&ss)); + + /* lookup the local ip it connected to */ + c->server.len = sizeof(c->server.ss); + if (getsockname(s, sa(&c->server.ss), &c->server.len) == -1) + lerr(1, "getsockname"); + + event_set(&c->ev, s, EV_READ | EV_PERSIST, identd_request, c); + event_add(&c->ev, &timeout); +} + +void +identd_request(int fd, short events, void *arg) +{ + struct ident_client *c = arg; + char buf[64]; + ssize_t n, i; + char *errstr = "INVALID-PORT"; + + if (events & EV_TIMEOUT) { + ldebug("%s request timeout", gethost(&c->client.ss)); + goto fail; + } + + n = read(fd, buf, sizeof(buf)); + switch (n) { + case -1: + switch (errno) { + case EINTR: + case EAGAIN: + return; + default: + lwarn("%s read", gethost(&c->client.ss)); + goto fail; + } + break; + + case 0: + ldebug("%s closed connection", gethost(&c->client.ss)); + goto fail; + default: + break; + } + + for (i = 0; c->state < S_EOL && i < n; i++) + c->state = identd_parse(c, buf[i]); + + if (c->state == S_DEAD) + goto error; + if (c->state != S_EOL) + return; + + if (c->server.port < 1 || c->client.port < 1) + goto error; + + event_del(&c->ev); + + if (fetchuid(c) == -1) { + errstr = "NO-USER"; + goto error; + } + + SIMPLEQ_INSERT_TAIL(&sc.child.pushing, c, entry); + + event_set(&c->ev, fd, EV_READ | EV_PERSIST, identd_resolving, c); + event_add(&c->ev, &timeout); + + event_add(&proc_wr, NULL); + return; + +error: + n = asprintf(&c->buf, "%u , %u : ERROR : %s\r\n", + c->server.port, c->client.port, errstr); + if (n == -1) + goto fail; + + c->buflen = n; + + event_del(&c->ev); + + event_set(&c->ev, fd, EV_READ | EV_WRITE | EV_PERSIST, + identd_response, c); + event_add(&c->ev, &timeout); + return; + +fail: + event_del(&c->ev); + close(fd); + free(c); +} + +void +identd_resolving(int fd, short events, void *arg) +{ + struct ident_client *c = arg; + char buf[64]; + ssize_t n; + + /* + * something happened while we're waiting for the parent to lookup + * the user. + */ + + if (events & EV_TIMEOUT) { + ldebug("%s timeout during resolving", + gethost(&c->client.ss)); + goto error; + } + + n = read(fd, buf, sizeof(buf)); + switch (n) { + case -1: + switch (errno) { + case EINTR: + case EAGAIN: + return; + default: + lerrx(1, "resolving read"); + } + break; + case 0: + ldebug("%s closed connection during resolving", + gethost(&c->client.ss)); + goto fail; + default: + break; + } + + /* throw it away */ + return; + +error: + n = asprintf(&c->buf, "%u , %u : ERROR : UNKNOWN-ERROR\r\n", + c->server.port, c->client.port); + if (n == -1) + goto fail; + + c->buflen = n; + + event_del(&c->ev); + event_set(&c->ev, fd, EV_READ | EV_WRITE | EV_PERSIST, + identd_response, c); + event_add(&c->ev, &timeout); + c->state = S_DYING; + return; + +fail: + event_del(&c->ev); + close(fd); + c->state = S_DEAD; +} + +enum ident_client_state +identd_parse(struct ident_client *c, int ch) +{ + enum ident_client_state s = c->state; + + switch (s) { + case S_BEGINNING: + /* ignore leading space */ + if (ch == '\t' || ch == ' ') + return (s); + + if (ch == '0' || !isdigit(ch)) + return (S_DEAD); + + c->server.port = ch - '0'; + return (S_SERVER_PORT); + + case S_SERVER_PORT: + if (ch == '\t' || ch == ' ') + return (S_PRE_COMMA); + if (ch == ',') + return (S_POST_COMMA); + + if (!isdigit(ch)) + return (S_DEAD); + + c->server.port *= 10; + c->server.port += ch - '0'; + if (c->server.port > 65535) + return (S_DEAD); + + return (s); + + case S_PRE_COMMA: + if (ch == '\t' || ch == ' ') + return (s); + if (ch == ',') + return (S_POST_COMMA); + + return (S_DEAD); + + case S_POST_COMMA: + if (ch == '\t' || ch == ' ') + return (s); + + if (ch == '0' || !isdigit(ch)) + return (S_DEAD); + + c->client.port = ch - '0'; + return (S_CLIENT_PORT); + + case S_CLIENT_PORT: + if (ch == '\t' || ch == ' ') + return (S_PRE_EOL); + if (ch == '\r' || ch == '\n') + return (S_EOL); + + if (!isdigit(ch)) + return (S_DEAD); + + c->client.port *= 10; + c->client.port += ch - '0'; + if (c->client.port > 65535) + return (S_DEAD); + + return (s); + + case S_PRE_EOL: + if (ch == '\t' || ch == ' ') + return (s); + if (ch == '\r' || ch == '\n') + return (S_EOL); + + return (S_DEAD); + + case S_EOL: + /* ignore trailing garbage */ + return (s); + + default: + return (S_DEAD); + } +} + +void +identd_response(int fd, short events, void *arg) +{ + struct ident_client *c = arg; + char buf[64]; + ssize_t n; + + if (events & EV_TIMEOUT) { + ldebug("%s timeout during response", gethost(&c->client.ss)); + goto done; + } + + if (events & EV_READ) { + n = read(fd, buf, sizeof(buf)); + switch (n) { + case -1: + switch (errno) { + case EINTR: + case EAGAIN: + /* meh, try a write */ + break; + default: + lerrx(1, "response read"); + } + break; + case 0: + ldebug("%s closed connection during response", + gethost(&c->client.ss)); + goto done; + default: + /* flushed socket */ + break; + } + } + + if (!(events & EV_WRITE)) + return; /* try again later */ + + n = write(fd, c->buf + c->bufoff, c->buflen - c->bufoff); + if (n == -1) { + switch (errno) { + case EAGAIN: + return; /* try again later */ + default: + goto done; + } + } + + c->bufoff += n; + if (c->bufoff != c->buflen) + return; /* try again later */ + +done: + event_del(&c->ev); + close(fd); + free(c->buf); + if (c->state == S_DYING) /* it was queued for resolving */ + c->state = S_DEAD; + else + free(c); +} + +void +syslog_vstrerror(int e, int priority, const char *fmt, va_list ap) +{ + char *s; + + if (vasprintf(&s, fmt, ap) == -1) { + syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror"); + exit(1); + } + + syslog(priority, "%s: %s", s, strerror(e)); + + free(s); +} + +void +syslog_err(int ecode, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + syslog_vstrerror(errno, LOG_EMERG, fmt, ap); + va_end(ap); + + exit(ecode); +} + +void +syslog_errx(int ecode, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_WARNING, fmt, ap); + va_end(ap); + + exit(ecode); +} + +void +syslog_warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + syslog_vstrerror(errno, LOG_WARNING, fmt, ap); + va_end(ap); +} + +void +syslog_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_WARNING, fmt, ap); + va_end(ap); +} + +void +syslog_info(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_INFO, fmt, ap); + va_end(ap); +} + +void +syslog_debug(const char *fmt, ...) +{ + va_list ap; + + if (!debug) + return; + + va_start(ap, fmt); + vsyslog(LOG_DEBUG, fmt, ap); + va_end(ap); +} + +const char * +gethost(struct sockaddr_storage *ss) +{ + struct sockaddr *sa = (struct sockaddr *)ss; + static char buf[NI_MAXHOST]; + + if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), + NULL, 0, NI_NUMERICHOST) != 0) + return ("(unknown)"); + + return (buf); +} + +const char * +getport(struct sockaddr_storage *ss) +{ + struct sockaddr *sa = (struct sockaddr *)ss; + static char buf[NI_MAXSERV]; + + if (getnameinfo(sa, sa->sa_len, NULL, 0, buf, sizeof(buf), + NI_NUMERICSERV) != 0) + return ("(unknown)"); + + return (buf); +} + +int +fetchuid(struct ident_client *c) +{ + struct tcp_ident_mapping tir; + int mib[] = { CTL_NET, PF_INET, IPPROTO_TCP, TCPCTL_IDENT }; + struct sockaddr_in *s4; + struct sockaddr_in6 *s6; + int err = 0; + size_t len; + + memset(&tir, 0, sizeof(tir)); + memcpy(&tir.faddr, &c->client.ss, sizeof(&tir.faddr)); + memcpy(&tir.laddr, &c->server.ss, sizeof(&tir.laddr)); + + switch (c->server.ss.ss_family) { + case AF_INET: + s4 = (struct sockaddr_in *)&tir.faddr; + s4->sin_port = htons(c->client.port); + + s4 = (struct sockaddr_in *)&tir.laddr; + s4->sin_port = htons(c->server.port); + break; + case AF_INET6: + s6 = (struct sockaddr_in6 *)&tir.faddr; + s6->sin6_port = htons(c->client.port); + + s6 = (struct sockaddr_in6 *)&tir.laddr; + s6->sin6_port = htons(c->server.port); + break; + default: + lerrx(1, "unexpected family %d", c->server.ss.ss_family); + } + + len = sizeof(tir); + err = sysctl(mib, sizeof(mib) / sizeof(mib[0]), &tir, &len, NULL, 0); + if (err == -1) + lerr(1, "sysctl"); + + if (tir.ruid == -1) + return (-1); + + c->uid = tir.ruid; + return (0); +} |