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