1171802Sdelphij/*	$NetBSD: send_to_kdc.c,v 1.9 2023/06/19 21:41:44 christos Exp $	*/
2170808Sdelphij
3182739Sdelphij/*
4171802Sdelphij * Copyright (c) 1997 - 2002 Kungliga Tekniska H��gskolan
5170808Sdelphij * (Royal Institute of Technology, Stockholm, Sweden).
6170808Sdelphij * All rights reserved.
7170808Sdelphij *
8170808Sdelphij * Portions Copyright (c) 2010 - 2013 Apple Inc. All rights reserved.
9170808Sdelphij *
10170808Sdelphij * Redistribution and use in source and binary forms, with or without
11170808Sdelphij * modification, are permitted provided that the following conditions
12170808Sdelphij * are met:
13170808Sdelphij *
14170808Sdelphij * 1. Redistributions of source code must retain the above copyright
15170808Sdelphij *    notice, this list of conditions and the following disclaimer.
16170808Sdelphij *
17170808Sdelphij * 2. Redistributions in binary form must reproduce the above copyright
18170808Sdelphij *    notice, this list of conditions and the following disclaimer in the
19170808Sdelphij *    documentation and/or other materials provided with the distribution.
20170808Sdelphij *
21170808Sdelphij * 3. Neither the name of the Institute nor the names of its contributors
22170808Sdelphij *    may be used to endorse or promote products derived from this software
23170808Sdelphij *    without specific prior written permission.
24170808Sdelphij *
25170808Sdelphij * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26170808Sdelphij * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27170808Sdelphij * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28170808Sdelphij * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29170808Sdelphij * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30170808Sdelphij * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31170808Sdelphij * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32170808Sdelphij * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33170808Sdelphij * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34170808Sdelphij * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35170808Sdelphij * SUCH DAMAGE.
36170808Sdelphij */
37170808Sdelphij
38170808Sdelphij#include "krb5_locl.h"
39170808Sdelphij#include "send_to_kdc_plugin.h"
40170808Sdelphij
41170808Sdelphij/**
42170808Sdelphij * @section send_to_kdc Locating and sending packets to the KDC
43170808Sdelphij *
44170808Sdelphij * The send to kdc code is responsible to request the list of KDC from
45197850Sdelphij * the locate-kdc subsystem and then send requests to each of them.
46197850Sdelphij *
47170808Sdelphij * - Each second a new hostname is tried.
48170808Sdelphij * - If the hostname have several addresses, the first will be tried
49233851Sgleb *   directly then in turn the other will be tried every 3 seconds
50170808Sdelphij *   (host_timeout).
51170808Sdelphij * - UDP requests are tried 3 times, and it tried with a individual timeout of kdc_timeout / 3.
52170808Sdelphij * - TCP and HTTP requests are tried 1 time.
53170808Sdelphij *
54170808Sdelphij *  Total wait time shorter then (number of addresses * 3) + kdc_timeout seconds.
55170808Sdelphij *
56170808Sdelphij */
57188929Salc
58170808Sdelphijstatic int
59170808Sdelphijinit_port(const char *s, int fallback)
60170808Sdelphij{
61170808Sdelphij    int tmp;
62170808Sdelphij
63170808Sdelphij    if (s && sscanf(s, "%d", &tmp) == 1)
64233851Sgleb        return htons(tmp);
65233851Sgleb    return fallback;
66233851Sgleb}
67233851Sgleb
68233851Sglebstruct send_via_plugin_s {
69233851Sgleb    krb5_const_realm realm;
70233851Sgleb    krb5_krbhst_info *hi;
71170808Sdelphij    time_t timeout;
72170808Sdelphij    const krb5_data *send_data;
73171069Sdelphij    krb5_data *receive;
74170808Sdelphij};
75170808Sdelphij
76170808Sdelphij
77170808Sdelphijstatic krb5_error_code KRB5_LIB_CALL
78170808Sdelphijkdccallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
79170808Sdelphij{
80170808Sdelphij    const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
81170808Sdelphij    struct send_via_plugin_s *ctx = userctx;
82170808Sdelphij
83170808Sdelphij    if (service->send_to_kdc == NULL)
84170808Sdelphij	return KRB5_PLUGIN_NO_HANDLE;
85170808Sdelphij    return service->send_to_kdc(context, plugctx, ctx->hi, ctx->timeout,
86170808Sdelphij				ctx->send_data, ctx->receive);
87170808Sdelphij}
88191990Sattilio
89170808Sdelphijstatic krb5_error_code KRB5_LIB_CALL
90170808Sdelphijrealmcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
91170808Sdelphij{
92170808Sdelphij    const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
93170808Sdelphij    struct send_via_plugin_s *ctx = userctx;
94170808Sdelphij
95170808Sdelphij    if (service->send_to_realm == NULL)
96170808Sdelphij	return KRB5_PLUGIN_NO_HANDLE;
97197953Sdelphij    return service->send_to_realm(context, plugctx, ctx->realm, ctx->timeout,
98197953Sdelphij				  ctx->send_data, ctx->receive);
99197953Sdelphij}
100197953Sdelphij
101197953Sdelphijstatic krb5_error_code
102170808Sdelphijkdc_via_plugin(krb5_context context,
103171799Sdelphij	       krb5_krbhst_info *hi,
104171799Sdelphij	       time_t timeout,
105176559Sattilio	       const krb5_data *send_data,
106171802Sdelphij	       krb5_data *receive)
107175294Sattilio{
108170808Sdelphij    struct send_via_plugin_s userctx;
109171799Sdelphij
110191990Sattilio    userctx.realm = NULL;
111170808Sdelphij    userctx.hi = hi;
112175202Sattilio    userctx.timeout = timeout;
113171802Sdelphij    userctx.send_data = send_data;
114170808Sdelphij    userctx.receive = receive;
115170808Sdelphij
116170808Sdelphij    return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC,
117170808Sdelphij			      KRB5_PLUGIN_SEND_TO_KDC_VERSION_0, 0,
118170808Sdelphij			      &userctx, kdccallback);
119188318Skib}
120211598Sed
121211598Sedstatic krb5_error_code
122211598Sedrealm_via_plugin(krb5_context context,
123170808Sdelphij		 krb5_const_realm realm,
124170808Sdelphij		 time_t timeout,
125170808Sdelphij		 const krb5_data *send_data,
126170808Sdelphij		 krb5_data *receive)
127170808Sdelphij{
128170808Sdelphij    struct send_via_plugin_s userctx;
129211598Sed
130211598Sed    userctx.realm = realm;
131211598Sed    userctx.hi = NULL;
132211598Sed    userctx.timeout = timeout;
133170808Sdelphij    userctx.send_data = send_data;
134170808Sdelphij    userctx.receive = receive;
135170808Sdelphij
136170808Sdelphij    return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC,
137170808Sdelphij			      KRB5_PLUGIN_SEND_TO_KDC_VERSION_2, 0,
138170808Sdelphij			      &userctx, realmcallback);
139170808Sdelphij}
140170808Sdelphij
141170808Sdelphijstruct krb5_sendto_ctx_data {
142170808Sdelphij    int flags;
143170808Sdelphij    int type;
144170808Sdelphij    krb5_sendto_ctx_func func;
145170808Sdelphij    void *data;
146170808Sdelphij    char *hostname;
147170808Sdelphij    krb5_krbhst_handle krbhst;
148170808Sdelphij
149170808Sdelphij    /* context2 */
150170808Sdelphij    const krb5_data *send_data;
151170808Sdelphij    krb5_data response;
152170808Sdelphij    heim_array_t hosts;
153170808Sdelphij    int stateflags;
154170808Sdelphij#define KRBHST_COMPLETED	1
155170808Sdelphij
156170808Sdelphij    /* prexmit */
157170808Sdelphij    krb5_sendto_prexmit prexmit_func;
158170808Sdelphij    void *prexmit_ctx;
159170808Sdelphij
160170808Sdelphij    /* stats */
161170808Sdelphij    struct {
162170808Sdelphij	struct timeval start_time;
163170808Sdelphij	struct timeval name_resolution;
164170808Sdelphij	struct timeval krbhst;
165170808Sdelphij	unsigned long sent_packets;
166170808Sdelphij	unsigned long num_hosts;
167170808Sdelphij    } stats;
168170808Sdelphij    unsigned int stid;
169170808Sdelphij};
170170808Sdelphij
171170808Sdelphijstatic void
172170808Sdelphijdealloc_sendto_ctx(void *ptr)
173171070Sdelphij{
174170808Sdelphij    krb5_sendto_ctx ctx = (krb5_sendto_ctx)ptr;
175171799Sdelphij    if (ctx->hostname)
176191990Sattilio	free(ctx->hostname);
177170808Sdelphij    heim_release(ctx->hosts);
178170808Sdelphij    heim_release(ctx->krbhst);
179170808Sdelphij}
180170808Sdelphij
181170808SdelphijKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
182170808Sdelphijkrb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx)
183170808Sdelphij{
184170808Sdelphij    *ctx = heim_alloc(sizeof(**ctx), "sendto-context", dealloc_sendto_ctx);
185170808Sdelphij    if (*ctx == NULL)
186170808Sdelphij	return krb5_enomem(context);
187171070Sdelphij    (*ctx)->hosts = heim_array_create();
188170808Sdelphij
189171799Sdelphij    return 0;
190171799Sdelphij}
191191990Sattilio
192170808SdelphijKRB5_LIB_FUNCTION void KRB5_LIB_CALL
193170808Sdelphijkrb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags)
194170808Sdelphij{
195170808Sdelphij    ctx->flags |= flags;
196170808Sdelphij}
197170808Sdelphij
198170808SdelphijKRB5_LIB_FUNCTION int KRB5_LIB_CALL
199170808Sdelphijkrb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)
200170808Sdelphij{
201170808Sdelphij    return ctx->flags;
202170808Sdelphij}
203170808Sdelphij
204170808SdelphijKRB5_LIB_FUNCTION void KRB5_LIB_CALL
205176559Sattiliokrb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type)
206170808Sdelphij{
207170808Sdelphij    ctx->type = type;
208170808Sdelphij}
209170808Sdelphij
210170808SdelphijKRB5_LIB_FUNCTION void KRB5_LIB_CALL
211170808Sdelphijkrb5_sendto_ctx_set_func(krb5_sendto_ctx ctx,
212171069Sdelphij			 krb5_sendto_ctx_func func,
213170808Sdelphij			 void *data)
214170808Sdelphij{
215170808Sdelphij    ctx->func = func;
216170808Sdelphij    ctx->data = data;
217170808Sdelphij}
218170808Sdelphij
219170808SdelphijKRB5_LIB_FUNCTION void KRB5_LIB_CALL
220170808Sdelphij_krb5_sendto_ctx_set_prexmit(krb5_sendto_ctx ctx,
221170808Sdelphij			     krb5_sendto_prexmit prexmit,
222170808Sdelphij			     void *data)
223170808Sdelphij{
224170808Sdelphij    ctx->prexmit_func = prexmit;
225170808Sdelphij    ctx->prexmit_ctx = data;
226171069Sdelphij}
227170808Sdelphij
228170808SdelphijKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
229170808Sdelphijkrb5_sendto_set_hostname(krb5_context context,
230170808Sdelphij			 krb5_sendto_ctx ctx,
231170808Sdelphij			 const char *hostname)
232170808Sdelphij{
233170808Sdelphij    if (ctx->hostname == NULL)
234170808Sdelphij	free(ctx->hostname);
235170808Sdelphij    ctx->hostname = strdup(hostname);
236170808Sdelphij    if (ctx->hostname == NULL) {
237170808Sdelphij	krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
238170808Sdelphij	return ENOMEM;
239170808Sdelphij    }
240170808Sdelphij    return 0;
241170808Sdelphij}
242170808Sdelphij
243171069SdelphijKRB5_LIB_FUNCTION void KRB5_LIB_CALL
244170808Sdelphij_krb5_sendto_ctx_set_krb5hst(krb5_context context,
245170808Sdelphij			     krb5_sendto_ctx ctx,
246170808Sdelphij			     krb5_krbhst_handle handle)
247170808Sdelphij{
248170808Sdelphij    heim_release(ctx->krbhst);
249170808Sdelphij    ctx->krbhst = heim_retain(handle);
250170808Sdelphij}
251170808Sdelphij
252176559SattilioKRB5_LIB_FUNCTION void KRB5_LIB_CALL
253170808Sdelphijkrb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx)
254170808Sdelphij{
255171070Sdelphij    heim_release(ctx);
256170808Sdelphij}
257170808Sdelphij
258170808SdelphijKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
259170808Sdelphij_krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data,
260170808Sdelphij		const krb5_data *reply, int *action)
261170808Sdelphij{
262170808Sdelphij    krb5_error_code ret;
263170808Sdelphij    KRB_ERROR error;
264170808Sdelphij
265170808Sdelphij    if(krb5_rd_error(context, reply, &error))
266170808Sdelphij	return 0;
267171070Sdelphij
268170808Sdelphij    ret = krb5_error_from_rd_error(context, &error, NULL);
269170808Sdelphij    krb5_free_error_contents(context, &error);
270176559Sattilio
271170808Sdelphij    switch(ret) {
272170808Sdelphij    case KRB5KRB_ERR_RESPONSE_TOO_BIG: {
273170808Sdelphij	if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG)
274170808Sdelphij	    break;
275170808Sdelphij	krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG);
276171069Sdelphij	*action = KRB5_SENDTO_RESET;
277170808Sdelphij	break;
278170808Sdelphij    }
279170808Sdelphij    case KRB5KDC_ERR_SVC_UNAVAILABLE:
280170808Sdelphij	*action = KRB5_SENDTO_CONTINUE;
281176559Sattilio	break;
282170808Sdelphij    }
283218949Salc    return 0;
284218949Salc}
285170808Sdelphij
286218949Salc/*
287170808Sdelphij *
288170808Sdelphij */
289170808Sdelphij
290170808Sdelphijstruct host;
291170808Sdelphij
292170808Sdelphijstruct host_fun {
293170808Sdelphij    krb5_error_code (*prepare)(krb5_context, struct host *, const krb5_data *);
294170808Sdelphij    krb5_error_code (*send_fn)(krb5_context, struct host *);
295184413Strasz    krb5_error_code (*recv_fn)(krb5_context, struct host *, krb5_data *);
296170808Sdelphij    int ntries;
297170808Sdelphij};
298170808Sdelphij
299170808Sdelphijstruct host {
300170808Sdelphij    enum host_state { CONNECT, CONNECTING, CONNECTED, WAITING_REPLY, DEAD } state;
301176559Sattilio    krb5_krbhst_info *hi;
302170808Sdelphij    struct addrinfo *ai;
303170808Sdelphij    rk_socket_t fd;
304170808Sdelphij    struct host_fun *fun;
305170808Sdelphij    unsigned int tries;
306170808Sdelphij    time_t timeout;
307170808Sdelphij    krb5_data data;
308170808Sdelphij    unsigned int tid;
309170808Sdelphij};
310170808Sdelphij
311184413Straszstatic void
312170808Sdelphijdebug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
313170808Sdelphij	__attribute__ ((__format__ (__printf__, 4, 5)));
314170808Sdelphij
315170808Sdelphijstatic void
316170808Sdelphijdebug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
317170808Sdelphij{
318170808Sdelphij    const char *proto = "unknown";
319170808Sdelphij    const char *state;
320170808Sdelphij    char name[NI_MAXHOST], port[NI_MAXSERV];
321170808Sdelphij    char *text = NULL;
322170808Sdelphij    va_list ap;
323170808Sdelphij    int ret;
324170808Sdelphij
325170808Sdelphij    if (!_krb5_have_debug(context, 5))
326170808Sdelphij	return;
327170808Sdelphij
328170808Sdelphij    va_start(ap, fmt);
329170808Sdelphij    ret = vasprintf(&text, fmt, ap);
330170808Sdelphij    va_end(ap);
331184413Strasz    if (ret == -1 || text == NULL)
332170808Sdelphij	return;
333170808Sdelphij
334170808Sdelphij    if (host->hi->proto == KRB5_KRBHST_HTTP)
335170808Sdelphij	proto = "http";
336170808Sdelphij    else if (host->hi->proto == KRB5_KRBHST_TCP)
337184413Strasz	proto = "tcp";
338170808Sdelphij    else if (host->hi->proto == KRB5_KRBHST_UDP)
339170808Sdelphij	proto = "udp";
340176559Sattilio
341170808Sdelphij    if (getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen,
342170808Sdelphij		    name, sizeof(name), port, sizeof(port), NI_NUMERICHOST) != 0)
343170808Sdelphij	name[0] = '\0';
344170808Sdelphij
345170808Sdelphij    switch (host->state) {
346170808Sdelphij    case CONNECT:	state = "CONNECT";		break;
347170808Sdelphij    case CONNECTING:	state = "CONNECTING";		break;
348170808Sdelphij    case CONNECTED:	state = "CONNECTED";		break;
349170808Sdelphij    case WAITING_REPLY:	state = "WAITING_REPLY";	break;
350170808Sdelphij    case DEAD:		state = "DEAD";			break;
351170808Sdelphij    default:		state = "unknown";		break;
352170808Sdelphij    }
353170808Sdelphij
354170808Sdelphij    _krb5_debug(context, level, "%s: %s %s:%s (%s) state=%s tid: %08x", text,
355170808Sdelphij		proto, name, port, host->hi->hostname, state, host->tid);
356170808Sdelphij    free(text);
357170808Sdelphij}
358170808Sdelphij
359170808Sdelphij
360170808Sdelphijstatic void
361170808Sdelphijdeallocate_host(void *ptr)
362170808Sdelphij{
363170808Sdelphij    struct host *host = ptr;
364170808Sdelphij    if (!rk_IS_BAD_SOCKET(host->fd))
365170808Sdelphij	rk_closesocket(host->fd);
366170808Sdelphij    krb5_data_free(&host->data);
367170808Sdelphij    host->ai = NULL;
368170808Sdelphij}
369170808Sdelphij
370170808Sdelphijstatic void
371170808Sdelphijhost_dead(krb5_context context, struct host *host, const char *msg)
372170808Sdelphij{
373170808Sdelphij    debug_host(context, 5, host, "%s", msg);
374170808Sdelphij    rk_closesocket(host->fd);
375183214Skib    host->fd = rk_INVALID_SOCKET;
376170808Sdelphij    host->state = DEAD;
377183212Skib}
378170808Sdelphij
379170808Sdelphijstatic krb5_error_code
380170808Sdelphijsend_stream(krb5_context context, struct host *host)
381170808Sdelphij{
382170808Sdelphij    ssize_t len;
383170808Sdelphij
384170808Sdelphij    len = krb5_net_write(context, &host->fd, host->data.data, host->data.length);
385170808Sdelphij
386170808Sdelphij    if (len < 0)
387170808Sdelphij	return errno;
388170808Sdelphij    else if (len < host->data.length) {
389170808Sdelphij	host->data.length -= len;
390170808Sdelphij	memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len);
391170808Sdelphij	return -1;
392182371Sattilio    } else {
393170808Sdelphij	krb5_data_free(&host->data);
394170808Sdelphij	return 0;
395170808Sdelphij    }
396176559Sattilio}
397170808Sdelphij
398170808Sdelphijstatic krb5_error_code
399170808Sdelphijrecv_stream(krb5_context context, struct host *host)
400170808Sdelphij{
401170808Sdelphij    krb5_error_code ret;
402170808Sdelphij    size_t oldlen;
403170808Sdelphij    ssize_t sret;
404170808Sdelphij    int nbytes;
405170808Sdelphij
406170808Sdelphij    if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
407170808Sdelphij	return HEIM_NET_CONN_REFUSED;
408170808Sdelphij
409170808Sdelphij    if (context->max_msg_size - host->data.length < nbytes) {
410170808Sdelphij	krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
411170808Sdelphij			       N_("TCP message from KDC too large %d", ""),
412182371Sattilio			       (int)(host->data.length + nbytes));
413170808Sdelphij	return KRB5KRB_ERR_FIELD_TOOLONG;
414170808Sdelphij    }
415182371Sattilio
416170808Sdelphij    oldlen = host->data.length;
417170808Sdelphij
418182371Sattilio    ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */);
419170808Sdelphij    if (ret)
420170808Sdelphij	return ret;
421182371Sattilio
422170808Sdelphij    sret = krb5_net_read(context, &host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes);
423170808Sdelphij    if (sret <= 0) {
424170808Sdelphij	ret = errno;
425170808Sdelphij	return ret;
426170808Sdelphij    }
427170808Sdelphij    host->data.length = oldlen + sret;
428170808Sdelphij    /* zero terminate for http transport */
429171070Sdelphij    ((uint8_t *)host->data.data)[host->data.length] = '\0';
430182371Sattilio
431170808Sdelphij    return 0;
432170808Sdelphij}
433170808Sdelphij
434170808Sdelphij/*
435170808Sdelphij *
436170808Sdelphij */
437176559Sattilio
438170808Sdelphijstatic void
439170808Sdelphijhost_next_timeout(krb5_context context, struct host *host)
440170808Sdelphij{
441170808Sdelphij    host->timeout = context->kdc_timeout / host->fun->ntries;
442170808Sdelphij    if (host->timeout == 0)
443197850Sdelphij	host->timeout = 1;
444197850Sdelphij
445197850Sdelphij    host->timeout += time(NULL);
446197850Sdelphij}
447197850Sdelphij
448197850Sdelphij/*
449171489Sdelphij * connected host
450197850Sdelphij */
451197850Sdelphij
452197850Sdelphijstatic void
453231775Salchost_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
454197850Sdelphij{
455197850Sdelphij    krb5_error_code ret;
456197850Sdelphij
457197850Sdelphij    host->state = CONNECTED;
458197850Sdelphij    /*
459197850Sdelphij     * Now prepare data to send to host
460197850Sdelphij     */
461197850Sdelphij    if (ctx->prexmit_func) {
462197850Sdelphij	krb5_data data;
463197850Sdelphij
464197850Sdelphij	krb5_data_zero(&data);
465197850Sdelphij
466197850Sdelphij	ret = ctx->prexmit_func(context, host->hi->proto,
467197850Sdelphij				ctx->prexmit_ctx, host->fd, &data);
468207573Salc	if (ret == 0) {
469197850Sdelphij	    if (data.length == 0) {
470207573Salc		host_dead(context, host, "prexmit function didn't send data");
471197850Sdelphij		return;
472197850Sdelphij	    }
473197850Sdelphij	    ret = host->fun->prepare(context, host, &data);
474197850Sdelphij	    krb5_data_free(&data);
475197850Sdelphij	}
476197850Sdelphij
477197850Sdelphij    } else {
478197850Sdelphij	ret = host->fun->prepare(context, host, ctx->send_data);
479197850Sdelphij    }
480197850Sdelphij    if (ret)
481197850Sdelphij	debug_host(context, 5, host, "failed to prexmit/prepare");
482197850Sdelphij}
483197850Sdelphij
484197850Sdelphij/*
485197850Sdelphij * connect host
486197850Sdelphij */
487197850Sdelphij
488197850Sdelphijstatic void
489197850Sdelphijhost_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
490197850Sdelphij{
491197850Sdelphij    krb5_krbhst_info *hi = host->hi;
492197850Sdelphij    struct addrinfo *ai = host->ai;
493197850Sdelphij
494197850Sdelphij    debug_host(context, 5, host, "connecting to host");
495197850Sdelphij
496197850Sdelphij    if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) {
497197850Sdelphij#ifdef HAVE_WINSOCK
498197850Sdelphij	if (WSAGetLastError() == WSAEWOULDBLOCK)
499170808Sdelphij	    errno = EINPROGRESS;
500171489Sdelphij#endif /* HAVE_WINSOCK */
501170808Sdelphij	if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) {
502197850Sdelphij	    debug_host(context, 5, host, "connecting to %d", host->fd);
503171489Sdelphij	    host->state = CONNECTING;
504171489Sdelphij	} else {
505188929Salc	    host_dead(context, host, "failed to connect");
506188929Salc	}
507171489Sdelphij    } else {
508197850Sdelphij	host_connected(context, ctx, host);
509171489Sdelphij    }
510170808Sdelphij
511171489Sdelphij    host_next_timeout(context, host);
512171489Sdelphij}
513171489Sdelphij
514171489Sdelphij/*
515170808Sdelphij * HTTP transport
516197740Sdelphij */
517197740Sdelphij
518171489Sdelphijstatic krb5_error_code
519170808Sdelphijprepare_http(krb5_context context, struct host *host, const krb5_data *data)
520171489Sdelphij{
521171489Sdelphij    char *str = NULL, *request = NULL;
522171489Sdelphij    krb5_error_code ret;
523171489Sdelphij    int len;
524207530Salc
525207530Salc    heim_assert(host->data.length == 0, "prepare_http called twice");
526207530Salc
527207530Salc    len = rk_base64_encode(data->data, data->length, &str);
528207530Salc    if(len < 0)
529225418Skib	return ENOMEM;
530207530Salc
531171489Sdelphij    if (context->http_proxy)
532207530Salc	ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str);
533171489Sdelphij    else
534171489Sdelphij	ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str);
535188929Salc    free(str);
536171489Sdelphij    if(ret < 0 || request == NULL)
537171489Sdelphij	return ENOMEM;
538171489Sdelphij
539171489Sdelphij    host->data.data = request;
540197850Sdelphij    host->data.length = strlen(request);
541213735Savg
542213735Savg    return 0;
543207530Salc}
544207530Salc
545207530Salcstatic krb5_error_code
546207530Salcrecv_http(krb5_context context, struct host *host, krb5_data *data)
547207530Salc{
548225418Skib    krb5_error_code ret;
549207530Salc    unsigned long rep_len;
550197850Sdelphij    size_t len;
551207530Salc    char *p;
552197850Sdelphij
553197850Sdelphij    /*
554197850Sdelphij     * recv_stream returns a NUL terminated stream
555197850Sdelphij     */
556197850Sdelphij
557213735Savg    ret = recv_stream(context, host);
558197850Sdelphij    if (ret)
559213735Savg	return ret;
560213735Savg
561197850Sdelphij    p = strstr(host->data.data, "\r\n\r\n");
562197850Sdelphij    if (p == NULL)
563197850Sdelphij	return -1;
564197850Sdelphij    p += 4;
565197850Sdelphij
566197850Sdelphij    len = host->data.length - (p - (char *)host->data.data);
567212650Savg    if (len < 4)
568213735Savg	return -1;
569197850Sdelphij
570197850Sdelphij    _krb5_get_int(p, &rep_len, 4);
571197850Sdelphij    if (len < rep_len)
572171799Sdelphij	return -1;
573171489Sdelphij
574171489Sdelphij    p += 4;
575197850Sdelphij
576171489Sdelphij    memmove(host->data.data, p, rep_len);
577171489Sdelphij    host->data.length = rep_len;
578170808Sdelphij
579170808Sdelphij    *data = host->data;
580171069Sdelphij    krb5_data_zero(&host->data);
581170808Sdelphij
582170808Sdelphij    return 0;
583170808Sdelphij}
584170808Sdelphij
585170808Sdelphij/*
586170808Sdelphij * TCP transport
587170808Sdelphij */
588171489Sdelphij
589171489Sdelphijstatic krb5_error_code
590170808Sdelphijprepare_tcp(krb5_context context, struct host *host, const krb5_data *data)
591174265Swkoszek{
592170808Sdelphij    krb5_error_code ret;
593170808Sdelphij    krb5_storage *sp;
594170808Sdelphij
595170808Sdelphij    heim_assert(host->data.length == 0, "prepare_tcp called twice");
596170808Sdelphij
597170808Sdelphij    sp = krb5_storage_emem();
598170808Sdelphij    if (sp == NULL)
599170808Sdelphij	return ENOMEM;
600170808Sdelphij
601170808Sdelphij    ret = krb5_store_data(sp, *data);
602170808Sdelphij    if (ret) {
603170808Sdelphij	krb5_storage_free(sp);
604170808Sdelphij	return ret;
605170808Sdelphij    }
606170808Sdelphij    ret = krb5_storage_to_data(sp, &host->data);
607170808Sdelphij    krb5_storage_free(sp);
608171489Sdelphij
609171489Sdelphij    return ret;
610171489Sdelphij}
611171489Sdelphij
612171489Sdelphijstatic krb5_error_code
613171489Sdelphijrecv_tcp(krb5_context context, struct host *host, krb5_data *data)
614171489Sdelphij{
615171489Sdelphij    krb5_error_code ret;
616171489Sdelphij    unsigned long pktlen;
617171489Sdelphij
618171489Sdelphij    ret = recv_stream(context, host);
619170808Sdelphij    if (ret)
620170808Sdelphij	return ret;
621170808Sdelphij
622170808Sdelphij    if (host->data.length < 4)
623170808Sdelphij	return -1;
624170808Sdelphij
625170808Sdelphij    _krb5_get_int(host->data.data, &pktlen, 4);
626170808Sdelphij
627171069Sdelphij    if (pktlen > host->data.length - 4)
628171489Sdelphij	return -1;
629171489Sdelphij
630171489Sdelphij    memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4);
631171489Sdelphij    host->data.length -= 4;
632188929Salc
633188929Salc    *data = host->data;
634171489Sdelphij    krb5_data_zero(&host->data);
635171489Sdelphij
636171489Sdelphij    return 0;
637174265Swkoszek}
638174265Swkoszek
639171489Sdelphij/*
640171489Sdelphij * UDP transport
641171489Sdelphij */
642171489Sdelphij
643171489Sdelphijstatic krb5_error_code
644197740Sdelphijprepare_udp(krb5_context context, struct host *host, const krb5_data *data)
645197740Sdelphij{
646171489Sdelphij    return krb5_data_copy(&host->data, data->data, data->length);
647171489Sdelphij}
648171489Sdelphij
649171489Sdelphijstatic krb5_error_code
650171489Sdelphijsend_udp(krb5_context context, struct host *host)
651171489Sdelphij{
652171489Sdelphij    if (send(host->fd, host->data.data, host->data.length, 0) < 0)
653171489Sdelphij	return errno;
654207530Salc    return 0;
655207530Salc}
656207530Salc
657207530Salcstatic krb5_error_code
658207530Salcrecv_udp(krb5_context context, struct host *host, krb5_data *data)
659225418Skib{
660207530Salc    krb5_error_code ret;
661171489Sdelphij    int nbytes;
662207530Salc
663171489Sdelphij
664171489Sdelphij    if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0)
665171489Sdelphij	return HEIM_NET_CONN_REFUSED;
666188929Salc
667171489Sdelphij    if (context->max_msg_size < nbytes) {
668197740Sdelphij	krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
669197740Sdelphij			       N_("UDP message from KDC too large %d", ""),
670171489Sdelphij			       (int)nbytes);
671171489Sdelphij	return KRB5KRB_ERR_FIELD_TOOLONG;
672171489Sdelphij    }
673171489Sdelphij
674171489Sdelphij    ret = krb5_data_alloc(data, nbytes);
675171489Sdelphij    if (ret)
676171489Sdelphij	return ret;
677231775Salc
678171489Sdelphij    ret = recv(host->fd, data->data, data->length, 0);
679194124Salc    if (ret < 0) {
680171489Sdelphij	ret = errno;
681171489Sdelphij	krb5_data_free(data);
682171489Sdelphij	return ret;
683171489Sdelphij    }
684171489Sdelphij    data->length = ret;
685171489Sdelphij
686171489Sdelphij    return 0;
687171489Sdelphij}
688171489Sdelphij
689188929Salcstatic struct host_fun http_fun = {
690188929Salc    prepare_http,
691188929Salc    send_stream,
692171489Sdelphij    recv_http,
693171489Sdelphij    1
694171489Sdelphij};
695171489Sdelphijstatic struct host_fun tcp_fun = {
696171489Sdelphij    prepare_tcp,
697171489Sdelphij    send_stream,
698171489Sdelphij    recv_tcp,
699171489Sdelphij    1
700192917Salc};
701192917Salcstatic struct host_fun udp_fun = {
702171489Sdelphij    prepare_udp,
703171489Sdelphij    send_udp,
704209226Salc    recv_udp,
705188921Salc    3
706207573Salc};
707171489Sdelphij
708171489Sdelphij
709171489Sdelphij/*
710171489Sdelphij * Host state machine
711171489Sdelphij */
712171489Sdelphij
713171489Sdelphijstatic int
714171489Sdelphijeval_host_state(krb5_context context,
715171489Sdelphij		krb5_sendto_ctx ctx,
716171489Sdelphij		struct host *host,
717171489Sdelphij		int readable, int writeable)
718171489Sdelphij{
719170808Sdelphij    krb5_error_code ret;
720170808Sdelphij
721170808Sdelphij    if (host->state == CONNECT) {
722170808Sdelphij	/* check if its this host time to connect */
723170808Sdelphij	if (host->timeout < time(NULL))
724170808Sdelphij	    host_connect(context, ctx, host);
725170808Sdelphij	return 0;
726171489Sdelphij    }
727170808Sdelphij
728170808Sdelphij    if (host->state == CONNECTING && writeable)
729170808Sdelphij	host_connected(context, ctx, host);
730171489Sdelphij
731171489Sdelphij    if (readable) {
732170808Sdelphij
733170808Sdelphij	debug_host(context, 5, host, "reading packet");
734170808Sdelphij
735170808Sdelphij	ret = host->fun->recv_fn(context, host, &ctx->response);
736170808Sdelphij	if (ret == -1) {
737170808Sdelphij	    /* not done yet */
738170808Sdelphij	} else if (ret == 0) {
739170808Sdelphij	    /* if recv_foo function returns 0, we have a complete reply */
740170808Sdelphij	    debug_host(context, 5, host, "host completed");
741170808Sdelphij	    return 1;
742170808Sdelphij	} else {
743170808Sdelphij	    host_dead(context, host, "host disconnected");
744170808Sdelphij	}
745170808Sdelphij    }
746170808Sdelphij
747170808Sdelphij    /* check if there is anything to send, state might DEAD after read */
748171070Sdelphij    if (writeable && host->state == CONNECTED) {
749171070Sdelphij
750170808Sdelphij	ctx->stats.sent_packets++;
751170808Sdelphij
752170808Sdelphij	debug_host(context, 5, host, "writing packet");
753207719Strasz
754207662Strasz	ret = host->fun->send_fn(context, host);
755170808Sdelphij	if (ret == -1) {
756170808Sdelphij	    /* not done yet */
757170808Sdelphij	} else if (ret) {
758170808Sdelphij	    host_dead(context, host, "host dead, write failed");
759170808Sdelphij	} else
760170808Sdelphij	    host->state = WAITING_REPLY;
761170808Sdelphij    }
762170808Sdelphij
763170808Sdelphij    return 0;
764171489Sdelphij}
765171489Sdelphij
766171489Sdelphij/*
767171489Sdelphij *
768171489Sdelphij */
769171489Sdelphij
770171489Sdelphijstatic krb5_error_code
771171489Sdelphijsubmit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)
772171489Sdelphij{
773171489Sdelphij    unsigned long submitted_host = 0;
774170808Sdelphij    krb5_boolean freeai = FALSE;
775170808Sdelphij    struct timeval nrstart, nrstop;
776170808Sdelphij    krb5_error_code ret;
777170808Sdelphij    struct addrinfo *ai = NULL, *a;
778170808Sdelphij    struct host *host;
779170808Sdelphij
780170808Sdelphij    ret = kdc_via_plugin(context, hi, context->kdc_timeout,
781170808Sdelphij			 ctx->send_data, &ctx->response);
782170808Sdelphij    if (ret == 0) {
783170808Sdelphij	return 0;
784170808Sdelphij    } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
785170808Sdelphij	_krb5_debug(context, 5, "send via plugin failed %s: %d",
786170808Sdelphij		    hi->hostname, ret);
787170808Sdelphij	return ret;
788170808Sdelphij    }
789170808Sdelphij
790170808Sdelphij    /*
791170808Sdelphij     * If we have a proxy, let use the address of the proxy instead of
792170808Sdelphij     * the KDC and let the proxy deal with the resolving of the KDC.
793170808Sdelphij     */
794170808Sdelphij
795171069Sdelphij    gettimeofday(&nrstart, NULL);
796170808Sdelphij
797170808Sdelphij    if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
798170808Sdelphij	char *proxy2 = strdup(context->http_proxy);
799170808Sdelphij	char *el, *proxy  = proxy2;
800176559Sattilio	struct addrinfo hints;
801170808Sdelphij	char portstr[NI_MAXSERV];
802170808Sdelphij	unsigned short nport;
803170808Sdelphij
804170808Sdelphij	if (proxy == NULL)
805170808Sdelphij	    return ENOMEM;
806170808Sdelphij	if (strncmp(proxy, "http://", 7) == 0)
807170808Sdelphij	    proxy += 7;
808170808Sdelphij
809171069Sdelphij	/* check for url terminating slash */
810170808Sdelphij	el = strchr(proxy, '/');
811170808Sdelphij	if (el != NULL)
812170808Sdelphij	    *el = '\0';
813170808Sdelphij
814170808Sdelphij	/* check for port in hostname, used below as port */
815170808Sdelphij	el = strchr(proxy, ':');
816170808Sdelphij	if(el != NULL)
817170808Sdelphij	    *el++ = '\0';
818170808Sdelphij
819170808Sdelphij	memset(&hints, 0, sizeof(hints));
820170808Sdelphij	hints.ai_family   = PF_UNSPEC;
821176559Sattilio	hints.ai_socktype = SOCK_STREAM;
822176559Sattilio
823170808Sdelphij	/* On some systems ntohs(foo(..., htons(...))) causes shadowing */
824170808Sdelphij	nport = init_port(el, htons(80));
825170808Sdelphij	snprintf(portstr, sizeof(portstr), "%d", ntohs(nport));
826170808Sdelphij
827170808Sdelphij	ret = getaddrinfo(proxy, portstr, &hints, &ai);
828170808Sdelphij	free(proxy2);
829170808Sdelphij	if (ret)
830170808Sdelphij	    return krb5_eai_to_heim_errno(ret, errno);
831170808Sdelphij
832188318Skib	freeai = TRUE;
833170808Sdelphij
834170808Sdelphij    } else {
835170808Sdelphij	ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
836170808Sdelphij	if (ret)
837170808Sdelphij	    return ret;
838170808Sdelphij    }
839170808Sdelphij
840170808Sdelphij    /* add up times */
841170808Sdelphij    gettimeofday(&nrstop, NULL);
842170808Sdelphij    timevalsub(&nrstop, &nrstart);
843170808Sdelphij    timevaladd(&ctx->stats.name_resolution, &nrstop);
844170808Sdelphij
845211598Sed    ctx->stats.num_hosts++;
846211598Sed
847170808Sdelphij    for (a = ai; a != NULL; a = a->ai_next) {
848170808Sdelphij	rk_socket_t fd;
849170808Sdelphij
850170808Sdelphij	fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
851170808Sdelphij	if (rk_IS_BAD_SOCKET(fd))
852170808Sdelphij	    continue;
853218949Salc	rk_cloexec(fd);
854170808Sdelphij
855170808Sdelphij#ifndef NO_LIMIT_FD_SETSIZE
856170808Sdelphij	if (fd >= FD_SETSIZE) {
857170808Sdelphij	    _krb5_debug(context, 0, "fd too large for select");
858170808Sdelphij	    rk_closesocket(fd);
859170808Sdelphij	    continue;
860170808Sdelphij	}
861170808Sdelphij#endif
862170808Sdelphij	socket_set_nonblocking(fd, 1);
863171069Sdelphij
864170808Sdelphij	host = heim_alloc(sizeof(*host), "sendto-host", deallocate_host);
865170808Sdelphij	if (host == NULL) {
866170808Sdelphij            if (freeai)
867170808Sdelphij                freeaddrinfo(ai);
868170808Sdelphij	    rk_closesocket(fd);
869170808Sdelphij	    return ENOMEM;
870170808Sdelphij	}
871170808Sdelphij	host->hi = hi;
872170808Sdelphij	host->fd = fd;
873170808Sdelphij	host->ai = a;
874176559Sattilio	/* next version of stid */
875170808Sdelphij	host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1);
876170808Sdelphij
877170808Sdelphij	host->state = CONNECT;
878170808Sdelphij
879170808Sdelphij	switch (host->hi->proto) {
880170808Sdelphij	case KRB5_KRBHST_HTTP :
881170808Sdelphij	    host->fun = &http_fun;
882170808Sdelphij	    break;
883170808Sdelphij	case KRB5_KRBHST_TCP :
884170808Sdelphij	    host->fun = &tcp_fun;
885170808Sdelphij	    break;
886170808Sdelphij	case KRB5_KRBHST_UDP :
887170808Sdelphij	    host->fun = &udp_fun;
888170808Sdelphij	    break;
889170808Sdelphij	default:
890170808Sdelphij	    heim_abort("undefined http transport protocol: %d", (int)host->hi->proto);
891170808Sdelphij	}
892170808Sdelphij
893170808Sdelphij	host->tries = host->fun->ntries;
894170808Sdelphij
895170808Sdelphij	/*
896170808Sdelphij	 * Connect directly next host, wait a host_timeout for each next address.
897170808Sdelphij	 * We try host_connect() here, checking the return code because as we do
898170808Sdelphij	 * non-blocking connects, any error here indicates that the address is just
899170808Sdelphij	 * offline.  That is, it's something like "No route to host" which is not
900170808Sdelphij	 * worth retrying.  And so, we fail directly and immediately to the next
901170808Sdelphij	 * address for this host without enqueueing the address for retries.
902170808Sdelphij	 */
903170808Sdelphij	if (submitted_host == 0) {
904170808Sdelphij	    host_connect(context, ctx, host);
905170808Sdelphij	    if (host->state == DEAD)
906170808Sdelphij		continue;
907170808Sdelphij	} else {
908170808Sdelphij	    debug_host(context, 5, host,
909170808Sdelphij		       "Queuing host in future (in %ds), its the %lu address on the same name",
910170808Sdelphij		       (int)(context->host_timeout * submitted_host), submitted_host + 1);
911170808Sdelphij	    host->timeout = time(NULL) + (submitted_host * context->host_timeout);
912170808Sdelphij	}
913170808Sdelphij
914170808Sdelphij	heim_array_append_value(ctx->hosts, host);
915211598Sed	heim_release(host);
916211598Sed	submitted_host++;
917170808Sdelphij    }
918170808Sdelphij
919170808Sdelphij    if (freeai)
920170808Sdelphij	freeaddrinfo(ai);
921170808Sdelphij
922170808Sdelphij    if (submitted_host == 0)
923170808Sdelphij	return KRB5_KDC_UNREACH;
924171070Sdelphij
925170808Sdelphij    return 0;
926170808Sdelphij}
927170808Sdelphij
928170808Sdelphijstruct wait_ctx {
929170808Sdelphij    krb5_context context;
930170808Sdelphij    krb5_sendto_ctx ctx;
931233851Sgleb    fd_set rfds;
932233851Sgleb    fd_set wfds;
933233851Sgleb    rk_socket_t max_fd;
934233851Sgleb    int got_reply;
935233851Sgleb    time_t timenow;
936233851Sgleb};
937233851Sgleb
938233851Sglebstatic void
939171069Sdelphijwait_setup(heim_object_t obj, void *iter_ctx, int *stop)
940233851Sgleb{
941233851Sgleb    struct wait_ctx *wait_ctx = iter_ctx;
942233851Sgleb    struct host *h = (struct host *)obj;
943233851Sgleb
944233851Sgleb    if (h->state == CONNECT) {
945233851Sgleb	if (h->timeout >= wait_ctx->timenow)
946233851Sgleb	    return;
947233851Sgleb	host_connect(wait_ctx->context, wait_ctx->ctx, h);
948233851Sgleb    }
949233851Sgleb
950233851Sgleb    /* skip dead hosts */
951233851Sgleb    if (h->state == DEAD)
952233851Sgleb	return;
953233851Sgleb
954233851Sgleb    /* if host timed out, dec tries and (retry or kill host) */
955233851Sgleb    if (h->timeout < wait_ctx->timenow) {
956233851Sgleb	heim_assert(h->tries != 0, "tries should not reach 0");
957233851Sgleb	h->tries--;
958233851Sgleb	if (h->tries == 0) {
959233851Sgleb	    host_dead(wait_ctx->context, h, "host timed out");
960233851Sgleb	    return;
961233851Sgleb	} else {
962233851Sgleb	    debug_host(wait_ctx->context, 5, h, "retrying sending to");
963233851Sgleb	    host_next_timeout(wait_ctx->context, h);
964233851Sgleb	    host_connected(wait_ctx->context, wait_ctx->ctx, h);
965233851Sgleb	}
966233851Sgleb    }
967233851Sgleb
968233851Sgleb#ifndef NO_LIMIT_FD_SETSIZE
969233851Sgleb    heim_assert(h->fd < FD_SETSIZE, "fd too large");
970233851Sgleb#endif
971233851Sgleb    switch (h->state) {
972233851Sgleb    case WAITING_REPLY:
973233851Sgleb	FD_SET(h->fd, &wait_ctx->rfds);
974233851Sgleb	break;
975233851Sgleb    case CONNECTING:
976233851Sgleb    case CONNECTED:
977233851Sgleb	FD_SET(h->fd, &wait_ctx->rfds);
978233851Sgleb	FD_SET(h->fd, &wait_ctx->wfds);
979233851Sgleb	break;
980233851Sgleb    default:
981233851Sgleb	debug_host(wait_ctx->context, 5, h, "invalid sendto host state");
982233851Sgleb	heim_abort("invalid sendto host state");
983233851Sgleb    }
984233851Sgleb    if (h->fd > wait_ctx->max_fd || wait_ctx->max_fd == rk_INVALID_SOCKET)
985233851Sgleb	wait_ctx->max_fd = h->fd;
986233851Sgleb}
987233851Sgleb
988233851Sglebstatic int
989233851Sglebwait_filter_dead(heim_object_t obj, void *ctx)
990233851Sgleb{
991233851Sgleb    struct host *h = (struct host *)obj;
992233851Sgleb    return (int)((h->state == DEAD) ? true : false);
993233851Sgleb}
994233851Sgleb
995233851Sglebstatic void
996233851Sglebwait_accelerate(heim_object_t obj, void *ctx, int *stop)
997233851Sgleb{
998233851Sgleb    struct host *h = (struct host *)obj;
999233851Sgleb
1000233851Sgleb    if (h->state == CONNECT && h->timeout > 0)
1001233851Sgleb	h->timeout--;
1002233851Sgleb}
1003233851Sgleb
1004233851Sglebstatic void
1005233851Sglebwait_process(heim_object_t obj, void *ctx, int *stop)
1006233851Sgleb{
1007233851Sgleb    struct wait_ctx *wait_ctx = ctx;
1008233851Sgleb    struct host *h = (struct host *)obj;
1009233851Sgleb    int readable, writeable;
1010233851Sgleb    heim_assert(h->state != DEAD, "dead host resurected");
1011233851Sgleb
1012233851Sgleb#ifndef NO_LIMIT_FD_SETSIZE
1013233851Sgleb    heim_assert(h->fd < FD_SETSIZE, "fd too large");
1014233851Sgleb#endif
1015233851Sgleb    readable = FD_ISSET(h->fd, &wait_ctx->rfds);
1016233851Sgleb    writeable = FD_ISSET(h->fd, &wait_ctx->wfds);
1017233851Sgleb
1018233851Sgleb    if (readable || writeable || h->state == CONNECT)
1019233851Sgleb	wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable);
1020233851Sgleb
1021233851Sgleb    /* if there is already a reply, just fall though the array */
1022233851Sgleb    if (wait_ctx->got_reply)
1023233851Sgleb	*stop = 1;
1024233851Sgleb}
1025233851Sgleb
1026233851Sglebstatic krb5_error_code
1027233851Sglebwait_response(krb5_context context, int *action, krb5_sendto_ctx ctx)
1028233851Sgleb{
1029233851Sgleb    struct wait_ctx wait_ctx;
1030233851Sgleb    struct timeval tv;
1031233851Sgleb    int ret;
1032233851Sgleb
1033233851Sgleb    wait_ctx.context = context;
1034233851Sgleb    wait_ctx.ctx = ctx;
1035233851Sgleb    FD_ZERO(&wait_ctx.rfds);
1036233851Sgleb    FD_ZERO(&wait_ctx.wfds);
1037233851Sgleb    wait_ctx.max_fd = rk_INVALID_SOCKET;
1038233851Sgleb
1039233851Sgleb    /* oh, we have a reply, it must be a plugin that got it for us */
1040233851Sgleb    if (ctx->response.length) {
1041233851Sgleb	*action = KRB5_SENDTO_FILTER;
1042233851Sgleb	return 0;
1043233851Sgleb    }
1044233851Sgleb
1045233851Sgleb    wait_ctx.timenow = time(NULL);
1046233851Sgleb
1047233851Sgleb    heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup);
1048233851Sgleb    heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead);
1049233851Sgleb
1050233851Sgleb    if (heim_array_get_length(ctx->hosts) == 0) {
1051233851Sgleb	if (ctx->stateflags & KRBHST_COMPLETED) {
1052233851Sgleb	    _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1053233851Sgleb			 "trying to pulling more hosts");
1054233851Sgleb	    *action = KRB5_SENDTO_FAILED;
1055233851Sgleb	} else {
1056233851Sgleb	    _krb5_debug(context, 5, "no more hosts to send/recv packets to/from "
1057233851Sgleb			 "and no more hosts -> failure");
1058233851Sgleb	    *action = KRB5_SENDTO_TIMEOUT;
1059233851Sgleb	}
1060233851Sgleb	return 0;
1061233851Sgleb    }
1062233851Sgleb
1063233851Sgleb    if (wait_ctx.max_fd == rk_INVALID_SOCKET) {
1064233851Sgleb	/*
1065170808Sdelphij	 * If we don't find a host which can make progress, then
1066170808Sdelphij	 * we accelerate the process by moving all of the contestants
1067170808Sdelphij	 * up by 1s.
1068170808Sdelphij	 */
1069170808Sdelphij	_krb5_debug(context, 5, "wait_response: moving the contestants forward");
1070170808Sdelphij	heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_accelerate);
1071170808Sdelphij	return 0;
1072170808Sdelphij    }
1073233851Sgleb
1074170808Sdelphij    tv.tv_sec = 1;
1075170808Sdelphij    tv.tv_usec = 0;
1076170808Sdelphij
1077170808Sdelphij    ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv);
1078197953Sdelphij    if (ret < 0)
1079170808Sdelphij	return errno;
1080170808Sdelphij    if (ret == 0) {
1081171799Sdelphij	*action = KRB5_SENDTO_TIMEOUT;
1082170808Sdelphij	return 0;
1083170808Sdelphij    }
1084176559Sattilio
1085176559Sattilio    wait_ctx.got_reply = 0;
1086170808Sdelphij    heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process);
1087170808Sdelphij    if (wait_ctx.got_reply)
1088170808Sdelphij	*action = KRB5_SENDTO_FILTER;
1089170808Sdelphij    else
1090170808Sdelphij	*action = KRB5_SENDTO_CONTINUE;
1091170808Sdelphij
1092170808Sdelphij    return 0;
1093170808Sdelphij}
1094170808Sdelphij
1095170808Sdelphijstatic void
1096170808Sdelphijreset_context(krb5_context context, krb5_sendto_ctx ctx)
1097170808Sdelphij{
1098170808Sdelphij    krb5_data_free(&ctx->response);
1099170808Sdelphij    heim_release(ctx->hosts);
1100170808Sdelphij    ctx->hosts = heim_array_create();
1101170808Sdelphij    ctx->stateflags = 0;
1102170808Sdelphij}
1103173725Sdelphij
1104173725Sdelphij
1105233851Sgleb/*
1106233851Sgleb *
1107233851Sgleb */
1108233851Sgleb
1109233851SglebKRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1110233851Sglebkrb5_sendto_context(krb5_context context,
1111233851Sgleb		    krb5_sendto_ctx ctx,
1112233851Sgleb		    const krb5_data *send_data,
1113233851Sgleb		    krb5_const_realm realm,
1114233851Sgleb		    krb5_data *receive)
1115233851Sgleb{
1116233851Sgleb    krb5_error_code ret = 0;
1117233851Sgleb    krb5_krbhst_handle handle = NULL;
1118233851Sgleb    struct timeval nrstart, nrstop, stop_time;
1119233851Sgleb    int type, freectx = 0;
1120233851Sgleb    int action;
1121233851Sgleb    int numreset = 0;
1122233851Sgleb
1123233851Sgleb    krb5_data_zero(receive);
1124233851Sgleb
1125233851Sgleb    if (ctx == NULL) {
1126233851Sgleb	ret = krb5_sendto_ctx_alloc(context, &ctx);
1127233851Sgleb	if (ret)
1128233851Sgleb	    goto out;
1129233851Sgleb	freectx = 1;
1130233851Sgleb    }
1131233851Sgleb
1132233851Sgleb    ctx->stid = (context->num_kdc_requests++) << 16;
1133233851Sgleb
1134233851Sgleb    memset(&ctx->stats, 0, sizeof(ctx->stats));
1135233851Sgleb    gettimeofday(&ctx->stats.start_time, NULL);
1136173725Sdelphij
1137173725Sdelphij    type = ctx->type;
1138188318Skib    if (type == 0) {
1139173725Sdelphij	if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
1140212305Sivoras	    type = KRB5_KRBHST_ADMIN;
1141212305Sivoras	else
1142170808Sdelphij	    type = KRB5_KRBHST_KDC;
1143212305Sivoras    }
1144212305Sivoras
1145212305Sivoras    ctx->send_data = send_data;
1146212305Sivoras
1147212305Sivoras    if ((int)send_data->length > context->large_msg_size)
1148173725Sdelphij	ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
1149170808Sdelphij
1150170808Sdelphij    /* loop until we get back a appropriate response */
1151170808Sdelphij
1152171070Sdelphij    action = KRB5_SENDTO_INITIAL;
1153170808Sdelphij
1154171070Sdelphij    while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) {
1155170808Sdelphij	krb5_krbhst_info *hi;
1156170808Sdelphij
1157170808Sdelphij	switch (action) {
1158171799Sdelphij	case KRB5_SENDTO_INITIAL:
1159171070Sdelphij	    ret = realm_via_plugin(context, realm, context->kdc_timeout,
1160170808Sdelphij				   send_data, &ctx->response);
1161170808Sdelphij	    if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) {
1162170808Sdelphij		action = KRB5_SENDTO_DONE;
1163173725Sdelphij		break;
1164170808Sdelphij	    }
1165170808Sdelphij	    action = KRB5_SENDTO_KRBHST;
1166171799Sdelphij	    /* FALLTHROUGH */
1167171799Sdelphij	case KRB5_SENDTO_KRBHST:
1168171799Sdelphij	    if (ctx->krbhst == NULL) {
1169173725Sdelphij		ret = krb5_krbhst_init_flags(context, realm, type,
1170171799Sdelphij					     ctx->flags, &handle);
1171171799Sdelphij		if (ret)
1172171799Sdelphij		    goto out;
1173173725Sdelphij
1174171799Sdelphij		if (ctx->hostname) {
1175171799Sdelphij		    ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname);
1176173725Sdelphij		    if (ret)
1177171799Sdelphij			goto out;
1178171799Sdelphij		}
1179171799Sdelphij
1180170808Sdelphij	    } else {
1181170808Sdelphij		handle = heim_retain(ctx->krbhst);
1182170808Sdelphij	    }
1183170808Sdelphij	    action = KRB5_SENDTO_TIMEOUT;
1184170808Sdelphij	    /* FALLTHROUGH */
1185170808Sdelphij	case KRB5_SENDTO_TIMEOUT:
1186170808Sdelphij
1187170808Sdelphij	    /*
1188170808Sdelphij	     * If we completed, just got to next step
1189170808Sdelphij	     */
1190170808Sdelphij
1191170808Sdelphij	    if (ctx->stateflags & KRBHST_COMPLETED) {
1192183299Sobrien		action = KRB5_SENDTO_CONTINUE;
1193171087Sdelphij		break;
1194170808Sdelphij	    }
1195170808Sdelphij
1196170808Sdelphij	    /*
1197170808Sdelphij	     * Pull out next host, if there is no more, close the
1198170808Sdelphij	     * handle and mark as completed.
1199170808Sdelphij	     *
1200170808Sdelphij	     * Collect time spent in krbhst (dns, plugin, etc)
1201170808Sdelphij	     */
1202170808Sdelphij
1203170808Sdelphij
1204170808Sdelphij	    gettimeofday(&nrstart, NULL);
1205170808Sdelphij
1206170808Sdelphij	    ret = krb5_krbhst_next(context, handle, &hi);
1207170808Sdelphij
1208170808Sdelphij	    gettimeofday(&nrstop, NULL);
1209197953Sdelphij	    timevalsub(&nrstop, &nrstart);
1210197953Sdelphij	    timevaladd(&ctx->stats.krbhst, &nrstop);
1211197953Sdelphij
1212197953Sdelphij	    action = KRB5_SENDTO_CONTINUE;
1213197953Sdelphij	    if (ret == 0) {
1214170808Sdelphij		_krb5_debug(context, 5, "submitting new requests to new host");
1215197953Sdelphij		if (submit_request(context, ctx, hi) != 0)
1216197953Sdelphij		    action = KRB5_SENDTO_TIMEOUT;
1217170808Sdelphij	    } else {
1218197953Sdelphij		_krb5_debug(context, 5, "out of hosts, waiting for replies");
1219197953Sdelphij		ctx->stateflags |= KRBHST_COMPLETED;
1220170808Sdelphij	    }
1221170808Sdelphij
1222171087Sdelphij	    break;
1223170808Sdelphij	case KRB5_SENDTO_CONTINUE:
1224170808Sdelphij
1225197953Sdelphij	    ret = wait_response(context, &action, ctx);
1226197953Sdelphij	    if (ret)
1227197953Sdelphij		goto out;
1228197953Sdelphij
1229197953Sdelphij	    break;
1230197953Sdelphij	case KRB5_SENDTO_RESET:
1231197953Sdelphij	    /* start over */
1232197953Sdelphij	    _krb5_debug(context, 5,
1233197953Sdelphij			"krb5_sendto trying over again (reset): %d",
1234197953Sdelphij			numreset);
1235197953Sdelphij	    reset_context(context, ctx);
1236197953Sdelphij	    if (handle) {
1237197953Sdelphij		krb5_krbhst_free(context, handle);
1238170808Sdelphij		handle = NULL;
1239197953Sdelphij	    }
1240197953Sdelphij	    numreset++;
1241197953Sdelphij	    if (numreset >= 3)
1242197953Sdelphij		action = KRB5_SENDTO_FAILED;
1243197953Sdelphij	    else
1244197953Sdelphij		action = KRB5_SENDTO_KRBHST;
1245197953Sdelphij
1246197953Sdelphij	    break;
1247170808Sdelphij	case KRB5_SENDTO_FILTER:
1248170808Sdelphij	    /* default to next state, the filter function might modify this */
1249170808Sdelphij	    action = KRB5_SENDTO_DONE;
1250197953Sdelphij
1251170808Sdelphij	    if (ctx->func) {
1252197953Sdelphij		ret = (*ctx->func)(context, ctx, ctx->data,
1253170808Sdelphij				   &ctx->response, &action);
1254170808Sdelphij		if (ret)
1255170808Sdelphij		    goto out;
1256170808Sdelphij	    }
1257197953Sdelphij	    break;
1258197953Sdelphij	case KRB5_SENDTO_FAILED:
1259197953Sdelphij	    ret = KRB5_KDC_UNREACH;
1260197953Sdelphij	    break;
1261197953Sdelphij	case KRB5_SENDTO_DONE:
1262197953Sdelphij	    ret = 0;
1263197953Sdelphij	    break;
1264170808Sdelphij	default:
1265197953Sdelphij	    heim_abort("invalid krb5_sendto_context state");
1266170808Sdelphij	}
1267170808Sdelphij    }
1268170808Sdelphij
1269170808Sdelphijout:
1270170808Sdelphij    gettimeofday(&stop_time, NULL);
1271211598Sed    timevalsub(&stop_time, &ctx->stats.start_time);
1272211598Sed    if (ret == 0 && ctx->response.length) {
1273211598Sed	*receive = ctx->response;
1274211598Sed	krb5_data_zero(&ctx->response);
1275170808Sdelphij    } else {
1276170808Sdelphij	krb5_data_free(&ctx->response);
1277170808Sdelphij	krb5_clear_error_message (context);
1278170808Sdelphij	ret = KRB5_KDC_UNREACH;
1279170808Sdelphij	krb5_set_error_message(context, ret,
1280170808Sdelphij			       N_("unable to reach any KDC in realm %s", ""),
1281170808Sdelphij			       realm);
1282170808Sdelphij    }
1283171087Sdelphij
1284170808Sdelphij    _krb5_debug(context, 1,
1285170808Sdelphij		"%s %s done: %d hosts %lu packets %lu:"
1286170808Sdelphij		" wc: %jd.%06lu nr: %jd.%06lu kh: %jd.%06lu tid: %08x",
1287170808Sdelphij		__func__, realm, ret,
1288170808Sdelphij		ctx->stats.num_hosts, ctx->stats.sent_packets,
1289170808Sdelphij		(intmax_t)stop_time.tv_sec,
1290170808Sdelphij		(unsigned long)stop_time.tv_usec,
1291170808Sdelphij		(intmax_t)ctx->stats.name_resolution.tv_sec,
1292170808Sdelphij		(unsigned long)ctx->stats.name_resolution.tv_usec,
1293170808Sdelphij		(intmax_t)ctx->stats.krbhst.tv_sec,
1294170808Sdelphij		(unsigned long)ctx->stats.krbhst.tv_usec, ctx->stid);
1295170808Sdelphij
1296188318Skib    if (freectx)
1297170808Sdelphij	krb5_sendto_ctx_free(context, ctx);
1298170808Sdelphij    else
1299170808Sdelphij	reset_context(context, ctx);
1300170808Sdelphij
1301170808Sdelphij    if (handle)
1302170808Sdelphij	krb5_krbhst_free(context, handle);
1303170808Sdelphij
1304229130Spho    return ret;
1305233385Sjhb}
1306233385Sjhb