xref: /freebsd/crypto/krb5/src/lib/krb5/os/dnssrv.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/os/dnssrv.c - Perform DNS SRV queries */
3 /*
4  * Copyright 1990,2000,2001,2002,2003 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.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26 
27 #include "autoconf.h"
28 #ifdef KRB5_DNS_LOOKUP
29 #include "k5-int.h"
30 #include "os-proto.h"
31 
32 /*
33  * Lookup a KDC via DNS SRV records
34  */
35 
36 void
krb5int_free_srv_dns_data(struct srv_dns_entry * p)37 krb5int_free_srv_dns_data (struct srv_dns_entry *p)
38 {
39     struct srv_dns_entry *next;
40     while (p) {
41         next = p->next;
42         free(p->host);
43         free(p);
44         p = next;
45     }
46 }
47 
48 /* Construct a DNS label of the form "service.[protocol.]realm.".  protocol may
49  * and/or sitename be NULL. */
50 static char *
make_lookup_name(const krb5_data * realm,const char * service,const char * protocol,const char * sitename)51 make_lookup_name(const krb5_data *realm, const char *service,
52                  const char *protocol, const char *sitename)
53 {
54     struct k5buf buf;
55 
56     if (memchr(realm->data, 0, realm->length))
57         return NULL;
58 
59     k5_buf_init_dynamic(&buf);
60     k5_buf_add_fmt(&buf, "%s.", service);
61     if (protocol != NULL)
62         k5_buf_add_fmt(&buf, "%s.", protocol);
63     if (sitename != NULL)
64         k5_buf_add_fmt(&buf, "%s._sites.", sitename);
65     k5_buf_add_len(&buf, realm->data, realm->length);
66 
67     /*
68      * Realm names don't (normally) end with ".", but if the query doesn't end
69      * with "." and doesn't get an answer as is, the resolv code will try
70      * appending the local domain.  Since the realm names are absolutes, let's
71      * stop that.
72      */
73 
74     if (buf.len > 0 && ((char *)buf.data)[buf.len - 1] != '.')
75         k5_buf_add(&buf, ".");
76 
77     return k5_buf_cstring(&buf);
78 }
79 
80 /* Insert new into the list *head, ordering by priority.  Weight is not
81  * currently used. */
82 static void
place_srv_entry(struct srv_dns_entry ** head,struct srv_dns_entry * new)83 place_srv_entry(struct srv_dns_entry **head, struct srv_dns_entry *new)
84 {
85     struct srv_dns_entry *entry;
86 
87     if (*head == NULL || (*head)->priority > new->priority) {
88         new->next = *head;
89         *head = new;
90         return;
91     }
92 
93     for (entry = *head; entry != NULL; entry = entry->next) {
94         /*
95          * Insert an entry into the next spot if there is no next entry (we're
96          * at the end), or if the next entry has a higher priority (lower
97          * priorities are preferred).
98          */
99         if (entry->next == NULL || entry->next->priority > new->priority) {
100             new->next = entry->next;
101             entry->next = new;
102             break;
103         }
104     }
105 }
106 
107 #ifdef _WIN32
108 
109 #include <windns.h>
110 
111 krb5_error_code
k5_make_uri_query(krb5_context context,const krb5_data * realm,const char * service,const char * sitename,struct srv_dns_entry ** answers)112 k5_make_uri_query(krb5_context context, const krb5_data *realm,
113                   const char *service, const char *sitename,
114                   struct srv_dns_entry **answers)
115 {
116     /* Windows does not currently support the URI record type or make it
117      * possible to query for a record type it does not have support for. */
118     *answers = NULL;
119     return 0;
120 }
121 
122 krb5_error_code
krb5int_make_srv_query_realm(krb5_context context,const krb5_data * realm,const char * service,const char * protocol,const char * sitename,struct srv_dns_entry ** answers)123 krb5int_make_srv_query_realm(krb5_context context, const krb5_data *realm,
124                              const char *service, const char *protocol,
125                              const char *sitename,
126                              struct srv_dns_entry **answers)
127 {
128     char *name = NULL;
129     DNS_STATUS st;
130     PDNS_RECORD records, rr;
131     struct srv_dns_entry *head = NULL, *srv = NULL;
132 
133     *answers = NULL;
134 
135     name = make_lookup_name(realm, service, protocol, sitename);
136     if (name == NULL)
137         return 0;
138 
139     TRACE_DNS_SRV_SEND(context, name);
140 
141     st = DnsQuery_UTF8(name, DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &records,
142                        NULL);
143     if (st != ERROR_SUCCESS && sitename != NULL) {
144         /* Try again without the site name. */
145         free(name);
146         return krb5int_make_srv_query_realm(context, realm, service, protocol,
147                                             NULL, answers);
148     }
149     if (st != ERROR_SUCCESS)
150         return 0;
151 
152     for (rr = records; rr != NULL; rr = rr->pNext) {
153         if (rr->wType != DNS_TYPE_SRV)
154             continue;
155 
156         srv = malloc(sizeof(struct srv_dns_entry));
157         if (srv == NULL)
158             goto cleanup;
159 
160         srv->priority = rr->Data.SRV.wPriority;
161         srv->weight = rr->Data.SRV.wWeight;
162         srv->port = rr->Data.SRV.wPort;
163         /* Make sure the name looks fully qualified to the resolver. */
164         if (asprintf(&srv->host, "%s.", rr->Data.SRV.pNameTarget) < 0) {
165             free(srv);
166             goto cleanup;
167         }
168 
169         TRACE_DNS_SRV_ANS(context, srv->host, srv->port, srv->priority,
170                           srv->weight);
171         place_srv_entry(&head, srv);
172     }
173 
174 cleanup:
175     free(name);
176     if (records != NULL)
177         DnsRecordListFree(records, DnsFreeRecordList);
178     *answers = head;
179     return 0;
180 }
181 
182 #else /* _WIN32 */
183 
184 #include "dnsglue.h"
185 
186 /* Query the URI RR, collecting weight, priority, and target. */
187 krb5_error_code
k5_make_uri_query(krb5_context context,const krb5_data * realm,const char * service,const char * sitename,struct srv_dns_entry ** answers)188 k5_make_uri_query(krb5_context context, const krb5_data *realm,
189                   const char *service, const char *sitename,
190                   struct srv_dns_entry **answers)
191 {
192     const unsigned char *p = NULL, *base = NULL;
193     char *name = NULL;
194     int size, ret, rdlen;
195     unsigned short priority, weight;
196     struct krb5int_dns_state *ds = NULL;
197     struct srv_dns_entry *head = NULL, *uri = NULL;
198 
199     *answers = NULL;
200 
201     /* Construct service.realm. */
202     name = make_lookup_name(realm, service, NULL, sitename);
203     if (name == NULL)
204         return 0;
205 
206     TRACE_DNS_URI_SEND(context, name);
207 
208     size = krb5int_dns_init(&ds, name, C_IN, T_URI);
209     if (size < 0 && sitename != NULL) {
210         /* Try again without the site name. */
211         free(name);
212         return k5_make_uri_query(context, realm, service, NULL, answers);
213     }
214     if (size < 0)
215         goto out;
216 
217     for (;;) {
218         ret = krb5int_dns_nextans(ds, &base, &rdlen);
219         if (ret < 0 || base == NULL)
220             goto out;
221 
222         p = base;
223 
224         SAFE_GETUINT16(base, rdlen, p, 2, priority, out);
225         SAFE_GETUINT16(base, rdlen, p, 2, weight, out);
226 
227         uri = k5alloc(sizeof(*uri), &ret);
228         if (uri == NULL)
229             goto out;
230 
231         uri->priority = priority;
232         uri->weight = weight;
233         /* rdlen - 4 bytes remain after the priority and weight. */
234         uri->host = k5memdup0(p, rdlen - 4, &ret);
235         if (uri->host == NULL) {
236             free(uri);
237             goto out;
238         }
239 
240         TRACE_DNS_URI_ANS(context, uri->host, uri->priority, uri->weight);
241         place_srv_entry(&head, uri);
242     }
243 
244 out:
245     krb5int_dns_fini(ds);
246     free(name);
247     *answers = head;
248     return 0;
249 }
250 
251 /*
252  * Do DNS SRV query, return results in *answers.
253  *
254  * Make a best effort to return all the data we can.  On memory or decoding
255  * errors, just return what we've got.  Always return 0, currently.
256  */
257 
258 krb5_error_code
krb5int_make_srv_query_realm(krb5_context context,const krb5_data * realm,const char * service,const char * protocol,const char * sitename,struct srv_dns_entry ** answers)259 krb5int_make_srv_query_realm(krb5_context context, const krb5_data *realm,
260                              const char *service, const char *protocol,
261                              const char *sitename,
262                              struct srv_dns_entry **answers)
263 {
264     const unsigned char *p = NULL, *base = NULL;
265     char *name = NULL, host[MAXDNAME];
266     int size, ret, rdlen, nlen;
267     unsigned short priority, weight, port;
268     struct krb5int_dns_state *ds = NULL;
269     struct srv_dns_entry *head = NULL, *srv = NULL;
270 
271     /*
272      * First off, build a query of the form:
273      *
274      * service.protocol.realm
275      *
276      * which will most likely be something like:
277      *
278      * _kerberos._udp.REALM
279      *
280      */
281 
282     name = make_lookup_name(realm, service, protocol, sitename);
283     if (name == NULL)
284         return 0;
285 
286     TRACE_DNS_SRV_SEND(context, name);
287 
288     size = krb5int_dns_init(&ds, name, C_IN, T_SRV);
289     if (size < 0 && sitename) {
290         /* Try again without the site name. */
291         free(name);
292         return krb5int_make_srv_query_realm(context, realm, service, protocol,
293                                             NULL, answers);
294     }
295     if (size < 0)
296         goto out;
297 
298     for (;;) {
299         ret = krb5int_dns_nextans(ds, &base, &rdlen);
300         if (ret < 0 || base == NULL)
301             goto out;
302 
303         p = base;
304 
305         SAFE_GETUINT16(base, rdlen, p, 2, priority, out);
306         SAFE_GETUINT16(base, rdlen, p, 2, weight, out);
307         SAFE_GETUINT16(base, rdlen, p, 2, port, out);
308 
309         /*
310          * RFC 2782 says the target is never compressed in the reply;
311          * do we believe that?  We need to flatten it anyway, though.
312          */
313         nlen = krb5int_dns_expand(ds, p, host, sizeof(host));
314         if (nlen < 0 || !INCR_OK(base, rdlen, p, nlen))
315             goto out;
316 
317         /*
318          * We got everything!  Insert it into our list, but make sure
319          * it's in the right order.  Right now we don't do anything
320          * with the weight field
321          */
322 
323         srv = malloc(sizeof(struct srv_dns_entry));
324         if (srv == NULL)
325             goto out;
326 
327         srv->priority = priority;
328         srv->weight = weight;
329         srv->port = port;
330         /* The returned names are fully qualified.  Don't let the
331          * local resolver code do domain search path stuff. */
332         if (asprintf(&srv->host, "%s.", host) < 0) {
333             free(srv);
334             goto out;
335         }
336 
337         TRACE_DNS_SRV_ANS(context, srv->host, srv->port, srv->priority,
338                           srv->weight);
339         place_srv_entry(&head, srv);
340     }
341 
342 out:
343     krb5int_dns_fini(ds);
344     free(name);
345     *answers = head;
346     return 0;
347 }
348 
349 #endif /* not _WIN32 */
350 #endif /* KRB5_DNS_LOOKUP */
351