xref: /freebsd/crypto/heimdal/lib/krb5/send_to_kdc.c (revision 23f282aa31e9b6fceacd449020e936e98d6f2298)
1 /*
2  * Copyright (c) 1997 - 2000 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 
36 RCSID("$Id: send_to_kdc.c,v 1.36 2000/01/06 07:59:11 assar Exp $");
37 
38 /*
39  * send the data in `req' on the socket `fd' (which is datagram iff udp)
40  * waiting `tmout' for a reply and returning the reply in `rep'.
41  * iff limit read up to this many bytes
42  * returns 0 and data in `rep' if succesful, otherwise -1
43  */
44 
45 static int
46 recv_loop (int fd,
47 	   time_t tmout,
48 	   int udp,
49 	   size_t limit,
50 	   krb5_data *rep)
51 {
52      fd_set fdset;
53      struct timeval timeout;
54      int ret;
55      int nbytes;
56 
57      krb5_data_zero(rep);
58      do {
59 	 FD_ZERO(&fdset);
60 	 FD_SET(fd, &fdset);
61 	 timeout.tv_sec  = tmout;
62 	 timeout.tv_usec = 0;
63 	 ret = select (fd + 1, &fdset, NULL, NULL, &timeout);
64 	 if (ret < 0) {
65 	     if (errno == EINTR)
66 		 continue;
67 	     return -1;
68 	 } else if (ret == 0) {
69 	     return 0;
70 	 } else {
71 	     void *tmp;
72 
73 	     if (ioctl (fd, FIONREAD, &nbytes) < 0) {
74 		 krb5_data_free (rep);
75 		 return -1;
76 	     }
77 	     if(nbytes == 0)
78 		 return 0;
79 
80 	     if (limit)
81 		 nbytes = min(nbytes, limit - rep->length);
82 
83 	     tmp = realloc (rep->data, rep->length + nbytes);
84 	     if (tmp == NULL) {
85 		 krb5_data_free (rep);
86 		 return -1;
87 	     }
88 	     rep->data = tmp;
89 	     ret = recv (fd, (char*)tmp + rep->length, nbytes, 0);
90 	     if (ret < 0) {
91 		 krb5_data_free (rep);
92 		 return -1;
93 	     }
94 	     rep->length += ret;
95 	 }
96      } while(!udp && (limit == 0 || rep->length < limit));
97      return 0;
98 }
99 
100 /*
101  * Send kerberos requests and receive a reply on a udp or any other kind
102  * of a datagram socket.  See `recv_loop'.
103  */
104 
105 static int
106 send_and_recv_udp(int fd,
107 		  time_t tmout,
108 		  const krb5_data *req,
109 		  krb5_data *rep)
110 {
111     if (send (fd, req->data, req->length, 0) < 0)
112 	return -1;
113 
114     return recv_loop(fd, tmout, 1, 0, rep);
115 }
116 
117 /*
118  * `send_and_recv' for a TCP (or any other stream) socket.
119  * Since there are no record limits on a stream socket the protocol here
120  * is to prepend the request with 4 bytes of its length and the reply
121  * is similarly encoded.
122  */
123 
124 static int
125 send_and_recv_tcp(int fd,
126 		  time_t tmout,
127 		  const krb5_data *req,
128 		  krb5_data *rep)
129 {
130     unsigned char len[4];
131     unsigned long rep_len;
132     krb5_data len_data;
133 
134     _krb5_put_int(len, req->length, 4);
135     if(net_write(fd, len, sizeof(len)) < 0)
136 	return -1;
137     if(net_write(fd, req->data, req->length) < 0)
138 	return -1;
139     if (recv_loop (fd, tmout, 0, 4, &len_data) < 0)
140 	return -1;
141     if (len_data.length != 4) {
142 	krb5_data_free (&len_data);
143 	return -1;
144     }
145     _krb5_get_int(len_data.data, &rep_len, 4);
146     krb5_data_free (&len_data);
147     if (recv_loop (fd, tmout, 0, rep_len, rep) < 0)
148 	return -1;
149     if(rep->length != rep_len) {
150 	krb5_data_free (rep);
151 	return -1;
152     }
153     return 0;
154 }
155 
156 /*
157  * `send_and_recv' tailored for the HTTP protocol.
158  */
159 
160 static int
161 send_and_recv_http(int fd,
162 		   time_t tmout,
163 		   const char *prefix,
164 		   const krb5_data *req,
165 		   krb5_data *rep)
166 {
167     char *request;
168     char *str;
169     int ret;
170     int len = base64_encode(req->data, req->length, &str);
171 
172     if(len < 0)
173 	return -1;
174     asprintf(&request, "GET %s%s HTTP/1.0\r\n\r\n", prefix, str);
175     free(str);
176     if (request == NULL)
177 	return -1;
178     ret = net_write (fd, request, strlen(request));
179     free (request);
180     if (ret < 0)
181 	return ret;
182     ret = recv_loop(fd, tmout, 0, 0, rep);
183     if(ret)
184 	return ret;
185     {
186 	unsigned long rep_len;
187 	char *s, *p;
188 
189 	s = realloc(rep->data, rep->length + 1);
190 	if (s == NULL) {
191 	    krb5_data_free (rep);
192 	    return -1;
193 	}
194 	s[rep->length] = 0;
195 	p = strstr(s, "\r\n\r\n");
196 	if(p == NULL) {
197 	    free(s);
198 	    return -1;
199 	}
200 	p += 4;
201 	rep->data = s;
202 	rep->length -= p - s;
203 	if(rep->length < 4) { /* remove length */
204 	    free(s);
205 	    return -1;
206 	}
207 	rep->length -= 4;
208 	_krb5_get_int(p, &rep_len, 4);
209 	if (rep_len != rep->length) {
210 	    free(s);
211 	    return -1;
212 	}
213 	memmove(rep->data, p + 4, rep->length);
214     }
215     return 0;
216 }
217 
218 static int
219 init_port(const char *s, int fallback)
220 {
221     if (s) {
222 	int tmp;
223 
224 	sscanf (s, "%d", &tmp);
225 	return htons(tmp);
226     } else
227 	return fallback;
228 }
229 
230 /*
231  * Return 0 if succesful, otherwise 1
232  */
233 
234 static int
235 send_via_proxy (krb5_context context,
236 		const char *hostname,
237 		const krb5_data *send,
238 		krb5_data *receive)
239 {
240     char *proxy = strdup(context->http_proxy);
241     char *prefix;
242     char *colon;
243     struct addrinfo hints;
244     struct addrinfo *ai, *a;
245     int ret;
246     int s;
247     char portstr[NI_MAXSERV];
248 
249     colon = strchr(proxy, ':');
250     if(colon != NULL)
251 	*colon++ = '\0';
252     memset (&hints, 0, sizeof(hints));
253     hints.ai_family   = PF_UNSPEC;
254     hints.ai_socktype = SOCK_STREAM;
255     snprintf (portstr, sizeof(portstr), "%d",
256 	      ntohs(init_port (colon, htons(80))));
257     ret = getaddrinfo (proxy, portstr, NULL, &ai);
258     free (proxy);
259     if (ret)
260 	return ret;
261 
262     for (a = ai; a != NULL; a = a->ai_next) {
263 	s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
264 	if (s < 0)
265 	    continue;
266 	if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
267 	    close (s);
268 	    continue;
269 	}
270 	break;
271     }
272     if (a == NULL) {
273 	freeaddrinfo (ai);
274 	return 1;
275     }
276     freeaddrinfo (ai);
277 
278     asprintf(&prefix, "http://%s/", hostname);
279     if(prefix == NULL) {
280 	close(s);
281 	return 1;
282     }
283     ret = send_and_recv_http(s, context->kdc_timeout,
284 			     prefix, send, receive);
285     close (s);
286     free(prefix);
287     if(ret == 0 && receive->length != 0)
288 	return 0;
289     return 1;
290 }
291 
292 /*
293  * Send the data `send' to one KDC in `realm' and get back the reply
294  * in `receive'.
295  */
296 
297 krb5_error_code
298 krb5_sendto_kdc (krb5_context context,
299 		 const krb5_data *send,
300 		 const krb5_realm *realm,
301 		 krb5_data *receive)
302 {
303      krb5_error_code ret;
304      char **hostlist, **hp, *p;
305      int fd;
306      int port;
307      int i;
308 
309      port = krb5_getportbyname (context, "kerberos", "udp", 88);
310 
311      if (context->use_admin_kdc)
312 	 ret = krb5_get_krb_admin_hst (context, realm, &hostlist);
313      else
314 	 ret = krb5_get_krbhst (context, realm, &hostlist);
315      if (ret)
316 	  return ret;
317 
318      for (i = 0; i < context->max_retries; ++i)
319 	 for (hp = hostlist; (p = *hp); ++hp) {
320 	     char *colon;
321 	     int http_flag = 0;
322 	     int tcp_flag = 0;
323 	     struct addrinfo *ai, *a;
324 	     struct addrinfo hints;
325 	     char portstr[NI_MAXSERV];
326 
327 	     if(strncmp(p, "http://", 7) == 0){
328 		 p += 7;
329 		 http_flag = 1;
330 		 port = htons(80);
331 	     } else if(strncmp(p, "http/", 5) == 0) {
332 		 p += 5;
333 		 http_flag = 1;
334 		 port = htons(80);
335 	     }else if(strncmp(p, "tcp/", 4) == 0){
336 		 p += 4;
337 		 tcp_flag = 1;
338 	     } else if(strncmp(p, "udp/", 4) == 0) {
339 		 p += 4;
340 	     }
341 	     if(http_flag && context->http_proxy) {
342 		 if (send_via_proxy (context, p, send, receive))
343 		     continue;
344 		 else
345 		     goto out;
346 	     }
347 	     colon = strchr (p, ':');
348 	     if (colon)
349 		 *colon++ = '\0';
350 
351 	     memset (&hints, 0, sizeof(hints));
352 	     hints.ai_family = PF_UNSPEC;
353 	     if (tcp_flag || http_flag)
354 		 hints.ai_socktype = SOCK_STREAM;
355 	     else
356 		 hints.ai_socktype = SOCK_DGRAM;
357 	     snprintf (portstr, sizeof(portstr), "%d",
358 		       ntohs(init_port (colon, port)));
359 	     ret = getaddrinfo (p, portstr, &hints, &ai);
360 	     if (ret)
361 		 continue;
362 	     for (a = ai; a != NULL; a = a->ai_next) {
363 		 fd = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
364 		 if (fd < 0)
365 		     continue;
366 		 if (connect (fd, a->ai_addr, a->ai_addrlen) < 0) {
367 		     close (fd);
368 		     continue;
369 		 }
370 		 break;
371 	     }
372 	     if (a == NULL) {
373 		 freeaddrinfo (ai);
374 		 continue;
375 	     }
376 	     freeaddrinfo (ai);
377 
378 	     if(http_flag)
379 		 ret = send_and_recv_http(fd, context->kdc_timeout,
380 					  "", send, receive);
381 	     else if(tcp_flag)
382 		 ret = send_and_recv_tcp (fd, context->kdc_timeout,
383 					  send, receive);
384 	     else
385 		 ret = send_and_recv_udp (fd, context->kdc_timeout,
386 					  send, receive);
387 	     close (fd);
388 	     if(ret == 0 && receive->length != 0)
389 		 goto out;
390 	 }
391      ret = KRB5_KDC_UNREACH;
392 out:
393      krb5_free_krbhst (context, hostlist);
394      return ret;
395 }
396