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