Update from tzcode1999h.
[kopensolaris-gnu/glibc.git] / hesiod / 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
44 #if defined(LIBC_SCCS) && !defined(lint)
45 static const char rcsid[] = "$Id$";
46 #endif
47
48 #include <sys/types.h>
49 #include <netinet/in.h>
50 #include <arpa/nameser.h>
51 #include <errno.h>
52 #include <netdb.h>
53 #include <resolv.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <ctype.h>
58 #include "hesiod.h"
59 #include "hesiod_p.h"
60
61 /* A few operating systems don't define these. */
62 #ifndef C_HS
63 # define C_HS   4
64 #endif
65 #ifndef T_TXT
66 # define T_TXT  16
67 #endif
68
69 static int read_config_file (struct hesiod_p *ctx, const char *filename);
70 static char **get_txt_records (struct hesiod_p *ctx, int class,
71                                const char *name);
72 #ifdef _LIBC
73 # define cistrcmp(s1, s2) strcasecmp (s1, s2)
74 #else
75 static int cistrcmp (const char *s1, const char *s2);
76 #endif
77
78 /* This function is called to initialize a hesiod_p. */
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     {
88       *context = ctx;
89       configname = __secure_getenv ("HESIOD_CONFIG");
90       if (!configname)
91         configname = SYSCONFDIR "/hesiod.conf";
92       if (read_config_file (ctx, configname) >= 0)
93         {
94           /* The default rhs can be overridden by an environment variable. */
95           p = __secure_getenv ("HES_DOMAIN");
96           if (p)
97             {
98               if (ctx->rhs)
99                 free (ctx->rhs);
100               ctx->rhs = malloc (strlen (p) + 2);
101               if (ctx->rhs)
102                 {
103                   *ctx->rhs = '.';
104                   strcpy (ctx->rhs + 1, (*p == '.') ? p + 1 : p);
105                   return 0;
106                 }
107               else
108                 __set_errno (ENOMEM);
109             }
110           else
111             return 0;
112         }
113     }
114   else
115     __set_errno (ENOMEM);
116
117   if (ctx->lhs)
118     free (ctx->lhs);
119   if (ctx->rhs)
120     free (ctx->rhs);
121   if (ctx)
122     free (ctx);
123   return -1;
124 }
125
126 /* This function deallocates the hesiod_p. */
127 void
128 hesiod_end (void *context)
129 {
130   struct hesiod_p *ctx = (struct hesiod_p *) context;
131
132   free (ctx->rhs);
133   if (ctx->lhs)
134     free (ctx->lhs);
135   free (ctx);
136 }
137
138 /* This function takes a hesiod (name, type) and returns a DNS
139  * name which is to be resolved.
140  */
141 char *
142 hesiod_to_bind (void *context, const char *name, const char *type)
143 {
144   struct hesiod_p *ctx = (struct hesiod_p *) context;
145   char bindname[MAXDNAME], *p, *endp, *ret, **rhs_list = NULL;
146   const char *rhs;
147   size_t len;
148
149   endp = stpcpy (bindname, name);
150
151   /* Find the right right hand side to use, possibly truncating bindname. */
152   p = strchr (bindname, '@');
153   if (p)
154     {
155       *p++ = 0;
156       if (strchr (p, '.'))
157         rhs = name + (p - bindname);
158       else
159         {
160           rhs_list = hesiod_resolve (context, p, "rhs-extension");
161           if (rhs_list)
162             rhs = *rhs_list;
163           else
164             {
165               __set_errno (ENOENT);
166               return NULL;
167             }
168         }
169     }
170   else
171     rhs = ctx->rhs;
172
173   /* See if we have enough room. */
174   len = (endp - bindname) + 1 + strlen (type);
175   if (ctx->lhs)
176     len += strlen (ctx->lhs) + ((ctx->lhs[0] != '.') ? 1 : 0);
177   len += strlen (rhs) + ((rhs[0] != '.') ? 1 : 0);
178   if (len > sizeof (bindname) - 1)
179     {
180       if (rhs_list)
181         hesiod_free_list (context, rhs_list);
182       __set_errno (EMSGSIZE);
183       return NULL;
184     }
185
186   /* Put together the rest of the domain. */
187   endp = stpcpy (stpcpy (endp, "."), type);
188   if (ctx->lhs)
189     {
190       if (ctx->lhs[0] != '.')
191         endp = stpcpy (endp, ".");
192       endp = stpcpy (endp, ctx->lhs);
193     }
194   if (rhs[0] != '.')
195     endp = stpcpy (endp, ".");
196   endp = stpcpy (endp, rhs);
197
198   /* rhs_list is no longer needed, since we're done with rhs. */
199   if (rhs_list)
200     hesiod_free_list (context, rhs_list);
201
202   /* Make a copy of the result and return it to the caller. */
203   ret = malloc ((endp - bindname) + 1);
204   if (!ret)
205     {
206       __set_errno (ENOMEM);
207       return NULL;
208     }
209   return strcpy (ret, bindname);
210 }
211
212 /* This is the core function.  Given a hesiod name and type, it
213  * returns an array of strings returned by the resolver.
214  */
215 char **
216 hesiod_resolve (void *context, const char *name, const char *type)
217 {
218   struct hesiod_p *ctx = (struct hesiod_p *) context;
219   char *bindname, **retvec;
220
221   bindname = hesiod_to_bind (context, name, type);
222   if (bindname == NULL)
223     return NULL;
224
225   retvec = get_txt_records(ctx, ctx->classes[0], bindname);
226   if (retvec == NULL && errno == ENOENT && ctx->classes[1])
227     retvec = get_txt_records (ctx, ctx->classes[1], bindname);
228
229   free (bindname);
230   return retvec;
231 }
232
233 void
234 hesiod_free_list (void *context, char **list)
235 {
236   char **p;
237
238   for (p = list; *p; p++)
239     free (*p);
240   free (list);
241 }
242
243 /* This function parses the /etc/hesiod.conf file.  Returns 0 on success,
244  * -1 on failure.  On failure, it might leave values in ctx->lhs or
245  * ctx->rhs which need to be freed by the caller. */
246 static int
247 read_config_file (struct hesiod_p *ctx, const char *filename)
248 {
249   char *key, *data, *p, **which;
250   char buf[MAXDNAME + 7];
251   int n;
252   FILE *fp;
253
254   /* Set default query classes. */
255   ctx->classes[0] = C_IN;
256   ctx->classes[1] = C_HS;
257
258   /* Try to open the configuration file. */
259   fp = fopen (filename, "r");
260   if (fp == NULL)
261     {
262       /* Use compiled in default domain names. */
263       ctx->lhs = malloc (strlen (DEF_LHS) + 1);
264       ctx->rhs = malloc (strlen (DEF_RHS) + 1);
265       if (ctx->lhs && ctx->rhs)
266         {
267           strcpy (ctx->lhs, DEF_LHS);
268           strcpy (ctx->rhs, DEF_RHS);
269           return 0;
270         }
271       else
272         {
273           __set_errno (ENOMEM);
274           return -1;
275         }
276     }
277
278   ctx->lhs = NULL;
279   ctx->rhs = NULL;
280   while (fgets (buf, sizeof (buf), fp) != NULL)
281     {
282       p = buf;
283       if (*p == '#' || *p == '\n' || *p == '\r')
284         continue;
285       while (*p == ' ' || *p == '\t')
286         ++p;
287       key = p;
288       while(*p != ' ' && *p != '\t' && *p != '=')
289         ++p;
290       *p++ = 0;
291
292       while (isspace (*p) || *p == '=')
293         ++p;
294       data = p;
295       while (!isspace (*p))
296         ++p;
297       *p = 0;
298
299       if (cistrcmp (key, "lhs") == 0 || cistrcmp (key, "rhs") == 0)
300         {
301           which = (strcmp (key, "lhs") == 0) ? &ctx->lhs : &ctx->rhs;
302           *which = strdup (data);
303           if (!*which)
304             {
305               __set_errno (ENOMEM);
306               return -1;
307             }
308         }
309       else if (cistrcmp (key, "classes") == 0)
310         {
311           n = 0;
312           while (*data && n < 2)
313             {
314               p = data;
315               while (*p && *p != ',')
316                 ++p;
317               if (*p)
318                 *p++ = 0;
319               if (cistrcmp (data, "IN") == 0)
320                 ctx->classes[n++] = C_IN;
321               else if (cistrcmp (data, "HS") == 0)
322                 ctx->classes[n++] = C_HS;
323               data = p;
324             }
325           while (n < 2)
326             ctx->classes[n++] = 0;
327         }
328     }
329   fclose (fp);
330
331   if (!ctx->rhs || ctx->classes[0] == 0 || ctx->classes[0] == ctx->classes[1])
332     {
333       __set_errno (ENOEXEC);
334       return -1;
335     }
336
337   return 0;
338 }
339
340 /* Given a DNS class and a DNS name, do a lookup for TXT records, and
341  * return a list of them.
342  */
343 static char **
344 get_txt_records (struct hesiod_p *ctx, int qclass, const char *name)
345 {
346   HEADER *hp;
347   unsigned char qbuf[PACKETSZ], abuf[MAX_HESRESP], *p, *eom, *eor;
348   char *dst, **list;
349   int ancount, qdcount, i, j, n, skip, type, class, len;
350
351   /* Make sure the resolver is initialized. */
352   if ((_res.options & RES_INIT) == 0 && res_init () == -1)
353     return NULL;
354
355   /* Construct the query. */
356   n = res_mkquery (QUERY, name, qclass, T_TXT, NULL, 0,
357                    NULL, qbuf, PACKETSZ);
358   if (n < 0)
359       return NULL;
360
361   /* Send the query. */
362   n = res_send (qbuf, n, abuf, MAX_HESRESP);
363   if (n < 0)
364     {
365       __set_errno (ECONNREFUSED);
366       return NULL;
367     }
368
369   /* Parse the header of the result. */
370   hp = (HEADER *) abuf;
371   ancount = ntohs (hp->ancount);
372   qdcount = ntohs (hp->qdcount);
373   p = abuf + sizeof (HEADER);
374   eom = abuf + n;
375
376   /* Skip questions, trying to get to the answer section which follows. */
377   for (i = 0; i < qdcount; ++i)
378     {
379       skip = dn_skipname (p, eom);
380       if (skip < 0 || p + skip + QFIXEDSZ > eom)
381         {
382           __set_errno (EMSGSIZE);
383           return NULL;
384         }
385       p += skip + QFIXEDSZ;
386     }
387
388   /* Allocate space for the text record answers. */
389   list = malloc ((ancount + 1) * sizeof(char *));
390   if (list == NULL)
391     {
392       __set_errno (ENOMEM);
393       return NULL;
394     }
395
396   /* Parse the answers. */
397   j = 0;
398   for (i = 0; i < ancount; i++)
399     {
400       /* Parse the header of this answer. */
401       skip = dn_skipname (p, eom);
402       if (skip < 0 || p + skip + 10 > eom)
403         break;
404       type = p[skip + 0] << 8 | p[skip + 1];
405       class = p[skip + 2] << 8 | p[skip + 3];
406       len = p[skip + 8] << 8 | p[skip + 9];
407       p += skip + 10;
408       if (p + len > eom)
409         {
410           __set_errno (EMSGSIZE);
411           break;
412         }
413
414       /* Skip entries of the wrong class and type. */
415       if (class != qclass || type != T_TXT)
416         {
417           p += len;
418           continue;
419         }
420
421       /* Allocate space for this answer. */
422       list[j] = malloc (len);
423       if (!list[j])
424         {
425           __set_errno (ENOMEM);
426           break;
427         }
428       dst = list[j++];
429
430       /* Copy answer data into the allocated area. */
431       eor = p + len;
432       while (p < eor)
433         {
434           n = (unsigned char) *p++;
435           if (p + n > eor)
436             {
437               __set_errno (EMSGSIZE);
438               break;
439             }
440           dst = mempcpy (dst, p, n);
441           p += n;
442         }
443       if (p < eor)
444         {
445           __set_errno (EMSGSIZE);
446           break;
447         }
448       *dst = 0;
449     }
450
451   /* If we didn't terminate the loop normally, something went wrong. */
452   if (i < ancount)
453     {
454       for (i = 0; i < j; i++)
455         free (list[i]);
456       free (list);
457       return NULL;
458     }
459
460   if (j == 0)
461     {
462       __set_errno (ENOENT);
463       free (list);
464       return NULL;
465     }
466
467   list[j] = NULL;
468   return list;
469 }
470
471 #ifndef _LIBC
472 static int
473 cistrcmp (const char *s1, const char *s2)
474 {
475   while (*s1 && tolower(*s1) == tolower(*s2))
476     {
477       s1++;
478       s2++;
479     }
480   return tolower(*s1) - tolower(*s2);
481 }
482 #endif