source: heimdal/trunk/lib/krb5/send_to_kdc.c

Last change on this file was 1, checked in by Paul Smedley, 10 years ago

Initial commit of Heimdal 1.5.3

File size: 17.1 KB
Line 
1/*
2 * Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *
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.
16 *
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.
20 *
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.
32 */
33
34#include "krb5_locl.h"
35#include "send_to_kdc_plugin.h"
36
37struct send_to_kdc {
38 krb5_send_to_kdc_func func;
39 void *data;
40};
41
42/*
43 * connect to a remote host and in the case of stream sockets, provide
44 * a timeout for the connexion.
45 */
46
47static int
48timed_connect(int s, struct addrinfo *addr, time_t tmout)
49{
50#ifdef HAVE_POLL
51 socklen_t sl;
52 int err;
53 int flags;
54 int ret;
55
56 if (addr->ai_socktype != SOCK_STREAM)
57 return connect(s, addr->ai_addr, addr->ai_addrlen);
58
59 flags = fcntl(s, F_GETFL);
60 if (flags == -1)
61 return -1;
62
63 fcntl(s, F_SETFL, flags | O_NONBLOCK);
64 ret = connect(s, addr->ai_addr, addr->ai_addrlen);
65 if (ret == -1 && errno != EINPROGRESS)
66 return -1;
67
68 for (;;) {
69 struct pollfd fds;
70
71 fds.fd = s;
72 fds.events = POLLIN | POLLOUT;
73 fds.revents = 0;
74
75 ret = poll(&fds, 1, tmout * 1000);
76 if (ret != -1 || errno != EINTR)
77 break;
78 }
79 fcntl(s, F_SETFL, flags);
80
81 if (ret != 1)
82 return -1;
83
84 sl = sizeof(err);
85 ret = getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &sl);
86 if (ret == -1)
87 return -1;
88 if (err != 0)
89 return -1;
90
91 return 0;
92#else
93 return connect(s, addr->ai_addr, addr->ai_addrlen);
94#endif
95}
96
97/*
98 * send the data in `req' on the socket `fd' (which is datagram iff udp)
99 * waiting `tmout' for a reply and returning the reply in `rep'.
100 * iff limit read up to this many bytes
101 * returns 0 and data in `rep' if succesful, otherwise -1
102 */
103
104static int
105recv_loop (krb5_socket_t fd,
106 time_t tmout,
107 int udp,
108 size_t limit,
109 krb5_data *rep)
110{
111 fd_set fdset;
112 struct timeval timeout;
113 int ret;
114 int nbytes;
115
116#ifndef NO_LIMIT_FD_SETSIZE
117 if (fd >= FD_SETSIZE) {
118 return -1;
119 }
120#endif
121
122 krb5_data_zero(rep);
123 do {
124 FD_ZERO(&fdset);
125 FD_SET(fd, &fdset);
126 timeout.tv_sec = tmout;
127 timeout.tv_usec = 0;
128 ret = select (fd + 1, &fdset, NULL, NULL, &timeout);
129 if (ret < 0) {
130 if (errno == EINTR)
131 continue;
132 return -1;
133 } else if (ret == 0) {
134 return 0;
135 } else {
136 void *tmp;
137
138 if (rk_SOCK_IOCTL (fd, FIONREAD, &nbytes) < 0) {
139 krb5_data_free (rep);
140 return -1;
141 }
142 if(nbytes <= 0)
143 return 0;
144
145 if (limit)
146 nbytes = min((size_t)nbytes, limit - rep->length);
147
148 tmp = realloc (rep->data, rep->length + nbytes);
149 if (tmp == NULL) {
150 krb5_data_free (rep);
151 return -1;
152 }
153 rep->data = tmp;
154 ret = recv (fd, (char*)tmp + rep->length, nbytes, 0);
155 if (ret < 0) {
156 krb5_data_free (rep);
157 return -1;
158 }
159 rep->length += ret;
160 }
161 } while(!udp && (limit == 0 || rep->length < limit));
162 return 0;
163}
164
165/*
166 * Send kerberos requests and receive a reply on a udp or any other kind
167 * of a datagram socket. See `recv_loop'.
168 */
169
170static int
171send_and_recv_udp(krb5_socket_t fd,
172 time_t tmout,
173 const krb5_data *req,
174 krb5_data *rep)
175{
176 if (send (fd, req->data, req->length, 0) < 0)
177 return -1;
178
179 return recv_loop(fd, tmout, 1, 0, rep);
180}
181
182/*
183 * `send_and_recv' for a TCP (or any other stream) socket.
184 * Since there are no record limits on a stream socket the protocol here
185 * is to prepend the request with 4 bytes of its length and the reply
186 * is similarly encoded.
187 */
188
189static int
190send_and_recv_tcp(krb5_socket_t fd,
191 time_t tmout,
192 const krb5_data *req,
193 krb5_data *rep)
194{
195 unsigned char len[4];
196 unsigned long rep_len;
197 krb5_data len_data;
198
199 _krb5_put_int(len, req->length, 4);
200 if(net_write (fd, len, sizeof(len)) < 0)
201 return -1;
202 if(net_write (fd, req->data, req->length) < 0)
203 return -1;
204 if (recv_loop (fd, tmout, 0, 4, &len_data) < 0)
205 return -1;
206 if (len_data.length != 4) {
207 krb5_data_free (&len_data);
208 return -1;
209 }
210 _krb5_get_int(len_data.data, &rep_len, 4);
211 krb5_data_free (&len_data);
212 if (recv_loop (fd, tmout, 0, rep_len, rep) < 0)
213 return -1;
214 if(rep->length != rep_len) {
215 krb5_data_free (rep);
216 return -1;
217 }
218 return 0;
219}
220
221int
222_krb5_send_and_recv_tcp(krb5_socket_t fd,
223 time_t tmout,
224 const krb5_data *req,
225 krb5_data *rep)
226{
227 return send_and_recv_tcp(fd, tmout, req, rep);
228}
229
230/*
231 * `send_and_recv' tailored for the HTTP protocol.
232 */
233
234static int
235send_and_recv_http(krb5_socket_t fd,
236 time_t tmout,
237 const char *prefix,
238 const krb5_data *req,
239 krb5_data *rep)
240{
241 char *request = NULL;
242 char *str;
243 int ret;
244 int len = base64_encode(req->data, req->length, &str);
245
246 if(len < 0)
247 return -1;
248 ret = asprintf(&request, "GET %s%s HTTP/1.0\r\n\r\n", prefix, str);
249 free(str);
250 if (ret < 0 || request == NULL)
251 return -1;
252 ret = net_write (fd, request, strlen(request));
253 free (request);
254 if (ret < 0)
255 return ret;
256 ret = recv_loop(fd, tmout, 0, 0, rep);
257 if(ret)
258 return ret;
259 {
260 unsigned long rep_len;
261 char *s, *p;
262
263 s = realloc(rep->data, rep->length + 1);
264 if (s == NULL) {
265 krb5_data_free (rep);
266 return -1;
267 }
268 s[rep->length] = 0;
269 p = strstr(s, "\r\n\r\n");
270 if(p == NULL) {
271 krb5_data_zero(rep);
272 free(s);
273 return -1;
274 }
275 p += 4;
276 rep->data = s;
277 rep->length -= p - s;
278 if(rep->length < 4) { /* remove length */
279 krb5_data_zero(rep);
280 free(s);
281 return -1;
282 }
283 rep->length -= 4;
284 _krb5_get_int(p, &rep_len, 4);
285 if (rep_len != rep->length) {
286 krb5_data_zero(rep);
287 free(s);
288 return -1;
289 }
290 memmove(rep->data, p + 4, rep->length);
291 }
292 return 0;
293}
294
295static int
296init_port(const char *s, int fallback)
297{
298 if (s) {
299 int tmp;
300
301 sscanf (s, "%d", &tmp);
302 return htons(tmp);
303 } else
304 return fallback;
305}
306
307/*
308 * Return 0 if succesful, otherwise 1
309 */
310
311static int
312send_via_proxy (krb5_context context,
313 const krb5_krbhst_info *hi,
314 const krb5_data *send_data,
315 krb5_data *receive)
316{
317 char *proxy2 = strdup(context->http_proxy);
318 char *proxy = proxy2;
319 char *prefix = NULL;
320 char *colon;
321 struct addrinfo hints;
322 struct addrinfo *ai, *a;
323 int ret;
324 krb5_socket_t s = rk_INVALID_SOCKET;
325 char portstr[NI_MAXSERV];
326
327 if (proxy == NULL)
328 return ENOMEM;
329 if (strncmp (proxy, "http://", 7) == 0)
330 proxy += 7;
331
332 colon = strchr(proxy, ':');
333 if(colon != NULL)
334 *colon++ = '\0';
335 memset (&hints, 0, sizeof(hints));
336 hints.ai_family = PF_UNSPEC;
337 hints.ai_socktype = SOCK_STREAM;
338 snprintf (portstr, sizeof(portstr), "%d",
339 ntohs(init_port (colon, htons(80))));
340 ret = getaddrinfo (proxy, portstr, &hints, &ai);
341 free (proxy2);
342 if (ret)
343 return krb5_eai_to_heim_errno(ret, errno);
344
345 for (a = ai; a != NULL; a = a->ai_next) {
346 s = socket (a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
347 if (s < 0)
348 continue;
349 rk_cloexec(s);
350 if (timed_connect (s, a, context->kdc_timeout) < 0) {
351 rk_closesocket (s);
352 continue;
353 }
354 break;
355 }
356 if (a == NULL) {
357 freeaddrinfo (ai);
358 return 1;
359 }
360 freeaddrinfo (ai);
361
362 ret = asprintf(&prefix, "http://%s/", hi->hostname);
363 if(ret < 0 || prefix == NULL) {
364 close(s);
365 return 1;
366 }
367 ret = send_and_recv_http(s, context->kdc_timeout,
368 prefix, send_data, receive);
369 rk_closesocket (s);
370 free(prefix);
371 if(ret == 0 && receive->length != 0)
372 return 0;
373 return 1;
374}
375
376static krb5_error_code
377send_via_plugin(krb5_context context,
378 krb5_krbhst_info *hi,
379 time_t timeout,
380 const krb5_data *send_data,
381 krb5_data *receive)
382{
383 struct krb5_plugin *list = NULL, *e;
384 krb5_error_code ret;
385
386 ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA, KRB5_PLUGIN_SEND_TO_KDC, &list);
387 if(ret != 0 || list == NULL)
388 return KRB5_PLUGIN_NO_HANDLE;
389
390 for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) {
391 krb5plugin_send_to_kdc_ftable *service;
392 void *ctx;
393
394 service = _krb5_plugin_get_symbol(e);
395 if (service->minor_version != 0)
396 continue;
397
398 (*service->init)(context, &ctx);
399 ret = (*service->send_to_kdc)(context, ctx, hi,
400 timeout, send_data, receive);
401 (*service->fini)(ctx);
402 if (ret == 0)
403 break;
404 if (ret != KRB5_PLUGIN_NO_HANDLE) {
405 krb5_set_error_message(context, ret,
406 N_("Plugin send_to_kdc failed to "
407 "lookup with error: %d", ""), ret);
408 break;
409 }
410 }
411 _krb5_plugin_free(list);
412 return KRB5_PLUGIN_NO_HANDLE;
413}
414
415
416/*
417 * Send the data `send' to one host from `handle` and get back the reply
418 * in `receive'.
419 */
420
421KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
422krb5_sendto (krb5_context context,
423 const krb5_data *send_data,
424 krb5_krbhst_handle handle,
425 krb5_data *receive)
426{
427 krb5_error_code ret;
428 krb5_socket_t fd;
429 size_t i;
430
431 krb5_data_zero(receive);
432
433 for (i = 0; i < context->max_retries; ++i) {
434 krb5_krbhst_info *hi;
435
436 while (krb5_krbhst_next(context, handle, &hi) == 0) {
437 struct addrinfo *ai, *a;
438
439 _krb5_debug(context, 2,
440 "trying to communicate with host %s in realm %s",
441 hi->hostname, _krb5_krbhst_get_realm(handle));
442
443 if (context->send_to_kdc) {
444 struct send_to_kdc *s = context->send_to_kdc;
445
446 ret = (*s->func)(context, s->data, hi,
447 context->kdc_timeout, send_data, receive);
448 if (ret == 0 && receive->length != 0)
449 goto out;
450 continue;
451 }
452
453 ret = send_via_plugin(context, hi, context->kdc_timeout,
454 send_data, receive);
455 if (ret == 0 && receive->length != 0)
456 goto out;
457 else if (ret != KRB5_PLUGIN_NO_HANDLE)
458 continue;
459
460 if(hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
461 if (send_via_proxy (context, hi, send_data, receive) == 0) {
462 ret = 0;
463 goto out;
464 }
465 continue;
466 }
467
468 ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
469 if (ret)
470 continue;
471
472 for (a = ai; a != NULL; a = a->ai_next) {
473 fd = socket (a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);
474 if (rk_IS_BAD_SOCKET(fd))
475 continue;
476 rk_cloexec(fd);
477 if (timed_connect (fd, a, context->kdc_timeout) < 0) {
478 rk_closesocket (fd);
479 continue;
480 }
481 switch (hi->proto) {
482 case KRB5_KRBHST_HTTP :
483 ret = send_and_recv_http(fd, context->kdc_timeout,
484 "", send_data, receive);
485 break;
486 case KRB5_KRBHST_TCP :
487 ret = send_and_recv_tcp (fd, context->kdc_timeout,
488 send_data, receive);
489 break;
490 case KRB5_KRBHST_UDP :
491 ret = send_and_recv_udp (fd, context->kdc_timeout,
492 send_data, receive);
493 break;
494 }
495 rk_closesocket (fd);
496 if(ret == 0 && receive->length != 0)
497 goto out;
498 }
499 }
500 krb5_krbhst_reset(context, handle);
501 }
502 krb5_clear_error_message (context);
503 ret = KRB5_KDC_UNREACH;
504out:
505 _krb5_debug(context, 2,
506 "result of trying to talk to realm %s = %d",
507 _krb5_krbhst_get_realm(handle), ret);
508 return ret;
509}
510
511KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
512krb5_sendto_kdc(krb5_context context,
513 const krb5_data *send_data,
514 const krb5_realm *realm,
515 krb5_data *receive)
516{
517 return krb5_sendto_kdc_flags(context, send_data, realm, receive, 0);
518}
519
520KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
521krb5_sendto_kdc_flags(krb5_context context,
522 const krb5_data *send_data,
523 const krb5_realm *realm,
524 krb5_data *receive,
525 int flags)
526{
527 krb5_error_code ret;
528 krb5_sendto_ctx ctx;
529
530 ret = krb5_sendto_ctx_alloc(context, &ctx);
531 if (ret)
532 return ret;
533 krb5_sendto_ctx_add_flags(ctx, flags);
534 krb5_sendto_ctx_set_func(ctx, _krb5_kdc_retry, NULL);
535
536 ret = krb5_sendto_context(context, ctx, send_data, *realm, receive);
537 krb5_sendto_ctx_free(context, ctx);
538 return ret;
539}
540
541KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
542krb5_set_send_to_kdc_func(krb5_context context,
543 krb5_send_to_kdc_func func,
544 void *data)
545{
546 free(context->send_to_kdc);
547 if (func == NULL) {
548 context->send_to_kdc = NULL;
549 return 0;
550 }
551
552 context->send_to_kdc = malloc(sizeof(*context->send_to_kdc));
553 if (context->send_to_kdc == NULL) {
554 krb5_set_error_message(context, ENOMEM,
555 N_("malloc: out of memory", ""));
556 return ENOMEM;
557 }
558
559 context->send_to_kdc->func = func;
560 context->send_to_kdc->data = data;
561 return 0;
562}
563
564KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
565_krb5_copy_send_to_kdc_func(krb5_context context, krb5_context to)
566{
567 if (context->send_to_kdc)
568 return krb5_set_send_to_kdc_func(to,
569 context->send_to_kdc->func,
570 context->send_to_kdc->data);
571 else
572 return krb5_set_send_to_kdc_func(to, NULL, NULL);
573}
574
575
576
577struct krb5_sendto_ctx_data {
578 int flags;
579 int type;
580 krb5_sendto_ctx_func func;
581 void *data;
582};
583
584KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
585krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx)
586{
587 *ctx = calloc(1, sizeof(**ctx));
588 if (*ctx == NULL) {
589 krb5_set_error_message(context, ENOMEM,
590 N_("malloc: out of memory", ""));
591 return ENOMEM;
592 }
593 return 0;
594}
595
596KRB5_LIB_FUNCTION void KRB5_LIB_CALL
597krb5_sendto_ctx_add_flags(krb5_sendto_ctx ctx, int flags)
598{
599 ctx->flags |= flags;
600}
601
602KRB5_LIB_FUNCTION int KRB5_LIB_CALL
603krb5_sendto_ctx_get_flags(krb5_sendto_ctx ctx)
604{
605 return ctx->flags;
606}
607
608KRB5_LIB_FUNCTION void KRB5_LIB_CALL
609krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type)
610{
611 ctx->type = type;
612}
613
614
615KRB5_LIB_FUNCTION void KRB5_LIB_CALL
616krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx,
617 krb5_sendto_ctx_func func,
618 void *data)
619{
620 ctx->func = func;
621 ctx->data = data;
622}
623
624KRB5_LIB_FUNCTION void KRB5_LIB_CALL
625krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx)
626{
627 memset(ctx, 0, sizeof(*ctx));
628 free(ctx);
629}
630
631KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
632krb5_sendto_context(krb5_context context,
633 krb5_sendto_ctx ctx,
634 const krb5_data *send_data,
635 const krb5_realm realm,
636 krb5_data *receive)
637{
638 krb5_error_code ret;
639 krb5_krbhst_handle handle = NULL;
640 int type, freectx = 0;
641 int action;
642
643 krb5_data_zero(receive);
644
645 if (ctx == NULL) {
646 freectx = 1;
647 ret = krb5_sendto_ctx_alloc(context, &ctx);
648 if (ret)
649 return ret;
650 }
651
652 type = ctx->type;
653 if (type == 0) {
654 if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
655 type = KRB5_KRBHST_ADMIN;
656 else
657 type = KRB5_KRBHST_KDC;
658 }
659
660 if ((int)send_data->length > context->large_msg_size)
661 ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
662
663 /* loop until we get back a appropriate response */
664
665 do {
666 action = KRB5_SENDTO_DONE;
667
668 krb5_data_free(receive);
669
670 if (handle == NULL) {
671 ret = krb5_krbhst_init_flags(context, realm, type,
672 ctx->flags, &handle);
673 if (ret) {
674 if (freectx)
675 krb5_sendto_ctx_free(context, ctx);
676 return ret;
677 }
678 }
679
680 ret = krb5_sendto(context, send_data, handle, receive);
681 if (ret)
682 break;
683 if (ctx->func) {
684 ret = (*ctx->func)(context, ctx, ctx->data, receive, &action);
685 if (ret)
686 break;
687 }
688 if (action != KRB5_SENDTO_CONTINUE) {
689 krb5_krbhst_free(context, handle);
690 handle = NULL;
691 }
692 } while (action != KRB5_SENDTO_DONE);
693 if (handle)
694 krb5_krbhst_free(context, handle);
695 if (ret == KRB5_KDC_UNREACH)
696 krb5_set_error_message(context, ret,
697 N_("unable to reach any KDC in realm %s", ""),
698 realm);
699 if (ret)
700 krb5_data_free(receive);
701 if (freectx)
702 krb5_sendto_ctx_free(context, ctx);
703 return ret;
704}
705
706krb5_error_code KRB5_CALLCONV
707_krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data,
708 const krb5_data *reply, int *action)
709{
710 krb5_error_code ret;
711 KRB_ERROR error;
712
713 if(krb5_rd_error(context, reply, &error))
714 return 0;
715
716 ret = krb5_error_from_rd_error(context, &error, NULL);
717 krb5_free_error_contents(context, &error);
718
719 switch(ret) {
720 case KRB5KRB_ERR_RESPONSE_TOO_BIG: {
721 if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG)
722 break;
723 krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG);
724 *action = KRB5_SENDTO_RESTART;
725 break;
726 }
727 case KRB5KDC_ERR_SVC_UNAVAILABLE:
728 *action = KRB5_SENDTO_CONTINUE;
729 break;
730 }
731 return 0;
732}
Note: See TracBrowser for help on using the repository browser.