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