summaryrefslogtreecommitdiff
path: root/src/lib/apputils
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/apputils')
-rw-r--r--src/lib/apputils/Makefile.in33
-rw-r--r--src/lib/apputils/daemon.c96
-rw-r--r--src/lib/apputils/deps43
-rw-r--r--src/lib/apputils/net-server.c1580
-rw-r--r--src/lib/apputils/udppktinfo.c525
-rw-r--r--src/lib/apputils/udppktinfo.h58
6 files changed, 2335 insertions, 0 deletions
diff --git a/src/lib/apputils/Makefile.in b/src/lib/apputils/Makefile.in
new file mode 100644
index 000000000000..93e3d87ee28f
--- /dev/null
+++ b/src/lib/apputils/Makefile.in
@@ -0,0 +1,33 @@
+prefix=@prefix@
+bindir=@bindir@
+datadir=@datadir@
+mydatadir=$(datadir)/apputils
+mydir=lib$(S)apputils
+BUILDTOP=$(REL)..$(S)..
+RELDIR=../lib/apputils
+SED = sed
+
+##DOS##BUILDTOP = ..\..
+##DOS##LIBNAME=$(OUTPRE)apputils.lib
+##DOS##XTRA=
+##DOS##OBJFILE=$(OUTPRE)apputils.lst
+
+STLIBOBJS=net-server.o udppktinfo.o @LIBOBJS@
+LIBBASE=apputils
+
+all-unix: all-liblinks
+clean-unix:: clean-liblinks clean-libs clean-libobjs
+install-unix: install-libs
+
+LINTFLAGS=-uhvb
+LINTFILES= daemon.c
+LIBOBJS=$(OUTPRE)daemon.$(OBJEXT)
+
+SRCS= $(srcdir)/daemon.c \
+ $(srcdir)/net-server.c \
+ $(srcdir)/udppktinfo.c
+
+@libpriv_frag@
+@lib_frag@
+@libobj_frag@
+
diff --git a/src/lib/apputils/daemon.c b/src/lib/apputils/daemon.c
new file mode 100644
index 000000000000..a3d7cd737304
--- /dev/null
+++ b/src/lib/apputils/daemon.c
@@ -0,0 +1,96 @@
+/* -*- mode: c; c-file-style: "bsd"; indent-tabs-mode: t -*- */
+/*
+ * Copyright (c) 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "k5-int.h"
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/file.h>
+#include <unistd.h>
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+
+#ifndef _PATH_DEVNULL
+#define _PATH_DEVNULL "/dev/null"
+#endif
+
+int
+daemon(nochdir, noclose)
+ int nochdir, noclose;
+{
+ int cpid;
+
+ if ((cpid = fork()) == -1)
+ return (-1);
+ if (cpid)
+ exit(0);
+#ifdef HAVE_SETSID
+ (void) setsid();
+#else
+#ifndef TIOCNOTTY
+ setpgrp();
+#else
+ {
+ int n;
+
+ /*
+ * The open below may hang on pseudo ttys if the person
+ * who starts named logs out before this point. Thus,
+ * the need for the timer.
+ */
+ alarm(120);
+ n = open("/dev/tty", O_RDWR);
+ alarm(0);
+ if (n > 0) {
+ (void) ioctl(n, TIOCNOTTY, (char *)NULL);
+ (void) close(n);
+ }
+ }
+#endif
+#endif
+ if (!nochdir)
+ (void) chdir("/");
+ if (!noclose) {
+ int devnull = open(_PATH_DEVNULL, O_RDWR, 0);
+
+ if (devnull != -1) {
+ (void) dup2(devnull, 0);
+ (void) dup2(devnull, 1);
+ (void) dup2(devnull, 2);
+ if (devnull > 2)
+ (void) close(devnull);
+ }
+ }
+ return (0);
+}
diff --git a/src/lib/apputils/deps b/src/lib/apputils/deps
new file mode 100644
index 000000000000..9605f5c90d4f
--- /dev/null
+++ b/src/lib/apputils/deps
@@ -0,0 +1,43 @@
+#
+# Generated makefile dependencies follow.
+#
+daemon.so daemon.po $(OUTPRE)daemon.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
+ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
+ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
+ $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
+ $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
+ $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
+ $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
+ $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
+ $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h daemon.c
+net-server.so net-server.po $(OUTPRE)net-server.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/gssapi/gssapi.h \
+ $(BUILDTOP)/include/gssrpc/types.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(VERTO_DEPS) $(top_srcdir)/include/adm_proto.h \
+ $(top_srcdir)/include/fake-addrinfo.h $(top_srcdir)/include/foreachaddr.h \
+ $(top_srcdir)/include/gssrpc/auth.h $(top_srcdir)/include/gssrpc/auth_gss.h \
+ $(top_srcdir)/include/gssrpc/auth_unix.h $(top_srcdir)/include/gssrpc/clnt.h \
+ $(top_srcdir)/include/gssrpc/rename.h $(top_srcdir)/include/gssrpc/rpc.h \
+ $(top_srcdir)/include/gssrpc/rpc_msg.h $(top_srcdir)/include/gssrpc/svc.h \
+ $(top_srcdir)/include/gssrpc/svc_auth.h $(top_srcdir)/include/gssrpc/xdr.h \
+ $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/net-server.h $(top_srcdir)/include/port-sockets.h \
+ $(top_srcdir)/include/socket-utils.h net-server.c udppktinfo.h
+udppktinfo.so udppktinfo.po $(OUTPRE)udppktinfo.$(OBJEXT): \
+ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
+ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
+ $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \
+ $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \
+ $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \
+ $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \
+ $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \
+ $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
+ $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
+ udppktinfo.c udppktinfo.h
diff --git a/src/lib/apputils/net-server.c b/src/lib/apputils/net-server.c
new file mode 100644
index 000000000000..29ec84a15b22
--- /dev/null
+++ b/src/lib/apputils/net-server.c
@@ -0,0 +1,1580 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/apputils/net-server.c - Network code for krb5 servers (kdc, kadmind) */
+/*
+ * Copyright 1990,2000,2007,2008,2009,2010,2016 by the Massachusetts Institute
+ * of Technology.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include "adm_proto.h"
+#include <sys/ioctl.h>
+#include <syslog.h>
+
+#include <stddef.h>
+#include "port-sockets.h"
+#include "socket-utils.h"
+
+#include <gssrpc/rpc.h>
+
+#ifdef HAVE_NETINET_IN_H
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#ifdef HAVE_SYS_SOCKIO_H
+/* for SIOCGIFCONF, etc. */
+#include <sys/sockio.h>
+#endif
+#include <sys/time.h>
+#if HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#include <arpa/inet.h>
+
+#ifndef ARPHRD_ETHER /* OpenBSD breaks on multiple inclusions */
+#include <net/if.h>
+#endif
+
+#ifdef HAVE_SYS_FILIO_H
+#include <sys/filio.h> /* FIONBIO */
+#endif
+
+#include "fake-addrinfo.h"
+#include "net-server.h"
+#include <signal.h>
+#include <netdb.h>
+
+#include "udppktinfo.h"
+
+/* XXX */
+#define KDC5_NONET (-1779992062L)
+
+static int tcp_or_rpc_data_counter;
+static int max_tcp_or_rpc_data_connections = 45;
+
+static int
+setreuseaddr(int sock, int value)
+{
+ return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
+}
+
+#if defined(IPV6_V6ONLY)
+static int
+setv6only(int sock, int value)
+{
+ return setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value));
+}
+#endif
+
+static const char *
+paddr(struct sockaddr *sa)
+{
+ static char buf[100];
+ char portbuf[10];
+ if (getnameinfo(sa, sa_socklen(sa),
+ buf, sizeof(buf), portbuf, sizeof(portbuf),
+ NI_NUMERICHOST|NI_NUMERICSERV))
+ strlcpy(buf, "<unprintable>", sizeof(buf));
+ else {
+ unsigned int len = sizeof(buf) - strlen(buf);
+ char *p = buf + strlen(buf);
+ if (len > 2+strlen(portbuf)) {
+ *p++ = '.';
+ len--;
+ strncpy(p, portbuf, len);
+ }
+ }
+ return buf;
+}
+
+/* Return true if sa is an IPv4 or IPv6 wildcard address. */
+static int
+is_wildcard(struct sockaddr *sa)
+{
+ if (sa->sa_family == AF_INET6)
+ return IN6_IS_ADDR_UNSPECIFIED(&sa2sin6(sa)->sin6_addr);
+ else if (sa->sa_family == AF_INET)
+ return sa2sin(sa)->sin_addr.s_addr == INADDR_ANY;
+ return 0;
+}
+
+/* KDC data. */
+
+enum conn_type {
+ CONN_UDP, CONN_TCP_LISTENER, CONN_TCP, CONN_RPC_LISTENER, CONN_RPC
+};
+
+enum bind_type {
+ UDP, TCP, RPC
+};
+
+static const char *const bind_type_names[] = {
+ [UDP] = "UDP",
+ [TCP] = "TCP",
+ [RPC] = "RPC",
+};
+
+/* Per-connection info. */
+struct connection {
+ void *handle;
+ const char *prog;
+ enum conn_type type;
+
+ /* Connection fields (TCP or RPC) */
+ struct sockaddr_storage addr_s;
+ socklen_t addrlen;
+ char addrbuf[56];
+ krb5_fulladdr faddr;
+ krb5_address kaddr;
+
+ /* Incoming data (TCP) */
+ size_t bufsiz;
+ size_t offset;
+ char *buffer;
+ size_t msglen;
+
+ /* Outgoing data (TCP) */
+ krb5_data *response;
+ unsigned char lenbuf[4];
+ sg_buf sgbuf[2];
+ sg_buf *sgp;
+ int sgnum;
+
+ /* Crude denial-of-service avoidance support (TCP or RPC) */
+ time_t start_time;
+
+ /* RPC-specific fields */
+ SVCXPRT *transp;
+ int rpc_force_close;
+};
+
+#define SET(TYPE) struct { TYPE *data; size_t n, max; }
+
+/* Start at the top and work down -- this should allow for deletions
+ without disrupting the iteration, since we delete by overwriting
+ the element to be removed with the last element. */
+#define FOREACH_ELT(set,idx,vvar) \
+ for (idx = set.n-1; idx >= 0 && (vvar = set.data[idx], 1); idx--)
+
+#define GROW_SET(set, incr, tmpptr) \
+ ((set.max + incr < set.max \
+ || ((set.max + incr) * sizeof(set.data[0]) / sizeof(set.data[0]) \
+ != set.max + incr)) \
+ ? 0 /* overflow */ \
+ : ((tmpptr = realloc(set.data, \
+ (set.max + incr) * sizeof(set.data[0]))) \
+ ? (set.data = tmpptr, set.max += incr, 1) \
+ : 0))
+
+/* 1 = success, 0 = failure */
+#define ADD(set, val, tmpptr) \
+ ((set.n < set.max || GROW_SET(set, 10, tmpptr)) \
+ ? (set.data[set.n++] = val, 1) \
+ : 0)
+
+#define DEL(set, idx) \
+ (set.data[idx] = set.data[--set.n], 0)
+
+#define FREE_SET_DATA(set) \
+ (free(set.data), set.data = 0, set.max = 0, set.n = 0)
+
+/*
+ * N.B.: The Emacs cc-mode indentation code seems to get confused if
+ * the macro argument here is one word only. So use "unsigned short"
+ * instead of the "u_short" we were using before.
+ */
+struct rpc_svc_data {
+ u_long prognum;
+ u_long versnum;
+ void (*dispatch)();
+};
+
+struct bind_address {
+ char *address;
+ u_short port;
+ enum bind_type type;
+ struct rpc_svc_data rpc_svc_data;
+};
+
+static SET(verto_ev *) events;
+static SET(struct bind_address) bind_addresses;
+
+verto_ctx *
+loop_init(verto_ev_type types)
+{
+ types |= VERTO_EV_TYPE_IO;
+ types |= VERTO_EV_TYPE_SIGNAL;
+ types |= VERTO_EV_TYPE_TIMEOUT;
+ return verto_default(NULL, types);
+}
+
+static void
+do_break(verto_ctx *ctx, verto_ev *ev)
+{
+ krb5_klog_syslog(LOG_DEBUG, _("Got signal to request exit"));
+ verto_break(ctx);
+}
+
+struct sighup_context {
+ void *handle;
+ void (*reset)(void *);
+};
+
+static void
+do_reset(verto_ctx *ctx, verto_ev *ev)
+{
+ struct sighup_context *sc = (struct sighup_context*) verto_get_private(ev);
+
+ krb5_klog_syslog(LOG_DEBUG, _("Got signal to reset"));
+ krb5_klog_reopen(get_context(sc->handle));
+ if (sc->reset)
+ sc->reset(sc->handle);
+}
+
+static void
+free_sighup_context(verto_ctx *ctx, verto_ev *ev)
+{
+ free(verto_get_private(ev));
+}
+
+krb5_error_code
+loop_setup_signals(verto_ctx *ctx, void *handle, void (*reset)())
+{
+ struct sighup_context *sc;
+ verto_ev *ev;
+
+ if (!verto_add_signal(ctx, VERTO_EV_FLAG_PERSIST, do_break, SIGINT) ||
+ !verto_add_signal(ctx, VERTO_EV_FLAG_PERSIST, do_break, SIGTERM) ||
+ !verto_add_signal(ctx, VERTO_EV_FLAG_PERSIST, do_break, SIGQUIT) ||
+ !verto_add_signal(ctx, VERTO_EV_FLAG_PERSIST, VERTO_SIG_IGN, SIGPIPE))
+ return ENOMEM;
+
+ ev = verto_add_signal(ctx, VERTO_EV_FLAG_PERSIST, do_reset, SIGHUP);
+ if (!ev)
+ return ENOMEM;
+
+ sc = malloc(sizeof(*sc));
+ if (!sc)
+ return ENOMEM;
+
+ sc->handle = handle;
+ sc->reset = reset;
+ verto_set_private(ev, sc, free_sighup_context);
+ return 0;
+}
+
+/*
+ * Add a bind address to the loop.
+ *
+ * Arguments:
+ * - address
+ * A string for the address (or hostname). Pass NULL to use the wildcard
+ * address. The address is parsed with k5_parse_host_string().
+ * - port
+ * What port the socket should be set to.
+ * - type
+ * bind_type for the socket.
+ * - rpc_data
+ * For RPC addresses, the svc_register() arguments to use when TCP
+ * connections are created. Ignored for other types.
+ */
+static krb5_error_code
+loop_add_address(const char *address, int port, enum bind_type type,
+ struct rpc_svc_data *rpc_data)
+{
+ struct bind_address addr, val;
+ int i;
+ void *tmp;
+ char *addr_copy = NULL;
+
+ assert(!(type == RPC && rpc_data == NULL));
+
+ /* Make sure a valid port number was passed. */
+ if (port < 0 || port > 65535) {
+ krb5_klog_syslog(LOG_ERR, _("Invalid port %d"), port);
+ return EINVAL;
+ }
+
+ /* Check for conflicting addresses. */
+ FOREACH_ELT(bind_addresses, i, val) {
+ if (type != val.type || port != val.port)
+ continue;
+
+ /* If a wildcard address is being added, make sure to remove any direct
+ * addresses. */
+ if (address == NULL && val.address != NULL) {
+ krb5_klog_syslog(LOG_DEBUG,
+ _("Removing address %s since wildcard address"
+ " is being added"),
+ val.address);
+ free(val.address);
+ DEL(bind_addresses, i);
+ } else if (val.address == NULL || !strcmp(address, val.address)) {
+ krb5_klog_syslog(LOG_DEBUG,
+ _("Address already added to server"));
+ return 0;
+ }
+ }
+
+ /* Copy the address if it is specified. */
+ if (address != NULL) {
+ addr_copy = strdup(address);
+ if (addr_copy == NULL)
+ return ENOMEM;
+ }
+
+ /* Add the new address to bind_addresses. */
+ memset(&addr, 0, sizeof(addr));
+ addr.address = addr_copy;
+ addr.port = port;
+ addr.type = type;
+ if (rpc_data != NULL)
+ addr.rpc_svc_data = *rpc_data;
+ if (!ADD(bind_addresses, addr, tmp)) {
+ free(addr_copy);
+ return ENOMEM;
+ }
+
+ return 0;
+}
+
+/*
+ * Add bind addresses to the loop.
+ *
+ * Arguments:
+ *
+ * - addresses
+ * A string for the addresses. Pass NULL to use the wildcard address.
+ * Supported delimeters can be found in ADDRESSES_DELIM. Addresses are
+ * parsed with k5_parse_host_name().
+ * - default_port
+ * What port the socket should be set to if not specified in addresses.
+ * - type
+ * bind_type for the socket.
+ * - rpc_data
+ * For RPC addresses, the svc_register() arguments to use when TCP
+ * connections are created. Ignored for other types.
+ */
+static krb5_error_code
+loop_add_addresses(const char *addresses, int default_port,
+ enum bind_type type, struct rpc_svc_data *rpc_data)
+{
+ krb5_error_code ret = 0;
+ char *addresses_copy = NULL, *host = NULL, *saveptr, *addr;
+ int port;
+
+ /* If no addresses are set, add a wildcard address. */
+ if (addresses == NULL)
+ return loop_add_address(NULL, default_port, type, rpc_data);
+
+ /* Copy the addresses string before using strtok(). */
+ addresses_copy = strdup(addresses);
+ if (addresses_copy == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+
+ /* Start tokenizing the addresses string. If we get NULL the string
+ * contained no addresses, so add a wildcard address. */
+ addr = strtok_r(addresses_copy, ADDRESSES_DELIM, &saveptr);
+ if (addr == NULL) {
+ ret = loop_add_address(NULL, default_port, type, rpc_data);
+ goto cleanup;
+ }
+
+ /* Loop through each address and add it to the loop. */
+ for (; addr != NULL; addr = strtok_r(NULL, ADDRESSES_DELIM, &saveptr)) {
+ /* Parse the host string. */
+ ret = k5_parse_host_string(addr, default_port, &host, &port);
+ if (ret)
+ goto cleanup;
+
+ ret = loop_add_address(host, port, type, rpc_data);
+ if (ret)
+ goto cleanup;
+
+ free(host);
+ host = NULL;
+ }
+
+cleanup:
+ free(addresses_copy);
+ free(host);
+ return ret;
+}
+
+krb5_error_code
+loop_add_udp_address(int default_port, const char *addresses)
+{
+ return loop_add_addresses(addresses, default_port, UDP, NULL);
+}
+
+krb5_error_code
+loop_add_tcp_address(int default_port, const char *addresses)
+{
+ return loop_add_addresses(addresses, default_port, TCP, NULL);
+}
+
+krb5_error_code
+loop_add_rpc_service(int default_port, const char *addresses, u_long prognum,
+ u_long versnum, void (*dispatchfn)())
+{
+ struct rpc_svc_data svc;
+
+ svc.prognum = prognum;
+ svc.versnum = versnum;
+ svc.dispatch = dispatchfn;
+ return loop_add_addresses(addresses, default_port, RPC, &svc);
+}
+
+#define USE_AF AF_INET
+#define USE_TYPE SOCK_DGRAM
+#define USE_PROTO 0
+#define SOCKET_ERRNO errno
+#include "foreachaddr.h"
+
+struct socksetup {
+ verto_ctx *ctx;
+ void *handle;
+ const char *prog;
+ krb5_error_code retval;
+ int listen_backlog;
+};
+
+static void
+free_connection(struct connection *conn)
+{
+ if (!conn)
+ return;
+ if (conn->response)
+ krb5_free_data(get_context(conn->handle), conn->response);
+ if (conn->buffer)
+ free(conn->buffer);
+ if (conn->type == CONN_RPC_LISTENER && conn->transp != NULL)
+ svc_destroy(conn->transp);
+ free(conn);
+}
+
+static void
+remove_event_from_set(verto_ev *ev)
+{
+ verto_ev *tmp;
+ int i;
+
+ /* Remove the event from the events. */
+ FOREACH_ELT(events, i, tmp)
+ if (tmp == ev) {
+ DEL(events, i);
+ break;
+ }
+}
+
+static void
+free_socket(verto_ctx *ctx, verto_ev *ev)
+{
+ struct connection *conn = NULL;
+ fd_set fds;
+ int fd;
+
+ remove_event_from_set(ev);
+
+ fd = verto_get_fd(ev);
+ conn = verto_get_private(ev);
+
+ /* Close the file descriptor. */
+ krb5_klog_syslog(LOG_INFO, _("closing down fd %d"), fd);
+ if (fd >= 0 && (!conn || conn->type != CONN_RPC || conn->rpc_force_close))
+ close(fd);
+
+ /* Free the connection struct. */
+ if (conn) {
+ switch (conn->type) {
+ case CONN_RPC:
+ if (conn->rpc_force_close) {
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+ svc_getreqset(&fds);
+ if (FD_ISSET(fd, &svc_fdset)) {
+ krb5_klog_syslog(LOG_ERR,
+ _("descriptor %d closed but still "
+ "in svc_fdset"),
+ fd);
+ }
+ }
+ /* Fall through. */
+ case CONN_TCP:
+ tcp_or_rpc_data_counter--;
+ break;
+ default:
+ break;
+ }
+
+ free_connection(conn);
+ }
+}
+
+static verto_ev *
+make_event(verto_ctx *ctx, verto_ev_flag flags, verto_callback callback,
+ int sock, struct connection *conn, int addevent)
+{
+ verto_ev *ev;
+ void *tmp;
+
+ ev = verto_add_io(ctx, flags, callback, sock);
+ if (!ev) {
+ com_err(conn->prog, ENOMEM, _("cannot create io event"));
+ return NULL;
+ }
+
+ if (addevent) {
+ if (!ADD(events, ev, tmp)) {
+ com_err(conn->prog, ENOMEM, _("cannot save event"));
+ verto_del(ev);
+ return NULL;
+ }
+ }
+
+ verto_set_private(ev, conn, free_socket);
+ return ev;
+}
+
+static verto_ev *
+add_fd(struct socksetup *data, int sock, enum conn_type conntype,
+ verto_ev_flag flags, verto_callback callback, int addevent)
+{
+ struct connection *newconn;
+
+#ifndef _WIN32
+ if (sock >= FD_SETSIZE) {
+ data->retval = EMFILE; /* XXX */
+ com_err(data->prog, 0,
+ _("file descriptor number %d too high"), sock);
+ return 0;
+ }
+#endif
+ newconn = malloc(sizeof(*newconn));
+ if (newconn == NULL) {
+ data->retval = ENOMEM;
+ com_err(data->prog, ENOMEM,
+ _("cannot allocate storage for connection info"));
+ return 0;
+ }
+ memset(newconn, 0, sizeof(*newconn));
+ newconn->handle = data->handle;
+ newconn->prog = data->prog;
+ newconn->type = conntype;
+
+ return make_event(data->ctx, flags, callback, sock, newconn, addevent);
+}
+
+static void process_packet(verto_ctx *ctx, verto_ev *ev);
+static void accept_tcp_connection(verto_ctx *ctx, verto_ev *ev);
+static void process_tcp_connection_read(verto_ctx *ctx, verto_ev *ev);
+static void process_tcp_connection_write(verto_ctx *ctx, verto_ev *ev);
+static void accept_rpc_connection(verto_ctx *ctx, verto_ev *ev);
+static void process_rpc_connection(verto_ctx *ctx, verto_ev *ev);
+
+static verto_ev *
+add_tcp_read_fd(struct socksetup *data, int sock)
+{
+ return add_fd(data, sock, CONN_TCP,
+ VERTO_EV_FLAG_IO_READ | VERTO_EV_FLAG_PERSIST,
+ process_tcp_connection_read, 1);
+}
+
+/*
+ * Create a socket and bind it to addr. Ensure the socket will work with
+ * select(). Set the socket cloexec, reuseaddr, and if applicable v6-only.
+ * Does not call listen(). Returns -1 on failure after logging an error.
+ */
+static int
+create_server_socket(struct socksetup *data, struct sockaddr *addr, int type)
+{
+ int sock;
+
+ sock = socket(addr->sa_family, type, 0);
+ if (sock == -1) {
+ data->retval = errno;
+ com_err(data->prog, errno, _("Cannot create TCP server socket on %s"),
+ paddr(addr));
+ return -1;
+ }
+ set_cloexec_fd(sock);
+
+#ifndef _WIN32 /* Windows FD_SETSIZE is a count. */
+ if (sock >= FD_SETSIZE) {
+ close(sock);
+ com_err(data->prog, 0, _("TCP socket fd number %d (for %s) too high"),
+ sock, paddr(addr));
+ return -1;
+ }
+#endif
+
+ if (setreuseaddr(sock, 1) < 0) {
+ com_err(data->prog, errno,
+ _("Cannot enable SO_REUSEADDR on fd %d"), sock);
+ }
+
+ if (addr->sa_family == AF_INET6) {
+#ifdef IPV6_V6ONLY
+ if (setv6only(sock, 1))
+ com_err(data->prog, errno,
+ _("setsockopt(%d,IPV6_V6ONLY,1) failed"), sock);
+ else
+ com_err(data->prog, 0, _("setsockopt(%d,IPV6_V6ONLY,1) worked"),
+ sock);
+#else
+ krb5_klog_syslog(LOG_INFO, _("no IPV6_V6ONLY socket option support"));
+#endif /* IPV6_V6ONLY */
+ }
+
+ if (bind(sock, addr, sa_socklen(addr)) == -1) {
+ data->retval = errno;
+ com_err(data->prog, errno, _("Cannot bind server socket on %s"),
+ paddr(addr));
+ close(sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+static verto_ev *
+add_rpc_data_fd(struct socksetup *data, int sock)
+{
+ return add_fd(data, sock, CONN_RPC,
+ VERTO_EV_FLAG_IO_READ | VERTO_EV_FLAG_PERSIST,
+ process_rpc_connection, 1);
+}
+
+static const int one = 1;
+
+static int
+setnbio(int sock)
+{
+ return ioctlsocket(sock, FIONBIO, (const void *)&one);
+}
+
+static int
+setkeepalive(int sock)
+{
+ return setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one));
+}
+
+static int
+setnolinger(int s)
+{
+ static const struct linger ling = { 0, 0 };
+ return setsockopt(s, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
+}
+
+/* An enum map to socket families for each bind_type. */
+static const int bind_socktypes[] =
+{
+ [UDP] = SOCK_DGRAM,
+ [TCP] = SOCK_STREAM,
+ [RPC] = SOCK_STREAM
+};
+
+/* An enum map containing conn_type (for struct connection) for each
+ * bind_type. */
+static const enum conn_type bind_conn_types[] =
+{
+ [UDP] = CONN_UDP,
+ [TCP] = CONN_TCP_LISTENER,
+ [RPC] = CONN_RPC_LISTENER
+};
+
+/*
+ * Set up a listening socket.
+ *
+ * Arguments:
+ *
+ * - ba
+ * The bind address and port for the socket.
+ * - ai
+ * The addrinfo struct to use for creating the socket.
+ * - ctype
+ * The conn_type of this socket.
+ */
+static krb5_error_code
+setup_socket(struct socksetup *data, struct bind_address *ba,
+ struct sockaddr *sock_address, verto_callback vcb,
+ enum conn_type ctype)
+{
+ krb5_error_code ret;
+ struct connection *conn;
+ verto_ev *ev = NULL;
+ int sock = -1;
+
+ krb5_klog_syslog(LOG_DEBUG, _("Setting up %s socket for address %s"),
+ bind_type_names[ba->type], paddr(sock_address));
+
+ /* Create the socket. */
+ sock = create_server_socket(data, sock_address, bind_socktypes[ba->type]);
+ if (sock == -1) {
+ ret = data->retval;
+ goto cleanup;
+ }
+
+ /* Listen for backlogged connections on TCP sockets. (For RPC sockets this
+ * will be done by svc_register().) */
+ if (ba->type == TCP && listen(sock, data->listen_backlog) != 0) {
+ ret = errno;
+ com_err(data->prog, errno,
+ _("Cannot listen on %s server socket on %s"),
+ bind_type_names[ba->type], paddr(sock_address));
+ goto cleanup;
+ }
+
+ /* Set non-blocking I/O for UDP and TCP listener sockets. */
+ if (ba->type != RPC && setnbio(sock) != 0) {
+ ret = errno;
+ com_err(data->prog, errno,
+ _("cannot set listening %s socket on %s non-blocking"),
+ bind_type_names[ba->type], paddr(sock_address));
+ goto cleanup;
+ }
+
+ /* Turn off the linger option for TCP sockets. */
+ if (ba->type == TCP && setnolinger(sock) != 0) {
+ ret = errno;
+ com_err(data->prog, errno,
+ _("cannot set SO_LINGER on %s socket on %s"),
+ bind_type_names[ba->type], paddr(sock_address));
+ goto cleanup;
+ }
+
+ /* Try to turn on pktinfo for UDP wildcard sockets. */
+ if (ba->type == UDP && is_wildcard(sock_address)) {
+ krb5_klog_syslog(LOG_DEBUG, _("Setting pktinfo on socket %s"),
+ paddr(sock_address));
+ ret = set_pktinfo(sock, sock_address->sa_family);
+ if (ret) {
+ com_err(data->prog, ret,
+ _("Cannot request packet info for UDP socket address "
+ "%s port %d"), paddr(sock_address), ba->port);
+ krb5_klog_syslog(LOG_INFO, _("System does not support pktinfo yet "
+ "binding to a wildcard address. "
+ "Packets are not guaranteed to "
+ "return on the received address."));
+ }
+ }
+
+ /* Add the socket to the event loop. */
+ ev = add_fd(data, sock, ctype,
+ VERTO_EV_FLAG_IO_READ |
+ VERTO_EV_FLAG_PERSIST |
+ VERTO_EV_FLAG_REINITIABLE, vcb, 1);
+ if (ev == NULL) {
+ krb5_klog_syslog(LOG_ERR, _("Error attempting to add verto event"));
+ ret = data->retval;
+ goto cleanup;
+ }
+
+ if (ba->type == RPC) {
+ conn = verto_get_private(ev);
+ conn->transp = svctcp_create(sock, 0, 0);
+ if (conn->transp == NULL) {
+ ret = errno;
+ krb5_klog_syslog(LOG_ERR, _("Cannot create RPC service: %s"),
+ strerror(ret));
+ goto cleanup;
+ }
+
+ ret = svc_register(conn->transp, ba->rpc_svc_data.prognum,
+ ba->rpc_svc_data.versnum, ba->rpc_svc_data.dispatch,
+ 0);
+ if (!ret) {
+ ret = errno;
+ krb5_klog_syslog(LOG_ERR, _("Cannot register RPC service: %s"),
+ strerror(ret));
+ goto cleanup;
+ }
+ }
+
+ ev = NULL;
+ sock = -1;
+ ret = 0;
+
+cleanup:
+ if (sock >= 0)
+ close(sock);
+ if (ev != NULL)
+ verto_del(ev);
+ return ret;
+}
+
+/*
+ * Setup all the socket addresses that the net-server should listen to.
+ *
+ * This function uses getaddrinfo to figure out all the addresses. This will
+ * automatically figure out which socket families that should be used on the
+ * host making it useful even for wildcard addresses.
+ *
+ * Arguments:
+ * - data
+ * A pointer to the socksetup data.
+ */
+static krb5_error_code
+setup_addresses(struct socksetup *data)
+{
+ /* An bind_type enum map for the verto callback functions. */
+ static verto_callback *const verto_callbacks[] = {
+ [UDP] = &process_packet,
+ [TCP] = &accept_tcp_connection,
+ [RPC] = &accept_rpc_connection
+ };
+ krb5_error_code ret = 0;
+ size_t i;
+ int err, bound_any;
+ struct bind_address addr;
+ struct addrinfo hints, *ai_list = NULL, *ai = NULL;
+ verto_callback vcb;
+
+ /* Check to make sure addresses were added to the server. */
+ if (bind_addresses.n == 0) {
+ krb5_klog_syslog(LOG_ERR, _("No addresses added to the net server"));
+ return EINVAL;
+ }
+
+ /* Ask for all address families, listener addresses, and no port name
+ * resolution. */
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
+
+ /* Add all the requested addresses. */
+ for (i = 0; i < bind_addresses.n; i++) {
+ addr = bind_addresses.data[i];
+ hints.ai_socktype = bind_socktypes[addr.type];
+
+ /* Call getaddrinfo, using a dummy port value. */
+ err = getaddrinfo(addr.address, "0", &hints, &ai_list);
+ if (err) {
+ krb5_klog_syslog(LOG_ERR,
+ _("Failed getting address info (for %s): %s"),
+ (addr.address == NULL) ? "<wildcard>" :
+ addr.address, gai_strerror(err));
+ ret = EIO;
+ goto cleanup;
+ }
+
+ /*
+ * Loop through all the sockets that getaddrinfo could find to match
+ * the requested address. For wildcard listeners, this should usually
+ * have two results, one for each of IPv4 and IPv6, or one or the
+ * other, depending on the system. On IPv4-only systems, getaddrinfo()
+ * may return both IPv4 and IPv6 addresses, but creating an IPv6 socket
+ * may give an EAFNOSUPPORT error, so tolerate that error as long as we
+ * can bind at least one socket.
+ */
+ bound_any = 0;
+ for (ai = ai_list; ai != NULL; ai = ai->ai_next) {
+ /* Make sure getaddrinfo returned a socket with the same type that
+ * was requested. */
+ assert(hints.ai_socktype == ai->ai_socktype);
+
+ /* Set the real port number. */
+ sa_setport(ai->ai_addr, addr.port);
+
+ ret = setup_socket(data, &addr, ai->ai_addr,
+ verto_callbacks[addr.type],
+ bind_conn_types[addr.type]);
+ if (ret) {
+ krb5_klog_syslog(LOG_ERR,
+ _("Failed setting up a %s socket (for %s)"),
+ bind_type_names[addr.type],
+ paddr(ai->ai_addr));
+ if (ret != EAFNOSUPPORT)
+ goto cleanup;
+ } else {
+ bound_any = 1;
+ }
+ }
+ if (!bound_any)
+ goto cleanup;
+ ret = 0;
+
+ if (ai_list != NULL)
+ freeaddrinfo(ai_list);
+ ai_list = NULL;
+ }
+
+cleanup:
+ if (ai_list != NULL)
+ freeaddrinfo(ai_list);
+ return ret;
+}
+
+krb5_error_code
+loop_setup_network(verto_ctx *ctx, void *handle, const char *prog,
+ int tcp_listen_backlog)
+{
+ struct socksetup setup_data;
+ verto_ev *ev;
+ int i, ret;
+
+ /* Check to make sure that at least one address was added to the loop. */
+ if (bind_addresses.n == 0)
+ return EINVAL;
+
+ /* Close any open connections. */
+ FOREACH_ELT(events, i, ev)
+ verto_del(ev);
+ events.n = 0;
+
+ setup_data.ctx = ctx;
+ setup_data.handle = handle;
+ setup_data.prog = prog;
+ setup_data.retval = 0;
+ setup_data.listen_backlog = tcp_listen_backlog;
+
+ krb5_klog_syslog(LOG_INFO, _("setting up network..."));
+ ret = setup_addresses(&setup_data);
+ if (ret != 0) {
+ com_err(prog, ret, _("Error setting up network"));
+ exit(1);
+ }
+ krb5_klog_syslog (LOG_INFO, _("set up %d sockets"), (int) events.n);
+ if (events.n == 0) {
+ /* If no sockets were set up, we can't continue. */
+ com_err(prog, 0, _("no sockets set up?"));
+ exit (1);
+ }
+
+ return 0;
+}
+
+void
+init_addr(krb5_fulladdr *faddr, struct sockaddr *sa)
+{
+ switch (sa->sa_family) {
+ case AF_INET:
+ faddr->address->addrtype = ADDRTYPE_INET;
+ faddr->address->length = 4;
+ faddr->address->contents = (krb5_octet *) &sa2sin(sa)->sin_addr;
+ faddr->port = ntohs(sa2sin(sa)->sin_port);
+ break;
+ case AF_INET6:
+ if (IN6_IS_ADDR_V4MAPPED(&sa2sin6(sa)->sin6_addr)) {
+ faddr->address->addrtype = ADDRTYPE_INET;
+ faddr->address->length = 4;
+ faddr->address->contents = 12 + (krb5_octet *) &sa2sin6(sa)->sin6_addr;
+ } else {
+ faddr->address->addrtype = ADDRTYPE_INET6;
+ faddr->address->length = 16;
+ faddr->address->contents = (krb5_octet *) &sa2sin6(sa)->sin6_addr;
+ }
+ faddr->port = ntohs(sa2sin6(sa)->sin6_port);
+ break;
+ default:
+ faddr->address->addrtype = -1;
+ faddr->address->length = 0;
+ faddr->address->contents = 0;
+ faddr->port = 0;
+ break;
+ }
+}
+
+struct udp_dispatch_state {
+ void *handle;
+ const char *prog;
+ int port_fd;
+ krb5_address addr;
+ krb5_fulladdr faddr;
+ socklen_t saddr_len;
+ socklen_t daddr_len;
+ struct sockaddr_storage saddr;
+ struct sockaddr_storage daddr;
+ aux_addressing_info auxaddr;
+ krb5_data request;
+ char pktbuf[MAX_DGRAM_SIZE];
+};
+
+static void
+process_packet_response(void *arg, krb5_error_code code, krb5_data *response)
+{
+ struct udp_dispatch_state *state = arg;
+ int cc;
+
+ if (code)
+ com_err(state->prog ? state->prog : NULL, code,
+ _("while dispatching (udp)"));
+ if (code || response == NULL)
+ goto out;
+
+ cc = send_to_from(state->port_fd, response->data,
+ (socklen_t) response->length, 0,
+ (struct sockaddr *)&state->saddr, state->saddr_len,
+ (struct sockaddr *)&state->daddr, state->daddr_len,
+ &state->auxaddr);
+ if (cc == -1) {
+ /* Note that the local address (daddr*) has no port number
+ * info associated with it. */
+ char saddrbuf[NI_MAXHOST], sportbuf[NI_MAXSERV];
+ char daddrbuf[NI_MAXHOST];
+ int e = errno;
+
+ if (getnameinfo((struct sockaddr *)&state->daddr, state->daddr_len,
+ daddrbuf, sizeof(daddrbuf), 0, 0,
+ NI_NUMERICHOST) != 0) {
+ strlcpy(daddrbuf, "?", sizeof(daddrbuf));
+ }
+
+ if (getnameinfo((struct sockaddr *)&state->saddr, state->saddr_len,
+ saddrbuf, sizeof(saddrbuf), sportbuf, sizeof(sportbuf),
+ NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
+ strlcpy(saddrbuf, "?", sizeof(saddrbuf));
+ strlcpy(sportbuf, "?", sizeof(sportbuf));
+ }
+
+ com_err(state->prog, e, _("while sending reply to %s/%s from %s"),
+ saddrbuf, sportbuf, daddrbuf);
+ goto out;
+ }
+ if ((size_t)cc != response->length) {
+ com_err(state->prog, 0, _("short reply write %d vs %d\n"),
+ response->length, cc);
+ }
+
+out:
+ krb5_free_data(get_context(state->handle), response);
+ free(state);
+}
+
+static void
+process_packet(verto_ctx *ctx, verto_ev *ev)
+{
+ int cc;
+ struct connection *conn;
+ struct udp_dispatch_state *state;
+
+ conn = verto_get_private(ev);
+
+ state = malloc(sizeof(*state));
+ if (!state) {
+ com_err(conn->prog, ENOMEM, _("while dispatching (udp)"));
+ return;
+ }
+
+ state->handle = conn->handle;
+ state->prog = conn->prog;
+ state->port_fd = verto_get_fd(ev);
+ assert(state->port_fd >= 0);
+
+ state->saddr_len = sizeof(state->saddr);
+ state->daddr_len = sizeof(state->daddr);
+ memset(&state->auxaddr, 0, sizeof(state->auxaddr));
+ cc = recv_from_to(state->port_fd, state->pktbuf, sizeof(state->pktbuf), 0,
+ (struct sockaddr *)&state->saddr, &state->saddr_len,
+ (struct sockaddr *)&state->daddr, &state->daddr_len,
+ &state->auxaddr);
+ if (cc == -1) {
+ if (errno != EINTR && errno != EAGAIN
+ /*
+ * This is how Linux indicates that a previous transmission was
+ * refused, e.g., if the client timed out before getting the
+ * response packet.
+ */
+ && errno != ECONNREFUSED
+ )
+ com_err(conn->prog, errno, _("while receiving from network"));
+ free(state);
+ return;
+ }
+ if (!cc) { /* zero-length packet? */
+ free(state);
+ return;
+ }
+
+#if 0
+ if (state->daddr_len > 0) {
+ char addrbuf[100];
+ if (getnameinfo(ss2sa(&state->daddr), state->daddr_len,
+ addrbuf, sizeof(addrbuf),
+ 0, 0, NI_NUMERICHOST))
+ strlcpy(addrbuf, "?", sizeof(addrbuf));
+ com_err(conn->prog, 0, _("pktinfo says local addr is %s"), addrbuf);
+ }
+#endif
+
+ if (state->daddr_len == 0 && conn->type == CONN_UDP) {
+ /*
+ * An address couldn't be obtained, so the PKTINFO option probably
+ * isn't available. If the socket is bound to a specific address, then
+ * try to get the address here.
+ */
+ state->daddr_len = sizeof(state->daddr);
+ if (getsockname(state->port_fd, (struct sockaddr *)&state->daddr,
+ &state->daddr_len) != 0)
+ state->daddr_len = 0;
+ /* On failure, keep going anyways. */
+ }
+
+ state->request.length = cc;
+ state->request.data = state->pktbuf;
+ state->faddr.address = &state->addr;
+ init_addr(&state->faddr, ss2sa(&state->saddr));
+ /* This address is in net order. */
+ dispatch(state->handle, ss2sa(&state->daddr), &state->faddr,
+ &state->request, 0, ctx, process_packet_response, state);
+}
+
+static int
+kill_lru_tcp_or_rpc_connection(void *handle, verto_ev *newev)
+{
+ struct connection *c = NULL, *oldest_c = NULL;
+ verto_ev *ev, *oldest_ev = NULL;
+ int i, fd = -1;
+
+ krb5_klog_syslog(LOG_INFO, _("too many connections"));
+
+ FOREACH_ELT (events, i, ev) {
+ if (ev == newev)
+ continue;
+
+ c = verto_get_private(ev);
+ if (!c)
+ continue;
+ if (c->type != CONN_TCP && c->type != CONN_RPC)
+ continue;
+#if 0
+ krb5_klog_syslog(LOG_INFO, "fd %d started at %ld",
+ verto_get_fd(oldest_ev),
+ c->start_time);
+#endif
+ if (oldest_c == NULL
+ || oldest_c->start_time > c->start_time) {
+ oldest_ev = ev;
+ oldest_c = c;
+ }
+ }
+ if (oldest_c != NULL) {
+ krb5_klog_syslog(LOG_INFO, _("dropping %s fd %d from %s"),
+ c->type == CONN_RPC ? "rpc" : "tcp",
+ verto_get_fd(oldest_ev), oldest_c->addrbuf);
+ if (oldest_c->type == CONN_RPC)
+ oldest_c->rpc_force_close = 1;
+ verto_del(oldest_ev);
+ }
+ return fd;
+}
+
+static void
+accept_tcp_connection(verto_ctx *ctx, verto_ev *ev)
+{
+ int s;
+ struct sockaddr_storage addr_s;
+ struct sockaddr *addr = (struct sockaddr *)&addr_s;
+ socklen_t addrlen = sizeof(addr_s);
+ struct socksetup sockdata;
+ struct connection *newconn, *conn;
+ char tmpbuf[10];
+ verto_ev *newev;
+
+ conn = verto_get_private(ev);
+ s = accept(verto_get_fd(ev), addr, &addrlen);
+ if (s < 0)
+ return;
+ set_cloexec_fd(s);
+#ifndef _WIN32
+ if (s >= FD_SETSIZE) {
+ close(s);
+ return;
+ }
+#endif
+ setnbio(s), setnolinger(s), setkeepalive(s);
+
+ sockdata.ctx = ctx;
+ sockdata.handle = conn->handle;
+ sockdata.prog = conn->prog;
+ sockdata.retval = 0;
+
+ newev = add_tcp_read_fd(&sockdata, s);
+ if (newev == NULL) {
+ close(s);
+ return;
+ }
+ newconn = verto_get_private(newev);
+
+ if (getnameinfo((struct sockaddr *)&addr_s, addrlen,
+ newconn->addrbuf, sizeof(newconn->addrbuf),
+ tmpbuf, sizeof(tmpbuf),
+ NI_NUMERICHOST | NI_NUMERICSERV))
+ strlcpy(newconn->addrbuf, "???", sizeof(newconn->addrbuf));
+ else {
+ char *p, *end;
+ p = newconn->addrbuf;
+ end = p + sizeof(newconn->addrbuf);
+ p += strlen(p);
+ if ((size_t)(end - p) > 2 + strlen(tmpbuf)) {
+ *p++ = '.';
+ strlcpy(p, tmpbuf, end - p);
+ }
+ }
+#if 0
+ krb5_klog_syslog(LOG_INFO, "accepted TCP connection on socket %d from %s",
+ s, newconn->addrbuf);
+#endif
+
+ newconn->addr_s = addr_s;
+ newconn->addrlen = addrlen;
+ newconn->bufsiz = 1024 * 1024;
+ newconn->buffer = malloc(newconn->bufsiz);
+ newconn->start_time = time(0);
+
+ if (++tcp_or_rpc_data_counter > max_tcp_or_rpc_data_connections)
+ kill_lru_tcp_or_rpc_connection(conn->handle, newev);
+
+ if (newconn->buffer == 0) {
+ com_err(conn->prog, errno,
+ _("allocating buffer for new TCP session from %s"),
+ newconn->addrbuf);
+ verto_del(newev);
+ return;
+ }
+ newconn->offset = 0;
+ newconn->faddr.address = &newconn->kaddr;
+ init_addr(&newconn->faddr, ss2sa(&newconn->addr_s));
+ SG_SET(&newconn->sgbuf[0], newconn->lenbuf, 4);
+ SG_SET(&newconn->sgbuf[1], 0, 0);
+}
+
+struct tcp_dispatch_state {
+ struct sockaddr_storage local_saddr;
+ struct connection *conn;
+ krb5_data request;
+ verto_ctx *ctx;
+ int sock;
+};
+
+static void
+process_tcp_response(void *arg, krb5_error_code code, krb5_data *response)
+{
+ struct tcp_dispatch_state *state = arg;
+ verto_ev *ev;
+
+ assert(state);
+ state->conn->response = response;
+
+ if (code)
+ com_err(state->conn->prog, code, _("while dispatching (tcp)"));
+ if (code || !response)
+ goto kill_tcp_connection;
+
+ /* Queue outgoing response. */
+ store_32_be(response->length, state->conn->lenbuf);
+ SG_SET(&state->conn->sgbuf[1], response->data, response->length);
+ state->conn->sgp = state->conn->sgbuf;
+ state->conn->sgnum = 2;
+
+ ev = make_event(state->ctx, VERTO_EV_FLAG_IO_WRITE | VERTO_EV_FLAG_PERSIST,
+ process_tcp_connection_write, state->sock, state->conn, 1);
+ if (ev) {
+ free(state);
+ return;
+ }
+
+kill_tcp_connection:
+ tcp_or_rpc_data_counter--;
+ free_connection(state->conn);
+ close(state->sock);
+ free(state);
+}
+
+/* Creates the tcp_dispatch_state and deletes the verto event. */
+static struct tcp_dispatch_state *
+prepare_for_dispatch(verto_ctx *ctx, verto_ev *ev)
+{
+ struct tcp_dispatch_state *state;
+
+ state = malloc(sizeof(*state));
+ if (!state) {
+ krb5_klog_syslog(LOG_ERR, _("error allocating tcp dispatch private!"));
+ return NULL;
+ }
+ state->conn = verto_get_private(ev);
+ state->sock = verto_get_fd(ev);
+ state->ctx = ctx;
+ verto_set_private(ev, NULL, NULL); /* Don't close the fd or free conn! */
+ remove_event_from_set(ev); /* Remove it from the set. */
+ verto_del(ev);
+ return state;
+}
+
+static void
+process_tcp_connection_read(verto_ctx *ctx, verto_ev *ev)
+{
+ struct tcp_dispatch_state *state = NULL;
+ struct connection *conn = NULL;
+ ssize_t nread;
+ size_t len;
+
+ conn = verto_get_private(ev);
+
+ /*
+ * Read message length and data into one big buffer, already allocated
+ * at connect time. If we have a complete message, we stop reading, so
+ * we should only be here if there is no data in the buffer, or only an
+ * incomplete message.
+ */
+ if (conn->offset < 4) {
+ krb5_data *response = NULL;
+
+ /* msglen has not been computed. XXX Doing at least two reads
+ * here, letting the kernel worry about buffering. */
+ len = 4 - conn->offset;
+ nread = SOCKET_READ(verto_get_fd(ev),
+ conn->buffer + conn->offset, len);
+ if (nread < 0) /* error */
+ goto kill_tcp_connection;
+ if (nread == 0) /* eof */
+ goto kill_tcp_connection;
+ conn->offset += nread;
+ if (conn->offset == 4) {
+ unsigned char *p = (unsigned char *)conn->buffer;
+ conn->msglen = load_32_be(p);
+ if (conn->msglen > conn->bufsiz - 4) {
+ krb5_error_code err;
+ /* Message too big. */
+ krb5_klog_syslog(LOG_ERR, _("TCP client %s wants %lu bytes, "
+ "cap is %lu"), conn->addrbuf,
+ (unsigned long) conn->msglen,
+ (unsigned long) conn->bufsiz - 4);
+ /* XXX Should return an error. */
+ err = make_toolong_error (conn->handle,
+ &response);
+ if (err) {
+ krb5_klog_syslog(LOG_ERR, _("error constructing "
+ "KRB_ERR_FIELD_TOOLONG error! %s"),
+ error_message(err));
+ goto kill_tcp_connection;
+ }
+
+ state = prepare_for_dispatch(ctx, ev);
+ if (!state) {
+ krb5_free_data(get_context(conn->handle), response);
+ goto kill_tcp_connection;
+ }
+ process_tcp_response(state, 0, response);
+ }
+ }
+ } else {
+ /* msglen known. */
+ socklen_t local_saddrlen = sizeof(struct sockaddr_storage);
+ struct sockaddr *local_saddrp = NULL;
+
+ len = conn->msglen - (conn->offset - 4);
+ nread = SOCKET_READ(verto_get_fd(ev),
+ conn->buffer + conn->offset, len);
+ if (nread < 0) /* error */
+ goto kill_tcp_connection;
+ if (nread == 0) /* eof */
+ goto kill_tcp_connection;
+ conn->offset += nread;
+ if (conn->offset < conn->msglen + 4)
+ return;
+
+ /* Have a complete message, and exactly one message. */
+ state = prepare_for_dispatch(ctx, ev);
+ if (!state)
+ goto kill_tcp_connection;
+
+ state->request.length = conn->msglen;
+ state->request.data = conn->buffer + 4;
+
+ if (getsockname(verto_get_fd(ev), ss2sa(&state->local_saddr),
+ &local_saddrlen) == 0)
+ local_saddrp = ss2sa(&state->local_saddr);
+
+ dispatch(state->conn->handle, local_saddrp, &conn->faddr,
+ &state->request, 1, ctx, process_tcp_response, state);
+ }
+
+ return;
+
+kill_tcp_connection:
+ verto_del(ev);
+}
+
+static void
+process_tcp_connection_write(verto_ctx *ctx, verto_ev *ev)
+{
+ struct connection *conn;
+ SOCKET_WRITEV_TEMP tmp;
+ ssize_t nwrote;
+ int sock;
+
+ conn = verto_get_private(ev);
+ sock = verto_get_fd(ev);
+
+ nwrote = SOCKET_WRITEV(sock, conn->sgp,
+ conn->sgnum, tmp);
+ if (nwrote > 0) { /* non-error and non-eof */
+ while (nwrote) {
+ sg_buf *sgp = conn->sgp;
+ if ((size_t)nwrote < SG_LEN(sgp)) {
+ SG_ADVANCE(sgp, (size_t)nwrote);
+ nwrote = 0;
+ } else {
+ nwrote -= SG_LEN(sgp);
+ conn->sgp++;
+ conn->sgnum--;
+ if (conn->sgnum == 0 && nwrote != 0)
+ abort();
+ }
+ }
+
+ /* If we still have more data to send, just return so that
+ * the main loop can call this function again when the socket
+ * is ready for more writing. */
+ if (conn->sgnum > 0)
+ return;
+ }
+
+ /* Finished sending. We should go back to reading, though if we
+ * sent a FIELD_TOOLONG error in reply to a length with the high
+ * bit set, RFC 4120 says we have to close the TCP stream. */
+ verto_del(ev);
+}
+
+void
+loop_free(verto_ctx *ctx)
+{
+ int i;
+ struct bind_address val;
+
+ verto_free(ctx);
+
+ /* Free each addresses added to the loop. */
+ FOREACH_ELT(bind_addresses, i, val)
+ free(val.address);
+ FREE_SET_DATA(bind_addresses);
+ FREE_SET_DATA(events);
+}
+
+static int
+have_event_for_fd(int fd)
+{
+ verto_ev *ev;
+ int i;
+
+ FOREACH_ELT(events, i, ev) {
+ if (verto_get_fd(ev) == fd)
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+accept_rpc_connection(verto_ctx *ctx, verto_ev *ev)
+{
+ struct socksetup sockdata;
+ struct connection *conn;
+ fd_set fds;
+ register int s;
+
+ conn = verto_get_private(ev);
+
+ sockdata.ctx = ctx;
+ sockdata.handle = conn->handle;
+ sockdata.prog = conn->prog;
+ sockdata.retval = 0;
+
+ /* Service the woken RPC listener descriptor. */
+ FD_ZERO(&fds);
+ FD_SET(verto_get_fd(ev), &fds);
+ svc_getreqset(&fds);
+
+ /* Scan svc_fdset for any new connections. */
+ for (s = 0; s < FD_SETSIZE; s++) {
+ struct sockaddr_storage addr_s;
+ struct sockaddr *addr = (struct sockaddr *) &addr_s;
+ socklen_t addrlen = sizeof(addr_s);
+ struct connection *newconn;
+ char tmpbuf[10];
+ verto_ev *newev;
+
+ /* If we already have this fd, continue. */
+ if (!FD_ISSET(s, &svc_fdset) || have_event_for_fd(s))
+ continue;
+
+ newev = add_rpc_data_fd(&sockdata, s);
+ if (newev == NULL)
+ continue;
+ newconn = verto_get_private(newev);
+
+ set_cloexec_fd(s);
+#if 0
+ setnbio(s), setnolinger(s), setkeepalive(s);
+#endif
+
+ if (getpeername(s, addr, &addrlen) ||
+ getnameinfo(addr, addrlen,
+ newconn->addrbuf,
+ sizeof(newconn->addrbuf),
+ tmpbuf, sizeof(tmpbuf),
+ NI_NUMERICHOST | NI_NUMERICSERV)) {
+ strlcpy(newconn->addrbuf, "???",
+ sizeof(newconn->addrbuf));
+ } else {
+ char *p, *end;
+ p = newconn->addrbuf;
+ end = p + sizeof(newconn->addrbuf);
+ p += strlen(p);
+ if ((size_t)(end - p) > 2 + strlen(tmpbuf)) {
+ *p++ = '.';
+ strlcpy(p, tmpbuf, end - p);
+ }
+ }
+#if 0
+ krb5_klog_syslog(LOG_INFO, _("accepted RPC connection on socket %d "
+ "from %s"), s, newconn->addrbuf);
+#endif
+
+ newconn->addr_s = addr_s;
+ newconn->addrlen = addrlen;
+ newconn->start_time = time(0);
+
+ if (++tcp_or_rpc_data_counter > max_tcp_or_rpc_data_connections)
+ kill_lru_tcp_or_rpc_connection(newconn->handle, newev);
+
+ newconn->faddr.address = &newconn->kaddr;
+ init_addr(&newconn->faddr, ss2sa(&newconn->addr_s));
+ }
+}
+
+static void
+process_rpc_connection(verto_ctx *ctx, verto_ev *ev)
+{
+ fd_set fds;
+
+ FD_ZERO(&fds);
+ FD_SET(verto_get_fd(ev), &fds);
+ svc_getreqset(&fds);
+
+ if (!FD_ISSET(verto_get_fd(ev), &svc_fdset))
+ verto_del(ev);
+}
+
+#endif /* INET */
diff --git a/src/lib/apputils/udppktinfo.c b/src/lib/apputils/udppktinfo.c
new file mode 100644
index 000000000000..bc7ad09b070c
--- /dev/null
+++ b/src/lib/apputils/udppktinfo.c
@@ -0,0 +1,525 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright 2016 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "udppktinfo.h"
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#if defined(IP_PKTINFO) && defined(HAVE_STRUCT_IN_PKTINFO)
+#define HAVE_IP_PKTINFO
+#endif
+
+#if defined(IPV6_PKTINFO) && defined(HAVE_STRUCT_IN6_PKTINFO)
+#define HAVE_IPV6_PKTINFO
+#endif
+
+#if defined(HAVE_IP_PKTINFO) || defined(IP_SENDSRCADDR) || \
+ defined(HAVE_IPV6_PKTINFO)
+#define HAVE_PKTINFO_SUPPORT
+#endif
+
+/* Use RFC 3542 API below, but fall back from IPV6_RECVPKTINFO to IPV6_PKTINFO
+ * for RFC 2292 implementations. */
+#if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO)
+#define IPV6_RECVPKTINFO IPV6_PKTINFO
+#endif
+
+/* Parallel, though not standardized. */
+#if !defined(IP_RECVPKTINFO) && defined(IP_PKTINFO)
+#define IP_RECVPKTINFO IP_PKTINFO
+#endif /* IP_RECVPKTINFO */
+
+#if defined(CMSG_SPACE) && defined(HAVE_STRUCT_CMSGHDR) && \
+ defined(HAVE_PKTINFO_SUPPORT)
+union pktinfo {
+#ifdef HAVE_STRUCT_IN6_PKTINFO
+ struct in6_pktinfo pi6;
+#endif
+#ifdef HAVE_STRUCT_IN_PKTINFO
+ struct in_pktinfo pi4;
+#endif
+#ifdef IP_RECVDSTADDR
+ struct in_addr iaddr;
+#endif
+ char c;
+};
+#endif /* HAVE_IPV6_PKTINFO && HAVE_STRUCT_CMSGHDR && HAVE_PKTINFO_SUPPORT */
+
+#ifdef HAVE_IP_PKTINFO
+
+#define set_ipv4_pktinfo set_ipv4_recvpktinfo
+static inline krb5_error_code
+set_ipv4_recvpktinfo(int sock)
+{
+ int sockopt = 1;
+ return setsockopt(sock, IPPROTO_IP, IP_RECVPKTINFO, &sockopt,
+ sizeof(sockopt));
+}
+
+#elif defined(IP_RECVDSTADDR) /* HAVE_IP_PKTINFO */
+
+#define set_ipv4_pktinfo set_ipv4_recvdstaddr
+static inline krb5_error_code
+set_ipv4_recvdstaddr(int sock)
+{
+ int sockopt = 1;
+ return setsockopt(sock, IPPROTO_IP, IP_RECVDSTADDR, &sockopt,
+ sizeof(sockopt));
+}
+
+#else /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */
+#define set_ipv4_pktinfo(s) EINVAL
+#endif /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */
+
+#ifdef HAVE_IPV6_PKTINFO
+
+#define set_ipv6_pktinfo set_ipv6_recvpktinfo
+static inline krb5_error_code
+set_ipv6_recvpktinfo(int sock)
+{
+ int sockopt = 1;
+ return setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &sockopt,
+ sizeof(sockopt));
+}
+
+#else /* HAVE_IPV6_PKTINFO */
+#define set_ipv6_pktinfo(s) EINVAL
+#endif /* HAVE_IPV6_PKTINFO */
+
+/*
+ * Set pktinfo option on a socket. Takes a socket and the socket address family
+ * as arguments.
+ *
+ * Returns 0 on success, EINVAL if pktinfo is not supported for the address
+ * family.
+ */
+krb5_error_code
+set_pktinfo(int sock, int family)
+{
+ switch (family) {
+ case AF_INET:
+ return set_ipv4_pktinfo(sock);
+ case AF_INET6:
+ return set_ipv6_pktinfo(sock);
+ default:
+ return EINVAL;
+ }
+}
+
+#if defined(HAVE_PKTINFO_SUPPORT) && defined(CMSG_SPACE)
+
+/*
+ * Check if a socket is bound to a wildcard address.
+ * Returns 1 if it is, 0 if it's bound to a specific address, or -1 on error
+ * with errno set to the error.
+ */
+static int
+is_socket_bound_to_wildcard(int sock)
+{
+ struct sockaddr_storage bound_addr;
+ socklen_t bound_addr_len = sizeof(bound_addr);
+
+ if (getsockname(sock, ss2sa(&bound_addr), &bound_addr_len) < 0)
+ return -1;
+
+ switch (ss2sa(&bound_addr)->sa_family) {
+ case AF_INET:
+ return ss2sin(&bound_addr)->sin_addr.s_addr == INADDR_ANY;
+ case AF_INET6:
+ return IN6_IS_ADDR_UNSPECIFIED(&ss2sin6(&bound_addr)->sin6_addr);
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+#ifdef HAVE_IP_PKTINFO
+
+static inline struct in_pktinfo *
+cmsg2pktinfo(struct cmsghdr *cmsgptr)
+{
+ return (struct in_pktinfo *)(void *)CMSG_DATA(cmsgptr);
+}
+
+#define check_cmsg_v4_pktinfo check_cmsg_ip_pktinfo
+static int
+check_cmsg_ip_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr *to,
+ socklen_t *tolen, aux_addressing_info *auxaddr)
+{
+ struct in_pktinfo *pktinfo;
+
+ if (cmsgptr->cmsg_level == IPPROTO_IP &&
+ cmsgptr->cmsg_type == IP_PKTINFO &&
+ *tolen >= sizeof(struct sockaddr_in)) {
+
+ memset(to, 0, sizeof(struct sockaddr_in));
+ pktinfo = cmsg2pktinfo(cmsgptr);
+ sa2sin(to)->sin_addr = pktinfo->ipi_addr;
+ sa2sin(to)->sin_family = AF_INET;
+ *tolen = sizeof(struct sockaddr_in);
+ return 1;
+ }
+ return 0;
+}
+
+#elif defined(IP_RECVDSTADDR) /* HAVE_IP_PKTINFO */
+
+static inline struct in_addr *
+cmsg2sin(struct cmsghdr *cmsgptr)
+{
+ return (struct in_addr *)(void *)CMSG_DATA(cmsgptr);
+}
+
+#define check_cmsg_v4_pktinfo check_cmsg_ip_recvdstaddr
+static int
+check_cmsg_ip_recvdstaddr(struct cmsghdr *cmsgptr, struct sockaddr *to,
+ socklen_t *tolen, aux_addressing_info * auxaddr)
+{
+ if (cmsgptr->cmsg_level == IPPROTO_IP &&
+ cmsgptr->cmsg_type == IP_RECVDSTADDR &&
+ *tolen >= sizeof(struct sockaddr_in)) {
+ struct in_addr *sin_addr;
+
+ memset(to, 0, sizeof(struct sockaddr_in));
+ sin_addr = cmsg2sin(cmsgptr);
+ sa2sin(to)->sin_addr = *sin_addr;
+ sa2sin(to)->sin_family = AF_INET;
+ *tolen = sizeof(struct sockaddr_in);
+ return 1;
+ }
+ return 0;
+}
+
+#else /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */
+#define check_cmsg_v4_pktinfo(c, t, l, a) 0
+#endif /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */
+
+#ifdef HAVE_IPV6_PKTINFO
+
+static inline struct in6_pktinfo *
+cmsg2pktinfo6(struct cmsghdr *cmsgptr)
+{
+ return (struct in6_pktinfo *)(void *)CMSG_DATA(cmsgptr);
+}
+
+#define check_cmsg_v6_pktinfo check_cmsg_ipv6_pktinfo
+static int
+check_cmsg_ipv6_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr *to,
+ socklen_t *tolen, aux_addressing_info *auxaddr)
+{
+ struct in6_pktinfo *pktinfo;
+
+ if (cmsgptr->cmsg_level == IPPROTO_IPV6 &&
+ cmsgptr->cmsg_type == IPV6_PKTINFO &&
+ *tolen >= sizeof(struct sockaddr_in6)) {
+
+ memset(to, 0, sizeof(struct sockaddr_in6));
+ pktinfo = cmsg2pktinfo6(cmsgptr);
+ sa2sin6(to)->sin6_addr = pktinfo->ipi6_addr;
+ sa2sin6(to)->sin6_family = AF_INET6;
+ *tolen = sizeof(struct sockaddr_in6);
+ auxaddr->ipv6_ifindex = pktinfo->ipi6_ifindex;
+ return 1;
+ }
+ return 0;
+}
+#else /* HAVE_IPV6_PKTINFO */
+#define check_cmsg_v6_pktinfo(c, t, l, a) 0
+#endif /* HAVE_IPV6_PKTINFO */
+
+static int
+check_cmsg_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr *to,
+ socklen_t *tolen, aux_addressing_info *auxaddr)
+{
+ return check_cmsg_v4_pktinfo(cmsgptr, to, tolen, auxaddr) ||
+ check_cmsg_v6_pktinfo(cmsgptr, to, tolen, auxaddr);
+}
+
+/*
+ * Receive a message from a socket.
+ *
+ * Arguments:
+ * sock
+ * buf - The buffer to store the message in.
+ * len - buf length
+ * flags
+ * from - Set to the address that sent the message
+ * fromlen
+ * to - Set to the address that the message was sent to if possible.
+ * May not be set in certain cases such as if pktinfo support is
+ * missing. May be NULL.
+ * tolen
+ * auxaddr - Miscellaneous address information.
+ *
+ * Returns 0 on success, otherwise an error code.
+ */
+krb5_error_code
+recv_from_to(int sock, void *buf, size_t len, int flags,
+ struct sockaddr *from, socklen_t * fromlen,
+ struct sockaddr *to, socklen_t * tolen,
+ aux_addressing_info *auxaddr)
+
+{
+ int r;
+ struct iovec iov;
+ char cmsg[CMSG_SPACE(sizeof(union pktinfo))];
+ struct cmsghdr *cmsgptr;
+ struct msghdr msg;
+
+ /* Don't use pktinfo if the socket isn't bound to a wildcard address. */
+ r = is_socket_bound_to_wildcard(sock);
+ if (r < 0)
+ return errno;
+
+ if (!to || !tolen || !r)
+ return recvfrom(sock, buf, len, flags, from, fromlen);
+
+ /* Clobber with something recognizeable in case we can't extract the
+ * address but try to use it anyways. */
+ memset(to, 0x40, *tolen);
+
+ iov.iov_base = buf;
+ iov.iov_len = len;
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = from;
+ msg.msg_namelen = *fromlen;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cmsg;
+ msg.msg_controllen = sizeof(cmsg);
+
+ r = recvmsg(sock, &msg, flags);
+ if (r < 0)
+ return r;
+ *fromlen = msg.msg_namelen;
+
+ /*
+ * On Darwin (and presumably all *BSD with KAME stacks), CMSG_FIRSTHDR
+ * doesn't check for a non-zero controllen. RFC 3542 recommends making
+ * this check, even though the (new) spec for CMSG_FIRSTHDR says it's
+ * supposed to do the check.
+ */
+ if (msg.msg_controllen) {
+ cmsgptr = CMSG_FIRSTHDR(&msg);
+ while (cmsgptr) {
+ if (check_cmsg_pktinfo(cmsgptr, to, tolen, auxaddr))
+ return r;
+ cmsgptr = CMSG_NXTHDR(&msg, cmsgptr);
+ }
+ }
+ /* No info about destination addr was available. */
+ *tolen = 0;
+ return r;
+}
+
+#ifdef HAVE_IP_PKTINFO
+
+#define set_msg_from_ipv4 set_msg_from_ip_pktinfo
+static krb5_error_code
+set_msg_from_ip_pktinfo(struct msghdr *msg, struct cmsghdr *cmsgptr,
+ struct sockaddr *from, socklen_t fromlen,
+ aux_addressing_info *auxaddr)
+{
+ struct in_pktinfo *p = cmsg2pktinfo(cmsgptr);
+ const struct sockaddr_in *from4 = sa2sin(from);
+
+ if (fromlen != sizeof(struct sockaddr_in))
+ return EINVAL;
+ cmsgptr->cmsg_level = IPPROTO_IP;
+ cmsgptr->cmsg_type = IP_PKTINFO;
+ cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+ p->ipi_spec_dst = from4->sin_addr;
+
+ msg->msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
+ return 0;
+}
+
+#elif defined(IP_SENDSRCADDR) /* HAVE_IP_PKTINFO */
+
+#define set_msg_from_ipv4 set_msg_from_ip_sendsrcaddr
+static krb5_error_code
+set_msg_from_ip_sendsrcaddr(struct msghdr *msg, struct cmsghdr *cmsgptr,
+ struct sockaddr *from, socklen_t fromlen,
+ aux_addressing_info *auxaddr)
+{
+ struct in_addr *sin_addr = cmsg2sin(cmsgptr);
+ const struct sockaddr_in *from4 = sa2sin(from);
+ if (fromlen != sizeof(struct sockaddr_in))
+ return EINVAL;
+ cmsgptr->cmsg_level = IPPROTO_IP;
+ cmsgptr->cmsg_type = IP_SENDSRCADDR;
+ cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
+ msg->msg_controllen = CMSG_SPACE(sizeof(struct in_addr));
+ *sin_addr = from4->sin_addr;
+ return 0;
+}
+
+#else /* HAVE_IP_PKTINFO || IP_SENDSRCADDR */
+#define set_msg_from_ipv4(m, c, f, l, a) EINVAL
+#endif /* HAVE_IP_PKTINFO || IP_SENDSRCADDR */
+
+#ifdef HAVE_IPV6_PKTINFO
+
+#define set_msg_from_ipv6 set_msg_from_ipv6_pktinfo
+static krb5_error_code
+set_msg_from_ipv6_pktinfo(struct msghdr *msg, struct cmsghdr *cmsgptr,
+ struct sockaddr *from, socklen_t fromlen,
+ aux_addressing_info *auxaddr)
+{
+ struct in6_pktinfo *p = cmsg2pktinfo6(cmsgptr);
+ const struct sockaddr_in6 *from6 = sa2sin6(from);
+
+ if (fromlen != sizeof(struct sockaddr_in6))
+ return EINVAL;
+ cmsgptr->cmsg_level = IPPROTO_IPV6;
+ cmsgptr->cmsg_type = IPV6_PKTINFO;
+ cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+
+ p->ipi6_addr = from6->sin6_addr;
+ /*
+ * Because of the possibility of asymmetric routing, we
+ * normally don't want to specify an interface. However,
+ * Mac OS X doesn't like sending from a link-local address
+ * (which can come up in testing at least, if you wind up
+ * with a "foo.local" name) unless we do specify the
+ * interface.
+ */
+ if (IN6_IS_ADDR_LINKLOCAL(&from6->sin6_addr))
+ p->ipi6_ifindex = auxaddr->ipv6_ifindex;
+ /* otherwise, already zero */
+
+ msg->msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
+ return 0;
+}
+
+#else /* HAVE_IPV6_PKTINFO */
+#define set_msg_from_ipv6(m, c, f, l, a) EINVAL
+#endif /* HAVE_IPV6_PKTINFO */
+
+static krb5_error_code
+set_msg_from(int family, struct msghdr *msg, struct cmsghdr *cmsgptr,
+ struct sockaddr *from, socklen_t fromlen,
+ aux_addressing_info *auxaddr)
+{
+ switch (family) {
+ case AF_INET:
+ return set_msg_from_ipv4(msg, cmsgptr, from, fromlen, auxaddr);
+ case AF_INET6:
+ return set_msg_from_ipv6(msg, cmsgptr, from, fromlen, auxaddr);
+ }
+
+ return EINVAL;
+}
+
+/*
+ * Send a message to an address.
+ *
+ * Arguments:
+ * sock
+ * buf - The message to send.
+ * len - buf length
+ * flags
+ * to - The address to send the message to.
+ * tolen
+ * from - The address to attempt to send the message from. May be NULL.
+ * fromlen
+ * auxaddr - Miscellaneous address information.
+ *
+ * Returns 0 on success, otherwise an error code.
+ */
+krb5_error_code
+send_to_from(int sock, void *buf, size_t len, int flags,
+ const struct sockaddr *to, socklen_t tolen, struct sockaddr *from,
+ socklen_t fromlen, aux_addressing_info *auxaddr)
+{
+ int r;
+ struct iovec iov;
+ struct msghdr msg;
+ struct cmsghdr *cmsgptr;
+ char cbuf[CMSG_SPACE(sizeof(union pktinfo))];
+
+ /* Don't use pktinfo if the socket isn't bound to a wildcard address. */
+ r = is_socket_bound_to_wildcard(sock);
+ if (r < 0)
+ return errno;
+
+ if (from == NULL || fromlen == 0 || from->sa_family != to->sa_family || !r)
+ goto use_sendto;
+
+ iov.iov_base = buf;
+ iov.iov_len = len;
+ /* Truncation? */
+ if (iov.iov_len != len)
+ return EINVAL;
+ memset(cbuf, 0, sizeof(cbuf));
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = (void *)to;
+ msg.msg_namelen = tolen;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = cbuf;
+ /* CMSG_FIRSTHDR needs a non-zero controllen, or it'll return NULL on
+ * Linux. */
+ msg.msg_controllen = sizeof(cbuf);
+ cmsgptr = CMSG_FIRSTHDR(&msg);
+ msg.msg_controllen = 0;
+
+ if (set_msg_from(from->sa_family, &msg, cmsgptr, from, fromlen, auxaddr))
+ goto use_sendto;
+ return sendmsg(sock, &msg, flags);
+
+use_sendto:
+ return sendto(sock, buf, len, flags, to, tolen);
+}
+
+#else /* HAVE_PKTINFO_SUPPORT && CMSG_SPACE */
+
+krb5_error_code
+recv_from_to(int sock, void *buf, size_t len, int flags,
+ struct sockaddr *from, socklen_t *fromlen,
+ struct sockaddr *to, socklen_t *tolen,
+ aux_addressing_info *auxaddr)
+{
+ if (to && tolen) {
+ /* Clobber with something recognizeable in case we try to use the
+ * address. */
+ memset(to, 0x40, *tolen);
+ *tolen = 0;
+ }
+
+ return recvfrom(sock, buf, len, flags, from, fromlen);
+}
+
+krb5_error_code
+send_to_from(int sock, void *buf, size_t len, int flags,
+ const struct sockaddr *to, socklen_t tolen,
+ struct sockaddr *from, socklen_t fromlen,
+ aux_addressing_info *auxaddr)
+{
+ return sendto(sock, buf, len, flags, to, tolen);
+}
+
+#endif /* HAVE_PKTINFO_SUPPORT && CMSG_SPACE */
diff --git a/src/lib/apputils/udppktinfo.h b/src/lib/apputils/udppktinfo.h
new file mode 100644
index 000000000000..b0c7ea36b7b9
--- /dev/null
+++ b/src/lib/apputils/udppktinfo.h
@@ -0,0 +1,58 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright 2016 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#ifndef UDPPKTINFO_H
+#define UDPPKTINFO_H
+
+#include "k5-int.h"
+
+/*
+ * This holds whatever additional information might be needed to
+ * properly send back to the client from the correct local address.
+ *
+ * In this case, we only need one datum so far: On Mac OS X, the
+ * kernel doesn't seem to like sending from link-local addresses
+ * unless we specify the correct interface.
+ */
+typedef union aux_addressing_info
+{
+ int ipv6_ifindex;
+} aux_addressing_info;
+
+krb5_error_code
+set_pktinfo(int sock, int family);
+
+krb5_error_code
+recv_from_to(int sock, void *buf, size_t len, int flags,
+ struct sockaddr *from, socklen_t *fromlen,
+ struct sockaddr *to, socklen_t *tolen,
+ aux_addressing_info *auxaddr);
+
+krb5_error_code
+send_to_from(int sock, void *buf, size_t len, int flags,
+ const struct sockaddr *to, socklen_t tolen, struct sockaddr *from,
+ socklen_t fromlen, aux_addressing_info *auxaddr);
+
+#endif /* UDPPKTINFO_H */