1/* Copyright (C) 1997-2016 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, see
16 <http://www.gnu.org/licenses/>. */
17
18/*
19 * Copyright (c) 1996,1999 by Internet Software Consortium.
20 *
21 * Permission to use, copy, modify, and distribute this software for any
22 * purpose with or without fee is hereby granted, provided that the above
23 * copyright notice and this permission notice appear in all copies.
24 *
25 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
26 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
28 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
29 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
30 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
31 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * SOFTWARE.
33 */
34
35/*
36 * This file is primarily maintained by <tytso@mit.edu> and <ghudson@mit.edu>.
37 */
38
39/*
40 * hesiod.c --- the core portion of the hesiod resolver.
41 *
42 * This file is derived from the hesiod library from Project Athena;
43 * It has been extensively rewritten by Theodore Ts'o to have a more
44 * thread-safe interface.
45 */
46
47/* Imports */
48
49#include <sys/types.h>
50#include <netinet/in.h>
51#include <arpa/nameser.h>
52
53#include <errno.h>
54#include <netdb.h>
55#include <resolv.h>
56#include <stdio.h>
57#include <stdlib.h>
58#include <string.h>
59
60#include "hesiod.h"
61#include "hesiod_p.h"
62#undef DEF_RHS
63
64#define _PATH_HESIOD_CONF "/etc/hesiod.conf"
65
66/* Forward */
67
68static int parse_config_file(struct hesiod_p *ctx, const char *filename);
69static char ** get_txt_records(struct hesiod_p *ctx, int class,
70 const char *name);
71
72/* Public */
73
74/*
75 * This function is called to initialize a hesiod_p.
76 */
77int
78hesiod_init(void **context) {
79 struct hesiod_p *ctx;
80 const char *configname;
81 char *cp;
82
83 ctx = malloc(sizeof(struct hesiod_p));
84 if (ctx == 0)
85 return (-1);
86
87 ctx->LHS = NULL;
88 ctx->RHS = NULL;
89 /* Set default query classes. */
90 ctx->classes[0] = C_IN;
91 ctx->classes[1] = C_HS;
92
93 configname = __libc_secure_getenv("HESIOD_CONFIG");
94 if (!configname)
95 configname = _PATH_HESIOD_CONF;
96 if (parse_config_file(ctx, configname) < 0) {
97#ifdef DEF_RHS
98 /*
99 * Use compiled in defaults.
100 */
101 ctx->LHS = malloc(strlen(DEF_LHS)+1);
102 ctx->RHS = malloc(strlen(DEF_RHS)+1);
103 if (ctx->LHS == 0 || ctx->RHS == 0)
104 goto cleanup;
105 strcpy(ctx->LHS, DEF_LHS);
106 strcpy(ctx->RHS, DEF_RHS);
107#else
108 goto cleanup;
109#endif
110 }
111 /*
112 * The default RHS can be overridden by an environment
113 * variable.
114 */
115 if ((cp = __libc_secure_getenv("HES_DOMAIN")) != NULL) {
116 free(ctx->RHS);
117 ctx->RHS = malloc(strlen(cp)+2);
118 if (!ctx->RHS)
119 goto cleanup;
120 if (cp[0] == '.')
121 strcpy(ctx->RHS, cp);
122 else {
123 ctx->RHS[0] = '.';
124 strcpy(ctx->RHS + 1, cp);
125 }
126 }
127
128 /*
129 * If there is no default hesiod realm set, we return an
130 * error.
131 */
132 if (!ctx->RHS) {
133 __set_errno(ENOEXEC);
134 goto cleanup;
135 }
136
137 *context = ctx;
138 return (0);
139
140 cleanup:
141 hesiod_end(ctx);
142 return (-1);
143}
144
145/*
146 * This function deallocates the hesiod_p
147 */
148void
149hesiod_end(void *context) {
150 struct hesiod_p *ctx = (struct hesiod_p *) context;
151 int save_errno = errno;
152
153 free(ctx->RHS);
154 free(ctx->LHS);
155 free(ctx);
156 __set_errno(save_errno);
157}
158
159/*
160 * This function takes a hesiod (name, type) and returns a DNS
161 * name which is to be resolved.
162 */
163char *
164hesiod_to_bind(void *context, const char *name, const char *type) {
165 struct hesiod_p *ctx = (struct hesiod_p *) context;
166 char *bindname;
167 char **rhs_list = NULL;
168 const char *RHS, *cp;
169 char *endp;
170
171 /* Decide what our RHS is, and set cp to the end of the actual name. */
172 if ((cp = strchr(name, '@')) != NULL) {
173 if (strchr(cp + 1, '.'))
174 RHS = cp + 1;
175 else if ((rhs_list = hesiod_resolve(context, cp + 1,
176 "rhs-extension")) != NULL)
177 RHS = *rhs_list;
178 else {
179 __set_errno(ENOENT);
180 return (NULL);
181 }
182 } else {
183 RHS = ctx->RHS;
184 cp = name + strlen(name);
185 }
186
187 /*
188 * Allocate the space we need, including up to three periods and
189 * the terminating NUL.
190 */
191 if ((bindname = malloc((cp - name) + strlen(type) + strlen(RHS) +
192 (ctx->LHS ? strlen(ctx->LHS) : 0) + 4)) == NULL) {
193 if (rhs_list)
194 hesiod_free_list(context, rhs_list);
195 return NULL;
196 }
197
198 /* Now put together the DNS name. */
199 endp = (char *) __mempcpy (bindname, name, cp - name);
200 *endp++ = '.';
201 endp = (char *) __stpcpy (endp, type);
202 if (ctx->LHS) {
203 if (ctx->LHS[0] != '.')
204 *endp++ = '.';
205 endp = __stpcpy (endp, ctx->LHS);
206 }
207 if (RHS[0] != '.')
208 *endp++ = '.';
209 strcpy (endp, RHS);
210
211 if (rhs_list)
212 hesiod_free_list(context, rhs_list);
213
214 return (bindname);
215}
216
217/*
218 * This is the core function. Given a hesiod (name, type), it
219 * returns an array of strings returned by the resolver.
220 */
221char **
222hesiod_resolve(void *context, const char *name, const char *type) {
223 struct hesiod_p *ctx = (struct hesiod_p *) context;
224 char *bindname = hesiod_to_bind(context, name, type);
225 char **retvec;
226
227 if (bindname == NULL)
228 return (NULL);
229
230 retvec = get_txt_records(ctx, ctx->classes[0], bindname);
231
232 if (retvec == NULL && (errno == ENOENT || errno == ECONNREFUSED) && ctx->classes[1])
233 retvec = get_txt_records(ctx, ctx->classes[1], bindname);
234
235
236 free(bindname);
237 return (retvec);
238}
239
240void
241hesiod_free_list(void *context, char **list) {
242 char **p;
243
244 for (p = list; *p; p++)
245 free(*p);
246 free(list);
247}
248
249/*
250 * This function parses the /etc/hesiod.conf file
251 */
252static int
253parse_config_file(struct hesiod_p *ctx, const char *filename) {
254 char buf[MAXDNAME+7];
255 FILE *fp;
256
257 /*
258 * Clear the existing configuration variable, just in case
259 * they're set.
260 */
261 free(ctx->RHS);
262 free(ctx->LHS);
263 ctx->RHS = ctx->LHS = 0;
264 /* Set default query classes. */
265 ctx->classes[0] = C_IN;
266 ctx->classes[1] = C_HS;
267
268 /*
269 * Now open and parse the file...
270 */
271 if (!(fp = fopen(filename, "rce")))
272 return (-1);
273
274 while (fgets(buf, sizeof(buf), fp) != NULL) {
275 char *key, *data, *cp, **cpp;
276
277 cp = buf;
278 if (*cp == '#' || *cp == '\n' || *cp == '\r')
279 continue;
280 while(*cp == ' ' || *cp == '\t')
281 cp++;
282 key = cp;
283 while(*cp != ' ' && *cp != '\t' && *cp != '=')
284 cp++;
285 *cp++ = '\0';
286
287 while(*cp == ' ' || *cp == '\t' || *cp == '=')
288 cp++;
289 data = cp;
290 while(*cp != ' ' && *cp != '\n' && *cp != '\r')
291 cp++;
292 *cp++ = '\0';
293
294 cpp = NULL;
295 if (strcasecmp(key, "lhs") == 0)
296 cpp = &ctx->LHS;
297 else if (strcasecmp(key, "rhs") == 0)
298 cpp = &ctx->RHS;
299 if (cpp) {
300 *cpp = strdup(data);
301 if (!*cpp)
302 goto cleanup;
303 } else if (strcasecmp(key, "classes") == 0) {
304 int n = 0;
305 while (*data && n < 2) {
306 cp = strchrnul(data, ',');
307 if (*cp != '\0')
308 *cp++ = '\0';
309 if (strcasecmp(data, "IN") == 0)
310 ctx->classes[n++] = C_IN;
311 else if (strcasecmp(data, "HS") == 0)
312 ctx->classes[n++] = C_HS;
313 data = cp;
314 }
315 if (n == 0) {
316 /* Restore the default. Better than
317 nother at all. */
318 ctx->classes[0] = C_IN;
319 ctx->classes[1] = C_HS;
320 } else if (n == 1
321 || ctx->classes[0] == ctx->classes[1])
322 ctx->classes[1] = 0;
323 }
324 }
325 fclose(fp);
326 return (0);
327
328 cleanup:
329 fclose(fp);
330 free(ctx->RHS);
331 free(ctx->LHS);
332 ctx->RHS = ctx->LHS = 0;
333 return (-1);
334}
335
336/*
337 * Given a DNS class and a DNS name, do a lookup for TXT records, and
338 * return a list of them.
339 */
340static char **
341get_txt_records(struct hesiod_p *ctx, int class, const char *name) {
342 struct {
343 int type; /* RR type */
344 int class; /* RR class */
345 int dlen; /* len of data section */
346 u_char *data; /* pointer to data */
347 } rr;
348 HEADER *hp;
349 u_char qbuf[MAX_HESRESP], abuf[MAX_HESRESP];
350 u_char *cp, *erdata, *eom;
351 char *dst, *edst, **list;
352 int ancount, qdcount;
353 int i, j, n, skip;
354
355 /*
356 * Construct the query and send it.
357 */
358 n = res_mkquery(QUERY, name, class, T_TXT, NULL, 0,
359 NULL, qbuf, MAX_HESRESP);
360 if (n < 0) {
361 __set_errno(EMSGSIZE);
362 return (NULL);
363 }
364 n = res_send(qbuf, n, abuf, MAX_HESRESP);
365 if (n < 0) {
366 __set_errno(ECONNREFUSED);
367 return (NULL);
368 }
369 if (n < HFIXEDSZ) {
370 __set_errno(EMSGSIZE);
371 return (NULL);
372 }
373
374 /*
375 * OK, parse the result.
376 */
377 hp = (HEADER *) abuf;
378 ancount = ntohs(hp->ancount);
379 qdcount = ntohs(hp->qdcount);
380 cp = abuf + sizeof(HEADER);
381 eom = abuf + n;
382
383 /* Skip query, trying to get to the answer section which follows. */
384 for (i = 0; i < qdcount; i++) {
385 skip = dn_skipname(cp, eom);
386 if (skip < 0 || cp + skip + QFIXEDSZ > eom) {
387 __set_errno(EMSGSIZE);
388 return (NULL);
389 }
390 cp += skip + QFIXEDSZ;
391 }
392
393 list = malloc((ancount + 1) * sizeof(char *));
394 if (!list)
395 return (NULL);
396 j = 0;
397 for (i = 0; i < ancount; i++) {
398 skip = dn_skipname(cp, eom);
399 if (skip < 0) {
400 __set_errno(EMSGSIZE);
401 goto cleanup;
402 }
403 cp += skip;
404 if (cp + 3 * INT16SZ + INT32SZ > eom) {
405 __set_errno(EMSGSIZE);
406 goto cleanup;
407 }
408 rr.type = ns_get16(cp);
409 cp += INT16SZ;
410 rr.class = ns_get16(cp);
411 cp += INT16SZ + INT32SZ; /* skip the ttl, too */
412 rr.dlen = ns_get16(cp);
413 cp += INT16SZ;
414 if (rr.dlen == 0 || cp + rr.dlen > eom) {
415 __set_errno(EMSGSIZE);
416 goto cleanup;
417 }
418 rr.data = cp;
419 cp += rr.dlen;
420 if (rr.class != class || rr.type != T_TXT)
421 continue;
422 if (!(list[j] = malloc(rr.dlen)))
423 goto cleanup;
424 dst = list[j++];
425 edst = dst + rr.dlen;
426 erdata = rr.data + rr.dlen;
427 cp = rr.data;
428 while (cp < erdata) {
429 n = (unsigned char) *cp++;
430 if (cp + n > eom || dst + n > edst) {
431 __set_errno(EMSGSIZE);
432 goto cleanup;
433 }
434 memcpy(dst, cp, n);
435 cp += n;
436 dst += n;
437 }
438 if (cp != erdata) {
439 __set_errno(EMSGSIZE);
440 goto cleanup;
441 }
442 *dst = '\0';
443 }
444 list[j] = NULL;
445 if (j == 0) {
446 __set_errno(ENOENT);
447 goto cleanup;
448 }
449 return (list);
450
451 cleanup:
452 for (i = 0; i < j; i++)
453 free(list[i]);
454 free(list);
455 return (NULL);
456}
457