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