f4fffee9ba8fa68e27ceabf12b3a56f40fc26534
[krb5.git] / src / lib / krb5 / os / sendto_kdc.c
1 /*
2  * lib/krb5/os/sendto_kdc.c
3  *
4  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  * 
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  M.I.T. makes no representations about the suitability of
20  * this software for any purpose.  It is provided "as is" without express
21  * or implied warranty.
22  * 
23  *
24  * Send packet to KDC for realm; wait for response, retransmitting
25  * as necessary.
26  */
27
28 #define NEED_SOCKETS
29 #define NEED_LOWLEVEL_IO
30 #include "k5-int.h"
31
32 #ifdef HAVE_SYS_TIME_H
33 #include <sys/time.h>
34 #else
35 #include <time.h>
36 #endif
37 #include "os-proto.h"
38
39 #ifdef _AIX
40 #include <sys/select.h>
41 #endif
42
43 /*
44  * send the formatted request 'message' to a KDC for realm 'realm' and
45  * return the response (if any) in 'reply'.
46  *
47  * If the message is sent and a response is received, 0 is returned,
48  * otherwise an error code is returned.
49  *
50  * The storage for 'reply' is allocated and should be freed by the caller
51  * when finished.
52  */
53
54 extern int krb5_max_dgram_size;
55 extern int krb5_max_skdc_timeout;
56 extern int krb5_skdc_timeout_shift;
57 extern int krb5_skdc_timeout_1;
58
59 krb5_error_code INTERFACE
60 krb5_sendto_kdc (context, message, realm, reply)
61     krb5_context context;
62     const krb5_data * message;
63     const krb5_data * realm;
64     krb5_data * reply;
65 {
66     register int timeout, host, i;
67     struct sockaddr *addr;
68     int naddr;
69     int sent, nready;
70     krb5_error_code retval;
71     SOCKET *socklist;
72     fd_set readable;
73     struct timeval waitlen;
74     int cc;
75
76     /*
77      * find KDC location(s) for realm
78      */
79
80     if (retval = krb5_locate_kdc (context, realm, &addr, &naddr))
81         return retval;
82     if (naddr == 0)
83         return KRB5_REALM_UNKNOWN;
84
85     socklist = (SOCKET *)malloc(naddr * sizeof(SOCKET));
86     if (socklist == NULL) {
87         krb5_xfree(addr);
88         return ENOMEM;
89     }
90     for (i = 0; i < naddr; i++)
91         socklist[i] = INVALID_SOCKET;
92
93     if (!(reply->data = malloc(krb5_max_dgram_size))) {
94         krb5_xfree(addr);
95         krb5_xfree(socklist);
96         return ENOMEM;
97     }
98     reply->length = krb5_max_dgram_size;
99
100     if (SOCKET_INITIALIZE()) {  /* PC needs this for some tcp/ip stacks */
101         krb5_xfree(addr);
102         krb5_xfree(socklist);
103         free(reply->data);
104         return SOCKET_ERRNO;
105     }
106
107     /*
108      * do exponential backoff.
109      */
110
111     for (timeout = krb5_skdc_timeout_1; timeout < krb5_max_skdc_timeout;
112          timeout <<= krb5_skdc_timeout_shift) {
113         sent = 0;
114         for (host = 0; host < naddr; host++) {
115             /* send to the host, wait timeout seconds for a response,
116                then move on. */
117             /* cache some sockets for each host */
118             if (socklist[host] == INVALID_SOCKET) {
119                 /* XXX 4.2/4.3BSD has PF_xxx = AF_xxx, so the socket
120                    creation here will work properly... */
121                 /*
122                  * From socket(2):
123                  *
124                  * The protocol specifies a particular protocol to be
125                  * used with the socket.  Normally only a single
126                  * protocol exists to support a particular socket type
127                  * within a given protocol family.
128                  */
129                 socklist[host] = socket(addr[host].sa_family, SOCK_DGRAM, 0);
130                 if (socklist[host] == INVALID_SOCKET)
131                     continue;           /* try other hosts */
132                 /* have a socket to send/recv from */
133                 /* On BSD systems, a connected UDP socket will get connection
134                    refused and net unreachable errors while an unconnected
135                    socket will time out, so use connect, send, recv instead of
136                    sendto, recvfrom.  The connect here may return an error if
137                    the destination host is known to be unreachable. */
138                 if (connect(socklist[host],
139                             &addr[host], sizeof(addr[host])) == SOCKET_ERROR)
140                   continue;
141             }
142             if (send(socklist[host],
143                        message->data, message->length, 0) != message->length)
144               continue;
145         retry:
146             waitlen.tv_usec = 0;
147             waitlen.tv_sec = timeout;
148             FD_ZERO(&readable);
149             FD_SET(socklist[host], &readable);
150             if (nready = select(SOCKET_NFDS(socklist[host]),
151                                 &readable,
152                                 0,
153                                 0,
154                                 &waitlen)) {
155                 if (nready == SOCKET_ERROR) {
156                     if (SOCKET_ERRNO == SOCKET_EINTR)
157                         goto retry;
158                     retval = SOCKET_ERRNO;
159                     goto out;
160                 }
161                 if ((cc = recv(socklist[host],
162                                reply->data, reply->length, 0)) == SOCKET_ERROR)
163                   {
164                     /* man page says error could be:
165                        EBADF: won't happen
166                        ENOTSOCK: it's a socket.
167                        EWOULDBLOCK: not marked non-blocking, and we selected.
168                        EINTR: could happen
169                        EFAULT: we allocated the reply packet.
170
171                        In addition, net related errors like ECONNREFUSED
172                        are possble (but undocumented).  Assume anything
173                        other than EINTR is a permanent error for the
174                        server (i.e. don't set sent = 1).
175                        */
176
177                     if (SOCKET_ERRNO == SOCKET_EINTR)
178                       sent = 1;
179                     continue;
180                   }
181
182                 /* We might consider here verifying that the reply
183                    came from one of the KDC's listed for that address type,
184                    but that check can be fouled by some implementations of
185                    some network types which might show a loopback return
186                    address, for example, if the KDC is on the same host
187                    as the client. */
188
189                 reply->length = cc;
190                 retval = 0;
191                 goto out;
192             } else if (nready == 0) {
193                 /* timeout */
194                 sent = 1;
195             }
196             /* not ready, go on to next server */
197         }
198         if (!sent) {
199             /* never were able to send to any servers; give up */
200             retval = KRB5_KDC_UNREACH;
201             break;
202         }
203     }
204     retval = KRB5_KDC_UNREACH;
205  out:
206     SOCKET_CLEANUP();                           /* Done with sockets for now */
207     for (i = 0; i < naddr; i++)
208         if (socklist[i] != INVALID_SOCKET)
209             (void) closesocket (socklist[i]);
210     krb5_xfree(addr);
211     krb5_xfree(socklist);
212     if (retval) {
213         free(reply->data);
214         reply->data = 0;
215         reply->length = 0;
216     }
217     return retval;
218 }