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