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  * be NULL. */
50 static char *
make_lookup_name(const krb5_data * realm,const char * service,const char * protocol)51 make_lookup_name(const krb5_data *realm, const char *service,
52                  const char *protocol)
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     k5_buf_add_len(&buf, realm->data, realm->length);
64 
65     /*
66      * Realm names don't (normally) end with ".", but if the query doesn't end
67      * with "." and doesn't get an answer as is, the resolv code will try
68      * appending the local domain.  Since the realm names are absolutes, let's
69      * stop that.
70      */
71 
72     if (buf.len > 0 && ((char *)buf.data)[buf.len - 1] != '.')
73         k5_buf_add(&buf, ".");
74 
75     return buf.data;
76 }
77 
78 /* Insert new into the list *head, ordering by priority.  Weight is not
79  * currently used. */
80 static void
place_srv_entry(struct srv_dns_entry ** head,struct srv_dns_entry * new)81 place_srv_entry(struct srv_dns_entry **head, struct srv_dns_entry *new)
82 {
83     struct srv_dns_entry *entry;
84 
85     if (*head == NULL || (*head)->priority > new->priority) {
86         new->next = *head;
87         *head = new;
88         return;
89     }
90 
91     for (entry = *head; entry != NULL; entry = entry->next) {
92         /*
93          * Insert an entry into the next spot if there is no next entry (we're
94          * at the end), or if the next entry has a higher priority (lower
95          * priorities are preferred).
96          */
97         if (entry->next == NULL || entry->next->priority > new->priority) {
98             new->next = entry->next;
99             entry->next = new;
100             break;
101         }
102     }
103 }
104 
105 #ifdef _WIN32
106 
107 #include <windns.h>
108 
109 krb5_error_code
k5_make_uri_query(krb5_context context,const krb5_data * realm,const char * service,struct srv_dns_entry ** answers)110 k5_make_uri_query(krb5_context context, const krb5_data *realm,
111                   const char *service, struct srv_dns_entry **answers)
112 {
113     /* Windows does not currently support the URI record type or make it
114      * possible to query for a record type it does not have support for. */
115     *answers = NULL;
116     return 0;
117 }
118 
119 krb5_error_code
krb5int_make_srv_query_realm(krb5_context context,const krb5_data * realm,const char * service,const char * protocol,struct srv_dns_entry ** answers)120 krb5int_make_srv_query_realm(krb5_context context, const krb5_data *realm,
121                              const char *service, const char *protocol,
122                              struct srv_dns_entry **answers)
123 {
124     char *name = NULL;
125     DNS_STATUS st;
126     PDNS_RECORD records, rr;
127     struct srv_dns_entry *head = NULL, *srv = NULL;
128 
129     *answers = NULL;
130 
131     name = make_lookup_name(realm, service, protocol);
132     if (name == NULL)
133         return 0;
134 
135     TRACE_DNS_SRV_SEND(context, name);
136 
137     st = DnsQuery_UTF8(name, DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &records,
138                        NULL);
139     if (st != ERROR_SUCCESS)
140         return 0;
141 
142     for (rr = records; rr != NULL; rr = rr->pNext) {
143         if (rr->wType != DNS_TYPE_SRV)
144             continue;
145 
146         srv = malloc(sizeof(struct srv_dns_entry));
147         if (srv == NULL)
148             goto cleanup;
149 
150         srv->priority = rr->Data.SRV.wPriority;
151         srv->weight = rr->Data.SRV.wWeight;
152         srv->port = rr->Data.SRV.wPort;
153         /* Make sure the name looks fully qualified to the resolver. */
154         if (asprintf(&srv->host, "%s.", rr->Data.SRV.pNameTarget) < 0) {
155             free(srv);
156             goto cleanup;
157         }
158 
159         TRACE_DNS_SRV_ANS(context, srv->host, srv->port, srv->priority,
160                           srv->weight);
161         place_srv_entry(&head, srv);
162     }
163 
164 cleanup:
165     free(name);
166     if (records != NULL)
167         DnsRecordListFree(records, DnsFreeRecordList);
168     *answers = head;
169     return 0;
170 }
171 
172 #else /* _WIN32 */
173 
174 #include "dnsglue.h"
175 
176 /* Query the URI RR, collecting weight, priority, and target. */
177 krb5_error_code
k5_make_uri_query(krb5_context context,const krb5_data * realm,const char * service,struct srv_dns_entry ** answers)178 k5_make_uri_query(krb5_context context, const krb5_data *realm,
179                   const char *service, struct srv_dns_entry **answers)
180 {
181     const unsigned char *p = NULL, *base = NULL;
182     char *name = NULL;
183     int size, ret, rdlen;
184     unsigned short priority, weight;
185     struct krb5int_dns_state *ds = NULL;
186     struct srv_dns_entry *head = NULL, *uri = NULL;
187 
188     *answers = NULL;
189 
190     /* Construct service.realm. */
191     name = make_lookup_name(realm, service, NULL);
192     if (name == NULL)
193         return 0;
194 
195     TRACE_DNS_URI_SEND(context, name);
196 
197     size = krb5int_dns_init(&ds, name, C_IN, T_URI);
198     if (size < 0)
199         goto out;
200 
201     for (;;) {
202         ret = krb5int_dns_nextans(ds, &base, &rdlen);
203         if (ret < 0 || base == NULL)
204             goto out;
205 
206         p = base;
207 
208         SAFE_GETUINT16(base, rdlen, p, 2, priority, out);
209         SAFE_GETUINT16(base, rdlen, p, 2, weight, out);
210 
211         uri = k5alloc(sizeof(*uri), &ret);
212         if (uri == NULL)
213             goto out;
214 
215         uri->priority = priority;
216         uri->weight = weight;
217         /* rdlen - 4 bytes remain after the priority and weight. */
218         uri->host = k5memdup0(p, rdlen - 4, &ret);
219         if (uri->host == NULL) {
220             ret = errno;
221             goto out;
222         }
223 
224         TRACE_DNS_URI_ANS(context, uri->host, uri->priority, uri->weight);
225         place_srv_entry(&head, uri);
226     }
227 
228 out:
229     krb5int_dns_fini(ds);
230     free(name);
231     *answers = head;
232     return 0;
233 }
234 
235 /*
236  * Do DNS SRV query, return results in *answers.
237  *
238  * Make a best effort to return all the data we can.  On memory or decoding
239  * errors, just return what we've got.  Always return 0, currently.
240  */
241 
242 krb5_error_code
krb5int_make_srv_query_realm(krb5_context context,const krb5_data * realm,const char * service,const char * protocol,struct srv_dns_entry ** answers)243 krb5int_make_srv_query_realm(krb5_context context, const krb5_data *realm,
244                              const char *service, const char *protocol,
245                              struct srv_dns_entry **answers)
246 {
247     const unsigned char *p = NULL, *base = NULL;
248     char *name = NULL, host[MAXDNAME];
249     int size, ret, rdlen, nlen;
250     unsigned short priority, weight, port;
251     struct krb5int_dns_state *ds = NULL;
252     struct srv_dns_entry *head = NULL, *srv = NULL;
253 
254     /*
255      * First off, build a query of the form:
256      *
257      * service.protocol.realm
258      *
259      * which will most likely be something like:
260      *
261      * _kerberos._udp.REALM
262      *
263      */
264 
265     name = make_lookup_name(realm, service, protocol);
266     if (name == NULL)
267         return 0;
268 
269     TRACE_DNS_SRV_SEND(context, name);
270 
271     size = krb5int_dns_init(&ds, name, C_IN, T_SRV);
272     if (size < 0)
273         goto out;
274 
275     for (;;) {
276         ret = krb5int_dns_nextans(ds, &base, &rdlen);
277         if (ret < 0 || base == NULL)
278             goto out;
279 
280         p = base;
281 
282         SAFE_GETUINT16(base, rdlen, p, 2, priority, out);
283         SAFE_GETUINT16(base, rdlen, p, 2, weight, out);
284         SAFE_GETUINT16(base, rdlen, p, 2, port, out);
285 
286         /*
287          * RFC 2782 says the target is never compressed in the reply;
288          * do we believe that?  We need to flatten it anyway, though.
289          */
290         nlen = krb5int_dns_expand(ds, p, host, sizeof(host));
291         if (nlen < 0 || !INCR_OK(base, rdlen, p, nlen))
292             goto out;
293 
294         /*
295          * We got everything!  Insert it into our list, but make sure
296          * it's in the right order.  Right now we don't do anything
297          * with the weight field
298          */
299 
300         srv = malloc(sizeof(struct srv_dns_entry));
301         if (srv == NULL)
302             goto out;
303 
304         srv->priority = priority;
305         srv->weight = weight;
306         srv->port = port;
307         /* The returned names are fully qualified.  Don't let the
308          * local resolver code do domain search path stuff. */
309         if (asprintf(&srv->host, "%s.", host) < 0) {
310             free(srv);
311             goto out;
312         }
313 
314         TRACE_DNS_SRV_ANS(context, srv->host, srv->port, srv->priority,
315                           srv->weight);
316         place_srv_entry(&head, srv);
317     }
318 
319 out:
320     krb5int_dns_fini(ds);
321     free(name);
322     *answers = head;
323     return 0;
324 }
325 
326 #endif /* not _WIN32 */
327 #endif /* KRB5_DNS_LOOKUP */
328