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 (ntries), and it tried with a individual timeout of kdc_timeout / ntries.
50 * - TCP and HTTP requests are tried 1 time.
51 *
52 *  Total wait time is (number of addresses * 3) + kdc_timeout seconds.
53 *
54 */
55
56static int
57init_port(const char *s, int fallback)
58{
59    if (s) {
60	int tmp;
61
62	sscanf (s, "%d", &tmp);
63	return htons(tmp);
64    } else
65	return fallback;
66}
67
68struct send_via_plugin_s {
69    krb5_const_realm realm;
70    krb5_krbhst_info *hi;
71    time_t timeout;
72    const krb5_data *send_data;
73    krb5_data *receive;
74};
75
76
77static krb5_error_code KRB5_LIB_CALL
78kdccallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
79{
80    const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
81    struct send_via_plugin_s *ctx = userctx;
82
83    if (service->send_to_kdc == NULL)
84	return KRB5_PLUGIN_NO_HANDLE;
85    return service->send_to_kdc(context, plugctx, ctx->hi, ctx->timeout,
86				ctx->send_data, ctx->receive);
87}
88
89static krb5_error_code KRB5_LIB_CALL
90realmcallback(krb5_context context, const void *plug, void *plugctx, void *userctx)
91{
92    const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug;
93    struct send_via_plugin_s *ctx = userctx;
94
95    if (service->send_to_realm == NULL)
96	return KRB5_PLUGIN_NO_HANDLE;
97    return service->send_to_realm(context, plugctx, ctx->realm, ctx->timeout,
98				  ctx->send_data, ctx->receive);
99}
100
101static krb5_error_code
102kdc_via_plugin(krb5_context context,
103	       krb5_krbhst_info *hi,
104	       time_t timeout,
105	       const krb5_data *send_data,
106	       krb5_data *receive)
107{
108    struct send_via_plugin_s userctx;
109
110    userctx.realm = NULL;
111    userctx.hi = hi;
112    userctx.timeout = timeout;
113    userctx.send_data = send_data;
114    userctx.receive = receive;
115
116    return krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC,
117			     KRB5_PLUGIN_SEND_TO_KDC_VERSION_0, 0,
118			     &userctx, kdccallback);
119}
120
121static krb5_error_code
122realm_via_plugin(krb5_context context,
123		 krb5_const_realm realm,
124		 time_t timeout,
125		 const krb5_data *send_data,
126		 krb5_data *receive)
127{
128    struct send_via_plugin_s userctx;
129
130    userctx.realm = realm;
131    userctx.hi = NULL;
132    userctx.timeout = timeout;
133    userctx.send_data = send_data;
134    userctx.receive = receive;
135
136    return krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC,
137			     KRB5_PLUGIN_SEND_TO_KDC_VERSION_2, 0,
138			     &userctx, realmcallback);
139}
140
141struct krb5_sendto_ctx_data {
142    struct heim_base_uniq base;
143    int flags;
144    int type;
145    krb5_sendto_ctx_func func;
146    void *data;
147    char *hostname;
148    krb5_krbhst_handle krbhst;
149
150    /* context2 */
151    const krb5_data *send_data;
152    krb5_data response;
153    heim_array_t hosts;
154    const char *realm;
155    int stateflags;
156#define KRBHST_COMPLETED	1
157
158    /* prexmit */
159    krb5_sendto_prexmit prexmit_func;
160    void *prexmit_ctx;
161
162    /* stats */
163    struct {
164	struct timeval start_time;
165	struct timeval name_resolution;
166	struct timeval krbhst;
167	unsigned long sent_packets;
168	unsigned long num_hosts;
169    } stats;
170    unsigned int stid;
171};
172
173static void
174dealloc_sendto_ctx(void *ptr)
175{
176    krb5_sendto_ctx ctx = (krb5_sendto_ctx)ptr;
177    if (ctx->hostname)
178	free(ctx->hostname);
179    heim_release(ctx->hosts);
180    heim_release(ctx->krbhst);
181}
182
183KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
184krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx)
185{
186    *ctx = heim_uniq_alloc(sizeof(**ctx), "sendto-context", dealloc_sendto_ctx);
187    (*ctx)->hosts = heim_array_create();
188
189    return 0;
190}
191
192KRB5_LIB_FUNCTION void KRB5_LIB_CALL
193krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags)
194{
195    ctx->flags |= flags;
196}
197
198KRB5_LIB_FUNCTION int KRB5_LIB_CALL
199krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)
200{
201    return ctx->flags;
202}
203
204KRB5_LIB_FUNCTION void KRB5_LIB_CALL
205krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type)
206{
207    ctx->type = type;
208}
209
210KRB5_LIB_FUNCTION void KRB5_LIB_CALL
211krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx,
212			 krb5_sendto_ctx_func func,
213			 void *data)
214{
215    ctx->func = func;
216    ctx->data = data;
217}
218
219KRB5_LIB_FUNCTION void KRB5_LIB_CALL
220_krb5_sendto_ctx_set_prexmit(krb5_sendto_ctx ctx,
221			     krb5_sendto_prexmit prexmit,
222			     void *data)
223{
224    ctx->prexmit_func = prexmit;
225    ctx->prexmit_ctx = data;
226}
227
228KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
229krb5_sendto_set_hostname(krb5_context context,
230			 krb5_sendto_ctx ctx,
231			 const char *hostname)
232{
233    if (ctx->hostname == NULL)
234	free(ctx->hostname);
235    ctx->hostname = strdup(hostname);
236    if (ctx->hostname == NULL) {
237	krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
238	return ENOMEM;
239    }
240    return 0;
241}
242
243KRB5_LIB_FUNCTION void KRB5_LIB_CALL
244_krb5_sendto_ctx_set_krb5hst(krb5_context context,
245			     krb5_sendto_ctx ctx,
246			     krb5_krbhst_handle handle)
247{
248    heim_release(ctx->krbhst);
249    ctx->krbhst = heim_retain(handle);
250}
251
252KRB5_LIB_FUNCTION void KRB5_LIB_CALL
253krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx)
254{
255    heim_release(ctx);
256}
257
258krb5_error_code
259_krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data,
260		const krb5_data *reply, int *action)
261{
262    krb5_error_code ret;
263    KRB_ERROR error;
264
265    if(krb5_rd_error(context, reply, &error))
266	return 0;
267
268    ret = krb5_error_from_rd_error(context, &error, NULL);
269    krb5_free_error_contents(context, &error);
270
271    switch(ret) {
272    case KRB5KRB_ERR_RESPONSE_TOO_BIG: {
273	if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG)
274	    break;
275	krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG);
276	*action = KRB5_SENDTO_RESET;
277	break;
278    }
279    case KRB5KDC_ERR_SVC_UNAVAILABLE:
280	*action = KRB5_SENDTO_CONTINUE;
281	break;
282    }
283    return 0;
284}
285
286/*
287 *
288 */
289
290struct host;
291
292struct host_fun {
293    krb5_error_code (*prepare)(krb5_context, struct host *, const krb5_data *);
294    krb5_error_code (*send)(krb5_context, struct host *);
295    krb5_error_code (*recv)(krb5_context, struct host *, krb5_data *);
296    int ntries;
297};
298
299struct host {
300    struct heim_base_uniq base;
301    krb5_sendto_ctx ctx;
302    enum host_state { CONNECT, CONNECTING, CONNECTED, WAITING_REPLY, DEAD } state;
303    krb5_krbhst_info *hi;
304    struct addrinfo *ai;
305    rk_socket_t fd;
306    rk_socket_t fd2;
307    struct host_fun *fun;
308    unsigned int tries;
309    time_t timeout;
310    krb5_data data;
311    unsigned int tid;
312};
313
314static void
315debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
316	__attribute__((__format__(__printf__, 4, 5)));
317
318static void
319debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...)
320{
321    const char *proto = "unknown";
322    char name[NI_MAXHOST], port[NI_MAXSERV];
323    char *text = NULL;
324    va_list ap;
325    int ret;
326
327    if (!_krb5_have_debug(context, 5))
328	return;
329
330    va_start(ap, fmt);
331    ret = vasprintf(&text, fmt, ap);
332    va_end(ap);
333    if (ret == -1 || text == NULL)
334	return;
335
336    if (host->hi->proto == KRB5_KRBHST_HTTP)
337	proto = "http";
338    else if (host->hi->proto == KRB5_KRBHST_TCP)
339	proto = "tcp";
340    else if (host->hi->proto == KRB5_KRBHST_UDP)
341	proto = "udp";
342    else if (host->hi->proto == KRB5_KRBHST_KKDCP)
343	proto = "kkdcp";
344
345    if (host->ai == NULL ||
346	getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen,
347		    name, sizeof(name), port, sizeof(port), NI_NUMERICHOST) != 0)
348    {
349	name[0] = '\0';
350	port[0] = '\0';
351    }
352
353    _krb5_debugx(context, level, "%s: %s %s:%s (%s) tid: %08x",
354		 text, proto, name, port, host->hi->hostname, host->tid);
355    free(text);
356}
357
358
359static void
360deallocate_host(void *ptr)
361{
362    struct host *host = ptr;
363    if (!rk_IS_BAD_SOCKET(host->fd))
364	rk_closesocket(host->fd);
365    if (!rk_IS_BAD_SOCKET(host->fd2))
366	rk_closesocket(host->fd2);
367    krb5_data_free(&host->data);
368    host->ai = NULL;
369}
370
371static void
372host_dead(krb5_context context, struct host *host, const char *msg)
373{
374    debug_host(context, 5, host, "%s", msg);
375    rk_closesocket(host->fd);
376    host->fd = rk_INVALID_SOCKET;
377    host->state = DEAD;
378}
379
380static krb5_error_code
381send_stream(krb5_context context, struct host *host)
382{
383    ssize_t len;
384
385    len = write(host->fd, host->data.data, host->data.length);
386
387    if (len < 0)
388	return errno;
389    else if ((size_t)len < host->data.length) {
390	host->data.length -= len;
391	memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len);
392	return -1;
393    } else {
394	krb5_data_free(&host->data);
395	return 0;
396    }
397}
398
399static krb5_error_code
400recv_stream(krb5_context context, struct host *host)
401{
402    krb5_error_code ret;
403    size_t oldlen;
404    ssize_t sret;
405    int nbytes;
406
407    if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0) {
408	debug_host(context, 5, host, "failed to get nbytes from socket, no bytes there?");
409	return HEIM_NET_CONN_REFUSED;
410    }
411
412    if (context->max_msg_size - host->data.length < (size_t)nbytes) {
413	krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
414			       N_("TCP message from KDC too large %d", ""),
415			       (int)(host->data.length + nbytes));
416	return KRB5KRB_ERR_FIELD_TOOLONG;
417    }
418
419    oldlen = host->data.length;
420
421    ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */);
422    if (ret)
423	return ret;
424
425    sret = read(host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes);
426    if (sret <= 0) {
427	ret = errno;
428	debug_host(context, 5, host, "failed to read bytes from stream: %d", ret);
429	return ret;
430    }
431    host->data.length = oldlen + sret;
432    /* zero terminate for http transport */
433    ((uint8_t *)host->data.data)[host->data.length] = '\0';
434
435    return 0;
436}
437
438/*
439 *
440 */
441
442static krb5_error_code
443send_kkdcp(krb5_context context, struct host *host)
444{
445#ifdef __APPLE__
446    dispatch_queue_t q;
447    char *url = NULL;
448    char *path = host->hi->path;
449    __block krb5_data data;
450
451    q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
452
453    heim_retain(host);
454    heim_retain(host->ctx);
455
456    if (path == NULL)
457	path = "";
458
459    if (host->hi->def_port != host->hi->port)
460	asprintf(&url, "https://%s:%d/%s", host->hi->hostname, host->hi->port, path);
461    else
462	asprintf(&url, "https://%s/%s", host->hi->hostname, path);
463    if (url == NULL)
464	return ENOMEM;
465
466    data = host->data;
467    krb5_data_zero(&host->data);
468
469    debug_host(context, 5, host, "sending request to: %s", url);
470
471    heim_retain(context);
472
473    dispatch_async(q, ^{
474	    krb5_error_code ret;
475	    krb5_data retdata;
476
477 	    krb5_data_zero(&retdata);
478
479	    ret = _krb5_kkdcp_request(context, host->ctx->realm, url,
480				      &data, &retdata);
481	    krb5_data_free(&data);
482 	    free(url);
483 	    if (ret == 0) {
484		uint8_t length[4];
485
486		debug_host(context, 5, host, "kkdcp: got %d bytes, feeding them back", (int)retdata.length);
487
488		_krb5_put_int(length, retdata.length, 4);
489		krb5_net_write_block(context, &host->fd2, length, sizeof(length), 2);
490		krb5_net_write_block(context, &host->fd2, retdata.data, retdata.length, 2);
491	    }
492
493 	    close(host->fd2);
494	    host->fd2 = -1;
495	    heim_release(host->ctx);
496 	    heim_release(host);
497	    heim_release(context);
498 	});
499     return 0;
500#else
501     close(host->fd2);
502     host->fd2 = -1;
503#endif
504}
505
506/*
507 *
508 */
509
510static void
511host_next_timeout(krb5_context context, struct host *host)
512{
513    host->timeout = context->kdc_timeout / host->fun->ntries;
514    if (host->timeout == 0)
515	host->timeout = 1;
516
517    host->timeout += time(NULL);
518}
519
520/*
521 * connected host
522 */
523
524static void
525host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
526{
527    krb5_error_code ret;
528
529    host->state = CONNECTED;
530    /*
531     * Now prepare data to send to host
532     */
533    if (ctx->prexmit_func) {
534	krb5_data data;
535
536	krb5_data_zero(&data);
537
538	ret = ctx->prexmit_func(context, host->hi->proto,
539				ctx->prexmit_ctx, host->fd, &data);
540	if (ret == 0) {
541	    if (data.length == 0) {
542		host_dead(context, host, "prexmit function didn't send data");
543		return;
544	    }
545	    ret = host->fun->prepare(context, host, &data);
546	    krb5_data_free(&data);
547	}
548
549    } else {
550	ret = host->fun->prepare(context, host, ctx->send_data);
551    }
552    if (ret)
553	debug_host(context, 5, host, "failed to prexmit/prepare");
554}
555
556/*
557 * connect host
558 */
559
560static void
561host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host)
562{
563    krb5_krbhst_info *hi = host->hi;
564    struct addrinfo *ai = host->ai;
565
566    debug_host(context, 5, host, "connecting to host");
567
568    if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) {
569	if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) {
570	    debug_host(context, 5, host, "connecting to %d", host->fd);
571	    host->state = CONNECTING;
572	} else {
573	    host_dead(context, host, "failed to connect");
574	}
575    } else {
576	host_connected(context, ctx, host);
577    }
578
579    host_next_timeout(context, host);
580}
581
582/*
583 * HTTP transport
584 */
585
586static krb5_error_code
587prepare_http(krb5_context context, struct host *host, const krb5_data *data)
588{
589    char *str = NULL, *request = NULL;
590    krb5_error_code ret;
591    int len;
592
593    heim_assert(host->data.length == 0, "prepare_http called twice");
594
595    len = base64_encode(data->data, (int)data->length, &str);
596    if(len < 0)
597	return ENOMEM;
598
599    if (context->http_proxy)
600	ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str);
601    else
602	ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str);
603    free(str);
604    if(ret < 0 || request == NULL)
605	return ENOMEM;
606
607    host->data.data = request;
608    host->data.length = strlen(request);
609
610    return 0;
611}
612
613static krb5_error_code
614recv_http(krb5_context context, struct host *host, krb5_data *data)
615{
616    krb5_error_code ret;
617    unsigned long rep_len;
618    size_t len;
619    char *p;
620
621    /*
622     * recv_stream returns a NUL terminated stream
623     */
624
625    ret = recv_stream(context, host);
626    if (ret)
627	return ret;
628
629    p = strstr(host->data.data, "\r\n\r\n");
630    if (p == NULL)
631	return -1;
632    p += 4;
633
634    len = host->data.length - (p - (char *)host->data.data);
635    if (len < 4)
636	return KRB5KRB_ERR_FIELD_TOOLONG;
637
638    _krb5_get_int(p, &rep_len, 4);
639    if (len < rep_len)
640	return -1;
641
642    p += 4;
643
644    memmove(host->data.data, p, rep_len);
645    host->data.length = rep_len;
646
647    *data = host->data;
648    krb5_data_zero(&host->data);
649
650    return 0;
651}
652
653/*
654 * TCP transport
655 */
656
657static krb5_error_code
658prepare_tcp(krb5_context context, struct host *host, const krb5_data *data)
659{
660    krb5_error_code ret;
661    krb5_storage *sp;
662
663    heim_assert(host->data.length == 0, "prepare_tcp called twice");
664
665    sp = krb5_storage_emem();
666    if (sp == NULL)
667	return ENOMEM;
668
669    ret = krb5_store_data(sp, *data);
670    if (ret) {
671	krb5_storage_free(sp);
672	return ret;
673    }
674    ret = krb5_storage_to_data(sp, &host->data);
675    krb5_storage_free(sp);
676
677    return ret;
678}
679
680static krb5_error_code
681recv_tcp(krb5_context context, struct host *host, krb5_data *data)
682{
683    krb5_error_code ret;
684    unsigned long pktlen;
685
686    ret = recv_stream(context, host);
687    if (ret)
688	return ret;
689
690    if (host->data.length < 4)
691	return -1;
692
693    _krb5_get_int(host->data.data, &pktlen, 4);
694
695    if (pktlen > host->data.length - 4)
696	return -1;
697
698    memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4);
699    host->data.length -= 4;
700
701    *data = host->data;
702    krb5_data_zero(&host->data);
703
704    return 0;
705}
706
707/*
708 * UDP transport
709 */
710
711static krb5_error_code
712prepare_udp(krb5_context context, struct host *host, const krb5_data *data)
713{
714    return krb5_data_copy(&host->data, data->data, data->length);
715}
716
717static krb5_error_code
718send_udp(krb5_context context, struct host *host)
719{
720    if (send(host->fd, host->data.data, host->data.length, 0) < 0)
721	return errno;
722    return 0;
723}
724
725static krb5_error_code
726recv_udp(krb5_context context, struct host *host, krb5_data *data)
727{
728    krb5_error_code ret;
729    int nbytes;
730    ssize_t sret;
731
732    if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0) {
733	debug_host(context, 5, host, "failed to get nbytes from socket, no bytes there?");
734	return HEIM_NET_CONN_REFUSED;
735    }
736
737    if (nbytes > context->max_msg_size) {
738	debug_host(context, 5, host, "server sent too large message %d (max is %d)",
739		   (int)nbytes, (int)context->max_msg_size);
740	krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG,
741			       N_("UDP message from KDC too large %d", ""),
742			       (int)nbytes);
743	return KRB5KRB_ERR_FIELD_TOOLONG;
744    }
745
746    ret = krb5_data_alloc(data, nbytes);
747    if (ret)
748	return ret;
749
750    sret = recv(host->fd, data->data, data->length, 0);
751    if (sret < 0) {
752	debug_host(context, 5, host, "read data from nbytes from host: %d", errno);
753	ret = errno;
754	krb5_data_free(data);
755	return ret;
756    }
757    data->length = sret;
758
759    return 0;
760}
761
762static struct host_fun http_fun = {
763    prepare_http,
764    send_stream,
765    recv_http,
766    1
767};
768static struct host_fun kkdcp_fun = {
769    prepare_udp,
770    send_kkdcp,
771    recv_tcp,
772    1
773};
774static struct host_fun tcp_fun = {
775    prepare_tcp,
776    send_stream,
777    recv_tcp,
778    1
779};
780static struct host_fun udp_fun = {
781    prepare_udp,
782    send_udp,
783    recv_udp,
784    3
785};
786
787
788/*
789 * Host state machine
790 */
791
792static int
793eval_host_state(krb5_context context,
794		krb5_sendto_ctx ctx,
795		struct host *host,
796		int readable, int writeable)
797{
798    krb5_error_code ret;
799
800    if (host->state == CONNECTING && writeable)
801	host_connected(context, ctx, host);
802
803    if (readable) {
804
805	debug_host(context, 5, host, "reading packet");
806
807	ret = host->fun->recv(context, host, &ctx->response);
808	if (ret == -1) {
809	    /* not done yet */
810	} else if (ret == 0) {
811	    /* if recv_foo function returns 0, we have a complete reply */
812	    debug_host(context, 5, host, "host completed");
813	    return 1;
814	} else {
815	    host_dead(context, host, "host disconnected");
816	}
817    }
818
819    /* check if there is anything to send, state might DEAD after read */
820    if (writeable && host->state == CONNECTED) {
821
822	ctx->stats.sent_packets++;
823
824	debug_host(context, 5, host, "writing packet");
825
826	ret = host->fun->send(context, host);
827	if (ret == -1) {
828	    /* not done yet */
829	} else if (ret) {
830	    host_dead(context, host, "host dead, write failed");
831	} else
832	    host->state = WAITING_REPLY;
833    }
834
835    return 0;
836}
837
838/*
839 *
840 */
841
842static struct host *
843host_create(krb5_context context,
844	    krb5_sendto_ctx ctx,
845	    krb5_krbhst_info *hi,
846	    struct addrinfo *ai,
847	    int fd)
848{
849    struct host *host;
850
851    host = heim_uniq_alloc(sizeof(*host), "sendto-host", deallocate_host);
852    if (host == NULL)
853	    return ENOMEM;
854
855    host->hi = hi;
856    host->fd = fd;
857    host->fd2 = -1;
858    host->ai = ai;
859    host->ctx = ctx;
860    /* next version of stid */
861    host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1);
862
863    host->state = CONNECT;
864
865    switch (host->hi->proto) {
866    case KRB5_KRBHST_HTTP :
867	host->fun = &http_fun;
868	break;
869    case KRB5_KRBHST_KKDCP :
870	host->fun = &kkdcp_fun;
871	break;
872    case KRB5_KRBHST_TCP :
873	host->fun = &tcp_fun;
874	break;
875    case KRB5_KRBHST_UDP :
876	host->fun = &udp_fun;
877	break;
878    }
879
880    host->tries = host->fun->ntries;
881
882    heim_array_append_value(ctx->hosts, host);
883
884    return host;
885}
886
887
888/*
889 *
890 */
891
892static krb5_error_code
893submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi)
894{
895    unsigned long submitted_host = 0;
896    krb5_boolean freeai = FALSE;
897    struct timeval nrstart, nrstop;
898    krb5_error_code ret;
899    struct addrinfo *ai = NULL, *a;
900    struct host *host;
901
902    ret = kdc_via_plugin(context, hi, context->kdc_timeout,
903			 ctx->send_data, &ctx->response);
904    if (ret == 0) {
905	return 0;
906    } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
907	_krb5_debugx(context, 5, "send via plugin failed %s: %d",
908		     hi->hostname, ret);
909	return ret;
910    }
911
912    /*
913     * If we have a proxy, let use the address of the proxy instead of
914     * the KDC and let the proxy deal with the resolving of the KDC.
915     */
916
917    gettimeofday(&nrstart, NULL);
918
919    if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
920	char *proxy2 = strdup(context->http_proxy);
921	char *el, *proxy  = proxy2;
922	struct addrinfo hints;
923	char portstr[NI_MAXSERV];
924
925	if (proxy == NULL)
926	    return ENOMEM;
927	if (strncmp(proxy, "http://", 7) == 0)
928	    proxy += 7;
929
930	/* check for url terminating slash */
931	el = strchr(proxy, '/');
932	if (el != NULL)
933	    *el = '\0';
934
935	/* check for port in hostname, used below as port */
936	el = strchr(proxy, ':');
937	if(el != NULL)
938	    *el++ = '\0';
939
940	memset(&hints, 0, sizeof(hints));
941	hints.ai_family   = PF_UNSPEC;
942	hints.ai_socktype = SOCK_STREAM;
943
944	snprintf(portstr, sizeof(portstr), "%d",
945		 ntohs(init_port(el, htons(80))));
946
947	ret = getaddrinfo(proxy, portstr, &hints, &ai);
948	free(proxy2);
949	if (ret)
950	    return krb5_eai_to_heim_errno(ret, errno);
951
952	freeai = TRUE;
953    } else if (hi->proto == KRB5_KRBHST_KKDCP) {
954	ai = NULL;
955    } else {
956	ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
957	if (ret)
958	    return ret;
959    }
960
961    /* add up times */
962    gettimeofday(&nrstop, NULL);
963    timevalsub(&nrstop, &nrstart);
964    timevaladd(&ctx->stats.name_resolution, &nrstop);
965
966    ctx->stats.num_hosts++;
967
968    for (a = ai; a != NULL; a = a->ai_next) {
969	rk_socket_t fd;
970
971	fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
972	if (rk_IS_BAD_SOCKET(fd))
973	    continue;
974	rk_cloexec(fd);
975	socket_set_nopipe(fd, 1);
976	socket_set_nonblocking(fd, 1);
977
978#ifndef NO_LIMIT_FD_SETSIZE
979	if (fd >= FD_SETSIZE) {
980	    _krb5_debugx(context, 0, "fd too large for select");
981	    rk_closesocket(fd);
982	    continue;
983	}
984#endif
985	host = host_create(context, ctx, hi, a, fd);
986	if (host == NULL) {
987	    rk_closesocket(fd);
988	    continue;
989	}
990
991	/*
992	 * Connect directly next host, wait a host_timeout for each next address
993	 */
994	if (submitted_host == 0)
995	    host_connect(context, ctx, host);
996	else {
997	    debug_host(context, 5, host,
998		       "Queuing host in future (in %ds), "
999		       "its the %lu address on the same name",
1000		       (int)(context->host_timeout * submitted_host),
1001		       submitted_host + 1);
1002	    host->timeout = time(NULL) + (submitted_host * context->host_timeout);
1003	}
1004
1005	submitted_host++;
1006	heim_release(host);
1007    }
1008
1009    if (hi->proto == KRB5_KRBHST_KKDCP) {
1010	int fds[2];
1011
1012	heim_assert(ai == NULL, "kkdcp host with ai ?");
1013
1014	if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) < 0)
1015	    return KRB5_KDC_UNREACH;
1016
1017	socket_set_nopipe(fds[0], 1);
1018	socket_set_nopipe(fds[1], 1);
1019	socket_set_nonblocking(fds[0], 1);
1020	socket_set_nonblocking(fds[1], 1);
1021
1022	host = host_create(context, ctx, hi, NULL, fds[0]);
1023	if (host == NULL) {
1024	    close(fds[0]);
1025	    close(fds[1]);
1026	    return ENOMEM;
1027	}
1028	host->fd2 = fds[1];
1029
1030	host_next_timeout(context, host);
1031	host_connected(context, ctx, host);
1032
1033	submitted_host++;
1034	heim_release(host);
1035    }
1036
1037
1038    if (freeai)
1039	freeaddrinfo(ai);
1040
1041    if (!submitted_host)
1042	return KRB5_KDC_UNREACH;
1043
1044    return 0;
1045}
1046
1047static void
1048set_fd_status(struct host *h, fd_set *rfds, fd_set *wfds, int *max_fd)
1049{
1050#ifndef NO_LIMIT_FD_SETSIZE
1051    heim_assert(h->fd < FD_SETSIZE, "fd too large");
1052#endif
1053    switch (h->state) {
1054    case WAITING_REPLY:
1055	FD_SET(h->fd, rfds);
1056	break;
1057    case CONNECTING:
1058    case CONNECTED:
1059	FD_SET(h->fd, rfds);
1060	FD_SET(h->fd, wfds);
1061	break;
1062    case DEAD:
1063    case CONNECT:
1064	break;
1065    }
1066    if (h->fd > *max_fd)
1067	*max_fd = h->fd + 1;
1068}
1069
1070
1071
1072static krb5_error_code
1073wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx)
1074{
1075    __block struct host *next_pending = NULL;
1076    __block fd_set rfds, wfds;
1077    __block int max_fd = 0;
1078    __block int ret;
1079    struct timeval tv;
1080    time_t timenow;
1081
1082    FD_ZERO(&rfds);
1083    FD_ZERO(&wfds);
1084
1085    /* oh, we have a reply, it must be a plugin that got it for us */
1086    if (ctx->response.length) {
1087	*action = KRB5_SENDTO_FILTER;
1088	return 0;
1089    }
1090
1091    timenow = time(NULL);
1092
1093    heim_array_iterate(ctx->hosts, ^(heim_object_t obj, int *stop) {
1094	    struct host *h = (struct host *)obj;
1095
1096	    /* skip dead hosts */
1097	    if (h->state == DEAD)
1098		return;
1099
1100	    /*
1101	     * process submitted by pending hosts here
1102	     */
1103	    if (h->state == CONNECT) {
1104		if (h->timeout < timenow) {
1105		    host_connect(context, ctx, h);
1106		} else if (next_pending == NULL || next_pending->timeout > h->timeout) {
1107		    next_pending = h;
1108		    return;
1109		} else {
1110		    return;
1111		}
1112	    }
1113
1114	    /* if host timed out, dec tries and (retry or kill host) */
1115	    if (h->timeout < timenow) {
1116		heim_assert(h->tries != 0, "tries should not reach 0");
1117		h->tries--;
1118		if (h->tries == 0) {
1119		    host_dead(context, h, "host timed out");
1120		    return;
1121		} else {
1122		    debug_host(context, 5, h, "retrying sending to");
1123		    host_next_timeout(context, h);
1124		    host_connected(context, ctx, h);
1125		}
1126	    }
1127
1128	    set_fd_status(h, &rfds, &wfds, &max_fd);
1129	});
1130
1131    /*
1132     * We have no host to wait for, but there is one pending, lets
1133     * kick that one off.
1134     */
1135    if (max_fd == 0 && next_pending) {
1136	time_t forward = next_pending->timeout - timenow;
1137
1138	host_connect(context, ctx, next_pending);
1139	set_fd_status(next_pending, &rfds, &wfds, &max_fd);
1140
1141	/*
1142	 * Move all waiting host forward in time too, only if the next
1143	 * host didn't happen the same about the same time as the last
1144	 * expiration
1145	*/
1146	if (forward > 0) {
1147	    heim_array_iterate(ctx->hosts, ^(heim_object_t obj, int *stop) {
1148		    struct host *h = (struct host *)obj;
1149
1150		    if (h->state != CONNECT)
1151			return;
1152		    h->timeout -= forward;
1153		    if (h->timeout < timenow)
1154			h->timeout = timenow;
1155		});
1156	}
1157    }
1158
1159    heim_array_filter(ctx->hosts, ^(heim_object_t obj) {
1160	    struct host *h = (struct host *)obj;
1161	    return (int)((h->state == DEAD) ? true : false);
1162	});
1163
1164    if (heim_array_get_length(ctx->hosts) == 0) {
1165	if (ctx->stateflags & KRBHST_COMPLETED) {
1166	    _krb5_debugx(context, 5, "no more hosts to send/recv packets to/from "
1167			 "trying to pulling more hosts");
1168	    *action = KRB5_SENDTO_FAILED;
1169	} else {
1170	    _krb5_debugx(context, 5, "no more hosts to send/recv packets to/from "
1171			 "and no more hosts -> failure");
1172	    *action = KRB5_SENDTO_TIMEOUT;
1173	}
1174	return 0;
1175    }
1176
1177    tv.tv_sec = 1;
1178    tv.tv_usec = 0;
1179
1180    ret = select(max_fd + 1, &rfds, &wfds, NULL, &tv);
1181    if (ret < 0) {
1182	if (errno != EAGAIN || errno != EINTR)
1183	    return errno;
1184	ret = 0;
1185    }
1186    if (ret == 0) {
1187	*action = KRB5_SENDTO_TIMEOUT;
1188	return 0;
1189    }
1190
1191    ret = 0;
1192    heim_array_iterate(ctx->hosts, ^(heim_object_t obj, int *stop) {
1193	    struct host *h = (struct host *)obj;
1194	    int readable, writeable;
1195	    heim_assert(h->state != DEAD, "dead host resurected");
1196
1197#ifndef NO_LIMIT_FD_SETSIZE
1198	    heim_assert(h->fd < FD_SETSIZE, "fd too large");
1199#endif
1200	    readable = FD_ISSET(h->fd, &rfds);
1201	    writeable = FD_ISSET(h->fd, &wfds);
1202
1203	    if (readable || writeable)
1204		ret |= eval_host_state(context, ctx, h, readable, writeable);
1205
1206	    /* if there is already a reply, just fall though the array */
1207	    if (ret)
1208		*stop = 1;
1209	});
1210    if (ret)
1211	*action = KRB5_SENDTO_FILTER;
1212    else
1213	*action = KRB5_SENDTO_CONTINUE;
1214
1215    return 0;
1216}
1217
1218static void
1219reset_context(krb5_context context, krb5_sendto_ctx ctx)
1220{
1221    krb5_data_free(&ctx->response);
1222    heim_release(ctx->hosts);
1223    ctx->hosts = heim_array_create();
1224    ctx->stateflags = 0;
1225}
1226
1227
1228/*
1229 *
1230 */
1231
1232KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
1233krb5_sendto_context(krb5_context context,
1234		    krb5_sendto_ctx ctx,
1235		    const krb5_data *send_data,
1236		    krb5_const_realm realm,
1237		    krb5_data *receive)
1238{
1239    krb5_error_code ret = KRB5_KDC_UNREACH;
1240    krb5_krbhst_handle handle = NULL;
1241    struct timeval nrstart, nrstop, stop_time;
1242    int type, freectx = 0;
1243    int action;
1244    int numreset = 0;
1245
1246    krb5_data_zero(receive);
1247
1248    HEIM_WARN_BLOCKING("krb5_sendto_context", warn_once);
1249
1250    if (ctx == NULL) {
1251	ret = krb5_sendto_ctx_alloc(context, &ctx);
1252	if (ret)
1253	    goto out;
1254	freectx = 1;
1255    }
1256
1257    memset(&ctx->stats, 0, sizeof(ctx->stats));
1258    gettimeofday(&ctx->stats.start_time, NULL);
1259
1260    ctx->realm = realm;
1261    ctx->stid = (context->num_kdc_requests++) << 16;
1262
1263    type = ctx->type;
1264    if (type == 0) {
1265	if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
1266	    type = KRB5_KRBHST_ADMIN;
1267	else
1268	    type = KRB5_KRBHST_KDC;
1269    }
1270
1271    ctx->send_data = send_data;
1272
1273    if ((int)send_data->length > context->large_msg_size)
1274	ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
1275
1276    /* loop until we get back a appropriate response */
1277
1278    action = KRB5_SENDTO_INITIAL;
1279
1280    while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) {
1281	krb5_krbhst_info *hi;
1282
1283	switch (action) {
1284	case KRB5_SENDTO_INITIAL:
1285	    ret = realm_via_plugin(context, realm, context->kdc_timeout,
1286				   send_data, receive);
1287	    if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) {
1288		action = KRB5_SENDTO_DONE;
1289		break;
1290	    }
1291	    action = KRB5_SENDTO_KRBHST;
1292	    /* FALLTHOUGH */
1293	case KRB5_SENDTO_KRBHST:
1294	    if (ctx->krbhst == NULL) {
1295		ret = krb5_krbhst_init_flags(context, realm, type,
1296					     ctx->flags, &handle);
1297		if (ret)
1298		    goto out;
1299
1300		if (ctx->hostname) {
1301		    ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname);
1302		    if (ret)
1303			goto out;
1304		}
1305	    } else {
1306		handle = heim_retain(ctx->krbhst);
1307	    }
1308	    action = KRB5_SENDTO_TIMEOUT;
1309	    /* FALLTHOUGH */
1310	case KRB5_SENDTO_TIMEOUT:
1311
1312	    /*
1313	     * If we completed, just got to next step
1314	     */
1315
1316	    if (ctx->stateflags & KRBHST_COMPLETED) {
1317		action = KRB5_SENDTO_CONTINUE;
1318		break;
1319	    }
1320
1321	    /*
1322	     * Pull out next host, if there is no more, close the
1323	     * handle and mark as completed.
1324	     *
1325	     * Collect time spent in krbhst (dns, plugin, etc)
1326	     */
1327
1328
1329	    gettimeofday(&nrstart, NULL);
1330
1331	    ret = krb5_krbhst_next(context, handle, &hi);
1332
1333	    gettimeofday(&nrstop, NULL);
1334	    timevalsub(&nrstop, &nrstart);
1335	    timevaladd(&ctx->stats.krbhst, &nrstop);
1336
1337	    action = KRB5_SENDTO_CONTINUE;
1338	    if (ret == 0) {
1339		_krb5_debugx(context, 5, "submissing new requests to new host");
1340		if (submit_request(context, ctx, hi) != 0)
1341		    action = KRB5_SENDTO_TIMEOUT;
1342	    } else {
1343		_krb5_debugx(context, 5, "out of hosts, waiting for replies");
1344		ctx->stateflags |= KRBHST_COMPLETED;
1345	    }
1346
1347	    break;
1348	case KRB5_SENDTO_CONTINUE:
1349
1350	    ret = wait_response(context, &action, ctx);
1351	    if (ret)
1352		goto out;
1353
1354	    break;
1355	case KRB5_SENDTO_RESET:
1356	    /* start over */
1357	    _krb5_debugx(context, 5,
1358			"krb5_sendto trying over again (reset): %d",
1359			numreset);
1360	    reset_context(context, ctx);
1361	    if (handle) {
1362		krb5_krbhst_free(context, handle);
1363		handle = NULL;
1364	    }
1365	    numreset++;
1366	    if (numreset >= 3)
1367		action = KRB5_SENDTO_FAILED;
1368	    else
1369		action = KRB5_SENDTO_KRBHST;
1370
1371	    ret = 0;
1372	    break;
1373	case KRB5_SENDTO_FILTER:
1374	    /* default to next state, the filter function might modify this */
1375	    action = KRB5_SENDTO_DONE;
1376
1377	    if (ctx->func) {
1378		ret = (*ctx->func)(context, ctx, ctx->data,
1379				   &ctx->response, &action);
1380		if (ret)
1381		    goto out;
1382	    }
1383	    break;
1384	case KRB5_SENDTO_FAILED:
1385	    ret = KRB5_KDC_UNREACH;
1386	    break;
1387	case KRB5_SENDTO_DONE:
1388	    ret = 0;
1389	    break;
1390	default:
1391	    heim_abort("invalid krb5_sendto_context action: %d", (int)action);
1392	}
1393    }
1394
1395 out:
1396    gettimeofday(&stop_time, NULL);
1397    timevalsub(&stop_time, &ctx->stats.start_time);
1398
1399    if (ret == 0 && ctx->response.length) {
1400	*receive = ctx->response;
1401	krb5_data_zero(&ctx->response);
1402    } else {
1403	krb5_data_free(&ctx->response);
1404	krb5_clear_error_message (context);
1405	ret = KRB5_KDC_UNREACH;
1406	krb5_set_error_message(context, ret,
1407			       N_("unable to reach any KDC in realm %s, tried %lu %s", ""),
1408			       realm, ctx->stats.num_hosts,
1409			       ctx->stats.num_hosts == 1 ? "KDC" : "KDCs");
1410    }
1411
1412    _krb5_debugx(context, 1,
1413		 "krb5_sendto_context %s done: %d hosts %lu packets %lu wc: %ld.%06d nr: %ld.%06d kh: %ld.%06d tid: %08x",
1414		 ctx->realm, ret,
1415		 ctx->stats.num_hosts, ctx->stats.sent_packets,
1416		 stop_time.tv_sec, stop_time.tv_usec,
1417		 ctx->stats.name_resolution.tv_sec, ctx->stats.name_resolution.tv_usec,
1418		 ctx->stats.krbhst.tv_sec, ctx->stats.krbhst.tv_usec, ctx->stid);
1419
1420
1421    if (freectx)
1422	krb5_sendto_ctx_free(context, ctx);
1423    else
1424	reset_context(context, ctx);
1425
1426    if (handle)
1427	krb5_krbhst_free(context, handle);
1428
1429    return ret;
1430}
1431