xref: /freebsd/crypto/krb5/src/lib/krb5/os/sendto_kdc.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/os/sendto_kdc.c */
3 /*
4  * Copyright 1990,1991,2001,2002,2004,2005,2007,2008 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26 /*
27  * MS-KKDCP implementation Copyright 2013,2014 Red Hat, Inc.
28  *
29  * Redistribution and use in source and binary forms, with or without
30  * modification, are permitted provided that the following conditions are met:
31  *
32  *    1. Redistributions of source code must retain the above copyright
33  *       notice, this list of conditions and the following disclaimer.
34  *
35  *    2. Redistributions in binary form must reproduce the above copyright
36  *       notice, this list of conditions and the following disclaimer in
37  *       the documentation and/or other materials provided with the
38  *       distribution.
39  *
40  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
41  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
42  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
43  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
44  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
45  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
46  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
47  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
48  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
49  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
50  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51  */
52 
53 /* Send packet to KDC for realm; wait for response, retransmitting
54  * as necessary. */
55 
56 #include "k5-int.h"
57 #include "k5-tls.h"
58 #include "fake-addrinfo.h"
59 
60 #include "os-proto.h"
61 
62 #if defined(HAVE_POLL_H)
63 #include <poll.h>
64 #define USE_POLL
65 #define MAX_POLLFDS 1024
66 #elif defined(HAVE_SYS_SELECT_H)
67 #include <sys/select.h>
68 #endif
69 
70 #ifndef _WIN32
71 /* For FIONBIO.  */
72 #include <sys/ioctl.h>
73 #ifdef HAVE_SYS_FILIO_H
74 #include <sys/filio.h>
75 #endif
76 #endif
77 
78 #define MAX_PASS                    3
79 #define DEFAULT_UDP_PREF_LIMIT   1465
80 #define HARD_UDP_LIMIT          32700 /* could probably do 64K-epsilon ? */
81 #define PORT_LENGTH                 6 /* decimal repr of UINT16_MAX */
82 
83 /* Select state flags.  */
84 #define SSF_READ 0x01
85 #define SSF_WRITE 0x02
86 #define SSF_EXCEPTION 0x04
87 
88 typedef int64_t time_ms;
89 
90 /* This can be pretty large, so should not be stack-allocated. */
91 struct select_state {
92 #ifdef USE_POLL
93     struct pollfd fds[MAX_POLLFDS];
94 #else
95     int max;
96     fd_set rfds, wfds, xfds;
97 #endif
98     int nfds;
99 };
100 
101 /* connection states */
102 enum conn_states { INITIALIZING, CONNECTING, WRITING, READING, FAILED };
103 struct incoming_message {
104     size_t bufsizebytes_read;
105     size_t bufsize;
106     size_t pos;
107     char *buf;
108     unsigned char bufsizebytes[4];
109     size_t n_left;
110 };
111 
112 struct outgoing_message {
113     sg_buf sgbuf[2];
114     sg_buf *sgp;
115     int sg_count;
116     unsigned char msg_len_buf[4];
117 };
118 
119 struct conn_state;
120 typedef krb5_boolean fd_handler_fn(krb5_context context,
121                                    const krb5_data *realm,
122                                    struct conn_state *conn,
123                                    struct select_state *selstate);
124 
125 struct conn_state {
126     SOCKET fd;
127     enum conn_states state;
128     fd_handler_fn *service_connect;
129     fd_handler_fn *service_write;
130     fd_handler_fn *service_read;
131     struct remote_address addr;
132     struct incoming_message in;
133     struct outgoing_message out;
134     krb5_data callback_buffer;
135     size_t server_index;
136     struct conn_state *next;
137     time_ms endtime;
138     krb5_boolean defer;
139     struct {
140         const char *uri_path;
141         const char *servername;
142         char port[PORT_LENGTH];
143         char *https_request;
144         k5_tls_handle tls;
145     } http;
146 };
147 
148 /* Set up context->tls.  On allocation failure, return ENOMEM.  On plugin load
149  * failure, set context->tls to point to a nulled vtable and return 0. */
150 static krb5_error_code
init_tls_vtable(krb5_context context)151 init_tls_vtable(krb5_context context)
152 {
153     krb5_plugin_initvt_fn initfn;
154     krb5_error_code ret;
155 
156     if (context->tls != NULL)
157         return 0;
158 
159     context->tls = calloc(1, sizeof(*context->tls));
160     if (context->tls == NULL)
161         return ENOMEM;
162 
163     /* Attempt to load the module; just let it stay nulled out on failure. */
164     k5_plugin_register_dyn(context, PLUGIN_INTERFACE_TLS, "k5tls", "tls");
165     ret = k5_plugin_load(context, PLUGIN_INTERFACE_TLS, "k5tls", &initfn);
166     if (!ret)
167         (*initfn)(context, 0, 0, (krb5_plugin_vtable)context->tls);
168     else
169         TRACE_SENDTO_KDC_K5TLS_LOAD_ERROR(context, ret);
170 
171     return 0;
172 }
173 
174 /* Get current time in milliseconds. */
175 static krb5_error_code
get_curtime_ms(time_ms * time_out)176 get_curtime_ms(time_ms *time_out)
177 {
178     struct timeval tv;
179 
180     *time_out = 0;
181 
182     if (gettimeofday(&tv, 0))
183         return errno;
184     *time_out = (time_ms)tv.tv_sec * 1000 + tv.tv_usec / 1000;
185     return 0;
186 }
187 
188 static void
free_http_tls_data(krb5_context context,struct conn_state * state)189 free_http_tls_data(krb5_context context, struct conn_state *state)
190 {
191     if (state->http.tls != NULL)
192         context->tls->free_handle(context, state->http.tls);
193     state->http.tls = NULL;
194     free(state->http.https_request);
195     state->http.https_request = NULL;
196 }
197 
198 #ifdef USE_POLL
199 
200 /* Find a pollfd in selstate by fd, or abort if we can't find it. */
201 static inline struct pollfd *
find_pollfd(struct select_state * selstate,int fd)202 find_pollfd(struct select_state *selstate, int fd)
203 {
204     int i;
205 
206     for (i = 0; i < selstate->nfds; i++) {
207         if (selstate->fds[i].fd == fd)
208             return &selstate->fds[i];
209     }
210     abort();
211 }
212 
213 static void
cm_init_selstate(struct select_state * selstate)214 cm_init_selstate(struct select_state *selstate)
215 {
216     selstate->nfds = 0;
217 }
218 
219 static krb5_boolean
cm_add_fd(struct select_state * selstate,int fd)220 cm_add_fd(struct select_state *selstate, int fd)
221 {
222     if (selstate->nfds >= MAX_POLLFDS)
223         return FALSE;
224     selstate->fds[selstate->nfds].fd = fd;
225     selstate->fds[selstate->nfds].events = 0;
226     selstate->nfds++;
227     return TRUE;
228 }
229 
230 static void
cm_remove_fd(struct select_state * selstate,int fd)231 cm_remove_fd(struct select_state *selstate, int fd)
232 {
233     struct pollfd *pfd = find_pollfd(selstate, fd);
234 
235     *pfd = selstate->fds[selstate->nfds - 1];
236     selstate->nfds--;
237 }
238 
239 /* Poll for reading (and not writing) on fd the next time we poll. */
240 static void
cm_read(struct select_state * selstate,int fd)241 cm_read(struct select_state *selstate, int fd)
242 {
243     find_pollfd(selstate, fd)->events = POLLIN;
244 }
245 
246 /* Poll for writing (and not reading) on fd the next time we poll. */
247 static void
cm_write(struct select_state * selstate,int fd)248 cm_write(struct select_state *selstate, int fd)
249 {
250     find_pollfd(selstate, fd)->events = POLLOUT;
251 }
252 
253 /* Get the output events for fd in the form of ssflags. */
254 static unsigned int
cm_get_ssflags(struct select_state * selstate,int fd)255 cm_get_ssflags(struct select_state *selstate, int fd)
256 {
257     struct pollfd *pfd = find_pollfd(selstate, fd);
258 
259     /*
260      * macOS sets POLLHUP without POLLOUT on connection error.  Catch this as
261      * well as other error events such as POLLNVAL, but only if POLLIN and
262      * POLLOUT aren't set, as we can get POLLHUP along with POLLIN with TCP
263      * data still to be read.
264      */
265     if (pfd->revents != 0 && !(pfd->revents & (POLLIN | POLLOUT)))
266         return SSF_EXCEPTION;
267 
268     return ((pfd->revents & POLLIN) ? SSF_READ : 0) |
269         ((pfd->revents & POLLOUT) ? SSF_WRITE : 0) |
270         ((pfd->revents & POLLERR) ? SSF_EXCEPTION : 0);
271 }
272 
273 #else /* not USE_POLL */
274 
275 static void
cm_init_selstate(struct select_state * selstate)276 cm_init_selstate(struct select_state *selstate)
277 {
278     selstate->nfds = 0;
279     selstate->max = 0;
280     FD_ZERO(&selstate->rfds);
281     FD_ZERO(&selstate->wfds);
282     FD_ZERO(&selstate->xfds);
283 }
284 
285 static krb5_boolean
cm_add_fd(struct select_state * selstate,int fd)286 cm_add_fd(struct select_state *selstate, int fd)
287 {
288 #ifndef _WIN32  /* On Windows FD_SETSIZE is a count, not a max value. */
289     if (fd >= FD_SETSIZE)
290         return FALSE;
291 #endif
292     FD_SET(fd, &selstate->xfds);
293     if (selstate->max <= fd)
294         selstate->max = fd + 1;
295     selstate->nfds++;
296     return TRUE;
297 }
298 
299 static void
cm_remove_fd(struct select_state * selstate,int fd)300 cm_remove_fd(struct select_state *selstate, int fd)
301 {
302     FD_CLR(fd, &selstate->rfds);
303     FD_CLR(fd, &selstate->wfds);
304     FD_CLR(fd, &selstate->xfds);
305     if (selstate->max == fd + 1) {
306         while (selstate->max > 0 &&
307                !FD_ISSET(selstate->max - 1, &selstate->rfds) &&
308                !FD_ISSET(selstate->max - 1, &selstate->wfds) &&
309                !FD_ISSET(selstate->max - 1, &selstate->xfds))
310             selstate->max--;
311     }
312     selstate->nfds--;
313 }
314 
315 /* Select for reading (and not writing) on fd the next time we select. */
316 static void
cm_read(struct select_state * selstate,int fd)317 cm_read(struct select_state *selstate, int fd)
318 {
319     FD_SET(fd, &selstate->rfds);
320     FD_CLR(fd, &selstate->wfds);
321 }
322 
323 /* Select for writing (and not reading) on fd the next time we select. */
324 static void
cm_write(struct select_state * selstate,int fd)325 cm_write(struct select_state *selstate, int fd)
326 {
327     FD_CLR(fd, &selstate->rfds);
328     FD_SET(fd, &selstate->wfds);
329 }
330 
331 /* Get the events for fd from selstate after a select. */
332 static unsigned int
cm_get_ssflags(struct select_state * selstate,int fd)333 cm_get_ssflags(struct select_state *selstate, int fd)
334 {
335     return (FD_ISSET(fd, &selstate->rfds) ? SSF_READ : 0) |
336         (FD_ISSET(fd, &selstate->wfds) ? SSF_WRITE : 0) |
337         (FD_ISSET(fd, &selstate->xfds) ? SSF_EXCEPTION : 0);
338 }
339 
340 #endif /* not USE_POLL */
341 
342 static krb5_error_code
cm_select_or_poll(const struct select_state * in,time_ms endtime,struct select_state * out,int * sret)343 cm_select_or_poll(const struct select_state *in, time_ms endtime,
344                   struct select_state *out, int *sret)
345 {
346 #ifndef USE_POLL
347     struct timeval tv;
348 #endif
349     krb5_error_code retval;
350     time_ms curtime, interval;
351 
352     retval = get_curtime_ms(&curtime);
353     if (retval != 0)
354         return retval;
355     interval = (curtime < endtime) ? endtime - curtime : 0;
356 
357     /* We don't need a separate copy of the selstate for poll, but use one for
358      * consistency with how we use select. */
359     *out = *in;
360 
361 #ifdef USE_POLL
362     *sret = poll(out->fds, out->nfds, interval);
363 #else
364     tv.tv_sec = interval / 1000;
365     tv.tv_usec = interval % 1000 * 1000;
366     *sret = select(out->max, &out->rfds, &out->wfds, &out->xfds, &tv);
367 #endif
368 
369     return (*sret < 0) ? SOCKET_ERRNO : 0;
370 }
371 
372 static int
socktype_for_transport(k5_transport transport)373 socktype_for_transport(k5_transport transport)
374 {
375     switch (transport) {
376     case UDP:
377         return SOCK_DGRAM;
378     case TCP:
379     case HTTPS:
380         return SOCK_STREAM;
381     default:
382         return 0;
383     }
384 }
385 
386 static int
check_for_svc_unavailable(krb5_context context,const krb5_data * reply,void * msg_handler_data)387 check_for_svc_unavailable (krb5_context context,
388                            const krb5_data *reply,
389                            void *msg_handler_data)
390 {
391     krb5_error_code *retval = (krb5_error_code *)msg_handler_data;
392 
393     *retval = 0;
394 
395     if (krb5_is_krb_error(reply)) {
396         krb5_error *err_reply;
397 
398         if (decode_krb5_error(reply, &err_reply) == 0) {
399             *retval = err_reply->error;
400             krb5_free_error(context, err_reply);
401 
402             /* Returning 0 means continue to next KDC */
403             return (*retval != KDC_ERR_SVC_UNAVAILABLE);
404         }
405     }
406 
407     return 1;
408 }
409 
410 void KRB5_CALLCONV
krb5_set_kdc_send_hook(krb5_context context,krb5_pre_send_fn send_hook,void * data)411 krb5_set_kdc_send_hook(krb5_context context, krb5_pre_send_fn send_hook,
412                        void *data)
413 {
414     context->kdc_send_hook = send_hook;
415     context->kdc_send_hook_data = data;
416 }
417 
418 void KRB5_CALLCONV
krb5_set_kdc_recv_hook(krb5_context context,krb5_post_recv_fn recv_hook,void * data)419 krb5_set_kdc_recv_hook(krb5_context context, krb5_post_recv_fn recv_hook,
420                        void *data)
421 {
422     context->kdc_recv_hook = recv_hook;
423     context->kdc_recv_hook_data = data;
424 }
425 
426 /*
427  * send the formatted request 'message' to a KDC for realm 'realm' and
428  * return the response (if any) in 'reply'.
429  *
430  * If the message is sent and a response is received, 0 is returned,
431  * otherwise an error code is returned.
432  *
433  * The storage for 'reply' is allocated and should be freed by the caller
434  * when finished.
435  */
436 
437 krb5_error_code
krb5_sendto_kdc(krb5_context context,const krb5_data * message,const krb5_data * realm,krb5_data * reply_out,int * use_primary,int no_udp)438 krb5_sendto_kdc(krb5_context context, const krb5_data *message,
439                 const krb5_data *realm, krb5_data *reply_out, int *use_primary,
440                 int no_udp)
441 {
442     krb5_error_code retval, oldret, err;
443     struct serverlist servers;
444     int server_used;
445     k5_transport_strategy strategy;
446     krb5_data reply = empty_data(), *hook_message = NULL, *hook_reply = NULL;
447 
448     *reply_out = empty_data();
449 
450     /*
451      * find KDC location(s) for realm
452      */
453 
454     /*
455      * BUG: This code won't return "interesting" errors (e.g., out of mem,
456      * bad config file) from locate_kdc.  KRB5_REALM_CANT_RESOLVE can be
457      * ignored from one query of two, but if only one query is done, or
458      * both return that error, it should be returned to the caller.  Also,
459      * "interesting" errors (not KRB5_KDC_UNREACH) from sendto_{udp,tcp}
460      * should probably be returned as well.
461      */
462 
463     TRACE_SENDTO_KDC(context, message->length, realm, *use_primary, no_udp);
464 
465     if (!no_udp && context->udp_pref_limit < 0) {
466         int tmp;
467         retval = profile_get_integer(context->profile,
468                                      KRB5_CONF_LIBDEFAULTS, KRB5_CONF_UDP_PREFERENCE_LIMIT, 0,
469                                      DEFAULT_UDP_PREF_LIMIT, &tmp);
470         if (retval)
471             return retval;
472         if (tmp < 0)
473             tmp = DEFAULT_UDP_PREF_LIMIT;
474         else if (tmp > HARD_UDP_LIMIT)
475             /* In the unlikely case that a *really* big value is
476                given, let 'em use as big as we think we can
477                support.  */
478             tmp = HARD_UDP_LIMIT;
479         context->udp_pref_limit = tmp;
480     }
481 
482     if (no_udp)
483         strategy = NO_UDP;
484     else if (message->length <= (unsigned int) context->udp_pref_limit)
485         strategy = UDP_FIRST;
486     else
487         strategy = UDP_LAST;
488 
489     retval = k5_locate_kdc(context, realm, &servers, *use_primary, no_udp);
490     if (retval)
491         return retval;
492 
493     if (context->kdc_send_hook != NULL) {
494         retval = context->kdc_send_hook(context, context->kdc_send_hook_data,
495                                         realm, message, &hook_message,
496                                         &hook_reply);
497         if (retval)
498             goto cleanup;
499 
500         if (hook_reply != NULL) {
501             *reply_out = *hook_reply;
502             free(hook_reply);
503             goto cleanup;
504         }
505 
506         if (hook_message != NULL)
507             message = hook_message;
508     }
509 
510     err = 0;
511     retval = k5_sendto(context, message, realm, &servers, strategy, NULL,
512                        &reply, NULL, NULL, &server_used,
513                        check_for_svc_unavailable, &err);
514     if (retval == KRB5_KDC_UNREACH) {
515         if (err == KDC_ERR_SVC_UNAVAILABLE) {
516             retval = KRB5KDC_ERR_SVC_UNAVAILABLE;
517         } else {
518             k5_setmsg(context, retval,
519                       _("Cannot contact any KDC for realm '%.*s'"),
520                       realm->length, realm->data);
521         }
522     }
523 
524     if (context->kdc_recv_hook != NULL) {
525         oldret = retval;
526         retval = context->kdc_recv_hook(context, context->kdc_recv_hook_data,
527                                         retval, realm, message, &reply,
528                                         &hook_reply);
529         if (oldret && !retval) {
530             /*
531              * The hook must set a reply if it overrides an error from
532              * k5_sendto().  Treat this reply as coming from the primary
533              * KDC.
534              */
535             assert(hook_reply != NULL);
536             *use_primary = 1;
537         }
538     }
539     if (retval)
540         goto cleanup;
541 
542     if (hook_reply != NULL) {
543         *reply_out = *hook_reply;
544         free(hook_reply);
545     } else {
546         *reply_out = reply;
547         reply = empty_data();
548     }
549 
550     /* Set use_primary to 1 if we ended up talking to a primary when we didn't
551      * explicitly request to. */
552     if (*use_primary == 0) {
553         *use_primary = k5_kdc_is_primary(context, realm,
554                                          &servers.servers[server_used]);
555         TRACE_SENDTO_KDC_PRIMARY(context, *use_primary);
556     }
557 
558 cleanup:
559     krb5_free_data(context, hook_message);
560     krb5_free_data_contents(context, &reply);
561     k5_free_serverlist(&servers);
562     return retval;
563 }
564 
565 /*
566  * Notes:
567  *
568  * Getting "connection refused" on a connected UDP socket causes
569  * select to indicate write capability on UNIX, but only shows up
570  * as an exception on Windows.  (I don't think any UNIX system flags
571  * the error as an exception.)  So we check for both, or make it
572  * system-specific.
573  *
574  * Always watch for responses from *any* of the servers.  Eventually
575  * fix the UDP code to do the same.
576  *
577  * To do:
578  * - TCP NOPUSH/CORK socket options?
579  * - error codes that don't suck
580  * - getsockopt(SO_ERROR) to check connect status
581  * - handle error RESPONSE_TOO_BIG from UDP server and use TCP
582  *   connections already in progress
583  */
584 
585 static fd_handler_fn service_tcp_connect;
586 static fd_handler_fn service_tcp_write;
587 static fd_handler_fn service_tcp_read;
588 static fd_handler_fn service_udp_read;
589 static fd_handler_fn service_https_write;
590 static fd_handler_fn service_https_read;
591 
592 static krb5_error_code
make_proxy_request(struct conn_state * state,const krb5_data * realm,const krb5_data * message,char ** req_out,size_t * len_out)593 make_proxy_request(struct conn_state *state, const krb5_data *realm,
594                    const krb5_data *message, char **req_out, size_t *len_out)
595 {
596     krb5_kkdcp_message pm;
597     krb5_data *encoded_pm = NULL;
598     struct k5buf buf;
599     const char *uri_path;
600     krb5_error_code ret;
601 
602     *req_out = NULL;
603     *len_out = 0;
604 
605     /*
606      * Stuff the message length in at the front of the kerb_message field
607      * before encoding.  The proxied messages are actually the payload we'd
608      * be sending and receiving if we were using plain TCP.
609      */
610     memset(&pm, 0, sizeof(pm));
611     ret = alloc_data(&pm.kerb_message, message->length + 4);
612     if (ret != 0)
613         goto cleanup;
614     store_32_be(message->length, pm.kerb_message.data);
615     memcpy(pm.kerb_message.data + 4, message->data, message->length);
616     pm.target_domain = *realm;
617     ret = encode_krb5_kkdcp_message(&pm, &encoded_pm);
618     if (ret != 0)
619         goto cleanup;
620 
621     /* Build the request to transmit: the headers + the proxy message. */
622     k5_buf_init_dynamic(&buf);
623     uri_path = (state->http.uri_path != NULL) ? state->http.uri_path : "";
624     k5_buf_add_fmt(&buf, "POST /%s HTTP/1.0\r\n", uri_path);
625     k5_buf_add_fmt(&buf, "Host: %s:%s\r\n", state->http.servername,
626                    state->http.port);
627     k5_buf_add(&buf, "Cache-Control: no-cache\r\n");
628     k5_buf_add(&buf, "Pragma: no-cache\r\n");
629     k5_buf_add(&buf, "User-Agent: kerberos/1.0\r\n");
630     k5_buf_add(&buf, "Content-type: application/kerberos\r\n");
631     k5_buf_add_fmt(&buf, "Content-Length: %d\r\n\r\n", encoded_pm->length);
632     k5_buf_add_len(&buf, encoded_pm->data, encoded_pm->length);
633     if (k5_buf_status(&buf) != 0) {
634         ret = ENOMEM;
635         goto cleanup;
636     }
637 
638     *req_out = buf.data;
639     *len_out = buf.len;
640 
641 cleanup:
642     krb5_free_data_contents(NULL, &pm.kerb_message);
643     krb5_free_data(NULL, encoded_pm);
644     return ret;
645 }
646 
647 /* Set up the actual message we will send across the underlying transport to
648  * communicate the payload message, using one or both of state->out.sgbuf. */
649 static krb5_error_code
set_transport_message(struct conn_state * state,const krb5_data * realm,const krb5_data * message)650 set_transport_message(struct conn_state *state, const krb5_data *realm,
651                       const krb5_data *message)
652 {
653     struct outgoing_message *out = &state->out;
654     char *req = NULL;
655     size_t reqlen;
656     krb5_error_code ret;
657 
658     if (message == NULL || message->length == 0)
659         return 0;
660 
661     if (state->addr.transport == TCP) {
662         store_32_be(message->length, out->msg_len_buf);
663         SG_SET(&out->sgbuf[0], out->msg_len_buf, 4);
664         SG_SET(&out->sgbuf[1], message->data, message->length);
665         out->sg_count = 2;
666         return 0;
667     } else if (state->addr.transport == HTTPS) {
668         ret = make_proxy_request(state, realm, message, &req, &reqlen);
669         if (ret != 0)
670             return ret;
671         SG_SET(&state->out.sgbuf[0], req, reqlen);
672         SG_SET(&state->out.sgbuf[1], 0, 0);
673         state->out.sg_count = 1;
674         free(state->http.https_request);
675         state->http.https_request = req;
676         return 0;
677     } else {
678         SG_SET(&out->sgbuf[0], message->data, message->length);
679         SG_SET(&out->sgbuf[1], NULL, 0);
680         out->sg_count = 1;
681         return 0;
682     }
683 }
684 
685 static krb5_error_code
add_connection(struct conn_state ** conns,k5_transport transport,krb5_boolean defer,struct addrinfo * ai,size_t server_index,const krb5_data * realm,const char * hostname,const char * port,const char * uri_path,char ** udpbufp)686 add_connection(struct conn_state **conns, k5_transport transport,
687                krb5_boolean defer, struct addrinfo *ai, size_t server_index,
688                const krb5_data *realm, const char *hostname,
689                const char *port, const char *uri_path, char **udpbufp)
690 {
691     struct conn_state *state, **tailptr;
692 
693     state = calloc(1, sizeof(*state));
694     if (state == NULL)
695         return ENOMEM;
696     state->state = INITIALIZING;
697     state->out.sgp = state->out.sgbuf;
698     state->addr.transport = transport;
699     state->addr.family = ai->ai_family;
700     state->addr.len = ai->ai_addrlen;
701     memcpy(&state->addr.saddr, ai->ai_addr, ai->ai_addrlen);
702     state->defer = defer;
703     state->fd = INVALID_SOCKET;
704     state->server_index = server_index;
705     SG_SET(&state->out.sgbuf[1], NULL, 0);
706     if (transport == TCP) {
707         state->service_connect = service_tcp_connect;
708         state->service_write = service_tcp_write;
709         state->service_read = service_tcp_read;
710     } else if (transport == HTTPS) {
711         assert(hostname != NULL && port != NULL);
712         state->service_connect = service_tcp_connect;
713         state->service_write = service_https_write;
714         state->service_read = service_https_read;
715         state->http.uri_path = uri_path;
716         state->http.servername = hostname;
717         strlcpy(state->http.port, port, PORT_LENGTH);
718     } else {
719         state->service_connect = NULL;
720         state->service_write = NULL;
721         state->service_read = service_udp_read;
722 
723         if (*udpbufp == NULL) {
724             *udpbufp = malloc(MAX_DGRAM_SIZE);
725             if (*udpbufp == NULL) {
726                 free(state);
727                 return ENOMEM;
728             }
729         }
730         state->in.buf = *udpbufp;
731         state->in.bufsize = MAX_DGRAM_SIZE;
732     }
733 
734     /* Chain the new state onto the tail of the list. */
735     for (tailptr = conns; *tailptr != NULL; tailptr = &(*tailptr)->next);
736     *tailptr = state;
737 
738     return 0;
739 }
740 
741 static int
translate_ai_error(int err)742 translate_ai_error (int err)
743 {
744     switch (err) {
745     case 0:
746         return 0;
747     case EAI_BADFLAGS:
748     case EAI_FAMILY:
749     case EAI_SOCKTYPE:
750     case EAI_SERVICE:
751         /* All of these indicate bad inputs to getaddrinfo.  */
752         return EINVAL;
753     case EAI_AGAIN:
754         /* Translate to standard errno code.  */
755         return EAGAIN;
756     case EAI_MEMORY:
757         /* Translate to standard errno code.  */
758         return ENOMEM;
759 #ifdef EAI_ADDRFAMILY
760     case EAI_ADDRFAMILY:
761 #endif
762 #if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME
763     case EAI_NODATA:
764 #endif
765     case EAI_NONAME:
766         /* Name not known or no address data, but no error.  Do
767            nothing more.  */
768         return 0;
769 #ifdef EAI_OVERFLOW
770     case EAI_OVERFLOW:
771         /* An argument buffer overflowed.  */
772         return EINVAL;          /* XXX */
773 #endif
774 #ifdef EAI_SYSTEM
775     case EAI_SYSTEM:
776         /* System error, obviously.  */
777         return errno;
778 #endif
779     default:
780         /* An error code we haven't handled?  */
781         return EINVAL;
782     }
783 }
784 
785 /*
786  * Resolve the entry in servers with index ind, adding connections to the list
787  * *conns.  Connections are added for each of socktype1 and (if not zero)
788  * socktype2.  message and udpbufp are used to initialize the connections; see
789  * add_connection above.  If no addresses are available for an entry but no
790  * internal name resolution failure occurs, return 0 without adding any new
791  * connections.
792  */
793 static krb5_error_code
resolve_server(krb5_context context,const krb5_data * realm,const struct serverlist * servers,size_t ind,k5_transport_strategy strategy,const krb5_data * message,char ** udpbufp,struct conn_state ** conns)794 resolve_server(krb5_context context, const krb5_data *realm,
795                const struct serverlist *servers, size_t ind,
796                k5_transport_strategy strategy, const krb5_data *message,
797                char **udpbufp, struct conn_state **conns)
798 {
799     krb5_error_code retval;
800     struct server_entry *entry = &servers->servers[ind];
801     k5_transport transport;
802     struct addrinfo *addrs, *a, hint, ai;
803     krb5_boolean defer = FALSE;
804     int err, result;
805     char portbuf[PORT_LENGTH];
806 
807     /* Skip entries excluded by the strategy. */
808     if (strategy == NO_UDP && entry->transport == UDP)
809         return 0;
810     if (strategy == ONLY_UDP && entry->transport != UDP &&
811         entry->transport != TCP_OR_UDP)
812         return 0;
813 
814     transport = (strategy == UDP_FIRST || strategy == ONLY_UDP) ? UDP : TCP;
815     if (entry->hostname == NULL) {
816         /* Added by a module, so transport is either TCP or UDP. */
817         ai.ai_socktype = socktype_for_transport(entry->transport);
818         ai.ai_family = entry->family;
819         ai.ai_addrlen = entry->addrlen;
820         ai.ai_addr = (struct sockaddr *)&entry->addr;
821         defer = (entry->transport != transport);
822         return add_connection(conns, entry->transport, defer, &ai, ind, realm,
823                               NULL, NULL, entry->uri_path, udpbufp);
824     }
825 
826     /* If the entry has a specified transport, use it, but possibly defer the
827      * addresses we add based on the strategy. */
828     if (entry->transport != TCP_OR_UDP) {
829         transport = entry->transport;
830         defer = (entry->transport == TCP && strategy == UDP_FIRST) ||
831             (entry->transport == UDP && strategy == UDP_LAST);
832     }
833 
834     memset(&hint, 0, sizeof(hint));
835     hint.ai_family = entry->family;
836     hint.ai_socktype = socktype_for_transport(transport);
837     hint.ai_flags = AI_ADDRCONFIG;
838 #ifdef AI_NUMERICSERV
839     hint.ai_flags |= AI_NUMERICSERV;
840 #endif
841     result = snprintf(portbuf, sizeof(portbuf), "%d", entry->port);
842     if (SNPRINTF_OVERFLOW(result, sizeof(portbuf)))
843         return EINVAL;
844     TRACE_SENDTO_KDC_RESOLVING(context, entry->hostname);
845     err = getaddrinfo(entry->hostname, portbuf, &hint, &addrs);
846     if (err)
847         return translate_ai_error(err);
848 
849     /* Add each address with the specified or preferred transport. */
850     retval = 0;
851     for (a = addrs; a != 0 && retval == 0; a = a->ai_next) {
852         retval = add_connection(conns, transport, defer, a, ind, realm,
853                                 entry->hostname, portbuf, entry->uri_path,
854                                 udpbufp);
855     }
856 
857     /* For TCP_OR_UDP entries, add each address again with the non-preferred
858      * transport, if there is one.  Flag these as deferred. */
859     if (retval == 0 && entry->transport == TCP_OR_UDP &&
860         (strategy == UDP_FIRST || strategy == UDP_LAST)) {
861         transport = (strategy == UDP_FIRST) ? TCP : UDP;
862         for (a = addrs; a != 0 && retval == 0; a = a->ai_next) {
863             a->ai_socktype = socktype_for_transport(transport);
864             retval = add_connection(conns, transport, TRUE, a, ind, realm,
865                                     entry->hostname, portbuf,
866                                     entry->uri_path, udpbufp);
867         }
868     }
869     freeaddrinfo(addrs);
870     return retval;
871 }
872 
873 static int
start_connection(krb5_context context,struct conn_state * state,const krb5_data * message,struct select_state * selstate,const krb5_data * realm,struct sendto_callback_info * callback_info)874 start_connection(krb5_context context, struct conn_state *state,
875                  const krb5_data *message, struct select_state *selstate,
876                  const krb5_data *realm,
877                  struct sendto_callback_info *callback_info)
878 {
879     int fd, e, type;
880     static const int one = 1;
881     static const struct linger lopt = { 0, 0 };
882 
883     type = socktype_for_transport(state->addr.transport);
884     fd = socket(state->addr.family, type, 0);
885     if (fd == INVALID_SOCKET)
886         return -1;              /* try other hosts */
887     set_cloexec_fd(fd);
888     /* Make it non-blocking.  */
889     ioctlsocket(fd, FIONBIO, (const void *) &one);
890     if (state->addr.transport == TCP) {
891         setsockopt(fd, SOL_SOCKET, SO_LINGER, &lopt, sizeof(lopt));
892         TRACE_SENDTO_KDC_TCP_CONNECT(context, &state->addr);
893     }
894 
895     /* Start connecting to KDC.  */
896     e = SOCKET_CONNECT(fd, (struct sockaddr *)&state->addr.saddr,
897                        state->addr.len);
898     if (e != 0) {
899         /*
900          * This is the path that should be followed for non-blocking
901          * connections.
902          */
903         if (SOCKET_ERRNO == EINPROGRESS || SOCKET_ERRNO == EWOULDBLOCK) {
904             state->state = CONNECTING;
905             state->fd = fd;
906         } else {
907             (void) closesocket(fd);
908             state->state = FAILED;
909             return -2;
910         }
911     } else {
912         /*
913          * Connect returned zero even though we made it non-blocking.  This
914          * happens normally for UDP sockets, and can perhaps also happen for
915          * TCP sockets connecting to localhost.
916          */
917         state->state = WRITING;
918         state->fd = fd;
919     }
920 
921     /*
922      * Here's where KPASSWD callback gets the socket information it needs for
923      * a kpasswd request
924      */
925     if (callback_info) {
926 
927         e = callback_info->pfn_callback(state->fd, callback_info->data,
928                                         &state->callback_buffer);
929         if (e != 0) {
930             (void) closesocket(fd);
931             state->fd = INVALID_SOCKET;
932             state->state = FAILED;
933             return -3;
934         }
935 
936         message = &state->callback_buffer;
937     }
938 
939     e = set_transport_message(state, realm, message);
940     if (e != 0) {
941         TRACE_SENDTO_KDC_ERROR_SET_MESSAGE(context, &state->addr, e);
942         (void) closesocket(state->fd);
943         state->fd = INVALID_SOCKET;
944         state->state = FAILED;
945         return -4;
946     }
947 
948     if (state->addr.transport == UDP) {
949         /* Send it now.  */
950         ssize_t ret;
951         sg_buf *sg = &state->out.sgbuf[0];
952 
953         TRACE_SENDTO_KDC_UDP_SEND_INITIAL(context, &state->addr);
954         ret = send(state->fd, SG_BUF(sg), SG_LEN(sg), 0);
955         if (ret < 0 || (size_t) ret != SG_LEN(sg)) {
956             TRACE_SENDTO_KDC_UDP_ERROR_SEND_INITIAL(context, &state->addr,
957                                                     SOCKET_ERRNO);
958             (void) closesocket(state->fd);
959             state->fd = INVALID_SOCKET;
960             state->state = FAILED;
961             return -5;
962         } else {
963             state->state = READING;
964         }
965     }
966 
967     if (!cm_add_fd(selstate, state->fd)) {
968         (void) closesocket(state->fd);
969         state->fd = INVALID_SOCKET;
970         state->state = FAILED;
971         return -1;
972     }
973     if (state->state == CONNECTING || state->state == WRITING)
974         cm_write(selstate, state->fd);
975     else
976         cm_read(selstate, state->fd);
977 
978     return 0;
979 }
980 
981 /* Return 0 if we sent something, non-0 otherwise.
982    If 0 is returned, the caller should delay waiting for a response.
983    Otherwise, the caller should immediately move on to process the
984    next connection.  */
985 static int
maybe_send(krb5_context context,struct conn_state * conn,const krb5_data * message,struct select_state * selstate,const krb5_data * realm,struct sendto_callback_info * callback_info)986 maybe_send(krb5_context context, struct conn_state *conn,
987            const krb5_data *message, struct select_state *selstate,
988            const krb5_data *realm,
989            struct sendto_callback_info *callback_info)
990 {
991     sg_buf *sg;
992     ssize_t ret;
993 
994     if (conn->state == INITIALIZING) {
995         return start_connection(context, conn, message, selstate,
996                                 realm, callback_info);
997     }
998 
999     /* Did we already shut down this channel?  */
1000     if (conn->state == FAILED) {
1001         return -1;
1002     }
1003 
1004     if (conn->addr.transport != UDP) {
1005         /* The select callback will handle flushing any data we
1006            haven't written yet, and we only write it once.  */
1007         return -1;
1008     }
1009 
1010     /* UDP - retransmit after a previous attempt timed out. */
1011     sg = &conn->out.sgbuf[0];
1012     TRACE_SENDTO_KDC_UDP_SEND_RETRY(context, &conn->addr);
1013     ret = send(conn->fd, SG_BUF(sg), SG_LEN(sg), 0);
1014     if (ret < 0 || (size_t) ret != SG_LEN(sg)) {
1015         TRACE_SENDTO_KDC_UDP_ERROR_SEND_RETRY(context, &conn->addr,
1016                                               SOCKET_ERRNO);
1017         /* Keep connection alive, we'll try again next pass.
1018 
1019            Is this likely to catch any errors we didn't get from the
1020            select callbacks?  */
1021         return -1;
1022     }
1023     /* Yay, it worked.  */
1024     return 0;
1025 }
1026 
1027 static void
kill_conn(krb5_context context,struct conn_state * conn,struct select_state * selstate)1028 kill_conn(krb5_context context, struct conn_state *conn,
1029           struct select_state *selstate)
1030 {
1031     free_http_tls_data(context, conn);
1032 
1033     if (socktype_for_transport(conn->addr.transport) == SOCK_STREAM)
1034         TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &conn->addr);
1035     cm_remove_fd(selstate, conn->fd);
1036 
1037     closesocket(conn->fd);
1038     conn->fd = INVALID_SOCKET;
1039     conn->state = FAILED;
1040 }
1041 
1042 /* Check socket for error.  */
1043 static int
get_so_error(int fd)1044 get_so_error(int fd)
1045 {
1046     int e, sockerr;
1047     socklen_t sockerrlen;
1048 
1049     sockerr = 0;
1050     sockerrlen = sizeof(sockerr);
1051     e = getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &sockerrlen);
1052     if (e != 0) {
1053         /* What to do now?  */
1054         e = SOCKET_ERRNO;
1055         return e;
1056     }
1057     return sockerr;
1058 }
1059 
1060 /* Perform next step in sending.  Return true on usable data. */
1061 static krb5_boolean
service_dispatch(krb5_context context,const krb5_data * realm,struct conn_state * conn,struct select_state * selstate,int ssflags)1062 service_dispatch(krb5_context context, const krb5_data *realm,
1063                  struct conn_state *conn, struct select_state *selstate,
1064                  int ssflags)
1065 {
1066     /* Check for a socket exception. */
1067     if (ssflags & SSF_EXCEPTION) {
1068         kill_conn(context, conn, selstate);
1069         return FALSE;
1070     }
1071 
1072     switch (conn->state) {
1073     case CONNECTING:
1074         assert(conn->service_connect != NULL);
1075         return conn->service_connect(context, realm, conn, selstate);
1076     case WRITING:
1077         assert(conn->service_write != NULL);
1078         return conn->service_write(context, realm, conn, selstate);
1079     case READING:
1080         assert(conn->service_read != NULL);
1081         return conn->service_read(context, realm, conn, selstate);
1082     default:
1083         abort();
1084     }
1085 }
1086 
1087 /* Initialize TCP transport. */
1088 static krb5_boolean
service_tcp_connect(krb5_context context,const krb5_data * realm,struct conn_state * conn,struct select_state * selstate)1089 service_tcp_connect(krb5_context context, const krb5_data *realm,
1090                     struct conn_state *conn, struct select_state *selstate)
1091 {
1092     /* Check whether the connection succeeded. */
1093     int e = get_so_error(conn->fd);
1094 
1095     if (e) {
1096         TRACE_SENDTO_KDC_TCP_ERROR_CONNECT(context, &conn->addr, e);
1097         kill_conn(context, conn, selstate);
1098         return FALSE;
1099     }
1100 
1101     conn->state = WRITING;
1102 
1103     /* Record this connection's timeout for service_fds. */
1104     if (get_curtime_ms(&conn->endtime) == 0)
1105         conn->endtime += 10000;
1106 
1107     return conn->service_write(context, realm, conn, selstate);
1108 }
1109 
1110 /* Sets conn->state to READING when done. */
1111 static krb5_boolean
service_tcp_write(krb5_context context,const krb5_data * realm,struct conn_state * conn,struct select_state * selstate)1112 service_tcp_write(krb5_context context, const krb5_data *realm,
1113                   struct conn_state *conn, struct select_state *selstate)
1114 {
1115     ssize_t nwritten;
1116     SOCKET_WRITEV_TEMP tmp;
1117 
1118     TRACE_SENDTO_KDC_TCP_SEND(context, &conn->addr);
1119     nwritten = SOCKET_WRITEV(conn->fd, conn->out.sgp, conn->out.sg_count, tmp);
1120     if (nwritten < 0) {
1121         TRACE_SENDTO_KDC_TCP_ERROR_SEND(context, &conn->addr, SOCKET_ERRNO);
1122         kill_conn(context, conn, selstate);
1123         return FALSE;
1124     }
1125     while (nwritten) {
1126         sg_buf *sgp = conn->out.sgp;
1127         if ((size_t)nwritten < SG_LEN(sgp)) {
1128             SG_ADVANCE(sgp, (size_t)nwritten);
1129             nwritten = 0;
1130         } else {
1131             nwritten -= SG_LEN(sgp);
1132             conn->out.sgp++;
1133             conn->out.sg_count--;
1134         }
1135     }
1136     if (conn->out.sg_count == 0) {
1137         /* Done writing, switch to reading. */
1138         cm_read(selstate, conn->fd);
1139         conn->state = READING;
1140     }
1141     return FALSE;
1142 }
1143 
1144 /* Return true on usable data. */
1145 static krb5_boolean
service_tcp_read(krb5_context context,const krb5_data * realm,struct conn_state * conn,struct select_state * selstate)1146 service_tcp_read(krb5_context context, const krb5_data *realm,
1147                  struct conn_state *conn, struct select_state *selstate)
1148 {
1149     ssize_t nread;
1150     int e = 0;
1151     struct incoming_message *in = &conn->in;
1152 
1153     if (in->bufsizebytes_read == 4) {
1154         /* Reading data.  */
1155         nread = SOCKET_READ(conn->fd, &in->buf[in->pos], in->n_left);
1156         if (nread <= 0) {
1157             e = nread ? SOCKET_ERRNO : ECONNRESET;
1158             TRACE_SENDTO_KDC_TCP_ERROR_RECV(context, &conn->addr, e);
1159             kill_conn(context, conn, selstate);
1160             return FALSE;
1161         }
1162         in->n_left -= nread;
1163         in->pos += nread;
1164         if (in->n_left <= 0)
1165             return TRUE;
1166     } else {
1167         /* Reading length.  */
1168         nread = SOCKET_READ(conn->fd, in->bufsizebytes + in->bufsizebytes_read,
1169                             4 - in->bufsizebytes_read);
1170         if (nread <= 0) {
1171             e = nread ? SOCKET_ERRNO : ECONNRESET;
1172             TRACE_SENDTO_KDC_TCP_ERROR_RECV_LEN(context, &conn->addr, e);
1173             kill_conn(context, conn, selstate);
1174             return FALSE;
1175         }
1176         in->bufsizebytes_read += nread;
1177         if (in->bufsizebytes_read == 4) {
1178             unsigned long len = load_32_be(in->bufsizebytes);
1179             /* Arbitrary 1M cap.  */
1180             if (len > 1 * 1024 * 1024) {
1181                 kill_conn(context, conn, selstate);
1182                 return FALSE;
1183             }
1184             in->bufsize = in->n_left = len;
1185             in->pos = 0;
1186             in->buf = malloc(len);
1187             if (in->buf == NULL) {
1188                 kill_conn(context, conn, selstate);
1189                 return FALSE;
1190             }
1191         }
1192     }
1193     return FALSE;
1194 }
1195 
1196 /* Process events on a UDP socket.  Return true if we get a reply. */
1197 static krb5_boolean
service_udp_read(krb5_context context,const krb5_data * realm,struct conn_state * conn,struct select_state * selstate)1198 service_udp_read(krb5_context context, const krb5_data *realm,
1199                  struct conn_state *conn, struct select_state *selstate)
1200 {
1201     int nread;
1202 
1203     nread = recv(conn->fd, conn->in.buf, conn->in.bufsize, 0);
1204     if (nread < 0) {
1205         TRACE_SENDTO_KDC_UDP_ERROR_RECV(context, &conn->addr, SOCKET_ERRNO);
1206         kill_conn(context, conn, selstate);
1207         return FALSE;
1208     }
1209     conn->in.pos = nread;
1210     return TRUE;
1211 }
1212 
1213 /* Set up conn->http.tls.  Return true on success. */
1214 static krb5_boolean
setup_tls(krb5_context context,const krb5_data * realm,struct conn_state * conn,struct select_state * selstate)1215 setup_tls(krb5_context context, const krb5_data *realm,
1216           struct conn_state *conn, struct select_state *selstate)
1217 {
1218     krb5_error_code ret;
1219     krb5_boolean ok = FALSE;
1220     char **anchors = NULL, *realmstr = NULL;
1221     const char *names[4];
1222 
1223     if (init_tls_vtable(context) != 0 || context->tls->setup == NULL)
1224         return FALSE;
1225 
1226     realmstr = k5memdup0(realm->data, realm->length, &ret);
1227     if (realmstr == NULL)
1228         goto cleanup;
1229 
1230     /* Load the configured anchors. */
1231     names[0] = KRB5_CONF_REALMS;
1232     names[1] = realmstr;
1233     names[2] = KRB5_CONF_HTTP_ANCHORS;
1234     names[3] = NULL;
1235     ret = profile_get_values(context->profile, names, &anchors);
1236     if (ret != 0 && ret != PROF_NO_RELATION)
1237         goto cleanup;
1238 
1239     if (context->tls->setup(context, conn->fd, conn->http.servername, anchors,
1240                             &conn->http.tls) != 0) {
1241         TRACE_SENDTO_KDC_HTTPS_ERROR_CONNECT(context, &conn->addr);
1242         goto cleanup;
1243     }
1244 
1245     ok = TRUE;
1246 
1247 cleanup:
1248     free(realmstr);
1249     profile_free_list(anchors);
1250     return ok;
1251 }
1252 
1253 /* Set conn->state to READING when done; otherwise, call a cm_set_. */
1254 static krb5_boolean
service_https_write(krb5_context context,const krb5_data * realm,struct conn_state * conn,struct select_state * selstate)1255 service_https_write(krb5_context context, const krb5_data *realm,
1256                     struct conn_state *conn, struct select_state *selstate)
1257 {
1258     k5_tls_status st;
1259 
1260     /* If this is our first time in here, set up the SSL context. */
1261     if (conn->http.tls == NULL && !setup_tls(context, realm, conn, selstate)) {
1262         kill_conn(context, conn, selstate);
1263         return FALSE;
1264     }
1265 
1266     /* Try to transmit our request to the server. */
1267     st = context->tls->write(context, conn->http.tls, SG_BUF(conn->out.sgp),
1268                              SG_LEN(conn->out.sgbuf));
1269     if (st == DONE) {
1270         TRACE_SENDTO_KDC_HTTPS_SEND(context, &conn->addr);
1271         cm_read(selstate, conn->fd);
1272         conn->state = READING;
1273     } else if (st == WANT_READ) {
1274         cm_read(selstate, conn->fd);
1275     } else if (st == WANT_WRITE) {
1276         cm_write(selstate, conn->fd);
1277     } else if (st == ERROR_TLS) {
1278         TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(context, &conn->addr);
1279         kill_conn(context, conn, selstate);
1280     }
1281 
1282     return FALSE;
1283 }
1284 
1285 /* Return true on finished data.  Call a cm_read/write function and return
1286  * false if the TLS layer needs it.  Kill the connection on error. */
1287 static krb5_boolean
https_read_bytes(krb5_context context,struct conn_state * conn,struct select_state * selstate)1288 https_read_bytes(krb5_context context, struct conn_state *conn,
1289                  struct select_state *selstate)
1290 {
1291     size_t bufsize, nread;
1292     k5_tls_status st;
1293     char *tmp;
1294     struct incoming_message *in = &conn->in;
1295 
1296     for (;;) {
1297         if (in->buf == NULL || in->bufsize - in->pos < 1024) {
1298             bufsize = in->bufsize ? in->bufsize * 2 : 8192;
1299             if (bufsize > 1024 * 1024) {
1300                 kill_conn(context, conn, selstate);
1301                 return FALSE;
1302             }
1303             tmp = realloc(in->buf, bufsize);
1304             if (tmp == NULL) {
1305                 kill_conn(context, conn, selstate);
1306                 return FALSE;
1307             }
1308             in->buf = tmp;
1309             in->bufsize = bufsize;
1310         }
1311 
1312         st = context->tls->read(context, conn->http.tls, &in->buf[in->pos],
1313                                 in->bufsize - in->pos - 1, &nread);
1314         if (st != DATA_READ)
1315             break;
1316 
1317         in->pos += nread;
1318         in->buf[in->pos] = '\0';
1319     }
1320 
1321     if (st == DONE)
1322         return TRUE;
1323 
1324     if (st == WANT_READ) {
1325         cm_read(selstate, conn->fd);
1326     } else if (st == WANT_WRITE) {
1327         cm_write(selstate, conn->fd);
1328     } else if (st == ERROR_TLS) {
1329         TRACE_SENDTO_KDC_HTTPS_ERROR_RECV(context, &conn->addr);
1330         kill_conn(context, conn, selstate);
1331     }
1332     return FALSE;
1333 }
1334 
1335 /* Return true on readable, valid KKDCPP data. */
1336 static krb5_boolean
service_https_read(krb5_context context,const krb5_data * realm,struct conn_state * conn,struct select_state * selstate)1337 service_https_read(krb5_context context, const krb5_data *realm,
1338                    struct conn_state *conn, struct select_state *selstate)
1339 {
1340     krb5_kkdcp_message *pm = NULL;
1341     krb5_data buf;
1342     const char *rep;
1343     struct incoming_message *in = &conn->in;
1344 
1345     /* Read data through the encryption layer. */
1346     if (!https_read_bytes(context, conn, selstate))
1347         return FALSE;
1348 
1349     /* Find the beginning of the response body. */
1350     rep = strstr(in->buf, "\r\n\r\n");
1351     if (rep == NULL)
1352         goto kill_conn;
1353     rep += 4;
1354 
1355     /* Decode the response. */
1356     buf = make_data((char *)rep, in->pos - (rep - in->buf));
1357     if (decode_krb5_kkdcp_message(&buf, &pm) != 0)
1358         goto kill_conn;
1359 
1360     /* Check and discard the message length at the front of the kerb_message
1361      * field after decoding.  If it's wrong or missing, something broke. */
1362     if (pm->kerb_message.length < 4 ||
1363         load_32_be(pm->kerb_message.data) != pm->kerb_message.length - 4) {
1364         goto kill_conn;
1365     }
1366 
1367     /* Replace all of the content that we read back with just the message. */
1368     memcpy(in->buf, pm->kerb_message.data + 4, pm->kerb_message.length - 4);
1369     in->pos = pm->kerb_message.length - 4;
1370     k5_free_kkdcp_message(context, pm);
1371 
1372     return TRUE;
1373 
1374 kill_conn:
1375     TRACE_SENDTO_KDC_HTTPS_ERROR(context, in->buf);
1376     k5_free_kkdcp_message(context, pm);
1377     kill_conn(context, conn, selstate);
1378     return FALSE;
1379 }
1380 
1381 /* Return the maximum of endtime and the endtime fields of all currently active
1382  * TCP connections. */
1383 static time_ms
get_endtime(time_ms endtime,struct conn_state * conns)1384 get_endtime(time_ms endtime, struct conn_state *conns)
1385 {
1386     struct conn_state *state;
1387 
1388     for (state = conns; state != NULL; state = state->next) {
1389         if ((state->state == READING || state->state == WRITING) &&
1390             state->endtime > endtime)
1391             endtime = state->endtime;
1392     }
1393     return endtime;
1394 }
1395 
1396 static krb5_boolean
service_fds(krb5_context context,struct select_state * selstate,time_ms interval,struct conn_state * conns,struct select_state * seltemp,const krb5_data * realm,int (* msg_handler)(krb5_context,const krb5_data *,void *),void * msg_handler_data,struct conn_state ** winner_out)1397 service_fds(krb5_context context, struct select_state *selstate,
1398             time_ms interval, struct conn_state *conns,
1399             struct select_state *seltemp, const krb5_data *realm,
1400             int (*msg_handler)(krb5_context, const krb5_data *, void *),
1401             void *msg_handler_data, struct conn_state **winner_out)
1402 {
1403     int e, selret = 0;
1404     time_ms endtime;
1405     struct conn_state *state;
1406 
1407     *winner_out = NULL;
1408 
1409     e = get_curtime_ms(&endtime);
1410     if (e)
1411         return TRUE;
1412     endtime += interval;
1413 
1414     e = 0;
1415     while (selstate->nfds > 0) {
1416         e = cm_select_or_poll(selstate, get_endtime(endtime, conns),
1417                               seltemp, &selret);
1418         if (e == EINTR)
1419             continue;
1420         if (e != 0)
1421             break;
1422 
1423         if (selret == 0)
1424             /* Timeout, return to caller.  */
1425             return FALSE;
1426 
1427         /* Got something on a socket, process it.  */
1428         for (state = conns; state != NULL; state = state->next) {
1429             int ssflags;
1430 
1431             if (state->fd == INVALID_SOCKET)
1432                 continue;
1433             ssflags = cm_get_ssflags(seltemp, state->fd);
1434             if (!ssflags)
1435                 continue;
1436 
1437             if (service_dispatch(context, realm, state, selstate, ssflags)) {
1438                 int stop = 1;
1439 
1440                 if (msg_handler != NULL) {
1441                     krb5_data reply = make_data(state->in.buf, state->in.pos);
1442 
1443                     stop = (msg_handler(context, &reply, msg_handler_data) != 0);
1444                 }
1445 
1446                 if (stop) {
1447                     *winner_out = state;
1448                     return TRUE;
1449                 }
1450             }
1451         }
1452     }
1453     if (e != 0)
1454         return TRUE;
1455     return FALSE;
1456 }
1457 
1458 /*
1459  * Current worst-case timeout behavior:
1460  *
1461  * First pass, 1s per udp or tcp server, plus 2s at end.
1462  * Second pass, 1s per udp server, plus 4s.
1463  * Third pass, 1s per udp server, plus 8s.
1464  * Fourth => 16s, etc.
1465  *
1466  * Restated:
1467  * Per UDP server, 1s per pass.
1468  * Per TCP server, 1s.
1469  * Backoff delay, 2**(P+1) - 2, where P is total number of passes.
1470  *
1471  * Total = 2**(P+1) + U*P + T - 2.
1472  *
1473  * If P=3, Total = 3*U + T + 14.
1474  * If P=4, Total = 4*U + T + 30.
1475  *
1476  * Note that if you try to reach two ports on one server, it counts as two.
1477  *
1478  * There is one exception to the above rules.  Whenever a TCP connection is
1479  * established, we wait up to ten seconds for it to finish or fail before
1480  * moving on.  This reduces network traffic significantly in a TCP environment.
1481  */
1482 
1483 krb5_error_code
k5_sendto(krb5_context context,const krb5_data * message,const krb5_data * realm,const struct serverlist * servers,k5_transport_strategy strategy,struct sendto_callback_info * callback_info,krb5_data * reply,struct sockaddr * remoteaddr,socklen_t * remoteaddrlen,int * server_used,int (* msg_handler)(krb5_context,const krb5_data *,void *),void * msg_handler_data)1484 k5_sendto(krb5_context context, const krb5_data *message,
1485           const krb5_data *realm, const struct serverlist *servers,
1486           k5_transport_strategy strategy,
1487           struct sendto_callback_info* callback_info, krb5_data *reply,
1488           struct sockaddr *remoteaddr, socklen_t *remoteaddrlen,
1489           int *server_used,
1490           /* return 0 -> keep going, 1 -> quit */
1491           int (*msg_handler)(krb5_context, const krb5_data *, void *),
1492           void *msg_handler_data)
1493 {
1494     int pass;
1495     time_ms delay;
1496     krb5_error_code retval;
1497     struct conn_state *conns = NULL, *state, **tailptr, *next, *winner;
1498     size_t s;
1499     struct select_state *sel_state = NULL, *seltemp;
1500     char *udpbuf = NULL;
1501     krb5_boolean done = FALSE;
1502 
1503     *reply = empty_data();
1504 
1505     /* One for use here, listing all our fds in use, and one for
1506      * temporary use in service_fds, for the fds of interest.  */
1507     sel_state = malloc(2 * sizeof(*sel_state));
1508     if (sel_state == NULL) {
1509         retval = ENOMEM;
1510         goto cleanup;
1511     }
1512     seltemp = &sel_state[1];
1513     cm_init_selstate(sel_state);
1514 
1515     /* First pass: resolve server hosts, communicate with resulting addresses
1516      * of the preferred transport, and wait 1s for an answer from each. */
1517     for (s = 0; s < servers->nservers && !done; s++) {
1518         /* Find the current tail pointer. */
1519         for (tailptr = &conns; *tailptr != NULL; tailptr = &(*tailptr)->next);
1520         retval = resolve_server(context, realm, servers, s, strategy, message,
1521                                 &udpbuf, &conns);
1522         if (retval)
1523             goto cleanup;
1524         for (state = *tailptr; state != NULL && !done; state = state->next) {
1525             /* Contact each new connection, deferring those which use the
1526              * non-preferred RFC 4120 transport. */
1527             if (state->defer)
1528                 continue;
1529             if (maybe_send(context, state, message, sel_state, realm,
1530                            callback_info))
1531                 continue;
1532             done = service_fds(context, sel_state, 1000, conns, seltemp,
1533                                realm, msg_handler, msg_handler_data, &winner);
1534         }
1535     }
1536 
1537     /* Complete the first pass by contacting servers of the non-preferred RFC
1538      * 4120 transport (if given), waiting 1s for an answer from each. */
1539     for (state = conns; state != NULL && !done; state = state->next) {
1540         if (!state->defer)
1541             continue;
1542         if (maybe_send(context, state, message, sel_state, realm,
1543                        callback_info))
1544             continue;
1545         done = service_fds(context, sel_state, 1000, conns, seltemp,
1546                            realm, msg_handler, msg_handler_data, &winner);
1547     }
1548 
1549     /* Wait for two seconds at the end of the first pass. */
1550     if (!done) {
1551         done = service_fds(context, sel_state, 2000, conns, seltemp,
1552                            realm, msg_handler, msg_handler_data, &winner);
1553     }
1554 
1555     /* Make remaining passes over all of the connections. */
1556     delay = 4000;
1557     for (pass = 1; pass < MAX_PASS && !done; pass++) {
1558         for (state = conns; state != NULL && !done; state = state->next) {
1559             if (maybe_send(context, state, message, sel_state, realm,
1560                            callback_info))
1561                 continue;
1562             done = service_fds(context, sel_state, 1000, conns, seltemp,
1563                                realm, msg_handler, msg_handler_data, &winner);
1564             if (sel_state->nfds == 0)
1565                 break;
1566         }
1567         /* Wait for the delay backoff at the end of this pass. */
1568         if (!done) {
1569             done = service_fds(context, sel_state, delay, conns, seltemp,
1570                                realm, msg_handler, msg_handler_data, &winner);
1571         }
1572         if (sel_state->nfds == 0)
1573             break;
1574         delay *= 2;
1575     }
1576 
1577     if (sel_state->nfds == 0 || !done || winner == NULL) {
1578         retval = KRB5_KDC_UNREACH;
1579         goto cleanup;
1580     }
1581     /* Success!  */
1582     *reply = make_data(winner->in.buf, winner->in.pos);
1583     retval = 0;
1584     winner->in.buf = NULL;
1585     if (server_used != NULL)
1586         *server_used = winner->server_index;
1587     if (remoteaddr != NULL && remoteaddrlen != 0 && *remoteaddrlen > 0)
1588         (void)getpeername(winner->fd, remoteaddr, remoteaddrlen);
1589     TRACE_SENDTO_KDC_RESPONSE(context, reply->length, &winner->addr);
1590 
1591 cleanup:
1592     for (state = conns; state != NULL; state = next) {
1593         next = state->next;
1594         if (state->fd != INVALID_SOCKET) {
1595             if (socktype_for_transport(state->addr.transport) == SOCK_STREAM)
1596                 TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &state->addr);
1597             closesocket(state->fd);
1598             free_http_tls_data(context, state);
1599         }
1600         if (state->in.buf != udpbuf)
1601             free(state->in.buf);
1602         if (callback_info) {
1603             callback_info->pfn_cleanup(callback_info->data,
1604                                        &state->callback_buffer);
1605         }
1606         free(state);
1607     }
1608 
1609     if (reply->data != udpbuf)
1610         free(udpbuf);
1611     free(sel_state);
1612     return retval;
1613 }
1614