76f5b63a1774715d4086c92aceceb8df0689f86e
[platform/upstream/krb5.git] / src / lib / krb5 / os / dnssrv.c
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
30 #include "dnsglue.h"
31
32 /*
33  * Lookup a KDC via DNS SRV records
34  */
35
36 void
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.", placing the
49  * result into fixed_buf.  protocol may be NULL. */
50 static krb5_error_code
51 prepare_lookup_buf(const krb5_data *realm, const char *service,
52                    const char *protocol, char *fixed_buf, size_t bufsize)
53 {
54     struct k5buf buf;
55
56     if (memchr(realm->data, 0, realm->length))
57         return EINVAL;
58
59     k5_buf_init_fixed(&buf, fixed_buf, bufsize);
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 k5_buf_status(&buf);
76 }
77
78 /* Insert new into the list *head, ordering by priority.  Weight is not
79  * currently used. */
80 static void
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 /* Query the URI RR, collecting weight, priority, and target. */
106 krb5_error_code
107 k5_make_uri_query(const krb5_data *realm, const char *service,
108                   struct srv_dns_entry **answers)
109 {
110     const unsigned char *p = NULL, *base = NULL;
111     char host[MAXDNAME];
112     int size, ret, rdlen;
113     unsigned short priority, weight;
114     struct krb5int_dns_state *ds = NULL;
115     struct srv_dns_entry *head = NULL, *uri = NULL;
116
117     *answers = NULL;
118
119     /* Construct service.realm. */
120     ret = prepare_lookup_buf(realm, service, NULL, host, sizeof(host));
121     if (ret)
122         return 0;
123
124     size = krb5int_dns_init(&ds, host, C_IN, T_URI);
125     if (size < 0)
126         goto out;
127
128     for (;;) {
129         ret = krb5int_dns_nextans(ds, &base, &rdlen);
130         if (ret < 0 || base == NULL)
131             goto out;
132
133         p = base;
134
135         SAFE_GETUINT16(base, rdlen, p, 2, priority, out);
136         SAFE_GETUINT16(base, rdlen, p, 2, weight, out);
137
138         uri = k5alloc(sizeof(*uri), &ret);
139         if (uri == NULL)
140             goto out;
141
142         uri->priority = priority;
143         uri->weight = weight;
144         /* rdlen - 4 bytes remain after the priority and weight. */
145         uri->host = k5memdup0(p, rdlen - 4, &ret);
146         if (uri->host == NULL) {
147             ret = errno;
148             goto out;
149         }
150
151         place_srv_entry(&head, uri);
152     }
153
154 out:
155     krb5int_dns_fini(ds);
156     *answers = head;
157     return 0;
158 }
159
160 /*
161  * Do DNS SRV query, return results in *answers.
162  *
163  * Make a best effort to return all the data we can.  On memory or decoding
164  * errors, just return what we've got.  Always return 0, currently.
165  */
166
167 krb5_error_code
168 krb5int_make_srv_query_realm(const krb5_data *realm,
169                              const char *service,
170                              const char *protocol,
171                              struct srv_dns_entry **answers)
172 {
173     const unsigned char *p = NULL, *base = NULL;
174     char host[MAXDNAME];
175     int size, ret, rdlen, nlen;
176     unsigned short priority, weight, port;
177     struct krb5int_dns_state *ds = NULL;
178     struct srv_dns_entry *head = NULL, *srv = NULL;
179
180     /*
181      * First off, build a query of the form:
182      *
183      * service.protocol.realm
184      *
185      * which will most likely be something like:
186      *
187      * _kerberos._udp.REALM
188      *
189      */
190
191     ret = prepare_lookup_buf(realm, service, protocol, host, sizeof(host));
192     if (ret)
193         return 0;
194
195 #ifdef TEST
196     fprintf(stderr, "sending DNS SRV query for %s\n", host);
197 #endif
198
199     size = krb5int_dns_init(&ds, host, C_IN, T_SRV);
200     if (size < 0)
201         goto out;
202
203     for (;;) {
204         ret = krb5int_dns_nextans(ds, &base, &rdlen);
205         if (ret < 0 || base == NULL)
206             goto out;
207
208         p = base;
209
210         SAFE_GETUINT16(base, rdlen, p, 2, priority, out);
211         SAFE_GETUINT16(base, rdlen, p, 2, weight, out);
212         SAFE_GETUINT16(base, rdlen, p, 2, port, out);
213
214         /*
215          * RFC 2782 says the target is never compressed in the reply;
216          * do we believe that?  We need to flatten it anyway, though.
217          */
218         nlen = krb5int_dns_expand(ds, p, host, sizeof(host));
219         if (nlen < 0 || !INCR_OK(base, rdlen, p, nlen))
220             goto out;
221
222         /*
223          * We got everything!  Insert it into our list, but make sure
224          * it's in the right order.  Right now we don't do anything
225          * with the weight field
226          */
227
228         srv = malloc(sizeof(struct srv_dns_entry));
229         if (srv == NULL)
230             goto out;
231
232         srv->priority = priority;
233         srv->weight = weight;
234         srv->port = port;
235         /* The returned names are fully qualified.  Don't let the
236          * local resolver code do domain search path stuff. */
237         if (asprintf(&srv->host, "%s.", host) < 0) {
238             free(srv);
239             goto out;
240         }
241
242         place_srv_entry(&head, srv);
243     }
244
245 out:
246     krb5int_dns_fini(ds);
247     *answers = head;
248     return 0;
249 }
250 #endif