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