(obstack_free): Explicitly convert __obj to
[kopensolaris-gnu/glibc.git] / resolv / res_hconf.c
1 /* Copyright (C) 1993, 95, 96, 97, 98, 99 Free Software Foundation, Inc.
2    Contributed by David Mosberger (davidm@azstarnet.com).
3
4    The GNU C Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public License as
6    published by the Free Software Foundation; either version 2 of the
7    License, or (at your option) any later version.
8
9    The GNU C Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13
14    You should have received a copy of the GNU Library General Public
15    License along with the GNU C Library; see the file COPYING.LIB.  If not,
16    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17    Boston, MA 02111-1307, USA.  */
18
19 /* This file provides a Linux /etc/host.conf compatible front end to
20 the various name resolvers (/etc/hosts, named, NIS server, etc.).
21 Though mostly compatibly, the following differences exist compared
22 to the original implementation:
23
24         - new command "spoof" takes an arguments like RESOLV_SPOOF_CHECK
25           environment variable (i.e., `off', `nowarn', or `warn').
26
27         - line comments can appear anywhere (not just at the beginning of
28           a line)
29 */
30 #include <ctype.h>
31 #include <memory.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <net/if.h>
36
37 #include "res_hconf.h"
38
39 #define _PATH_HOSTCONF  "/etc/host.conf"
40
41 /* Environment vars that all user to override default behavior:  */
42 #define ENV_HOSTCONF    "RESOLV_HOST_CONF"
43 #define ENV_SERVORDER   "RESOLV_SERV_ORDER"
44 #define ENV_SPOOF       "RESOLV_SPOOF_CHECK"
45 #define ENV_TRIM_OVERR  "RESOLV_OVERRIDE_TRIM_DOMAINS"
46 #define ENV_TRIM_ADD    "RESOLV_ADD_TRIM_DOMAINS"
47 #define ENV_MULTI       "RESOLV_MULTI"
48 #define ENV_REORDER     "RESOLV_REORDER"
49
50 static const char *arg_service_list (const char *, int, const char *,
51                                      unsigned int);
52 static const char *arg_trimdomain_list (const char *, int, const char *,
53                                         unsigned int);
54 static const char *arg_spoof (const char *, int, const char *, unsigned int);
55 static const char *arg_bool (const char *, int, const char *, unsigned int);
56
57 static struct cmd
58 {
59   const char *name;
60   const char *(*parse_args) (const char * filename, int line_num,
61                              const char * args, unsigned int arg);
62   unsigned int arg;
63 } cmd[] =
64 {
65   {"order",             arg_service_list,       0},
66   {"trim",              arg_trimdomain_list,    0},
67   {"spoof",             arg_spoof,              0},
68   {"multi",             arg_bool,               HCONF_FLAG_MULTI},
69   {"nospoof",           arg_bool,               HCONF_FLAG_SPOOF},
70   {"spoofalert",        arg_bool,               HCONF_FLAG_SPOOFALERT},
71   {"reorder",           arg_bool,               HCONF_FLAG_REORDER}
72 };
73
74 /* Structure containing the state.  */
75 struct hconf _res_hconf;
76
77 /* Skip white space.  */
78 static const char *
79 skip_ws (const char *str)
80 {
81   while (isspace (*str)) ++str;
82   return str;
83 }
84
85
86 /* Skip until whitespace, comma, end of line, or comment character.  */
87 static const char *
88 skip_string (const char *str)
89 {
90   while (*str && !isspace (*str) && *str != '#' && *str != ',')
91     ++str;
92   return str;
93 }
94
95
96 static const char *
97 arg_service_list (const char *fname, int line_num, const char *args,
98                   unsigned int arg)
99 {
100   enum Name_Service service;
101   const char *start;
102   size_t len;
103   int i;
104   static struct
105   {
106     const char * name;
107     enum Name_Service service;
108   } svcs[] =
109     {
110       {"bind",  SERVICE_BIND},
111       {"hosts", SERVICE_HOSTS},
112       {"nis",   SERVICE_NIS},
113     };
114
115   do
116     {
117       start = args;
118       args = skip_string (args);
119       len = args - start;
120
121       service = SERVICE_NONE;
122       for (i = 0; i < sizeof (svcs) / sizeof (svcs[0]); ++i)
123         {
124           if (__strncasecmp (start, svcs[i].name, len) == 0
125               && len == strlen (svcs[i].name))
126           {
127             service = svcs[i].service;
128             break;
129           }
130       }
131       if (service == SERVICE_NONE)
132         {
133           fprintf (stderr, "%s: line %d: expected service, found `%s'\n",
134                    fname, line_num, start);
135           return 0;
136         }
137       if (_res_hconf.num_services >= SERVICE_MAX)
138         {
139           fprintf (stderr, "%s: line %d: cannot specify more than %d services",
140                    fname, line_num, SERVICE_MAX);
141           return 0;
142         }
143       _res_hconf.service[_res_hconf.num_services++] = service;
144
145       args = skip_ws (args);
146       switch (*args)
147         {
148         case ',':
149         case ';':
150         case ':':
151           args = skip_ws (++args);
152           if (!*args || *args == '#')
153             {
154               fprintf (stderr,
155                        "%s: line %d: list delimiter not followed by keyword",
156                        fname, line_num);
157               return 0;
158             }
159         default:
160           break;
161         }
162     }
163   while (*args && *args != '#');
164   return args;
165 }
166
167
168 static const char *
169 arg_trimdomain_list (const char *fname, int line_num, const char *args,
170                      unsigned int flag)
171 {
172   const char * start;
173   size_t len;
174
175   do
176     {
177       start = args;
178       args = skip_string (args);
179       len = args - start;
180
181       if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
182         {
183           fprintf (stderr,
184                    "%s: line %d: cannot specify more than %d trim domains",
185                    fname, line_num, TRIMDOMAINS_MAX);
186           return 0;
187         }
188       _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
189         __strndup (start, len);
190       args = skip_ws (args);
191       switch (*args)
192         {
193         case ',': case ';': case ':':
194           args = skip_ws (++args);
195           if (!*args || *args == '#')
196             {
197               fprintf (stderr,
198                        "%s: line %d: list delimiter not followed by domain",
199                        fname, line_num);
200               return 0;
201             }
202         default:
203           break;
204         }
205     }
206   while (*args && *args != '#');
207   return args;
208 }
209
210
211 static const char *
212 arg_spoof (const char *fname, int line_num, const char *args, unsigned flag)
213 {
214   const char *start = args;
215   size_t len;
216
217   args = skip_string (args);
218   len = args - start;
219
220   if (len == 3 && __strncasecmp (start, "off", len) == 0)
221     _res_hconf.flags &= ~(HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
222   else
223     {
224       _res_hconf.flags |= (HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
225       if ((len == 6 && __strncasecmp (start, "nowarn", len) == 0)
226           || !(len == 4 && __strncasecmp (start, "warn", len) == 0))
227         _res_hconf.flags &= ~HCONF_FLAG_SPOOFALERT;
228     }
229   return args;
230 }
231
232
233 static const char *
234 arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
235 {
236   if (__strncasecmp (args, "on", 2) == 0)
237     {
238       args += 2;
239       _res_hconf.flags |= flag;
240     }
241   else if (__strncasecmp (args, "off", 3) == 0)
242     {
243       args += 3;
244       _res_hconf.flags &= ~flag;
245     }
246   else
247     {
248       fprintf (stderr, "%s: line %d: expected `on' or `off', found `%s'\n",
249                fname, line_num, args);
250       return 0;
251     }
252   return args;
253 }
254
255
256 static void
257 parse_line (const char *fname, int line_num, const char *str)
258 {
259   const char *start;
260   struct cmd *c = 0;
261   size_t len;
262   int i;
263
264   str = skip_ws (str);
265
266   /* skip line comment and empty lines: */
267   if (*str == '\0' || *str == '#') return;
268
269   start = str;
270   str = skip_string (str);
271   len = str - start;
272
273   for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
274     {
275       if (strncasecmp (start, cmd[i].name, len) == 0
276           && strlen (cmd[i].name) == len)
277         {
278           c = &cmd[i];
279           break;
280         }
281     }
282   if (c == NULL)
283     {
284       fprintf (stderr, "%s: line %d: bad command `%s'\n",
285                fname, line_num, start);
286       return;
287     }
288
289   /* process args: */
290   str = skip_ws (str);
291   str = (*c->parse_args) (fname, line_num, str, c->arg);
292   if (!str)
293     return;
294
295   /* rest of line must contain white space or comment only: */
296   while (*str)
297     {
298       if (!isspace (*str)) {
299         if (*str != '#')
300           fprintf (stderr, "%s: line %d: ignoring trailing garbage `%s'\n",
301                    fname, line_num, str);
302         break;
303       }
304       ++str;
305     }
306 }
307
308
309 /* Initialize hconf datastructure by reading host.conf file and
310    environment variables.  */
311 void
312 _res_hconf_init (void)
313 {
314   const char *hconf_name;
315   int line_num = 0;
316   char buf[256], *end, *envval;
317   FILE *fp;
318
319   if (_res_hconf.initialized)
320     return;
321
322   memset (&_res_hconf, '\0', sizeof (_res_hconf));
323
324   hconf_name = __secure_getenv (ENV_HOSTCONF);
325   if (hconf_name == NULL)
326     hconf_name = _PATH_HOSTCONF;
327
328   fp = fopen (hconf_name, "r");
329   if (!fp)
330     /* make up something reasonable: */
331     _res_hconf.service[_res_hconf.num_services++] = SERVICE_BIND;
332   else
333     {
334       while (fgets_unlocked (buf, sizeof (buf), fp))
335         {
336           ++line_num;
337           end = strchr (buf, '\n');
338           if (end)
339             *end = '\0';
340           parse_line (hconf_name, line_num, buf);
341         }
342       fclose (fp);
343     }
344
345   envval = getenv (ENV_SERVORDER);
346   if (envval)
347     {
348       _res_hconf.num_services = 0;
349       arg_service_list (ENV_SERVORDER, 1, envval, 0);
350     }
351
352   envval = getenv (ENV_SPOOF);
353   if (envval)
354     arg_spoof (ENV_SPOOF, 1, envval, 0);
355
356   envval = getenv (ENV_MULTI);
357   if (envval)
358     arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
359
360   envval = getenv (ENV_REORDER);
361   if (envval)
362     arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
363
364   envval = getenv (ENV_TRIM_ADD);
365   if (envval)
366     arg_trimdomain_list (ENV_TRIM_ADD, 1, envval, 0);
367
368   envval = getenv (ENV_TRIM_OVERR);
369   if (envval)
370     {
371       _res_hconf.num_trimdomains = 0;
372       arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval, 0);
373     }
374
375   _res_hconf.initialized = 1;
376 }
377
378
379 /* Reorder addresses returned in a hostent such that the first address
380    is an address on the local subnet, if there is such an address.
381    Otherwise, nothing is changed.  */
382
383 void
384 _res_hconf_reorder_addrs (struct hostent *hp)
385 {
386 #if defined SIOCGIFCONF && defined SIOCGIFNETMASK
387   static int num_ifs = -1;      /* number of interfaces */
388   static struct netaddr
389   {
390     int addrtype;
391     union
392     {
393       struct
394       {
395         u_int32_t       addr;
396         u_int32_t       mask;
397       } ipv4
398     } u;
399   } *ifaddrs;
400
401   if (hp->h_addrtype != AF_INET)
402     return;     /* can't deal with anything but IPv4 for now... */
403
404   if (num_ifs <= 0)
405     {
406       struct ifconf ifs;
407       struct ifreq *ifr;
408       size_t size, num;
409       int sd;
410
411       /* initialize interface table: */
412
413       num_ifs = 0;
414
415       sd = __socket (AF_INET, SOCK_DGRAM, 0);
416       if (sd < 0)
417         return;
418
419       /* Now get list of interfaces.  Since we don't know how many
420          interfaces there are, we keep increasing the buffer size
421          until we have at least sizeof(struct ifreq) too many bytes.
422          That implies that the ioctl() return because it ran out of
423          interfaces, not memory */
424       size = 0;
425       ifs.ifc_buf = 0;
426       do
427         {
428           size += 4 * sizeof (struct ifreq);
429           ifs.ifc_buf = realloc (ifs.ifs_buf, size);
430           if (ifs.ifc_buf == NULL)
431             {
432               close (sd);
433               return;
434             }
435           ifs.ifc_len = size;
436           if (__ioctl (sd, SIOCGIFCONF, &ifs) < 0)
437             goto cleanup;
438         }
439       while (size - ifs.ifc_len < sizeof (struct ifreq));
440
441       num = ifs.ifc_len / sizeof (struct ifreq);
442
443       ifaddrs = malloc (num * sizeof (ifaddrs[0]));
444       if (!ifaddrs)
445         goto cleanup;
446
447       ifr = ifs.ifc_req;
448       for (i = 0; i < num; ++i)
449         {
450           if (ifr->ifr_addr.sa_family != AF_INET)
451             continue;
452           ifaddrs[num_ifs].addrtype = AF_INET;
453
454           memcpy (&ifaddrs[num_ifs].u.ipv4.addr,
455                   &((struct sockaddr_in *)ifr->ifr_addr)->sin_addr, 4);
456
457           if (__ioctl (sd, SIOCGIFNETMASK, if) < 0)
458             continue;
459           memcpy (&ifaddrs[num_ifs].u.ipv4.mask,
460                   ((struct sockaddr_in *)ifr->ifr_mask)->sin_addr, 4);
461
462           ++num_ifs;    /* now we're committed to this entry */
463         }
464       /* just keep enough memory to hold all the interfaces we want: */
465       ifaddrs = realloc (ifaddrs, num_ifs * sizeof (ifaddrs[0]));
466
467     cleanup:
468       close (sd);
469       free (ifs.ifc_buf);
470     }
471
472   if (num_ifs == 0)
473     return;
474
475   /* find an address for which we have a direct connection: */
476   for (i = 0; hp->h_addr_list[i]; ++i)
477     {
478       h_addr = (struct in_addr *) hp->h_addr_list[i];
479
480       for (j = 0; j < num_ifs; ++j)
481         {
482           if_addr    = ifaddrs[j].u.ipv4.addr;
483           if_netmask = ifaddrs[j].u.ipv4.mask;
484
485           if (((h_addr->s_addr ^ if_addr) & if_netmask) == 0)
486             {
487               void *tmp;
488
489               tmp = hp->h_addr_list[i];
490               hp->h_addr_list[i] = hp->h_addr_list[0];
491               hp->h_addr_list[0] = tmp;
492               return;
493             }
494         }
495     }
496 #endif /* defined(SIOCGIFCONF) && ... */
497 }
498
499
500 /* If HOSTNAME has a postfix matching any of the trimdomains, trim away
501    that postfix.  Notice that HOSTNAME is modified inplace.  Also, the
502    original code applied all trimdomains in order, meaning that the
503    same domainname could be trimmed multiple times.  I believe this
504    was unintentional.  */
505 void
506 _res_hconf_trim_domain (char *hostname)
507 {
508   size_t hostname_len, trim_len;
509   int i;
510
511   hostname_len = strlen (hostname);
512
513   for (i = 0; i < _res_hconf.num_trimdomains; ++i)
514     {
515       const char *trim = _res_hconf.trimdomain[i];
516
517       trim_len = strlen (trim);
518       if (hostname_len > trim_len
519           && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
520         {
521           hostname[hostname_len - trim_len] = '\0';
522           break;
523         }
524     }
525 }
526
527
528 /* Trim all hostnames/aliases in HP according to the trimdomain list.
529    Notice that HP is modified inplace!  */
530 void
531 _res_hconf_trim_domains (struct hostent *hp)
532 {
533   int i;
534
535   if (_res_hconf.num_trimdomains == 0)
536     return;
537
538   _res_hconf_trim_domain (hp->h_name);
539   for (i = 0; hp->h_aliases[i]; ++i)
540     _res_hconf_trim_domain (hp->h_aliases[i]);
541 }