/* $OpenBSD: identd.c,v 1.40 2019/07/03 03:24:03 deraadt Exp $ */ /* * Copyright (c) 2013 David Gwynne * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IDENTD_USER "_identd" #define DOTNOIDENT ".noident" #define TIMEOUT_MIN 4 #define TIMEOUT_MAX 240 #define TIMEOUT_DEFAULT 120 #define INPUT_MAX 256 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_QUEUED }; #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; struct event tmo; size_t rxbytes; 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; }; struct identd_listener { struct event ev, pause; }; void parent_rd(int, short, void *); void parent_wr(int, short, void *); int parent_username(struct ident_resolver *, struct passwd *); int parent_uid(struct ident_resolver *, struct passwd *); int parent_token(struct ident_resolver *, struct passwd *); void parent_noident(struct ident_resolver *, struct passwd *); void child_rd(int, short, void *); void child_wr(int, short, void *); void identd_listen(const char *, const char *, int); void identd_paused(int, short, void *); void identd_accept(int, short, void *); int identd_error(struct ident_client *, const char *); void identd_close(struct ident_client *); void identd_timeout(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 *gentoken(void); struct loggers { __dead void (*err)(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); __dead void (*errx)(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); void (*warn)(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void (*warnx)(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void (*notice)(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void (*debug)(const char *, ...) __attribute__((__format__ (printf, 1, 2))); }; const struct loggers conslogger = { err, errx, warn, warnx, warnx, /* notice */ warnx /* debug */ }; __dead void syslog_err(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); __dead void syslog_errx(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); void syslog_warn(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void syslog_warnx(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void syslog_notice(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void syslog_debug(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void syslog_vstrerror(int, int, const char *, va_list) __attribute__((__format__ (printf, 3, 0))); const struct loggers syslogger = { syslog_err, syslog_errx, syslog_warn, syslog_warnx, syslog_notice, 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 lnotice(_f...) logger->notice(_f) #define ldebug(_f...) logger->debug(_f) #define sa(_ss) ((struct sockaddr *)(_ss)) static __dead void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-46deHhNn] [-l address] [-t timeout]\n", __progname); exit(1); } struct timeval timeout = { TIMEOUT_DEFAULT, 0 }; int debug = 0; int noident = 0; int unknown_err = 0; int hideall = 0; int (*parent_uprintf)(struct ident_resolver *, struct passwd *) = parent_username; 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; int family = AF_UNSPEC; int pair[2]; pid_t parent; int sibling; while ((c = getopt(argc, argv, "46deHhl:Nnt:")) != -1) { switch (c) { case '4': family = AF_INET; break; case '6': family = AF_INET6; break; case 'd': debug = 1; break; case 'e': unknown_err = 1; break; case 'H': hideall = 1; /* FALLTHROUGH */ case 'h': parent_uprintf = parent_token; break; case 'l': addr = optarg; break; case 'N': noident = 1; break; case 'n': parent_uprintf = parent_uid; 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 | SOCK_NONBLOCK, PF_UNSPEC, pair) == -1) err(1, "socketpair"); pw = getpwnam(IDENTD_USER); if (pw == NULL) errx(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 (signal(SIGPIPE, SIG_IGN) == SIG_ERR) lerr(1, "signal(SIGPIPE)"); if (parent) { if (pledge("stdio getpw rpath id", NULL) == -1) err(1, "pledge"); 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, "auth", 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"); if (parent) { if (noident) { if (pledge("stdio getpw rpath", NULL) == -1) err(1, "pledge"); } else { if (pledge("stdio getpw", NULL) == -1) err(1, "pledge"); } } 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); } r = calloc(1, sizeof(*r)); if (r == NULL) lerr(1, "resolver alloc"); pw = getpwuid(uid); if (pw == NULL && !hideall) { r->error = E_NOUSER; goto done; } if (noident && !hideall) { parent_noident(r, pw); if (r->error != E_NONE) goto done; } n = (*parent_uprintf)(r, pw); if (n == -1) { r->error = E_UNKNOWN; goto done; } r->buflen = n + 1; done: SIMPLEQ_INSERT_TAIL(&sc.parent.replies, r, entry); event_add(&proc_wr, NULL); } int parent_username(struct ident_resolver *r, struct passwd *pw) { return (asprintf(&r->buf, "%s", pw->pw_name)); } int parent_uid(struct ident_resolver *r, struct passwd *pw) { return (asprintf(&r->buf, "%u", (u_int)pw->pw_uid)); } int parent_token(struct ident_resolver *r, struct passwd *pw) { const char *token; int rv; token = gentoken(); rv = asprintf(&r->buf, "%s", token); if (rv != -1) { if (pw) lnotice("token %s == uid %u (%s)", token, (u_int)pw->pw_uid, pw->pw_name); else lnotice("token %s == NO USER", token); } return (rv); } void parent_noident(struct ident_resolver *r, struct passwd *pw) { char path[PATH_MAX]; struct stat st; int rv; rv = snprintf(path, sizeof(path), "%s/%s", pw->pw_dir, DOTNOIDENT); if (rv < 0 || rv >= sizeof(path)) { r->error = E_UNKNOWN; return; } if (stat(path, &st) == -1) return; r->error = E_HIDDEN; } 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) { switch (errno) { case EINTR: case EAGAIN: event_add(&proc_wr, NULL); return; default: 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); if (!SIMPLEQ_EMPTY(&sc.parent.replies)) event_add(&proc_wr, NULL); } 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 */ if (c->state == S_DEAD) { free(c); return; } c->state = S_DEAD; 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 : %s\r\n", c->server.port, c->client.port, unknown_err ? "UNKNOWN-ERROR" : "NO-USER"); 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, NULL); return; fail: identd_close(c); } void child_wr(int fd, short events, void *arg) { struct ident_client *c = SIMPLEQ_FIRST(&sc.child.pushing); const char *errstr = NULL; ssize_t n; n = write(fd, &c->uid, sizeof(c->uid)); switch (n) { case -1: switch (errno) { case EINTR: case EAGAIN: event_add(&proc_wr, NULL); return; case ENOBUFS: /* parent has a backlog of requests */ errstr = "UNKNOWN-ERROR"; break; default: lerr(1, "child write"); } break; case sizeof(c->uid): break; default: lerrx(1, "unexpected child write length %zd", n); } SIMPLEQ_REMOVE_HEAD(&sc.child.pushing, entry); if (errstr == NULL) SIMPLEQ_INSERT_TAIL(&sc.child.popping, c, entry); else if (identd_error(c, errstr) == -1) identd_close(c); if (!SIMPLEQ_EMPTY(&sc.child.pushing)) event_add(&proc_wr, NULL); } void identd_listen(const char *addr, const char *port, int family) { struct identd_listener *l = NULL; struct addrinfo hints, *res, *res0; int error, s; const char *cause = NULL; int on = 1; 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 | SOCK_NONBLOCK, 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) { int serrno = errno; cause = "bind"; close(s); errno = serrno; continue; } if (listen(s, 5) == -1) err(1, "listen"); l = calloc(1, sizeof(*l)); if (l == NULL) err(1, "listener ev alloc"); event_set(&l->ev, s, EV_READ | EV_PERSIST, identd_accept, l); event_add(&l->ev, NULL); evtimer_set(&l->pause, identd_paused, l); } if (l == NULL) err(1, "%s", cause); freeaddrinfo(res0); } void identd_paused(int fd, short events, void *arg) { struct identd_listener *l = arg; event_add(&l->ev, NULL); } void identd_accept(int fd, short events, void *arg) { struct identd_listener *l = arg; struct sockaddr_storage ss; struct timeval pause = { 1, 0 }; struct ident_client *c = NULL; socklen_t len; int s; len = sizeof(ss); s = accept4(fd, sa(&ss), &len, SOCK_NONBLOCK); if (s == -1) { switch (errno) { case EINTR: case EWOULDBLOCK: case ECONNABORTED: return; case EMFILE: case ENFILE: event_del(&l->ev); evtimer_add(&l->pause, &pause); return; default: lerr(1, "accept"); } } 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, NULL); evtimer_set(&c->tmo, identd_timeout, c); evtimer_add(&c->tmo, &timeout); } void identd_timeout(int fd, short events, void *arg) { struct ident_client *c = arg; event_del(&c->ev); close(fd); free(c->buf); if (c->state == S_QUEUED) /* it is queued for resolving */ c->state = S_DEAD; else free(c); } void identd_request(int fd, short events, void *arg) { struct ident_client *c = arg; unsigned char buf[64]; ssize_t n, i; char *errstr = unknown_err ? "UNKNOWN-ERROR" : "INVALID-PORT"; 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; } c->rxbytes += n; if (c->rxbytes >= INPUT_MAX) goto fail; 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; if (fetchuid(c) == -1) { errstr = unknown_err ? "UNKNOWN-ERROR" : "NO-USER"; goto error; } SIMPLEQ_INSERT_TAIL(&sc.child.pushing, c, entry); c->state = S_QUEUED; event_del(&c->ev); event_set(&c->ev, fd, EV_READ | EV_PERSIST, identd_resolving, c); event_add(&c->ev, NULL); event_add(&proc_wr, NULL); return; error: if (identd_error(c, errstr) == -1) goto fail; return; fail: identd_close(c); } int identd_error(struct ident_client *c, const char *errstr) { int fd = EVENT_FD(&c->ev); ssize_t n; n = asprintf(&c->buf, "%u , %u : ERROR : %s\r\n", c->server.port, c->client.port, errstr); if (n == -1) return (-1); 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, NULL); return (0); } void identd_close(struct ident_client *c) { int fd = EVENT_FD(&c->ev); evtimer_del(&c->tmo); event_del(&c->ev); close(fd); free(c->buf); 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. */ n = read(fd, buf, sizeof(buf)); switch (n) { case -1: switch (errno) { case EINTR: case EAGAIN: return; default: lwarn("resolving read"); break; } break; case 0: ldebug("%s closed connection during resolving", gethost(&c->client.ss)); break; default: c->rxbytes += n; if (c->rxbytes >= INPUT_MAX) break; /* ignore extra input */ return; } evtimer_del(&c->tmo); event_del(&c->ev); close(fd); c->state = S_DEAD; /* on the resolving queue */ } 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_READ) { n = read(fd, buf, sizeof(buf)); switch (n) { case -1: switch (errno) { case EINTR: case EAGAIN: /* meh, try a write */ break; default: lwarn("response read"); goto done; } break; case 0: ldebug("%s closed connection during response", gethost(&c->client.ss)); goto done; default: c->rxbytes += n; if (c->rxbytes >= INPUT_MAX) goto done; /* ignore extra input */ 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 EINTR: case EAGAIN: return; /* try again later */ case EPIPE: goto done; default: lwarn("response write"); goto done; } } c->bufoff += n; if (c->bufoff != c->buflen) return; /* try again later */ done: identd_close(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_CRIT, fmt, ap); va_end(ap); exit(ecode); } void syslog_errx(int ecode, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsyslog(LOG_CRIT, fmt, ap); va_end(ap); exit(ecode); } void syslog_warn(const char *fmt, ...) { va_list ap; va_start(ap, fmt); syslog_vstrerror(errno, LOG_ERR, fmt, ap); va_end(ap); } void syslog_warnx(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsyslog(LOG_ERR, fmt, ap); va_end(ap); } void syslog_notice(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsyslog(LOG_NOTICE, 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 * gentoken(void) { static char buf[21]; u_int32_t r; int i; buf[0] = 'a' + arc4random_uniform(26); for (i = 1; i < sizeof(buf) - 1; i++) { r = arc4random_uniform(36); buf[i] = (r < 26 ? 'a' : '0' - 26) + r; } buf[i] = '\0'; 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); }