#include "k5-int.h"
#include "com_err.h"
#include "kdc_util.h"
#include "extern.h"
#include "kdc5_err.h"
#include "adm_proto.h"
#include <sys/ioctl.h>
#include <syslog.h>
#include <stddef.h>
#include <ctype.h>
#include "port-sockets.h"
#include "socket-utils.h"
#ifdef HAVE_NETINET_IN_H
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_SOCKIO_H
#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
#include <net/if.h>
#endif
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#include "fake-addrinfo.h"
static void
set_sa_port(struct sockaddr *addr, int port)
{
switch (addr->sa_family) {
case AF_INET:
sa2sin(addr)->sin_port = port;
break;
#ifdef KRB5_USE_INET6
case AF_INET6:
sa2sin6(addr)->sin6_port = port;
break;
#endif
default:
break;
}
}
static int ipv6_enabled()
{
#ifdef KRB5_USE_INET6
static int result = -1;
if (result == -1) {
int s;
s = socket(AF_INET6, SOCK_STREAM, 0);
if (s >= 0) {
result = 1;
close(s);
} else
result = 0;
}
return result;
#else
return 0;
#endif
}
static int
setreuseaddr(int sock, int value)
{
return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
}
#if defined(KRB5_USE_INET6) && defined(IPV6_V6ONLY)
static int
setv6only(int sock, int value)
{
return setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value));
}
#endif
#ifndef IPV6_RECVPKTINFO
#define IPV6_RECVPKTINFO IPV6_PKTINFO
#endif
#ifndef IP_RECVPKTINFO
#define IP_RECVPKTINFO IP_PKTINFO
#endif
static int
set_pktinfo(int sock, int family)
{
int sockopt = 1;
int option = 0, proto = 0;
switch (family) {
#if defined(IP_PKTINFO) && defined(HAVE_STRUCT_IN_PKTINFO)
case AF_INET:
proto = IPPROTO_IP;
option = IP_RECVPKTINFO;
break;
#endif
#if defined(IPV6_PKTINFO) && defined(HAVE_STRUCT_IN6_PKTINFO)
case AF_INET6:
proto = IPPROTO_IPV6;
option = IPV6_RECVPKTINFO;
break;
#endif
default:
return EINVAL;
}
if (setsockopt(sock, proto, option, &sockopt, sizeof(sockopt)))
return errno;
return 0;
}
static const char *paddr (struct sockaddr *sa)
{
static char buf[100];
char portbuf[10];
if (getnameinfo(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;
}
enum kdc_conn_type { CONN_UDP, CONN_UDP_PKTINFO, CONN_TCP_LISTENER, CONN_TCP, CONN_ROUTING };
struct connection {
int fd;
enum kdc_conn_type type;
void (*service)(struct connection *, const char *, int);
union {
#if 0
struct {
int x;
} udp;
struct {
int x;
} udp_pktinfo;
struct {
int x;
} tcp_listener;
#endif
struct {
struct sockaddr_storage addr_s;
socklen_t addrlen;
char addrbuf[56];
krb5_fulladdr faddr;
krb5_address kaddr;
size_t bufsiz;
size_t offset;
char *buffer;
size_t msglen;
krb5_data *response;
unsigned char lenbuf[4];
sg_buf sgbuf[2];
sg_buf *sgp;
int sgnum;
time_t start_time;
} tcp;
} u;
};
#define SET(TYPE) struct { TYPE *data; int n, max; }
#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) \
(((int)(set.max + incr) < set.max \
|| (((size_t)((int)(set.max + incr) * sizeof(set.data[0])) \
/ sizeof(set.data[0])) \
!= (set.max + incr))) \
? 0 \
: ((tmpptr = realloc(set.data, \
(int)(set.max + incr) * sizeof(set.data[0]))) \
? (set.data = tmpptr, set.max += incr, 1) \
: 0))
#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)
static SET(struct connection *) connections;
#define n_sockets connections.n
#define conns connections.data
static SET(u_short) udp_port_data, tcp_port_data;
#include "cm.h"
static struct select_state sstate;
static krb5_error_code add_udp_port(int port)
{
int i;
void *tmp;
u_short val;
u_short s_port = port;
if (s_port != port)
return EINVAL;
FOREACH_ELT (udp_port_data, i, val)
if (s_port == val)
return 0;
if (!ADD(udp_port_data, s_port, tmp))
return ENOMEM;
return 0;
}
static krb5_error_code add_tcp_port(int port)
{
int i;
void *tmp;
u_short val;
u_short s_port = port;
if (s_port != port)
return EINVAL;
FOREACH_ELT (tcp_port_data, i, val)
if (s_port == val)
return 0;
if (!ADD(tcp_port_data, s_port, tmp))
return ENOMEM;
return 0;
}
#define USE_AF AF_INET
#define USE_TYPE SOCK_DGRAM
#define USE_PROTO 0
#define SOCKET_ERRNO errno
#include "foreachaddr.h"
struct socksetup {
const char *prog;
krb5_error_code retval;
int udp_flags;
#define UDP_DO_IPV4 1
#define UDP_DO_IPV6 2
};
static struct connection *
add_fd (struct socksetup *data, int sock, enum kdc_conn_type conntype,
void (*service)(struct connection *, const char *, int))
{
struct connection *newconn;
void *tmp;
#ifndef _WIN32
if (sock >= FD_SETSIZE) {
data->retval = EMFILE;
com_err(data->prog, 0,
"file descriptor number %d too high", sock);
return 0;
}
#endif
newconn = malloc(sizeof(*newconn));
if (newconn == 0) {
data->retval = ENOMEM;
com_err(data->prog, ENOMEM,
"cannot allocate storage for connection info");
return 0;
}
if (!ADD(connections, newconn, tmp)) {
data->retval = ENOMEM;
com_err(data->prog, ENOMEM, "cannot save socket info");
free(newconn);
return 0;
}
memset(newconn, 0, sizeof(*newconn));
newconn->type = conntype;
newconn->fd = sock;
newconn->service = service;
return newconn;
}
static void process_packet(struct connection *, const char *, int);
static void accept_tcp_connection(struct connection *, const char *, int);
static void process_tcp_connection(struct connection *, const char *, int);
static struct connection *
add_udp_fd (struct socksetup *data, int sock, int pktinfo)
{
return add_fd(data, sock, pktinfo ? CONN_UDP_PKTINFO : CONN_UDP,
process_packet);
}
static struct connection *
add_tcp_listener_fd (struct socksetup *data, int sock)
{
return add_fd(data, sock, CONN_TCP_LISTENER, accept_tcp_connection);
}
static struct connection *
add_tcp_data_fd (struct socksetup *data, int sock)
{
return add_fd(data, sock, CONN_TCP, process_tcp_connection);
}
static void
delete_fd (struct connection *xconn)
{
struct connection *conn;
int i;
FOREACH_ELT(connections, i, conn)
if (conn == xconn) {
DEL(connections, i);
break;
}
free(xconn);
}
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));
}
static int
setup_a_tcp_listener(struct socksetup *data, struct sockaddr *addr)
{
int sock;
sock = socket(addr->sa_family, SOCK_STREAM, 0);
if (sock == -1) {
com_err(data->prog, errno, "Cannot create TCP server socket on %s",
paddr(addr));
return -1;
}
set_cloexec_fd(sock);
#ifndef _WIN32
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);
#ifdef KRB5_USE_INET6
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
}
#endif
if (bind(sock, addr, socklen(addr)) == -1) {
com_err(data->prog, errno,
"Cannot bind TCP server socket on %s", paddr(addr));
close(sock);
return -1;
}
if (listen(sock, 5) < 0) {
com_err(data->prog, errno, "Cannot listen on TCP server socket on %s",
paddr(addr));
close(sock);
return -1;
}
if (setnbio(sock)) {
com_err(data->prog, errno,
"cannot set listening tcp socket on %s non-blocking",
paddr(addr));
close(sock);
return -1;
}
if (setnolinger(sock)) {
com_err(data->prog, errno, "disabling SO_LINGER on TCP socket on %s",
paddr(addr));
close(sock);
return -1;
}
return sock;
}
static int
setup_tcp_listener_ports(struct socksetup *data)
{
struct sockaddr_in sin4;
#ifdef KRB5_USE_INET6
struct sockaddr_in6 sin6;
#endif
int i, port;
memset(&sin4, 0, sizeof(sin4));
sin4.sin_family = AF_INET;
#ifdef HAVE_SA_LEN
sin4.sin_len = sizeof(sin4);
#endif
sin4.sin_addr.s_addr = INADDR_ANY;
#ifdef KRB5_USE_INET6
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
#ifdef SIN6_LEN
sin6.sin6_len = sizeof(sin6);
#endif
sin6.sin6_addr = in6addr_any;
#endif
FOREACH_ELT (tcp_port_data, i, port) {
int s4, s6;
set_sa_port((struct sockaddr *)&sin4, htons(port));
if (!ipv6_enabled()) {
s4 = setup_a_tcp_listener(data, (struct sockaddr *)&sin4);
if (s4 < 0)
return -1;
s6 = -1;
} else {
#ifndef KRB5_USE_INET6
abort();
#else
s4 = s6 = -1;
set_sa_port((struct sockaddr *)&sin6, htons(port));
s6 = setup_a_tcp_listener(data, (struct sockaddr *)&sin6);
if (s6 < 0)
return -1;
s4 = setup_a_tcp_listener(data, (struct sockaddr *)&sin4);
#endif
}
if (s4 >= 0) {
FD_SET(s4, &sstate.rfds);
if (s4 >= sstate.max)
sstate.max = s4 + 1;
if (add_tcp_listener_fd(data, s4) == 0)
close(s4);
else
krb5_klog_syslog(LOG_INFO, "listening on fd %d: tcp %s",
s4, paddr((struct sockaddr *)&sin4));
}
#ifdef KRB5_USE_INET6
if (s6 >= 0) {
FD_SET(s6, &sstate.rfds);
if (s6 >= sstate.max)
sstate.max = s6 + 1;
if (add_tcp_listener_fd(data, s6) == 0) {
close(s6);
s6 = -1;
} else
krb5_klog_syslog(LOG_INFO, "listening on fd %d: tcp %s",
s6, paddr((struct sockaddr *)&sin6));
if (s4 < 0)
krb5_klog_syslog(LOG_INFO,
"assuming IPv6 socket accepts IPv4");
}
#endif
}
return 0;
}
#if defined(CMSG_SPACE) && defined(HAVE_STRUCT_CMSGHDR) && (defined(IP_PKTINFO) || defined(IPV6_PKTINFO))
union pktinfo {
#ifdef HAVE_STRUCT_IN6_PKTINFO
struct in6_pktinfo pi6;
#endif
#ifdef HAVE_STRUCT_IN_PKTINFO
struct in_pktinfo pi4;
#endif
char c;
};
static int
setup_udp_port_1(struct socksetup *data, struct sockaddr *addr,
char *haddrbuf, int pktinfo);
static void
setup_udp_pktinfo_ports(struct socksetup *data)
{
#ifdef IP_PKTINFO
{
struct sockaddr_in sa;
int r;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
#ifdef HAVE_SA_LEN
sa.sin_len = sizeof(sa);
#endif
r = setup_udp_port_1(data, (struct sockaddr *) &sa, "0.0.0.0", 4);
if (r == 0)
data->udp_flags &= ~UDP_DO_IPV4;
}
#endif
#ifdef IPV6_PKTINFO
{
struct sockaddr_in6 sa;
int r;
memset(&sa, 0, sizeof(sa));
sa.sin6_family = AF_INET6;
#ifdef HAVE_SA_LEN
sa.sin6_len = sizeof(sa);
#endif
r = setup_udp_port_1(data, (struct sockaddr *) &sa, "::", 6);
if (r == 0)
data->udp_flags &= ~UDP_DO_IPV6;
}
#endif
}
#else
static void
setup_udp_pktinfo_ports(struct socksetup *data)
{
}
#endif
static int
setup_udp_port_1(struct socksetup *data, struct sockaddr *addr,
char *haddrbuf, int pktinfo)
{
int sock = -1, i, r;
u_short port;
FOREACH_ELT (udp_port_data, i, port) {
sock = socket (addr->sa_family, SOCK_DGRAM, 0);
if (sock == -1) {
data->retval = errno;
com_err(data->prog, data->retval,
"Cannot create server socket for port %d address %s",
port, haddrbuf);
return 1;
}
set_cloexec_fd(sock);
#ifdef KRB5_USE_INET6
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
}
#endif
set_sa_port(addr, htons(port));
if (bind (sock, (struct sockaddr *)addr, socklen (addr)) == -1) {
data->retval = errno;
com_err(data->prog, data->retval,
"Cannot bind server socket to port %d address %s",
port, haddrbuf);
close(sock);
return 1;
}
#if !(defined(CMSG_SPACE) && defined(HAVE_STRUCT_CMSGHDR) && (defined(IP_PKTINFO) || defined(IPV6_PKTINFO)))
assert(pktinfo == 0);
#endif
if (pktinfo) {
r = set_pktinfo(sock, addr->sa_family);
if (r) {
com_err(data->prog, r,
"Cannot request packet info for udp socket address %s port %d",
haddrbuf, port);
close(sock);
return 1;
}
}
FD_SET (sock, &sstate.rfds);
if (sock >= sstate.max)
sstate.max = sock + 1;
krb5_klog_syslog (LOG_INFO, "listening on fd %d: udp %s%s", sock,
paddr((struct sockaddr *)addr),
pktinfo ? " (pktinfo)" : "");
if (add_udp_fd (data, sock, pktinfo) == 0) {
close(sock);
return 1;
}
}
return 0;
}
static int
setup_udp_port(void *P_data, struct sockaddr *addr)
{
struct socksetup *data = P_data;
char haddrbuf[NI_MAXHOST];
int err;
if (addr->sa_family == AF_INET && !(data->udp_flags & UDP_DO_IPV4))
return 0;
#ifdef AF_INET6
if (addr->sa_family == AF_INET6 && !(data->udp_flags & UDP_DO_IPV6))
return 0;
#endif
err = getnameinfo(addr, socklen(addr), haddrbuf, sizeof(haddrbuf),
0, 0, NI_NUMERICHOST);
if (err)
strlcpy(haddrbuf, "<unprintable>", sizeof(haddrbuf));
switch (addr->sa_family) {
case AF_INET:
break;
#ifdef AF_INET6
case AF_INET6:
#ifdef KRB5_USE_INET6
break;
#else
{
static int first = 1;
if (first) {
krb5_klog_syslog (LOG_INFO, "skipping local ipv6 addresses");
first = 0;
}
return 0;
}
#endif
#endif
#ifdef AF_LINK
case AF_LINK:
return 0;
#endif
#ifdef AF_DLI
case AF_DLI:
return 0;
#endif
#ifdef AF_APPLETALK
case AF_APPLETALK:
return 0;
#endif
default:
krb5_klog_syslog (LOG_INFO,
"skipping unrecognized local address family %d",
addr->sa_family);
return 0;
}
return setup_udp_port_1(data, addr, haddrbuf, 0);
}
#if 1
static void klog_handler(const void *data, size_t len)
{
static char buf[BUFSIZ];
static int bufoffset;
void *p;
#define flush_buf() \
(bufoffset \
? (((buf[0] == 0 || buf[0] == '\n') \
? (fork()==0?abort():(void)0) \
: (void)0), \
krb5_klog_syslog(LOG_INFO, "%s", buf), \
memset(buf, 0, sizeof(buf)), \
bufoffset = 0) \
: 0)
p = memchr(data, 0, len);
if (p)
len = (const char *)p - (const char *)data;
scan_for_newlines:
if (len == 0)
return;
p = memchr(data, '\n', len);
if (p) {
if (p != data)
klog_handler(data, (size_t)((const char *)p - (const char *)data));
flush_buf();
len -= ((const char *)p - (const char *)data) + 1;
data = 1 + (const char *)p;
goto scan_for_newlines;
} else if (len > sizeof(buf) - 1 || len + bufoffset > sizeof(buf) - 1) {
size_t x = sizeof(buf) - len - 1;
klog_handler(data, x);
flush_buf();
len -= x;
data = (const char *)data + x;
goto scan_for_newlines;
} else {
memcpy(buf + bufoffset, data, len);
bufoffset += len;
}
}
#endif
static int network_reconfiguration_needed = 0;
#ifdef HAVE_STRUCT_RT_MSGHDR
#include <net/route.h>
static char *rtm_type_name(int type)
{
switch (type) {
case RTM_ADD: return "RTM_ADD";
case RTM_DELETE: return "RTM_DELETE";
case RTM_NEWADDR: return "RTM_NEWADDR";
case RTM_DELADDR: return "RTM_DELADDR";
case RTM_IFINFO: return "RTM_IFINFO";
case RTM_OLDADD: return "RTM_OLDADD";
case RTM_OLDDEL: return "RTM_OLDDEL";
case RTM_RESOLVE: return "RTM_RESOLVE";
#ifdef RTM_NEWMADDR
case RTM_NEWMADDR: return "RTM_NEWMADDR";
case RTM_DELMADDR: return "RTM_DELMADDR";
#endif
case RTM_MISS: return "RTM_MISS";
case RTM_REDIRECT: return "RTM_REDIRECT";
case RTM_LOSING: return "RTM_LOSING";
case RTM_GET: return "RTM_GET";
default: return "?";
}
}
static void process_routing_update(struct connection *conn, const char *prog,
int selflags)
{
int n_read;
struct rt_msghdr rtm;
krb5_klog_syslog(LOG_INFO, "routing socket readable");
while ((n_read = read(conn->fd, &rtm, sizeof(rtm))) > 0) {
if (n_read < sizeof(rtm)) {
#define RS(FIELD) (offsetof(struct rt_msghdr, FIELD) + sizeof(rtm.FIELD))
if (n_read < RS(rtm_type) ||
n_read < RS(rtm_version) ||
n_read < RS(rtm_msglen)) {
krb5_klog_syslog(LOG_ERR,
"short read (%d/%d) from routing socket",
n_read, (int) sizeof(rtm));
return;
}
}
krb5_klog_syslog(LOG_INFO,
"got routing msg type %d(%s) v%d",
rtm.rtm_type, rtm_type_name(rtm.rtm_type),
rtm.rtm_version);
if (rtm.rtm_msglen > sizeof(rtm)) {
} else if (rtm.rtm_msglen != n_read) {
krb5_klog_syslog(LOG_ERR,
"read %d from routing socket but msglen is %d",
n_read, rtm.rtm_msglen);
}
switch (rtm.rtm_type) {
case RTM_ADD:
case RTM_DELETE:
case RTM_NEWADDR:
case RTM_DELADDR:
case RTM_IFINFO:
case RTM_OLDADD:
case RTM_OLDDEL:
krb5_klog_syslog(LOG_INFO, "reconfiguration needed");
network_reconfiguration_needed = 1;
break;
case RTM_RESOLVE:
#ifdef RTM_NEWMADDR
case RTM_NEWMADDR:
case RTM_DELMADDR:
#endif
case RTM_MISS:
case RTM_REDIRECT:
case RTM_LOSING:
case RTM_GET:
krb5_klog_syslog(LOG_DEBUG, "routing msg not interesting");
break;
default:
krb5_klog_syslog(LOG_INFO, "unhandled routing message type, will reconfigure just for the fun of it");
network_reconfiguration_needed = 1;
break;
}
}
}
static void
setup_routing_socket(struct socksetup *data)
{
int sock = socket(PF_ROUTE, SOCK_RAW, 0);
if (sock < 0) {
int e = errno;
krb5_klog_syslog(LOG_INFO, "couldn't set up routing socket: %s",
strerror(e));
} else {
krb5_klog_syslog(LOG_INFO, "routing socket is fd %d", sock);
add_fd(data, sock, CONN_ROUTING, process_routing_update);
setnbio(sock);
FD_SET(sock, &sstate.rfds);
}
}
#endif
extern int krb5int_debug_sendto_kdc;
extern void (*krb5int_sendtokdc_debug_handler)(const void*, size_t);
krb5_error_code
setup_network(const char *prog)
{
struct socksetup setup_data;
krb5_error_code retval;
char *cp;
int i, port;
FD_ZERO(&sstate.rfds);
FD_ZERO(&sstate.wfds);
FD_ZERO(&sstate.xfds);
sstate.max = 0;
krb5int_sendtokdc_debug_handler = klog_handler;
for (i=0; i<kdc_numrealms; i++) {
cp = kdc_realmlist[i]->realm_ports;
while (cp && *cp) {
if (*cp == ',' || isspace((int) *cp)) {
cp++;
continue;
}
port = strtol(cp, &cp, 10);
if (cp == 0)
break;
retval = add_udp_port(port);
if (retval)
return retval;
}
cp = kdc_realmlist[i]->realm_tcp_ports;
while (cp && *cp) {
if (*cp == ',' || isspace((int) *cp)) {
cp++;
continue;
}
port = strtol(cp, &cp, 10);
if (cp == 0)
break;
retval = add_tcp_port(port);
if (retval)
return retval;
}
}
setup_data.prog = prog;
setup_data.retval = 0;
krb5_klog_syslog (LOG_INFO, "setting up network...");
#ifdef HAVE_STRUCT_RT_MSGHDR
setup_routing_socket(&setup_data);
#endif
setup_data.udp_flags = UDP_DO_IPV4 | UDP_DO_IPV6;
setup_udp_pktinfo_ports(&setup_data);
if (setup_data.udp_flags) {
if (foreach_localaddr (&setup_data, setup_udp_port, 0, 0)) {
return setup_data.retval;
}
}
setup_tcp_listener_ports(&setup_data);
krb5_klog_syslog (LOG_INFO, "set up %d sockets", n_sockets);
if (n_sockets == 0) {
com_err(prog, 0, "no sockets set up?");
exit (1);
}
return 0;
}
static 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;
#ifdef KRB5_USE_INET6
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;
#endif
default:
faddr->address->addrtype = -1;
faddr->address->length = 0;
faddr->address->contents = 0;
faddr->port = 0;
break;
}
}
static int
recv_from_to(int s, void *buf, size_t len, int flags,
struct sockaddr *from, socklen_t *fromlen,
struct sockaddr *to, socklen_t *tolen)
{
#if (!defined(IP_PKTINFO) && !defined(IPV6_PKTINFO)) || !defined(CMSG_SPACE)
if (to && tolen)
*tolen = 0;
return recvfrom(s, buf, len, flags, from, fromlen);
#else
int r;
struct iovec iov;
char cmsg[CMSG_SPACE(sizeof(union pktinfo))];
struct cmsghdr *cmsgptr;
struct msghdr msg;
if (!to || !tolen)
return recvfrom(s, buf, len, flags, from, fromlen);
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(s, &msg, flags);
if (r < 0)
return r;
*fromlen = msg.msg_namelen;
if (msg.msg_controllen) {
cmsgptr = CMSG_FIRSTHDR(&msg);
while (cmsgptr) {
#ifdef IP_PKTINFO
if (cmsgptr->cmsg_level == IPPROTO_IP
&& cmsgptr->cmsg_type == IP_PKTINFO
&& *tolen >= sizeof(struct sockaddr_in)) {
struct in_pktinfo *pktinfo;
memset(to, 0, sizeof(struct sockaddr_in));
pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsgptr);
((struct sockaddr_in *)to)->sin_addr = pktinfo->ipi_addr;
((struct sockaddr_in *)to)->sin_family = AF_INET;
*tolen = sizeof(struct sockaddr_in);
return r;
}
#endif
#if defined(KRB5_USE_INET6) && defined(IPV6_PKTINFO)&& defined(HAVE_STRUCT_IN6_PKTINFO)
if (cmsgptr->cmsg_level == IPPROTO_IPV6
&& cmsgptr->cmsg_type == IPV6_PKTINFO
&& *tolen >= sizeof(struct sockaddr_in6)) {
struct in6_pktinfo *pktinfo;
memset(to, 0, sizeof(struct sockaddr_in6));
pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsgptr);
((struct sockaddr_in6 *)to)->sin6_addr = pktinfo->ipi6_addr;
((struct sockaddr_in6 *)to)->sin6_family = AF_INET6;
*tolen = sizeof(struct sockaddr_in6);
return r;
}
#endif
cmsgptr = CMSG_NXTHDR(&msg, cmsgptr);
}
}
*tolen = 0;
return r;
#endif
}
static int
send_to_from(int s, void *buf, size_t len, int flags,
const struct sockaddr *to, socklen_t tolen,
const struct sockaddr *from, socklen_t fromlen)
{
#if (!defined(IP_PKTINFO) && !defined(IPV6_PKTINFO)) || !defined(CMSG_SPACE)
return sendto(s, buf, len, flags, to, tolen);
#else
struct iovec iov;
struct msghdr msg;
struct cmsghdr *cmsgptr;
char cbuf[CMSG_SPACE(sizeof(union pktinfo))];
if (from == 0 || fromlen == 0 || from->sa_family != to->sa_family) {
use_sendto:
return sendto(s, buf, len, flags, to, tolen);
}
iov.iov_base = buf;
iov.iov_len = len;
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;
msg.msg_controllen = sizeof(cbuf);
cmsgptr = CMSG_FIRSTHDR(&msg);
msg.msg_controllen = 0;
switch (from->sa_family) {
#if defined(IP_PKTINFO)
case AF_INET:
if (fromlen != sizeof(struct sockaddr_in))
goto use_sendto;
cmsgptr->cmsg_level = IPPROTO_IP;
cmsgptr->cmsg_type = IP_PKTINFO;
cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
{
struct in_pktinfo *p = (struct in_pktinfo *)CMSG_DATA(cmsgptr);
const struct sockaddr_in *from4 = (const struct sockaddr_in *)from;
p->ipi_spec_dst = from4->sin_addr;
}
msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
break;
#endif
#if defined(KRB5_USE_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_STRUCT_IN6_PKTINFO)
case AF_INET6:
if (fromlen != sizeof(struct sockaddr_in6))
goto use_sendto;
cmsgptr->cmsg_level = IPPROTO_IPV6;
cmsgptr->cmsg_type = IPV6_PKTINFO;
cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
{
struct in6_pktinfo *p = (struct in6_pktinfo *)CMSG_DATA(cmsgptr);
const struct sockaddr_in6 *from6 = (const struct sockaddr_in6 *)from;
p->ipi6_addr = from6->sin6_addr;
}
msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
break;
#endif
default:
goto use_sendto;
}
return sendmsg(s, &msg, flags);
#endif
}
static void process_packet(struct connection *conn, const char *prog,
int selflags)
{
int cc;
socklen_t saddr_len, daddr_len;
krb5_fulladdr faddr;
krb5_error_code retval;
struct sockaddr_storage saddr, daddr;
krb5_address addr;
krb5_data request;
krb5_data *response;
char pktbuf[MAX_DGRAM_SIZE];
int port_fd = conn->fd;
response = NULL;
saddr_len = sizeof(saddr);
daddr_len = sizeof(daddr);
cc = recv_from_to(port_fd, pktbuf, sizeof(pktbuf), 0,
(struct sockaddr *)&saddr, &saddr_len,
(struct sockaddr *)&daddr, &daddr_len);
if (cc == -1) {
if (errno != EINTR
&& errno != ECONNREFUSED
)
com_err(prog, errno, "while receiving from network");
return;
}
if (!cc)
return;
#if 0
if (daddr_len > 0) {
char addrbuf[100];
if (getnameinfo(ss2sa(&daddr), daddr_len, addrbuf, sizeof(addrbuf),
0, 0, NI_NUMERICHOST))
strlcpy(addrbuf, "?", sizeof(addrbuf));
com_err(prog, 0, "pktinfo says local addr is %s", addrbuf);
}
#endif
request.length = cc;
request.data = pktbuf;
faddr.address = &addr;
init_addr(&faddr, ss2sa(&saddr));
if ((retval = dispatch(&request, &faddr, &response))) {
com_err(prog, retval, "while dispatching (udp)");
return;
}
if (response == NULL)
return;
cc = send_to_from(port_fd, response->data, (socklen_t) response->length, 0,
(struct sockaddr *)&saddr, saddr_len,
(struct sockaddr *)&daddr, daddr_len);
if (cc == -1) {
char addrbuf[46];
krb5_free_data(kdc_context, response);
if (inet_ntop(((struct sockaddr *)&saddr)->sa_family,
addr.contents, addrbuf, sizeof(addrbuf)) == 0) {
strlcpy(addrbuf, "?", sizeof(addrbuf));
}
com_err(prog, errno, "while sending reply to %s/%d",
addrbuf, faddr.port);
return;
}
if (cc != response->length) {
com_err(prog, 0, "short reply write %d vs %d\n",
response->length, cc);
}
krb5_free_data(kdc_context, response);
return;
}
static int tcp_data_counter;
static int max_tcp_data_connections = 30;
static void kill_tcp_connection(struct connection *);
static void accept_tcp_connection(struct connection *conn, const char *prog,
int selflags)
{
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;
char tmpbuf[10];
s = accept(conn->fd, 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.prog = prog;
sockdata.retval = 0;
newconn = add_tcp_data_fd(&sockdata, s);
if (newconn == 0)
return;
if (getnameinfo((struct sockaddr *)&addr_s, addrlen,
newconn->u.tcp.addrbuf, sizeof(newconn->u.tcp.addrbuf),
tmpbuf, sizeof(tmpbuf),
NI_NUMERICHOST | NI_NUMERICSERV))
strlcpy(newconn->u.tcp.addrbuf, "???", sizeof(newconn->u.tcp.addrbuf));
else {
char *p, *end;
p = newconn->u.tcp.addrbuf;
end = p + sizeof(newconn->u.tcp.addrbuf);
p += strlen(p);
if (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->u.tcp.addrbuf);
#endif
newconn->u.tcp.addr_s = addr_s;
newconn->u.tcp.addrlen = addrlen;
newconn->u.tcp.bufsiz = 1024 * 1024;
newconn->u.tcp.buffer = malloc(newconn->u.tcp.bufsiz);
newconn->u.tcp.start_time = time(0);
if (++tcp_data_counter > max_tcp_data_connections) {
struct connection *oldest_tcp = NULL;
struct connection *c;
int i;
krb5_klog_syslog(LOG_INFO, "too many connections");
FOREACH_ELT (connections, i, c) {
if (c->type != CONN_TCP)
continue;
if (c == newconn)
continue;
#if 0
krb5_klog_syslog(LOG_INFO, "fd %d started at %ld", c->fd,
c->u.tcp.start_time);
#endif
if (oldest_tcp == NULL
|| oldest_tcp->u.tcp.start_time > c->u.tcp.start_time)
oldest_tcp = c;
}
if (oldest_tcp != NULL) {
krb5_klog_syslog(LOG_INFO, "dropping tcp fd %d from %s",
oldest_tcp->fd, oldest_tcp->u.tcp.addrbuf);
kill_tcp_connection(oldest_tcp);
}
}
if (newconn->u.tcp.buffer == 0) {
com_err(prog, errno, "allocating buffer for new TCP session from %s",
newconn->u.tcp.addrbuf);
delete_fd(newconn);
close(s);
tcp_data_counter--;
return;
}
newconn->u.tcp.offset = 0;
newconn->u.tcp.faddr.address = &newconn->u.tcp.kaddr;
init_addr(&newconn->u.tcp.faddr, ss2sa(&newconn->u.tcp.addr_s));
SG_SET(&newconn->u.tcp.sgbuf[0], newconn->u.tcp.lenbuf, 4);
SG_SET(&newconn->u.tcp.sgbuf[1], 0, 0);
FD_SET(s, &sstate.rfds);
if (sstate.max <= s)
sstate.max = s + 1;
}
static void
kill_tcp_connection(struct connection *conn)
{
if (conn->u.tcp.response)
krb5_free_data(kdc_context, conn->u.tcp.response);
if (conn->u.tcp.buffer)
free(conn->u.tcp.buffer);
FD_CLR(conn->fd, &sstate.rfds);
FD_CLR(conn->fd, &sstate.wfds);
if (sstate.max == conn->fd + 1)
while (sstate.max > 0
&& ! FD_ISSET(sstate.max-1, &sstate.rfds)
&& ! FD_ISSET(sstate.max-1, &sstate.wfds)
)
sstate.max--;
close(conn->fd);
conn->fd = -1;
delete_fd(conn);
tcp_data_counter--;
}
static krb5_error_code
make_toolong_error (krb5_data **out)
{
krb5_error errpkt;
krb5_error_code retval;
krb5_data *scratch;
retval = krb5_us_timeofday(kdc_context, &errpkt.stime, &errpkt.susec);
if (retval)
return retval;
errpkt.error = KRB_ERR_FIELD_TOOLONG;
errpkt.server = tgs_server;
errpkt.client = NULL;
errpkt.cusec = 0;
errpkt.ctime = 0;
errpkt.text.length = 0;
errpkt.text.data = 0;
errpkt.e_data.length = 0;
errpkt.e_data.data = 0;
scratch = malloc(sizeof(*scratch));
if (scratch == NULL)
return ENOMEM;
retval = krb5_mk_error(kdc_context, &errpkt, scratch);
if (retval) {
free(scratch);
return retval;
}
*out = scratch;
return 0;
}
static void
queue_tcp_outgoing_response(struct connection *conn)
{
store_32_be(conn->u.tcp.response->length, conn->u.tcp.lenbuf);
SG_SET(&conn->u.tcp.sgbuf[1], conn->u.tcp.response->data,
conn->u.tcp.response->length);
conn->u.tcp.sgp = conn->u.tcp.sgbuf;
conn->u.tcp.sgnum = 2;
FD_SET(conn->fd, &sstate.wfds);
}
static void
process_tcp_connection(struct connection *conn, const char *prog, int selflags)
{
if (selflags & SSF_WRITE) {
ssize_t nwrote;
SOCKET_WRITEV_TEMP tmp;
nwrote = SOCKET_WRITEV(conn->fd, conn->u.tcp.sgp, conn->u.tcp.sgnum,
tmp);
if (nwrote < 0) {
goto kill_tcp_connection;
}
if (nwrote == 0)
goto kill_tcp_connection;
while (nwrote) {
sg_buf *sgp = conn->u.tcp.sgp;
if (nwrote < SG_LEN(sgp)) {
SG_ADVANCE(sgp, nwrote);
nwrote = 0;
} else {
nwrote -= SG_LEN(sgp);
conn->u.tcp.sgp++;
conn->u.tcp.sgnum--;
if (conn->u.tcp.sgnum == 0 && nwrote != 0)
abort();
}
}
if (conn->u.tcp.sgnum == 0) {
goto kill_tcp_connection;
}
} else if (selflags & SSF_READ) {
size_t len;
ssize_t nread;
if (conn->u.tcp.offset < 4) {
len = 4 - conn->u.tcp.offset;
nread = SOCKET_READ(conn->fd,
conn->u.tcp.buffer + conn->u.tcp.offset, len);
if (nread < 0)
goto kill_tcp_connection;
if (nread == 0)
goto kill_tcp_connection;
conn->u.tcp.offset += nread;
if (conn->u.tcp.offset == 4) {
unsigned char *p = (unsigned char *)conn->u.tcp.buffer;
conn->u.tcp.msglen = load_32_be(p);
if (conn->u.tcp.msglen > conn->u.tcp.bufsiz - 4) {
krb5_error_code err;
krb5_klog_syslog(LOG_ERR, "TCP client %s wants %lu bytes, cap is %lu",
conn->u.tcp.addrbuf, (unsigned long) conn->u.tcp.msglen,
(unsigned long) conn->u.tcp.bufsiz - 4);
err = make_toolong_error (&conn->u.tcp.response);
if (err) {
krb5_klog_syslog(LOG_ERR,
"error constructing KRB_ERR_FIELD_TOOLONG error! %s",
error_message(err));
goto kill_tcp_connection;
}
goto have_response;
}
}
} else {
krb5_data request;
krb5_error_code err;
len = conn->u.tcp.msglen - (conn->u.tcp.offset - 4);
nread = SOCKET_READ(conn->fd,
conn->u.tcp.buffer + conn->u.tcp.offset, len);
if (nread < 0)
goto kill_tcp_connection;
if (nread == 0)
goto kill_tcp_connection;
conn->u.tcp.offset += nread;
if (conn->u.tcp.offset < conn->u.tcp.msglen + 4)
return;
request.length = conn->u.tcp.msglen;
request.data = conn->u.tcp.buffer + 4;
err = dispatch(&request, &conn->u.tcp.faddr,
&conn->u.tcp.response);
if (err) {
com_err(prog, err, "while dispatching (tcp)");
goto kill_tcp_connection;
}
have_response:
queue_tcp_outgoing_response(conn);
FD_CLR(conn->fd, &sstate.rfds);
}
} else
abort();
return;
kill_tcp_connection:
kill_tcp_connection(conn);
}
static void service_conn(struct connection *conn, const char *prog,
int selflags)
{
conn->service(conn, prog, selflags);
}
static int getcurtime(struct timeval *tvp)
{
#ifdef _WIN32
struct _timeb tb;
_ftime(&tb);
tvp->tv_sec = tb.time;
tvp->tv_usec = tb.millitm * 1000;
return 0;
#else
return gettimeofday(tvp, 0) ? errno : 0;
#endif
}
krb5_error_code
listen_and_process(const char *prog)
{
int nfound;
static struct select_state sout;
int i, sret, netchanged = 0;
krb5_error_code err;
if (conns == (struct connection **) NULL)
return KDC5_NONET;
while (!signal_requests_exit) {
if (signal_requests_hup) {
krb5_klog_reopen(kdc_context);
signal_requests_hup = 0;
}
if (network_reconfiguration_needed) {
krb5_klog_syslog(LOG_INFO, "network reconfiguration needed");
err = getcurtime(&sstate.end_time);
if (err) {
com_err(prog, err, "while getting the time");
continue;
}
sstate.end_time.tv_sec += 3;
netchanged = 1;
} else
sstate.end_time.tv_sec = sstate.end_time.tv_usec = 0;
err = krb5int_cm_call_select(&sstate, &sout, &sret);
if (err) {
if (err != EINTR)
com_err(prog, err, "while selecting for network input(1)");
continue;
}
if (sret == 0 && netchanged) {
network_reconfiguration_needed = 0;
closedown_network(prog);
err = setup_network(prog);
if (err) {
com_err(prog, err, "while reinitializing network");
return err;
}
netchanged = 0;
}
if (sret == -1) {
if (errno != EINTR)
com_err(prog, errno, "while selecting for network input(2)");
continue;
}
nfound = sret;
for (i=0; i<n_sockets && nfound > 0; i++) {
int sflags = 0;
if (conns[i]->fd < 0)
abort();
if (FD_ISSET(conns[i]->fd, &sout.rfds))
sflags |= SSF_READ, nfound--;
if (FD_ISSET(conns[i]->fd, &sout.wfds))
sflags |= SSF_WRITE, nfound--;
if (sflags)
service_conn(conns[i], prog, sflags);
}
}
krb5_klog_syslog(LOG_INFO, "shutdown signal received");
return 0;
}
krb5_error_code
closedown_network(const char *prog)
{
int i;
struct connection *conn;
if (conns == (struct connection **) NULL)
return KDC5_NONET;
FOREACH_ELT (connections, i, conn) {
if (conn->fd >= 0) {
krb5_klog_syslog(LOG_INFO, "closing down fd %d", conn->fd);
(void) close(conn->fd);
}
DEL (connections, i);
free(conn);
}
FREE_SET_DATA(connections);
FREE_SET_DATA(udp_port_data);
FREE_SET_DATA(tcp_port_data);
return 0;
}
#endif