DNS lookup implementation, conditionally compiled under KRB5_DNS_LOOKUP (which
[krb5.git] / src / lib / krb5 / os / hst_realm.c
1 /*
2  * lib/krb5/os/hst_realm.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  * krb5_get_host_realm()
25  */
26
27
28 /*
29  Figures out the Kerberos realm names for host, filling in a
30  pointer to an argv[] style list of names, terminated with a null pointer.
31  
32  If host is NULL, the local host's realms are determined.
33
34  If there are no known realms for the host, the filled-in pointer is set
35  to NULL.
36
37  The pointer array and strings pointed to are all in allocated storage,
38  and should be freed by the caller when finished.
39
40  returns system errors
41 */
42
43 /*
44  * Implementation notes:
45  *
46  * this implementation only provides one realm per host, using the same
47  * mapping file used in kerberos v4.
48
49  * Given a fully-qualified domain-style primary host name,
50  * return the name of the Kerberos realm for the host.
51  * If the hostname contains no discernable domain, or an error occurs,
52  * return the local realm name, as supplied by krb5_get_default_realm().
53  * If the hostname contains a domain, but no translation is found,
54  * the hostname's domain is converted to upper-case and returned.
55  *
56  * The format of each line of the translation file is:
57  * domain_name kerberos_realm
58  * -or-
59  * host_name kerberos_realm
60  *
61  * domain_name should be of the form .XXX.YYY (e.g. .LCS.MIT.EDU)
62  * host names should be in the usual form (e.g. FOO.BAR.BAZ)
63  */
64
65 #define NEED_SOCKETS
66 #include "k5-int.h"
67 #include <ctype.h>
68 #include <stdio.h>
69 #ifdef HAVE_STRING_H
70 #include <string.h>
71 #else
72 #include <strings.h>
73 #endif
74
75 #ifdef KRB5_DNS_LOOKUP       
76 #ifdef WSHELPER
77 #include <wshelper.h>
78 #else /* WSHELPER */
79 #include <arpa/inet.h>       
80 #include <arpa/nameser.h>    
81 #include <resolv.h>          
82 #include <netdb.h>
83 #endif /* WSHELPER */
84 #endif /* KRB5_DNS_LOOKUP */ 
85
86 /* for old Unixes and friends ... */
87 #ifndef MAXHOSTNAMELEN
88 #define MAXHOSTNAMELEN 64
89 #endif
90
91 #define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
92
93 #ifdef KRB5_DNS_LOOKUP
94 /*
95  * Try to look up a TXT record pointing to a Kerberos realm
96  */
97
98 krb5_error_code
99 krb5_try_realm_txt_rr(prefix, name, realm)
100     const char *prefix, *name;
101     char **realm;
102 {
103     union {
104         unsigned char bytes[2048];
105         HEADER hdr;
106     } answer;
107     unsigned char *p;
108     char host[MAX_DNS_NAMELEN], *h;
109     int size;
110     int type, class, numanswers, numqueries, rdlen, len;
111
112     /*
113      * Form our query, and send it via DNS
114      */
115
116     if (name == NULL || name[0] == '\0') {
117         strcpy(host,prefix);
118     } else {
119         if ( strlen(prefix) + strlen(name) + 3 > MAX_DNS_NAMELEN )
120             return KRB5_ERR_HOST_REALM_UNKNOWN;
121         sprintf(host,"%s.%s", prefix, name);
122     }
123     /* Realm names don't (normally) end with ".", but if the query
124        doesn't end with "." and doesn't get an answer as is, the
125        resolv code will try appending the local domain.  Since the
126        realm names are absolutes, let's stop that.  */
127     h = host + strlen (host);
128     if (h > host && h[-1] != '.')
129         strcpy (h, ".");
130
131     size = res_search(host, C_IN, T_TXT, answer.bytes, sizeof(answer.bytes));
132
133     if (size < 0)
134         return KRB5_ERR_HOST_REALM_UNKNOWN;
135
136     p = answer.bytes;
137
138     numqueries = ntohs(answer.hdr.qdcount);
139     numanswers = ntohs(answer.hdr.ancount);
140
141     p += sizeof(HEADER);
142
143     /*
144      * We need to skip over the questions before we can get to the answers,
145      * which means we have to iterate over every query record.  We use
146      * dn_expand to tell us how long each compressed name is.
147      */
148
149 #define INCR_CHECK(x, y) x += y; if (x > size + answer.bytes) \
150                          return KRB5_ERR_HOST_REALM_UNKNOWN
151 #define CHECK(x, y) if (x + y > size + answer.bytes) \
152                          return KRB5_ERR_HOST_REALM_UNKNOWN
153 #define NTOHSP(x, y) x[0] << 8 | x[1]; x += y
154
155     while (numqueries--) {
156         len = dn_expand(answer.bytes, answer.bytes + size, p, host, 
157                          sizeof(host));
158         if (len < 0)
159             return KRB5_ERR_HOST_REALM_UNKNOWN;
160         INCR_CHECK(p, len + 4);         /* Name plus type plus class */
161     }
162
163     /*
164      * We're now pointing at the answer records.  Process the first
165      * TXT record we find.
166      */
167
168     while (numanswers--) {
169         
170         /* First the name; use dn_expand to get the compressed size */
171         len = dn_expand(answer.bytes, answer.bytes + size, p,
172                         host, sizeof(host));
173         if (len < 0)
174             return KRB5_ERR_HOST_REALM_UNKNOWN;
175         INCR_CHECK(p, len);
176
177         /* Next is the query type */
178         CHECK(p, 2);
179         type = NTOHSP(p,2);
180
181         /* Next is the query class; also skip over 4 byte TTL */
182         CHECK(p,6);
183         class = NTOHSP(p,6);
184
185         /* Record data length - make sure we aren't truncated */
186
187         CHECK(p,2);
188         rdlen = NTOHSP(p,2);
189
190         if (p + rdlen > answer.bytes + size)
191             return KRB5_ERR_HOST_REALM_UNKNOWN;
192
193         /*
194          * If this is a TXT record, return the string.  Note that the
195          * string has a 1-byte length in the front
196          */
197         /* XXX What about flagging multiple TXT records as an error?  */
198
199         if (class == C_IN && type == T_TXT) {
200             len = *p++;
201             if (p + len > answer.bytes + size)
202                 return KRB5_ERR_HOST_REALM_UNKNOWN;
203             *realm = malloc(len + 1);
204             if (*realm == NULL)
205                 return ENOMEM;
206             strncpy(*realm, (char *) p, len);
207             (*realm)[len] = '\0';
208             /* Avoid a common error. */
209             if ( (*realm)[len-1] == '.' )
210                 (*realm)[len-1] = '\0';
211             return 0;
212         }
213     }
214
215     return KRB5_ERR_HOST_REALM_UNKNOWN;
216 }
217 #endif /* KRB5_DNS_LOOKUP */
218
219
220 KRB5_DLLIMP krb5_error_code KRB5_CALLCONV
221 krb5_get_host_realm(context, host, realmsp)
222     krb5_context context;
223     const char FAR *host;
224     char FAR * FAR * FAR *realmsp;
225 {
226     char **retrealms;
227     char *default_realm, *realm, *cp;
228     krb5_error_code retval;
229     int l;
230     char local_host[MAX_DNS_NAMELEN+1];
231
232     if (host)
233         strncpy(local_host, host, MAX_DNS_NAMELEN);
234     else {
235         if (gethostname(local_host, sizeof(local_host)-1) == -1)
236             return SOCKET_ERRNO;
237     }
238     local_host[MAX_DNS_NAMELEN] = '\0';
239     for (cp = local_host; *cp; cp++) {
240         if (isupper(*cp))
241             *cp = tolower(*cp);
242     }
243     l = strlen(local_host);
244     /* strip off trailing dot */
245     if (l && local_host[l-1] == '.')
246             local_host[l-1] = 0;
247
248     /*
249        Search for the best match for the host or domain.
250        Example: Given a host a.b.c.d, try to match on:
251          1) A.B.C.D
252          2) .B.C.D
253          3) B.C.D
254          4) .C.D
255          5) C.D
256          6) .D
257          7) D
258      */
259
260     cp = local_host;
261     realm = default_realm = (char *)NULL;
262     while (cp) {
263         retval = profile_get_string(context->profile, "domain_realm", cp,
264                                     0, (char *)NULL, &realm);
265         if (retval)
266             return retval;
267         if (realm != (char *)NULL)
268             break;      /* Match found */
269
270         /* Setup for another test */
271         if (*cp == '.') {
272             cp++;
273             if (default_realm == (char *)NULL) {
274                 /* If nothing else works, use the host's domain */
275                 default_realm = cp;
276             }
277         } else {
278             cp = strchr(cp, '.');
279         }
280     }
281
282 #ifdef KRB5_DNS_LOOKUP
283     if (realm == (char *)NULL) {
284         /*
285         * Since this didn't appear in our config file, try looking
286         * it up via DNS.  Look for a TXT records of the form:
287         *
288         * _kerberos.<hostname>
289         * _kerberos.<searchlist>
290         * _kerberos.<defaultrealm>
291         *
292         */
293         cp = local_host;
294         do {
295             retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm);
296             cp = strchr(cp,'.');
297             if (cp) 
298                 cp++;
299         } while (retval && cp && cp[0]);
300         if (retval)
301             retval = krb5_try_realm_txt_rr("_kerberos", "", &realm);
302         if (retval && default_realm) {
303             cp = default_realm;
304             do {
305                 retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm);
306                 cp = strchr(cp,'.');
307                 if (cp) 
308                     cp++;
309             } while (retval && cp && cp[0]);
310         }
311     }
312 #endif /* KRB5_DNS_LOOKUP */
313     if (realm == (char *)NULL) {
314         if (default_realm != (char *)NULL) {
315             /* We are defaulting to the realm of the host */
316             if (!(cp = (char *)malloc(strlen(default_realm)+1)))
317                 return ENOMEM;
318             strcpy(cp, default_realm);
319             realm = cp;
320
321             /* Assume the realm name is upper case */
322             for (cp = realm; *cp; cp++)
323                 if (islower(*cp))
324                     *cp = toupper(*cp);
325         } else {    
326             /* We are defaulting to the local realm */
327             retval = krb5_get_default_realm(context, &realm);
328             if (retval) {
329                 return retval;
330             }
331         }
332     }
333     if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) {
334         if (realm != (char *)NULL)
335             free(realm);
336         return ENOMEM;
337     }
338
339     retrealms[0] = realm;
340     retrealms[1] = 0;
341     
342     *realmsp = retrealms;
343     return 0;
344 }