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