Initial import from FreeBSD RELENG_4:
[games.git] / crypto / kerberosIV / lib / krb / send_to_kdc.c
1 /* 
2   Copyright (C) 1989 by the Massachusetts Institute of Technology
3
4    Export of this software from the United States of America is assumed
5    to require a specific license from the United States Government.
6    It is the responsibility of any person or organization contemplating
7    export to obtain such a license before exporting.
8
9 WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
10 distribute this software and its documentation for any purpose and
11 without fee is hereby granted, provided that the above copyright
12 notice appear in all copies and that both that copyright notice and
13 this permission notice appear in supporting documentation, and that
14 the name of M.I.T. not be used in advertising or publicity pertaining
15 to distribution of the software without specific, written prior
16 permission.  M.I.T. makes no representations about the suitability of
17 this software for any purpose.  It is provided "as is" without express
18 or implied warranty.
19
20   */
21
22 #include "krb_locl.h"
23 #include <base64.h>
24
25 RCSID("$Id: send_to_kdc.c,v 1.71.2.1 2000/10/10 12:47:21 assar Exp $");
26
27 struct host {
28     struct sockaddr_in addr;
29     const char *hostname;
30     enum krb_host_proto proto;
31 };
32
33 static int send_recv(KTEXT pkt, KTEXT rpkt, struct host *host);
34
35 /*
36  * send_to_kdc() sends a message to the Kerberos authentication
37  * server(s) in the given realm and returns the reply message.
38  * The "pkt" argument points to the message to be sent to Kerberos;
39  * the "rpkt" argument will be filled in with Kerberos' reply.
40  * The "realm" argument indicates the realm of the Kerberos server(s)
41  * to transact with.  If the realm is null, the local realm is used.
42  *
43  * If more than one Kerberos server is known for a given realm,
44  * different servers will be queried until one of them replies.
45  * Several attempts (retries) are made for each server before
46  * giving up entirely.
47  *
48  * If an answer was received from a Kerberos host, KSUCCESS is
49  * returned.  The following errors can be returned:
50  *
51  * SKDC_CANT    - can't get local realm
52  *              - can't find "kerberos" in /etc/services database
53  *              - can't open socket
54  *              - can't bind socket
55  *              - all ports in use
56  *              - couldn't find any Kerberos host
57  *
58  * SKDC_RETRY   - couldn't get an answer from any Kerberos server,
59  *                after several retries
60  */
61
62 /* always use the admin server */
63 static int krb_use_admin_server_flag = 0;
64
65 static int client_timeout = -1;
66
67 int
68 krb_use_admin_server(int flag)
69 {
70     int old = krb_use_admin_server_flag;
71     krb_use_admin_server_flag = flag;
72     return old;
73 }
74
75 #define PROXY_VAR "krb4_proxy"
76
77 static int
78 expand (struct host **ptr, size_t sz)
79 {
80     void *tmp;
81
82     tmp = realloc (*ptr, sz) ;
83     if (tmp == NULL)
84         return SKDC_CANT;
85     *ptr = tmp;
86     return 0;
87 }
88
89 int
90 send_to_kdc(KTEXT pkt, KTEXT rpkt, const char *realm)
91 {
92     int i;
93     int no_host; /* was a kerberos host found? */
94     int retry;
95     int n_hosts;
96     int retval;
97     struct hostent *host;
98     char lrealm[REALM_SZ];
99     struct krb_host *k_host;
100     struct host *hosts = malloc(sizeof(*hosts));
101     const char *proxy = krb_get_config_string (PROXY_VAR);
102
103     if (hosts == NULL)
104         return SKDC_CANT;
105
106     if (client_timeout == -1) {
107         const char *to;
108
109         client_timeout = CLIENT_KRB_TIMEOUT;
110         to = krb_get_config_string ("kdc_timeout");
111         if (to != NULL) {
112             int tmp;
113             char *end;
114
115             tmp = strtol (to, &end, 0);
116             if (end != to)
117                 client_timeout = tmp;
118         }
119     }
120
121     /*
122      * If "realm" is non-null, use that, otherwise get the
123      * local realm.
124      */
125     if (realm == NULL) {
126         if (krb_get_lrealm(lrealm,1)) {
127             if (krb_debug)
128                 krb_warning("send_to_kdc: can't get local realm\n");
129             return(SKDC_CANT);
130         }
131         realm = lrealm;
132     }
133     if (krb_debug)
134         krb_warning("lrealm is %s\n", realm);
135
136     no_host = 1;
137     /* get an initial allocation */
138     n_hosts = 0;
139     for (i = 1;
140          (k_host = krb_get_host(i, realm, krb_use_admin_server_flag)); 
141          ++i) {
142         char *p;
143         char **addr_list;
144         int j;
145         int n_addrs;
146         struct host *tmp;
147
148         if (k_host->proto == PROTO_HTTP && proxy != NULL) {
149             n_addrs = 1;
150             no_host = 0;
151
152             retval = expand (&hosts, (n_hosts + n_addrs) * sizeof(*hosts));
153             if (retval)
154                 goto rtn;
155
156             memset (&hosts[n_hosts].addr, 0, sizeof(struct sockaddr_in));
157             hosts[n_hosts].addr.sin_port = htons(k_host->port);
158             hosts[n_hosts].proto         = k_host->proto;
159             hosts[n_hosts].hostname      = k_host->host;
160         } else {
161             if (krb_debug)
162                 krb_warning("Getting host entry for %s...", k_host->host);
163             host = gethostbyname(k_host->host);
164             if (krb_debug) {
165                 krb_warning("%s.\n",
166                             host ? "Got it" : "Didn't get it");
167             }
168             if (host == NULL)
169                 continue;
170             no_host = 0;    /* found at least one */
171
172             n_addrs = 0;
173             for (addr_list = host->h_addr_list;
174                  *addr_list != NULL;
175                  ++addr_list)
176                 ++n_addrs;
177
178             retval = expand (&hosts, (n_hosts + n_addrs) * sizeof(*hosts));
179             if (retval)
180                 goto rtn;
181
182             for (addr_list = host->h_addr_list, j = 0;
183                  (p = *addr_list) != NULL;
184                  ++addr_list, ++j) {
185                 memset (&hosts[n_hosts + j].addr, 0,
186                         sizeof(struct sockaddr_in));
187                 hosts[n_hosts + j].addr.sin_family = host->h_addrtype;
188                 hosts[n_hosts + j].addr.sin_port   = htons(k_host->port);
189                 hosts[n_hosts + j].proto           = k_host->proto;
190                 hosts[n_hosts + j].hostname        = k_host->host;
191                 memcpy(&hosts[n_hosts + j].addr.sin_addr, p,
192                        sizeof(struct in_addr));
193             }
194         }
195
196         for (j = 0; j < n_addrs; ++j) {
197             if (send_recv(pkt, rpkt, &hosts[n_hosts + j])) {
198                 retval = KSUCCESS;
199                 goto rtn;
200             }
201             if (krb_debug) {
202                 krb_warning("Timeout, error, or wrong descriptor\n");
203             }
204         }
205         n_hosts += j;
206     }
207     if (no_host) {
208         if (krb_debug)
209             krb_warning("send_to_kdc: can't find any Kerberos host.\n");
210         retval = SKDC_CANT;
211         goto rtn;
212     }
213     /* retry each host in sequence */
214     for (retry = 0; retry < CLIENT_KRB_RETRY; ++retry) {
215         for (i = 0; i < n_hosts; ++i) {
216             if (send_recv(pkt, rpkt, &hosts[i])) {
217                 retval = KSUCCESS;
218                 goto rtn;
219             }
220         }
221     }
222     retval = SKDC_RETRY;
223 rtn:
224     free(hosts);
225     return(retval);
226 }
227
228 static int
229 udp_socket(void)
230 {
231     return socket(AF_INET, SOCK_DGRAM, 0);
232 }
233
234 static int
235 udp_connect(int s, struct host *host)
236 {
237     if(krb_debug) {
238         krb_warning("connecting to %s (%s) udp, port %d\n", 
239                     host->hostname,
240                     inet_ntoa(host->addr.sin_addr),
241                     ntohs(host->addr.sin_port));
242     }
243     return connect(s, (struct sockaddr*)&host->addr, sizeof(host->addr));
244 }
245
246 static int
247 udp_send(int s, struct host *host, KTEXT pkt)
248 {
249     if(krb_debug) {
250         krb_warning("sending %d bytes to %s (%s), udp port %d\n", 
251                     pkt->length,
252                     host->hostname,
253                     inet_ntoa(host->addr.sin_addr), 
254                     ntohs(host->addr.sin_port));
255     }
256     return send(s, pkt->dat, pkt->length, 0);
257 }
258
259 static int
260 tcp_socket(void)
261 {
262     return socket(AF_INET, SOCK_STREAM, 0);
263 }
264
265 static int
266 tcp_connect(int s, struct host *host)
267 {
268     if(krb_debug) {
269         krb_warning("connecting to %s (%s), tcp port %d\n", 
270                     host->hostname,
271                     inet_ntoa(host->addr.sin_addr), 
272                     ntohs(host->addr.sin_port));
273     }
274     return connect(s, (struct sockaddr*)&host->addr, sizeof(host->addr));
275 }
276
277 static int
278 tcp_send(int s, struct host *host, KTEXT pkt)
279 {
280     unsigned char len[4];
281
282     if(krb_debug) {
283         krb_warning("sending %d bytes to %s (%s), tcp port %d\n", 
284                     pkt->length,
285                     host->hostname,
286                     inet_ntoa(host->addr.sin_addr), 
287                     ntohs(host->addr.sin_port));
288     }
289     krb_put_int(pkt->length, len, sizeof(len), 4);
290     if(send(s, len, sizeof(len), 0) != sizeof(len))
291         return -1;
292     return send(s, pkt->dat, pkt->length, 0);
293 }
294
295 static int
296 udptcp_recv(void *buf, size_t len, KTEXT rpkt)
297 {
298     int pktlen = min(len, MAX_KTXT_LEN);
299
300     if(krb_debug)
301         krb_warning("recieved %lu bytes on udp/tcp socket\n", 
302                     (unsigned long)len);
303     memcpy(rpkt->dat, buf, pktlen);
304     rpkt->length = pktlen;
305     return 0;
306 }
307
308 static int
309 url_parse(const char *url, char *host, size_t len, short *port)
310 {
311     const char *p;
312     size_t n;
313
314     if(strncmp(url, "http://", 7))
315         return -1;
316     url += 7;
317     p = strchr(url, ':');
318     if(p) {
319         char *end;
320
321         *port = htons(strtol(p + 1, &end, 0));
322         if (end == p + 1)
323             return -1;
324         n = p - url;
325     } else {
326         *port = k_getportbyname ("http", "tcp", htons(80));
327         p = strchr(url, '/');
328         if (p)
329             n = p - url;
330         else
331             n = strlen(url);
332     }
333     if (n >= len)
334         return -1;
335     memcpy(host, url, n);
336     host[n] = '\0';
337     return 0;
338 }
339
340 static int
341 http_connect(int s, struct host *host)
342 {
343     const char *proxy = krb_get_config_string(PROXY_VAR);
344     char proxy_host[MaxHostNameLen];
345     short port;
346     struct hostent *hp;
347     struct sockaddr_in sin;
348
349     if(proxy == NULL) {
350         if(krb_debug)
351             krb_warning("Not using proxy.\n");
352         return tcp_connect(s, host);
353     }
354     if(url_parse(proxy, proxy_host, sizeof(proxy_host), &port) < 0)
355         return -1;
356     hp = gethostbyname(proxy_host);
357     if(hp == NULL)
358         return -1;
359     memset(&sin, 0, sizeof(sin));
360     sin.sin_family = AF_INET;
361     memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
362     sin.sin_port = port;
363     if(krb_debug) {
364         krb_warning("connecting to proxy on %s (%s) port %d\n", 
365                     proxy_host, inet_ntoa(sin.sin_addr), ntohs(port));
366     }
367     return connect(s, (struct sockaddr*)&sin, sizeof(sin));
368 }
369
370 static int
371 http_send(int s, struct host *host, KTEXT pkt)
372 {
373     const char *proxy = krb_get_config_string (PROXY_VAR);
374     char *str;
375     char *msg;
376
377     if(base64_encode(pkt->dat, pkt->length, &str) < 0)
378         return -1;
379     if(proxy != NULL) {
380         if(krb_debug) {
381             krb_warning("sending %d bytes to %s, tcp port %d (via proxy)\n", 
382                         pkt->length,
383                         host->hostname,
384                         ntohs(host->addr.sin_port));
385         }
386         asprintf(&msg, "GET http://%s:%d/%s HTTP/1.0\r\n\r\n",
387                  host->hostname,
388                  ntohs(host->addr.sin_port),
389                  str);
390     } else {
391         if(krb_debug) {
392             krb_warning("sending %d bytes to %s (%s), http port %d\n", 
393                         pkt->length,
394                         host->hostname,
395                         inet_ntoa(host->addr.sin_addr), 
396                         ntohs(host->addr.sin_port));
397         }
398         asprintf(&msg, "GET %s HTTP/1.0\r\n\r\n", str);
399     }
400     free(str);
401
402     if (msg == NULL)
403         return -1;
404         
405     if(send(s, msg, strlen(msg), 0) != strlen(msg)){
406         free(msg);
407         return -1;
408     }
409     free(msg);
410     return 0;
411 }
412
413 static int
414 http_recv(void *buf, size_t len, KTEXT rpkt)
415 {
416     char *p;
417     char *tmp = malloc(len + 1);
418
419     if (tmp == NULL)
420         return -1;
421     memcpy(tmp, buf, len);
422     tmp[len] = 0;
423     p = strstr(tmp, "\r\n\r\n");
424     if(p == NULL){
425         free(tmp);
426         return -1;
427     }
428     p += 4;
429     if(krb_debug)
430         krb_warning("recieved %lu bytes on http socket\n", 
431                     (unsigned long)((tmp + len) - p));
432     if((tmp + len) - p > MAX_KTXT_LEN) {
433         free(tmp);
434         return -1;
435     }
436     if (strncasecmp (tmp, "HTTP/1.0 2", 10) != 0
437         && strncasecmp (tmp, "HTTP/1.1 2", 10) != 0) {
438         free (tmp);
439         return -1;
440     }
441     memcpy(rpkt->dat, p, (tmp + len) - p);
442     rpkt->length = (tmp + len) - p;
443     free(tmp);
444     return 0;
445 }
446
447 static struct proto_descr {
448     int proto;
449     int stream_flag;
450     int (*socket)(void);
451     int (*connect)(int, struct host *host);
452     int (*send)(int, struct host *host, KTEXT);
453     int (*recv)(void*, size_t, KTEXT);
454 } protos[] = {
455     { PROTO_UDP, 0, udp_socket, udp_connect, udp_send, udptcp_recv },
456     { PROTO_TCP, 1, tcp_socket, tcp_connect, tcp_send, udptcp_recv },
457     { PROTO_HTTP, 1, tcp_socket, http_connect, http_send, http_recv }
458 };
459
460 static int
461 send_recv(KTEXT pkt, KTEXT rpkt, struct host *host)
462 {
463     int i;
464     int s;
465     unsigned char buf[MAX_KTXT_LEN];
466     int offset = 0;
467     
468     for(i = 0; i < sizeof(protos) / sizeof(protos[0]); i++){
469         if(protos[i].proto == host->proto)
470             break;
471     }
472     if(i == sizeof(protos) / sizeof(protos[0]))
473         return FALSE;
474     if((s = (*protos[i].socket)()) < 0)
475         return FALSE;
476     if((*protos[i].connect)(s, host) < 0) {
477         close(s);
478         return FALSE;
479     }
480     if((*protos[i].send)(s, host, pkt) < 0) {
481         close(s);
482         return FALSE;
483     }
484     do{
485         fd_set readfds;
486         struct timeval timeout;
487         int len;
488         timeout.tv_sec = client_timeout;
489         timeout.tv_usec = 0;
490         FD_ZERO(&readfds);
491         if (s >= FD_SETSIZE) {
492             if (krb_debug)
493                 krb_warning("fd too large\n");
494             close (s);
495             return FALSE;
496         }
497         FD_SET(s, &readfds);
498         
499         /* select - either recv is ready, or timeout */
500         /* see if timeout or error or wrong descriptor */
501         if(select(s + 1, &readfds, 0, 0, &timeout) < 1 
502            || !FD_ISSET(s, &readfds)) {
503             if (krb_debug)
504                 krb_warning("select failed: errno = %d\n", errno);
505             close(s);
506             return FALSE;
507         }
508         len = recv(s, buf + offset, sizeof(buf) - offset, 0);
509         if (len < 0) {
510             close(s);
511             return FALSE;
512         }
513         if(len == 0)
514             break;
515         offset += len;
516     } while(protos[i].stream_flag);
517     close(s);
518     if((*protos[i].recv)(buf, offset, rpkt) < 0)
519         return FALSE;
520     return TRUE;
521 }
522
523 /* The configuration line "hosts: dns files" in /etc/nsswitch.conf is
524  * rumored to avoid triggering this bug. */
525 #if defined(linux) && defined(HAVE__DNS_GETHOSTBYNAME) && 0
526 /* Linux libc 5.3 is broken probably somewhere in nsw_hosts.o,
527  * for now keep this kludge. */
528 static
529 struct hostent *gethostbyname(const char *name)
530 {
531   return (void *)_dns_gethostbyname(name);
532 }
533 #endif