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 fix-15632 9995c5c2 Lines: 440 673 65.4 %
Date: 2024-04-13 12:30:31 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       72835 : kdccallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
      77             : {
      78       72835 :     const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
      79       72835 :     struct send_via_plugin_s *ctx = userctx;
      80             : 
      81       72835 :     if (service->send_to_kdc == NULL)
      82           0 :         return KRB5_PLUGIN_NO_HANDLE;
      83       72835 :     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       82600 : realmcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
      89             : {
      90       82600 :     const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
      91       82600 :     struct send_via_plugin_s *ctx = userctx;
      92             : 
      93       82600 :     if (service->send_to_realm == NULL)
      94           0 :         return KRB5_PLUGIN_NO_HANDLE;
      95       82600 :     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       73361 : 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       73361 :     userctx.realm = NULL;
     120       73361 :     userctx.hi = hi;
     121       73361 :     userctx.timeout = timeout;
     122       73361 :     userctx.send_data = send_data;
     123       73361 :     userctx.receive = receive;
     124             : 
     125       73361 :     return _krb5_plugin_run_f(context, &send_to_kdc_plugin_data, 0,
     126             :                               &userctx, kdccallback);
     127             : }
     128             : 
     129             : static krb5_error_code
     130       83011 : 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       83011 :     userctx.realm = realm;
     139       83011 :     userctx.hi = NULL;
     140       83011 :     userctx.timeout = timeout;
     141       83011 :     userctx.send_data = send_data;
     142       83011 :     userctx.receive = receive;
     143             : 
     144       83011 :     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       68614 : dealloc_sendto_ctx(void *ptr)
     181             : {
     182       68614 :     krb5_sendto_ctx ctx = (krb5_sendto_ctx)ptr;
     183       68614 :     if (ctx->hostname)
     184           0 :         free(ctx->hostname);
     185       68614 :     if (ctx->sitename)
     186           0 :         free(ctx->sitename);
     187       68614 :     heim_release(ctx->hosts);
     188       68614 :     heim_release(ctx->krbhst);
     189       68614 : }
     190             : 
     191             : KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
     192       68614 : krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx)
     193             : {
     194       68614 :     *ctx = heim_alloc(sizeof(**ctx), "sendto-context", dealloc_sendto_ctx);
     195       68614 :     if (*ctx == NULL)
     196           0 :         return krb5_enomem(context);
     197       68614 :     (*ctx)->hosts = heim_array_create();
     198             : 
     199       68614 :     return 0;
     200             : }
     201             : 
     202             : KRB5_LIB_FUNCTION void KRB5_LIB_CALL
     203       12179 : krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags)
     204             : {
     205       12179 :     ctx->flags |= flags;
     206       12179 : }
     207             : 
     208             : KRB5_LIB_FUNCTION int KRB5_LIB_CALL
     209       12179 : krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)
     210             : {
     211       12179 :     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       68614 : krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx,
     222             :                          krb5_sendto_ctx_func func,
     223             :                          void *data)
     224             : {
     225       68614 :     ctx->func = func;
     226       68614 :     ctx->data = data;
     227       68614 : }
     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       68614 : krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx)
     283             : {
     284       68614 :     heim_release(ctx);
     285       68614 : }
     286             : 
     287             : KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
     288       73357 : _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       73357 :     if(krb5_rd_error(context, reply, &error))
     295       46153 :         return 0;
     296             : 
     297       24961 :     ret = krb5_error_from_rd_error(context, &error, NULL);
     298       24961 :     krb5_free_error_contents(context, &error);
     299             : 
     300       24961 :     switch(ret) {
     301       12179 :     case KRB5KRB_ERR_RESPONSE_TOO_BIG: {
     302       12179 :         if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG)
     303           0 :             break;
     304       12179 :         krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG);
     305       12179 :         *action = KRB5_SENDTO_RESET;
     306       12179 :         break;
     307             :     }
     308           0 :     case KRB5KDC_ERR_SVC_UNAVAILABLE:
     309           0 :         *action = KRB5_SENDTO_RESET;
     310           0 :         break;
     311             :     }
     312       23791 :     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 *ai;
     332             :     rk_socket_t fd;
     333             :     const struct host_fun *fun;
     334             :     unsigned int tries;
     335             :     time_t timeout;
     336             :     krb5_data data;
     337             :     unsigned int tid;
     338             : };
     339             : 
     340             : static void
     341             : debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
     342             :         __attribute__ ((__format__ (__printf__, 4, 5)));
     343             : 
     344             : static void
     345      174954 : debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
     346             : {
     347      174954 :     const char *proto = "unknown";
     348        6924 :     const char *state;
     349        6924 :     char name[NI_MAXHOST], port[NI_MAXSERV];
     350      174954 :     char *text = NULL;
     351        6924 :     va_list ap;
     352        6924 :     int ret;
     353             : 
     354      174954 :     if (!_krb5_have_debug(context, 5))
     355      174938 :         return;
     356             : 
     357          16 :     va_start(ap, fmt);
     358          16 :     ret = vasprintf(&text, fmt, ap);
     359          16 :     va_end(ap);
     360          16 :     if (ret == -1 || text == NULL)
     361           0 :         return;
     362             : 
     363          16 :     if (host->hi->proto == KRB5_KRBHST_HTTP)
     364           0 :         proto = "http";
     365          16 :     else if (host->hi->proto == KRB5_KRBHST_TCP)
     366           4 :         proto = "tcp";
     367          12 :     else if (host->hi->proto == KRB5_KRBHST_UDP)
     368          12 :         proto = "udp";
     369             : 
     370          16 :     if (getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen,
     371             :                     name, sizeof(name), port, sizeof(port), NI_NUMERICHOST) != 0)
     372           0 :         name[0] = '\0';
     373             : 
     374          16 :     switch (host->state) {
     375           4 :     case CONNECT:       state = "CONNECT";            break;
     376           0 :     case CONNECTING:    state = "CONNECTING";         break;
     377           4 :     case CONNECTED:     state = "CONNECTED";          break;
     378           8 :     case WAITING_REPLY: state = "WAITING_REPLY";      break;
     379           0 :     case DEAD:          state = "DEAD";                       break;
     380           0 :     default:            state = "unknown";            break;
     381             :     }
     382             : 
     383          16 :     _krb5_debug(context, level, "%s: %s %s:%s (%s) state=%s tid: %08x", text,
     384          16 :                 proto, name, port, host->hi->hostname, state, host->tid);
     385          16 :     free(text);
     386             : }
     387             : 
     388             : 
     389             : static void HEIM_CALLCONV
     390       41582 : deallocate_host(void *ptr)
     391             : {
     392       41582 :     struct host *host = ptr;
     393       41582 :     if (!rk_IS_BAD_SOCKET(host->fd))
     394       41582 :         rk_closesocket(host->fd);
     395       41582 :     krb5_data_free(&host->data);
     396       41582 :     host->ai = NULL;
     397       41582 : }
     398             : 
     399             : static void
     400           0 : host_dead(krb5_context context, struct host *host, const char *msg)
     401             : {
     402           0 :     debug_host(context, 5, host, "%s", msg);
     403           0 :     rk_closesocket(host->fd);
     404           0 :     host->fd = rk_INVALID_SOCKET;
     405           0 :     host->state = DEAD;
     406           0 : }
     407             : 
     408             : static krb5_error_code
     409       37977 : send_stream(krb5_context context, struct host *host)
     410             : {
     411        1658 :     ssize_t len;
     412             : 
     413       37977 :     len = krb5_net_write(context, &host->fd, host->data.data, host->data.length);
     414             : 
     415       37977 :     if (len < 0)
     416           0 :         return errno;
     417       37977 :     else if (len < host->data.length) {
     418           0 :         host->data.length -= len;
     419           0 :         memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len);
     420           0 :         return -1;
     421             :     } else {
     422       37977 :         krb5_data_free(&host->data);
     423       37977 :         return 0;
     424             :     }
     425             : }
     426             : 
     427             : static krb5_error_code
     428       46654 : recv_stream(krb5_context context, struct host *host)
     429             : {
     430        1950 :     krb5_error_code ret;
     431        1950 :     size_t oldlen;
     432        1950 :     ssize_t sret;
     433        1950 :     int nbytes;
     434             : 
     435       46654 :     if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
     436           0 :         return HEIM_NET_CONN_REFUSED;
     437             : 
     438       46654 :     if (context->max_msg_size - host->data.length < nbytes) {
     439           0 :         krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
     440           0 :                                N_("TCP message from KDC too large %d", ""),
     441           0 :                                (int)(host->data.length + nbytes));
     442           0 :         return KRB5KRB_ERR_FIELD_TOOLONG;
     443             :     }
     444             : 
     445       46654 :     oldlen = host->data.length;
     446             : 
     447       46654 :     ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */);
     448       46654 :     if (ret)
     449           0 :         return ret;
     450             : 
     451       46654 :     sret = krb5_net_read(context, &host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes);
     452       46654 :     if (sret <= 0) {
     453           0 :         ret = errno;
     454           0 :         return ret;
     455             :     }
     456       46654 :     host->data.length = oldlen + sret;
     457             :     /* zero terminate for http transport */
     458       46654 :     ((uint8_t *)host->data.data)[host->data.length] = '\0';
     459             : 
     460       46654 :     return 0;
     461             : }
     462             : 
     463             : /*
     464             :  *
     465             :  */
     466             : 
     467             : static void
     468       41565 : host_next_timeout(krb5_context context, struct host *host)
     469             : {
     470       41565 :     host->timeout = context->kdc_timeout / host->fun->ntries;
     471       41565 :     if (host->timeout == 0)
     472           0 :         host->timeout = 1;
     473             : 
     474       41565 :     host->timeout += time(NULL);
     475       41565 : }
     476             : 
     477             : /*
     478             :  * connected host
     479             :  */
     480             : 
     481             : static void
     482       41565 : host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
     483             : {
     484        1658 :     krb5_error_code ret;
     485             : 
     486       41565 :     host->state = CONNECTED; 
     487             :     /*
     488             :      * Now prepare data to send to host
     489             :      */
     490       41565 :     if (ctx->prexmit_func) {
     491           0 :         krb5_data data;
     492             :             
     493           0 :         krb5_data_zero(&data);
     494             : 
     495           0 :         ret = ctx->prexmit_func(context, host->hi->proto,
     496             :                                 ctx->prexmit_ctx, host->fd, &data);
     497           0 :         if (ret == 0) {
     498           0 :             if (data.length == 0) {
     499           0 :                 host_dead(context, host, "prexmit function didn't send data");
     500           0 :                 return;
     501             :             }
     502           0 :             ret = host->fun->prepare(context, host, &data);
     503           0 :             krb5_data_free(&data);
     504             :         }
     505             :         
     506             :     } else {
     507       41565 :         ret = host->fun->prepare(context, host, ctx->send_data);
     508             :     }
     509       41565 :     if (ret)
     510           0 :         debug_host(context, 5, host, "failed to prexmit/prepare");
     511             : }
     512             : 
     513             : /*
     514             :  * connect host
     515             :  */
     516             : 
     517             : static void
     518       41565 : host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
     519             : {
     520       41565 :     krb5_krbhst_info *hi = host->hi;
     521       41565 :     struct addrinfo *ai = host->ai;
     522             : 
     523       41565 :     debug_host(context, 5, host, "connecting to host");
     524             : 
     525       41565 :     if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) {
     526             : #ifdef HAVE_WINSOCK
     527             :         if (WSAGetLastError() == WSAEWOULDBLOCK)
     528             :             errno = EINPROGRESS;
     529             : #endif /* HAVE_WINSOCK */
     530           0 :         if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) {
     531           0 :             debug_host(context, 5, host, "connecting to %d", host->fd);
     532           0 :             host->state = CONNECTING;
     533             :         } else {
     534           0 :             host_dead(context, host, "failed to connect");
     535             :         }
     536             :     } else {
     537       41565 :         host_connected(context, ctx, host);
     538             :     }
     539             : 
     540       41565 :     host_next_timeout(context, host);
     541       41565 : }
     542             : 
     543             : /*
     544             :  * HTTP transport
     545             :  */
     546             : 
     547             : static krb5_error_code
     548           0 : prepare_http(krb5_context context, struct host *host, const krb5_data *data)
     549             : {
     550           0 :     char *str = NULL, *request = NULL;
     551           0 :     krb5_error_code ret;
     552           0 :     int len;
     553             : 
     554           0 :     heim_assert(host->data.length == 0, "prepare_http called twice");
     555             : 
     556           0 :     len = rk_base64_encode(data->data, data->length, &str);
     557           0 :     if(len < 0)
     558           0 :         return ENOMEM;
     559             : 
     560           0 :     if (context->http_proxy)
     561           0 :         ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str);
     562             :     else
     563           0 :         ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str);
     564           0 :     free(str);
     565           0 :     if(ret < 0 || request == NULL)
     566           0 :         return ENOMEM;
     567             :     
     568           0 :     host->data.data = request;
     569           0 :     host->data.length = strlen(request);
     570             : 
     571           0 :     return 0;
     572             : }
     573             : 
     574             : static krb5_error_code
     575           0 : recv_http(krb5_context context, struct host *host, krb5_data *data)
     576             : {
     577           0 :     krb5_error_code ret;
     578           0 :     unsigned long rep_len;
     579           0 :     size_t len;
     580           0 :     char *p;
     581             : 
     582             :     /*
     583             :      * recv_stream returns a NUL terminated stream
     584             :      */
     585             : 
     586           0 :     ret = recv_stream(context, host);
     587           0 :     if (ret)
     588           0 :         return ret;
     589             : 
     590           0 :     p = strstr(host->data.data, "\r\n\r\n");
     591           0 :     if (p == NULL)
     592           0 :         return -1;
     593           0 :     p += 4;
     594             : 
     595           0 :     len = host->data.length - (p - (char *)host->data.data);
     596           0 :     if (len < 4)
     597           0 :         return -1;
     598             : 
     599           0 :     _krb5_get_int(p, &rep_len, 4);
     600           0 :     if (len < rep_len)
     601           0 :         return -1;
     602             : 
     603           0 :     p += 4;
     604             : 
     605           0 :     memmove(host->data.data, p, rep_len);
     606           0 :     host->data.length = rep_len;
     607             : 
     608           0 :     *data = host->data;
     609           0 :     krb5_data_zero(&host->data);
     610             : 
     611           0 :     return 0;
     612             : }
     613             : 
     614             : /*
     615             :  * TCP transport
     616             :  */
     617             : 
     618             : static krb5_error_code
     619       37977 : prepare_tcp(krb5_context context, struct host *host, const krb5_data *data)
     620             : {
     621        1658 :     krb5_error_code ret;
     622        1658 :     krb5_storage *sp;
     623             : 
     624       37977 :     heim_assert(host->data.length == 0, "prepare_tcp called twice");
     625             : 
     626       37977 :     sp = krb5_storage_emem();
     627       37977 :     if (sp == NULL)
     628           0 :         return ENOMEM;
     629             :     
     630       37977 :     ret = krb5_store_data(sp, *data);
     631       37977 :     if (ret) {
     632           0 :         krb5_storage_free(sp);
     633           0 :         return ret;
     634             :     }
     635       37977 :     ret = krb5_storage_to_data(sp, &host->data);
     636       37977 :     krb5_storage_free(sp);
     637             : 
     638       37977 :     return ret;
     639             : }
     640             : 
     641             : static krb5_error_code
     642       46654 : recv_tcp(krb5_context context, struct host *host, krb5_data *data)
     643             : {
     644        1950 :     krb5_error_code ret;
     645        1950 :     unsigned long pktlen;
     646             : 
     647       46654 :     ret = recv_stream(context, host);
     648       46654 :     if (ret)
     649           0 :         return ret;
     650             : 
     651       46654 :     if (host->data.length < 4)
     652           0 :         return -1;
     653             : 
     654       46654 :     _krb5_get_int(host->data.data, &pktlen, 4);
     655             :     
     656       46654 :     if (pktlen > host->data.length - 4)
     657        8385 :         return -1;
     658             : 
     659       37977 :     memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4);
     660       37977 :     host->data.length -= 4;
     661             : 
     662       37977 :     *data = host->data;
     663       37977 :     krb5_data_zero(&host->data);
     664             :     
     665       37977 :     return 0;
     666             : }
     667             : 
     668             : /*
     669             :  * UDP transport
     670             :  */
     671             : 
     672             : static krb5_error_code
     673        3588 : prepare_udp(krb5_context context, struct host *host, const krb5_data *data)
     674             : {
     675        3588 :     return krb5_data_copy(&host->data, data->data, data->length);
     676             : }
     677             : 
     678             : static krb5_error_code
     679        3588 : send_udp(krb5_context context, struct host *host)
     680             : {
     681        3588 :     if (send(host->fd, host->data.data, host->data.length, 0) < 0)
     682           0 :         return errno;
     683        3588 :     return 0;
     684             : }
     685             : 
     686             : static krb5_error_code
     687        3588 : recv_udp(krb5_context context, struct host *host, krb5_data *data)
     688             : {
     689           0 :     krb5_error_code ret;
     690           0 :     int nbytes;
     691             : 
     692             : 
     693        3588 :     if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
     694           0 :         return HEIM_NET_CONN_REFUSED;
     695             : 
     696        3588 :     if (context->max_msg_size < nbytes) {
     697           0 :         krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
     698           0 :                                N_("UDP message from KDC too large %d", ""),
     699             :                                (int)nbytes);
     700           0 :         return KRB5KRB_ERR_FIELD_TOOLONG;
     701             :     }
     702             : 
     703        3588 :     ret = krb5_data_alloc(data, nbytes);
     704        3588 :     if (ret)
     705           0 :         return ret;
     706             : 
     707        3588 :     ret = recv(host->fd, data->data, data->length, 0);
     708        3588 :     if (ret < 0) {
     709           0 :         ret = errno;
     710           0 :         krb5_data_free(data);
     711           0 :         return ret;
     712             :     }
     713        3588 :     data->length = ret;
     714             : 
     715        3588 :     return 0;
     716             : }
     717             : 
     718             : static const struct host_fun http_fun = {
     719             :     prepare_http,
     720             :     send_stream,
     721             :     recv_http,
     722             :     1
     723             : };
     724             : static const struct host_fun tcp_fun = {
     725             :     prepare_tcp,
     726             :     send_stream,
     727             :     recv_tcp,
     728             :     1
     729             : };
     730             : static const struct host_fun udp_fun = {
     731             :     prepare_udp,
     732             :     send_udp,
     733             :     recv_udp,
     734             :     3
     735             : };
     736             : 
     737             : 
     738             : /*
     739             :  * Host state machine
     740             :  */
     741             : 
     742             : static int
     743       91825 : eval_host_state(krb5_context context,
     744             :                 krb5_sendto_ctx ctx,
     745             :                 struct host *host,
     746             :                 int readable, int writeable)
     747             : {
     748        3608 :     krb5_error_code ret;
     749             : 
     750       91825 :     if (host->state == CONNECT) {
     751             :         /* check if its this host time to connect */
     752          18 :         if (host->timeout < time(NULL))
     753           0 :             host_connect(context, ctx, host);
     754          18 :         return 0;
     755             :     }
     756             : 
     757       91807 :     if (host->state == CONNECTING && writeable)
     758           0 :         host_connected(context, ctx, host);
     759             : 
     760       91807 :     if (readable) {
     761             : 
     762       50242 :         debug_host(context, 5, host, "reading packet");
     763             : 
     764       50242 :         ret = host->fun->recv_fn(context, host, &ctx->response);
     765       50242 :         if (ret == -1) {
     766             :             /* not done yet */
     767       41565 :         } else if (ret == 0) {
     768             :             /* if recv_foo function returns 0, we have a complete reply */
     769       41565 :             debug_host(context, 5, host, "host completed");
     770       41565 :             return 1;
     771             :         } else {
     772           0 :             host_dead(context, host, "host disconnected");
     773             :         }
     774             :     }
     775             : 
     776             :     /* check if there is anything to send, state might DEAD after read */
     777       50242 :     if (writeable && host->state == CONNECTED) {
     778             : 
     779       41565 :         ctx->stats.sent_packets++;
     780             : 
     781       41565 :         debug_host(context, 5, host, "writing packet");
     782             : 
     783       41565 :         ret = host->fun->send_fn(context, host);
     784       41565 :         if (ret == -1) {
     785             :             /* not done yet */
     786       41565 :         } else if (ret) {
     787           0 :             host_dead(context, host, "host dead, write failed");
     788             :         } else
     789       41565 :             host->state = WAITING_REPLY;
     790             :     }
     791             : 
     792       48292 :     return 0;
     793             : }
     794             : 
     795             : /*
     796             :  *
     797             :  */
     798             : 
     799             : static krb5_error_code
     800       73361 : submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)
     801             : {
     802       73361 :     unsigned long submitted_host = 0;
     803       73361 :     krb5_boolean freeai = FALSE;
     804        3413 :     struct timeval nrstart, nrstop;
     805        3413 :     krb5_error_code ret;
     806       73361 :     struct addrinfo *ai = NULL, *a;
     807        3413 :     struct host *host;
     808             : 
     809       73361 :     ret = kdc_via_plugin(context, hi, context->kdc_timeout,
     810             :                          ctx->send_data, &ctx->response);
     811       73361 :     if (ret == 0) {
     812       30037 :         return 0;
     813       41569 :     } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
     814           4 :         _krb5_debug(context, 5, "send via plugin failed %s: %d",
     815           4 :                     hi->hostname, ret);
     816           4 :         return ret;
     817             :     }
     818             : 
     819             :     /*
     820             :      * If we have a proxy, let use the address of the proxy instead of
     821             :      * the KDC and let the proxy deal with the resolving of the KDC.
     822             :      */
     823             : 
     824       41565 :     gettimeofday(&nrstart, NULL);
     825             : 
     826       41565 :     if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
     827           0 :         char *proxy2 = strdup(context->http_proxy);
     828           0 :         char *el, *proxy  = proxy2;
     829           0 :         struct addrinfo hints;
     830           0 :         char portstr[NI_MAXSERV];
     831           0 :         unsigned short nport;
     832             :         
     833           0 :         if (proxy == NULL)
     834           0 :             return ENOMEM;
     835           0 :         if (strncmp(proxy, "http://", 7) == 0)
     836           0 :             proxy += 7;
     837             :         
     838             :         /* check for url terminating slash */
     839           0 :         el = strchr(proxy, '/');
     840           0 :         if (el != NULL)
     841           0 :             *el = '\0';
     842             : 
     843             :         /* check for port in hostname, used below as port */
     844           0 :         el = strchr(proxy, ':');
     845           0 :         if(el != NULL)
     846           0 :             *el++ = '\0';
     847             : 
     848           0 :         memset(&hints, 0, sizeof(hints));
     849           0 :         hints.ai_family   = PF_UNSPEC;
     850           0 :         hints.ai_socktype = SOCK_STREAM;
     851             : 
     852             :         /* On some systems ntohs(foo(..., htons(...))) causes shadowing */
     853           0 :         nport = init_port(el, htons(80));
     854           0 :         snprintf(portstr, sizeof(portstr), "%d", ntohs(nport));
     855             : 
     856           0 :         ret = getaddrinfo(proxy, portstr, &hints, &ai);
     857           0 :         free(proxy2);
     858           0 :         if (ret)
     859           0 :             return krb5_eai_to_heim_errno(ret, errno);
     860             :         
     861           0 :         freeai = TRUE;
     862             : 
     863             :     } else {
     864       41565 :         ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
     865       41565 :         if (ret)
     866           0 :             return ret;
     867             :     }
     868             : 
     869             :     /* add up times */
     870       41565 :     gettimeofday(&nrstop, NULL);
     871       41565 :     timevalsub(&nrstop, &nrstart);
     872       41565 :     timevaladd(&ctx->stats.name_resolution, &nrstop);
     873             : 
     874       41565 :     ctx->stats.num_hosts++;
     875             : 
     876       83147 :     for (a = ai; a != NULL; a = a->ai_next) {
     877        1658 :         rk_socket_t fd;
     878             : 
     879       41582 :         fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
     880       41582 :         if (rk_IS_BAD_SOCKET(fd))
     881           0 :             continue;
     882       41582 :         rk_cloexec(fd);
     883             : 
     884             : #ifndef NO_LIMIT_FD_SETSIZE
     885       41582 :         if (fd >= FD_SETSIZE) {
     886           0 :             _krb5_debug(context, 0, "fd too large for select");
     887           0 :             rk_closesocket(fd);
     888           0 :             continue;
     889             :         }
     890             : #endif
     891       41582 :         socket_set_nonblocking(fd, 1);
     892             : 
     893       41582 :         host = heim_alloc(sizeof(*host), "sendto-host", deallocate_host);
     894       41582 :         if (host == NULL) {
     895           0 :             if (freeai)
     896           0 :                 freeaddrinfo(ai);
     897           0 :             rk_closesocket(fd);
     898           0 :             return ENOMEM;
     899             :         }
     900       41582 :         host->hi = hi;
     901       41582 :         host->fd = fd;
     902       41582 :         host->ai = a;
     903             :         /* next version of stid */
     904       41582 :         host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1);
     905             : 
     906       41582 :         host->state = CONNECT;
     907             : 
     908       41582 :         switch (host->hi->proto) {
     909           0 :         case KRB5_KRBHST_HTTP :
     910           0 :             host->fun = &http_fun;
     911           0 :             break;
     912       37988 :         case KRB5_KRBHST_TCP :
     913       37988 :             host->fun = &tcp_fun;
     914       37988 :             break;
     915        3594 :         case KRB5_KRBHST_UDP :
     916        3594 :             host->fun = &udp_fun;
     917        3594 :             break;
     918           0 :         default:
     919           0 :             heim_abort("undefined http transport protocol: %d", (int)host->hi->proto);
     920             :         }
     921             : 
     922       41582 :         host->tries = host->fun->ntries;
     923             : 
     924             :         /*
     925             :          * Connect directly next host, wait a host_timeout for each next address.
     926             :          * We try host_connect() here, checking the return code because as we do
     927             :          * non-blocking connects, any error here indicates that the address is just
     928             :          * offline.  That is, it's something like "No route to host" which is not
     929             :          * worth retrying.  And so, we fail directly and immediately to the next
     930             :          * address for this host without enqueueing the address for retries.
     931             :          */
     932       41582 :         if (submitted_host == 0) {
     933       41565 :             host_connect(context, ctx, host);
     934       41565 :             if (host->state == DEAD)
     935           0 :                 continue;
     936             :         } else {
     937          17 :             debug_host(context, 5, host,
     938             :                        "Queuing host in future (in %ds), its the %lu address on the same name",
     939          17 :                        (int)(context->host_timeout * submitted_host), submitted_host + 1);
     940          17 :             host->timeout = time(NULL) + (submitted_host * context->host_timeout);
     941             :         }
     942             : 
     943       41582 :         heim_array_append_value(ctx->hosts, host);
     944       41582 :         heim_release(host);
     945       41582 :         submitted_host++;
     946             :     }
     947             : 
     948       41565 :     if (freeai)
     949           0 :         freeaddrinfo(ai);
     950             : 
     951       41565 :     if (submitted_host == 0)
     952           0 :         return KRB5_KDC_UNREACH;
     953             : 
     954       39907 :     return 0;
     955             : }
     956             : 
     957             : struct wait_ctx {
     958             :     krb5_context context;
     959             :     krb5_sendto_ctx ctx;
     960             :     fd_set rfds;
     961             :     fd_set wfds;
     962             :     rk_socket_t max_fd;
     963             :     int got_reply;
     964             :     time_t timenow;
     965             : };
     966             : 
     967             : static void
     968       91842 : wait_setup(heim_object_t obj, void *iter_ctx, int *stop)
     969             : {
     970       91842 :     struct wait_ctx *wait_ctx = iter_ctx;
     971       91842 :     struct host *h = (struct host *)obj;
     972             : 
     973       91842 :     if (h->state == CONNECT) {
     974          35 :         if (h->timeout >= wait_ctx->timenow)
     975          35 :             return;
     976           0 :         host_connect(wait_ctx->context, wait_ctx->ctx, h);
     977             :     }
     978             : 
     979             :     /* skip dead hosts */
     980       91807 :     if (h->state == DEAD)
     981           0 :         return;
     982             : 
     983             :     /* if host timed out, dec tries and (retry or kill host) */
     984       91807 :     if (h->timeout < wait_ctx->timenow) {
     985           0 :         heim_assert(h->tries != 0, "tries should not reach 0");
     986           0 :         h->tries--;
     987           0 :         if (h->tries == 0) {
     988           0 :             host_dead(wait_ctx->context, h, "host timed out");
     989           0 :             return;
     990             :         } else {
     991           0 :             debug_host(wait_ctx->context, 5, h, "retrying sending to");
     992           0 :             host_next_timeout(wait_ctx->context, h);
     993           0 :             host_connected(wait_ctx->context, wait_ctx->ctx, h);
     994             :         }
     995             :     }
     996             :     
     997             : #ifndef NO_LIMIT_FD_SETSIZE
     998       91807 :     heim_assert(h->fd < FD_SETSIZE, "fd too large");
     999             : #endif
    1000       91807 :     switch (h->state) {
    1001       50242 :     case WAITING_REPLY:
    1002       50242 :         FD_SET(h->fd, &wait_ctx->rfds);
    1003       50242 :         break;
    1004       41565 :     case CONNECTING:
    1005             :     case CONNECTED:
    1006       41565 :         FD_SET(h->fd, &wait_ctx->rfds);
    1007       41565 :         FD_SET(h->fd, &wait_ctx->wfds);
    1008       41565 :         break;
    1009           0 :     default:
    1010           0 :         debug_host(wait_ctx->context, 5, h, "invalid sendto host state");
    1011           0 :         heim_abort("invalid sendto host state");
    1012             :     }
    1013       91807 :     if (h->fd > wait_ctx->max_fd || wait_ctx->max_fd == rk_INVALID_SOCKET)
    1014       91807 :         wait_ctx->max_fd = h->fd;
    1015             : }
    1016             : 
    1017             : static int
    1018       91842 : wait_filter_dead(heim_object_t obj, void *ctx)
    1019             : {
    1020       91842 :     struct host *h = (struct host *)obj;
    1021       91842 :     return (int)((h->state == DEAD) ? true : false);
    1022             : }
    1023             : 
    1024             : static void
    1025           0 : wait_accelerate(heim_object_t obj, void *ctx, int *stop)
    1026             : {
    1027           0 :     struct host *h = (struct host *)obj;
    1028             : 
    1029           0 :     if (h->state == CONNECT && h->timeout > 0)
    1030           0 :         h->timeout--;
    1031           0 : }
    1032             : 
    1033             : static void
    1034       91825 : wait_process(heim_object_t obj, void *ctx, int *stop)
    1035             : {
    1036       91825 :     struct wait_ctx *wait_ctx = ctx;
    1037       91825 :     struct host *h = (struct host *)obj;
    1038        3608 :     int readable, writeable;
    1039       91825 :     heim_assert(h->state != DEAD, "dead host resurected");
    1040             : 
    1041             : #ifndef NO_LIMIT_FD_SETSIZE
    1042       91825 :     heim_assert(h->fd < FD_SETSIZE, "fd too large");
    1043             : #endif
    1044       91825 :     readable = FD_ISSET(h->fd, &wait_ctx->rfds);
    1045       91825 :     writeable = FD_ISSET(h->fd, &wait_ctx->wfds);
    1046             : 
    1047       91825 :     if (readable || writeable || h->state == CONNECT)
    1048       91825 :         wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable);
    1049             : 
    1050             :     /* if there is already a reply, just fall though the array */
    1051       91825 :     if (wait_ctx->got_reply)
    1052       41565 :         *stop = 1;
    1053       91825 : }
    1054             : 
    1055             : static krb5_error_code
    1056      131143 : wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx)
    1057             : {
    1058        5363 :     struct wait_ctx wait_ctx;
    1059        5363 :     struct timeval tv;
    1060        5363 :     int ret;
    1061             : 
    1062      131143 :     wait_ctx.context = context;
    1063      131143 :     wait_ctx.ctx = ctx;
    1064     2229431 :     FD_ZERO(&wait_ctx.rfds);
    1065     2229431 :     FD_ZERO(&wait_ctx.wfds);
    1066      131143 :     wait_ctx.max_fd = rk_INVALID_SOCKET;
    1067             : 
    1068             :     /* oh, we have a reply, it must be a plugin that got it for us */
    1069      131143 :     if (ctx->response.length) {
    1070       31792 :         *action = KRB5_SENDTO_FILTER;
    1071       31792 :         return 0;
    1072             :     }
    1073             : 
    1074       99351 :     wait_ctx.timenow = time(NULL);
    1075             : 
    1076       99351 :     heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup);
    1077       99351 :     heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead);
    1078             : 
    1079       99351 :     if (heim_array_get_length(ctx->hosts) == 0) {
    1080        7544 :         if (ctx->stateflags & KRBHST_COMPLETED) {
    1081        7544 :             _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
    1082             :                          "trying to pulling more hosts");
    1083        7544 :             *action = KRB5_SENDTO_FAILED;
    1084             :         } else {
    1085           0 :             _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
    1086             :                          "and no more hosts -> failure");
    1087           0 :             *action = KRB5_SENDTO_TIMEOUT;
    1088             :         }
    1089        7544 :         return 0;
    1090             :     }
    1091             : 
    1092       91807 :     if (wait_ctx.max_fd == rk_INVALID_SOCKET) {
    1093             :         /*
    1094             :          * If we don't find a host which can make progress, then
    1095             :          * we accelerate the process by moving all of the contestants
    1096             :          * up by 1s.
    1097             :          */
    1098           0 :         _krb5_debug(context, 5, "wait_response: moving the contestants forward");
    1099           0 :         heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_accelerate);
    1100           0 :         return 0;
    1101             :     }
    1102             : 
    1103       91807 :     tv.tv_sec = 1;
    1104       91807 :     tv.tv_usec = 0;
    1105             : 
    1106       91807 :     ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv);
    1107       91807 :     if (ret < 0)
    1108           0 :         return errno;
    1109       91807 :     if (ret == 0) {
    1110           0 :         *action = KRB5_SENDTO_TIMEOUT;
    1111           0 :         return 0;
    1112             :     }
    1113             : 
    1114       91807 :     wait_ctx.got_reply = 0;
    1115       91807 :     heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process);
    1116       91807 :     if (wait_ctx.got_reply)
    1117       41565 :         *action = KRB5_SENDTO_FILTER;
    1118             :     else
    1119       50242 :         *action = KRB5_SENDTO_CONTINUE;
    1120             : 
    1121       88199 :     return 0;
    1122             : }
    1123             : 
    1124             : static void
    1125       95190 : reset_context(krb5_context context, krb5_sendto_ctx ctx)
    1126             : {
    1127       95190 :     krb5_data_free(&ctx->response);
    1128       95190 :     heim_release(ctx->hosts);
    1129       95190 :     ctx->hosts = heim_array_create();
    1130       95190 :     ctx->stateflags = 0;
    1131       95190 : }
    1132             : 
    1133             : 
    1134             : /*
    1135             :  *
    1136             :  */
    1137             : 
    1138             : KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    1139       83011 : krb5_sendto_context(krb5_context context,
    1140             :                     krb5_sendto_ctx ctx,
    1141             :                     const krb5_data *send_data,
    1142             :                     krb5_const_realm realm,
    1143             :                     krb5_data *receive)
    1144             : {
    1145       83011 :     krb5_error_code ret = 0;
    1146       83011 :     krb5_krbhst_handle handle = NULL;
    1147        2828 :     struct timeval nrstart, nrstop, stop_time;
    1148       83011 :     int type, freectx = 0;
    1149        2828 :     int action;
    1150       83011 :     int numreset = 0;
    1151             : 
    1152       83011 :     krb5_data_zero(receive);
    1153             :     
    1154       83011 :     if (ctx == NULL) {
    1155           0 :         ret = krb5_sendto_ctx_alloc(context, &ctx);
    1156           0 :         if (ret)
    1157           0 :             goto out;
    1158           0 :         freectx = 1;
    1159             :     }
    1160             : 
    1161       83011 :     ctx->stid = (context->num_kdc_requests++) << 16;
    1162             : 
    1163       83011 :     memset(&ctx->stats, 0, sizeof(ctx->stats));
    1164       83011 :     gettimeofday(&ctx->stats.start_time, NULL);
    1165             : 
    1166       83011 :     type = ctx->type;
    1167       83011 :     if (type == 0) {
    1168       83011 :         if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
    1169           0 :             type = KRB5_KRBHST_ADMIN;
    1170             :         else
    1171       83011 :             type = KRB5_KRBHST_KDC;
    1172             :     }
    1173             : 
    1174       83011 :     ctx->send_data = send_data;
    1175             : 
    1176       83011 :     if ((int)send_data->length > context->large_msg_size)
    1177       46454 :         ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
    1178             : 
    1179             :     /* loop until we get back a appropriate response */
    1180             : 
    1181       83011 :     action = KRB5_SENDTO_INITIAL;
    1182             : 
    1183      311873 :     while (1) {
    1184       15602 :         krb5_krbhst_info *hi;
    1185             : 
    1186      394884 :         switch (action) {
    1187       83011 :         case KRB5_SENDTO_INITIAL:
    1188       85839 :             ret = realm_via_plugin(context, realm, context->kdc_timeout,
    1189       83011 :                                    send_data, &ctx->response);
    1190       83011 :             if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) {
    1191       14289 :                 action = KRB5_SENDTO_DONE;
    1192       14289 :                 break;
    1193             :             }
    1194       68722 :             action = KRB5_SENDTO_KRBHST;
    1195        3413 :             HEIM_FALLTHROUGH;
    1196       80901 :         case KRB5_SENDTO_KRBHST:
    1197       80901 :             if (ctx->krbhst == NULL) {
    1198       80901 :                 ret = krb5_krbhst_init_flags(context, realm, type,
    1199       77488 :                                              ctx->flags, &handle);
    1200       80901 :                 if (ret)
    1201       83011 :                     goto out;
    1202             : 
    1203       80901 :                 if (ctx->hostname) {
    1204           0 :                     ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname);
    1205           0 :                     if (ret)
    1206           0 :                         goto out;
    1207             :                 }
    1208       80901 :                 if (ctx->sitename) {
    1209           0 :                     ret = krb5_krbhst_set_sitename(context, handle, ctx->sitename);
    1210           0 :                     if (ret)
    1211           0 :                         goto out;
    1212             :                 }
    1213             :             } else {
    1214           0 :                 handle = heim_retain(ctx->krbhst);
    1215             :             }
    1216       80901 :             action = KRB5_SENDTO_TIMEOUT;
    1217        3413 :             HEIM_FALLTHROUGH;
    1218       80905 :         case KRB5_SENDTO_TIMEOUT:
    1219             : 
    1220             :             /*
    1221             :              * If we completed, just got to next step
    1222             :              */
    1223             : 
    1224       80905 :             if (ctx->stateflags & KRBHST_COMPLETED) {
    1225           0 :                 action = KRB5_SENDTO_CONTINUE;
    1226           0 :                 break;
    1227             :             }
    1228             : 
    1229             :             /*
    1230             :              * Pull out next host, if there is no more, close the
    1231             :              * handle and mark as completed.
    1232             :              *
    1233             :              * Collect time spent in krbhst (dns, plugin, etc)
    1234             :              */
    1235             : 
    1236             : 
    1237       80905 :             gettimeofday(&nrstart, NULL);
    1238             : 
    1239       80905 :             ret = krb5_krbhst_next(context, handle, &hi);
    1240             : 
    1241       80905 :             gettimeofday(&nrstop, NULL);
    1242       80905 :             timevalsub(&nrstop, &nrstart);
    1243       80905 :             timevaladd(&ctx->stats.krbhst, &nrstop);
    1244             : 
    1245       80905 :             action = KRB5_SENDTO_CONTINUE;
    1246       80905 :             if (ret == 0) {
    1247       73361 :                 _krb5_debug(context, 5, "submitting new requests to new host");
    1248       73361 :                 if (submit_request(context, ctx, hi) != 0)
    1249           4 :                     action = KRB5_SENDTO_TIMEOUT;
    1250             :             } else {
    1251        7544 :                 _krb5_debug(context, 5, "out of hosts, waiting for replies");
    1252        7544 :                 ctx->stateflags |= KRBHST_COMPLETED;
    1253             :             }
    1254             : 
    1255       77492 :             break;
    1256      131143 :         case KRB5_SENDTO_CONTINUE:
    1257             : 
    1258      131143 :             ret = wait_response(context, &action, ctx);
    1259      131143 :             if (ret)
    1260           0 :                 goto out;
    1261             : 
    1262      125780 :             break;
    1263       12179 :         case KRB5_SENDTO_RESET:
    1264             :             /* start over */
    1265       12179 :             _krb5_debug(context, 5,
    1266             :                         "krb5_sendto trying over again (reset): %d",
    1267             :                         numreset);
    1268       12179 :             reset_context(context, ctx);
    1269       12179 :             if (handle) {
    1270       12179 :                 krb5_krbhst_free(context, handle);
    1271       12179 :                 handle = NULL;
    1272             :             }
    1273       12179 :             numreset++;
    1274       12179 :             if (numreset >= 3)
    1275           0 :                 action = KRB5_SENDTO_FAILED;
    1276             :             else
    1277       12179 :                 action = KRB5_SENDTO_KRBHST;
    1278             : 
    1279       11594 :             break;
    1280       73357 :         case KRB5_SENDTO_FILTER:
    1281             :             /* default to next state, the filter function might modify this */
    1282       73357 :             action = KRB5_SENDTO_DONE;
    1283             : 
    1284       73357 :             if (ctx->func) {
    1285       76770 :                 ret = (*ctx->func)(context, ctx, ctx->data,
    1286       73357 :                                    &ctx->response, &action);
    1287       73357 :                 if (ret)
    1288           0 :                     goto out;
    1289             : 
    1290             :                 /*
    1291             :                  * If we are not done, ask to continue/reset
    1292             :                  */
    1293       73357 :                 switch (action) {
    1294       58350 :                 case KRB5_SENDTO_DONE:
    1295       58350 :                     break;
    1296       12179 :                 case KRB5_SENDTO_RESET:
    1297             :                 case KRB5_SENDTO_CONTINUE:
    1298             :                     /* free response to clear it out so we don't loop */
    1299       12179 :                     krb5_data_free(&ctx->response);
    1300       12179 :                     break;
    1301           0 :                 default:
    1302           0 :                     ret = KRB5_KDC_UNREACH;
    1303           0 :                     krb5_set_error_message(context, ret,
    1304             :                                            "sendto filter funcation return unsupported state: %d", (int)action);
    1305           0 :                     goto out;
    1306             :                 }
    1307             :             }
    1308       69944 :             break;
    1309        7544 :         case KRB5_SENDTO_FAILED:
    1310        7544 :             ret = KRB5_KDC_UNREACH;
    1311        7544 :             goto out;
    1312       75467 :         case KRB5_SENDTO_DONE:
    1313       75467 :             ret = 0;
    1314       75467 :             goto out;
    1315           0 :         default:
    1316           0 :             heim_abort("invalid krb5_sendto_context state");
    1317             :         }
    1318             :     }
    1319             : 
    1320       83011 : out:
    1321       83011 :     gettimeofday(&stop_time, NULL);
    1322       83011 :     timevalsub(&stop_time, &ctx->stats.start_time);
    1323       83011 :     if (ret == 0 && ctx->response.length) {
    1324       74927 :         *receive = ctx->response;
    1325       74927 :         krb5_data_zero(&ctx->response);
    1326             :     } else {
    1327        8084 :         krb5_data_free(&ctx->response);
    1328        8084 :         krb5_clear_error_message (context);
    1329        8084 :         ret = KRB5_KDC_UNREACH;
    1330        8084 :         krb5_set_error_message(context, ret,
    1331        8084 :                                N_("unable to reach any KDC in realm %s", ""),
    1332             :                                realm);
    1333             :     }
    1334             : 
    1335       83011 :     _krb5_debug(context, 1,
    1336             :                 "%s %s done: %d hosts: %lu packets: %lu"
    1337             :                 " wc: %lld.%06lu nr: %lld.%06lu kh: %lld.%06lu tid: %08x",
    1338             :                 __func__, realm, ret,
    1339       80183 :                 ctx->stats.num_hosts, ctx->stats.sent_packets,
    1340       83011 :                 (long long)stop_time.tv_sec,
    1341       83011 :                 (unsigned long)stop_time.tv_usec,
    1342       83011 :                 (long long)ctx->stats.name_resolution.tv_sec,
    1343       83011 :                 (unsigned long)ctx->stats.name_resolution.tv_usec,
    1344       83011 :                 (long long)ctx->stats.krbhst.tv_sec,
    1345       83011 :                 (unsigned long)ctx->stats.krbhst.tv_usec, ctx->stid);
    1346             : 
    1347             : 
    1348       83011 :     if (freectx)
    1349           0 :         krb5_sendto_ctx_free(context, ctx);
    1350             :     else
    1351       83011 :         reset_context(context, ctx);
    1352             : 
    1353       83011 :     if (handle)
    1354       68722 :         krb5_krbhst_free(context, handle);
    1355             : 
    1356       83011 :     return ret;
    1357             : }

Generated by: LCOV version 1.14