diff options
Diffstat (limited to 'src/lib/krb5/os/sendto_kdc.c')
| -rw-r--r-- | src/lib/krb5/os/sendto_kdc.c | 1600 |
1 files changed, 1600 insertions, 0 deletions
diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c new file mode 100644 index 000000000000..fffe0262f6bb --- /dev/null +++ b/src/lib/krb5/os/sendto_kdc.c @@ -0,0 +1,1600 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/os/sendto_kdc.c */ +/* + * Copyright 1990,1991,2001,2002,2004,2005,2007,2008 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ +/* + * MS-KKDCP implementation Copyright 2013,2014 Red Hat, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* Send packet to KDC for realm; wait for response, retransmitting + * as necessary. */ + +#include "k5-int.h" +#include "k5-tls.h" +#include "fake-addrinfo.h" + +#include "os-proto.h" + +#if defined(HAVE_POLL_H) +#include <poll.h> +#define USE_POLL +#define MAX_POLLFDS 1024 +#elif defined(HAVE_SYS_SELECT_H) +#include <sys/select.h> +#endif + +#ifndef _WIN32 +/* For FIONBIO. */ +#include <sys/ioctl.h> +#ifdef HAVE_SYS_FILIO_H +#include <sys/filio.h> +#endif +#endif + +#define MAX_PASS 3 +#define DEFAULT_UDP_PREF_LIMIT 1465 +#define HARD_UDP_LIMIT 32700 /* could probably do 64K-epsilon ? */ +#define PORT_LENGTH 6 /* decimal repr of UINT16_MAX */ + +/* Select state flags. */ +#define SSF_READ 0x01 +#define SSF_WRITE 0x02 +#define SSF_EXCEPTION 0x04 + +typedef int64_t time_ms; + +/* This can be pretty large, so should not be stack-allocated. */ +struct select_state { +#ifdef USE_POLL + struct pollfd fds[MAX_POLLFDS]; +#else + int max; + fd_set rfds, wfds, xfds; +#endif + int nfds; +}; + +/* connection states */ +enum conn_states { INITIALIZING, CONNECTING, WRITING, READING, FAILED }; +struct incoming_message { + size_t bufsizebytes_read; + size_t bufsize; + size_t pos; + char *buf; + unsigned char bufsizebytes[4]; + size_t n_left; +}; + +struct outgoing_message { + sg_buf sgbuf[2]; + sg_buf *sgp; + int sg_count; + unsigned char msg_len_buf[4]; +}; + +struct conn_state; +typedef krb5_boolean fd_handler_fn(krb5_context context, + const krb5_data *realm, + struct conn_state *conn, + struct select_state *selstate); + +struct conn_state { + SOCKET fd; + enum conn_states state; + fd_handler_fn *service_connect; + fd_handler_fn *service_write; + fd_handler_fn *service_read; + struct remote_address addr; + struct incoming_message in; + struct outgoing_message out; + krb5_data callback_buffer; + size_t server_index; + struct conn_state *next; + time_ms endtime; + krb5_boolean defer; + struct { + const char *uri_path; + const char *servername; + char port[PORT_LENGTH]; + char *https_request; + k5_tls_handle tls; + } http; +}; + +/* Set up context->tls. On allocation failure, return ENOMEM. On plugin load + * failure, set context->tls to point to a nulled vtable and return 0. */ +static krb5_error_code +init_tls_vtable(krb5_context context) +{ + krb5_plugin_initvt_fn initfn; + + if (context->tls != NULL) + return 0; + + context->tls = calloc(1, sizeof(*context->tls)); + if (context->tls == NULL) + return ENOMEM; + + /* Attempt to load the module; just let it stay nulled out on failure. */ + k5_plugin_register_dyn(context, PLUGIN_INTERFACE_TLS, "k5tls", "tls"); + if (k5_plugin_load(context, PLUGIN_INTERFACE_TLS, "k5tls", &initfn) == 0) + (*initfn)(context, 0, 0, (krb5_plugin_vtable)context->tls); + + return 0; +} + +/* Get current time in milliseconds. */ +static krb5_error_code +get_curtime_ms(time_ms *time_out) +{ + struct timeval tv; + + *time_out = 0; + + if (gettimeofday(&tv, 0)) + return errno; + *time_out = (time_ms)tv.tv_sec * 1000 + tv.tv_usec / 1000; + return 0; +} + +static void +free_http_tls_data(krb5_context context, struct conn_state *state) +{ + if (state->http.tls != NULL) + context->tls->free_handle(context, state->http.tls); + state->http.tls = NULL; + free(state->http.https_request); + state->http.https_request = NULL; +} + +#ifdef USE_POLL + +/* Find a pollfd in selstate by fd, or abort if we can't find it. */ +static inline struct pollfd * +find_pollfd(struct select_state *selstate, int fd) +{ + int i; + + for (i = 0; i < selstate->nfds; i++) { + if (selstate->fds[i].fd == fd) + return &selstate->fds[i]; + } + abort(); +} + +static void +cm_init_selstate(struct select_state *selstate) +{ + selstate->nfds = 0; +} + +static krb5_boolean +cm_add_fd(struct select_state *selstate, int fd) +{ + if (selstate->nfds >= MAX_POLLFDS) + return FALSE; + selstate->fds[selstate->nfds].fd = fd; + selstate->fds[selstate->nfds].events = 0; + selstate->nfds++; + return TRUE; +} + +static void +cm_remove_fd(struct select_state *selstate, int fd) +{ + struct pollfd *pfd = find_pollfd(selstate, fd); + + *pfd = selstate->fds[selstate->nfds - 1]; + selstate->nfds--; +} + +/* Poll for reading (and not writing) on fd the next time we poll. */ +static void +cm_read(struct select_state *selstate, int fd) +{ + find_pollfd(selstate, fd)->events = POLLIN; +} + +/* Poll for writing (and not reading) on fd the next time we poll. */ +static void +cm_write(struct select_state *selstate, int fd) +{ + find_pollfd(selstate, fd)->events = POLLOUT; +} + +/* Get the output events for fd in the form of ssflags. */ +static unsigned int +cm_get_ssflags(struct select_state *selstate, int fd) +{ + struct pollfd *pfd = find_pollfd(selstate, fd); + + /* + * OS X sets POLLHUP without POLLOUT on connection error. Catch this as + * well as other error events such as POLLNVAL, but only if POLLIN and + * POLLOUT aren't set, as we can get POLLHUP along with POLLIN with TCP + * data still to be read. + */ + if (pfd->revents != 0 && !(pfd->revents & (POLLIN | POLLOUT))) + return SSF_EXCEPTION; + + return ((pfd->revents & POLLIN) ? SSF_READ : 0) | + ((pfd->revents & POLLOUT) ? SSF_WRITE : 0) | + ((pfd->revents & POLLERR) ? SSF_EXCEPTION : 0); +} + +#else /* not USE_POLL */ + +static void +cm_init_selstate(struct select_state *selstate) +{ + selstate->nfds = 0; + selstate->max = 0; + FD_ZERO(&selstate->rfds); + FD_ZERO(&selstate->wfds); + FD_ZERO(&selstate->xfds); +} + +static krb5_boolean +cm_add_fd(struct select_state *selstate, int fd) +{ +#ifndef _WIN32 /* On Windows FD_SETSIZE is a count, not a max value. */ + if (fd >= FD_SETSIZE) + return FALSE; +#endif + FD_SET(fd, &selstate->xfds); + if (selstate->max <= fd) + selstate->max = fd + 1; + selstate->nfds++; + return TRUE; +} + +static void +cm_remove_fd(struct select_state *selstate, int fd) +{ + FD_CLR(fd, &selstate->rfds); + FD_CLR(fd, &selstate->wfds); + FD_CLR(fd, &selstate->xfds); + if (selstate->max == fd + 1) { + while (selstate->max > 0 && + !FD_ISSET(selstate->max - 1, &selstate->rfds) && + !FD_ISSET(selstate->max - 1, &selstate->wfds) && + !FD_ISSET(selstate->max - 1, &selstate->xfds)) + selstate->max--; + } + selstate->nfds--; +} + +/* Select for reading (and not writing) on fd the next time we select. */ +static void +cm_read(struct select_state *selstate, int fd) +{ + FD_SET(fd, &selstate->rfds); + FD_CLR(fd, &selstate->wfds); +} + +/* Select for writing (and not reading) on fd the next time we select. */ +static void +cm_write(struct select_state *selstate, int fd) +{ + FD_CLR(fd, &selstate->rfds); + FD_SET(fd, &selstate->wfds); +} + +/* Get the events for fd from selstate after a select. */ +static unsigned int +cm_get_ssflags(struct select_state *selstate, int fd) +{ + return (FD_ISSET(fd, &selstate->rfds) ? SSF_READ : 0) | + (FD_ISSET(fd, &selstate->wfds) ? SSF_WRITE : 0) | + (FD_ISSET(fd, &selstate->xfds) ? SSF_EXCEPTION : 0); +} + +#endif /* not USE_POLL */ + +static krb5_error_code +cm_select_or_poll(const struct select_state *in, time_ms endtime, + struct select_state *out, int *sret) +{ +#ifndef USE_POLL + struct timeval tv; +#endif + krb5_error_code retval; + time_ms curtime, interval; + + retval = get_curtime_ms(&curtime); + if (retval != 0) + return retval; + interval = (curtime < endtime) ? endtime - curtime : 0; + + /* We don't need a separate copy of the selstate for poll, but use one for + * consistency with how we use select. */ + *out = *in; + +#ifdef USE_POLL + *sret = poll(out->fds, out->nfds, interval); +#else + tv.tv_sec = interval / 1000; + tv.tv_usec = interval % 1000 * 1000; + *sret = select(out->max, &out->rfds, &out->wfds, &out->xfds, &tv); +#endif + + return (*sret < 0) ? SOCKET_ERRNO : 0; +} + +static int +socktype_for_transport(k5_transport transport) +{ + switch (transport) { + case UDP: + return SOCK_DGRAM; + case TCP: + case HTTPS: + return SOCK_STREAM; + default: + return 0; + } +} + +static int +check_for_svc_unavailable (krb5_context context, + const krb5_data *reply, + void *msg_handler_data) +{ + krb5_error_code *retval = (krb5_error_code *)msg_handler_data; + + *retval = 0; + + if (krb5_is_krb_error(reply)) { + krb5_error *err_reply; + + if (decode_krb5_error(reply, &err_reply) == 0) { + *retval = err_reply->error; + krb5_free_error(context, err_reply); + + /* Returning 0 means continue to next KDC */ + return (*retval != KDC_ERR_SVC_UNAVAILABLE); + } + } + + return 1; +} + +void KRB5_CALLCONV +krb5_set_kdc_send_hook(krb5_context context, krb5_pre_send_fn send_hook, + void *data) +{ + context->kdc_send_hook = send_hook; + context->kdc_send_hook_data = data; +} + +void KRB5_CALLCONV +krb5_set_kdc_recv_hook(krb5_context context, krb5_post_recv_fn recv_hook, + void *data) +{ + context->kdc_recv_hook = recv_hook; + context->kdc_recv_hook_data = data; +} + +/* + * send the formatted request 'message' to a KDC for realm 'realm' and + * return the response (if any) in 'reply'. + * + * If the message is sent and a response is received, 0 is returned, + * otherwise an error code is returned. + * + * The storage for 'reply' is allocated and should be freed by the caller + * when finished. + */ + +krb5_error_code +krb5_sendto_kdc(krb5_context context, const krb5_data *message, + const krb5_data *realm, krb5_data *reply_out, int *use_master, + int no_udp) +{ + krb5_error_code retval, oldret, err; + struct serverlist servers; + int server_used; + k5_transport_strategy strategy; + krb5_data reply = empty_data(), *hook_message = NULL, *hook_reply = NULL; + + *reply_out = empty_data(); + + /* + * find KDC location(s) for realm + */ + + /* + * BUG: This code won't return "interesting" errors (e.g., out of mem, + * bad config file) from locate_kdc. KRB5_REALM_CANT_RESOLVE can be + * ignored from one query of two, but if only one query is done, or + * both return that error, it should be returned to the caller. Also, + * "interesting" errors (not KRB5_KDC_UNREACH) from sendto_{udp,tcp} + * should probably be returned as well. + */ + + TRACE_SENDTO_KDC(context, message->length, realm, *use_master, no_udp); + + if (!no_udp && context->udp_pref_limit < 0) { + int tmp; + retval = profile_get_integer(context->profile, + KRB5_CONF_LIBDEFAULTS, KRB5_CONF_UDP_PREFERENCE_LIMIT, 0, + DEFAULT_UDP_PREF_LIMIT, &tmp); + if (retval) + return retval; + if (tmp < 0) + tmp = DEFAULT_UDP_PREF_LIMIT; + else if (tmp > HARD_UDP_LIMIT) + /* In the unlikely case that a *really* big value is + given, let 'em use as big as we think we can + support. */ + tmp = HARD_UDP_LIMIT; + context->udp_pref_limit = tmp; + } + + if (no_udp) + strategy = NO_UDP; + else if (message->length <= (unsigned int) context->udp_pref_limit) + strategy = UDP_FIRST; + else + strategy = UDP_LAST; + + retval = k5_locate_kdc(context, realm, &servers, *use_master, no_udp); + if (retval) + return retval; + + if (context->kdc_send_hook != NULL) { + retval = context->kdc_send_hook(context, context->kdc_send_hook_data, + realm, message, &hook_message, + &hook_reply); + if (retval) + goto cleanup; + + if (hook_reply != NULL) { + *reply_out = *hook_reply; + free(hook_reply); + goto cleanup; + } + + if (hook_message != NULL) + message = hook_message; + } + + err = 0; + retval = k5_sendto(context, message, realm, &servers, strategy, NULL, + &reply, NULL, NULL, &server_used, + check_for_svc_unavailable, &err); + if (retval == KRB5_KDC_UNREACH) { + if (err == KDC_ERR_SVC_UNAVAILABLE) { + retval = KRB5KDC_ERR_SVC_UNAVAILABLE; + } else { + k5_setmsg(context, retval, + _("Cannot contact any KDC for realm '%.*s'"), + realm->length, realm->data); + } + } + + if (context->kdc_recv_hook != NULL) { + oldret = retval; + retval = context->kdc_recv_hook(context, context->kdc_recv_hook_data, + retval, realm, message, &reply, + &hook_reply); + if (oldret && !retval) { + /* The hook must set a reply if it overrides an error from + * k5_sendto(). Treat this reply as coming from the master KDC. */ + assert(hook_reply != NULL); + *use_master = 1; + } + } + if (retval) + goto cleanup; + + if (hook_reply != NULL) { + *reply_out = *hook_reply; + free(hook_reply); + } else { + *reply_out = reply; + reply = empty_data(); + } + + /* Set use_master to 1 if we ended up talking to a master when we didn't + * explicitly request to. */ + if (*use_master == 0) { + *use_master = k5_kdc_is_master(context, realm, + &servers.servers[server_used]); + TRACE_SENDTO_KDC_MASTER(context, *use_master); + } + +cleanup: + krb5_free_data(context, hook_message); + krb5_free_data_contents(context, &reply); + k5_free_serverlist(&servers); + return retval; +} + +/* + * Notes: + * + * Getting "connection refused" on a connected UDP socket causes + * select to indicate write capability on UNIX, but only shows up + * as an exception on Windows. (I don't think any UNIX system flags + * the error as an exception.) So we check for both, or make it + * system-specific. + * + * Always watch for responses from *any* of the servers. Eventually + * fix the UDP code to do the same. + * + * To do: + * - TCP NOPUSH/CORK socket options? + * - error codes that don't suck + * - getsockopt(SO_ERROR) to check connect status + * - handle error RESPONSE_TOO_BIG from UDP server and use TCP + * connections already in progress + */ + +static fd_handler_fn service_tcp_connect; +static fd_handler_fn service_tcp_write; +static fd_handler_fn service_tcp_read; +static fd_handler_fn service_udp_read; +static fd_handler_fn service_https_write; +static fd_handler_fn service_https_read; + +static krb5_error_code +make_proxy_request(struct conn_state *state, const krb5_data *realm, + const krb5_data *message, char **req_out, size_t *len_out) +{ + krb5_kkdcp_message pm; + krb5_data *encoded_pm = NULL; + struct k5buf buf; + const char *uri_path; + krb5_error_code ret; + + *req_out = NULL; + *len_out = 0; + + /* + * Stuff the message length in at the front of the kerb_message field + * before encoding. The proxied messages are actually the payload we'd + * be sending and receiving if we were using plain TCP. + */ + memset(&pm, 0, sizeof(pm)); + ret = alloc_data(&pm.kerb_message, message->length + 4); + if (ret != 0) + goto cleanup; + store_32_be(message->length, pm.kerb_message.data); + memcpy(pm.kerb_message.data + 4, message->data, message->length); + pm.target_domain = *realm; + ret = encode_krb5_kkdcp_message(&pm, &encoded_pm); + if (ret != 0) + goto cleanup; + + /* Build the request to transmit: the headers + the proxy message. */ + k5_buf_init_dynamic(&buf); + uri_path = (state->http.uri_path != NULL) ? state->http.uri_path : ""; + k5_buf_add_fmt(&buf, "POST /%s HTTP/1.0\r\n", uri_path); + k5_buf_add_fmt(&buf, "Host: %s:%s\r\n", state->http.servername, + state->http.port); + k5_buf_add(&buf, "Cache-Control: no-cache\r\n"); + k5_buf_add(&buf, "Pragma: no-cache\r\n"); + k5_buf_add(&buf, "User-Agent: kerberos/1.0\r\n"); + k5_buf_add(&buf, "Content-type: application/kerberos\r\n"); + k5_buf_add_fmt(&buf, "Content-Length: %d\r\n\r\n", encoded_pm->length); + k5_buf_add_len(&buf, encoded_pm->data, encoded_pm->length); + if (k5_buf_status(&buf) != 0) { + ret = ENOMEM; + goto cleanup; + } + + *req_out = buf.data; + *len_out = buf.len; + +cleanup: + krb5_free_data_contents(NULL, &pm.kerb_message); + krb5_free_data(NULL, encoded_pm); + return ret; +} + +/* Set up the actual message we will send across the underlying transport to + * communicate the payload message, using one or both of state->out.sgbuf. */ +static krb5_error_code +set_transport_message(struct conn_state *state, const krb5_data *realm, + const krb5_data *message) +{ + struct outgoing_message *out = &state->out; + char *req = NULL; + size_t reqlen; + krb5_error_code ret; + + if (message == NULL || message->length == 0) + return 0; + + if (state->addr.transport == TCP) { + store_32_be(message->length, out->msg_len_buf); + SG_SET(&out->sgbuf[0], out->msg_len_buf, 4); + SG_SET(&out->sgbuf[1], message->data, message->length); + out->sg_count = 2; + return 0; + } else if (state->addr.transport == HTTPS) { + ret = make_proxy_request(state, realm, message, &req, &reqlen); + if (ret != 0) + return ret; + SG_SET(&state->out.sgbuf[0], req, reqlen); + SG_SET(&state->out.sgbuf[1], 0, 0); + state->out.sg_count = 1; + free(state->http.https_request); + state->http.https_request = req; + return 0; + } else { + SG_SET(&out->sgbuf[0], message->data, message->length); + SG_SET(&out->sgbuf[1], NULL, 0); + out->sg_count = 1; + return 0; + } +} + +static krb5_error_code +add_connection(struct conn_state **conns, k5_transport transport, + krb5_boolean defer, struct addrinfo *ai, size_t server_index, + const krb5_data *realm, const char *hostname, + const char *port, const char *uri_path, char **udpbufp) +{ + struct conn_state *state, **tailptr; + + state = calloc(1, sizeof(*state)); + if (state == NULL) + return ENOMEM; + state->state = INITIALIZING; + state->out.sgp = state->out.sgbuf; + state->addr.transport = transport; + state->addr.family = ai->ai_family; + state->addr.len = ai->ai_addrlen; + memcpy(&state->addr.saddr, ai->ai_addr, ai->ai_addrlen); + state->defer = defer; + state->fd = INVALID_SOCKET; + state->server_index = server_index; + SG_SET(&state->out.sgbuf[1], NULL, 0); + if (transport == TCP) { + state->service_connect = service_tcp_connect; + state->service_write = service_tcp_write; + state->service_read = service_tcp_read; + } else if (transport == HTTPS) { + assert(hostname != NULL && port != NULL); + state->service_connect = service_tcp_connect; + state->service_write = service_https_write; + state->service_read = service_https_read; + state->http.uri_path = uri_path; + state->http.servername = hostname; + strlcpy(state->http.port, port, PORT_LENGTH); + } else { + state->service_connect = NULL; + state->service_write = NULL; + state->service_read = service_udp_read; + + if (*udpbufp == NULL) { + *udpbufp = malloc(MAX_DGRAM_SIZE); + if (*udpbufp == 0) + return ENOMEM; + } + state->in.buf = *udpbufp; + state->in.bufsize = MAX_DGRAM_SIZE; + } + + /* Chain the new state onto the tail of the list. */ + for (tailptr = conns; *tailptr != NULL; tailptr = &(*tailptr)->next); + *tailptr = state; + + return 0; +} + +static int +translate_ai_error (int err) +{ + switch (err) { + case 0: + return 0; + case EAI_BADFLAGS: + case EAI_FAMILY: + case EAI_SOCKTYPE: + case EAI_SERVICE: + /* All of these indicate bad inputs to getaddrinfo. */ + return EINVAL; + case EAI_AGAIN: + /* Translate to standard errno code. */ + return EAGAIN; + case EAI_MEMORY: + /* Translate to standard errno code. */ + return ENOMEM; +#ifdef EAI_ADDRFAMILY + case EAI_ADDRFAMILY: +#endif +#if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME + case EAI_NODATA: +#endif + case EAI_NONAME: + /* Name not known or no address data, but no error. Do + nothing more. */ + return 0; +#ifdef EAI_OVERFLOW + case EAI_OVERFLOW: + /* An argument buffer overflowed. */ + return EINVAL; /* XXX */ +#endif +#ifdef EAI_SYSTEM + case EAI_SYSTEM: + /* System error, obviously. */ + return errno; +#endif + default: + /* An error code we haven't handled? */ + return EINVAL; + } +} + +/* + * Resolve the entry in servers with index ind, adding connections to the list + * *conns. Connections are added for each of socktype1 and (if not zero) + * socktype2. message and udpbufp are used to initialize the connections; see + * add_connection above. If no addresses are available for an entry but no + * internal name resolution failure occurs, return 0 without adding any new + * connections. + */ +static krb5_error_code +resolve_server(krb5_context context, const krb5_data *realm, + const struct serverlist *servers, size_t ind, + k5_transport_strategy strategy, const krb5_data *message, + char **udpbufp, struct conn_state **conns) +{ + krb5_error_code retval; + struct server_entry *entry = &servers->servers[ind]; + k5_transport transport; + struct addrinfo *addrs, *a, hint, ai; + krb5_boolean defer = FALSE; + int err, result; + char portbuf[PORT_LENGTH]; + + /* Skip UDP entries if we don't want UDP. */ + if (strategy == NO_UDP && entry->transport == UDP) + return 0; + + transport = (strategy == UDP_FIRST) ? UDP : TCP; + if (entry->hostname == NULL) { + /* Added by a module, so transport is either TCP or UDP. */ + ai.ai_socktype = socktype_for_transport(entry->transport); + ai.ai_family = entry->family; + ai.ai_addrlen = entry->addrlen; + ai.ai_addr = (struct sockaddr *)&entry->addr; + defer = (entry->transport != transport); + return add_connection(conns, entry->transport, defer, &ai, ind, realm, + NULL, NULL, entry->uri_path, udpbufp); + } + + /* If the entry has a specified transport, use it, but possibly defer the + * addresses we add based on the strategy. */ + if (entry->transport != TCP_OR_UDP) { + transport = entry->transport; + defer = (entry->transport == TCP && strategy == UDP_FIRST) || + (entry->transport == UDP && strategy == UDP_LAST); + } + + memset(&hint, 0, sizeof(hint)); + hint.ai_family = entry->family; + hint.ai_socktype = socktype_for_transport(transport); + hint.ai_flags = AI_ADDRCONFIG; +#ifdef AI_NUMERICSERV + hint.ai_flags |= AI_NUMERICSERV; +#endif + result = snprintf(portbuf, sizeof(portbuf), "%d", entry->port); + if (SNPRINTF_OVERFLOW(result, sizeof(portbuf))) + return EINVAL; + TRACE_SENDTO_KDC_RESOLVING(context, entry->hostname); + err = getaddrinfo(entry->hostname, portbuf, &hint, &addrs); + if (err) + return translate_ai_error(err); + + /* Add each address with the specified or preferred transport. */ + retval = 0; + for (a = addrs; a != 0 && retval == 0; a = a->ai_next) { + retval = add_connection(conns, transport, defer, a, ind, realm, + entry->hostname, portbuf, entry->uri_path, + udpbufp); + } + + /* For TCP_OR_UDP entries, add each address again with the non-preferred + * transport, unless we are avoiding UDP. Flag these as deferred. */ + if (retval == 0 && entry->transport == TCP_OR_UDP && strategy != NO_UDP) { + transport = (strategy == UDP_FIRST) ? TCP : UDP; + for (a = addrs; a != 0 && retval == 0; a = a->ai_next) { + a->ai_socktype = socktype_for_transport(transport); + retval = add_connection(conns, transport, TRUE, a, ind, realm, + entry->hostname, portbuf, + entry->uri_path, udpbufp); + } + } + freeaddrinfo(addrs); + return retval; +} + +static int +start_connection(krb5_context context, struct conn_state *state, + const krb5_data *message, struct select_state *selstate, + const krb5_data *realm, + struct sendto_callback_info *callback_info) +{ + int fd, e, type; + static const int one = 1; + static const struct linger lopt = { 0, 0 }; + + type = socktype_for_transport(state->addr.transport); + fd = socket(state->addr.family, type, 0); + if (fd == INVALID_SOCKET) + return -1; /* try other hosts */ + set_cloexec_fd(fd); + /* Make it non-blocking. */ + ioctlsocket(fd, FIONBIO, (const void *) &one); + if (state->addr.transport == TCP) { + setsockopt(fd, SOL_SOCKET, SO_LINGER, &lopt, sizeof(lopt)); + TRACE_SENDTO_KDC_TCP_CONNECT(context, &state->addr); + } + + /* Start connecting to KDC. */ + e = connect(fd, (struct sockaddr *)&state->addr.saddr, state->addr.len); + if (e != 0) { + /* + * This is the path that should be followed for non-blocking + * connections. + */ + if (SOCKET_ERRNO == EINPROGRESS || SOCKET_ERRNO == EWOULDBLOCK) { + state->state = CONNECTING; + state->fd = fd; + } else { + (void) closesocket(fd); + state->state = FAILED; + return -2; + } + } else { + /* + * Connect returned zero even though we made it non-blocking. This + * happens normally for UDP sockets, and can perhaps also happen for + * TCP sockets connecting to localhost. + */ + state->state = WRITING; + state->fd = fd; + } + + /* + * Here's where KPASSWD callback gets the socket information it needs for + * a kpasswd request + */ + if (callback_info) { + + e = callback_info->pfn_callback(state->fd, callback_info->data, + &state->callback_buffer); + if (e != 0) { + (void) closesocket(fd); + state->fd = INVALID_SOCKET; + state->state = FAILED; + return -3; + } + + message = &state->callback_buffer; + } + + e = set_transport_message(state, realm, message); + if (e != 0) { + TRACE_SENDTO_KDC_ERROR_SET_MESSAGE(context, &state->addr, e); + (void) closesocket(state->fd); + state->fd = INVALID_SOCKET; + state->state = FAILED; + return -4; + } + + if (state->addr.transport == UDP) { + /* Send it now. */ + ssize_t ret; + sg_buf *sg = &state->out.sgbuf[0]; + + TRACE_SENDTO_KDC_UDP_SEND_INITIAL(context, &state->addr); + ret = send(state->fd, SG_BUF(sg), SG_LEN(sg), 0); + if (ret < 0 || (size_t) ret != SG_LEN(sg)) { + TRACE_SENDTO_KDC_UDP_ERROR_SEND_INITIAL(context, &state->addr, + SOCKET_ERRNO); + (void) closesocket(state->fd); + state->fd = INVALID_SOCKET; + state->state = FAILED; + return -5; + } else { + state->state = READING; + } + } + + if (!cm_add_fd(selstate, state->fd)) { + (void) closesocket(state->fd); + state->fd = INVALID_SOCKET; + state->state = FAILED; + return -1; + } + if (state->state == CONNECTING || state->state == WRITING) + cm_write(selstate, state->fd); + else + cm_read(selstate, state->fd); + + return 0; +} + +/* Return 0 if we sent something, non-0 otherwise. + If 0 is returned, the caller should delay waiting for a response. + Otherwise, the caller should immediately move on to process the + next connection. */ +static int +maybe_send(krb5_context context, struct conn_state *conn, + const krb5_data *message, struct select_state *selstate, + const krb5_data *realm, + struct sendto_callback_info *callback_info) +{ + sg_buf *sg; + ssize_t ret; + + if (conn->state == INITIALIZING) { + return start_connection(context, conn, message, selstate, + realm, callback_info); + } + + /* Did we already shut down this channel? */ + if (conn->state == FAILED) { + return -1; + } + + if (conn->addr.transport != UDP) { + /* The select callback will handle flushing any data we + haven't written yet, and we only write it once. */ + return -1; + } + + /* UDP - retransmit after a previous attempt timed out. */ + sg = &conn->out.sgbuf[0]; + TRACE_SENDTO_KDC_UDP_SEND_RETRY(context, &conn->addr); + ret = send(conn->fd, SG_BUF(sg), SG_LEN(sg), 0); + if (ret < 0 || (size_t) ret != SG_LEN(sg)) { + TRACE_SENDTO_KDC_UDP_ERROR_SEND_RETRY(context, &conn->addr, + SOCKET_ERRNO); + /* Keep connection alive, we'll try again next pass. + + Is this likely to catch any errors we didn't get from the + select callbacks? */ + return -1; + } + /* Yay, it worked. */ + return 0; +} + +static void +kill_conn(krb5_context context, struct conn_state *conn, + struct select_state *selstate) +{ + free_http_tls_data(context, conn); + + if (socktype_for_transport(conn->addr.transport) == SOCK_STREAM) + TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &conn->addr); + cm_remove_fd(selstate, conn->fd); + + closesocket(conn->fd); + conn->fd = INVALID_SOCKET; + conn->state = FAILED; +} + +/* Check socket for error. */ +static int +get_so_error(int fd) +{ + int e, sockerr; + socklen_t sockerrlen; + + sockerr = 0; + sockerrlen = sizeof(sockerr); + e = getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &sockerrlen); + if (e != 0) { + /* What to do now? */ + e = SOCKET_ERRNO; + return e; + } + return sockerr; +} + +/* Perform next step in sending. Return true on usable data. */ +static krb5_boolean +service_dispatch(krb5_context context, const krb5_data *realm, + struct conn_state *conn, struct select_state *selstate, + int ssflags) +{ + /* Check for a socket exception. */ + if (ssflags & SSF_EXCEPTION) { + kill_conn(context, conn, selstate); + return FALSE; + } + + switch (conn->state) { + case CONNECTING: + assert(conn->service_connect != NULL); + return conn->service_connect(context, realm, conn, selstate); + case WRITING: + assert(conn->service_write != NULL); + return conn->service_write(context, realm, conn, selstate); + case READING: + assert(conn->service_read != NULL); + return conn->service_read(context, realm, conn, selstate); + default: + abort(); + } +} + +/* Initialize TCP transport. */ +static krb5_boolean +service_tcp_connect(krb5_context context, const krb5_data *realm, + struct conn_state *conn, struct select_state *selstate) +{ + /* Check whether the connection succeeded. */ + int e = get_so_error(conn->fd); + + if (e) { + TRACE_SENDTO_KDC_TCP_ERROR_CONNECT(context, &conn->addr, e); + kill_conn(context, conn, selstate); + return FALSE; + } + + conn->state = WRITING; + + /* Record this connection's timeout for service_fds. */ + if (get_curtime_ms(&conn->endtime) == 0) + conn->endtime += 10000; + + return conn->service_write(context, realm, conn, selstate); +} + +/* Sets conn->state to READING when done. */ +static krb5_boolean +service_tcp_write(krb5_context context, const krb5_data *realm, + struct conn_state *conn, struct select_state *selstate) +{ + ssize_t nwritten; + SOCKET_WRITEV_TEMP tmp; + + TRACE_SENDTO_KDC_TCP_SEND(context, &conn->addr); + nwritten = SOCKET_WRITEV(conn->fd, conn->out.sgp, conn->out.sg_count, tmp); + if (nwritten < 0) { + TRACE_SENDTO_KDC_TCP_ERROR_SEND(context, &conn->addr, SOCKET_ERRNO); + kill_conn(context, conn, selstate); + return FALSE; + } + while (nwritten) { + sg_buf *sgp = conn->out.sgp; + if ((size_t)nwritten < SG_LEN(sgp)) { + SG_ADVANCE(sgp, (size_t)nwritten); + nwritten = 0; + } else { + nwritten -= SG_LEN(sgp); + conn->out.sgp++; + conn->out.sg_count--; + } + } + if (conn->out.sg_count == 0) { + /* Done writing, switch to reading. */ + cm_read(selstate, conn->fd); + conn->state = READING; + } + return FALSE; +} + +/* Return true on usable data. */ +static krb5_boolean +service_tcp_read(krb5_context context, const krb5_data *realm, + struct conn_state *conn, struct select_state *selstate) +{ + ssize_t nread; + int e = 0; + struct incoming_message *in = &conn->in; + + if (in->bufsizebytes_read == 4) { + /* Reading data. */ + nread = SOCKET_READ(conn->fd, &in->buf[in->pos], in->n_left); + if (nread <= 0) { + e = nread ? SOCKET_ERRNO : ECONNRESET; + TRACE_SENDTO_KDC_TCP_ERROR_RECV(context, &conn->addr, e); + kill_conn(context, conn, selstate); + return FALSE; + } + in->n_left -= nread; + in->pos += nread; + if (in->n_left <= 0) + return TRUE; + } else { + /* Reading length. */ + nread = SOCKET_READ(conn->fd, in->bufsizebytes + in->bufsizebytes_read, + 4 - in->bufsizebytes_read); + if (nread <= 0) { + e = nread ? SOCKET_ERRNO : ECONNRESET; + TRACE_SENDTO_KDC_TCP_ERROR_RECV_LEN(context, &conn->addr, e); + kill_conn(context, conn, selstate); + return FALSE; + } + in->bufsizebytes_read += nread; + if (in->bufsizebytes_read == 4) { + unsigned long len = load_32_be(in->bufsizebytes); + /* Arbitrary 1M cap. */ + if (len > 1 * 1024 * 1024) { + kill_conn(context, conn, selstate); + return FALSE; + } + in->bufsize = in->n_left = len; + in->pos = 0; + in->buf = malloc(len); + if (in->buf == NULL) { + kill_conn(context, conn, selstate); + return FALSE; + } + } + } + return FALSE; +} + +/* Process events on a UDP socket. Return true if we get a reply. */ +static krb5_boolean +service_udp_read(krb5_context context, const krb5_data *realm, + struct conn_state *conn, struct select_state *selstate) +{ + int nread; + + nread = recv(conn->fd, conn->in.buf, conn->in.bufsize, 0); + if (nread < 0) { + TRACE_SENDTO_KDC_UDP_ERROR_RECV(context, &conn->addr, SOCKET_ERRNO); + kill_conn(context, conn, selstate); + return FALSE; + } + conn->in.pos = nread; + return TRUE; +} + +/* Set up conn->http.tls. Return true on success. */ +static krb5_boolean +setup_tls(krb5_context context, const krb5_data *realm, + struct conn_state *conn, struct select_state *selstate) +{ + krb5_error_code ret; + krb5_boolean ok = FALSE; + char **anchors = NULL, *realmstr = NULL; + const char *names[4]; + + if (init_tls_vtable(context) != 0 || context->tls->setup == NULL) + return FALSE; + + realmstr = k5memdup0(realm->data, realm->length, &ret); + if (realmstr == NULL) + goto cleanup; + + /* Load the configured anchors. */ + names[0] = KRB5_CONF_REALMS; + names[1] = realmstr; + names[2] = KRB5_CONF_HTTP_ANCHORS; + names[3] = NULL; + ret = profile_get_values(context->profile, names, &anchors); + if (ret != 0 && ret != PROF_NO_RELATION) + goto cleanup; + + if (context->tls->setup(context, conn->fd, conn->http.servername, anchors, + &conn->http.tls) != 0) { + TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(context, &conn->addr); + goto cleanup; + } + + ok = TRUE; + +cleanup: + free(realmstr); + profile_free_list(anchors); + return ok; +} + +/* Set conn->state to READING when done; otherwise, call a cm_set_. */ +static krb5_boolean +service_https_write(krb5_context context, const krb5_data *realm, + struct conn_state *conn, struct select_state *selstate) +{ + k5_tls_status st; + + /* If this is our first time in here, set up the SSL context. */ + if (conn->http.tls == NULL && !setup_tls(context, realm, conn, selstate)) { + kill_conn(context, conn, selstate); + return FALSE; + } + + /* Try to transmit our request to the server. */ + st = context->tls->write(context, conn->http.tls, SG_BUF(conn->out.sgp), + SG_LEN(conn->out.sgbuf)); + if (st == DONE) { + TRACE_SENDTO_KDC_HTTPS_SEND(context, &conn->addr); + cm_read(selstate, conn->fd); + conn->state = READING; + } else if (st == WANT_READ) { + cm_read(selstate, conn->fd); + } else if (st == WANT_WRITE) { + cm_write(selstate, conn->fd); + } else if (st == ERROR_TLS) { + TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(context, &conn->addr); + kill_conn(context, conn, selstate); + } + + return FALSE; +} + +/* Return true on finished data. Call a cm_read/write function and return + * false if the TLS layer needs it. Kill the connection on error. */ +static krb5_boolean +https_read_bytes(krb5_context context, struct conn_state *conn, + struct select_state *selstate) +{ + size_t bufsize, nread; + k5_tls_status st; + char *tmp; + struct incoming_message *in = &conn->in; + + for (;;) { + if (in->buf == NULL || in->bufsize - in->pos < 1024) { + bufsize = in->bufsize ? in->bufsize * 2 : 8192; + if (bufsize > 1024 * 1024) { + kill_conn(context, conn, selstate); + return FALSE; + } + tmp = realloc(in->buf, bufsize); + if (tmp == NULL) { + kill_conn(context, conn, selstate); + return FALSE; + } + in->buf = tmp; + in->bufsize = bufsize; + } + + st = context->tls->read(context, conn->http.tls, &in->buf[in->pos], + in->bufsize - in->pos - 1, &nread); + if (st != DATA_READ) + break; + + in->pos += nread; + in->buf[in->pos] = '\0'; + } + + if (st == DONE) + return TRUE; + + if (st == WANT_READ) { + cm_read(selstate, conn->fd); + } else if (st == WANT_WRITE) { + cm_write(selstate, conn->fd); + } else if (st == ERROR_TLS) { + TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(context, &conn->addr); + kill_conn(context, conn, selstate); + } + return FALSE; +} + +/* Return true on readable, valid KKDCPP data. */ +static krb5_boolean +service_https_read(krb5_context context, const krb5_data *realm, + struct conn_state *conn, struct select_state *selstate) +{ + krb5_kkdcp_message *pm = NULL; + krb5_data buf; + const char *rep; + struct incoming_message *in = &conn->in; + + /* Read data through the encryption layer. */ + if (!https_read_bytes(context, conn, selstate)) + return FALSE; + + /* Find the beginning of the response body. */ + rep = strstr(in->buf, "\r\n\r\n"); + if (rep == NULL) + goto kill_conn; + rep += 4; + + /* Decode the response. */ + buf = make_data((char *)rep, in->pos - (rep - in->buf)); + if (decode_krb5_kkdcp_message(&buf, &pm) != 0) + goto kill_conn; + + /* Check and discard the message length at the front of the kerb_message + * field after decoding. If it's wrong or missing, something broke. */ + if (pm->kerb_message.length < 4 || + load_32_be(pm->kerb_message.data) != pm->kerb_message.length - 4) { + goto kill_conn; + } + + /* Replace all of the content that we read back with just the message. */ + memcpy(in->buf, pm->kerb_message.data + 4, pm->kerb_message.length - 4); + in->pos = pm->kerb_message.length - 4; + k5_free_kkdcp_message(context, pm); + + return TRUE; + +kill_conn: + TRACE_SENDTO_KDC_HTTPS_ERROR(context, in->buf); + k5_free_kkdcp_message(context, pm); + kill_conn(context, conn, selstate); + return FALSE; +} + +/* Return the maximum of endtime and the endtime fields of all currently active + * TCP connections. */ +static time_ms +get_endtime(time_ms endtime, struct conn_state *conns) +{ + struct conn_state *state; + + for (state = conns; state != NULL; state = state->next) { + if (state->addr.transport == TCP && + (state->state == READING || state->state == WRITING) && + state->endtime > endtime) + endtime = state->endtime; + } + return endtime; +} + +static krb5_boolean +service_fds(krb5_context context, struct select_state *selstate, + time_ms interval, struct conn_state *conns, + struct select_state *seltemp, const krb5_data *realm, + int (*msg_handler)(krb5_context, const krb5_data *, void *), + void *msg_handler_data, struct conn_state **winner_out) +{ + int e, selret = 0; + time_ms endtime; + struct conn_state *state; + + *winner_out = NULL; + + e = get_curtime_ms(&endtime); + if (e) + return TRUE; + endtime += interval; + + e = 0; + while (selstate->nfds > 0) { + e = cm_select_or_poll(selstate, get_endtime(endtime, conns), + seltemp, &selret); + if (e == EINTR) + continue; + if (e != 0) + break; + + if (selret == 0) + /* Timeout, return to caller. */ + return FALSE; + + /* Got something on a socket, process it. */ + for (state = conns; state != NULL; state = state->next) { + int ssflags; + + if (state->fd == INVALID_SOCKET) + continue; + ssflags = cm_get_ssflags(seltemp, state->fd); + if (!ssflags) + continue; + + if (service_dispatch(context, realm, state, selstate, ssflags)) { + int stop = 1; + + if (msg_handler != NULL) { + krb5_data reply = make_data(state->in.buf, state->in.pos); + + stop = (msg_handler(context, &reply, msg_handler_data) != 0); + } + + if (stop) { + *winner_out = state; + return TRUE; + } + } + } + } + if (e != 0) + return TRUE; + return FALSE; +} + +/* + * Current worst-case timeout behavior: + * + * First pass, 1s per udp or tcp server, plus 2s at end. + * Second pass, 1s per udp server, plus 4s. + * Third pass, 1s per udp server, plus 8s. + * Fourth => 16s, etc. + * + * Restated: + * Per UDP server, 1s per pass. + * Per TCP server, 1s. + * Backoff delay, 2**(P+1) - 2, where P is total number of passes. + * + * Total = 2**(P+1) + U*P + T - 2. + * + * If P=3, Total = 3*U + T + 14. + * If P=4, Total = 4*U + T + 30. + * + * Note that if you try to reach two ports on one server, it counts as two. + * + * There is one exception to the above rules. Whenever a TCP connection is + * established, we wait up to ten seconds for it to finish or fail before + * moving on. This reduces network traffic significantly in a TCP environment. + */ + +krb5_error_code +k5_sendto(krb5_context context, const krb5_data *message, + const krb5_data *realm, const struct serverlist *servers, + k5_transport_strategy strategy, + struct sendto_callback_info* callback_info, krb5_data *reply, + struct sockaddr *remoteaddr, socklen_t *remoteaddrlen, + int *server_used, + /* return 0 -> keep going, 1 -> quit */ + int (*msg_handler)(krb5_context, const krb5_data *, void *), + void *msg_handler_data) +{ + int pass; + time_ms delay; + krb5_error_code retval; + struct conn_state *conns = NULL, *state, **tailptr, *next, *winner; + size_t s; + struct select_state *sel_state = NULL, *seltemp; + char *udpbuf = NULL; + krb5_boolean done = FALSE; + + *reply = empty_data(); + + /* One for use here, listing all our fds in use, and one for + * temporary use in service_fds, for the fds of interest. */ + sel_state = malloc(2 * sizeof(*sel_state)); + if (sel_state == NULL) { + retval = ENOMEM; + goto cleanup; + } + seltemp = &sel_state[1]; + cm_init_selstate(sel_state); + + /* First pass: resolve server hosts, communicate with resulting addresses + * of the preferred transport, and wait 1s for an answer from each. */ + for (s = 0; s < servers->nservers && !done; s++) { + /* Find the current tail pointer. */ + for (tailptr = &conns; *tailptr != NULL; tailptr = &(*tailptr)->next); + retval = resolve_server(context, realm, servers, s, strategy, message, + &udpbuf, &conns); + if (retval) + goto cleanup; + for (state = *tailptr; state != NULL && !done; state = state->next) { + /* Contact each new connection, deferring those which use the + * non-preferred RFC 4120 transport. */ + if (state->defer) + continue; + if (maybe_send(context, state, message, sel_state, realm, + callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, + realm, msg_handler, msg_handler_data, &winner); + } + } + + /* Complete the first pass by contacting servers of the non-preferred RFC + * 4120 transport (if given), waiting 1s for an answer from each. */ + for (state = conns; state != NULL && !done; state = state->next) { + if (!state->defer) + continue; + if (maybe_send(context, state, message, sel_state, realm, + callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, + realm, msg_handler, msg_handler_data, &winner); + } + + /* Wait for two seconds at the end of the first pass. */ + if (!done) { + done = service_fds(context, sel_state, 2000, conns, seltemp, + realm, msg_handler, msg_handler_data, &winner); + } + + /* Make remaining passes over all of the connections. */ + delay = 4000; + for (pass = 1; pass < MAX_PASS && !done; pass++) { + for (state = conns; state != NULL && !done; state = state->next) { + if (maybe_send(context, state, message, sel_state, realm, + callback_info)) + continue; + done = service_fds(context, sel_state, 1000, conns, seltemp, + realm, msg_handler, msg_handler_data, &winner); + if (sel_state->nfds == 0) + break; + } + /* Wait for the delay backoff at the end of this pass. */ + if (!done) { + done = service_fds(context, sel_state, delay, conns, seltemp, + realm, msg_handler, msg_handler_data, &winner); + } + if (sel_state->nfds == 0) + break; + delay *= 2; + } + + if (sel_state->nfds == 0 || !done || winner == NULL) { + retval = KRB5_KDC_UNREACH; + goto cleanup; + } + /* Success! */ + *reply = make_data(winner->in.buf, winner->in.pos); + retval = 0; + winner->in.buf = NULL; + if (server_used != NULL) + *server_used = winner->server_index; + if (remoteaddr != NULL && remoteaddrlen != 0 && *remoteaddrlen > 0) + (void)getpeername(winner->fd, remoteaddr, remoteaddrlen); + TRACE_SENDTO_KDC_RESPONSE(context, reply->length, &winner->addr); + +cleanup: + for (state = conns; state != NULL; state = next) { + next = state->next; + if (state->fd != INVALID_SOCKET) { + if (socktype_for_transport(state->addr.transport) == SOCK_STREAM) + TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &state->addr); + closesocket(state->fd); + free_http_tls_data(context, state); + } + if (state->in.buf != udpbuf) + free(state->in.buf); + if (callback_info) { + callback_info->pfn_cleanup(callback_info->data, + &state->callback_buffer); + } + free(state); + } + + if (reply->data != udpbuf) + free(udpbuf); + free(sel_state); + return retval; +} |
