1 /*        $NetBSD: hesiod.c,v 1.30 2017/03/10 18:02:32 maya 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 thread-safe because it uses res_nsend().
43  */
44 
45 #include <sys/cdefs.h>
46 
47 #if defined(LIBC_SCCS) && !defined(lint)
48 __IDSTRING(rcsid_hesiod_c,
49     "#Id: hesiod.c,v 1.18.2.1 1997/01/03 20:48:20 ghudson Exp #");
50 __IDSTRING(rcsid_hesiod_p_h,
51     "#Id: hesiod_p.h,v 1.1 1996/12/08 21:39:37 ghudson Exp #");
52 __IDSTRING(rcsid_hescompat_c,
53     "#Id: hescompat.c,v 1.1.2.1 1996/12/16 08:37:45 ghudson Exp #");
54 __RCSID("$NetBSD: hesiod.c,v 1.30 2017/03/10 18:02:32 maya Exp $");
55 #endif /* LIBC_SCCS and not lint */
56 
57 #include "namespace.h"
58 
59 #include <sys/types.h>
60 #include <sys/param.h>
61 #include <netinet/in.h>
62 #include <arpa/nameser.h>
63 
64 #include <assert.h>
65 #include <ctype.h>
66 #include <errno.h>
67 #include <hesiod.h>
68 #include <resolv.h>
69 #include <stdio.h>
70 #include <stdlib.h>
71 #include <string.h>
72 #include <unistd.h>
73 
74 #ifdef __weak_alias
75 __weak_alias(hesiod_init,_hesiod_init)
76 __weak_alias(hesiod_end,_hesiod_end)
77 __weak_alias(hesiod_to_bind,_hesiod_to_bind)
78 __weak_alias(hesiod_resolve,_hesiod_resolve)
79 __weak_alias(hesiod_free_list,_hesiod_free_list)
80 __weak_alias(hes_init,_hes_init)
81 __weak_alias(hes_to_bind,_hes_to_bind)
82 __weak_alias(hes_resolve,_hes_resolve)
83 __weak_alias(hes_error,_hes_error)
84 __weak_alias(hes_free,_hes_free)
85 #endif
86 
87 struct hesiod_p {
88           char      *lhs;                         /* normally ".ns" */
89           char      *rhs;                         /* AKA the default hesiod domain */
90           int        classes[2];                  /* The class search order. */
91 };
92 
93 #define   MAX_HESRESP         1024
94 
95 static int            read_config_file(struct hesiod_p *, const char *);
96 static char         **get_txt_records(int, const char *);
97 static int            init_context(void);
98 static void           translate_errors(void);
99 
100 
101 /*
102  * hesiod_init --
103  *        initialize a hesiod_p.
104  */
105 int
hesiod_init(void ** context)106 hesiod_init(void **context)
107 {
108           struct hesiod_p     *ctx;
109           const char          *p, *configname;
110           int serrno;
111 
112           _DIAGASSERT(context != NULL);
113 
114           ctx = calloc(1, sizeof(struct hesiod_p));
115           if (ctx) {
116                     *context = ctx;
117                     /*
118                      * don't permit overrides from environment
119                      * for set.id programs
120                      */
121                     if (issetugid())
122                               configname = NULL;
123                     else
124                               configname = getenv("HESIOD_CONFIG");
125                     if (!configname)
126                               configname = _PATH_HESIOD_CONF;
127                     if (read_config_file(ctx, configname) >= 0) {
128                               /*
129                                * The default rhs can be overridden by an
130                                * environment variable, unless set.id.
131                                */
132                               if (issetugid())
133                                         p = NULL;
134                               else
135                                         p = getenv("HES_DOMAIN");
136                               if (p) {
137                                         if (ctx->rhs)
138                                                   free(ctx->rhs);
139                                         ctx->rhs = malloc(strlen(p) + 2);
140                                         if (ctx->rhs) {
141                                                   *ctx->rhs = '.';
142                                                   strcpy(ctx->rhs + 1,
143                                                       (*p == '.') ? p + 1 : p);
144                                                   return 0;
145                                         } else
146                                                   errno = ENOMEM;
147                               } else
148                                         return 0;
149                     }
150           } else
151                     errno = ENOMEM;
152 
153           serrno = errno;
154           if (ctx) {
155                     free(ctx->lhs);
156                     free(ctx->rhs);
157                     free(ctx);
158           }
159           errno = serrno;
160           return -1;
161 }
162 
163 /*
164  * hesiod_end --
165  *        Deallocates the hesiod_p.
166  */
167 void
hesiod_end(void * context)168 hesiod_end(void *context)
169 {
170           struct hesiod_p *ctx = (struct hesiod_p *) context;
171 
172           _DIAGASSERT(context != NULL);
173 
174           free(ctx->rhs);
175           if (ctx->lhs)
176                     free(ctx->lhs);
177           free(ctx);
178 }
179 
180 /*
181  * hesiod_to_bind --
182  *        takes a hesiod (name, type) and returns a DNS
183  *        name which is to be resolved.
184  */
185 char *
hesiod_to_bind(void * context,const char * name,const char * type)186 hesiod_to_bind(void *context, const char *name, const char *type)
187 {
188           struct hesiod_p *ctx = (struct hesiod_p *) context;
189           char                 bindname[MAXDNAME], *p, *ret, **rhs_list = NULL;
190           const char          *rhs;
191           size_t               len;
192 
193           _DIAGASSERT(context != NULL);
194           _DIAGASSERT(name != NULL);
195           _DIAGASSERT(type != NULL);
196 
197         if (strlcpy(bindname, name, sizeof(bindname)) >= sizeof(bindname)) {
198                 errno = EMSGSIZE;
199                 return NULL;
200         }
201 
202           /*
203            * Find the right right hand side to use, possibly
204            * truncating bindname.
205            */
206           p = strchr(bindname, '@');
207           if (p) {
208                     *p++ = 0;
209                     if (strchr(p, '.'))
210                               rhs = name + (p - bindname);
211                     else {
212                               rhs_list = hesiod_resolve(context, p, "rhs-extension");
213                               if (rhs_list)
214                                         rhs = *rhs_list;
215                               else {
216                                         errno = ENOENT;
217                                         return NULL;
218                               }
219                     }
220           } else
221                     rhs = ctx->rhs;
222 
223           /* See if we have enough room. */
224           len = strlen(bindname) + 1 + strlen(type);
225           if (ctx->lhs)
226                     len += strlen(ctx->lhs) + ((ctx->lhs[0] != '.') ? 1 : 0);
227           len += strlen(rhs) + ((rhs[0] != '.') ? 1 : 0);
228           if (len > sizeof(bindname) - 1) {
229                     if (rhs_list)
230                               hesiod_free_list(context, rhs_list);
231                     errno = EMSGSIZE;
232                     return NULL;
233           }
234           /* Put together the rest of the domain. */
235           strlcat(bindname, ".", sizeof(bindname));
236           strlcat(bindname, type, sizeof(bindname));
237           /* Only append lhs if it isn't empty. */
238           if (ctx->lhs && ctx->lhs[0] != '\0' ) {
239                     if (ctx->lhs[0] != '.')
240                               strlcat(bindname, ".", sizeof(bindname));
241                     strlcat(bindname, ctx->lhs, sizeof(bindname));
242           }
243           if (rhs[0] != '.')
244                     strlcat(bindname, ".", sizeof(bindname));
245           strlcat(bindname, rhs, sizeof(bindname));
246 
247           /* rhs_list is no longer needed, since we're done with rhs. */
248           if (rhs_list)
249                     hesiod_free_list(context, rhs_list);
250 
251           /* Make a copy of the result and return it to the caller. */
252           ret = strdup(bindname);
253           if (ret == NULL)
254                     errno = ENOMEM;
255           return ret;
256 }
257 
258 /*
259  * hesiod_resolve --
260  *        Given a hesiod name and type, return an array of strings returned
261  *        by the resolver.
262  */
263 char **
hesiod_resolve(void * context,const char * name,const char * type)264 hesiod_resolve(void *context, const char *name, const char *type)
265 {
266           struct hesiod_p     *ctx = (struct hesiod_p *) context;
267           char                *bindname, **retvec;
268 
269           _DIAGASSERT(context != NULL);
270           _DIAGASSERT(name != NULL);
271           _DIAGASSERT(type != NULL);
272 
273           bindname = hesiod_to_bind(context, name, type);
274           if (!bindname)
275                     return NULL;
276 
277           retvec = get_txt_records(ctx->classes[0], bindname);
278           if (retvec == NULL && errno == ENOENT && ctx->classes[1])
279                     retvec = get_txt_records(ctx->classes[1], bindname);
280 
281           free(bindname);
282           return retvec;
283 }
284 
285 /*ARGSUSED*/
286 void
hesiod_free_list(void * context,char ** list)287 hesiod_free_list(void *context, char **list)
288 {
289           char  **p;
290 
291           _DIAGASSERT(context != NULL);
292 
293           if (list == NULL)
294                     return;
295           for (p = list; *p; p++)
296                     free(*p);
297           free(list);
298 }
299 
300 
301 /* read_config_file --
302  *        Parse the /etc/hesiod.conf file.  Returns 0 on success,
303  *        -1 on failure.  On failure, it might leave values in ctx->lhs
304  *        or ctx->rhs which need to be freed by the caller.
305  */
306 static int
read_config_file(struct hesiod_p * ctx,const char * filename)307 read_config_file(struct hesiod_p *ctx, const char *filename)
308 {
309           char      *buf, *key, *data, *p, **which;
310           int        n;
311           FILE      *fp;
312 
313           _DIAGASSERT(ctx != NULL);
314           _DIAGASSERT(filename != NULL);
315 
316           /* Set default query classes. */
317           ctx->classes[0] = C_IN;
318           ctx->classes[1] = C_HS;
319 
320           /* Try to open the configuration file. */
321           fp = fopen(filename, "re");
322           if (!fp) {
323                     /* Use compiled in default domain names. */
324                     ctx->lhs = strdup(DEF_LHS);
325                     ctx->rhs = strdup(DEF_RHS);
326                     if (ctx->lhs && ctx->rhs)
327                               return 0;
328                     else {
329                               errno = ENOMEM;
330                               return -1;
331                     }
332           }
333           ctx->lhs = NULL;
334           ctx->rhs = NULL;
335           for (; (buf = fparseln(fp, NULL, NULL, NULL, FPARSELN_UNESCALL))
336               != NULL; free(buf)) {
337                     p = buf;
338                     while (*p == ' ' || *p == '\t')
339                               p++;
340                     key = p;
341                     while (*p != ' ' && *p != '\t' && *p != '=' && *p)
342                               p++;
343 
344                     if (*p == '\0')
345                               continue;
346 
347                     *p++ = 0;
348 
349                     while (isspace((u_char) *p) || *p == '=')
350                               p++;
351 
352                     if (*p == '\0')
353                               continue;
354 
355                     data = p;
356                     while (!isspace((u_char) *p) && *p)
357                               p++;
358 
359                     *p = 0;
360 
361                     if (strcasecmp(key, "lhs") == 0 ||
362                         strcasecmp(key, "rhs") == 0) {
363                               which = (strcasecmp(key, "lhs") == 0)
364                                   ? &ctx->lhs : &ctx->rhs;
365                               *which = strdup(data);
366                               if (!*which) {
367                                         errno = ENOMEM;
368                                         free(buf);
369                                         (void)fclose(fp);
370                                         return -1;
371                               }
372                     } else {
373                               if (strcasecmp(key, "classes") == 0) {
374                                         n = 0;
375                                         while (*data && n < 2) {
376                                                   p = data;
377                                                   while (*p && *p != ',')
378                                                             p++;
379                                                   if (*p)
380                                                             *p++ = 0;
381                                                   if (strcasecmp(data, "IN") == 0)
382                                                             ctx->classes[n++] = C_IN;
383                                                   else
384                                                             if (strcasecmp(data, "HS") == 0)
385                                                                       ctx->classes[n++] =
386                                                                           C_HS;
387                                                   data = p;
388                                         }
389                                         while (n < 2)
390                                                   ctx->classes[n++] = 0;
391                               }
392                     }
393           }
394           fclose(fp);
395 
396           if (!ctx->rhs || ctx->classes[0] == 0 ||
397               ctx->classes[0] == ctx->classes[1]) {
398                     errno = ENOEXEC;
399                     return -1;
400           }
401           return 0;
402 }
403 
404 /*
405  * get_txt_records --
406  *        Given a DNS class and a DNS name, do a lookup for TXT records, and
407  *        return a list of them.
408  */
409 static char **
get_txt_records(int qclass,const char * name)410 get_txt_records(int qclass, const char *name)
411 {
412           HEADER              *hp;
413           unsigned char        qbuf[PACKETSZ], abuf[MAX_HESRESP], *p, *eom, *eor;
414           char                *dst, **list;
415           int                  ancount, qdcount, i, j, n, skip, type, class, len;
416           res_state  res = __res_get_state();
417 
418           if (res == NULL)
419                     return NULL;
420 
421           _DIAGASSERT(name != NULL);
422 
423           /* Construct the query. */
424           n = res_nmkquery(res, QUERY, name, qclass, T_TXT, NULL, 0,
425               NULL, qbuf, PACKETSZ);
426           if (n < 0) {
427                     errno = EMSGSIZE;
428                     __res_put_state(res);
429                     return NULL;
430           }
431 
432           /* Send the query. */
433           n = res_nsend(res, qbuf, n, abuf, MAX_HESRESP);
434           __res_put_state(res);
435           if (n < 0) {
436                     errno = ECONNREFUSED;
437                     return NULL;
438           }
439           /* Parse the header of the result. */
440           hp = (HEADER *) (void *) abuf;
441           ancount = ntohs(hp->ancount);
442           qdcount = ntohs(hp->qdcount);
443           p = abuf + sizeof(HEADER);
444           eom = abuf + n;
445 
446           /*
447            * Skip questions, trying to get to the answer section
448            * which follows.
449            */
450           for (i = 0; i < qdcount; i++) {
451                     skip = dn_skipname(p, eom);
452                     if (skip < 0 || p + skip + QFIXEDSZ > eom) {
453                               errno = EMSGSIZE;
454                               return NULL;
455                     }
456                     p += skip + QFIXEDSZ;
457           }
458 
459           /* Allocate space for the text record answers. */
460           list = malloc((ancount + 1) * sizeof(char *));
461           if (!list) {
462                     errno = ENOMEM;
463                     return NULL;
464           }
465           /* Parse the answers. */
466           j = 0;
467           for (i = 0; i < ancount; i++) {
468                     /* Parse the header of this answer. */
469                     skip = dn_skipname(p, eom);
470                     if (skip < 0 || p + skip + 10 > eom)
471                               break;
472                     type = p[skip + 0] << 8 | p[skip + 1];
473                     class = p[skip + 2] << 8 | p[skip + 3];
474                     len = p[skip + 8] << 8 | p[skip + 9];
475                     p += skip + 10;
476                     if (p + len > eom) {
477                               errno = EMSGSIZE;
478                               break;
479                     }
480                     /* Skip entries of the wrong class and type. */
481                     if (class != qclass || type != T_TXT) {
482                               p += len;
483                               continue;
484                     }
485                     /* Allocate space for this answer. */
486                     list[j] = malloc((size_t)len);
487                     if (!list[j]) {
488                               errno = ENOMEM;
489                               break;
490                     }
491                     dst = list[j++];
492 
493                     /* Copy answer data into the allocated area. */
494                     eor = p + len;
495                     while (p < eor) {
496                               n = (unsigned char) *p++;
497                               if (p + n > eor) {
498                                         errno = EMSGSIZE;
499                                         break;
500                               }
501                               memcpy(dst, p, (size_t)n);
502                               p += n;
503                               dst += n;
504                     }
505                     if (p < eor) {
506                               errno = EMSGSIZE;
507                               break;
508                     }
509                     *dst = 0;
510           }
511 
512           /*
513            * If we didn't terminate the loop normally, something
514            * went wrong.
515            */
516           if (i < ancount) {
517                     for (i = 0; i < j; i++)
518                               free(list[i]);
519                     free(list);
520                     return NULL;
521           }
522           if (j == 0) {
523                     errno = ENOENT;
524                     free(list);
525                     return NULL;
526           }
527           list[j] = NULL;
528           return list;
529 }
530 
531 /*
532  * COMPATIBILITY FUNCTIONS
533  */
534 
535 static int            inited = 0;
536 static void          *context;
537 static int            errval = HES_ER_UNINIT;
538 
539 int
hes_init(void)540 hes_init(void)
541 {
542           init_context();
543           return errval;
544 }
545 
546 char *
hes_to_bind(const char * name,const char * type)547 hes_to_bind(const char *name, const char *type)
548 {
549           static    char      *bindname;
550 
551           _DIAGASSERT(name != NULL);
552           _DIAGASSERT(type != NULL);
553 
554           if (init_context() < 0)
555                     return NULL;
556 
557           bindname = hesiod_to_bind(context, name, type);
558           if (!bindname)
559                     translate_errors();
560           return bindname;
561 }
562 
563 char **
hes_resolve(const char * name,const char * type)564 hes_resolve(const char *name, const char *type)
565 {
566           static char         **list;
567 
568           _DIAGASSERT(name != NULL);
569           _DIAGASSERT(type != NULL);
570 
571           if (init_context() < 0)
572                     return NULL;
573 
574           /*
575            * In the old Hesiod interface, the caller was responsible for
576            * freeing the returned strings but not the vector of strings itself.
577            */
578           if (list)
579                     free(list);
580 
581           list = hesiod_resolve(context, name, type);
582           if (!list)
583                     translate_errors();
584           return list;
585 }
586 
587 int
hes_error(void)588 hes_error(void)
589 {
590           return errval;
591 }
592 
593 void
hes_free(char ** hp)594 hes_free(char **hp)
595 {
596           hesiod_free_list(context, hp);
597 }
598 
599 static int
init_context(void)600 init_context(void)
601 {
602           if (!inited) {
603                     inited = 1;
604                     if (hesiod_init(&context) < 0) {
605                               errval = HES_ER_CONFIG;
606                               return -1;
607                     }
608                     errval = HES_ER_OK;
609           }
610           return 0;
611 }
612 
613 static void
translate_errors(void)614 translate_errors(void)
615 {
616           switch (errno) {
617           case ENOENT:
618                     errval = HES_ER_NOTFOUND;
619                     break;
620           case ECONNREFUSED:
621           case EMSGSIZE:
622                     errval = HES_ER_NET;
623                     break;
624           default:
625                     /* Not a good match, but the best we can do. */
626                     errval = HES_ER_CONFIG;
627                     break;
628           }
629 }
630