(_nss_dns_getnetbyname_r, _nss_dns_getnetbyaddr_r): Use alloca or
[kopensolaris-gnu/glibc.git] / resolv / res_hconf.c
1 /* Copyright (C) 1993, 1995-2001, 2002 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3    Contributed by David Mosberger (davidm@azstarnet.com).
4
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, write to the Free
17    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18    02111-1307 USA.  */
19
20 /* This file provides a Linux /etc/host.conf compatible front end to
21    the various name resolvers (/etc/hosts, named, NIS server, etc.).
22    Though mostly compatibly, the following differences exist compared
23    to the original implementation:
24
25         - new command "spoof" takes an arguments like RESOLV_SPOOF_CHECK
26           environment variable (i.e., `off', `nowarn', or `warn').
27
28         - line comments can appear anywhere (not just at the beginning of
29           a line)
30 */
31
32 #include <errno.h>
33 #include <ctype.h>
34 #include <libintl.h>
35 #include <memory.h>
36 #include <stdio.h>
37 #include <stdio_ext.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <net/if.h>
41 #include <sys/ioctl.h>
42 #include <unistd.h>
43 #include <netinet/in.h>
44 #include <bits/libc-lock.h>
45 #include "ifreq.h"
46 #include "res_hconf.h"
47 #ifdef USE_IN_LIBIO
48 # include <wchar.h>
49 #endif
50
51 #define _PATH_HOSTCONF  "/etc/host.conf"
52
53 /* Environment vars that all user to override default behavior:  */
54 #define ENV_HOSTCONF    "RESOLV_HOST_CONF"
55 #define ENV_SERVORDER   "RESOLV_SERV_ORDER"
56 #define ENV_SPOOF       "RESOLV_SPOOF_CHECK"
57 #define ENV_TRIM_OVERR  "RESOLV_OVERRIDE_TRIM_DOMAINS"
58 #define ENV_TRIM_ADD    "RESOLV_ADD_TRIM_DOMAINS"
59 #define ENV_MULTI       "RESOLV_MULTI"
60 #define ENV_REORDER     "RESOLV_REORDER"
61
62 static const char *arg_service_list (const char *, int, const char *,
63                                      unsigned int);
64 static const char *arg_trimdomain_list (const char *, int, const char *,
65                                         unsigned int);
66 static const char *arg_spoof (const char *, int, const char *, unsigned int);
67 static const char *arg_bool (const char *, int, const char *, unsigned int);
68
69 static struct cmd
70 {
71   const char *name;
72   const char *(*parse_args) (const char * filename, int line_num,
73                              const char * args, unsigned int arg);
74   unsigned int arg;
75 } cmd[] =
76 {
77   {"order",             arg_service_list,       0},
78   {"trim",              arg_trimdomain_list,    0},
79   {"spoof",             arg_spoof,              0},
80   {"multi",             arg_bool,               HCONF_FLAG_MULTI},
81   {"nospoof",           arg_bool,               HCONF_FLAG_SPOOF},
82   {"spoofalert",        arg_bool,               HCONF_FLAG_SPOOFALERT},
83   {"reorder",           arg_bool,               HCONF_FLAG_REORDER}
84 };
85
86 /* Structure containing the state.  */
87 struct hconf _res_hconf;
88
89 /* Skip white space.  */
90 static const char *
91 skip_ws (const char *str)
92 {
93   while (isspace (*str)) ++str;
94   return str;
95 }
96
97
98 /* Skip until whitespace, comma, end of line, or comment character.  */
99 static const char *
100 skip_string (const char *str)
101 {
102   while (*str && !isspace (*str) && *str != '#' && *str != ',')
103     ++str;
104   return str;
105 }
106
107
108 static const char *
109 arg_service_list (const char *fname, int line_num, const char *args,
110                   unsigned int arg)
111 {
112   enum Name_Service service;
113   const char *start;
114   size_t len;
115   size_t i;
116   static struct
117   {
118     const char * name;
119     enum Name_Service service;
120   } svcs[] =
121     {
122       {"bind",  SERVICE_BIND},
123       {"hosts", SERVICE_HOSTS},
124       {"nis",   SERVICE_NIS},
125     };
126
127   do
128     {
129       start = args;
130       args = skip_string (args);
131       len = args - start;
132
133       service = SERVICE_NONE;
134       for (i = 0; i < sizeof (svcs) / sizeof (svcs[0]); ++i)
135         {
136           if (__strncasecmp (start, svcs[i].name, len) == 0
137               && len == strlen (svcs[i].name))
138           {
139             service = svcs[i].service;
140             break;
141           }
142       }
143       if (service == SERVICE_NONE)
144         {
145           char *buf;
146
147           __asprintf (&buf, _("%s: line %d: expected service, found `%s'\n"),
148                       fname, line_num, start);
149
150 #ifdef USE_IN_LIBIO
151           if (_IO_fwide (stderr, 0) > 0)
152             __fwprintf (stderr, L"%s", buf);
153           else
154 #endif
155             fputs (buf, stderr);
156
157           free (buf);
158           return 0;
159         }
160       if (_res_hconf.num_services >= SERVICE_MAX)
161         {
162           char *buf;
163
164           __asprintf (&buf,
165                       _("%s: line %d: cannot specify more than %d services"),
166                       fname, line_num, SERVICE_MAX);
167
168 #ifdef USE_IN_LIBIO
169           if (_IO_fwide (stderr, 0) > 0)
170             __fwprintf (stderr, L"%s", buf);
171           else
172 #endif
173             fputs (buf, stderr);
174
175           free (buf);
176           return 0;
177         }
178       _res_hconf.service[_res_hconf.num_services++] = service;
179
180       args = skip_ws (args);
181       switch (*args)
182         {
183         case ',':
184         case ';':
185         case ':':
186           args = skip_ws (++args);
187           if (!*args || *args == '#')
188             {
189               char *buf;
190
191               __asprintf (&buf, _("\
192 %s: line %d: list delimiter not followed by keyword"),
193                           fname, line_num);
194
195 #ifdef USE_IN_LIBIO
196               if (_IO_fwide (stderr, 0) > 0)
197                 __fwprintf (stderr, L"%s", buf);
198               else
199 #endif
200                 fputs (buf, stderr);
201
202               free (buf);
203               return 0;
204             }
205         default:
206           break;
207         }
208     }
209   while (*args && *args != '#');
210   return args;
211 }
212
213
214 static const char *
215 arg_trimdomain_list (const char *fname, int line_num, const char *args,
216                      unsigned int flag)
217 {
218   const char * start;
219   size_t len;
220
221   do
222     {
223       start = args;
224       args = skip_string (args);
225       len = args - start;
226
227       if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
228         {
229           char *buf;
230
231           __asprintf (&buf, _("\
232 %s: line %d: cannot specify more than %d trim domains"),
233                       fname, line_num, TRIMDOMAINS_MAX);
234
235 #ifdef USE_IN_LIBIO
236               if (_IO_fwide (stderr, 0) > 0)
237                 __fwprintf (stderr, L"%s", buf);
238               else
239 #endif
240                 fputs (buf, stderr);
241
242               free (buf);
243           return 0;
244         }
245       _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
246         __strndup (start, len);
247       args = skip_ws (args);
248       switch (*args)
249         {
250         case ',': case ';': case ':':
251           args = skip_ws (++args);
252           if (!*args || *args == '#')
253             {
254               char *buf;
255
256               __asprintf (&buf, _("\
257 %s: line %d: list delimiter not followed by domain"),
258                           fname, line_num);
259
260 #ifdef USE_IN_LIBIO
261               if (_IO_fwide (stderr, 0) > 0)
262                 __fwprintf (stderr, L"%s", buf);
263               else
264 #endif
265                 fputs (buf, stderr);
266
267               free (buf);
268               return 0;
269             }
270         default:
271           break;
272         }
273     }
274   while (*args && *args != '#');
275   return args;
276 }
277
278
279 static const char *
280 arg_spoof (const char *fname, int line_num, const char *args, unsigned flag)
281 {
282   const char *start = args;
283   size_t len;
284
285   args = skip_string (args);
286   len = args - start;
287
288   if (len == 3 && __strncasecmp (start, "off", len) == 0)
289     _res_hconf.flags &= ~(HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
290   else
291     {
292       _res_hconf.flags |= (HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
293       if ((len == 6 && __strncasecmp (start, "nowarn", len) == 0)
294           || !(len == 4 && __strncasecmp (start, "warn", len) == 0))
295         _res_hconf.flags &= ~HCONF_FLAG_SPOOFALERT;
296     }
297   return args;
298 }
299
300
301 static const char *
302 arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
303 {
304   if (__strncasecmp (args, "on", 2) == 0)
305     {
306       args += 2;
307       _res_hconf.flags |= flag;
308     }
309   else if (__strncasecmp (args, "off", 3) == 0)
310     {
311       args += 3;
312       _res_hconf.flags &= ~flag;
313     }
314   else
315     {
316       char *buf;
317
318       __asprintf (&buf,
319                   _("%s: line %d: expected `on' or `off', found `%s'\n"),
320                   fname, line_num, args);
321
322 #ifdef USE_IN_LIBIO
323       if (_IO_fwide (stderr, 0) > 0)
324         __fwprintf (stderr, L"%s", buf);
325       else
326 #endif
327         fputs (buf, stderr);
328
329       free (buf);
330       return 0;
331     }
332   return args;
333 }
334
335
336 static void
337 parse_line (const char *fname, int line_num, const char *str)
338 {
339   const char *start;
340   struct cmd *c = 0;
341   size_t len;
342   size_t i;
343
344   str = skip_ws (str);
345
346   /* skip line comment and empty lines: */
347   if (*str == '\0' || *str == '#') return;
348
349   start = str;
350   str = skip_string (str);
351   len = str - start;
352
353   for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
354     {
355       if (__strncasecmp (start, cmd[i].name, len) == 0
356           && strlen (cmd[i].name) == len)
357         {
358           c = &cmd[i];
359           break;
360         }
361     }
362   if (c == NULL)
363     {
364       char *buf;
365
366       __asprintf (&buf, _("%s: line %d: bad command `%s'\n"),
367                   fname, line_num, start);
368
369 #ifdef USE_IN_LIBIO
370       if (_IO_fwide (stderr, 0) > 0)
371         __fwprintf (stderr, L"%s", buf);
372       else
373 #endif
374         fputs (buf, stderr);
375
376       free (buf);
377       return;
378     }
379
380   /* process args: */
381   str = skip_ws (str);
382   str = (*c->parse_args) (fname, line_num, str, c->arg);
383   if (!str)
384     return;
385
386   /* rest of line must contain white space or comment only: */
387   while (*str)
388     {
389       if (!isspace (*str)) {
390         if (*str != '#')
391           {
392             char *buf;
393
394             __asprintf (&buf,
395                         _("%s: line %d: ignoring trailing garbage `%s'\n"),
396                         fname, line_num, str);
397
398 #ifdef USE_IN_LIBIO
399             if (_IO_fwide (stderr, 0) > 0)
400               __fwprintf (stderr, L"%s", buf);
401             else
402 #endif
403               fputs (buf, stderr);
404
405             free (buf);
406           }
407         break;
408       }
409       ++str;
410     }
411 }
412
413
414 static void
415 do_init (void)
416 {
417   const char *hconf_name;
418   int line_num = 0;
419   char buf[256], *envval;
420   FILE *fp;
421
422   memset (&_res_hconf, '\0', sizeof (_res_hconf));
423
424   hconf_name = getenv (ENV_HOSTCONF);
425   if (hconf_name == NULL)
426     hconf_name = _PATH_HOSTCONF;
427
428   fp = fopen (hconf_name, "r");
429   if (!fp)
430     /* make up something reasonable: */
431     _res_hconf.service[_res_hconf.num_services++] = SERVICE_BIND;
432   else
433     {
434       /* No threads using this stream.  */
435       __fsetlocking (fp, FSETLOCKING_BYCALLER);
436
437       while (fgets_unlocked (buf, sizeof (buf), fp))
438         {
439           ++line_num;
440           *__strchrnul (buf, '\n') = '\0';
441           parse_line (hconf_name, line_num, buf);
442         }
443       fclose (fp);
444     }
445
446   envval = getenv (ENV_SERVORDER);
447   if (envval)
448     {
449       _res_hconf.num_services = 0;
450       arg_service_list (ENV_SERVORDER, 1, envval, 0);
451     }
452
453   envval = getenv (ENV_SPOOF);
454   if (envval)
455     arg_spoof (ENV_SPOOF, 1, envval, 0);
456
457   envval = getenv (ENV_MULTI);
458   if (envval)
459     arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
460
461   envval = getenv (ENV_REORDER);
462   if (envval)
463     arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
464
465   envval = getenv (ENV_TRIM_ADD);
466   if (envval)
467     arg_trimdomain_list (ENV_TRIM_ADD, 1, envval, 0);
468
469   envval = getenv (ENV_TRIM_OVERR);
470   if (envval)
471     {
472       _res_hconf.num_trimdomains = 0;
473       arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval, 0);
474     }
475
476   _res_hconf.initialized = 1;
477 }
478
479
480 /* Initialize hconf datastructure by reading host.conf file and
481    environment variables.  */
482 void
483 _res_hconf_init (void)
484 {
485   __libc_once_define (static, once);
486
487   __libc_once (once, do_init);
488 }
489
490
491 /* List of known interfaces.  */
492 static struct netaddr
493 {
494   int addrtype;
495   union
496   {
497     struct
498     {
499       u_int32_t addr;
500       u_int32_t mask;
501     } ipv4;
502   } u;
503 } *ifaddrs;
504
505 /* We need to protect the dynamic buffer handling.  */
506 __libc_lock_define_initialized (static, lock);
507
508 /* Reorder addresses returned in a hostent such that the first address
509    is an address on the local subnet, if there is such an address.
510    Otherwise, nothing is changed.
511
512    Note that this function currently only handles IPv4 addresses.  */
513
514 void
515 _res_hconf_reorder_addrs (struct hostent *hp)
516 {
517 #if defined SIOCGIFCONF && defined SIOCGIFNETMASK
518   int i, j;
519   /* Number of interfaces.  */
520   static int num_ifs = -1;
521
522   /* Only reorder if we're supposed to.  */
523   if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
524     return;
525
526   /* Can't deal with anything but IPv4 for now...  */
527   if (hp->h_addrtype != AF_INET)
528     return;
529
530   if (num_ifs <= 0)
531     {
532       struct ifreq *ifr, *cur_ifr;
533       int sd, num, i;
534       /* Save errno.  */
535       int save = errno;
536
537       /* Initialize interface table.  */
538
539       num_ifs = 0;
540
541       /* The SIOCGIFNETMASK ioctl will only work on an AF_INET socket.  */
542       sd = __socket (AF_INET, SOCK_DGRAM, 0);
543       if (sd < 0)
544         return;
545
546       /* Get lock.  */
547       __libc_lock_lock (lock);
548
549       /* Get a list of interfaces.  */
550       __ifreq (&ifr, &num, sd);
551       if (!ifr)
552         goto cleanup;
553
554       ifaddrs = malloc (num * sizeof (ifaddrs[0]));
555       if (!ifaddrs)
556         goto cleanup1;
557
558       /* Copy usable interfaces in ifaddrs structure.  */
559       for (cur_ifr = ifr, i = 0; i < num; cur_ifr = __if_nextreq (cur_ifr), ++i)
560         {
561           if (cur_ifr->ifr_addr.sa_family != AF_INET)
562             continue;
563
564           ifaddrs[num_ifs].addrtype = AF_INET;
565           ifaddrs[num_ifs].u.ipv4.addr =
566             ((struct sockaddr_in *) &cur_ifr->ifr_addr)->sin_addr.s_addr;
567
568           if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
569             continue;
570
571           ifaddrs[num_ifs].u.ipv4.mask =
572             ((struct sockaddr_in *) &cur_ifr->ifr_netmask)->sin_addr.s_addr;
573
574           /* Now we're committed to this entry.  */
575           ++num_ifs;
576         }
577       /* Just keep enough memory to hold all the interfaces we want.  */
578       ifaddrs = realloc (ifaddrs, num_ifs * sizeof (ifaddrs[0]));
579
580     cleanup1:
581       __if_freereq (ifr, num);
582
583     cleanup:
584       /* Release lock, preserve error value, and close socket.  */
585       save = errno;
586       __libc_lock_unlock (lock);
587       __close (sd);
588     }
589
590   if (num_ifs == 0)
591     return;
592
593   /* Find an address for which we have a direct connection.  */
594   for (i = 0; hp->h_addr_list[i]; ++i)
595     {
596       struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
597
598       for (j = 0; j < num_ifs; ++j)
599         {
600           u_int32_t if_addr    = ifaddrs[j].u.ipv4.addr;
601           u_int32_t if_netmask = ifaddrs[j].u.ipv4.mask;
602
603           if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
604             {
605               void *tmp;
606
607               tmp = hp->h_addr_list[i];
608               hp->h_addr_list[i] = hp->h_addr_list[0];
609               hp->h_addr_list[0] = tmp;
610               return;
611             }
612         }
613     }
614 #endif /* defined(SIOCGIFCONF) && ... */
615 }
616
617
618 /* If HOSTNAME has a postfix matching any of the trimdomains, trim away
619    that postfix.  Notice that HOSTNAME is modified inplace.  Also, the
620    original code applied all trimdomains in order, meaning that the
621    same domainname could be trimmed multiple times.  I believe this
622    was unintentional.  */
623 void
624 _res_hconf_trim_domain (char *hostname)
625 {
626   size_t hostname_len, trim_len;
627   int i;
628
629   hostname_len = strlen (hostname);
630
631   for (i = 0; i < _res_hconf.num_trimdomains; ++i)
632     {
633       const char *trim = _res_hconf.trimdomain[i];
634
635       trim_len = strlen (trim);
636       if (hostname_len > trim_len
637           && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
638         {
639           hostname[hostname_len - trim_len] = '\0';
640           break;
641         }
642     }
643 }
644
645
646 /* Trim all hostnames/aliases in HP according to the trimdomain list.
647    Notice that HP is modified inplace!  */
648 void
649 _res_hconf_trim_domains (struct hostent *hp)
650 {
651   int i;
652
653   if (_res_hconf.num_trimdomains == 0)
654     return;
655
656   _res_hconf_trim_domain (hp->h_name);
657   for (i = 0; hp->h_aliases[i]; ++i)
658     _res_hconf_trim_domain (hp->h_aliases[i]);
659 }
660
661
662 /* Free all resources if necessary.  */
663 static void __attribute__ ((unused))
664 free_mem (void)
665 {
666   free (ifaddrs);
667 }
668
669 text_set_element (__libc_subfreeres, free_mem);