diff options
author | Luke Howard <lukeh@padl.com> | 2008-11-25 05:40:30 +0000 |
---|---|---|
committer | Luke Howard <lukeh@padl.com> | 2008-11-25 05:40:30 +0000 |
commit | d34800eb4b7449875dc0f9873895ff3320c1b910 (patch) | |
tree | 882355fa565cfe833fd4e25256b5cde936d57579 | |
parent | 3bd525b539771edf52b2af26d313969dafa57315 (diff) | |
download | krb5-d34800eb4b7449875dc0f9873895ff3320c1b910.zip krb5-d34800eb4b7449875dc0f9873895ff3320c1b910.tar.gz krb5-d34800eb4b7449875dc0f9873895ff3320c1b910.tar.bz2 |
TCP support for kpasswd protocol
git-svn-id: svn://anonsvn.mit.edu/krb5/branches/mskrb-integ@21187 dc483132-0cff-0310-8789-dd5450dbe970
-rw-r--r-- | src/kadmin/server/Makefile.in | 21 | ||||
-rw-r--r-- | src/kadmin/server/misc.h | 33 | ||||
-rw-r--r-- | src/kadmin/server/network.c | 1957 | ||||
-rw-r--r-- | src/kadmin/server/ovsec_kadmd.c | 407 | ||||
-rw-r--r-- | src/kadmin/server/schpw.c | 116 |
5 files changed, 2062 insertions, 472 deletions
diff --git a/src/kadmin/server/Makefile.in b/src/kadmin/server/Makefile.in index fc1e42c..a73b944 100644 --- a/src/kadmin/server/Makefile.in +++ b/src/kadmin/server/Makefile.in @@ -13,8 +13,8 @@ PROG_LIBPATH=-L$(TOPLIBD) PROG_RPATH=$(KRB5_LIBDIR) PROG = kadmind -OBJS = kadm_rpc_svc.o server_stubs.o ovsec_kadmd.o schpw.o misc.o server_glue_v1.o ipropd_svc.o -SRCS = kadm_rpc_svc.c server_stubs.c ovsec_kadmd.c schpw.c misc.c server_glue_v1.c ipropd_svc.c +OBJS = kadm_rpc_svc.o server_stubs.o ovsec_kadmd.o schpw.o misc.o server_glue_v1.o ipropd_svc.o network.o +SRCS = kadm_rpc_svc.c server_stubs.c ovsec_kadmd.c schpw.c misc.c server_glue_v1.c ipropd_svc.c network.c all:: $(PROG) @@ -151,3 +151,20 @@ $(OUTPRE)ipropd_svc.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ $(SRCTOP)/include/k5-thread.h $(SRCTOP)/include/kdb.h \ $(SRCTOP)/include/kdb_log.h $(SRCTOP)/include/krb5.h \ $(SRCTOP)/lib/kadm5/srv/server_acl.h ipropd_svc.c misc.h +$(OUTPRE)network.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/gssapi/gssapi.h $(BUILDTOP)/include/gssrpc/types.h \ + $(BUILDTOP)/include/kadm5/admin.h $(BUILDTOP)/include/kadm5/admin_internal.h \ + $(BUILDTOP)/include/kadm5/chpass_util_strings.h $(BUILDTOP)/include/kadm5/kadm_err.h \ + $(BUILDTOP)/include/kadm5/kadm_rpc.h $(BUILDTOP)/include/kadm5/server_internal.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/lib/gssapi/krb5/gssapi_krb5.h $(COM_ERR_DEPS) \ + $(SRCTOP)/include/adm_proto.h $(SRCTOP)/include/gssrpc/auth.h \ + $(SRCTOP)/include/gssrpc/auth_gss.h $(SRCTOP)/include/gssrpc/auth_unix.h \ + $(SRCTOP)/include/gssrpc/clnt.h $(SRCTOP)/include/gssrpc/rename.h \ + $(SRCTOP)/include/gssrpc/rpc.h $(SRCTOP)/include/gssrpc/rpc_msg.h \ + $(SRCTOP)/include/gssrpc/svc.h $(SRCTOP)/include/gssrpc/svc_auth.h \ + $(SRCTOP)/include/gssrpc/xdr.h $(SRCTOP)/include/iprop.h \ + $(SRCTOP)/include/iprop_hdr.h $(SRCTOP)/include/k5-platform.h \ + $(SRCTOP)/include/k5-thread.h $(SRCTOP)/include/kdb.h \ + $(SRCTOP)/include/kdb_log.h $(SRCTOP)/include/krb5.h \ + $(SRCTOP)/lib/kadm5/srv/server_acl.h network.c misc.h diff --git a/src/kadmin/server/misc.h b/src/kadmin/server/misc.h index 190c5d7..9b22346 100644 --- a/src/kadmin/server/misc.h +++ b/src/kadmin/server/misc.h @@ -3,6 +3,14 @@ * */ +#ifndef _MISC_H +#define _MISC_H 1 + +typedef struct _krb5_fulladdr { + krb5_address * address; + krb5_ui_4 port; +} krb5_fulladdr; + kadm5_ret_t chpass_principal_wrapper_3(void *server_handle, krb5_principal principal, @@ -38,17 +46,32 @@ kadm5_ret_t kadm5_get_policy_v1(void *server_handle, kadm5_policy_t name, krb5_error_code process_chpw_request(krb5_context context, void *server_handle, - char *realm, int s, + char *realm, krb5_keytab keytab, - struct sockaddr_in *sockin, + krb5_fulladdr *local_faddr, + krb5_fulladdr *remote_faddr, krb5_data *req, krb5_data *rep); -#ifdef SVC_GETARGS -void kadm_1(struct svc_req *, SVCXPRT *); -#endif +void kadm_1(struct svc_req *, SVCXPRT *); +void krb5_iprop_prog_1(struct svc_req *, SVCXPRT *); void trunc_name(size_t *len, char **dots); int gss_to_krb5_name_1(struct svc_req *rqstp, krb5_context ctx, gss_name_t gss_name, krb5_principal *princ, gss_buffer_t gss_str); + +extern volatile int signal_request_exit; +extern volatile int signal_request_hup; + +void reset_db(void); + +void log_badauth(OM_uint32 major, OM_uint32 minor, + struct sockaddr_in *addr, char *data); + +/* network.c */ +krb5_error_code setup_network(void *handle, const char *prog); +krb5_error_code listen_and_process(void *handle, const char *prog); +krb5_error_code closedown_network(void *handle, const char *prog); + +#endif /* _MISC_H */ diff --git a/src/kadmin/server/network.c b/src/kadmin/server/network.c new file mode 100644 index 0000000..b7a6755 --- /dev/null +++ b/src/kadmin/server/network.c @@ -0,0 +1,1957 @@ +/* + * kadmin/server/network.c + * + * Copyright 1990,2000,2007,2008 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. + * + * + * Network code for Kerberos v5 kadmin server (based on KDC code). + */ + +#include "k5-int.h" +#include "com_err.h" +#include "kadm5/admin.h" +#include "kadm5/server_internal.h" +#include "kadm5/kadm_rpc.h" +#include "iprop.h" +#include "adm_proto.h" +#include "misc.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 +/* 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" + +/* XXX */ +#define KDC5_NONET (-1779992062L) + +/* Misc utility routines. */ +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 + +/* Use RFC 3542 API below, but fall back from IPV6_RECVPKTINFO to + IPV6_PKTINFO for RFC 2292 implementations. */ +#ifndef IPV6_RECVPKTINFO +#define IPV6_RECVPKTINFO IPV6_PKTINFO +#endif +/* Parallel, though not standardized. */ +#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; +} + +/* kadmin data. */ + +enum kadm_conn_type { CONN_UDP, CONN_UDP_PKTINFO, CONN_TCP_LISTENER, + CONN_TCP, CONN_ROUTING, CONN_RPC_LISTENER, CONN_RPC }; + +/* Per-connection info. */ +struct connection { + int fd; + enum kadm_conn_type type; + void (*service)(void *handle, struct connection *, const char *, int); + union { + /* Type-specific information. */ + struct { + /* connection */ + struct sockaddr_storage addr_s; + socklen_t addrlen; + char addrbuf[56]; + krb5_fulladdr faddr; + krb5_address kaddr; + /* incoming */ + size_t bufsiz; + size_t offset; + char *buffer; + size_t msglen; + /* outgoing */ + krb5_data *response; + unsigned char lenbuf[4]; + sg_buf sgbuf[2]; + sg_buf *sgp; + int sgnum; + /* crude denial-of-service avoidance support */ + time_t start_time; + } tcp; + struct { + SVCXPRT *transp; + } rpc; + } u; +}; + + +#define SET(TYPE) struct { TYPE *data; int 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) \ + (((int)(set.max + incr) < set.max \ + || (((size_t)((int)(set.max + incr) * sizeof(set.data[0])) \ + / sizeof(set.data[0])) \ + != (set.max + incr))) \ + ? 0 /* overflow */ \ + : ((tmpptr = realloc(set.data, \ + (int)(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) + + +/* Set<struct connection *> connections; */ +static SET(struct connection *) connections; +#define n_sockets connections.n +#define conns connections.data + +/* Set<u_short> udp_port_data, tcp_port_data; */ +static SET(u_short) udp_port_data, tcp_port_data; + +struct rpc_svc_data { + u_short port; + u_long prognum; + u_long versnum; + void (*dispatch)(); +}; + +static SET(struct rpc_svc_data) rpc_svc_data; + +#include "cm.h" + +static struct select_state sstate; +static fd_set rpc_listenfds; + +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; +} + +static krb5_error_code add_rpc_service(int port, u_long prognum, u_long versnum, + void (*dispatch)()) +{ + int i; + void *tmp; + struct rpc_svc_data svc, val; + + svc.port = port; + if (svc.port != port) + return EINVAL; + svc.prognum = prognum; + svc.versnum = versnum; + svc.dispatch = dispatch; + + FOREACH_ELT (rpc_svc_data, i, val) { + if (val.port == port) + return 0; + } + if (!ADD(rpc_svc_data, svc, tmp)) + return ENOMEM; + return 0; +} + + +#define USE_AF AF_INET +#define USE_TYPE SOCK_DGRAM + + +#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 kadm_conn_type conntype, + void (*service)(void *handle, struct connection *, const char *, int)) +{ + struct connection *newconn; + void *tmp; + +#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 = (struct connection *)malloc(sizeof(*newconn)); + if (newconn == NULL) { + 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(void *handle, struct connection *, const char *, int); +static void accept_tcp_connection(void *handle, struct connection *, const char *, int); +static void process_tcp_connection(void *handle, struct connection *, const char *, int); +static void accept_rpc_connection(void *handle, struct connection *, const char *, int); +static void process_rpc_connection(void *handle, 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 struct connection * +add_rpc_listener_fd (struct socksetup *data, struct rpc_svc_data *svc, int sock) +{ + struct connection *conn; + + conn = add_fd(data, sock, CONN_RPC_LISTENER, accept_rpc_connection); + if (conn == NULL) + return NULL; + + conn->u.rpc.transp = svctcp_create(sock, 0, 0); + if (conn->u.rpc.transp == NULL) { + krb5_klog_syslog(LOG_ERR, "Cannot create RPC service, continuing."); + delete_fd(conn); + return NULL; + } + + if (!svc_register(conn->u.rpc.transp, svc->prognum, svc->versnum, + svc->dispatch, IPPROTO_TCP)) { + krb5_klog_syslog(LOG_ERR, "Cannot register RPC service, continuing."); + delete_fd(conn); + return NULL; + } + + return conn; +} + +static struct connection * +add_rpc_data_fd (struct socksetup *data, int sock) +{ + return add_fd(data, sock, CONN_RPC, process_rpc_connection); +} + +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)); +} + +/* Returns -1 or socket fd. */ +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 /* IPV6_V6ONLY */ + } +#endif /* KRB5_USE_INET6 */ + 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; +} + +/* Returns -1 or socket fd. */ +static int +setup_a_rpc_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 RPC 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, "RPC 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 (bind(sock, addr, socklen(addr)) == -1) { + com_err(data->prog, errno, + "Cannot bind RPC server 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 /* KRB5_USE_INET6 */ + } + + /* Sockets are created, prepare to listen on them. */ + 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; +} + +static int +setup_rpc_listener_ports(struct socksetup *data) +{ + struct sockaddr_in sin4; + int i; + struct rpc_svc_data svc; + + 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; + + FOREACH_ELT (rpc_svc_data, i, svc) { + int s4; + + set_sa_port((struct sockaddr *)&sin4, htons(svc.port)); + s4 = setup_a_rpc_listener(data, (struct sockaddr *)&sin4); + if (s4 < 0) + return -1; + else { + FD_SET(s4, &sstate.rfds); + if (s4 >= sstate.max) + sstate.max = s4 + 1; + if (add_rpc_listener_fd(data, &svc, s4) == 0) + close(s4); + else + krb5_klog_syslog(LOG_INFO, "listening on fd %d: rpc %s", + s4, paddr((struct sockaddr *)&sin4)); + } + } + FD_ZERO(&rpc_listenfds); + rpc_listenfds = svc_fdset; + 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 /* no pktinfo compile-time support */ +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 /* IPV6_V6ONLY */ + } +#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 /* some BSD systems, AIX */ + case AF_LINK: + return 0; +#endif +#ifdef AF_DLI /* Direct Link Interface - DEC Ultrix/OSF1 link layer? */ + 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)) { + /* Quick hack to figure out if the interesting + fields are present in a short read. + + A short read seems to be normal for some message types. + Only complain if we don't have the critical initial + header fields. */ +#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)) { + /* It appears we get a partial message and the rest is + thrown away? */ + } 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: + /* Not interesting. */ + 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 + +/* XXX */ +extern int krb5int_debug_sendto_kdc; +extern void (*krb5int_sendtokdc_debug_handler)(const void*, size_t); + +krb5_error_code +setup_network(void *handle, const char *prog) +{ + struct socksetup setup_data; + krb5_error_code retval; + kadm5_server_handle_t server_handle = (kadm5_server_handle_t)handle; + + FD_ZERO(&sstate.rfds); + FD_ZERO(&sstate.wfds); + FD_ZERO(&sstate.xfds); + sstate.max = 0; + +/* krb5int_debug_sendto_kdc = 1; */ + krb5int_sendtokdc_debug_handler = klog_handler; + + retval = add_udp_port(server_handle->params.kpasswd_port); + if (retval) + return retval; + + retval = add_tcp_port(server_handle->params.kpasswd_port); + if (retval) + return retval; + + retval = add_rpc_service(server_handle->params.kadmind_port, + KADM, KADMVERS, + kadm_1); + if (retval) + return retval; + +#ifndef DISABLE_IPROP + if (server_handle->params.iprop_enabled) { + retval = add_rpc_service(server_handle->params.iprop_port, + KRB5_IPROP_PROG, KRB5_IPROP_VERS, + krb5_iprop_prog_1); + if (retval) + return retval; + } +#endif /* DISABLE_IPROP */ + + 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 + /* To do: Use RFC 2292 interface (or follow-on) and IPV6_PKTINFO, + so we might need only one UDP socket; fall back to binding + sockets on each address only if IPV6_PKTINFO isn't + supported. */ + 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); + setup_rpc_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; + + /* 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) { +#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); + } + } + /* No info about destination addr was available. */ + *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; + /* 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; + + 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 +} + +/* Dispatch routine for set/change password */ +static krb5_error_code +dispatch(void *handle, + struct sockaddr *local_saddr, krb5_fulladdr *remote_faddr, + krb5_data *request, krb5_data **response) +{ + krb5_error_code ret; + krb5_keytab kt = NULL; + kadm5_server_handle_t server_handle = (kadm5_server_handle_t)handle; + krb5_fulladdr local_faddr; + krb5_address **local_kaddrs = NULL, local_kaddr_buf; + + *response = NULL; + + if (local_saddr == NULL) { + ret = krb5_os_localaddr(server_handle->context, &local_kaddrs); + if (ret != 0) + goto cleanup; + + local_faddr.address = local_kaddrs[0]; + local_faddr.port = 0; + } else { + local_faddr.address = &local_kaddr_buf; + init_addr(&local_faddr, local_saddr); + } + + ret = krb5_kt_resolve(server_handle->context, "KDB:", &kt); + if (ret != 0) { + krb5_klog_syslog(LOG_ERR, "chpw: Couldn't open admin keytab %s", + krb5_get_error_message(server_handle->context, ret)); + goto cleanup; + } + + *response = (krb5_data *)malloc(sizeof(krb5_data)); + if (*response == NULL) { + ret = ENOMEM; + goto cleanup; + } + + ret = process_chpw_request(server_handle->context, + handle, + server_handle->params.realm, + kt, + &local_faddr, + remote_faddr, + request, + *response); + +cleanup: + if (local_kaddrs != NULL) + krb5_free_addresses(server_handle->context, local_kaddrs); + + krb5_kt_close(server_handle->context, kt); + + return ret; +} + +static void process_packet(void *handle, + 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; + kadm5_server_handle_t server_handle = (kadm5_server_handle_t)handle; + + 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 + /* 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(prog, errno, "while receiving from network"); + return; + } + if (!cc) + return; /* zero-length packet? */ + +#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)); + /* this address is in net order */ + if ((retval = dispatch(handle, ss2sa(&daddr), &faddr, &request, &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(server_handle->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(server_handle->context, response); + return; +} + +static int tcp_data_counter; +static int max_tcp_data_connections = 30; + +static void kill_tcp_or_rpc_connection(void *, struct connection *, int isForcedClose); + +static int kill_lru_tcp_or_rpc_connection(void *handle, struct connection *newconn) +{ + struct connection *oldest_tcp = NULL; + struct connection *c; + int i, fd = -1; + + krb5_klog_syslog(LOG_INFO, "too many connections"); + + FOREACH_ELT (connections, i, c) { + if (c->type != CONN_TCP && c->type != CONN_RPC) + 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); + fd = oldest_tcp->fd; + kill_tcp_or_rpc_connection(handle, oldest_tcp, 1); + } + return fd; +} + +static void accept_tcp_connection(void *handle, + 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 == NULL) + 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) + kill_lru_tcp_or_rpc_connection(handle, newconn); + + 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_or_rpc_connection(void *handle, struct connection *conn, int isForcedClose) +{ + kadm5_server_handle_t server_handle = (kadm5_server_handle_t)handle; + + assert(conn->type == CONN_TCP || conn->type == CONN_RPC); + assert(conn->fd != -1); + + if (conn->u.tcp.response) + krb5_free_data(server_handle->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) + /* && ! FD_ISSET(sstate.max-1, &sstate.xfds) */ + ) + sstate.max--; + + close(conn->fd); + + /* For RPC connections, call into RPC runtime to flush out any internal state */ + if (conn->type == CONN_RPC && isForcedClose) { + fd_set fds; + + FD_ZERO(&fds); + FD_SET(conn->fd, &fds); + + svc_getreqset(&fds); + + if (FD_ISSET(conn->fd, &svc_fdset)) { + krb5_klog_syslog(LOG_ERR, + "descriptor %d closed but still in svc_fdset", conn->fd); + } + } + + conn->fd = -1; + delete_fd(conn); + tcp_data_counter--; +} + +static krb5_error_code +make_toolong_error (void *handle, krb5_data **out) +{ + krb5_error errpkt; + krb5_error_code retval; + krb5_data *scratch; + kadm5_server_handle_t server_handle = (kadm5_server_handle_t)handle; + + retval = krb5_us_timeofday(server_handle->context, &errpkt.stime, &errpkt.susec); + if (retval) + return retval; + errpkt.error = KRB_ERR_FIELD_TOOLONG; + retval = krb5_build_principal(server_handle->context, &errpkt.server, + strlen(server_handle->params.realm), + server_handle->params.realm, + "kadmin", "changepw", NULL); + if (retval) + return retval; + 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(server_handle->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(void *handle, + struct connection *conn, const char *prog, int selflags) +{ + int isForcedClose = 1; /* not used now, but for completeness */ + + 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) { + /* eof */ + isForcedClose = 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) { + /* 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. */ + isForcedClose = 0; + goto kill_tcp_connection; + } + } else if (selflags & SSF_READ) { + /* 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. */ + size_t len; + ssize_t nread; + if (conn->u.tcp.offset < 4) { + /* msglen has not been computed */ + /* XXX Doing at least two reads here, letting the kernel + worry about buffering. It'll be faster when we add + code to manage the buffer here. */ + len = 4 - conn->u.tcp.offset; + nread = SOCKET_READ(conn->fd, + conn->u.tcp.buffer + conn->u.tcp.offset, len); + if (nread < 0) + /* error */ + goto kill_tcp_connection; + if (nread == 0) + /* eof */ + 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; + /* message too big */ + 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); + /* XXX Should return an error. */ + err = make_toolong_error (handle, &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 { + /* msglen known */ + krb5_data request; + krb5_error_code err; + struct sockaddr_storage local_saddr; + socklen_t local_saddrlen = sizeof(local_saddr); + struct sockaddr *local_saddrp = NULL; + + 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) + /* error */ + goto kill_tcp_connection; + if (nread == 0) + /* eof */ + goto kill_tcp_connection; + conn->u.tcp.offset += nread; + if (conn->u.tcp.offset < conn->u.tcp.msglen + 4) + return; + /* have a complete message, and exactly one message */ + request.length = conn->u.tcp.msglen; + request.data = conn->u.tcp.buffer + 4; + + if (getsockname(conn->fd, ss2sa(&local_saddr), &local_saddrlen) == 0) { + local_saddrp = ss2sa(&local_saddr); + } + + err = dispatch(handle, local_saddrp, &conn->u.tcp.faddr, + &request, &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_or_rpc_connection(handle, conn, isForcedClose); +} + +static void service_conn(void *handle, + struct connection *conn, const char *prog, + int selflags) +{ + conn->service(handle, 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(void *handle, const char *prog) +{ + int nfound; + /* This struct contains 3 fd_set objects; on some platforms, they + can be rather large. Making this static avoids putting all + that junk on the stack. */ + static struct select_state sout; + int i, sret, netchanged = 0; + krb5_error_code err; + kadm5_server_handle_t server_handle = (kadm5_server_handle_t)handle; + + if (conns == (struct connection **) NULL) + return KDC5_NONET; + + while (!signal_request_exit) { + if (signal_request_hup) { + krb5_klog_reopen(server_handle->context); + reset_db(); + signal_request_hup = 0; + } + + if (network_reconfiguration_needed) { + krb5_klog_syslog(LOG_INFO, "network reconfiguration needed"); + /* It might be tidier to add a timer-callback interface to + the control loop here, but for this one use, it's not a + big deal. */ + 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(handle, prog); + err = setup_network(handle, 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(handle, conns[i], prog, sflags); + } + } + krb5_klog_syslog(LOG_INFO, "shutdown signal received"); + return 0; +} + +krb5_error_code +closedown_network(void *handle, const char *prog) +{ + int i; + struct connection *conn; + + if (conns == (struct connection **) NULL) + return KDC5_NONET; + + FOREACH_ELT (connections, i, conn) { + if (conn->type == CONN_RPC_LISTENER) { + if (conn->u.rpc.transp != NULL) + svc_destroy(conn->u.rpc.transp); + } + if (conn->fd >= 0) { + krb5_klog_syslog(LOG_INFO, "closing down fd %d", conn->fd); + (void) close(conn->fd); + } + DEL (connections, i); + /* There may also be per-connection data in the tcp structure + (tcp.buffer, tcp.response) that we're not freeing here. + That should only happen if we quit with a connection in + progress. */ + free(conn); + } + FREE_SET_DATA(connections); + FREE_SET_DATA(udp_port_data); + FREE_SET_DATA(tcp_port_data); + FREE_SET_DATA(rpc_svc_data); + + return 0; +} + +static void accept_rpc_connection(void *handle, struct connection *conn, + const char *prog, int selflags) +{ + struct socksetup sockdata; + fd_set fds; + register int s; + + sockdata.prog = prog; + sockdata.retval = 0; + + /* + * Service the logical intersection of RPC listener descriptors and + * descriptors available for reading. + */ + FD_ZERO(&fds); + + for (s = 0; s < howmany(sstate.max, NFDBITS); s++) + __FDS_BITS(&fds)[s] = __FDS_BITS(&rpc_listenfds)[s] & + __FDS_BITS(&sstate.rfds)[s]; + + svc_getreqset(&fds); + + /* + * Scan svc_fdset for any new connections. + */ + for (s = 0; s < FD_SETSIZE; s++) { + if (FD_ISSET(s, &svc_fdset)) { + struct connection *newconn; + + /* Don't add listeners or anything we're already reading */ + if (FD_ISSET(s, &rpc_listenfds) || FD_ISSET(s, &sstate.rfds)) + continue; + + newconn = add_rpc_data_fd(&sockdata, s); + if (newconn == NULL) + continue; + + if (++tcp_data_counter > max_tcp_data_connections) + kill_lru_tcp_or_rpc_connection(handle, newconn); + + FD_SET(s, &sstate.rfds); + if (sstate.max <= s) + sstate.max = s + 1; + } + } + + return; +} + +static void process_rpc_connection(void *handle, struct connection *conn, + const char *prog, int selflags) +{ + fd_set fds; + + assert(selflags & SSF_READ); + + if (selflags & SSF_READ) { + FD_ZERO(&fds); + FD_SET(conn->fd, &fds); + + svc_getreqset(&fds); + + if (!FD_ISSET(conn->fd, &svc_fdset)) + kill_tcp_or_rpc_connection(handle, conn, 0); + } +} + +#endif /* INET */ diff --git a/src/kadmin/server/ovsec_kadmd.c b/src/kadmin/server/ovsec_kadmd.c index f4e1ce7..42f361b 100644 --- a/src/kadmin/server/ovsec_kadmd.c +++ b/src/kadmin/server/ovsec_kadmd.c @@ -77,7 +77,6 @@ void request_exit(int); void request_hup(int); void reset_db(void); void sig_pipe(int); -void kadm_svc_run(kadm5_config_params *params); #ifdef POSIX_SIGNALS static struct sigaction s_action; @@ -214,15 +213,12 @@ int nofork = 0; int main(int argc, char *argv[]) { - register SVCXPRT *transp, *iproptransp; extern char *optarg; extern int optind, opterr; int ret, oldnames = 0; OM_uint32 OMret, major_status, minor_status; char *whoami; gss_buffer_desc in_buf; - struct sockaddr_in addr; - int s; auth_gssapi_name names[4]; gss_buffer_desc gssbuf; gss_OID nt_krb5_name_oid; @@ -230,8 +226,8 @@ int main(int argc, char *argv[]) char **db_args = NULL; int db_args_size = 0; char *errmsg; + int i; - char *kiprop_name = NULL; /* iprop svc name */ kdb_log_context *log_ctx; setvbuf(stderr, NULL, _IONBF, 0); @@ -358,238 +354,17 @@ int main(int argc, char *argv[]) exit(1); } - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; - addr.sin_port = htons(params.kadmind_port); - - if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - const char *e_txt; - ret = SOCKET_ERRNO; - e_txt = krb5_get_error_message (context, ret); - krb5_klog_syslog(LOG_ERR, "Cannot create TCP socket: %s", - e_txt); - fprintf(stderr, "Cannot create TCP socket: %s", - e_txt); - kadm5_destroy(global_server_handle); - krb5_klog_close(context); - exit(1); - } - set_cloexec_fd(s); - - if ((schpw = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { - const char *e_txt; - ret = SOCKET_ERRNO; - e_txt = krb5_get_error_message (context, ret); - krb5_klog_syslog(LOG_ERR, - "cannot create simple chpw socket: %s", - e_txt); - fprintf(stderr, "Cannot create simple chpw socket: %s", - e_txt); - kadm5_destroy(global_server_handle); - krb5_klog_close(context); - exit(1); - } - set_cloexec_fd(schpw); - -#ifndef DISABLE_IPROP - if ((ipropfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - const char *e_txt; - ret = SOCKET_ERRNO; - e_txt = krb5_get_error_message (context, ret); - krb5_klog_syslog(LOG_ERR, - "cannot create iprop listening socket: %s", - e_txt); - fprintf(stderr, "cannot create iprop listening socket: %s", - e_txt); - kadm5_destroy(global_server_handle); - krb5_klog_close(context); - exit(1); - } - set_cloexec_fd(ipropfd); -#endif - -#ifdef SO_REUSEADDR - /* the old admin server turned on SO_REUSEADDR for non-default - port numbers. this was necessary, on solaris, for the tests - to work. jhawk argues that the debug and production modes - should be the same. I think I agree, so I'm always going to set - SO_REUSEADDR. The other option is to have the unit tests wait - until the port is useable, or use a different port each time. - --marc */ - - { - int allowed; - - allowed = 1; - if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, - (char *) &allowed, sizeof(allowed)) < 0 || - setsockopt(schpw, SOL_SOCKET, SO_REUSEADDR, - (char *) &allowed, sizeof(allowed)) < 0 -#ifndef DISABLE_IPROP - || setsockopt(ipropfd, SOL_SOCKET, SO_REUSEADDR, - (char *) &allowed, sizeof(allowed)) < 0 -#endif - ) { - const char *e_txt; - ret = SOCKET_ERRNO; - e_txt = krb5_get_error_message (context, ret); - krb5_klog_syslog(LOG_ERR, "Cannot set SO_REUSEADDR: %s", - e_txt); - fprintf(stderr, "Cannot set SO_REUSEADDR: %s", e_txt); - kadm5_destroy(global_server_handle); - krb5_klog_close(context); - exit(1); - } - } -#endif /* SO_REUSEADDR */ - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; - addr.sin_port = htons(params.kadmind_port); - - if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - int oerrno = errno; - const char *e_txt = krb5_get_error_message (context, errno); - fprintf(stderr, "%s: Cannot bind socket.\n", whoami); - fprintf(stderr, "bind: %s\n", e_txt); - errno = oerrno; - krb5_klog_syslog(LOG_ERR, "Cannot bind socket: %s", e_txt); - if(oerrno == EADDRINUSE) { - char *w = strrchr(whoami, '/'); - if (w) { - w++; - } - else { - w = whoami; - } - fprintf(stderr, -"This probably means that another %s process is already\n" -"running, or that another program is using the server port (number %d)\n" -"after being assigned it by the RPC portmap daemon. If another\n" -"%s is already running, you should kill it before\n" -"restarting the server. If, on the other hand, another program is\n" -"using the server port, you should kill it before running\n" -"%s, and ensure that the conflict does not occur in the\n" -"future by making sure that %s is started on reboot\n" - "before portmap.\n", w, ntohs(addr.sin_port), w, w, w); - krb5_klog_syslog(LOG_ERR, "Check for already-running %s or for " - "another process using port %d", w, - htons(addr.sin_port)); - } + if ((ret = setup_network(global_server_handle, whoami))) { + const char *e_txt = krb5_get_error_message (context, ret); + krb5_klog_syslog(LOG_ERR, "%s: %s while initializing network, aborting", + whoami, e_txt); + fprintf(stderr, "%s: %s while initializing network, aborting\n", + whoami, e_txt); kadm5_destroy(global_server_handle); krb5_klog_close(context); exit(1); } - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; - /* XXX */ - addr.sin_port = htons(params.kpasswd_port); - - if (bind(schpw, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - char portbuf[32]; - int oerrno = errno; - const char *e_txt = krb5_get_error_message (context, errno); - fprintf(stderr, "%s: Cannot bind socket.\n", whoami); - fprintf(stderr, "bind: %s\n", e_txt); - errno = oerrno; - snprintf(portbuf, sizeof(portbuf), "%d", ntohs(addr.sin_port)); - krb5_klog_syslog(LOG_ERR, "cannot bind simple chpw socket: %s", - e_txt); - if(oerrno == EADDRINUSE) { - char *w = strrchr(whoami, '/'); - if (w) { - w++; - } - else { - w = whoami; - } - fprintf(stderr, -"This probably means that another %s process is already\n" -"running, or that another program is using the server port (number %d).\n" -"If another %s is already running, you should kill it before\n" -"restarting the server.\n", - w, ntohs(addr.sin_port), w); - } - kadm5_destroy(global_server_handle); - krb5_klog_close(context); - exit(1); - } - -#ifndef DISABLE_IPROP - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = INADDR_ANY; - addr.sin_port = htons(params.iprop_port); - if (bind(ipropfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - char portbuf[32]; - int oerrno = errno; - const char *e_txt = krb5_get_error_message (context, errno); - fprintf(stderr, "%s: Cannot bind socket.\n", whoami); - fprintf(stderr, "bind: %s\n", e_txt); - errno = oerrno; - snprintf(portbuf, sizeof(portbuf), "%d", ntohs(addr.sin_port)); - krb5_klog_syslog(LOG_ERR, "cannot bind iprop socket: %s", - e_txt); - if(oerrno == EADDRINUSE) { - char *w = strrchr(whoami, '/'); - if (w) { - w++; - } - else { - w = whoami; - } - fprintf(stderr, -"This probably means that another %s process is already\n" -"running, or that another program is using the server port (number %d).\n" -"If another %s is already running, you should kill it before\n" -"restarting the server.\n", - w, ntohs(addr.sin_port), w); - } - kadm5_destroy(global_server_handle); - krb5_klog_close(context); - exit(1); - } -#endif - - transp = svctcp_create(s, 0, 0); - if(transp == NULL) { - fprintf(stderr, "%s: Cannot create RPC service.\n", whoami); - krb5_klog_syslog(LOG_ERR, "Cannot create RPC service: %m"); - kadm5_destroy(global_server_handle); - krb5_klog_close(context); - exit(1); - } - if(!svc_register(transp, KADM, KADMVERS, kadm_1, 0)) { - fprintf(stderr, "%s: Cannot register RPC service.\n", whoami); - krb5_klog_syslog(LOG_ERR, "Cannot register RPC service, failing."); - kadm5_destroy(global_server_handle); - krb5_klog_close(context); - exit(1); - } - -#ifndef DISABLE_IPROP - iproptransp = svctcp_create(ipropfd, 0, 0); - if (iproptransp == NULL) { - fprintf(stderr, "%s: Cannot create RPC service.\n", whoami); - krb5_klog_syslog(LOG_ERR, "Cannot create RPC service: %m"); - kadm5_destroy(global_server_handle); - krb5_klog_close(context); - exit(1); - } - if (!svc_register(iproptransp, KRB5_IPROP_PROG, KRB5_IPROP_VERS, krb5_iprop_prog_1, IPPROTO_TCP)) { - fprintf(stderr, "%s: Cannot register RPC service.\n", whoami); - krb5_klog_syslog(LOG_ERR, "Cannot register RPC service, continuing."); -#if 0 - kadm5_destroy(global_server_handle); - krb5_klog_close(context); - exit(1); -#endif - } -#endif - names[0].name = build_princ_name(KADM5_ADMIN_SERVICE, params.realm); names[1].name = build_princ_name(KADM5_CHANGEPW_SERVICE, params.realm); names[2].name = build_princ_name(OVSEC_KADM_ADMIN_SERVICE, params.realm); @@ -827,13 +602,13 @@ kterr: if (nofork) fprintf(stderr, "%s: starting...\n", whoami); - kadm_svc_run(¶ms); + listen_and_process(global_server_handle, whoami); krb5_klog_syslog(LOG_INFO, "finished, exiting"); /* Clean up memory, etc */ svcauth_gssapi_unset_names(); kadm5_destroy(global_server_handle); - close(s); + closedown_network(global_server_handle, whoami); kadm5int_acl_finish(context, 0); if(gss_changepw_name) { (void) gss_release_name(&OMret, &gss_changepw_name); @@ -841,9 +616,9 @@ kterr: if(gss_oldchangepw_name) { (void) gss_release_name(&OMret, &gss_oldchangepw_name); } - for(s = 0 ; s < 4; s++) { - if (names[s].name) { - free(names[s].name); + for(i = 0 ; i < 4; i++) { + if (names[i].name) { + free(names[i].name); } } @@ -905,68 +680,6 @@ void setup_signal_handlers(iprop_role iproprole) { #endif /* POSIX_SIGNALS */ } -/* - * Function: kadm_svc_run - * - * Purpose: modified version of sunrpc svc_run. - * which closes the database every TIMEOUT seconds. - * - * Arguments: - * Requires: - * Effects: - * Modifies: - */ - -void kadm_svc_run(params) -kadm5_config_params *params; -{ - fd_set rfd; - struct timeval timeout; - - while(signal_request_exit == 0) { - if (signal_request_hup) { - reset_db(); - krb5_klog_reopen(context); - signal_request_hup = 0; - } -#ifdef PURIFY - if (signal_pure_report) /* check to see if a report */ - /* should be dumped... */ - { - purify_new_reports(); - signal_pure_report = 0; - } - if (signal_pure_clear) /* ...before checking whether */ - /* the info should be cleared. */ - { - purify_clear_new_reports(); - signal_pure_clear = 0; - } -#endif /* PURIFY */ - timeout.tv_sec = TIMEOUT; - timeout.tv_usec = 0; - rfd = svc_fdset; - FD_SET(schpw, &rfd); -#define max(a, b) (((a) > (b)) ? (a) : (b)) - switch(select(max(schpw, svc_maxfd) + 1, - (fd_set *) &rfd, NULL, NULL, &timeout)) { - case -1: - if(errno == EINTR) - continue; - perror("select"); - return; - case 0: - reset_db(); - break; - default: - if (FD_ISSET(schpw, &rfd)) - do_schpw(schpw, params); - else - svc_getreqset(&rfd); - } - } -} - #ifdef PURIFY /* * Function: request_pure_report @@ -1338,99 +1051,3 @@ void log_badauth_display_status_1(char *m, OM_uint32 code, int type, } } -void do_schpw(int s1, kadm5_config_params *params) -{ - krb5_error_code ret; - /* XXX buffer = ethernet mtu */ - char req[1500]; - int len; - struct sockaddr_in from; - socklen_t fromlen; - krb5_keytab kt; - krb5_data reqdata, repdata; - int s2; - - fromlen = sizeof(from); - if ((len = recvfrom(s1, req, sizeof(req), 0, (struct sockaddr *)&from, - &fromlen)) < 0) { - krb5_klog_syslog(LOG_ERR, "chpw: Couldn't receive request: %s", - krb5_get_error_message (context, errno)); - return; - } - - if ((ret = krb5_kt_resolve(context, "KDB:", &kt))) { - krb5_klog_syslog(LOG_ERR, "chpw: Couldn't open admin keytab %s", - krb5_get_error_message (context, ret)); - return; - } - - reqdata.length = len; - reqdata.data = req; - - /* this is really obscure. s1 is used for all communications. it - is left unconnected in case the server is multihomed and routes - are asymmetric. s2 is connected to resolve routes and get - addresses. this is the *only* way to get proper addresses for - multihomed hosts if routing is asymmetric. - - A related problem in the server, but not the client, is that - many os's have no way to disconnect a connected udp socket, so - the s2 socket needs to be closed and recreated for each - request. The s1 socket must not be closed, or else queued - requests will be lost. - - A "naive" client implementation (one socket, no connect, - hostname resolution to get the local ip addr) will work and - interoperate if the client is single-homed. */ - - if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { - const char *errmsg = krb5_get_error_message (context, errno); - krb5_klog_syslog(LOG_ERR, "cannot create connecting socket: %s", - errmsg); - fprintf(stderr, "Cannot create connecting socket: %s", - errmsg); - svcauth_gssapi_unset_names(); - kadm5_destroy(global_server_handle); - krb5_klog_close(context); - exit(1); - } - set_cloexec_fd(s2); - - if (connect(s2, (struct sockaddr *) &from, sizeof(from)) < 0) { - krb5_klog_syslog(LOG_ERR, "chpw: Couldn't connect to client: %s", - krb5_get_error_message (context, errno)); - goto cleanup; - } - - if ((ret = process_chpw_request(context, global_server_handle, - params->realm, s2, kt, &from, - &reqdata, &repdata))) { - krb5_klog_syslog(LOG_ERR, "chpw: Error processing request: %s", - krb5_get_error_message (context, ret)); - } - - close(s2); - - if (repdata.length == 0) { - /* just return. This means something really bad happened */ - goto cleanup; - } - - len = sendto(s1, repdata.data, (int) repdata.length, 0, - (struct sockaddr *) &from, sizeof(from)); - - if (len < (int) repdata.length) { - krb5_xfree(repdata.data); - - krb5_klog_syslog(LOG_ERR, "chpw: Error sending reply: %s", - krb5_get_error_message (context, errno)); - goto cleanup; - } - - krb5_xfree(repdata.data); - -cleanup: - krb5_kt_close(context, kt); - - return; -} diff --git a/src/kadmin/server/schpw.c b/src/kadmin/server/schpw.c index c81fa33..53f2e59 100644 --- a/src/kadmin/server/schpw.c +++ b/src/kadmin/server/schpw.c @@ -14,30 +14,26 @@ #define RFC3244_VERSION 0xff80 krb5_error_code -process_chpw_request(context, server_handle, realm, s, keytab, sockin, - req, rep) +process_chpw_request(context, server_handle, realm, keytab, + local_faddr, remote_faddr, req, rep) krb5_context context; void *server_handle; char *realm; - int s; krb5_keytab keytab; - struct sockaddr_in *sockin; + krb5_fulladdr *local_faddr; + krb5_fulladdr *remote_faddr; krb5_data *req; krb5_data *rep; { krb5_error_code ret; char *ptr; int plen, vno; - krb5_address local_kaddr, remote_kaddr; - int allocated_mem = 0; krb5_data ap_req, ap_rep; krb5_auth_context auth_context; krb5_principal changepw; krb5_principal client, target = NULL; krb5_ticket *ticket; krb5_data cipher, clear; - struct sockaddr local_addr, remote_addr; - GETSOCKNAME_ARG3_TYPE addrlen; krb5_replay_data replay; krb5_error krberror; int numresult; @@ -45,6 +41,10 @@ process_chpw_request(context, server_handle, realm, s, keytab, sockin, char *clientstr = NULL, *targetstr = NULL; size_t clen; char *cdots; + struct sockaddr_storage ss; + socklen_t salen; + char addrbuf[100]; + krb5_address *addr = remote_faddr->address; ret = 0; rep->length = 0; @@ -142,61 +142,6 @@ process_chpw_request(context, server_handle, realm, s, keytab, sockin, goto chpwfail; } - /* set up address info */ - - addrlen = sizeof(local_addr); - - if (getsockname(s, &local_addr, &addrlen) < 0) { - ret = errno; - numresult = KRB5_KPASSWD_HARDERROR; - strlcpy(strresult, "Failed getting server internet address", - sizeof(strresult)); - goto chpwfail; - } - - /* some brain-dead OS's don't return useful information from - * the getsockname call. Namely, windows and solaris. */ - - if (((struct sockaddr_in *)&local_addr)->sin_addr.s_addr != 0) { - local_kaddr.addrtype = ADDRTYPE_INET; - local_kaddr.length = - sizeof(((struct sockaddr_in *) &local_addr)->sin_addr); - local_kaddr.contents = - (krb5_octet *) &(((struct sockaddr_in *) &local_addr)->sin_addr); - } else { - krb5_address **addrs; - - krb5_os_localaddr(context, &addrs); - local_kaddr.magic = addrs[0]->magic; - local_kaddr.addrtype = addrs[0]->addrtype; - local_kaddr.length = addrs[0]->length; - local_kaddr.contents = malloc(addrs[0]->length); - memcpy(local_kaddr.contents, addrs[0]->contents, addrs[0]->length); - allocated_mem++; - - krb5_free_addresses(context, addrs); - } - - addrlen = sizeof(remote_addr); - - if (getpeername(s, &remote_addr, &addrlen) < 0) { - ret = errno; - numresult = KRB5_KPASSWD_HARDERROR; - strlcpy(strresult, "Failed getting client internet address", - sizeof(strresult)); - goto chpwfail; - } - - remote_kaddr.addrtype = ADDRTYPE_INET; - remote_kaddr.length = - sizeof(((struct sockaddr_in *) &remote_addr)->sin_addr); - remote_kaddr.contents = - (krb5_octet *) &(((struct sockaddr_in *) &remote_addr)->sin_addr); - - remote_kaddr.addrtype = ADDRTYPE_INET; - remote_kaddr.length = sizeof(sockin->sin_addr); - remote_kaddr.contents = (krb5_octet *) &sockin->sin_addr; - /* mk_priv requires that the local address be set. getsockname is used for this. rd_priv requires that the remote address be set. recvfrom is used for this. If @@ -212,7 +157,7 @@ process_chpw_request(context, server_handle, realm, s, keytab, sockin, is specified. Are we having fun yet? */ ret = krb5_auth_con_setaddrs(context, auth_context, NULL, - &remote_kaddr); + remote_faddr->address); if (ret) { numresult = KRB5_KPASSWD_HARDERROR; strlcpy(strresult, "Failed storing client internet address", @@ -310,6 +255,39 @@ process_chpw_request(context, server_handle, realm, s, keytab, sockin, clen = strlen(clientstr); trunc_name(&clen, &cdots); + switch (addr->addrtype) { + case ADDRTYPE_INET: { + struct sockaddr_in *sin = ss2sin(&ss); + + sin->sin_family = AF_INET; + memcpy(&sin->sin_addr, addr->contents, addr->length); + sin->sin_port = htons(remote_faddr->port); + salen = sizeof(*sin); + break; + } + case ADDRTYPE_INET6: { + struct sockaddr_in6 *sin6 = ss2sin6(&ss); + + sin6->sin6_family = AF_INET6; + memcpy(&sin6->sin6_addr, addr->contents, addr->length); + sin6->sin6_port = htons(remote_faddr->port); + salen = sizeof(*sin6); + break; + } + default: { + struct sockaddr *sa = ss2sa(&ss); + + sa->sa_family = AF_UNSPEC; + salen = sizeof(*sa); + break; + } + } + + if (getnameinfo(ss2sa(&ss), salen, + addrbuf, sizeof(addrbuf), NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV) != 0) + strlcpy(addrbuf, "<unprintable>", sizeof(addrbuf)); + if (vno == RFC3244_VERSION) { size_t tlen; char *tdots; @@ -326,13 +304,13 @@ process_chpw_request(context, server_handle, realm, s, keytab, sockin, } krb5_klog_syslog(LOG_NOTICE, "setpw request from %s by %.*s%s for %.*s%s: %s", - inet_ntoa(((struct sockaddr_in *)&remote_addr)->sin_addr), + addrbuf, (int) clen, clientstr, cdots, (int) tlen, targetp, tdots, ret ? krb5_get_error_message (context, ret) : "success"); } else { krb5_klog_syslog(LOG_NOTICE, "chpw request from %s for %.*s%s: %s", - inet_ntoa(((struct sockaddr_in *)&remote_addr)->sin_addr), + addrbuf, (int) clen, clientstr, cdots, ret ? krb5_get_error_message (context, ret) : "success"); } @@ -371,8 +349,8 @@ chpwfail: cipher.length = 0; if (ap_rep.length) { - ret = krb5_auth_con_setaddrs(context, auth_context, &local_kaddr, - NULL); + ret = krb5_auth_con_setaddrs(context, auth_context, + local_faddr->address, NULL); if (ret) { numresult = KRB5_KPASSWD_HARDERROR; strlcpy(strresult, @@ -483,8 +461,6 @@ bailout: krb5_xfree(clear.data); if (cipher.length) krb5_xfree(cipher.data); - if (allocated_mem) - krb5_xfree(local_kaddr.contents); if (target) krb5_free_principal(context, target); if (targetstr) |