Pretty print.
[kopensolaris-gnu/glibc.git] / resolv / res_hconf.c
1 /* Copyright (C) 1993, 1995, 1996, 1997, 1998 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   if (*str == '#') return;              /* skip line comment */
267
268   start = str;
269   str = skip_string (str);
270   len = str - start;
271
272   for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
273     {
274       if (strncasecmp (start, cmd[i].name, len) == 0
275           && strlen (cmd[i].name) == len)
276         {
277           c = &cmd[i];
278           break;
279         }
280     }
281   if (c == NULL)
282     {
283       fprintf (stderr, "%s: line %d: bad command `%s'\n",
284                fname, line_num, start);
285       return;
286     }
287
288   /* process args: */
289   str = skip_ws (str);
290   str = (*c->parse_args) (fname, line_num, str, c->arg);
291   if (!str)
292     return;
293
294   /* rest of line must contain white space or comment only: */
295   while (*str)
296     {
297       if (!isspace (*str)) {
298         if (*str != '#')
299           fprintf (stderr, "%s: line %d: ignoring trailing garbage `%s'\n",
300                    fname, line_num, str);
301         break;
302       }
303       ++str;
304     }
305 }
306
307
308 /* Initialize hconf datastructure by reading host.conf file and
309    environment variables.  */
310 void
311 _res_hconf_init (void)
312 {
313   const char *hconf_name;
314   int line_num = 0;
315   char buf[256], *end, *envval;
316   FILE *fp;
317
318   if (_res_hconf.initialized)
319     return;
320
321   memset (&_res_hconf, '\0', sizeof (_res_hconf));
322
323   hconf_name = getenv (ENV_HOSTCONF);
324   if (hconf_name == NULL)
325     hconf_name = _PATH_HOSTCONF;
326
327   fp = fopen (hconf_name, "r");
328   if (!fp)
329     /* make up something reasonable: */
330     _res_hconf.service[_res_hconf.num_services++] = SERVICE_BIND;
331   else
332     {
333       while (fgets_unlocked (buf, sizeof (buf), fp))
334         {
335           ++line_num;
336           end = strchr (buf, '\n');
337           if (end)
338             *end = '\0';
339           parse_line (hconf_name, line_num, buf);
340         }
341       fclose (fp);
342     }
343
344   envval = getenv (ENV_SERVORDER);
345   if (envval)
346     {
347       _res_hconf.num_services = 0;
348       arg_service_list (ENV_SERVORDER, 1, envval, 0);
349     }
350
351   envval = getenv (ENV_SPOOF);
352   if (envval)
353     arg_spoof (ENV_SPOOF, 1, envval, 0);
354
355   envval = getenv (ENV_MULTI);
356   if (envval)
357     arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
358
359   envval = getenv (ENV_REORDER);
360   if (envval)
361     arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
362
363   envval = getenv (ENV_TRIM_ADD);
364   if (envval)
365     arg_trimdomain_list (ENV_TRIM_ADD, 1, envval, 0);
366
367   envval = getenv (ENV_TRIM_OVERR);
368   if (envval)
369     {
370       _res_hconf.num_trimdomains = 0;
371       arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval, 0);
372     }
373
374   _res_hconf.initialized = 1;
375 }
376
377
378 /* Reorder addresses returned in a hostent such that the first address
379    is an address on the local subnet, if there is such an address.
380    Otherwise, nothing is changed.  */
381
382 void
383 _res_hconf_reorder_addrs (struct hostent *hp)
384 {
385 #if defined SIOCGIFCONF && defined SIOCGIFNETMASK
386   static int num_ifs = -1;      /* number of interfaces */
387   static struct netaddr
388   {
389     int addrtype;
390     union
391     {
392       struct
393       {
394         u_int32_t       addr;
395         u_int32_t       mask;
396       } ipv4
397     } u;
398   } *ifaddrs;
399
400   if (hp->h_addrtype != AF_INET)
401     return;     /* can't deal with anything but IPv4 for now... */
402
403   if (num_ifs <= 0)
404     {
405       struct ifconf ifs;
406       struct ifreq *ifr;
407       size_t size, num;
408       int sd;
409
410       /* initialize interface table: */
411
412       num_ifs = 0;
413
414       sd = __socket (AF_INET, SOCK_DGRAM, 0);
415       if (sd < 0)
416         return;
417
418       /* Now get list of interfaces.  Since we don't know how many
419          interfaces there are, we keep increasing the buffer size
420          until we have at least sizeof(struct ifreq) too many bytes.
421          That implies that the ioctl() return because it ran out of
422          interfaces, not memory */
423       size = 0;
424       ifs.ifc_buf = 0;
425       do
426         {
427           size += 4 * sizeof (struct ifreq);
428           ifs.ifc_buf = realloc (ifs.ifs_buf, size);
429           if (ifs.ifc_buf == NULL)
430             {
431               close (sd);
432               return;
433             }
434           ifs.ifc_len = size;
435           if (__ioctl (sd, SIOCGIFCONF, &ifs) < 0)
436             goto cleanup;
437         }
438       while (size - ifs.ifc_len < sizeof (struct ifreq));
439
440       num = ifs.ifc_len / sizeof (struct ifreq);
441
442       ifaddrs = malloc (num * sizeof (ifaddrs[0]));
443       if (!ifaddrs)
444         goto cleanup;
445
446       ifr = ifs.ifc_req;
447       for (i = 0; i < num; ++i)
448         {
449           if (ifr->ifr_addr.sa_family != AF_INET)
450             continue;
451           ifaddrs[num_ifs].addrtype = AF_INET;
452
453           memcpy (&ifaddrs[num_ifs].u.ipv4.addr,
454                   &((struct sockaddr_in *)ifr->ifr_addr)->sin_addr, 4);
455
456           if (__ioctl (sd, SIOCGIFNETMASK, if) < 0)
457             continue;
458           memcpy (&ifaddrs[num_ifs].u.ipv4.mask,
459                   ((struct sockaddr_in *)ifr->ifr_mask)->sin_addr, 4);
460
461           ++num_ifs;    /* now we're committed to this entry */
462         }
463       /* just keep enough memory to hold all the interfaces we want: */
464       ifaddrs = realloc (ifaddrs, num_ifs * sizeof (ifaddrs[0]));
465
466     cleanup:
467       close (sd);
468       free (ifs.ifc_buf);
469     }
470
471   if (num_ifs == 0)
472     return;
473
474   /* find an address for which we have a direct connection: */
475   for (i = 0; hp->h_addr_list[i]; ++i)
476     {
477       h_addr = (struct in_addr *) hp->h_addr_list[i];
478
479       for (j = 0; j < num_ifs; ++j)
480         {
481           if_addr    = ifaddrs[j].u.ipv4.addr;
482           if_netmask = ifaddrs[j].u.ipv4.mask;
483
484           if (((h_addr->s_addr ^ if_addr) & if_netmask) == 0)
485             {
486               void *tmp;
487
488               tmp = hp->h_addr_list[i];
489               hp->h_addr_list[i] = hp->h_addr_list[0];
490               hp->h_addr_list[0] = tmp;
491               return;
492             }
493         }
494     }
495 #endif /* defined(SIOCGIFCONF) && ... */
496 }
497
498
499 /* If HOSTNAME has a postfix matching any of the trimdomains, trim away
500    that postfix.  Notice that HOSTNAME is modified inplace.  Also, the
501    original code applied all trimdomains in order, meaning that the
502    same domainname could be trimmed multiple times.  I believe this
503    was unintentional.  */
504 void
505 _res_hconf_trim_domain (char *hostname)
506 {
507   size_t hostname_len, trim_len;
508   int i;
509
510   hostname_len = strlen (hostname);
511
512   for (i = 0; i < _res_hconf.num_trimdomains; ++i)
513     {
514       const char *trim = _res_hconf.trimdomain[i];
515
516       trim_len = strlen (trim);
517       if (hostname_len > trim_len
518           && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
519         {
520           hostname[hostname_len - trim_len] = '\0';
521           break;
522         }
523     }
524 }
525
526
527 /* Trim all hostnames/aliases in HP according to the trimdomain list.
528    Notice that HP is modified inplace!  */
529 void
530 _res_hconf_trim_domains (struct hostent *hp)
531 {
532   int i;
533
534   if (_res_hconf.num_trimdomains == 0)
535     return;
536
537   _res_hconf_trim_domain (hp->h_name);
538   for (i = 0; hp->h_aliases[i]; ++i)
539     _res_hconf_trim_domain (hp->h_aliases[i]);
540 }