Ensure that timeouts apply to TCP socket connexions.
[heimdal.git] / lib / krb5 / send_to_kdc.c
blob73a56a4e38ec36055e1f2435274c963b59709e91
1 /*
2 * Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
34 #include "krb5_locl.h"
35 #include "send_to_kdc_plugin.h"
37 struct send_to_kdc {
38 krb5_send_to_kdc_func func;
39 void *data;
43 * connect to a remote host and in the case of stream sockets, provide
44 * a timeout for the connexion.
47 static int
48 timed_connect(int s, struct addrinfo *addr, time_t tmout)
50 struct timeval timeout;
51 fd_set wfds;
52 int flags;
53 int ret;
55 if (addr->ai_socktype != SOCK_STREAM)
56 return connect(s, addr->ai_addr, addr->ai_addrlen);
58 flags = fcntl(s, F_GETFL);
59 if (flags == -1)
60 return -1;
62 fcntl(s, F_SETFL, flags | O_NONBLOCK);
63 ret = connect(s, addr->ai_addr, addr->ai_addrlen);
64 if (ret == -1 && errno != EINPROGRESS)
65 return -1;
67 for (;;) {
68 FD_ZERO(&wfds);
69 FD_SET(s, &wfds);
70 timeout.tv_sec = tmout;
71 timeout.tv_usec = 0;
73 ret = select(s + 1, NULL, &wfds, NULL, &timeout);
74 if (ret != -1 || errno != EINTR)
75 break;
77 fcntl(s, F_SETFL, flags);
79 if (ret != 1)
80 return -1;
82 return 0;
86 * send the data in `req' on the socket `fd' (which is datagram iff udp)
87 * waiting `tmout' for a reply and returning the reply in `rep'.
88 * iff limit read up to this many bytes
89 * returns 0 and data in `rep' if succesful, otherwise -1
92 static int
93 recv_loop (krb5_socket_t fd,
94 time_t tmout,
95 int udp,
96 size_t limit,
97 krb5_data *rep)
99 fd_set fdset;
100 struct timeval timeout;
101 int ret;
102 int nbytes;
104 #ifndef NO_LIMIT_FD_SETSIZE
105 if (fd >= FD_SETSIZE) {
106 return -1;
108 #endif
110 krb5_data_zero(rep);
111 do {
112 FD_ZERO(&fdset);
113 FD_SET(fd, &fdset);
114 timeout.tv_sec = tmout;
115 timeout.tv_usec = 0;
116 ret = select (fd + 1, &fdset, NULL, NULL, &timeout);
117 if (ret < 0) {
118 if (errno == EINTR)
119 continue;
120 return -1;
121 } else if (ret == 0) {
122 return 0;
123 } else {
124 void *tmp;
126 if (rk_SOCK_IOCTL (fd, FIONREAD, &nbytes) < 0) {
127 krb5_data_free (rep);
128 return -1;
130 if(nbytes <= 0)
131 return 0;
133 if (limit)
134 nbytes = min((size_t)nbytes, limit - rep->length);
136 tmp = realloc (rep->data, rep->length + nbytes);
137 if (tmp == NULL) {
138 krb5_data_free (rep);
139 return -1;
141 rep->data = tmp;
142 ret = recv (fd, (char*)tmp + rep->length, nbytes, 0);
143 if (ret < 0) {
144 krb5_data_free (rep);
145 return -1;
147 rep->length += ret;
149 } while(!udp && (limit == 0 || rep->length < limit));
150 return 0;
154 * Send kerberos requests and receive a reply on a udp or any other kind
155 * of a datagram socket. See `recv_loop'.
158 static int
159 send_and_recv_udp(krb5_socket_t fd,
160 time_t tmout,
161 const krb5_data *req,
162 krb5_data *rep)
164 if (send (fd, req->data, req->length, 0) < 0)
165 return -1;
167 return recv_loop(fd, tmout, 1, 0, rep);
171 * `send_and_recv' for a TCP (or any other stream) socket.
172 * Since there are no record limits on a stream socket the protocol here
173 * is to prepend the request with 4 bytes of its length and the reply
174 * is similarly encoded.
177 static int
178 send_and_recv_tcp(krb5_socket_t fd,
179 time_t tmout,
180 const krb5_data *req,
181 krb5_data *rep)
183 unsigned char len[4];
184 unsigned long rep_len;
185 krb5_data len_data;
187 _krb5_put_int(len, req->length, 4);
188 if(net_write (fd, len, sizeof(len)) < 0)
189 return -1;
190 if(net_write (fd, req->data, req->length) < 0)
191 return -1;
192 if (recv_loop (fd, tmout, 0, 4, &len_data) < 0)
193 return -1;
194 if (len_data.length != 4) {
195 krb5_data_free (&len_data);
196 return -1;
198 _krb5_get_int(len_data.data, &rep_len, 4);
199 krb5_data_free (&len_data);
200 if (recv_loop (fd, tmout, 0, rep_len, rep) < 0)
201 return -1;
202 if(rep->length != rep_len) {
203 krb5_data_free (rep);
204 return -1;
206 return 0;
210 _krb5_send_and_recv_tcp(krb5_socket_t fd,
211 time_t tmout,
212 const krb5_data *req,
213 krb5_data *rep)
215 return send_and_recv_tcp(fd, tmout, req, rep);
219 * `send_and_recv' tailored for the HTTP protocol.
222 static int
223 send_and_recv_http(krb5_socket_t fd,
224 time_t tmout,
225 const char *prefix,
226 const krb5_data *req,
227 krb5_data *rep)
229 char *request = NULL;
230 char *str;
231 int ret;
232 int len = base64_encode(req->data, req->length, &str);
234 if(len < 0)
235 return -1;
236 ret = asprintf(&request, "GET %s%s HTTP/1.0\r\n\r\n", prefix, str);
237 free(str);
238 if (ret < 0 || request == NULL)
239 return -1;
240 ret = net_write (fd, request, strlen(request));
241 free (request);
242 if (ret < 0)
243 return ret;
244 ret = recv_loop(fd, tmout, 0, 0, rep);
245 if(ret)
246 return ret;
248 unsigned long rep_len;
249 char *s, *p;
251 s = realloc(rep->data, rep->length + 1);
252 if (s == NULL) {
253 krb5_data_free (rep);
254 return -1;
256 s[rep->length] = 0;
257 p = strstr(s, "\r\n\r\n");
258 if(p == NULL) {
259 krb5_data_zero(rep);
260 free(s);
261 return -1;
263 p += 4;
264 rep->data = s;
265 rep->length -= p - s;
266 if(rep->length < 4) { /* remove length */
267 krb5_data_zero(rep);
268 free(s);
269 return -1;
271 rep->length -= 4;
272 _krb5_get_int(p, &rep_len, 4);
273 if (rep_len != rep->length) {
274 krb5_data_zero(rep);
275 free(s);
276 return -1;
278 memmove(rep->data, p + 4, rep->length);
280 return 0;
283 static int
284 init_port(const char *s, int fallback)
286 if (s) {
287 int tmp;
289 sscanf (s, "%d", &tmp);
290 return htons(tmp);
291 } else
292 return fallback;
296 * Return 0 if succesful, otherwise 1
299 static int
300 send_via_proxy (krb5_context context,
301 const krb5_krbhst_info *hi,
302 const krb5_data *send_data,
303 krb5_data *receive)
305 char *proxy2 = strdup(context->http_proxy);
306 char *proxy = proxy2;
307 char *prefix = NULL;
308 char *colon;
309 struct addrinfo hints;
310 struct addrinfo *ai, *a;
311 int ret;
312 krb5_socket_t s = rk_INVALID_SOCKET;
313 char portstr[NI_MAXSERV];
315 if (proxy == NULL)
316 return ENOMEM;
317 if (strncmp (proxy, "http://", 7) == 0)
318 proxy += 7;
320 colon = strchr(proxy, ':');
321 if(colon != NULL)
322 *colon++ = '\0';
323 memset (&hints, 0, sizeof(hints));
324 hints.ai_family = PF_UNSPEC;
325 hints.ai_socktype = SOCK_STREAM;
326 snprintf (portstr, sizeof(portstr), "%d",
327 ntohs(init_port (colon, htons(80))));
328 ret = getaddrinfo (proxy, portstr, &hints, &ai);
329 free (proxy2);
330 if (ret)
331 return krb5_eai_to_heim_errno(ret, errno);
333 for (a = ai; a != NULL; a = a->ai_next) {
334 s = socket (a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
335 if (s < 0)
336 continue;
337 rk_cloexec(s);
338 if (timed_connect (s, a, context->kdc_timeout) < 0) {
339 rk_closesocket (s);
340 continue;
342 break;
344 if (a == NULL) {
345 freeaddrinfo (ai);
346 return 1;
348 freeaddrinfo (ai);
350 ret = asprintf(&prefix, "http://%s/", hi->hostname);
351 if(ret < 0 || prefix == NULL) {
352 close(s);
353 return 1;
355 ret = send_and_recv_http(s, context->kdc_timeout,
356 prefix, send_data, receive);
357 rk_closesocket (s);
358 free(prefix);
359 if(ret == 0 && receive->length != 0)
360 return 0;
361 return 1;
364 static krb5_error_code
365 send_via_plugin(krb5_context context,
366 krb5_krbhst_info *hi,
367 time_t timeout,
368 const krb5_data *send_data,
369 krb5_data *receive)
371 struct krb5_plugin *list = NULL, *e;
372 krb5_error_code ret;
374 ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA, KRB5_PLUGIN_SEND_TO_KDC, &list);
375 if(ret != 0 || list == NULL)
376 return KRB5_PLUGIN_NO_HANDLE;
378 for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) {
379 krb5plugin_send_to_kdc_ftable *service;
380 void *ctx;
382 service = _krb5_plugin_get_symbol(e);
383 if (service->minor_version != 0)
384 continue;
386 (*service->init)(context, &ctx);
387 ret = (*service->send_to_kdc)(context, ctx, hi,
388 timeout, send_data, receive);
389 (*service->fini)(ctx);
390 if (ret == 0)
391 break;
392 if (ret != KRB5_PLUGIN_NO_HANDLE) {
393 krb5_set_error_message(context, ret,
394 N_("Plugin send_to_kdc failed to "
395 "lookup with error: %d", ""), ret);
396 break;
399 _krb5_plugin_free(list);
400 return KRB5_PLUGIN_NO_HANDLE;
405 * Send the data `send' to one host from `handle` and get back the reply
406 * in `receive'.
409 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
410 krb5_sendto (krb5_context context,
411 const krb5_data *send_data,
412 krb5_krbhst_handle handle,
413 krb5_data *receive)
415 krb5_error_code ret;
416 krb5_socket_t fd;
417 size_t i;
419 krb5_data_zero(receive);
421 for (i = 0; i < context->max_retries; ++i) {
422 krb5_krbhst_info *hi;
424 while (krb5_krbhst_next(context, handle, &hi) == 0) {
425 struct addrinfo *ai, *a;
427 _krb5_debug(context, 2,
428 "trying to communicate with host %s in realm %s",
429 hi->hostname, _krb5_krbhst_get_realm(handle));
431 if (context->send_to_kdc) {
432 struct send_to_kdc *s = context->send_to_kdc;
434 ret = (*s->func)(context, s->data, hi,
435 context->kdc_timeout, send_data, receive);
436 if (ret == 0 && receive->length != 0)
437 goto out;
438 continue;
441 ret = send_via_plugin(context, hi, context->kdc_timeout,
442 send_data, receive);
443 if (ret == 0 && receive->length != 0)
444 goto out;
445 else if (ret != KRB5_PLUGIN_NO_HANDLE)
446 continue;
448 if(hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
449 if (send_via_proxy (context, hi, send_data, receive) == 0) {
450 ret = 0;
451 goto out;
453 continue;
456 ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
457 if (ret)
458 continue;
460 for (a = ai; a != NULL; a = a->ai_next) {
461 fd = socket (a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
462 if (rk_IS_BAD_SOCKET(fd))
463 continue;
464 rk_cloexec(fd);
465 if (timed_connect (fd, a, context->kdc_timeout) < 0) {
466 rk_closesocket (fd);
467 continue;
469 switch (hi->proto) {
470 case KRB5_KRBHST_HTTP :
471 ret = send_and_recv_http(fd, context->kdc_timeout,
472 "", send_data, receive);
473 break;
474 case KRB5_KRBHST_TCP :
475 ret = send_and_recv_tcp (fd, context->kdc_timeout,
476 send_data, receive);
477 break;
478 case KRB5_KRBHST_UDP :
479 ret = send_and_recv_udp (fd, context->kdc_timeout,
480 send_data, receive);
481 break;
483 rk_closesocket (fd);
484 if(ret == 0 && receive->length != 0)
485 goto out;
488 krb5_krbhst_reset(context, handle);
490 krb5_clear_error_message (context);
491 ret = KRB5_KDC_UNREACH;
492 out:
493 _krb5_debug(context, 2,
494 "result of trying to talk to realm %s = %d",
495 _krb5_krbhst_get_realm(handle), ret);
496 return ret;
499 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
500 krb5_sendto_kdc(krb5_context context,
501 const krb5_data *send_data,
502 const krb5_realm *realm,
503 krb5_data *receive)
505 return krb5_sendto_kdc_flags(context, send_data, realm, receive, 0);
508 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
509 krb5_sendto_kdc_flags(krb5_context context,
510 const krb5_data *send_data,
511 const krb5_realm *realm,
512 krb5_data *receive,
513 int flags)
515 krb5_error_code ret;
516 krb5_sendto_ctx ctx;
518 ret = krb5_sendto_ctx_alloc(context, &ctx);
519 if (ret)
520 return ret;
521 krb5_sendto_ctx_add_flags(ctx, flags);
522 krb5_sendto_ctx_set_func(ctx, _krb5_kdc_retry, NULL);
524 ret = krb5_sendto_context(context, ctx, send_data, *realm, receive);
525 krb5_sendto_ctx_free(context, ctx);
526 return ret;
529 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
530 krb5_set_send_to_kdc_func(krb5_context context,
531 krb5_send_to_kdc_func func,
532 void *data)
534 free(context->send_to_kdc);
535 if (func == NULL) {
536 context->send_to_kdc = NULL;
537 return 0;
540 context->send_to_kdc = malloc(sizeof(*context->send_to_kdc));
541 if (context->send_to_kdc == NULL) {
542 krb5_set_error_message(context, ENOMEM,
543 N_("malloc: out of memory", ""));
544 return ENOMEM;
547 context->send_to_kdc->func = func;
548 context->send_to_kdc->data = data;
549 return 0;
552 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
553 _krb5_copy_send_to_kdc_func(krb5_context context, krb5_context to)
555 if (context->send_to_kdc)
556 return krb5_set_send_to_kdc_func(to,
557 context->send_to_kdc->func,
558 context->send_to_kdc->data);
559 else
560 return krb5_set_send_to_kdc_func(to, NULL, NULL);
565 struct krb5_sendto_ctx_data {
566 int flags;
567 int type;
568 krb5_sendto_ctx_func func;
569 void *data;
572 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
573 krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx)
575 *ctx = calloc(1, sizeof(**ctx));
576 if (*ctx == NULL) {
577 krb5_set_error_message(context, ENOMEM,
578 N_("malloc: out of memory", ""));
579 return ENOMEM;
581 return 0;
584 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
585 krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags)
587 ctx->flags |= flags;
590 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
591 krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)
593 return ctx->flags;
596 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
597 krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type)
599 ctx->type = type;
603 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
604 krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx,
605 krb5_sendto_ctx_func func,
606 void *data)
608 ctx->func = func;
609 ctx->data = data;
612 KRB5_LIB_FUNCTION void KRB5_LIB_CALL
613 krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx)
615 memset(ctx, 0, sizeof(*ctx));
616 free(ctx);
619 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
620 krb5_sendto_context(krb5_context context,
621 krb5_sendto_ctx ctx,
622 const krb5_data *send_data,
623 const krb5_realm realm,
624 krb5_data *receive)
626 krb5_error_code ret;
627 krb5_krbhst_handle handle = NULL;
628 int type, freectx = 0;
629 int action;
631 krb5_data_zero(receive);
633 if (ctx == NULL) {
634 freectx = 1;
635 ret = krb5_sendto_ctx_alloc(context, &ctx);
636 if (ret)
637 return ret;
640 type = ctx->type;
641 if (type == 0) {
642 if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
643 type = KRB5_KRBHST_ADMIN;
644 else
645 type = KRB5_KRBHST_KDC;
648 if ((int)send_data->length > context->large_msg_size)
649 ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
651 /* loop until we get back a appropriate response */
653 do {
654 action = KRB5_SENDTO_DONE;
656 krb5_data_free(receive);
658 if (handle == NULL) {
659 ret = krb5_krbhst_init_flags(context, realm, type,
660 ctx->flags, &handle);
661 if (ret) {
662 if (freectx)
663 krb5_sendto_ctx_free(context, ctx);
664 return ret;
668 ret = krb5_sendto(context, send_data, handle, receive);
669 if (ret)
670 break;
671 if (ctx->func) {
672 ret = (*ctx->func)(context, ctx, ctx->data, receive, &action);
673 if (ret)
674 break;
676 if (action != KRB5_SENDTO_CONTINUE) {
677 krb5_krbhst_free(context, handle);
678 handle = NULL;
680 } while (action != KRB5_SENDTO_DONE);
681 if (handle)
682 krb5_krbhst_free(context, handle);
683 if (ret == KRB5_KDC_UNREACH)
684 krb5_set_error_message(context, ret,
685 N_("unable to reach any KDC in realm %s", ""),
686 realm);
687 if (ret)
688 krb5_data_free(receive);
689 if (freectx)
690 krb5_sendto_ctx_free(context, ctx);
691 return ret;
694 krb5_error_code KRB5_CALLCONV
695 _krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data,
696 const krb5_data *reply, int *action)
698 krb5_error_code ret;
699 KRB_ERROR error;
701 if(krb5_rd_error(context, reply, &error))
702 return 0;
704 ret = krb5_error_from_rd_error(context, &error, NULL);
705 krb5_free_error_contents(context, &error);
707 switch(ret) {
708 case KRB5KRB_ERR_RESPONSE_TOO_BIG: {
709 if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG)
710 break;
711 krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG);
712 *action = KRB5_SENDTO_RESTART;
713 break;
715 case KRB5KDC_ERR_SVC_UNAVAILABLE:
716 *action = KRB5_SENDTO_CONTINUE;
717 break;
719 return 0;