]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - lib/libc/net/hesiod.c
Fix multiple denial of service in ntpd.
[FreeBSD/FreeBSD.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/param.h>
55 #include <netinet/in.h>
56 #include <arpa/nameser.h>
57
58 #include <ctype.h>
59 #include <errno.h>
60 #include <hesiod.h>
61 #include <resolv.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <string.h>
65 #include <unistd.h>
66
67 struct hesiod_p {
68         char    *lhs;                   /* normally ".ns" */
69         char    *rhs;                   /* AKA the default hesiod domain */
70         int      classes[2];            /* The class search order. */
71 };
72
73 #define MAX_HESRESP     1024
74
75 static int        read_config_file(struct hesiod_p *, const char *);
76 static char     **get_txt_records(int, const char *);
77 static int        init_context(void);
78 static void       translate_errors(void);
79
80
81 /*
82  * hesiod_init --
83  *      initialize a hesiod_p.
84  */
85 int 
86 hesiod_init(context)
87         void    **context;
88 {
89         struct hesiod_p *ctx;
90         const char      *p, *configname;
91
92         ctx = malloc(sizeof(struct hesiod_p));
93         if (ctx) {
94                 *context = ctx;
95                 if (!issetugid())
96                         configname = getenv("HESIOD_CONFIG");
97                 else
98                         configname = NULL;
99                 if (!configname)
100                         configname = _PATH_HESIOD_CONF;
101                 if (read_config_file(ctx, configname) >= 0) {
102                         /*
103                          * The default rhs can be overridden by an
104                          * environment variable.
105                          */
106                         if (!issetugid())
107                                 p = getenv("HES_DOMAIN");
108                         else
109                                 p = NULL;
110                         if (p) {
111                                 if (ctx->rhs)
112                                         free(ctx->rhs);
113                                 ctx->rhs = malloc(strlen(p) + 2);
114                                 if (ctx->rhs) {
115                                         *ctx->rhs = '.';
116                                         strcpy(ctx->rhs + 1,
117                                             (*p == '.') ? p + 1 : p);
118                                         return 0;
119                                 } else
120                                         errno = ENOMEM;
121                         } else
122                                 return 0;
123                 }
124         } else
125                 errno = ENOMEM;
126
127         if (ctx->lhs)
128                 free(ctx->lhs);
129         if (ctx->rhs)
130                 free(ctx->rhs);
131         if (ctx)
132                 free(ctx);
133         return -1;
134 }
135
136 /*
137  * hesiod_end --
138  *      Deallocates the hesiod_p.
139  */
140 void 
141 hesiod_end(context)
142         void    *context;
143 {
144         struct hesiod_p *ctx = (struct hesiod_p *) context;
145
146         free(ctx->rhs);
147         if (ctx->lhs)
148                 free(ctx->lhs);
149         free(ctx);
150 }
151
152 /*
153  * hesiod_to_bind --
154  *      takes a hesiod (name, type) and returns a DNS
155  *      name which is to be resolved.
156  */
157 char *
158 hesiod_to_bind(void *context, const char *name, const char *type)
159 {
160         struct hesiod_p *ctx = (struct hesiod_p *) context;
161         char             bindname[MAXDNAME], *p, *ret, **rhs_list = NULL;
162         const char      *rhs;
163         int              len;
164
165         if (strlcpy(bindname, name, sizeof(bindname)) >= sizeof(bindname)) {
166                 errno = EMSGSIZE;
167                 return NULL;
168         }
169
170                 /*
171                  * Find the right right hand side to use, possibly
172                  * truncating bindname.
173                  */
174         p = strchr(bindname, '@');
175         if (p) {
176                 *p++ = 0;
177                 if (strchr(p, '.'))
178                         rhs = name + (p - bindname);
179                 else {
180                         rhs_list = hesiod_resolve(context, p, "rhs-extension");
181                         if (rhs_list)
182                                 rhs = *rhs_list;
183                         else {
184                                 errno = ENOENT;
185                                 return NULL;
186                         }
187                 }
188         } else
189                 rhs = ctx->rhs;
190
191                 /* See if we have enough room. */
192         len = strlen(bindname) + 1 + strlen(type);
193         if (ctx->lhs)
194                 len += strlen(ctx->lhs) + ((ctx->lhs[0] != '.') ? 1 : 0);
195         len += strlen(rhs) + ((rhs[0] != '.') ? 1 : 0);
196         if (len > sizeof(bindname) - 1) {
197                 if (rhs_list)
198                         hesiod_free_list(context, rhs_list);
199                 errno = EMSGSIZE;
200                 return NULL;
201         }
202                 /* Put together the rest of the domain. */
203         strcat(bindname, ".");
204         strcat(bindname, type);
205                 /* Only append lhs if it isn't empty. */
206         if (ctx->lhs && ctx->lhs[0] != '\0' ) {
207                 if (ctx->lhs[0] != '.')
208                         strcat(bindname, ".");
209                 strcat(bindname, ctx->lhs);
210         }
211         if (rhs[0] != '.')
212                 strcat(bindname, ".");
213         strcat(bindname, rhs);
214
215                 /* rhs_list is no longer needed, since we're done with rhs. */
216         if (rhs_list)
217                 hesiod_free_list(context, rhs_list);
218
219                 /* Make a copy of the result and return it to the caller. */
220         ret = strdup(bindname);
221         if (!ret)
222                 errno = ENOMEM;
223         return ret;
224 }
225
226 /*
227  * hesiod_resolve --
228  *      Given a hesiod name and type, return an array of strings returned
229  *      by the resolver.
230  */
231 char **
232 hesiod_resolve(context, name, type)
233         void            *context;
234         const char      *name;
235         const char      *type;
236 {
237         struct hesiod_p *ctx = (struct hesiod_p *) context;
238         char            *bindname, **retvec;
239
240         bindname = hesiod_to_bind(context, name, type);
241         if (!bindname)
242                 return NULL;
243
244         retvec = get_txt_records(ctx->classes[0], bindname);
245         if (retvec == NULL && errno == ENOENT && ctx->classes[1])
246                 retvec = get_txt_records(ctx->classes[1], bindname);
247
248         free(bindname);
249         return retvec;
250 }
251
252 /*ARGSUSED*/
253 void 
254 hesiod_free_list(context, list)
255         void     *context;
256         char    **list;
257 {
258         char  **p;
259
260         if (list == NULL)
261                 return;
262         for (p = list; *p; p++)
263                 free(*p);
264         free(list);
265 }
266
267
268 /* read_config_file --
269  *      Parse the /etc/hesiod.conf file.  Returns 0 on success,
270  *      -1 on failure.  On failure, it might leave values in ctx->lhs
271  *      or ctx->rhs which need to be freed by the caller.
272  */
273 static int 
274 read_config_file(ctx, filename)
275         struct hesiod_p *ctx;
276         const char      *filename;
277 {
278         char    *key, *data, *p, **which;
279         char     buf[MAXDNAME + 7];
280         int      n;
281         FILE    *fp;
282
283                 /* Set default query classes. */
284         ctx->classes[0] = C_IN;
285         ctx->classes[1] = C_HS;
286
287                 /* Try to open the configuration file. */
288         fp = fopen(filename, "re");
289         if (!fp) {
290                 /* Use compiled in default domain names. */
291                 ctx->lhs = strdup(DEF_LHS);
292                 ctx->rhs = strdup(DEF_RHS);
293                 if (ctx->lhs && ctx->rhs)
294                         return 0;
295                 else {
296                         errno = ENOMEM;
297                         return -1;
298                 }
299         }
300         ctx->lhs = NULL;
301         ctx->rhs = NULL;
302         while (fgets(buf, sizeof(buf), fp) != NULL) {
303                 p = buf;
304                 if (*p == '#' || *p == '\n' || *p == '\r')
305                         continue;
306                 while (*p == ' ' || *p == '\t')
307                         p++;
308                 key = p;
309                 while (*p != ' ' && *p != '\t' && *p != '=')
310                         p++;
311                 *p++ = 0;
312
313                 while (isspace(*p) || *p == '=')
314                         p++;
315                 data = p;
316                 while (!isspace(*p))
317                         p++;
318                 *p = 0;
319
320                 if (strcasecmp(key, "lhs") == 0 ||
321                     strcasecmp(key, "rhs") == 0) {
322                         which = (strcasecmp(key, "lhs") == 0)
323                             ? &ctx->lhs : &ctx->rhs;
324                         *which = strdup(data);
325                         if (!*which) {
326                                 fclose(fp);
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 }