/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* lib/apputils/net-server.c - Network code for krb5 servers (kdc, kadmind) */ /* * Copyright 1990,2000,2007,2008,2009,2010,2016 by the Massachusetts Institute * of Technology. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. */ #include "k5-int.h" #include "adm_proto.h" #include #include #include #include "port-sockets.h" #include "socket-utils.h" #include #ifdef HAVE_NETINET_IN_H #include #include #include #ifdef HAVE_SYS_SOCKIO_H /* for SIOCGIFCONF, etc. */ #include #endif #include #if HAVE_SYS_SELECT_H #include #endif #include #ifndef ARPHRD_ETHER /* OpenBSD breaks on multiple inclusions */ #include #endif #ifdef HAVE_SYS_FILIO_H #include /* FIONBIO */ #endif #include "fake-addrinfo.h" #include "net-server.h" #include #include #include "udppktinfo.h" /* XXX */ #define KDC5_NONET (-1779992062L) static int tcp_or_rpc_data_counter; static int max_tcp_or_rpc_data_connections = 45; static int setreuseaddr(int sock, int value) { int st; st = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)); if (st) return st; #ifdef SO_REUSEPORT st = setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value)); if (st) return st; #endif return 0; } #if defined(IPV6_V6ONLY) static int setv6only(int sock, int value) { return setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value)); } #endif static const char * paddr(struct sockaddr *sa) { static char buf[100]; char portbuf[10]; if (getnameinfo(sa, sa_socklen(sa), buf, sizeof(buf), portbuf, sizeof(portbuf), NI_NUMERICHOST|NI_NUMERICSERV)) strlcpy(buf, "", 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; } /* KDC data. */ enum conn_type { CONN_UDP, CONN_TCP_LISTENER, CONN_TCP, CONN_RPC_LISTENER, CONN_RPC }; enum bind_type { UDP, TCP, RPC }; static const char *const bind_type_names[] = { [UDP] = "UDP", [TCP] = "TCP", [RPC] = "RPC", }; /* Per-connection info. */ struct connection { void *handle; const char *prog; enum conn_type type; /* Connection fields (TCP or RPC) */ struct sockaddr_storage addr_s; socklen_t addrlen; char addrbuf[56]; krb5_address remote_addr_buf; krb5_fulladdr remote_addr; /* Incoming data (TCP) */ size_t bufsiz; size_t offset; char *buffer; size_t msglen; /* Outgoing data (TCP) */ krb5_data *response; unsigned char lenbuf[4]; sg_buf sgbuf[2]; sg_buf *sgp; int sgnum; /* Crude denial-of-service avoidance support (TCP or RPC) */ time_t start_time; /* RPC-specific fields */ SVCXPRT *transp; int rpc_force_close; }; #define SET(TYPE) struct { TYPE *data; size_t n, max; } /* Start at the top and work down -- this should allow for deletions without disrupting the iteration, since we delete by overwriting the element to be removed with the last element. */ #define FOREACH_ELT(set,idx,vvar) \ for (idx = set.n-1; idx >= 0 && (vvar = set.data[idx], 1); idx--) #define GROW_SET(set, incr, tmpptr) \ ((set.max + incr < set.max \ || ((set.max + incr) * sizeof(set.data[0]) / sizeof(set.data[0]) \ != set.max + incr)) \ ? 0 /* overflow */ \ : ((tmpptr = realloc(set.data, \ (set.max + incr) * sizeof(set.data[0]))) \ ? (set.data = tmpptr, set.max += incr, 1) \ : 0)) /* 1 = success, 0 = failure */ #define ADD(set, val, tmpptr) \ ((set.n < set.max || GROW_SET(set, 10, tmpptr)) \ ? (set.data[set.n++] = val, 1) \ : 0) #define DEL(set, idx) \ (set.data[idx] = set.data[--set.n], 0) #define FREE_SET_DATA(set) \ (free(set.data), set.data = 0, set.max = 0, set.n = 0) /* * N.B.: The Emacs cc-mode indentation code seems to get confused if * the macro argument here is one word only. So use "unsigned short" * instead of the "u_short" we were using before. */ struct rpc_svc_data { u_long prognum; u_long versnum; void (*dispatch)(); }; struct bind_address { char *address; u_short port; enum bind_type type; struct rpc_svc_data rpc_svc_data; }; static SET(verto_ev *) events; static SET(struct bind_address) bind_addresses; verto_ctx * loop_init(verto_ev_type types) { types |= VERTO_EV_TYPE_IO; types |= VERTO_EV_TYPE_SIGNAL; types |= VERTO_EV_TYPE_TIMEOUT; return verto_default(NULL, types); } static void do_break(verto_ctx *ctx, verto_ev *ev) { krb5_klog_syslog(LOG_DEBUG, _("Got signal to request exit")); verto_break(ctx); } struct sighup_context { void *handle; void (*reset)(void *); }; static void do_reset(verto_ctx *ctx, verto_ev *ev) { struct sighup_context *sc = (struct sighup_context*) verto_get_private(ev); krb5_klog_syslog(LOG_DEBUG, _("Got signal to reset")); krb5_klog_reopen(get_context(sc->handle)); if (sc->reset) sc->reset(sc->handle); } static void free_sighup_context(verto_ctx *ctx, verto_ev *ev) { free(verto_get_private(ev)); } krb5_error_code loop_setup_signals(verto_ctx *ctx, void *handle, void (*reset)()) { struct sighup_context *sc; verto_ev *ev; if (!verto_add_signal(ctx, VERTO_EV_FLAG_PERSIST, do_break, SIGINT) || !verto_add_signal(ctx, VERTO_EV_FLAG_PERSIST, do_break, SIGTERM) || !verto_add_signal(ctx, VERTO_EV_FLAG_PERSIST, do_break, SIGQUIT) || !verto_add_signal(ctx, VERTO_EV_FLAG_PERSIST, VERTO_SIG_IGN, SIGPIPE)) return ENOMEM; ev = verto_add_signal(ctx, VERTO_EV_FLAG_PERSIST, do_reset, SIGHUP); if (!ev) return ENOMEM; sc = malloc(sizeof(*sc)); if (!sc) return ENOMEM; sc->handle = handle; sc->reset = reset; verto_set_private(ev, sc, free_sighup_context); return 0; } /* * Add a bind address to the loop. * * Arguments: * - address * A string for the address (or hostname). Pass NULL to use the wildcard * address. The address is parsed with k5_parse_host_string(). * - port * What port the socket should be set to. * - type * bind_type for the socket. * - rpc_data * For RPC addresses, the svc_register() arguments to use when TCP * connections are created. Ignored for other types. */ static krb5_error_code loop_add_address(const char *address, int port, enum bind_type type, struct rpc_svc_data *rpc_data) { struct bind_address addr, val; int i; void *tmp; char *addr_copy = NULL; assert(!(type == RPC && rpc_data == NULL)); /* Make sure a valid port number was passed. */ if (port < 0 || port > 65535) { krb5_klog_syslog(LOG_ERR, _("Invalid port %d"), port); return EINVAL; } /* Check for conflicting addresses. */ FOREACH_ELT(bind_addresses, i, val) { if (type != val.type || port != val.port) continue; /* If a wildcard address is being added, make sure to remove any direct * addresses. */ if (address == NULL && val.address != NULL) { krb5_klog_syslog(LOG_DEBUG, _("Removing address %s since wildcard address" " is being added"), val.address); free(val.address); DEL(bind_addresses, i); } else if (val.address == NULL || !strcmp(address, val.address)) { krb5_klog_syslog(LOG_DEBUG, _("Address already added to server")); return 0; } } /* Copy the address if it is specified. */ if (address != NULL) { addr_copy = strdup(address); if (addr_copy == NULL) return ENOMEM; } /* Add the new address to bind_addresses. */ memset(&addr, 0, sizeof(addr)); addr.address = addr_copy; addr.port = port; addr.type = type; if (rpc_data != NULL) addr.rpc_svc_data = *rpc_data; if (!ADD(bind_addresses, addr, tmp)) { free(addr_copy); return ENOMEM; } return 0; } /* * Add bind addresses to the loop. * * Arguments: * * - addresses * A string for the addresses. Pass NULL to use the wildcard address. * Supported delimiters can be found in ADDRESSES_DELIM. Addresses are * parsed with k5_parse_host_name(). * - default_port * What port the socket should be set to if not specified in addresses. * - type * bind_type for the socket. * - rpc_data * For RPC addresses, the svc_register() arguments to use when TCP * connections are created. Ignored for other types. */ static krb5_error_code loop_add_addresses(const char *addresses, int default_port, enum bind_type type, struct rpc_svc_data *rpc_data) { krb5_error_code ret = 0; char *addresses_copy = NULL, *host = NULL, *saveptr, *addr; int port; /* If no addresses are set, add a wildcard address. */ if (addresses == NULL) return loop_add_address(NULL, default_port, type, rpc_data); /* Copy the addresses string before using strtok(). */ addresses_copy = strdup(addresses); if (addresses_copy == NULL) { ret = ENOMEM; goto cleanup; } /* Start tokenizing the addresses string. If we get NULL the string * contained no addresses, so add a wildcard address. */ addr = strtok_r(addresses_copy, ADDRESSES_DELIM, &saveptr); if (addr == NULL) { ret = loop_add_address(NULL, default_port, type, rpc_data); goto cleanup; } /* Loop through each address and add it to the loop. */ for (; addr != NULL; addr = strtok_r(NULL, ADDRESSES_DELIM, &saveptr)) { /* Parse the host string. */ ret = k5_parse_host_string(addr, default_port, &host, &port); if (ret) goto cleanup; ret = loop_add_address(host, port, type, rpc_data); if (ret) goto cleanup; free(host); host = NULL; } cleanup: free(addresses_copy); free(host); return ret; } krb5_error_code loop_add_udp_address(int default_port, const char *addresses) { return loop_add_addresses(addresses, default_port, UDP, NULL); } krb5_error_code loop_add_tcp_address(int default_port, const char *addresses) { return loop_add_addresses(addresses, default_port, TCP, NULL); } krb5_error_code loop_add_rpc_service(int default_port, const char *addresses, u_long prognum, u_long versnum, void (*dispatchfn)()) { struct rpc_svc_data svc; svc.prognum = prognum; svc.versnum = versnum; svc.dispatch = dispatchfn; return loop_add_addresses(addresses, default_port, RPC, &svc); } #define USE_AF AF_INET #define USE_TYPE SOCK_DGRAM #define USE_PROTO 0 #define SOCKET_ERRNO errno #include "foreachaddr.h" static void free_connection(struct connection *conn) { if (!conn) return; if (conn->response) krb5_free_data(get_context(conn->handle), conn->response); if (conn->buffer) free(conn->buffer); if (conn->type == CONN_RPC_LISTENER && conn->transp != NULL) svc_destroy(conn->transp); free(conn); } static void remove_event_from_set(verto_ev *ev) { verto_ev *tmp; int i; /* Remove the event from the events. */ FOREACH_ELT(events, i, tmp) if (tmp == ev) { DEL(events, i); break; } } static void free_socket(verto_ctx *ctx, verto_ev *ev) { struct connection *conn = NULL; fd_set fds; int fd; remove_event_from_set(ev); fd = verto_get_fd(ev); conn = verto_get_private(ev); /* Close the file descriptor. */ krb5_klog_syslog(LOG_INFO, _("closing down fd %d"), fd); if (fd >= 0 && (!conn || conn->type != CONN_RPC || conn->rpc_force_close)) close(fd); /* Free the connection struct. */ if (conn) { switch (conn->type) { case CONN_RPC: if (conn->rpc_force_close) { FD_ZERO(&fds); FD_SET(fd, &fds); svc_getreqset(&fds); if (FD_ISSET(fd, &svc_fdset)) { krb5_klog_syslog(LOG_ERR, _("descriptor %d closed but still " "in svc_fdset"), fd); } } /* Fall through. */ case CONN_TCP: tcp_or_rpc_data_counter--; break; default: break; } free_connection(conn); } } static verto_ev * make_event(verto_ctx *ctx, verto_ev_flag flags, verto_callback callback, int sock, struct connection *conn) { verto_ev *ev; void *tmp; ev = verto_add_io(ctx, flags, callback, sock); if (!ev) { com_err(conn->prog, ENOMEM, _("cannot create io event")); return NULL; } if (!ADD(events, ev, tmp)) { com_err(conn->prog, ENOMEM, _("cannot save event")); verto_del(ev); return NULL; } verto_set_private(ev, conn, free_socket); return ev; } static krb5_error_code add_fd(int sock, enum conn_type conntype, verto_ev_flag flags, void *handle, const char *prog, verto_ctx *ctx, verto_callback callback, verto_ev **ev_out) { struct connection *newconn; *ev_out = NULL; #ifndef _WIN32 if (sock >= FD_SETSIZE) { com_err(prog, 0, _("file descriptor number %d too high"), sock); return EMFILE; } #endif newconn = malloc(sizeof(*newconn)); if (newconn == NULL) { com_err(prog, ENOMEM, _("cannot allocate storage for connection info")); return ENOMEM; } memset(newconn, 0, sizeof(*newconn)); newconn->handle = handle; newconn->prog = prog; newconn->type = conntype; *ev_out = make_event(ctx, flags, callback, sock, newconn); return 0; } static void process_packet(verto_ctx *ctx, verto_ev *ev); static void accept_tcp_connection(verto_ctx *ctx, verto_ev *ev); static void process_tcp_connection_read(verto_ctx *ctx, verto_ev *ev); static void process_tcp_connection_write(verto_ctx *ctx, verto_ev *ev); static void accept_rpc_connection(verto_ctx *ctx, verto_ev *ev); static void process_rpc_connection(verto_ctx *ctx, verto_ev *ev); /* * Create a socket and bind it to addr. Ensure the socket will work with * select(). Set the socket cloexec, reuseaddr, and if applicable v6-only. * Does not call listen(). On failure, log an error and return an error code. */ static krb5_error_code create_server_socket(struct sockaddr *addr, int type, const char *prog, int *fd_out) { int sock, e; *fd_out = -1; sock = socket(addr->sa_family, type, 0); if (sock == -1) { e = errno; com_err(prog, e, _("Cannot create TCP server socket on %s"), paddr(addr)); return e; } set_cloexec_fd(sock); #ifndef _WIN32 /* Windows FD_SETSIZE is a count. */ if (sock >= FD_SETSIZE) { close(sock); com_err(prog, 0, _("TCP socket fd number %d (for %s) too high"), sock, paddr(addr)); return EMFILE; } #endif if (setreuseaddr(sock, 1) < 0) com_err(prog, errno, _("Cannot enable SO_REUSEADDR on fd %d"), sock); if (addr->sa_family == AF_INET6) { #ifdef IPV6_V6ONLY if (setv6only(sock, 1)) { com_err(prog, errno, _("setsockopt(%d,IPV6_V6ONLY,1) failed"), sock); } else { com_err(prog, 0, _("setsockopt(%d,IPV6_V6ONLY,1) worked"), sock); } #else krb5_klog_syslog(LOG_INFO, _("no IPV6_V6ONLY socket option support")); #endif /* IPV6_V6ONLY */ } if (bind(sock, addr, sa_socklen(addr)) == -1) { e = errno; com_err(prog, e, _("Cannot bind server socket on %s"), paddr(addr)); close(sock); return e; } *fd_out = sock; return 0; } static const int one = 1; static int setnbio(int sock) { return ioctlsocket(sock, FIONBIO, (const void *)&one); } static int setkeepalive(int sock) { return setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one)); } static int setnolinger(int s) { static const struct linger ling = { 0, 0 }; return setsockopt(s, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)); } /* An enum map to socket families for each bind_type. */ static const int bind_socktypes[] = { [UDP] = SOCK_DGRAM, [TCP] = SOCK_STREAM, [RPC] = SOCK_STREAM }; /* An enum map containing conn_type (for struct connection) for each * bind_type. */ static const enum conn_type bind_conn_types[] = { [UDP] = CONN_UDP, [TCP] = CONN_TCP_LISTENER, [RPC] = CONN_RPC_LISTENER }; /* * Set up a listening socket. * * Arguments: * * - ba * The bind address and port for the socket. * - ai * The addrinfo struct to use for creating the socket. * - ctype * The conn_type of this socket. */ static krb5_error_code setup_socket(struct bind_address *ba, struct sockaddr *sock_address, void *handle, const char *prog, verto_ctx *ctx, int tcp_listen_backlog, verto_callback vcb, enum conn_type ctype) { krb5_error_code ret; struct connection *conn; verto_ev_flag flags; verto_ev *ev = NULL; int sock = -1; krb5_klog_syslog(LOG_DEBUG, _("Setting up %s socket for address %s"), bind_type_names[ba->type], paddr(sock_address)); /* Create the socket. */ ret = create_server_socket(sock_address, bind_socktypes[ba->type], prog, &sock); if (ret) goto cleanup; /* Listen for backlogged connections on TCP sockets. (For RPC sockets this * will be done by svc_register().) */ if (ba->type == TCP && listen(sock, tcp_listen_backlog) != 0) { ret = errno; com_err(prog, errno, _("Cannot listen on %s server socket on %s"), bind_type_names[ba->type], paddr(sock_address)); goto cleanup; } /* Set non-blocking I/O for UDP and TCP listener sockets. */ if (ba->type != RPC && setnbio(sock) != 0) { ret = errno; com_err(prog, errno, _("cannot set listening %s socket on %s non-blocking"), bind_type_names[ba->type], paddr(sock_address)); goto cleanup; } /* Turn off the linger option for TCP sockets. */ if (ba->type == TCP && setnolinger(sock) != 0) { ret = errno; com_err(prog, errno, _("cannot set SO_LINGER on %s socket on %s"), bind_type_names[ba->type], paddr(sock_address)); goto cleanup; } /* Try to turn on pktinfo for UDP wildcard sockets. */ if (ba->type == UDP && sa_is_wildcard(sock_address)) { krb5_klog_syslog(LOG_DEBUG, _("Setting pktinfo on socket %s"), paddr(sock_address)); ret = set_pktinfo(sock, sock_address->sa_family); if (ret) { com_err(prog, ret, _("Cannot request packet info for UDP socket address " "%s port %d"), paddr(sock_address), ba->port); krb5_klog_syslog(LOG_INFO, _("System does not support pktinfo yet " "binding to a wildcard address. " "Packets are not guaranteed to " "return on the received address.")); } } /* Add the socket to the event loop. */ flags = VERTO_EV_FLAG_IO_READ | VERTO_EV_FLAG_PERSIST | VERTO_EV_FLAG_REINITIABLE; ret = add_fd(sock, ctype, flags, handle, prog, ctx, vcb, &ev); if (ret) { krb5_klog_syslog(LOG_ERR, _("Error attempting to add verto event")); goto cleanup; } if (ba->type == RPC) { conn = verto_get_private(ev); conn->transp = svctcp_create(sock, 0, 0); if (conn->transp == NULL) { ret = errno; krb5_klog_syslog(LOG_ERR, _("Cannot create RPC service: %s"), strerror(ret)); goto cleanup; } ret = svc_register(conn->transp, ba->rpc_svc_data.prognum, ba->rpc_svc_data.versnum, ba->rpc_svc_data.dispatch, 0); if (!ret) { ret = errno; krb5_klog_syslog(LOG_ERR, _("Cannot register RPC service: %s"), strerror(ret)); goto cleanup; } } ev = NULL; sock = -1; ret = 0; cleanup: if (sock >= 0) close(sock); if (ev != NULL) verto_del(ev); return ret; } /* * Setup all the socket addresses that the net-server should listen to. * * This function uses getaddrinfo to figure out all the addresses. This will * automatically figure out which socket families that should be used on the * host making it useful even for wildcard addresses. */ static krb5_error_code setup_addresses(verto_ctx *ctx, void *handle, const char *prog, int tcp_listen_backlog) { /* An bind_type enum map for the verto callback functions. */ static verto_callback *const verto_callbacks[] = { [UDP] = &process_packet, [TCP] = &accept_tcp_connection, [RPC] = &accept_rpc_connection }; krb5_error_code ret = 0; size_t i; int err, bound_any; struct bind_address addr; struct addrinfo hints, *ai_list = NULL, *ai = NULL; verto_callback vcb; /* Check to make sure addresses were added to the server. */ if (bind_addresses.n == 0) { krb5_klog_syslog(LOG_ERR, _("No addresses added to the net server")); return EINVAL; } /* Ask for all address families, listener addresses, and no port name * resolution. */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; /* Add all the requested addresses. */ for (i = 0; i < bind_addresses.n; i++) { addr = bind_addresses.data[i]; hints.ai_socktype = bind_socktypes[addr.type]; /* Call getaddrinfo, using a dummy port value. */ err = getaddrinfo(addr.address, "0", &hints, &ai_list); if (err) { krb5_klog_syslog(LOG_ERR, _("Failed getting address info (for %s): %s"), (addr.address == NULL) ? "" : addr.address, gai_strerror(err)); ret = EIO; goto cleanup; } /* * Loop through all the sockets that getaddrinfo could find to match * the requested address. For wildcard listeners, this should usually * have two results, one for each of IPv4 and IPv6, or one or the * other, depending on the system. On IPv4-only systems, getaddrinfo() * may return both IPv4 and IPv6 addresses, but creating an IPv6 socket * may give an EAFNOSUPPORT error, so tolerate that error as long as we * can bind at least one socket. */ bound_any = 0; for (ai = ai_list; ai != NULL; ai = ai->ai_next) { /* Make sure getaddrinfo returned a socket with the same type that * was requested. */ assert(hints.ai_socktype == ai->ai_socktype); /* Set the real port number. */ sa_setport(ai->ai_addr, addr.port); ret = setup_socket(&addr, ai->ai_addr, handle, prog, ctx, tcp_listen_backlog, verto_callbacks[addr.type], bind_conn_types[addr.type]); if (ret) { krb5_klog_syslog(LOG_ERR, _("Failed setting up a %s socket (for %s)"), bind_type_names[addr.type], paddr(ai->ai_addr)); if (ret != EAFNOSUPPORT) goto cleanup; } else { bound_any = 1; } } if (!bound_any) goto cleanup; ret = 0; if (ai_list != NULL) freeaddrinfo(ai_list); ai_list = NULL; } cleanup: if (ai_list != NULL) freeaddrinfo(ai_list); return ret; } krb5_error_code loop_setup_network(verto_ctx *ctx, void *handle, const char *prog, int tcp_listen_backlog) { krb5_error_code ret; verto_ev *ev; int i; /* Check to make sure that at least one address was added to the loop. */ if (bind_addresses.n == 0) return EINVAL; /* Close any open connections. */ FOREACH_ELT(events, i, ev) verto_del(ev); events.n = 0; krb5_klog_syslog(LOG_INFO, _("setting up network...")); ret = setup_addresses(ctx, handle, prog, tcp_listen_backlog); if (ret) { com_err(prog, ret, _("Error setting up network")); exit(1); } krb5_klog_syslog (LOG_INFO, _("set up %d sockets"), (int) events.n); if (events.n == 0) { /* If no sockets were set up, we can't continue. */ com_err(prog, 0, _("no sockets set up?")); exit (1); } return 0; } void init_addr(krb5_fulladdr *faddr, struct sockaddr *sa) { switch (sa->sa_family) { case AF_INET: faddr->address->addrtype = ADDRTYPE_INET; faddr->address->length = 4; faddr->address->contents = (krb5_octet *) &sa2sin(sa)->sin_addr; faddr->port = ntohs(sa2sin(sa)->sin_port); break; case AF_INET6: if (IN6_IS_ADDR_V4MAPPED(&sa2sin6(sa)->sin6_addr)) { faddr->address->addrtype = ADDRTYPE_INET; faddr->address->length = 4; faddr->address->contents = 12 + (krb5_octet *) &sa2sin6(sa)->sin6_addr; } else { faddr->address->addrtype = ADDRTYPE_INET6; faddr->address->length = 16; faddr->address->contents = (krb5_octet *) &sa2sin6(sa)->sin6_addr; } faddr->port = ntohs(sa2sin6(sa)->sin6_port); break; default: faddr->address->addrtype = -1; faddr->address->length = 0; faddr->address->contents = 0; faddr->port = 0; break; } } struct udp_dispatch_state { void *handle; const char *prog; int port_fd; krb5_address remote_addr_buf; krb5_fulladdr remote_addr; krb5_address local_addr_buf; krb5_fulladdr local_addr; socklen_t saddr_len; socklen_t daddr_len; struct sockaddr_storage saddr; struct sockaddr_storage daddr; aux_addressing_info auxaddr; krb5_data request; char pktbuf[MAX_DGRAM_SIZE]; }; static void process_packet_response(void *arg, krb5_error_code code, krb5_data *response) { struct udp_dispatch_state *state = arg; int cc; if (code) com_err(state->prog ? state->prog : NULL, code, _("while dispatching (udp)")); if (code || response == NULL) goto out; cc = send_to_from(state->port_fd, response->data, (socklen_t) response->length, 0, (struct sockaddr *)&state->saddr, state->saddr_len, (struct sockaddr *)&state->daddr, state->daddr_len, &state->auxaddr); if (cc == -1) { /* Note that the local address (daddr*) has no port number * info associated with it. */ char saddrbuf[NI_MAXHOST], sportbuf[NI_MAXSERV]; char daddrbuf[NI_MAXHOST]; int e = errno; if (getnameinfo((struct sockaddr *)&state->daddr, state->daddr_len, daddrbuf, sizeof(daddrbuf), 0, 0, NI_NUMERICHOST) != 0) { strlcpy(daddrbuf, "?", sizeof(daddrbuf)); } if (getnameinfo((struct sockaddr *)&state->saddr, state->saddr_len, saddrbuf, sizeof(saddrbuf), sportbuf, sizeof(sportbuf), NI_NUMERICHOST|NI_NUMERICSERV) != 0) { strlcpy(saddrbuf, "?", sizeof(saddrbuf)); strlcpy(sportbuf, "?", sizeof(sportbuf)); } com_err(state->prog, e, _("while sending reply to %s/%s from %s"), saddrbuf, sportbuf, daddrbuf); goto out; } if ((size_t)cc != response->length) { com_err(state->prog, 0, _("short reply write %d vs %d\n"), response->length, cc); } out: krb5_free_data(get_context(state->handle), response); free(state); } static void process_packet(verto_ctx *ctx, verto_ev *ev) { int cc; struct connection *conn; struct udp_dispatch_state *state; conn = verto_get_private(ev); state = malloc(sizeof(*state)); if (!state) { com_err(conn->prog, ENOMEM, _("while dispatching (udp)")); return; } state->handle = conn->handle; state->prog = conn->prog; state->port_fd = verto_get_fd(ev); assert(state->port_fd >= 0); state->saddr_len = sizeof(state->saddr); state->daddr_len = sizeof(state->daddr); memset(&state->auxaddr, 0, sizeof(state->auxaddr)); cc = recv_from_to(state->port_fd, state->pktbuf, sizeof(state->pktbuf), 0, (struct sockaddr *)&state->saddr, &state->saddr_len, (struct sockaddr *)&state->daddr, &state->daddr_len, &state->auxaddr); if (cc == -1) { if (errno != EINTR && errno != EAGAIN /* * This is how Linux indicates that a previous transmission was * refused, e.g., if the client timed out before getting the * response packet. */ && errno != ECONNREFUSED ) com_err(conn->prog, errno, _("while receiving from network")); free(state); return; } if (!cc) { /* zero-length packet? */ free(state); return; } if (state->daddr_len == 0 && conn->type == CONN_UDP) { /* * An address couldn't be obtained, so the PKTINFO option probably * isn't available. If the socket is bound to a specific address, then * try to get the address here. */ state->daddr_len = sizeof(state->daddr); if (getsockname(state->port_fd, (struct sockaddr *)&state->daddr, &state->daddr_len) != 0) state->daddr_len = 0; /* On failure, keep going anyways. */ } state->request.length = cc; state->request.data = state->pktbuf; state->remote_addr.address = &state->remote_addr_buf; init_addr(&state->remote_addr, ss2sa(&state->saddr)); state->local_addr.address = &state->local_addr_buf; init_addr(&state->local_addr, ss2sa(&state->daddr)); /* This address is in net order. */ dispatch(state->handle, &state->local_addr, &state->remote_addr, &state->request, 0, ctx, process_packet_response, state); } static int kill_lru_tcp_or_rpc_connection(void *handle, verto_ev *newev) { struct connection *c = NULL, *oldest_c = NULL; verto_ev *ev, *oldest_ev = NULL; int i, fd = -1; krb5_klog_syslog(LOG_INFO, _("too many connections")); FOREACH_ELT (events, i, ev) { if (ev == newev) continue; c = verto_get_private(ev); if (!c) continue; if (c->type != CONN_TCP && c->type != CONN_RPC) continue; if (oldest_c == NULL || oldest_c->start_time > c->start_time) { oldest_ev = ev; oldest_c = c; } } if (oldest_c != NULL) { krb5_klog_syslog(LOG_INFO, _("dropping %s fd %d from %s"), c->type == CONN_RPC ? "rpc" : "tcp", verto_get_fd(oldest_ev), oldest_c->addrbuf); if (oldest_c->type == CONN_RPC) oldest_c->rpc_force_close = 1; verto_del(oldest_ev); } return fd; } static void accept_tcp_connection(verto_ctx *ctx, verto_ev *ev) { int s; struct sockaddr_storage addr_s; struct sockaddr *addr = (struct sockaddr *)&addr_s; socklen_t addrlen = sizeof(addr_s); struct connection *newconn, *conn; char tmpbuf[10]; verto_ev_flag flags; verto_ev *newev; conn = verto_get_private(ev); s = accept(verto_get_fd(ev), addr, &addrlen); if (s < 0) return; set_cloexec_fd(s); #ifndef _WIN32 if (s >= FD_SETSIZE) { close(s); return; } #endif setnbio(s), setnolinger(s), setkeepalive(s); flags = VERTO_EV_FLAG_IO_READ | VERTO_EV_FLAG_PERSIST; if (add_fd(s, CONN_TCP, flags, conn->handle, conn->prog, ctx, process_tcp_connection_read, &newev) != 0) { close(s); return; } newconn = verto_get_private(newev); if (getnameinfo((struct sockaddr *)&addr_s, addrlen, newconn->addrbuf, sizeof(newconn->addrbuf), tmpbuf, sizeof(tmpbuf), NI_NUMERICHOST | NI_NUMERICSERV)) strlcpy(newconn->addrbuf, "???", sizeof(newconn->addrbuf)); else { char *p, *end; p = newconn->addrbuf; end = p + sizeof(newconn->addrbuf); p += strlen(p); if ((size_t)(end - p) > 2 + strlen(tmpbuf)) { *p++ = '.'; strlcpy(p, tmpbuf, end - p); } } newconn->addr_s = addr_s; newconn->addrlen = addrlen; newconn->bufsiz = 1024 * 1024; newconn->buffer = malloc(newconn->bufsiz); newconn->start_time = time(0); if (++tcp_or_rpc_data_counter > max_tcp_or_rpc_data_connections) kill_lru_tcp_or_rpc_connection(conn->handle, newev); if (newconn->buffer == 0) { com_err(conn->prog, errno, _("allocating buffer for new TCP session from %s"), newconn->addrbuf); verto_del(newev); return; } newconn->offset = 0; newconn->remote_addr.address = &newconn->remote_addr_buf; init_addr(&newconn->remote_addr, ss2sa(&newconn->addr_s)); SG_SET(&newconn->sgbuf[0], newconn->lenbuf, 4); SG_SET(&newconn->sgbuf[1], 0, 0); } struct tcp_dispatch_state { struct sockaddr_storage local_saddr; krb5_address local_addr_buf; krb5_fulladdr local_addr; struct connection *conn; krb5_data request; verto_ctx *ctx; int sock; }; static void process_tcp_response(void *arg, krb5_error_code code, krb5_data *response) { struct tcp_dispatch_state *state = arg; verto_ev *ev; assert(state); state->conn->response = response; if (code) com_err(state->conn->prog, code, _("while dispatching (tcp)")); if (code || !response) goto kill_tcp_connection; /* Queue outgoing response. */ store_32_be(response->length, state->conn->lenbuf); SG_SET(&state->conn->sgbuf[1], response->data, response->length); state->conn->sgp = state->conn->sgbuf; state->conn->sgnum = 2; ev = make_event(state->ctx, VERTO_EV_FLAG_IO_WRITE | VERTO_EV_FLAG_PERSIST, process_tcp_connection_write, state->sock, state->conn); if (ev) { free(state); return; } kill_tcp_connection: tcp_or_rpc_data_counter--; free_connection(state->conn); close(state->sock); free(state); } /* Creates the tcp_dispatch_state and deletes the verto event. */ static struct tcp_dispatch_state * prepare_for_dispatch(verto_ctx *ctx, verto_ev *ev) { struct tcp_dispatch_state *state; state = malloc(sizeof(*state)); if (!state) { krb5_klog_syslog(LOG_ERR, _("error allocating tcp dispatch private!")); return NULL; } state->conn = verto_get_private(ev); state->sock = verto_get_fd(ev); state->ctx = ctx; verto_set_private(ev, NULL, NULL); /* Don't close the fd or free conn! */ remove_event_from_set(ev); /* Remove it from the set. */ verto_del(ev); return state; } static void process_tcp_connection_read(verto_ctx *ctx, verto_ev *ev) { struct tcp_dispatch_state *state = NULL; struct connection *conn = NULL; ssize_t nread; size_t len; conn = verto_get_private(ev); /* * Read message length and data into one big buffer, already allocated * at connect time. If we have a complete message, we stop reading, so * we should only be here if there is no data in the buffer, or only an * incomplete message. */ if (conn->offset < 4) { krb5_data *response = NULL; /* msglen has not been computed. XXX Doing at least two reads * here, letting the kernel worry about buffering. */ len = 4 - conn->offset; nread = SOCKET_READ(verto_get_fd(ev), conn->buffer + conn->offset, len); if (nread < 0) /* error */ goto kill_tcp_connection; if (nread == 0) /* eof */ goto kill_tcp_connection; conn->offset += nread; if (conn->offset == 4) { unsigned char *p = (unsigned char *)conn->buffer; conn->msglen = load_32_be(p); if (conn->msglen > conn->bufsiz - 4) { krb5_error_code err; /* Message too big. */ krb5_klog_syslog(LOG_ERR, _("TCP client %s wants %lu bytes, " "cap is %lu"), conn->addrbuf, (unsigned long) conn->msglen, (unsigned long) conn->bufsiz - 4); /* XXX Should return an error. */ err = make_toolong_error (conn->handle, &response); if (err) { krb5_klog_syslog(LOG_ERR, _("error constructing " "KRB_ERR_FIELD_TOOLONG error! %s"), error_message(err)); goto kill_tcp_connection; } state = prepare_for_dispatch(ctx, ev); if (!state) { krb5_free_data(get_context(conn->handle), response); goto kill_tcp_connection; } process_tcp_response(state, 0, response); } } } else { /* msglen known. */ socklen_t local_saddrlen = sizeof(struct sockaddr_storage); len = conn->msglen - (conn->offset - 4); nread = SOCKET_READ(verto_get_fd(ev), conn->buffer + conn->offset, len); if (nread < 0) /* error */ goto kill_tcp_connection; if (nread == 0) /* eof */ goto kill_tcp_connection; conn->offset += nread; if (conn->offset < conn->msglen + 4) return; /* Have a complete message, and exactly one message. */ state = prepare_for_dispatch(ctx, ev); if (!state) goto kill_tcp_connection; state->request.length = conn->msglen; state->request.data = conn->buffer + 4; if (getsockname(verto_get_fd(ev), ss2sa(&state->local_saddr), &local_saddrlen) < 0) { krb5_klog_syslog(LOG_ERR, _("getsockname failed: %s"), error_message(errno)); goto kill_tcp_connection; } state->local_addr.address = &state->local_addr_buf; init_addr(&state->local_addr, ss2sa(&state->local_saddr)); dispatch(state->conn->handle, &state->local_addr, &conn->remote_addr, &state->request, 1, ctx, process_tcp_response, state); } return; kill_tcp_connection: verto_del(ev); } static void process_tcp_connection_write(verto_ctx *ctx, verto_ev *ev) { struct connection *conn; SOCKET_WRITEV_TEMP tmp; ssize_t nwrote; int sock; conn = verto_get_private(ev); sock = verto_get_fd(ev); nwrote = SOCKET_WRITEV(sock, conn->sgp, conn->sgnum, tmp); if (nwrote > 0) { /* non-error and non-eof */ while (nwrote) { sg_buf *sgp = conn->sgp; if ((size_t)nwrote < SG_LEN(sgp)) { SG_ADVANCE(sgp, (size_t)nwrote); nwrote = 0; } else { nwrote -= SG_LEN(sgp); conn->sgp++; conn->sgnum--; if (conn->sgnum == 0 && nwrote != 0) abort(); } } /* If we still have more data to send, just return so that * the main loop can call this function again when the socket * is ready for more writing. */ if (conn->sgnum > 0) return; } /* Finished sending. We should go back to reading, though if we * sent a FIELD_TOOLONG error in reply to a length with the high * bit set, RFC 4120 says we have to close the TCP stream. */ verto_del(ev); } void loop_free(verto_ctx *ctx) { int i; struct bind_address val; verto_free(ctx); /* Free each addresses added to the loop. */ FOREACH_ELT(bind_addresses, i, val) free(val.address); FREE_SET_DATA(bind_addresses); FREE_SET_DATA(events); } static int have_event_for_fd(int fd) { verto_ev *ev; int i; FOREACH_ELT(events, i, ev) { if (verto_get_fd(ev) == fd) return 1; } return 0; } static void accept_rpc_connection(verto_ctx *ctx, verto_ev *ev) { verto_ev_flag flags; struct connection *conn; fd_set fds; int s; conn = verto_get_private(ev); /* Service the woken RPC listener descriptor. */ FD_ZERO(&fds); FD_SET(verto_get_fd(ev), &fds); svc_getreqset(&fds); /* Scan svc_fdset for any new connections. */ for (s = 0; s < FD_SETSIZE; s++) { struct sockaddr_storage addr_s; struct sockaddr *addr = (struct sockaddr *) &addr_s; socklen_t addrlen = sizeof(addr_s); struct connection *newconn; char tmpbuf[10]; verto_ev *newev; /* If we already have this fd, continue. */ if (!FD_ISSET(s, &svc_fdset) || have_event_for_fd(s)) continue; flags = VERTO_EV_FLAG_IO_READ | VERTO_EV_FLAG_PERSIST; if (add_fd(s, CONN_RPC, flags, conn->handle, conn->prog, ctx, process_rpc_connection, &newev) != 0) continue; newconn = verto_get_private(newev); set_cloexec_fd(s); if (getpeername(s, addr, &addrlen) || getnameinfo(addr, addrlen, newconn->addrbuf, sizeof(newconn->addrbuf), tmpbuf, sizeof(tmpbuf), NI_NUMERICHOST | NI_NUMERICSERV)) { strlcpy(newconn->addrbuf, "???", sizeof(newconn->addrbuf)); } else { char *p, *end; p = newconn->addrbuf; end = p + sizeof(newconn->addrbuf); p += strlen(p); if ((size_t)(end - p) > 2 + strlen(tmpbuf)) { *p++ = '.'; strlcpy(p, tmpbuf, end - p); } } newconn->addr_s = addr_s; newconn->addrlen = addrlen; newconn->start_time = time(0); if (++tcp_or_rpc_data_counter > max_tcp_or_rpc_data_connections) kill_lru_tcp_or_rpc_connection(newconn->handle, newev); newconn->remote_addr.address = &newconn->remote_addr_buf; init_addr(&newconn->remote_addr, ss2sa(&newconn->addr_s)); } } static void process_rpc_connection(verto_ctx *ctx, verto_ev *ev) { fd_set fds; FD_ZERO(&fds); FD_SET(verto_get_fd(ev), &fds); svc_getreqset(&fds); if (!FD_ISSET(verto_get_fd(ev), &svc_fdset)) verto_del(ev); } #endif /* INET */