LCOV - code coverage report
Current view: top level - third_party/heimdal/lib/krb5 - send_to_kdc.c (source / functions) Hit Total Coverage
Test: coverage report for master 2f515e9b Lines: 445 681 65.3 %
Date: 2024-04-21 15:09:00 Functions: 31 41 75.6 %

          Line data    Source code
       1             : /*
       2             :  * Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan
       3             :  * (Royal Institute of Technology, Stockholm, Sweden).
       4             :  * All rights reserved.
       5             :  *
       6             :  * Portions Copyright (c) 2010 - 2013 Apple Inc. All rights reserved.
       7             :  *
       8             :  * Redistribution and use in source and binary forms, with or without
       9             :  * modification, are permitted provided that the following conditions
      10             :  * are met:
      11             :  *
      12             :  * 1. Redistributions of source code must retain the above copyright
      13             :  *    notice, this list of conditions and the following disclaimer.
      14             :  *
      15             :  * 2. Redistributions in binary form must reproduce the above copyright
      16             :  *    notice, this list of conditions and the following disclaimer in the
      17             :  *    documentation and/or other materials provided with the distribution.
      18             :  *
      19             :  * 3. Neither the name of the Institute nor the names of its contributors
      20             :  *    may be used to endorse or promote products derived from this software
      21             :  *    without specific prior written permission.
      22             :  *
      23             :  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
      24             :  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
      25             :  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
      26             :  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
      27             :  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
      28             :  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
      29             :  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      30             :  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      31             :  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
      32             :  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
      33             :  * SUCH DAMAGE.
      34             :  */
      35             : 
      36             : #include "krb5_locl.h"
      37             : #include "send_to_kdc_plugin.h"
      38             : 
      39             : /**
      40             :  * @section send_to_kdc Locating and sending packets to the KDC
      41             :  *
      42             :  * The send to kdc code is responsible to request the list of KDC from
      43             :  * the locate-kdc subsystem and then send requests to each of them.
      44             :  *
      45             :  * - Each second a new hostname is tried.
      46             :  * - If the hostname have several addresses, the first will be tried
      47             :  *   directly then in turn the other will be tried every 3 seconds
      48             :  *   (host_timeout).
      49             :  * - UDP requests are tried 3 times, and it tried with a individual timeout of kdc_timeout / 3.
      50             :  * - TCP and HTTP requests are tried 1 time.
      51             :  *
      52             :  *  Total wait time shorter then (number of addresses * 3) + kdc_timeout seconds.
      53             :  *
      54             :  */
      55             : 
      56             : static int
      57           0 : init_port(const char *s, int fallback)
      58             : {
      59           0 :     int tmp;
      60             : 
      61           0 :     if (s && sscanf(s, "%d", &tmp) == 1)
      62           0 :         return htons(tmp);
      63           0 :     return fallback;
      64             : }
      65             : 
      66             : struct send_via_plugin_s {
      67             :     krb5_const_realm realm;
      68             :     krb5_krbhst_info *hi;
      69             :     time_t timeout;
      70             :     const krb5_data *send_data;
      71             :     krb5_data *receive;
      72             : };
      73             :     
      74             : 
      75             : static krb5_error_code KRB5_LIB_CALL
      76       73814 : kdccallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
      77             : {
      78       73814 :     const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
      79       73814 :     struct send_via_plugin_s *ctx = userctx;
      80             : 
      81       73814 :     if (service->send_to_kdc == NULL)
      82           0 :         return KRB5_PLUGIN_NO_HANDLE;
      83       73814 :     return service->send_to_kdc(context, plugctx, ctx->hi, ctx->timeout,
      84             :                                 ctx->send_data, ctx->receive);
      85             : }
      86             : 
      87             : static krb5_error_code KRB5_LIB_CALL
      88       85946 : realmcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
      89             : {
      90       85946 :     const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
      91       85946 :     struct send_via_plugin_s *ctx = userctx;
      92             : 
      93       85946 :     if (service->send_to_realm == NULL)
      94           0 :         return KRB5_PLUGIN_NO_HANDLE;
      95       85946 :     return service->send_to_realm(context, plugctx, ctx->realm, ctx->timeout,
      96             :                                   ctx->send_data, ctx->receive);
      97             : }
      98             : 
      99             : static const char *const send_to_kdc_plugin_deps[] = { "krb5", NULL };
     100             : 
     101             : static const struct heim_plugin_data
     102             : send_to_kdc_plugin_data = {
     103             :     "krb5",
     104             :     KRB5_PLUGIN_SEND_TO_KDC,
     105             :     KRB5_PLUGIN_SEND_TO_KDC_VERSION_0,
     106             :     send_to_kdc_plugin_deps,
     107             :     krb5_get_instance
     108             : };
     109             : 
     110             : static krb5_error_code
     111       74337 : kdc_via_plugin(krb5_context context,
     112             :                krb5_krbhst_info *hi,
     113             :                time_t timeout,
     114             :                const krb5_data *send_data,
     115             :                krb5_data *receive)
     116             : {
     117        3413 :     struct send_via_plugin_s userctx;
     118             : 
     119       74337 :     userctx.realm = NULL;
     120       74337 :     userctx.hi = hi;
     121       74337 :     userctx.timeout = timeout;
     122       74337 :     userctx.send_data = send_data;
     123       74337 :     userctx.receive = receive;
     124             : 
     125       74337 :     return _krb5_plugin_run_f(context, &send_to_kdc_plugin_data, 0,
     126             :                               &userctx, kdccallback);
     127             : }
     128             : 
     129             : static krb5_error_code
     130       86355 : realm_via_plugin(krb5_context context,
     131             :                  krb5_const_realm realm,
     132             :                  time_t timeout,
     133             :                  const krb5_data *send_data,
     134             :                  krb5_data *receive)
     135             : {
     136        2828 :     struct send_via_plugin_s userctx;
     137             : 
     138       86355 :     userctx.realm = realm;
     139       86355 :     userctx.hi = NULL;
     140       86355 :     userctx.timeout = timeout;
     141       86355 :     userctx.send_data = send_data;
     142       86355 :     userctx.receive = receive;
     143             : 
     144       86355 :     return _krb5_plugin_run_f(context, &send_to_kdc_plugin_data, 0,
     145             :                               &userctx, realmcallback);
     146             : }
     147             : 
     148             : struct krb5_sendto_ctx_data {
     149             :     int flags;
     150             :     int type;
     151             :     krb5_sendto_ctx_func func;
     152             :     void *data;
     153             :     char *hostname;
     154             :     char *sitename;
     155             :     krb5_krbhst_handle krbhst;
     156             : 
     157             :     /* context2 */
     158             :     const krb5_data *send_data;
     159             :     krb5_data response;
     160             :     heim_array_t hosts;
     161             :     int stateflags;
     162             : #define KRBHST_COMPLETED        1
     163             : 
     164             :     /* prexmit */
     165             :     krb5_sendto_prexmit prexmit_func;
     166             :     void *prexmit_ctx;
     167             : 
     168             :     /* stats */
     169             :     struct {
     170             :         struct timeval start_time;
     171             :         struct timeval name_resolution;
     172             :         struct timeval krbhst;
     173             :         unsigned long sent_packets;
     174             :         unsigned long num_hosts;
     175             :     } stats;
     176             :     unsigned int stid;
     177             : };
     178             : 
     179             : static void KRB5_CALLCONV
     180       71298 : dealloc_sendto_ctx(void *ptr)
     181             : {
     182       71298 :     krb5_sendto_ctx ctx = (krb5_sendto_ctx)ptr;
     183       71298 :     if (ctx->hostname)
     184           0 :         free(ctx->hostname);
     185       71298 :     if (ctx->sitename)
     186           0 :         free(ctx->sitename);
     187       71298 :     heim_release(ctx->hosts);
     188       71298 :     heim_release(ctx->krbhst);
     189       71298 : }
     190             : 
     191             : KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
     192       71298 : krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx)
     193             : {
     194       71298 :     *ctx = heim_alloc(sizeof(**ctx), "sendto-context", dealloc_sendto_ctx);
     195       71298 :     if (*ctx == NULL)
     196           0 :         return krb5_enomem(context);
     197       71298 :     (*ctx)->hosts = heim_array_create();
     198             : 
     199       71298 :     return 0;
     200             : }
     201             : 
     202             : KRB5_LIB_FUNCTION void KRB5_LIB_CALL
     203       12431 : krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags)
     204             : {
     205       12431 :     ctx->flags |= flags;
     206       12431 : }
     207             : 
     208             : KRB5_LIB_FUNCTION int KRB5_LIB_CALL
     209       12431 : krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)
     210             : {
     211       12431 :     return ctx->flags;
     212             : }
     213             : 
     214             : KRB5_LIB_FUNCTION void KRB5_LIB_CALL
     215           0 : krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type)
     216             : {
     217           0 :     ctx->type = type;
     218           0 : }
     219             : 
     220             : KRB5_LIB_FUNCTION void KRB5_LIB_CALL
     221       71298 : krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx,
     222             :                          krb5_sendto_ctx_func func,
     223             :                          void *data)
     224             : {
     225       71298 :     ctx->func = func;
     226       71298 :     ctx->data = data;
     227       71298 : }
     228             : 
     229             : KRB5_LIB_FUNCTION void KRB5_LIB_CALL
     230           0 : _krb5_sendto_ctx_set_prexmit(krb5_sendto_ctx ctx,
     231             :                              krb5_sendto_prexmit prexmit,
     232             :                              void *data)
     233             : {
     234           0 :     ctx->prexmit_func = prexmit;
     235           0 :     ctx->prexmit_ctx = data;
     236           0 : }
     237             : 
     238             : KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
     239           0 : krb5_sendto_set_hostname(krb5_context context,
     240             :                          krb5_sendto_ctx ctx,
     241             :                          const char *hostname)
     242             : {
     243           0 :     char *newname;
     244             : 
     245             :     /*
     246             :      * Handle the case where hostname == ctx->hostname by copying it first, and
     247             :      * disposing of any previous value after.
     248             :      */
     249           0 :     newname = strdup(hostname);
     250           0 :     if (newname == NULL)
     251           0 :         return krb5_enomem(context);
     252           0 :     free(ctx->hostname);
     253           0 :     ctx->hostname = newname;
     254           0 :     return 0;
     255             : }
     256             : 
     257             : KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
     258           0 : krb5_sendto_set_sitename(krb5_context context,
     259             :                          krb5_sendto_ctx ctx,
     260             :                          const char *sitename)
     261             : {
     262           0 :     char *newname;
     263             : 
     264           0 :     newname = strdup(sitename);
     265           0 :     if (newname == NULL)
     266           0 :         return krb5_enomem(context);
     267           0 :     free(ctx->sitename);
     268           0 :     ctx->sitename = newname;
     269           0 :     return 0;
     270             : }
     271             : 
     272             : KRB5_LIB_FUNCTION void KRB5_LIB_CALL
     273           0 : _krb5_sendto_ctx_set_krb5hst(krb5_context context,
     274             :                              krb5_sendto_ctx ctx,
     275             :                              krb5_krbhst_handle handle)
     276             : {
     277           0 :     heim_release(ctx->krbhst);
     278           0 :     ctx->krbhst = heim_retain(handle);
     279           0 : }
     280             : 
     281             : KRB5_LIB_FUNCTION void KRB5_LIB_CALL
     282       71298 : krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx)
     283             : {
     284       71298 :     heim_release(ctx);
     285       71298 : }
     286             : 
     287             : KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
     288       74333 : _krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data,
     289             :                 const krb5_data *reply, int *action)
     290             : {
     291        3413 :     krb5_error_code ret;
     292        3413 :     KRB_ERROR error;
     293             : 
     294       74333 :     if(krb5_rd_error(context, reply, &error))
     295       46622 :         return 0;
     296             : 
     297       25468 :     ret = krb5_error_from_rd_error(context, &error, NULL);
     298       25468 :     krb5_free_error_contents(context, &error);
     299             : 
     300       25468 :     switch(ret) {
     301       12431 :     case KRB5KRB_ERR_RESPONSE_TOO_BIG: {
     302       12431 :         if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG)
     303           0 :             break;
     304       12431 :         krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG);
     305       12431 :         *action = KRB5_SENDTO_RESET;
     306       12431 :         break;
     307             :     }
     308           0 :     case KRB5KDC_ERR_SVC_UNAVAILABLE:
     309           0 :         *action = KRB5_SENDTO_RESET;
     310           0 :         break;
     311             :     }
     312       24298 :     return 0;
     313             : }
     314             : 
     315             : /*
     316             :  *
     317             :  */
     318             : 
     319             : struct host;
     320             : 
     321             : struct host_fun {
     322             :     krb5_error_code (*prepare)(krb5_context, struct host *, const krb5_data *);
     323             :     krb5_error_code (*send_fn)(krb5_context, struct host *);
     324             :     krb5_error_code (*recv_fn)(krb5_context, struct host *, krb5_data *);
     325             :     int ntries;
     326             : };
     327             : 
     328             : struct host {
     329             :     enum host_state { CONNECT, CONNECTING, CONNECTED, WAITING_REPLY, DEAD } state;
     330             :     krb5_krbhst_info *hi;
     331             :     struct addrinfo *freeai;
     332             :     struct addrinfo *ai;
     333             :     rk_socket_t fd;
     334             :     const struct host_fun *fun;
     335             :     unsigned int tries;
     336             :     time_t timeout;
     337             :     krb5_data data;
     338             :     unsigned int tid;
     339             : };
     340             : 
     341             : static void
     342             : debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
     343             :         __attribute__ ((__format__ (__printf__, 4, 5)));
     344             : 
     345             : static void
     346      176630 : debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
     347             : {
     348      176630 :     const char *proto = "unknown";
     349        6930 :     const char *state;
     350        6930 :     char name[NI_MAXHOST], port[NI_MAXSERV];
     351      176630 :     char *text = NULL;
     352        6930 :     va_list ap;
     353        6930 :     int ret;
     354             : 
     355      176630 :     if (!_krb5_have_debug(context, 5))
     356      176614 :         return;
     357             : 
     358          16 :     va_start(ap, fmt);
     359          16 :     ret = vasprintf(&text, fmt, ap);
     360          16 :     va_end(ap);
     361          16 :     if (ret == -1 || text == NULL)
     362           0 :         return;
     363             : 
     364          16 :     if (host->hi->proto == KRB5_KRBHST_HTTP)
     365           0 :         proto = "http";
     366          16 :     else if (host->hi->proto == KRB5_KRBHST_TCP)
     367           4 :         proto = "tcp";
     368          12 :     else if (host->hi->proto == KRB5_KRBHST_UDP)
     369          12 :         proto = "udp";
     370             : 
     371          16 :     if (getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen,
     372             :                     name, sizeof(name), port, sizeof(port),
     373             :                     NI_NUMERICHOST|NI_NUMERICSERV|NI_NUMERICSCOPE) != 0)
     374           0 :         name[0] = '\0';
     375             : 
     376          16 :     switch (host->state) {
     377           4 :     case CONNECT:       state = "CONNECT";            break;
     378           0 :     case CONNECTING:    state = "CONNECTING";         break;
     379           4 :     case CONNECTED:     state = "CONNECTED";          break;
     380           8 :     case WAITING_REPLY: state = "WAITING_REPLY";      break;
     381           0 :     case DEAD:          state = "DEAD";                       break;
     382           0 :     default:            state = "unknown";            break;
     383             :     }
     384             : 
     385          16 :     _krb5_debug(context, level, "%s: %s %s:%s (%s) state=%s tid: %08x", text,
     386          16 :                 proto, name, port, host->hi->hostname, state, host->tid);
     387          16 :     free(text);
     388             : }
     389             : 
     390             : 
     391             : static void HEIM_CALLCONV
     392       41795 : deallocate_host(void *ptr)
     393             : {
     394       41795 :     struct host *host = ptr;
     395       41795 :     if (!rk_IS_BAD_SOCKET(host->fd))
     396       41795 :         rk_closesocket(host->fd);
     397       41795 :     krb5_data_free(&host->data);
     398       41795 :     if (host->freeai)
     399           0 :         freeaddrinfo(host->freeai);
     400       41795 :     host->freeai = NULL;
     401       41795 :     host->ai = NULL;
     402       41795 : }
     403             : 
     404             : static void
     405           0 : host_dead(krb5_context context, struct host *host, const char *msg)
     406             : {
     407           0 :     debug_host(context, 5, host, "%s", msg);
     408           0 :     rk_closesocket(host->fd);
     409           0 :     host->fd = rk_INVALID_SOCKET;
     410           0 :     host->state = DEAD;
     411           0 : }
     412             : 
     413             : static krb5_error_code
     414       38193 : send_stream(krb5_context context, struct host *host)
     415             : {
     416        1658 :     ssize_t len;
     417             : 
     418       38193 :     len = krb5_net_write(context, &host->fd, host->data.data, host->data.length);
     419             : 
     420       38193 :     if (len < 0)
     421           0 :         return errno;
     422       38193 :     else if (len < host->data.length) {
     423           0 :         host->data.length -= len;
     424           0 :         memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len);
     425           0 :         return -1;
     426             :     } else {
     427       38193 :         krb5_data_free(&host->data);
     428       38193 :         return 0;
     429             :     }
     430             : }
     431             : 
     432             : static krb5_error_code
     433       47691 : recv_stream(krb5_context context, struct host *host)
     434             : {
     435        1956 :     krb5_error_code ret;
     436        1956 :     size_t oldlen;
     437        1956 :     ssize_t sret;
     438        1956 :     int nbytes;
     439             : 
     440       47691 :     if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
     441           0 :         return HEIM_NET_CONN_REFUSED;
     442             : 
     443       47691 :     if (context->max_msg_size - host->data.length < nbytes) {
     444           0 :         krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
     445           0 :                                N_("TCP message from KDC too large %d", ""),
     446           0 :                                (int)(host->data.length + nbytes));
     447           0 :         return KRB5KRB_ERR_FIELD_TOOLONG;
     448             :     }
     449             : 
     450       47691 :     oldlen = host->data.length;
     451             : 
     452       47691 :     ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */);
     453       47691 :     if (ret)
     454           0 :         return ret;
     455             : 
     456       47691 :     sret = krb5_net_read(context, &host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes);
     457       47691 :     if (sret <= 0) {
     458           0 :         ret = errno;
     459           0 :         return ret;
     460             :     }
     461       47691 :     host->data.length = oldlen + sret;
     462             :     /* zero terminate for http transport */
     463       47691 :     ((uint8_t *)host->data.data)[host->data.length] = '\0';
     464             : 
     465       47691 :     return 0;
     466             : }
     467             : 
     468             : /*
     469             :  *
     470             :  */
     471             : 
     472             : static void
     473       41779 : host_next_timeout(krb5_context context, struct host *host)
     474             : {
     475       41779 :     host->timeout = context->kdc_timeout / host->fun->ntries;
     476       41779 :     if (host->timeout == 0)
     477           0 :         host->timeout = 1;
     478             : 
     479       41779 :     host->timeout += time(NULL);
     480       41779 : }
     481             : 
     482             : /*
     483             :  * connected host
     484             :  */
     485             : 
     486             : static void
     487       41779 : host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
     488             : {
     489        1658 :     krb5_error_code ret;
     490             : 
     491       41779 :     host->state = CONNECTED; 
     492             :     /*
     493             :      * Now prepare data to send to host
     494             :      */
     495       41779 :     if (ctx->prexmit_func) {
     496           0 :         krb5_data data;
     497             :             
     498           0 :         krb5_data_zero(&data);
     499             : 
     500           0 :         ret = ctx->prexmit_func(context, host->hi->proto,
     501             :                                 ctx->prexmit_ctx, host->fd, &data);
     502           0 :         if (ret == 0) {
     503           0 :             if (data.length == 0) {
     504           0 :                 host_dead(context, host, "prexmit function didn't send data");
     505           0 :                 return;
     506             :             }
     507           0 :             ret = host->fun->prepare(context, host, &data);
     508           0 :             krb5_data_free(&data);
     509             :         }
     510             :         
     511             :     } else {
     512       41779 :         ret = host->fun->prepare(context, host, ctx->send_data);
     513             :     }
     514       41779 :     if (ret)
     515           0 :         debug_host(context, 5, host, "failed to prexmit/prepare");
     516             : }
     517             : 
     518             : /*
     519             :  * connect host
     520             :  */
     521             : 
     522             : static void
     523       41779 : host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
     524             : {
     525       41779 :     krb5_krbhst_info *hi = host->hi;
     526       41779 :     struct addrinfo *ai = host->ai;
     527             : 
     528       41779 :     debug_host(context, 5, host, "connecting to host");
     529             : 
     530       41779 :     if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) {
     531             : #ifdef HAVE_WINSOCK
     532             :         if (WSAGetLastError() == WSAEWOULDBLOCK)
     533             :             errno = EINPROGRESS;
     534             : #endif /* HAVE_WINSOCK */
     535           0 :         if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) {
     536           0 :             debug_host(context, 5, host, "connecting to %d", host->fd);
     537           0 :             host->state = CONNECTING;
     538             :         } else {
     539           0 :             host_dead(context, host, "failed to connect");
     540             :         }
     541             :     } else {
     542       41779 :         host_connected(context, ctx, host);
     543             :     }
     544             : 
     545       41779 :     host_next_timeout(context, host);
     546       41779 : }
     547             : 
     548             : /*
     549             :  * HTTP transport
     550             :  */
     551             : 
     552             : static krb5_error_code
     553           0 : prepare_http(krb5_context context, struct host *host, const krb5_data *data)
     554             : {
     555           0 :     char *str = NULL, *request = NULL;
     556           0 :     krb5_error_code ret;
     557           0 :     int len;
     558             : 
     559           0 :     heim_assert(host->data.length == 0, "prepare_http called twice");
     560             : 
     561           0 :     len = rk_base64_encode(data->data, data->length, &str);
     562           0 :     if(len < 0)
     563           0 :         return ENOMEM;
     564             : 
     565           0 :     if (context->http_proxy)
     566           0 :         ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str);
     567             :     else
     568           0 :         ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str);
     569           0 :     free(str);
     570           0 :     if(ret < 0 || request == NULL)
     571           0 :         return ENOMEM;
     572             :     
     573           0 :     host->data.data = request;
     574           0 :     host->data.length = strlen(request);
     575             : 
     576           0 :     return 0;
     577             : }
     578             : 
     579             : static krb5_error_code
     580           0 : recv_http(krb5_context context, struct host *host, krb5_data *data)
     581             : {
     582           0 :     krb5_error_code ret;
     583           0 :     unsigned long rep_len;
     584           0 :     size_t len;
     585           0 :     char *p;
     586             : 
     587             :     /*
     588             :      * recv_stream returns a NUL terminated stream
     589             :      */
     590             : 
     591           0 :     ret = recv_stream(context, host);
     592           0 :     if (ret)
     593           0 :         return ret;
     594             : 
     595           0 :     p = strstr(host->data.data, "\r\n\r\n");
     596           0 :     if (p == NULL)
     597           0 :         return -1;
     598           0 :     p += 4;
     599             : 
     600           0 :     len = host->data.length - (p - (char *)host->data.data);
     601           0 :     if (len < 4)
     602           0 :         return -1;
     603             : 
     604           0 :     _krb5_get_int(p, &rep_len, 4);
     605           0 :     if (len < rep_len)
     606           0 :         return -1;
     607             : 
     608           0 :     p += 4;
     609             : 
     610           0 :     memmove(host->data.data, p, rep_len);
     611           0 :     host->data.length = rep_len;
     612             : 
     613           0 :     *data = host->data;
     614           0 :     krb5_data_zero(&host->data);
     615             : 
     616           0 :     return 0;
     617             : }
     618             : 
     619             : /*
     620             :  * TCP transport
     621             :  */
     622             : 
     623             : static krb5_error_code
     624       38193 : prepare_tcp(krb5_context context, struct host *host, const krb5_data *data)
     625             : {
     626        1658 :     krb5_error_code ret;
     627        1658 :     krb5_storage *sp;
     628             : 
     629       38193 :     heim_assert(host->data.length == 0, "prepare_tcp called twice");
     630             : 
     631       38193 :     sp = krb5_storage_emem();
     632       38193 :     if (sp == NULL)
     633           0 :         return ENOMEM;
     634             :     
     635       38193 :     ret = krb5_store_data(sp, *data);
     636       38193 :     if (ret) {
     637           0 :         krb5_storage_free(sp);
     638           0 :         return ret;
     639             :     }
     640       38193 :     ret = krb5_storage_to_data(sp, &host->data);
     641       38193 :     krb5_storage_free(sp);
     642             : 
     643       38193 :     return ret;
     644             : }
     645             : 
     646             : static krb5_error_code
     647       47691 : recv_tcp(krb5_context context, struct host *host, krb5_data *data)
     648             : {
     649        1956 :     krb5_error_code ret;
     650        1956 :     unsigned long pktlen;
     651             : 
     652       47691 :     ret = recv_stream(context, host);
     653       47691 :     if (ret)
     654           0 :         return ret;
     655             : 
     656       47691 :     if (host->data.length < 4)
     657           0 :         return -1;
     658             : 
     659       47691 :     _krb5_get_int(host->data.data, &pktlen, 4);
     660             :     
     661       47691 :     if (pktlen > host->data.length - 4)
     662        9200 :         return -1;
     663             : 
     664       38193 :     memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4);
     665       38193 :     host->data.length -= 4;
     666             : 
     667       38193 :     *data = host->data;
     668       38193 :     krb5_data_zero(&host->data);
     669             :     
     670       38193 :     return 0;
     671             : }
     672             : 
     673             : /*
     674             :  * UDP transport
     675             :  */
     676             : 
     677             : static krb5_error_code
     678        3586 : prepare_udp(krb5_context context, struct host *host, const krb5_data *data)
     679             : {
     680        3586 :     return krb5_data_copy(&host->data, data->data, data->length);
     681             : }
     682             : 
     683             : static krb5_error_code
     684        3586 : send_udp(krb5_context context, struct host *host)
     685             : {
     686        3586 :     if (send(host->fd, host->data.data, host->data.length, 0) < 0)
     687           0 :         return errno;
     688        3586 :     return 0;
     689             : }
     690             : 
     691             : static krb5_error_code
     692        3586 : recv_udp(krb5_context context, struct host *host, krb5_data *data)
     693             : {
     694           0 :     krb5_error_code ret;
     695           0 :     int nbytes;
     696             : 
     697             : 
     698        3586 :     if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
     699           0 :         return HEIM_NET_CONN_REFUSED;
     700             : 
     701        3586 :     if (context->max_msg_size < nbytes) {
     702           0 :         krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
     703           0 :                                N_("UDP message from KDC too large %d", ""),
     704             :                                (int)nbytes);
     705           0 :         return KRB5KRB_ERR_FIELD_TOOLONG;
     706             :     }
     707             : 
     708        3586 :     ret = krb5_data_alloc(data, nbytes);
     709        3586 :     if (ret)
     710           0 :         return ret;
     711             : 
     712        3586 :     ret = recv(host->fd, data->data, data->length, 0);
     713        3586 :     if (ret < 0) {
     714           0 :         ret = errno;
     715           0 :         krb5_data_free(data);
     716           0 :         return ret;
     717             :     }
     718        3586 :     data->length = ret;
     719             : 
     720        3586 :     return 0;
     721             : }
     722             : 
     723             : static const struct host_fun http_fun = {
     724             :     prepare_http,
     725             :     send_stream,
     726             :     recv_http,
     727             :     1
     728             : };
     729             : static const struct host_fun tcp_fun = {
     730             :     prepare_tcp,
     731             :     send_stream,
     732             :     recv_tcp,
     733             :     1
     734             : };
     735             : static const struct host_fun udp_fun = {
     736             :     prepare_udp,
     737             :     send_udp,
     738             :     recv_udp,
     739             :     3
     740             : };
     741             : 
     742             : 
     743             : /*
     744             :  * Host state machine
     745             :  */
     746             : 
     747             : static int
     748       93074 : eval_host_state(krb5_context context,
     749             :                 krb5_sendto_ctx ctx,
     750             :                 struct host *host,
     751             :                 int readable, int writeable)
     752             : {
     753        3614 :     krb5_error_code ret;
     754             : 
     755       93074 :     if (host->state == CONNECT) {
     756             :         /* check if its this host time to connect */
     757          18 :         if (host->timeout < time(NULL))
     758           0 :             host_connect(context, ctx, host);
     759          18 :         return 0;
     760             :     }
     761             : 
     762       93056 :     if (host->state == CONNECTING && writeable)
     763           0 :         host_connected(context, ctx, host);
     764             : 
     765       93056 :     if (readable) {
     766             : 
     767       51277 :         debug_host(context, 5, host, "reading packet");
     768             : 
     769       51277 :         ret = host->fun->recv_fn(context, host, &ctx->response);
     770       51277 :         if (ret == -1) {
     771             :             /* not done yet */
     772       41779 :         } else if (ret == 0) {
     773             :             /* if recv_foo function returns 0, we have a complete reply */
     774       41779 :             debug_host(context, 5, host, "host completed");
     775       41779 :             return 1;
     776             :         } else {
     777           0 :             host_dead(context, host, "host disconnected");
     778             :         }
     779             :     }
     780             : 
     781             :     /* check if there is anything to send, state might DEAD after read */
     782       51277 :     if (writeable && host->state == CONNECTED) {
     783             : 
     784       41779 :         ctx->stats.sent_packets++;
     785             : 
     786       41779 :         debug_host(context, 5, host, "writing packet");
     787             : 
     788       41779 :         ret = host->fun->send_fn(context, host);
     789       41779 :         if (ret == -1) {
     790             :             /* not done yet */
     791       41779 :         } else if (ret) {
     792           0 :             host_dead(context, host, "host dead, write failed");
     793             :         } else
     794       41779 :             host->state = WAITING_REPLY;
     795             :     }
     796             : 
     797       49321 :     return 0;
     798             : }
     799             : 
     800             : /*
     801             :  *
     802             :  */
     803             : 
     804             : static krb5_error_code
     805       74337 : submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)
     806             : {
     807       74337 :     unsigned long submitted_host = 0;
     808       74337 :     struct addrinfo *freeai = NULL;
     809        3413 :     struct timeval nrstart, nrstop;
     810        3413 :     krb5_error_code ret;
     811       74337 :     struct addrinfo *ai = NULL, *a;
     812        3413 :     struct host *host;
     813             : 
     814       74337 :     ret = kdc_via_plugin(context, hi, context->kdc_timeout,
     815             :                          ctx->send_data, &ctx->response);
     816       74337 :     if (ret == 0) {
     817       30799 :         return 0;
     818       41783 :     } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
     819           4 :         _krb5_debug(context, 5, "send via plugin failed %s: %d",
     820           4 :                     hi->hostname, ret);
     821           4 :         return ret;
     822             :     }
     823             : 
     824             :     /*
     825             :      * If we have a proxy, let use the address of the proxy instead of
     826             :      * the KDC and let the proxy deal with the resolving of the KDC.
     827             :      */
     828             : 
     829       41779 :     gettimeofday(&nrstart, NULL);
     830             : 
     831       41779 :     if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
     832           0 :         char *proxy2 = strdup(context->http_proxy);
     833           0 :         char *el, *proxy  = proxy2;
     834           0 :         struct addrinfo hints;
     835           0 :         char portstr[NI_MAXSERV];
     836           0 :         unsigned short nport;
     837             :         
     838           0 :         if (proxy == NULL)
     839           0 :             return ENOMEM;
     840           0 :         if (strncmp(proxy, "http://", 7) == 0)
     841           0 :             proxy += 7;
     842             :         
     843             :         /* check for url terminating slash */
     844           0 :         el = strchr(proxy, '/');
     845           0 :         if (el != NULL)
     846           0 :             *el = '\0';
     847             : 
     848             :         /* check for port in hostname, used below as port */
     849           0 :         el = strchr(proxy, ':');
     850           0 :         if(el != NULL)
     851           0 :             *el++ = '\0';
     852             : 
     853           0 :         memset(&hints, 0, sizeof(hints));
     854           0 :         hints.ai_family   = PF_UNSPEC;
     855           0 :         hints.ai_socktype = SOCK_STREAM;
     856             : 
     857             :         /* On some systems ntohs(foo(..., htons(...))) causes shadowing */
     858           0 :         nport = init_port(el, htons(80));
     859           0 :         snprintf(portstr, sizeof(portstr), "%d", ntohs(nport));
     860             : 
     861           0 :         if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
     862             :                 NULL)) {
     863           0 :             hints.ai_flags &= ~AI_CANONNAME;
     864           0 :             hints.ai_flags |= AI_NUMERICHOST|AI_NUMERICSERV;
     865             :         }
     866           0 :         ret = getaddrinfo(proxy, portstr, &hints, &ai);
     867           0 :         free(proxy2);
     868           0 :         if (ret)
     869           0 :             return krb5_eai_to_heim_errno(ret, errno);
     870             : 
     871           0 :         freeai = ai;
     872             : 
     873             :     } else {
     874       41779 :         ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
     875       41779 :         if (ret)
     876           0 :             return ret;
     877             :     }
     878             : 
     879             :     /* add up times */
     880       41779 :     gettimeofday(&nrstop, NULL);
     881       41779 :     timevalsub(&nrstop, &nrstart);
     882       41779 :     timevaladd(&ctx->stats.name_resolution, &nrstop);
     883             : 
     884       41779 :     ctx->stats.num_hosts++;
     885             : 
     886       83574 :     for (a = ai; a != NULL; a = a->ai_next) {
     887        1658 :         rk_socket_t fd;
     888             : 
     889       41795 :         fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
     890       41795 :         if (rk_IS_BAD_SOCKET(fd))
     891           0 :             continue;
     892       41795 :         rk_cloexec(fd);
     893             : 
     894             : #ifndef NO_LIMIT_FD_SETSIZE
     895       41795 :         if (fd >= FD_SETSIZE) {
     896           0 :             _krb5_debug(context, 0, "fd too large for select");
     897           0 :             rk_closesocket(fd);
     898           0 :             continue;
     899             :         }
     900             : #endif
     901       41795 :         socket_set_nonblocking(fd, 1);
     902             : 
     903       41795 :         host = heim_alloc(sizeof(*host), "sendto-host", deallocate_host);
     904       41795 :         if (host == NULL) {
     905           0 :             if (freeai)
     906           0 :                 freeaddrinfo(freeai);
     907           0 :             rk_closesocket(fd);
     908           0 :             return ENOMEM;
     909             :         }
     910       41795 :         host->hi = hi;
     911       41795 :         host->fd = fd;
     912       41795 :         host->ai = a;
     913       41795 :         host->freeai = freeai;
     914       41795 :         freeai = NULL;
     915             :         /* next version of stid */
     916       41795 :         host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1);
     917             : 
     918       41795 :         host->state = CONNECT;
     919             : 
     920       41795 :         switch (host->hi->proto) {
     921           0 :         case KRB5_KRBHST_HTTP :
     922           0 :             host->fun = &http_fun;
     923           0 :             break;
     924       38203 :         case KRB5_KRBHST_TCP :
     925       38203 :             host->fun = &tcp_fun;
     926       38203 :             break;
     927        3592 :         case KRB5_KRBHST_UDP :
     928        3592 :             host->fun = &udp_fun;
     929        3592 :             break;
     930           0 :         default:
     931           0 :             heim_abort("undefined http transport protocol: %d", (int)host->hi->proto);
     932             :         }
     933             : 
     934       41795 :         host->tries = host->fun->ntries;
     935             : 
     936             :         /*
     937             :          * Connect directly next host, wait a host_timeout for each next address.
     938             :          * We try host_connect() here, checking the return code because as we do
     939             :          * non-blocking connects, any error here indicates that the address is just
     940             :          * offline.  That is, it's something like "No route to host" which is not
     941             :          * worth retrying.  And so, we fail directly and immediately to the next
     942             :          * address for this host without enqueueing the address for retries.
     943             :          */
     944       41795 :         if (submitted_host == 0) {
     945       41779 :             host_connect(context, ctx, host);
     946       41779 :             if (host->state == DEAD)
     947           0 :                 continue;
     948             :         } else {
     949          16 :             debug_host(context, 5, host,
     950             :                        "Queuing host in future (in %ds), its the %lu address on the same name",
     951          16 :                        (int)(context->host_timeout * submitted_host), submitted_host + 1);
     952          16 :             host->timeout = time(NULL) + (submitted_host * context->host_timeout);
     953             :         }
     954             : 
     955       41795 :         heim_array_append_value(ctx->hosts, host);
     956       41795 :         heim_release(host);
     957       41795 :         submitted_host++;
     958             :     }
     959             : 
     960       41779 :     if (freeai)
     961        1658 :         freeai = NULL;
     962             : 
     963       41779 :     if (submitted_host == 0)
     964           0 :         return KRB5_KDC_UNREACH;
     965             : 
     966       40121 :     return 0;
     967             : }
     968             : 
     969             : struct wait_ctx {
     970             :     krb5_context context;
     971             :     krb5_sendto_ctx ctx;
     972             :     fd_set rfds;
     973             :     fd_set wfds;
     974             :     rk_socket_t max_fd;
     975             :     int got_reply;
     976             :     time_t timenow;
     977             : };
     978             : 
     979             : static void
     980       93090 : wait_setup(heim_object_t obj, void *iter_ctx, int *stop)
     981             : {
     982       93090 :     struct wait_ctx *wait_ctx = iter_ctx;
     983       93090 :     struct host *h = (struct host *)obj;
     984             : 
     985       93090 :     if (h->state == CONNECT) {
     986          34 :         if (h->timeout >= wait_ctx->timenow)
     987          34 :             return;
     988           0 :         host_connect(wait_ctx->context, wait_ctx->ctx, h);
     989             :     }
     990             : 
     991             :     /* skip dead hosts */
     992       93056 :     if (h->state == DEAD)
     993           0 :         return;
     994             : 
     995             :     /* if host timed out, dec tries and (retry or kill host) */
     996       93056 :     if (h->timeout < wait_ctx->timenow) {
     997           0 :         heim_assert(h->tries != 0, "tries should not reach 0");
     998           0 :         h->tries--;
     999           0 :         if (h->tries == 0) {
    1000           0 :             host_dead(wait_ctx->context, h, "host timed out");
    1001           0 :             return;
    1002             :         } else {
    1003           0 :             debug_host(wait_ctx->context, 5, h, "retrying sending to");
    1004           0 :             host_next_timeout(wait_ctx->context, h);
    1005           0 :             host_connected(wait_ctx->context, wait_ctx->ctx, h);
    1006             :         }
    1007             :     }
    1008             :     
    1009             : #ifndef NO_LIMIT_FD_SETSIZE
    1010       93056 :     heim_assert(h->fd < FD_SETSIZE, "fd too large");
    1011             : #endif
    1012       93056 :     switch (h->state) {
    1013       51277 :     case WAITING_REPLY:
    1014       51277 :         FD_SET(h->fd, &wait_ctx->rfds);
    1015       51277 :         break;
    1016       41779 :     case CONNECTING:
    1017             :     case CONNECTED:
    1018       41779 :         FD_SET(h->fd, &wait_ctx->rfds);
    1019       41779 :         FD_SET(h->fd, &wait_ctx->wfds);
    1020       41779 :         break;
    1021           0 :     default:
    1022           0 :         debug_host(wait_ctx->context, 5, h, "invalid sendto host state");
    1023           0 :         heim_abort("invalid sendto host state");
    1024             :     }
    1025       93056 :     if (h->fd > wait_ctx->max_fd || wait_ctx->max_fd == rk_INVALID_SOCKET)
    1026       93056 :         wait_ctx->max_fd = h->fd;
    1027             : }
    1028             : 
    1029             : static int
    1030       93090 : wait_filter_dead(heim_object_t obj, void *ctx)
    1031             : {
    1032       93090 :     struct host *h = (struct host *)obj;
    1033       93090 :     return (int)((h->state == DEAD) ? true : false);
    1034             : }
    1035             : 
    1036             : static void
    1037           0 : wait_accelerate(heim_object_t obj, void *ctx, int *stop)
    1038             : {
    1039           0 :     struct host *h = (struct host *)obj;
    1040             : 
    1041           0 :     if (h->state == CONNECT && h->timeout > 0)
    1042           0 :         h->timeout--;
    1043           0 : }
    1044             : 
    1045             : static void
    1046       93074 : wait_process(heim_object_t obj, void *ctx, int *stop)
    1047             : {
    1048       93074 :     struct wait_ctx *wait_ctx = ctx;
    1049       93074 :     struct host *h = (struct host *)obj;
    1050        3614 :     int readable, writeable;
    1051       93074 :     heim_assert(h->state != DEAD, "dead host resurected");
    1052             : 
    1053             : #ifndef NO_LIMIT_FD_SETSIZE
    1054       93074 :     heim_assert(h->fd < FD_SETSIZE, "fd too large");
    1055             : #endif
    1056       93074 :     readable = FD_ISSET(h->fd, &wait_ctx->rfds);
    1057       93074 :     writeable = FD_ISSET(h->fd, &wait_ctx->wfds);
    1058             : 
    1059       93074 :     if (readable || writeable || h->state == CONNECT)
    1060       93074 :         wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable);
    1061             : 
    1062             :     /* if there is already a reply, just fall though the array */
    1063       93074 :     if (wait_ctx->got_reply)
    1064       41779 :         *stop = 1;
    1065       93074 : }
    1066             : 
    1067             : static krb5_error_code
    1068      133590 : wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx)
    1069             : {
    1070        5369 :     struct wait_ctx wait_ctx;
    1071        5369 :     struct timeval tv;
    1072        5369 :     int ret;
    1073             : 
    1074      133590 :     wait_ctx.context = context;
    1075      133590 :     wait_ctx.ctx = ctx;
    1076     2271030 :     FD_ZERO(&wait_ctx.rfds);
    1077     2271030 :     FD_ZERO(&wait_ctx.wfds);
    1078      133590 :     wait_ctx.max_fd = rk_INVALID_SOCKET;
    1079             : 
    1080             :     /* oh, we have a reply, it must be a plugin that got it for us */
    1081      133590 :     if (ctx->response.length) {
    1082       32554 :         *action = KRB5_SENDTO_FILTER;
    1083       32554 :         return 0;
    1084             :     }
    1085             : 
    1086      101036 :     wait_ctx.timenow = time(NULL);
    1087             : 
    1088      101036 :     heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup);
    1089      101036 :     heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead);
    1090             : 
    1091      101036 :     if (heim_array_get_length(ctx->hosts) == 0) {
    1092        7980 :         if (ctx->stateflags & KRBHST_COMPLETED) {
    1093        7980 :             _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
    1094             :                          "trying to pulling more hosts");
    1095        7980 :             *action = KRB5_SENDTO_FAILED;
    1096             :         } else {
    1097           0 :             _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
    1098             :                          "and no more hosts -> failure");
    1099           0 :             *action = KRB5_SENDTO_TIMEOUT;
    1100             :         }
    1101        7980 :         return 0;
    1102             :     }
    1103             : 
    1104       93056 :     if (wait_ctx.max_fd == rk_INVALID_SOCKET) {
    1105             :         /*
    1106             :          * If we don't find a host which can make progress, then
    1107             :          * we accelerate the process by moving all of the contestants
    1108             :          * up by 1s.
    1109             :          */
    1110           0 :         _krb5_debug(context, 5, "wait_response: moving the contestants forward");
    1111           0 :         heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_accelerate);
    1112           0 :         return 0;
    1113             :     }
    1114             : 
    1115       93056 :     tv.tv_sec = 1;
    1116       93056 :     tv.tv_usec = 0;
    1117             : 
    1118       93056 :     ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv);
    1119       93056 :     if (ret < 0)
    1120           0 :         return errno;
    1121       93056 :     if (ret == 0) {
    1122           0 :         *action = KRB5_SENDTO_TIMEOUT;
    1123           0 :         return 0;
    1124             :     }
    1125             : 
    1126       93056 :     wait_ctx.got_reply = 0;
    1127       93056 :     heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process);
    1128       93056 :     if (wait_ctx.got_reply)
    1129       41779 :         *action = KRB5_SENDTO_FILTER;
    1130             :     else
    1131       51277 :         *action = KRB5_SENDTO_CONTINUE;
    1132             : 
    1133       89442 :     return 0;
    1134             : }
    1135             : 
    1136             : static void
    1137       98786 : reset_context(krb5_context context, krb5_sendto_ctx ctx)
    1138             : {
    1139       98786 :     krb5_data_free(&ctx->response);
    1140       98786 :     heim_release(ctx->hosts);
    1141       98786 :     ctx->hosts = heim_array_create();
    1142       98786 :     ctx->stateflags = 0;
    1143       98786 : }
    1144             : 
    1145             : 
    1146             : /*
    1147             :  *
    1148             :  */
    1149             : 
    1150             : KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    1151       86355 : krb5_sendto_context(krb5_context context,
    1152             :                     krb5_sendto_ctx ctx,
    1153             :                     const krb5_data *send_data,
    1154             :                     krb5_const_realm realm,
    1155             :                     krb5_data *receive)
    1156             : {
    1157       86355 :     krb5_error_code ret = 0;
    1158       86355 :     krb5_krbhst_handle handle = NULL;
    1159        2828 :     struct timeval nrstart, nrstop, stop_time;
    1160       86355 :     int type, freectx = 0;
    1161        2828 :     int action;
    1162       86355 :     int numreset = 0;
    1163             : 
    1164       86355 :     krb5_data_zero(receive);
    1165             :     
    1166       86355 :     if (ctx == NULL) {
    1167           0 :         ret = krb5_sendto_ctx_alloc(context, &ctx);
    1168           0 :         if (ret)
    1169           0 :             goto out;
    1170           0 :         freectx = 1;
    1171             :     }
    1172             : 
    1173       86355 :     ctx->stid = (context->num_kdc_requests++) << 16;
    1174             : 
    1175       86355 :     memset(&ctx->stats, 0, sizeof(ctx->stats));
    1176       86355 :     gettimeofday(&ctx->stats.start_time, NULL);
    1177             : 
    1178       86355 :     type = ctx->type;
    1179       86355 :     if (type == 0) {
    1180       86355 :         if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
    1181           0 :             type = KRB5_KRBHST_ADMIN;
    1182             :         else
    1183       86355 :             type = KRB5_KRBHST_KDC;
    1184             :     }
    1185             : 
    1186       86355 :     ctx->send_data = send_data;
    1187             : 
    1188       86355 :     if ((int)send_data->length > context->large_msg_size)
    1189       48039 :         ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
    1190             : 
    1191             :     /* loop until we get back a appropriate response */
    1192             : 
    1193       86355 :     action = KRB5_SENDTO_INITIAL;
    1194             : 
    1195      319144 :     while (1) {
    1196       15608 :         krb5_krbhst_info *hi;
    1197             : 
    1198      405499 :         switch (action) {
    1199       86355 :         case KRB5_SENDTO_INITIAL:
    1200       89183 :             ret = realm_via_plugin(context, realm, context->kdc_timeout,
    1201       86355 :                                    send_data, &ctx->response);
    1202       86355 :             if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) {
    1203       16473 :                 action = KRB5_SENDTO_DONE;
    1204       16473 :                 break;
    1205             :             }
    1206       69882 :             action = KRB5_SENDTO_KRBHST;
    1207        3413 :             HEIM_FALLTHROUGH;
    1208       82313 :         case KRB5_SENDTO_KRBHST:
    1209       82313 :             if (ctx->krbhst == NULL) {
    1210       82313 :                 ret = krb5_krbhst_init_flags(context, realm, type,
    1211       78900 :                                              ctx->flags, &handle);
    1212       82313 :                 if (ret)
    1213       86355 :                     goto out;
    1214             : 
    1215       82313 :                 if (ctx->hostname) {
    1216           0 :                     ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname);
    1217           0 :                     if (ret)
    1218           0 :                         goto out;
    1219             :                 }
    1220       82313 :                 if (ctx->sitename) {
    1221           0 :                     ret = krb5_krbhst_set_sitename(context, handle, ctx->sitename);
    1222           0 :                     if (ret)
    1223           0 :                         goto out;
    1224             :                 }
    1225             :             } else {
    1226           0 :                 handle = heim_retain(ctx->krbhst);
    1227             :             }
    1228       82313 :             action = KRB5_SENDTO_TIMEOUT;
    1229        3413 :             HEIM_FALLTHROUGH;
    1230       82317 :         case KRB5_SENDTO_TIMEOUT:
    1231             : 
    1232             :             /*
    1233             :              * If we completed, just got to next step
    1234             :              */
    1235             : 
    1236       82317 :             if (ctx->stateflags & KRBHST_COMPLETED) {
    1237           0 :                 action = KRB5_SENDTO_CONTINUE;
    1238           0 :                 break;
    1239             :             }
    1240             : 
    1241             :             /*
    1242             :              * Pull out next host, if there is no more, close the
    1243             :              * handle and mark as completed.
    1244             :              *
    1245             :              * Collect time spent in krbhst (dns, plugin, etc)
    1246             :              */
    1247             : 
    1248             : 
    1249       82317 :             gettimeofday(&nrstart, NULL);
    1250             : 
    1251       82317 :             ret = krb5_krbhst_next(context, handle, &hi);
    1252             : 
    1253       82317 :             gettimeofday(&nrstop, NULL);
    1254       82317 :             timevalsub(&nrstop, &nrstart);
    1255       82317 :             timevaladd(&ctx->stats.krbhst, &nrstop);
    1256             : 
    1257       82317 :             action = KRB5_SENDTO_CONTINUE;
    1258       82317 :             if (ret == 0) {
    1259       74337 :                 _krb5_debug(context, 5, "submitting new requests to new host");
    1260       74337 :                 if (submit_request(context, ctx, hi) != 0)
    1261           4 :                     action = KRB5_SENDTO_TIMEOUT;
    1262             :             } else {
    1263        7980 :                 _krb5_debug(context, 5, "out of hosts, waiting for replies");
    1264        7980 :                 ctx->stateflags |= KRBHST_COMPLETED;
    1265             :             }
    1266             : 
    1267       78904 :             break;
    1268      133590 :         case KRB5_SENDTO_CONTINUE:
    1269             : 
    1270      133590 :             ret = wait_response(context, &action, ctx);
    1271      133590 :             if (ret)
    1272           0 :                 goto out;
    1273             : 
    1274      128221 :             break;
    1275       12431 :         case KRB5_SENDTO_RESET:
    1276             :             /* start over */
    1277       12431 :             _krb5_debug(context, 5,
    1278             :                         "krb5_sendto trying over again (reset): %d",
    1279             :                         numreset);
    1280       12431 :             reset_context(context, ctx);
    1281       12431 :             if (handle) {
    1282       12431 :                 krb5_krbhst_free(context, handle);
    1283       12431 :                 handle = NULL;
    1284             :             }
    1285       12431 :             numreset++;
    1286       12431 :             if (numreset >= 3)
    1287           0 :                 action = KRB5_SENDTO_FAILED;
    1288             :             else
    1289       12431 :                 action = KRB5_SENDTO_KRBHST;
    1290             : 
    1291       11846 :             break;
    1292       74333 :         case KRB5_SENDTO_FILTER:
    1293             :             /* default to next state, the filter function might modify this */
    1294       74333 :             action = KRB5_SENDTO_DONE;
    1295             : 
    1296       74333 :             if (ctx->func) {
    1297       77746 :                 ret = (*ctx->func)(context, ctx, ctx->data,
    1298       74333 :                                    &ctx->response, &action);
    1299       74333 :                 if (ret)
    1300           0 :                     goto out;
    1301             : 
    1302             :                 /*
    1303             :                  * If we are not done, ask to continue/reset
    1304             :                  */
    1305       74333 :                 switch (action) {
    1306       59074 :                 case KRB5_SENDTO_DONE:
    1307       59074 :                     break;
    1308       12431 :                 case KRB5_SENDTO_RESET:
    1309             :                 case KRB5_SENDTO_CONTINUE:
    1310             :                     /* free response to clear it out so we don't loop */
    1311       12431 :                     krb5_data_free(&ctx->response);
    1312       12431 :                     break;
    1313           0 :                 default:
    1314           0 :                     ret = KRB5_KDC_UNREACH;
    1315           0 :                     krb5_set_error_message(context, ret,
    1316             :                                            "sendto filter funcation return unsupported state: %d", (int)action);
    1317           0 :                     goto out;
    1318             :                 }
    1319             :             }
    1320       70920 :             break;
    1321        7980 :         case KRB5_SENDTO_FAILED:
    1322        7980 :             ret = KRB5_KDC_UNREACH;
    1323        7980 :             goto out;
    1324       78375 :         case KRB5_SENDTO_DONE:
    1325       78375 :             ret = 0;
    1326       78375 :             goto out;
    1327           0 :         default:
    1328           0 :             heim_abort("invalid krb5_sendto_context state");
    1329             :         }
    1330             :     }
    1331             : 
    1332       86355 : out:
    1333       86355 :     gettimeofday(&stop_time, NULL);
    1334       86355 :     timevalsub(&stop_time, &ctx->stats.start_time);
    1335       86355 :     if (ret == 0 && ctx->response.length) {
    1336       77295 :         *receive = ctx->response;
    1337       77295 :         krb5_data_zero(&ctx->response);
    1338             :     } else {
    1339        9060 :         krb5_data_free(&ctx->response);
    1340        9060 :         krb5_clear_error_message (context);
    1341        9060 :         ret = KRB5_KDC_UNREACH;
    1342        9060 :         krb5_set_error_message(context, ret,
    1343        9060 :                                N_("unable to reach any KDC in realm %s", ""),
    1344             :                                realm);
    1345             :     }
    1346             : 
    1347       86355 :     _krb5_debug(context, 1,
    1348             :                 "%s %s done: %d hosts: %lu packets: %lu"
    1349             :                 " wc: %lld.%06lu nr: %lld.%06lu kh: %lld.%06lu tid: %08x",
    1350             :                 __func__, realm, ret,
    1351       83527 :                 ctx->stats.num_hosts, ctx->stats.sent_packets,
    1352       86355 :                 (long long)stop_time.tv_sec,
    1353       86355 :                 (unsigned long)stop_time.tv_usec,
    1354       86355 :                 (long long)ctx->stats.name_resolution.tv_sec,
    1355       86355 :                 (unsigned long)ctx->stats.name_resolution.tv_usec,
    1356       86355 :                 (long long)ctx->stats.krbhst.tv_sec,
    1357       86355 :                 (unsigned long)ctx->stats.krbhst.tv_usec, ctx->stid);
    1358             : 
    1359             : 
    1360       86355 :     if (freectx)
    1361           0 :         krb5_sendto_ctx_free(context, ctx);
    1362             :     else
    1363       86355 :         reset_context(context, ctx);
    1364             : 
    1365       86355 :     if (handle)
    1366       69882 :         krb5_krbhst_free(context, handle);
    1367             : 
    1368       86355 :     return ret;
    1369             : }

Generated by: LCOV version 1.14