nrelease - fix/improve livecd
[dragonfly.git] / lib / libc / net / hesiod.c
1 /* Copyright (c) 1996 by Internet Software Consortium.
2  *
3  * Permission to use, copy, modify, and distribute this software for any
4  * purpose with or without fee is hereby granted, provided that the above
5  * copyright notice and this permission notice appear in all copies.
6  *
7  * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
8  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
9  * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
10  * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
11  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
12  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
13  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
14  * SOFTWARE.
15  */
16
17 /* Copyright 1996 by the Massachusetts Institute of Technology.
18  *
19  * Permission to use, copy, modify, and distribute this
20  * software and its documentation for any purpose and without
21  * fee is hereby granted, provided that the above copyright
22  * notice appear in all copies and that both that copyright
23  * notice and this permission notice appear in supporting
24  * documentation, and that the name of M.I.T. not be used in
25  * advertising or publicity pertaining to distribution of the
26  * software without specific, written prior permission.
27  * M.I.T. makes no representations about the suitability of
28  * this software for any purpose.  It is provided "as is"
29  * without express or implied warranty.
30  */
31
32 /* This file is part of the hesiod library.  It implements the core
33  * portion of the hesiod resolver.
34  *
35  * This file is loosely based on an interim version of hesiod.c from
36  * the BIND IRS library, which was in turn based on an earlier version
37  * of this file.  Extensive changes have been made on each step of the
38  * path.
39  *
40  * This implementation is not truly thread-safe at the moment because
41  * it uses res_send() and accesses _res.
42  *
43  * $NetBSD: hesiod.c,v 1.9 1999/02/11 06:16:38 simonb Exp $
44  * $FreeBSD: src/lib/libc/net/hesiod.c,v 1.9 2003/05/01 19:03:14 nectar Exp $
45  */
46
47 #include <sys/types.h>
48 #include <sys/param.h>
49 #include <netinet/in.h>
50 #include <arpa/nameser.h>
51
52 #include <ctype.h>
53 #include <errno.h>
54 #include <hesiod.h>
55 #include <resolv.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <unistd.h>
60
61 struct hesiod_p {
62         char    *lhs;                   /* normally ".ns" */
63         char    *rhs;                   /* AKA the default hesiod domain */
64         int      classes[2];            /* The class search order. */
65 };
66
67 #define MAX_HESRESP     1024
68
69 static int        read_config_file(struct hesiod_p *, const char *);
70 static char     **get_txt_records(int, const char *);
71 static int        init_context(void);
72 static void       translate_errors(void);
73
74
75 /*
76  * hesiod_init --
77  *      initialize a hesiod_p.
78  */
79 int
80 hesiod_init(void **context)
81 {
82         struct hesiod_p *ctx;
83         const char      *p, *configname;
84
85         ctx = malloc(sizeof(struct hesiod_p));
86         if (ctx) {
87                 *context = ctx;
88                 if (!issetugid())
89                         configname = getenv("HESIOD_CONFIG");
90                 else
91                         configname = NULL;
92                 if (!configname)
93                         configname = _PATH_HESIOD_CONF;
94                 if (read_config_file(ctx, configname) >= 0) {
95                         /*
96                          * The default rhs can be overridden by an
97                          * environment variable.
98                          */
99                         if (!issetugid())
100                                 p = getenv("HES_DOMAIN");
101                         else
102                                 p = NULL;
103                         if (p) {
104                                 if (ctx->rhs)
105                                         free(ctx->rhs);
106                                 ctx->rhs = malloc(strlen(p) + 2);
107                                 if (ctx->rhs) {
108                                         *ctx->rhs = '.';
109                                         strcpy(ctx->rhs + 1,
110                                             (*p == '.') ? p + 1 : p);
111                                         return 0;
112                                 } else
113                                         errno = ENOMEM;
114                         } else
115                                 return 0;
116                 }
117         } else
118                 errno = ENOMEM;
119
120         if (ctx->lhs)
121                 free(ctx->lhs);
122         if (ctx->rhs)
123                 free(ctx->rhs);
124         if (ctx)
125                 free(ctx);
126         return -1;
127 }
128
129 /*
130  * hesiod_end --
131  *      Deallocates the hesiod_p.
132  */
133 void
134 hesiod_end(void *context)
135 {
136         struct hesiod_p *ctx = (struct hesiod_p *) context;
137
138         free(ctx->rhs);
139         if (ctx->lhs)
140                 free(ctx->lhs);
141         free(ctx);
142 }
143
144 /*
145  * hesiod_to_bind --
146  *      takes a hesiod (name, type) and returns a DNS
147  *      name which is to be resolved.
148  */
149 char *
150 hesiod_to_bind(void *context, const char *name, const char *type)
151 {
152         struct hesiod_p *ctx = (struct hesiod_p *) context;
153         char             bindname[MAXDNAME], *p, *ret, **rhs_list = NULL;
154         const char      *rhs;
155         int              len;
156
157         if (strlcpy(bindname, name, sizeof(bindname)) >= sizeof(bindname)) {
158                 errno = EMSGSIZE;
159                 return NULL;
160         }
161
162                 /*
163                  * Find the right right hand side to use, possibly
164                  * truncating bindname.
165                  */
166         p = strchr(bindname, '@');
167         if (p) {
168                 *p++ = 0;
169                 if (strchr(p, '.'))
170                         rhs = name + (p - bindname);
171                 else {
172                         rhs_list = hesiod_resolve(context, p, "rhs-extension");
173                         if (rhs_list)
174                                 rhs = *rhs_list;
175                         else {
176                                 errno = ENOENT;
177                                 return NULL;
178                         }
179                 }
180         } else
181                 rhs = ctx->rhs;
182
183                 /* See if we have enough room. */
184         len = strlen(bindname) + 1 + strlen(type);
185         if (ctx->lhs)
186                 len += strlen(ctx->lhs) + ((ctx->lhs[0] != '.') ? 1 : 0);
187         len += strlen(rhs) + ((rhs[0] != '.') ? 1 : 0);
188         if (len > sizeof(bindname) - 1) {
189                 if (rhs_list)
190                         hesiod_free_list(context, rhs_list);
191                 errno = EMSGSIZE;
192                 return NULL;
193         }
194                 /* Put together the rest of the domain. */
195         strcat(bindname, ".");
196         strcat(bindname, type);
197                 /* Only append lhs if it isn't empty. */
198         if (ctx->lhs && ctx->lhs[0] != '\0' ) {
199                 if (ctx->lhs[0] != '.')
200                         strcat(bindname, ".");
201                 strcat(bindname, ctx->lhs);
202         }
203         if (rhs[0] != '.')
204                 strcat(bindname, ".");
205         strcat(bindname, rhs);
206
207                 /* rhs_list is no longer needed, since we're done with rhs. */
208         if (rhs_list)
209                 hesiod_free_list(context, rhs_list);
210
211                 /* Make a copy of the result and return it to the caller. */
212         ret = strdup(bindname);
213         if (!ret)
214                 errno = ENOMEM;
215         return ret;
216 }
217
218 /*
219  * hesiod_resolve --
220  *      Given a hesiod name and type, return an array of strings returned
221  *      by the resolver.
222  */
223 char **
224 hesiod_resolve(void *context, const char *name, const char *type)
225 {
226         struct hesiod_p *ctx = (struct hesiod_p *) context;
227         char            *bindname, **retvec;
228
229         bindname = hesiod_to_bind(context, name, type);
230         if (!bindname)
231                 return NULL;
232
233         retvec = get_txt_records(ctx->classes[0], bindname);
234         if (retvec == NULL && errno == ENOENT && ctx->classes[1])
235                 retvec = get_txt_records(ctx->classes[1], bindname);
236
237         free(bindname);
238         return retvec;
239 }
240
241 /*ARGSUSED*/
242 void
243 hesiod_free_list(void *context __unused, char **list)
244 {
245         char  **p;
246
247         if (list == NULL)
248                 return;
249         for (p = list; *p; p++)
250                 free(*p);
251         free(list);
252 }
253
254
255 /* read_config_file --
256  *      Parse the /etc/hesiod.conf file.  Returns 0 on success,
257  *      -1 on failure.  On failure, it might leave values in ctx->lhs
258  *      or ctx->rhs which need to be freed by the caller.
259  */
260 static int
261 read_config_file(struct hesiod_p *ctx, const char *filename)
262 {
263         char    *key, *data, *p, **which;
264         char     buf[MAXDNAME + 7];
265         int      n;
266         FILE    *fp;
267
268                 /* Set default query classes. */
269         ctx->classes[0] = C_IN;
270         ctx->classes[1] = C_HS;
271
272                 /* Try to open the configuration file. */
273         fp = fopen(filename, "r");
274         if (!fp) {
275                 /* Use compiled in default domain names. */
276                 ctx->lhs = strdup(DEF_LHS);
277                 ctx->rhs = strdup(DEF_RHS);
278                 if (ctx->lhs && ctx->rhs)
279                         return 0;
280                 else {
281                         errno = ENOMEM;
282                         return -1;
283                 }
284         }
285         ctx->lhs = NULL;
286         ctx->rhs = NULL;
287         while (fgets(buf, sizeof(buf), fp) != NULL) {
288                 p = buf;
289                 if (*p == '#' || *p == '\n' || *p == '\r')
290                         continue;
291                 while (*p == ' ' || *p == '\t')
292                         p++;
293                 key = p;
294                 while (*p != ' ' && *p != '\t' && *p != '=')
295                         p++;
296                 *p++ = 0;
297
298                 while (isspace(*p) || *p == '=')
299                         p++;
300                 data = p;
301                 while (!isspace(*p))
302                         p++;
303                 *p = 0;
304
305                 if (strcasecmp(key, "lhs") == 0 ||
306                     strcasecmp(key, "rhs") == 0) {
307                         which = (strcasecmp(key, "lhs") == 0)
308                             ? &ctx->lhs : &ctx->rhs;
309                         *which = strdup(data);
310                         if (!*which) {
311                                 errno = ENOMEM;
312                                 return -1;
313                         }
314                 } else {
315                         if (strcasecmp(key, "classes") == 0) {
316                                 n = 0;
317                                 while (*data && n < 2) {
318                                         p = data;
319                                         while (*p && *p != ',')
320                                                 p++;
321                                         if (*p)
322                                                 *p++ = 0;
323                                         if (strcasecmp(data, "IN") == 0)
324                                                 ctx->classes[n++] = C_IN;
325                                         else
326                                                 if (strcasecmp(data, "HS") == 0)
327                                                         ctx->classes[n++] =
328                                                             C_HS;
329                                         data = p;
330                                 }
331                                 while (n < 2)
332                                         ctx->classes[n++] = 0;
333                         }
334                 }
335         }
336         fclose(fp);
337
338         if (!ctx->rhs || ctx->classes[0] == 0 ||
339             ctx->classes[0] == ctx->classes[1]) {
340                 errno = ENOEXEC;
341                 return -1;
342         }
343         return 0;
344 }
345
346 /*
347  * get_txt_records --
348  *      Given a DNS class and a DNS name, do a lookup for TXT records, and
349  *      return a list of them.
350  */
351 static char **
352 get_txt_records(int qclass, const char *name)
353 {
354         HEADER          *hp;
355         unsigned char    qbuf[PACKETSZ], abuf[MAX_HESRESP], *p, *eom, *eor;
356         char            *dst, **list;
357         int              ancount, qdcount, i, j, n, skip, type, class, len;
358
359                 /* Make sure the resolver is initialized. */
360         if ((_res.options & RES_INIT) == 0 && res_init() == -1)
361                 return NULL;
362
363                 /* Construct the query. */
364         n = res_mkquery(QUERY, name, qclass, T_TXT, NULL, 0,
365             NULL, qbuf, PACKETSZ);
366         if (n < 0)
367                 return NULL;
368
369                 /* Send the query. */
370         n = res_send(qbuf, n, abuf, MAX_HESRESP);
371         if (n < 0 || n > MAX_HESRESP) {
372                 errno = ECONNREFUSED; /* XXX */
373                 return NULL;
374         }
375                 /* Parse the header of the result. */
376         hp = (HEADER *) (void *) abuf;
377         ancount = ntohs(hp->ancount);
378         qdcount = ntohs(hp->qdcount);
379         p = abuf + sizeof(HEADER);
380         eom = abuf + n;
381
382                 /*
383                  * Skip questions, trying to get to the answer section
384                  * which follows.
385                  */
386         for (i = 0; i < qdcount; i++) {
387                 skip = dn_skipname(p, eom);
388                 if (skip < 0 || p + skip + QFIXEDSZ > eom) {
389                         errno = EMSGSIZE;
390                         return NULL;
391                 }
392                 p += skip + QFIXEDSZ;
393         }
394
395                 /* Allocate space for the text record answers. */
396         list = malloc((ancount + 1) * sizeof(char *));
397         if (!list) {
398                 errno = ENOMEM;
399                 return NULL;
400         }
401                 /* Parse the answers. */
402         j = 0;
403         for (i = 0; i < ancount; i++) {
404                 /* Parse the header of this answer. */
405                 skip = dn_skipname(p, eom);
406                 if (skip < 0 || p + skip + 10 > eom)
407                         break;
408                 type = p[skip + 0] << 8 | p[skip + 1];
409                 class = p[skip + 2] << 8 | p[skip + 3];
410                 len = p[skip + 8] << 8 | p[skip + 9];
411                 p += skip + 10;
412                 if (p + len > eom) {
413                         errno = EMSGSIZE;
414                         break;
415                 }
416                 /* Skip entries of the wrong class and type. */
417                 if (class != qclass || type != T_TXT) {
418                         p += len;
419                         continue;
420                 }
421                 /* Allocate space for this answer. */
422                 list[j] = malloc((size_t)len);
423                 if (!list[j]) {
424                         errno = ENOMEM;
425                         break;
426                 }
427                 dst = list[j++];
428
429                 /* Copy answer data into the allocated area. */
430                 eor = p + len;
431                 while (p < eor) {
432                         n = (unsigned char) *p++;
433                         if (p + n > eor) {
434                                 errno = EMSGSIZE;
435                                 break;
436                         }
437                         memcpy(dst, p, (size_t)n);
438                         p += n;
439                         dst += n;
440                 }
441                 if (p < eor) {
442                         errno = EMSGSIZE;
443                         break;
444                 }
445                 *dst = 0;
446         }
447
448                 /*
449                  * If we didn't terminate the loop normally, something
450                  * went wrong.
451                  */
452         if (i < ancount) {
453                 for (i = 0; i < j; i++)
454                         free(list[i]);
455                 free(list);
456                 return NULL;
457         }
458         if (j == 0) {
459                 errno = ENOENT;
460                 free(list);
461                 return NULL;
462         }
463         list[j] = NULL;
464         return list;
465 }
466
467                 /*
468                  *      COMPATIBILITY FUNCTIONS
469                  */
470
471 static int        inited = 0;
472 static void      *context;
473 static int        errval = HES_ER_UNINIT;
474
475 int
476 hes_init(void)
477 {
478         init_context();
479         return errval;
480 }
481
482 char *
483 hes_to_bind(const char *name, const char *type)
484 {
485         static  char    *bindname;
486         if (init_context() < 0)
487                 return NULL;
488         if (bindname)
489                 free(bindname);
490         bindname = hesiod_to_bind(context, name, type);
491         if (!bindname)
492                 translate_errors();
493         return bindname;
494 }
495
496 char **
497 hes_resolve(const char *name, const char *type)
498 {
499         static char     **list;
500
501         if (init_context() < 0)
502                 return NULL;
503
504         /*
505          * In the old Hesiod interface, the caller was responsible for
506          * freeing the returned strings but not the vector of strings itself.
507          */
508         if (list)
509                 free(list);
510
511         list = hesiod_resolve(context, name, type);
512         if (!list)
513                 translate_errors();
514         return list;
515 }
516
517 int
518 hes_error(void)
519 {
520         return errval;
521 }
522
523 void
524 hes_free(char **hp)
525 {
526         hesiod_free_list(context, hp);
527 }
528
529 static int
530 init_context(void)
531 {
532         if (!inited) {
533                 inited = 1;
534                 if (hesiod_init(&context) < 0) {
535                         errval = HES_ER_CONFIG;
536                         return -1;
537                 }
538                 errval = HES_ER_OK;
539         }
540         return 0;
541 }
542
543 static void
544 translate_errors(void)
545 {
546         switch (errno) {
547         case ENOENT:
548                 errval = HES_ER_NOTFOUND;
549                 break;
550         case ECONNREFUSED:
551         case EMSGSIZE:
552                 errval = HES_ER_NET;
553                 break;
554         case ENOMEM:
555         default:
556                 /* Not a good match, but the best we can do. */
557                 errval = HES_ER_CONFIG;
558                 break;
559         }
560 }