Sat Jun 15 18:13:43 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
[kopensolaris-gnu/glibc.git] / resolv / res_hconf.c
1 /* Copyright (C) 1993, 1995, 1996 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
16 not, write to the Free Software Foundation, Inc., 675 Mass Ave,
17 Cambridge, MA 02139, 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
36 #include "res_hconf.h"
37
38 #define _PATH_HOSTCONF  "/etc/host.conf"
39
40 /* Environment vars that all user to override default behavior:  */
41 #define ENV_HOSTCONF    "RESOLV_HOST_CONF"
42 #define ENV_SERVORDER   "RESOLV_SERV_ORDER"
43 #define ENV_SPOOF       "RESOLV_SPOOF_CHECK"
44 #define ENV_TRIM_OVERR  "RESOLV_OVERRIDE_TRIM_DOMAINS"
45 #define ENV_TRIM_ADD    "RESOLV_ADD_TRIM_DOMAINS"
46 #define ENV_MULTI       "RESOLV_MULTI"
47 #define ENV_REORDER     "RESOLV_REORDER"
48
49 static const char * arg_service_list (const char *, int, const char *,
50                                       unsigned);
51 static const char * arg_trimdomain_list (const char *, int, const char *,
52                                          unsigned);
53 static const char * arg_spoof (const char *, int, const char *, unsigned);
54 static const char * arg_bool (const char *, int, const char *, unsigned);
55
56 static struct cmd {
57   const char *  name;
58   const char *  (*parse_args)(const char * filename, int line_num,
59                               const char * args, unsigned arg);
60   unsigned      arg;;
61 } cmd[] = {
62   {"order",             arg_service_list,       0},
63   {"trim",              arg_trimdomain_list,    0},
64   {"spoof",             arg_spoof,              0},
65   {"multi",             arg_bool,               HCONF_FLAG_MULTI},
66   {"nospoof",           arg_bool,               HCONF_FLAG_SPOOF},
67   {"spoofalert",        arg_bool,               HCONF_FLAG_SPOOFALERT},
68   {"reorder",           arg_bool,               HCONF_FLAG_REORDER}
69 };
70
71
72 /*
73  * Why isn't this in stdlib?
74  */
75 char *
76 strndup (const char * s, size_t n)
77 {
78   char * retval;
79
80   retval = malloc (n + 1);
81   if (!retval)
82     return retval;
83
84   memcpy (retval, s, n);
85   retval[n] = '\0';             /* ensure return value is terminated */
86   return retval;
87 }
88
89
90 /* Skip white space.  */
91 static const char *
92 skip_ws (const char * str)
93 {
94   while (isspace (*str)) ++str;
95   return str;
96 }
97
98
99 /* Skip until whitespace, comma, end of line, or comment character.  */
100 static const char *
101 skip_string (const char * str)
102 {
103   while (*str && !isspace (*str) && *str != '#' && *str != ',') ++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 arg)
111 {
112   enum Name_Service service;
113   const char * start;
114   size_t len;
115   int i;
116   static struct {
117     const char *        name;
118     enum Name_Service   service;
119   } svcs[] = {
120     {"bind",    SERVICE_BIND},
121     {"hosts",   SERVICE_HOSTS},
122     {"nis",     SERVICE_NIS},
123   };
124
125   do
126     {
127       start = args;
128       args = skip_string (args);
129       len = args - start;
130
131       service = SERVICE_NONE;
132       for (i = 0; i < sizeof (svcs) / sizeof (svcs[0]); ++i)
133         {
134           if (strncasecmp (start, svcs[i].name, len) == 0
135               && len == strlen (svcs[i].name))
136           {
137             service = svcs[i].service;
138             break;
139           }
140       }
141       if (service == SERVICE_NONE)
142         {
143           fprintf (stderr, "%s: line %d: expected service, found `%s'\n",
144                    fname, line_num, start);
145           return 0;
146         }
147       if (_res_hconf.num_services >= SERVICE_MAX)
148         {
149           fprintf (stderr, "%s: line %d: cannot specify more than %d services",
150                    fname, line_num, SERVICE_MAX);
151           return 0;
152         }
153       _res_hconf.service[_res_hconf.num_services++] = service;
154
155       args = skip_ws (args);
156       switch (*args)
157         {
158         case ',': case ';': case ':':
159           args = skip_ws (++args);
160           if (!*args || *args == '#')
161             {
162               fprintf (stderr,
163                        "%s: line %d: list delimiter not followed by keyword",
164                        fname, line_num);
165               return 0;
166             }
167         default:
168           break;
169         }
170     }
171   while (*args && *args != '#');
172   return args;
173 }
174
175
176 static const char *
177 arg_trimdomain_list (const char * fname, int line_num, const char * args,
178                      unsigned flag)
179 {
180   const char * start;
181   size_t len;
182
183   do
184     {
185       start = args;
186       args = skip_string (args);
187       len = args - start;
188
189       if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
190         {
191           fprintf (stderr,
192                    "%s: line %d: cannot specify more than %d trim domains",
193                    fname, line_num, TRIMDOMAINS_MAX);
194           return 0;
195         }
196       _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
197         strndup (start, len);
198       args = skip_ws (args);
199       switch (*args)
200         {
201         case ',': case ';': case ':':
202           args = skip_ws (++args);
203           if (!*args || *args == '#')
204             {
205               fprintf (stderr,
206                        "%s: line %d: list delimiter not followed by domain",
207                        fname, line_num);
208               return 0;
209             }
210         default:
211           break;
212         }
213     }
214   while (*args && *args != '#');
215   return args;
216 }
217
218
219 static const char *
220 arg_spoof (const char * fname, int line_num, const char * args, unsigned flag)
221 {
222   const char * start = args;
223   size_t len;
224
225   args = skip_string (args);
226   len = args - start;
227
228   if (len == 3 && strncasecmp (start, "off", len) == 0)
229     _res_hconf.flags &= ~(HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
230   else
231     {
232       _res_hconf.flags |= (HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
233       if ((len == 6 && strncasecmp (start, "nowarn", len) == 0)
234           || !(len == 4 && strncasecmp (start, "warn", len) == 0))
235         _res_hconf.flags &= ~HCONF_FLAG_SPOOFALERT;
236     }
237   return args;
238 }
239
240
241 static const char *
242 arg_bool (const char * fname, int line_num, const char * args, unsigned flag)
243 {
244   if (strncasecmp (args, "on", 2) == 0)
245     {
246       args += 2;
247       _res_hconf.flags |= flag;
248     }
249   else if (strncasecmp (args, "off", 3) == 0)
250     {
251       args += 3;
252       _res_hconf.flags &= ~flag;
253     }
254   else
255     {
256       fprintf (stderr, "%s: line %d: expected `on' or `off', found `%s'\n",
257                fname, line_num, args);
258       return 0;
259     }
260   return args;
261 }
262
263
264 static void
265 parse_line (const char * fname, int line_num, const char * str)
266 {
267   const char * start;
268   struct cmd * c = 0;
269   size_t len;
270   int i;
271
272   str = skip_ws (str);
273
274   if (*str == '#') return;              /* skip line comment */
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)
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], * end, * envval;
324   FILE * fp;
325
326   memset (&_res_hconf, 0, sizeof (_res_hconf));
327
328   hconf_name = getenv (ENV_HOSTCONF);
329   if (!hconf_name)
330     hconf_name = _PATH_HOSTCONF;
331
332   fp = fopen (hconf_name, "r");
333   if (!fp)
334     /* make up something reasonable: */
335     _res_hconf.service[_res_hconf.num_services++] = SERVICE_BIND;
336   else
337     {
338       while (fgets (buf, sizeof (buf), fp))
339         {
340           ++line_num;
341           end = strchr (buf, '\n');
342           if (end)
343             *end = '\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
380
381 /* Reorder addresses returned in a hostent such that the first address
382    is an address on the local subnet, if there is such an address.
383    Otherwise, nothing is changed.  */
384
385 void
386 _res_hconf_reorder_addrs (struct hostent * hp)
387 {
388 #if defined (SIOCGIFCONF) && defined (SIOCGIFNETMASK)
389   static int num_ifs = -1;      /* number of interfaces */
390   static struct netaddr {
391     int addrtype;
392     union {
393       struct {
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         size += 4 * sizeof (struct ifreq);
427         ifs.ifc_buf = realloc (ifs.ifs_buf, size);
428         if (!ifs.ifc_buf)
429           {
430             close (sd);
431             return;
432           }
433         ifs.ifc_len = size;
434         if (ioctl (sd, SIOCGIFCONF, &ifs) < 0)
435           goto cleanup;
436       } while (size - ifs.ifc_len < sizeof (struct ifreq));
437
438       num = ifs.ifc_len / sizeof (struct ifreq);
439
440       ifaddrs = malloc (num * sizeof (ifaddrs[0]));
441       if (!ifaddrs)
442         goto cleanup;
443
444       ifr = ifs.ifc_req;
445       for (i = 0; i < num; ++i) {
446         if (ifr->ifr_addr.sa_family != AF_INET)
447           continue;
448         ifaddrs[num_ifs].addrtype = AF_INET;
449
450         memcpy (&ifaddrs[num_ifs].u.ipv4.addr,
451                 &((struct sockaddr_in *)ifr->ifr_addr)->sin_addr, 4);
452
453         if (ioctl (sd, SIOCGIFNETMASK, if) < 0)
454           continue;
455         memcpy (&ifaddrs[num_ifs].u.ipv4.mask,
456                 ((struct sockaddr_in *)ifr->ifr_mask)->sin_addr, 4);
457
458         ++num_ifs;      /* now we're committed to this entry */
459       }
460       /* just keep enough memory to hold all the interfaces we want: */
461       ifaddrs = realloc (ifaddrs, num_ifs * sizeof (ifaddrs[0]));
462
463     cleanup:
464       close (sd);
465       free (ifs.ifc_buf);
466     }
467
468   if (num_ifs == 0)
469     return;
470
471   /* find an address for which we have a direct connection: */
472   for (i = 0; hp->h_addr_list[i]; ++i)
473     {
474       h_addr = (struct in_addr *) hp->h_addr_list[i];
475
476       for (j = 0; j < num_ifs; ++j)
477         {
478           if_addr    = ifaddrs[j].u.ipv4.addr;
479           if_netmask = ifaddrs[j].u.ipv4.mask;
480
481           if (((h_addr->s_addr ^ if_addr) & if_netmask) == 0)
482             {
483               void * tmp;
484
485               tmp                = hp->h_addr_list[i];
486               hp->h_addr_list[i] = hp->h_addr_list[0];
487               hp->h_addr_list[0] = tmp;
488               return;
489             }
490         }
491     }
492 #endif /* defined(SIOCGIFCONF) && ... */
493 }
494
495
496 /* If HOSTNAME has a postfix matching any of the trimdomains, trim away
497    that postfix.  Notice that HOSTNAME is modified inplace.  Also, the
498    original code applied all trimdomains in order, meaning that the
499    same domainname could be trimmed multiple times.  I believe this
500    was unintentional.  */
501 void
502 _res_hconf_trim_domain (char * hostname)
503 {
504   size_t hostname_len, trim_len;
505   int i;
506
507   hostname_len = strlen(hostname);
508
509   for (i = 0; i < _res_hconf.num_trimdomains; ++i)
510     {
511       const char * trim = _res_hconf.trimdomain[i];
512
513       trim_len = strlen(trim);
514       if (hostname_len > trim_len
515           && strcasecmp(&hostname[hostname_len - trim_len], trim) == 0)
516         {
517           hostname[hostname_len - trim_len] = '\0';
518           break;
519         }
520     }
521 }
522
523
524 /* Trim all hostnames/aliases in HP according to the trimdomain list.
525    Notice that HP is modified inplace!  */
526 void
527 _res_hconf_trim_domains (struct hostent * hp)
528 {
529   int i;
530
531   if (_res_hconf.num_trimdomains == 0)
532     return;
533
534   _res_hconf_trim_domain (hp->h_name);
535   for (i = 0; hp->h_aliases[i]; ++i)
536     _res_hconf_trim_domain (hp->h_aliases[i]);
537 }
538
539
540 #if 0
541
542 struct hostent *
543 _hconf_gethostent (void)
544 {
545 }
546
547
548 struct hostent *
549 _hconf_gethostbyname (const char * name)
550 {
551
552 }
553
554
555 struct hostent *
556 _hconf_gethostbyaddr (const char * addr, int len, int type)
557 {
558 }
559
560
561 struct hostent *
562 _hconf_gethtbyname (const char * name)
563 {
564 }
565
566 #endif