Update to LGPL v2.1.
[jlayton/glibc.git] / resolv / res_hconf.c
1 /* Copyright (C) 1993, 95, 96, 97, 98, 99, 2000 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 <memory.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <net/if.h>
39 #include <sys/ioctl.h>
40 #include <unistd.h>
41 #include <netinet/in.h>
42 #include <bits/libc-lock.h>
43 #include "ifreq.h"
44 #include "res_hconf.h"
45
46 #define _PATH_HOSTCONF  "/etc/host.conf"
47
48 /* Environment vars that all user to override default behavior:  */
49 #define ENV_HOSTCONF    "RESOLV_HOST_CONF"
50 #define ENV_SERVORDER   "RESOLV_SERV_ORDER"
51 #define ENV_SPOOF       "RESOLV_SPOOF_CHECK"
52 #define ENV_TRIM_OVERR  "RESOLV_OVERRIDE_TRIM_DOMAINS"
53 #define ENV_TRIM_ADD    "RESOLV_ADD_TRIM_DOMAINS"
54 #define ENV_MULTI       "RESOLV_MULTI"
55 #define ENV_REORDER     "RESOLV_REORDER"
56
57 static const char *arg_service_list (const char *, int, const char *,
58                                      unsigned int);
59 static const char *arg_trimdomain_list (const char *, int, const char *,
60                                         unsigned int);
61 static const char *arg_spoof (const char *, int, const char *, unsigned int);
62 static const char *arg_bool (const char *, int, const char *, unsigned int);
63
64 static struct cmd
65 {
66   const char *name;
67   const char *(*parse_args) (const char * filename, int line_num,
68                              const char * args, unsigned int arg);
69   unsigned int arg;
70 } cmd[] =
71 {
72   {"order",             arg_service_list,       0},
73   {"trim",              arg_trimdomain_list,    0},
74   {"spoof",             arg_spoof,              0},
75   {"multi",             arg_bool,               HCONF_FLAG_MULTI},
76   {"nospoof",           arg_bool,               HCONF_FLAG_SPOOF},
77   {"spoofalert",        arg_bool,               HCONF_FLAG_SPOOFALERT},
78   {"reorder",           arg_bool,               HCONF_FLAG_REORDER}
79 };
80
81 /* Structure containing the state.  */
82 struct hconf _res_hconf;
83
84 /* Skip white space.  */
85 static const char *
86 skip_ws (const char *str)
87 {
88   while (isspace (*str)) ++str;
89   return str;
90 }
91
92
93 /* Skip until whitespace, comma, end of line, or comment character.  */
94 static const char *
95 skip_string (const char *str)
96 {
97   while (*str && !isspace (*str) && *str != '#' && *str != ',')
98     ++str;
99   return str;
100 }
101
102
103 static const char *
104 arg_service_list (const char *fname, int line_num, const char *args,
105                   unsigned int arg)
106 {
107   enum Name_Service service;
108   const char *start;
109   size_t len;
110   int i;
111   static struct
112   {
113     const char * name;
114     enum Name_Service service;
115   } svcs[] =
116     {
117       {"bind",  SERVICE_BIND},
118       {"hosts", SERVICE_HOSTS},
119       {"nis",   SERVICE_NIS},
120     };
121
122   do
123     {
124       start = args;
125       args = skip_string (args);
126       len = args - start;
127
128       service = SERVICE_NONE;
129       for (i = 0; i < sizeof (svcs) / sizeof (svcs[0]); ++i)
130         {
131           if (__strncasecmp (start, svcs[i].name, len) == 0
132               && len == strlen (svcs[i].name))
133           {
134             service = svcs[i].service;
135             break;
136           }
137       }
138       if (service == SERVICE_NONE)
139         {
140           fprintf (stderr, "%s: line %d: expected service, found `%s'\n",
141                    fname, line_num, start);
142           return 0;
143         }
144       if (_res_hconf.num_services >= SERVICE_MAX)
145         {
146           fprintf (stderr, "%s: line %d: cannot specify more than %d services",
147                    fname, line_num, SERVICE_MAX);
148           return 0;
149         }
150       _res_hconf.service[_res_hconf.num_services++] = service;
151
152       args = skip_ws (args);
153       switch (*args)
154         {
155         case ',':
156         case ';':
157         case ':':
158           args = skip_ws (++args);
159           if (!*args || *args == '#')
160             {
161               fprintf (stderr,
162                        "%s: line %d: list delimiter not followed by keyword",
163                        fname, line_num);
164               return 0;
165             }
166         default:
167           break;
168         }
169     }
170   while (*args && *args != '#');
171   return args;
172 }
173
174
175 static const char *
176 arg_trimdomain_list (const char *fname, int line_num, const char *args,
177                      unsigned int flag)
178 {
179   const char * start;
180   size_t len;
181
182   do
183     {
184       start = args;
185       args = skip_string (args);
186       len = args - start;
187
188       if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
189         {
190           fprintf (stderr,
191                    "%s: line %d: cannot specify more than %d trim domains",
192                    fname, line_num, TRIMDOMAINS_MAX);
193           return 0;
194         }
195       _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
196         __strndup (start, len);
197       args = skip_ws (args);
198       switch (*args)
199         {
200         case ',': case ';': case ':':
201           args = skip_ws (++args);
202           if (!*args || *args == '#')
203             {
204               fprintf (stderr,
205                        "%s: line %d: list delimiter not followed by domain",
206                        fname, line_num);
207               return 0;
208             }
209         default:
210           break;
211         }
212     }
213   while (*args && *args != '#');
214   return args;
215 }
216
217
218 static const char *
219 arg_spoof (const char *fname, int line_num, const char *args, unsigned flag)
220 {
221   const char *start = args;
222   size_t len;
223
224   args = skip_string (args);
225   len = args - start;
226
227   if (len == 3 && __strncasecmp (start, "off", len) == 0)
228     _res_hconf.flags &= ~(HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
229   else
230     {
231       _res_hconf.flags |= (HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
232       if ((len == 6 && __strncasecmp (start, "nowarn", len) == 0)
233           || !(len == 4 && __strncasecmp (start, "warn", len) == 0))
234         _res_hconf.flags &= ~HCONF_FLAG_SPOOFALERT;
235     }
236   return args;
237 }
238
239
240 static const char *
241 arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
242 {
243   if (__strncasecmp (args, "on", 2) == 0)
244     {
245       args += 2;
246       _res_hconf.flags |= flag;
247     }
248   else if (__strncasecmp (args, "off", 3) == 0)
249     {
250       args += 3;
251       _res_hconf.flags &= ~flag;
252     }
253   else
254     {
255       fprintf (stderr, "%s: line %d: expected `on' or `off', found `%s'\n",
256                fname, line_num, args);
257       return 0;
258     }
259   return args;
260 }
261
262
263 static void
264 parse_line (const char *fname, int line_num, const char *str)
265 {
266   const char *start;
267   struct cmd *c = 0;
268   size_t len;
269   int i;
270
271   str = skip_ws (str);
272
273   /* skip line comment and empty lines: */
274   if (*str == '\0' || *str == '#') return;
275
276   start = str;
277   str = skip_string (str);
278   len = str - start;
279
280   for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
281     {
282       if (__strncasecmp (start, cmd[i].name, len) == 0
283           && strlen (cmd[i].name) == len)
284         {
285           c = &cmd[i];
286           break;
287         }
288     }
289   if (c == NULL)
290     {
291       fprintf (stderr, "%s: line %d: bad command `%s'\n",
292                fname, line_num, start);
293       return;
294     }
295
296   /* process args: */
297   str = skip_ws (str);
298   str = (*c->parse_args) (fname, line_num, str, c->arg);
299   if (!str)
300     return;
301
302   /* rest of line must contain white space or comment only: */
303   while (*str)
304     {
305       if (!isspace (*str)) {
306         if (*str != '#')
307           fprintf (stderr, "%s: line %d: ignoring trailing garbage `%s'\n",
308                    fname, line_num, str);
309         break;
310       }
311       ++str;
312     }
313 }
314
315
316 /* Initialize hconf datastructure by reading host.conf file and
317    environment variables.  */
318 void
319 _res_hconf_init (void)
320 {
321   const char *hconf_name;
322   int line_num = 0;
323   char buf[256], *envval;
324   FILE *fp;
325
326   if (_res_hconf.initialized)
327     return;
328
329   memset (&_res_hconf, '\0', sizeof (_res_hconf));
330
331   hconf_name = getenv (ENV_HOSTCONF);
332   if (hconf_name == NULL)
333     hconf_name = _PATH_HOSTCONF;
334
335   fp = fopen (hconf_name, "r");
336   if (!fp)
337     /* make up something reasonable: */
338     _res_hconf.service[_res_hconf.num_services++] = SERVICE_BIND;
339   else
340     {
341       while (fgets_unlocked (buf, sizeof (buf), fp))
342         {
343           ++line_num;
344           *__strchrnul (buf, '\n') = '\0';
345           parse_line (hconf_name, line_num, buf);
346         }
347       fclose (fp);
348     }
349
350   envval = getenv (ENV_SERVORDER);
351   if (envval)
352     {
353       _res_hconf.num_services = 0;
354       arg_service_list (ENV_SERVORDER, 1, envval, 0);
355     }
356
357   envval = getenv (ENV_SPOOF);
358   if (envval)
359     arg_spoof (ENV_SPOOF, 1, envval, 0);
360
361   envval = getenv (ENV_MULTI);
362   if (envval)
363     arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
364
365   envval = getenv (ENV_REORDER);
366   if (envval)
367     arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
368
369   envval = getenv (ENV_TRIM_ADD);
370   if (envval)
371     arg_trimdomain_list (ENV_TRIM_ADD, 1, envval, 0);
372
373   envval = getenv (ENV_TRIM_OVERR);
374   if (envval)
375     {
376       _res_hconf.num_trimdomains = 0;
377       arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval, 0);
378     }
379
380   _res_hconf.initialized = 1;
381 }
382
383
384 /* List of known interfaces.  */
385 static struct netaddr
386 {
387   int addrtype;
388   union
389   {
390     struct
391     {
392       u_int32_t addr;
393       u_int32_t mask;
394     } ipv4;
395   } u;
396 } *ifaddrs;
397
398 /* We need to protect the dynamic buffer handling.  */
399 __libc_lock_define_initialized (static, lock);
400
401 /* Reorder addresses returned in a hostent such that the first address
402    is an address on the local subnet, if there is such an address.
403    Otherwise, nothing is changed.
404
405    Note that this function currently only handles IPv4 addresses.  */
406
407 void
408 _res_hconf_reorder_addrs (struct hostent *hp)
409 {
410 #if defined SIOCGIFCONF && defined SIOCGIFNETMASK
411   int i, j;
412   /* Number of interfaces.  */
413   static int num_ifs = -1;
414
415   /* Only reorder if we're supposed to.  */
416   if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
417     return;
418
419   /* Can't deal with anything but IPv4 for now...  */
420   if (hp->h_addrtype != AF_INET)
421     return;
422
423   if (num_ifs <= 0)
424     {
425       struct ifreq *ifr, *cur_ifr;
426       int sd, num, i;
427       /* Save errno.  */
428       int save = errno;
429
430       /* Initialize interface table.  */
431
432       num_ifs = 0;
433
434       sd = __opensock ();
435       if (sd < 0)
436         return;
437
438       /* Get lock.  */
439       __libc_lock_lock (lock);
440
441       /* Get a list of interfaces.  */
442       __ifreq (&ifr, &num);
443       if (!ifr)
444         goto cleanup;
445
446       ifaddrs = malloc (num * sizeof (ifaddrs[0]));
447       if (!ifaddrs)
448         goto cleanup1;
449
450       /* Copy usable interfaces in ifaddrs structure.  */
451       for (cur_ifr = ifr, i = 0;  i < num; ++cur_ifr, ++i)
452         {
453           if (cur_ifr->ifr_addr.sa_family != AF_INET)
454             continue;
455
456           ifaddrs[num_ifs].addrtype = AF_INET;
457           ifaddrs[num_ifs].u.ipv4.addr =
458             ((struct sockaddr_in *) &cur_ifr->ifr_addr)->sin_addr.s_addr;
459
460           if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
461             continue;
462
463           ifaddrs[num_ifs].u.ipv4.mask =
464             ((struct sockaddr_in *) &cur_ifr->ifr_netmask)->sin_addr.s_addr;
465
466           /* Now we're committed to this entry.  */
467           ++num_ifs;
468         }
469       /* Just keep enough memory to hold all the interfaces we want.  */
470       ifaddrs = realloc (ifaddrs, num_ifs * sizeof (ifaddrs[0]));
471
472     cleanup1:
473       __if_freereq (ifr);
474
475     cleanup:
476       /* Release lock, preserve error value, and close socket.  */
477       save = errno;
478       __libc_lock_unlock (lock);
479       __close (sd);
480     }
481
482   if (num_ifs == 0)
483     return;
484
485   /* Find an address for which we have a direct connection.  */
486   for (i = 0; hp->h_addr_list[i]; ++i)
487     {
488       struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
489
490       for (j = 0; j < num_ifs; ++j)
491         {
492           u_int32_t if_addr    = ifaddrs[j].u.ipv4.addr;
493           u_int32_t if_netmask = ifaddrs[j].u.ipv4.mask;
494
495           if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
496             {
497               void *tmp;
498
499               tmp = hp->h_addr_list[i];
500               hp->h_addr_list[i] = hp->h_addr_list[0];
501               hp->h_addr_list[0] = tmp;
502               return;
503             }
504         }
505     }
506 #endif /* defined(SIOCGIFCONF) && ... */
507 }
508
509
510 /* If HOSTNAME has a postfix matching any of the trimdomains, trim away
511    that postfix.  Notice that HOSTNAME is modified inplace.  Also, the
512    original code applied all trimdomains in order, meaning that the
513    same domainname could be trimmed multiple times.  I believe this
514    was unintentional.  */
515 void
516 _res_hconf_trim_domain (char *hostname)
517 {
518   size_t hostname_len, trim_len;
519   int i;
520
521   hostname_len = strlen (hostname);
522
523   for (i = 0; i < _res_hconf.num_trimdomains; ++i)
524     {
525       const char *trim = _res_hconf.trimdomain[i];
526
527       trim_len = strlen (trim);
528       if (hostname_len > trim_len
529           && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
530         {
531           hostname[hostname_len - trim_len] = '\0';
532           break;
533         }
534     }
535 }
536
537
538 /* Trim all hostnames/aliases in HP according to the trimdomain list.
539    Notice that HP is modified inplace!  */
540 void
541 _res_hconf_trim_domains (struct hostent *hp)
542 {
543   int i;
544
545   if (_res_hconf.num_trimdomains == 0)
546     return;
547
548   _res_hconf_trim_domain (hp->h_name);
549   for (i = 0; hp->h_aliases[i]; ++i)
550     _res_hconf_trim_domain (hp->h_aliases[i]);
551 }
552
553
554 /* Free all resources if necessary.  */
555 static void __attribute__ ((unused))
556 free_mem (void)
557 {
558   free (ifaddrs);
559 }
560
561 text_set_element (__libc_subfreeres, free_mem);