]> git.ipfire.org Git - thirdparty/glibc.git/blame - resolv/res_hconf.c
Fix incorrect double-checked locking related to _res_hconf.initialized.
[thirdparty/glibc.git] / resolv / res_hconf.c
CommitLineData
f7a9f785 1/* Copyright (C) 1993-2016 Free Software Foundation, Inc.
41bdb6e2 2 This file is part of the GNU C Library.
fa0bc87c
RM
3 Contributed by David Mosberger (davidm@azstarnet.com).
4
c84142e8 5 The GNU C Library is free software; you can redistribute it and/or
41bdb6e2
AJ
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.
fa0bc87c 9
c84142e8
UD
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
41bdb6e2 13 Lesser General Public License for more details.
fa0bc87c 14
41bdb6e2 15 You should have received a copy of the GNU Lesser General Public
59ba27a6
PE
16 License along with the GNU C Library; if not, see
17 <http://www.gnu.org/licenses/>. */
fa0bc87c
RM
18
19/* This file provides a Linux /etc/host.conf compatible front end to
f720d3d2
UD
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:
fa0bc87c
RM
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*/
f720d3d2 30
9be31a51 31#include <assert.h>
f720d3d2 32#include <errno.h>
fa0bc87c 33#include <ctype.h>
51028f34 34#include <libintl.h>
fa0bc87c
RM
35#include <memory.h>
36#include <stdio.h>
2706ee38 37#include <stdio_ext.h>
fa0bc87c
RM
38#include <stdlib.h>
39#include <string.h>
5edb9387 40#include <net/if.h>
f720d3d2
UD
41#include <sys/ioctl.h>
42#include <unistd.h>
43#include <netinet/in.h>
ec999b8e 44#include <libc-lock.h>
f720d3d2 45#include "ifreq.h"
fa0bc87c 46#include "res_hconf.h"
3ce1f295 47#include <wchar.h>
f463c7b1 48#include <atomic.h>
fa0bc87c 49
84e5e756
JM
50#if IS_IN (libc)
51# define fgets_unlocked __fgets_unlocked
52#endif
53
fa0bc87c
RM
54#define _PATH_HOSTCONF "/etc/host.conf"
55
56/* Environment vars that all user to override default behavior: */
57#define ENV_HOSTCONF "RESOLV_HOST_CONF"
fa0bc87c
RM
58#define ENV_SPOOF "RESOLV_SPOOF_CHECK"
59#define ENV_TRIM_OVERR "RESOLV_OVERRIDE_TRIM_DOMAINS"
60#define ENV_TRIM_ADD "RESOLV_ADD_TRIM_DOMAINS"
61#define ENV_MULTI "RESOLV_MULTI"
62#define ENV_REORDER "RESOLV_REORDER"
63
bc054367
UD
64enum parse_cbs
65 {
66 CB_none,
67 CB_arg_trimdomain_list,
68 CB_arg_spoof,
69 CB_arg_bool
70 };
5edb9387 71
545f1b11 72static const struct cmd
5edb9387 73{
bc054367
UD
74 const char name[11];
75 uint8_t cb;
5edb9387
UD
76 unsigned int arg;
77} cmd[] =
78{
bc054367
UD
79 {"order", CB_none, 0},
80 {"trim", CB_arg_trimdomain_list, 0},
81 {"spoof", CB_arg_spoof, 0},
82 {"multi", CB_arg_bool, HCONF_FLAG_MULTI},
83 {"nospoof", CB_arg_bool, HCONF_FLAG_SPOOF},
84 {"spoofalert", CB_arg_bool, HCONF_FLAG_SPOOFALERT},
85 {"reorder", CB_arg_bool, HCONF_FLAG_REORDER}
fa0bc87c
RM
86};
87
5edb9387
UD
88/* Structure containing the state. */
89struct hconf _res_hconf;
fa0bc87c 90
fa0bc87c
RM
91/* Skip white space. */
92static const char *
5edb9387 93skip_ws (const char *str)
fa0bc87c
RM
94{
95 while (isspace (*str)) ++str;
96 return str;
97}
98
99
100/* Skip until whitespace, comma, end of line, or comment character. */
101static const char *
5edb9387 102skip_string (const char *str)
fa0bc87c 103{
5edb9387
UD
104 while (*str && !isspace (*str) && *str != '#' && *str != ',')
105 ++str;
fa0bc87c
RM
106 return str;
107}
108
109
a334319f 110static const char *
bc054367 111arg_trimdomain_list (const char *fname, int line_num, const char *args)
fa0bc87c
RM
112{
113 const char * start;
114 size_t len;
115
116 do
117 {
118 start = args;
119 args = skip_string (args);
120 len = args - start;
121
122 if (_res_hconf.num_trimdomains >= TRIMDOMAINS_MAX)
123 {
51028f34
UD
124 char *buf;
125
322861e8 126 if (__asprintf (&buf, _("\
51028f34 127%s: line %d: cannot specify more than %d trim domains"),
322861e8
UD
128 fname, line_num, TRIMDOMAINS_MAX) < 0)
129 return 0;
51028f34 130
8a259a23 131 __fxprintf (NULL, "%s", buf);
51028f34 132
df6f8969 133 free (buf);
fa0bc87c
RM
134 return 0;
135 }
136 _res_hconf.trimdomain[_res_hconf.num_trimdomains++] =
5edb9387 137 __strndup (start, len);
fa0bc87c
RM
138 args = skip_ws (args);
139 switch (*args)
140 {
141 case ',': case ';': case ':':
142 args = skip_ws (++args);
143 if (!*args || *args == '#')
144 {
51028f34
UD
145 char *buf;
146
322861e8 147 if (__asprintf (&buf, _("\
51028f34 148%s: line %d: list delimiter not followed by domain"),
322861e8
UD
149 fname, line_num) < 0)
150 return 0;
51028f34 151
8a259a23 152 __fxprintf (NULL, "%s", buf);
51028f34
UD
153
154 free (buf);
fa0bc87c
RM
155 return 0;
156 }
157 default:
158 break;
159 }
160 }
161 while (*args && *args != '#');
162 return args;
163}
164
165
166static const char *
bc054367 167arg_spoof (const char *fname, int line_num, const char *args)
fa0bc87c 168{
5edb9387 169 const char *start = args;
fa0bc87c
RM
170 size_t len;
171
172 args = skip_string (args);
173 len = args - start;
174
5edb9387 175 if (len == 3 && __strncasecmp (start, "off", len) == 0)
fa0bc87c
RM
176 _res_hconf.flags &= ~(HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
177 else
178 {
179 _res_hconf.flags |= (HCONF_FLAG_SPOOF | HCONF_FLAG_SPOOFALERT);
5edb9387
UD
180 if ((len == 6 && __strncasecmp (start, "nowarn", len) == 0)
181 || !(len == 4 && __strncasecmp (start, "warn", len) == 0))
fa0bc87c
RM
182 _res_hconf.flags &= ~HCONF_FLAG_SPOOFALERT;
183 }
184 return args;
185}
186
187
188static const char *
5edb9387 189arg_bool (const char *fname, int line_num, const char *args, unsigned flag)
fa0bc87c 190{
5edb9387 191 if (__strncasecmp (args, "on", 2) == 0)
fa0bc87c
RM
192 {
193 args += 2;
194 _res_hconf.flags |= flag;
195 }
5edb9387 196 else if (__strncasecmp (args, "off", 3) == 0)
fa0bc87c
RM
197 {
198 args += 3;
199 _res_hconf.flags &= ~flag;
200 }
201 else
202 {
51028f34
UD
203 char *buf;
204
322861e8
UD
205 if (__asprintf (&buf,
206 _("%s: line %d: expected `on' or `off', found `%s'\n"),
207 fname, line_num, args) < 0)
208 return 0;
51028f34 209
8a259a23 210 __fxprintf (NULL, "%s", buf);
51028f34
UD
211
212 free (buf);
fa0bc87c
RM
213 return 0;
214 }
215 return args;
216}
217
218
219static void
5edb9387 220parse_line (const char *fname, int line_num, const char *str)
fa0bc87c 221{
5edb9387 222 const char *start;
545f1b11 223 const struct cmd *c = 0;
fa0bc87c 224 size_t len;
57b36a0a 225 size_t i;
fa0bc87c
RM
226
227 str = skip_ws (str);
228
6dc25b55
UD
229 /* skip line comment and empty lines: */
230 if (*str == '\0' || *str == '#') return;
fa0bc87c
RM
231
232 start = str;
233 str = skip_string (str);
234 len = str - start;
235
236 for (i = 0; i < sizeof (cmd) / sizeof (cmd[0]); ++i)
237 {
4aebaa6b 238 if (__strncasecmp (start, cmd[i].name, len) == 0
fa0bc87c
RM
239 && strlen (cmd[i].name) == len)
240 {
241 c = &cmd[i];
242 break;
243 }
244 }
5edb9387 245 if (c == NULL)
fa0bc87c 246 {
51028f34
UD
247 char *buf;
248
322861e8
UD
249 if (__asprintf (&buf, _("%s: line %d: bad command `%s'\n"),
250 fname, line_num, start) < 0)
251 return;
51028f34 252
8a259a23 253 __fxprintf (NULL, "%s", buf);
51028f34
UD
254
255 free (buf);
fa0bc87c
RM
256 return;
257 }
258
259 /* process args: */
260 str = skip_ws (str);
bc054367
UD
261
262 if (c->cb == CB_arg_trimdomain_list)
263 str = arg_trimdomain_list (fname, line_num, str);
264 else if (c->cb == CB_arg_spoof)
265 str = arg_spoof (fname, line_num, str);
266 else if (c->cb == CB_arg_bool)
267 str = arg_bool (fname, line_num, str, c->arg);
268 else
269 /* Ignore the line. */
270 return;
271
fa0bc87c
RM
272 if (!str)
273 return;
274
275 /* rest of line must contain white space or comment only: */
276 while (*str)
277 {
278 if (!isspace (*str)) {
279 if (*str != '#')
51028f34
UD
280 {
281 char *buf;
282
322861e8
UD
283 if (__asprintf (&buf,
284 _("%s: line %d: ignoring trailing garbage `%s'\n"),
285 fname, line_num, str) < 0)
286 break;
51028f34 287
8a259a23 288 __fxprintf (NULL, "%s", buf);
51028f34
UD
289
290 free (buf);
291 }
fa0bc87c
RM
292 break;
293 }
294 ++str;
295 }
296}
297
298
9d957ce2
UD
299static void
300do_init (void)
fa0bc87c 301{
5edb9387 302 const char *hconf_name;
fa0bc87c 303 int line_num = 0;
2c68584c 304 char buf[256], *envval;
5edb9387 305 FILE *fp;
fa0bc87c 306
5edb9387 307 memset (&_res_hconf, '\0', sizeof (_res_hconf));
fa0bc87c 308
74955460 309 hconf_name = getenv (ENV_HOSTCONF);
5edb9387 310 if (hconf_name == NULL)
fa0bc87c
RM
311 hconf_name = _PATH_HOSTCONF;
312
312be3f9 313 fp = fopen (hconf_name, "rce");
b9c65d09 314 if (fp)
fa0bc87c 315 {
2706ee38
UD
316 /* No threads using this stream. */
317 __fsetlocking (fp, FSETLOCKING_BYCALLER);
318
5edb9387 319 while (fgets_unlocked (buf, sizeof (buf), fp))
fa0bc87c
RM
320 {
321 ++line_num;
c4563d2d 322 *__strchrnul (buf, '\n') = '\0';
fa0bc87c
RM
323 parse_line (hconf_name, line_num, buf);
324 }
325 fclose (fp);
326 }
327
fa0bc87c
RM
328 envval = getenv (ENV_SPOOF);
329 if (envval)
bc054367 330 arg_spoof (ENV_SPOOF, 1, envval);
fa0bc87c
RM
331
332 envval = getenv (ENV_MULTI);
333 if (envval)
334 arg_bool (ENV_MULTI, 1, envval, HCONF_FLAG_MULTI);
335
336 envval = getenv (ENV_REORDER);
337 if (envval)
338 arg_bool (ENV_REORDER, 1, envval, HCONF_FLAG_REORDER);
339
340 envval = getenv (ENV_TRIM_ADD);
341 if (envval)
bc054367 342 arg_trimdomain_list (ENV_TRIM_ADD, 1, envval);
fa0bc87c
RM
343
344 envval = getenv (ENV_TRIM_OVERR);
345 if (envval)
346 {
347 _res_hconf.num_trimdomains = 0;
bc054367 348 arg_trimdomain_list (ENV_TRIM_OVERR, 1, envval);
fa0bc87c 349 }
5edb9387 350
6f9d4f59
TR
351 /* See comments on the declaration of _res_hconf. */
352 atomic_store_release (&_res_hconf.initialized, 1);
fa0bc87c
RM
353}
354
355
9d957ce2
UD
356/* Initialize hconf datastructure by reading host.conf file and
357 environment variables. */
358void
359_res_hconf_init (void)
360{
361 __libc_once_define (static, once);
362
363 __libc_once (once, do_init);
364}
365
366
4f41c682 367#if IS_IN (libc)
f890a59b 368# if defined SIOCGIFCONF && defined SIOCGIFNETMASK
f720d3d2 369/* List of known interfaces. */
c877418f 370libc_freeres_ptr (
f720d3d2
UD
371static struct netaddr
372{
373 int addrtype;
374 union
375 {
376 struct
377 {
378 u_int32_t addr;
379 u_int32_t mask;
380 } ipv4;
381 } u;
c877418f 382} *ifaddrs);
f890a59b 383# endif
f720d3d2 384
fa0bc87c
RM
385/* Reorder addresses returned in a hostent such that the first address
386 is an address on the local subnet, if there is such an address.
f720d3d2
UD
387 Otherwise, nothing is changed.
388
389 Note that this function currently only handles IPv4 addresses. */
fa0bc87c
RM
390
391void
5edb9387 392_res_hconf_reorder_addrs (struct hostent *hp)
fa0bc87c 393{
5edb9387 394#if defined SIOCGIFCONF && defined SIOCGIFNETMASK
f720d3d2 395 int i, j;
f463c7b1
FW
396 /* Number of interfaces. Also serves as a flag for the
397 double-checked locking idiom. */
f720d3d2 398 static int num_ifs = -1;
f463c7b1
FW
399 /* Local copy of num_ifs, for non-atomic access. */
400 int num_ifs_local;
401 /* We need to protect the dynamic buffer handling. The lock is only
402 acquired during initialization. Afterwards, a positive num_ifs
403 value indicates completed initialization. */
5c3a3dba 404 __libc_lock_define_initialized (static, lock);
fa0bc87c 405
f720d3d2
UD
406 /* Only reorder if we're supposed to. */
407 if ((_res_hconf.flags & HCONF_FLAG_REORDER) == 0)
408 return;
4aebaa6b 409
f720d3d2 410 /* Can't deal with anything but IPv4 for now... */
fa0bc87c 411 if (hp->h_addrtype != AF_INET)
f720d3d2 412 return;
fa0bc87c 413
f463c7b1
FW
414 /* This load synchronizes with the release MO store in the
415 initialization block below. */
416 num_ifs_local = atomic_load_acquire (&num_ifs);
417 if (num_ifs_local <= 0)
fa0bc87c 418 {
f720d3d2
UD
419 struct ifreq *ifr, *cur_ifr;
420 int sd, num, i;
421 /* Save errno. */
422 int save = errno;
4aebaa6b 423
f720d3d2 424 /* Initialize interface table. */
fa0bc87c 425
7f1deee6
RM
426 /* The SIOCGIFNETMASK ioctl will only work on an AF_INET socket. */
427 sd = __socket (AF_INET, SOCK_DGRAM, 0);
fa0bc87c
RM
428 if (sd < 0)
429 return;
430
f720d3d2
UD
431 /* Get lock. */
432 __libc_lock_lock (lock);
fa0bc87c 433
f463c7b1
FW
434 /* Recheck, somebody else might have done the work by now. No
435 ordering is required for the load because we have the lock,
436 and num_ifs is only updated under the lock. Also see (3) in
437 the analysis below. */
438 num_ifs_local = atomic_load_relaxed (&num_ifs);
439 if (num_ifs_local <= 0)
5c3a3dba 440 {
f463c7b1
FW
441 /* This is the only block which writes to num_ifs. It can
442 be executed several times (sequentially) if
443 initialization does not yield any interfaces, and num_ifs
444 remains zero. However, once we stored a positive value
445 in num_ifs below, this block cannot be entered again due
446 to the condition above. */
5c3a3dba 447 int new_num_ifs = 0;
fa0bc87c 448
5c3a3dba
UD
449 /* Get a list of interfaces. */
450 __ifreq (&ifr, &num, sd);
451 if (!ifr)
452 goto cleanup;
4aebaa6b 453
5c3a3dba
UD
454 ifaddrs = malloc (num * sizeof (ifaddrs[0]));
455 if (!ifaddrs)
456 goto cleanup1;
4aebaa6b 457
5c3a3dba
UD
458 /* Copy usable interfaces in ifaddrs structure. */
459 for (cur_ifr = ifr, i = 0; i < num;
460 cur_ifr = __if_nextreq (cur_ifr), ++i)
461 {
2483fa85
SE
462 union
463 {
464 struct sockaddr sa;
465 struct sockaddr_in sin;
466 } ss;
467
5c3a3dba
UD
468 if (cur_ifr->ifr_addr.sa_family != AF_INET)
469 continue;
fa0bc87c 470
5c3a3dba 471 ifaddrs[new_num_ifs].addrtype = AF_INET;
2483fa85
SE
472 ss.sa = cur_ifr->ifr_addr;
473 ifaddrs[new_num_ifs].u.ipv4.addr = ss.sin.sin_addr.s_addr;
fa0bc87c 474
5c3a3dba
UD
475 if (__ioctl (sd, SIOCGIFNETMASK, cur_ifr) < 0)
476 continue;
f720d3d2 477
2483fa85
SE
478 ss.sa = cur_ifr->ifr_netmask;
479 ifaddrs[new_num_ifs].u.ipv4.mask = ss.sin.sin_addr.s_addr;
fa0bc87c 480
5c3a3dba
UD
481 /* Now we're committed to this entry. */
482 ++new_num_ifs;
483 }
484 /* Just keep enough memory to hold all the interfaces we want. */
485 ifaddrs = realloc (ifaddrs, new_num_ifs * sizeof (ifaddrs[0]));
486 assert (ifaddrs != NULL);
487
488 cleanup1:
489 __if_freereq (ifr, num);
490
491 cleanup:
492 /* Release lock, preserve error value, and close socket. */
5615eaf2 493 errno = save;
5c3a3dba 494
f463c7b1
FW
495 /* Advertise successful initialization if new_num_ifs is
496 positive (and no updates to ifaddrs are permitted after
497 that). Otherwise, num_ifs remains unchanged, at zero.
498 This store synchronizes with the initial acquire MO
499 load. */
500 atomic_store_release (&num_ifs, new_num_ifs);
501 /* Keep the local copy current, to save another load. */
502 num_ifs_local = new_num_ifs;
5c3a3dba 503 }
f720d3d2 504
b57525f1
DL
505 __libc_lock_unlock (lock);
506
4aebaa6b 507 __close (sd);
fa0bc87c
RM
508 }
509
f463c7b1
FW
510 /* num_ifs_local cannot be negative because the if statement above
511 covered this case. It can still be zero if we just performed
512 initialization, but could not find any interfaces. */
513 if (num_ifs_local == 0)
fa0bc87c
RM
514 return;
515
f463c7b1
FW
516 /* The code below accesses ifaddrs, so we need to ensure that the
517 initialization happens-before this point.
518
519 The actual initialization is sequenced-before the release store
520 to num_ifs, and sequenced-before the end of the critical section.
521
522 This means there are three possible executions:
523
524 (1) The thread that initialized the data also uses it, so
525 sequenced-before is sufficient to ensure happens-before.
526
527 (2) The release MO store of num_ifs synchronizes-with the acquire
528 MO load, and the acquire MO load is sequenced before the use
529 of the initialized data below.
530
531 (3) We enter the critical section, and the relaxed MO load of
532 num_ifs yields a positive value. The write to ifaddrs is
533 sequenced-before leaving the critical section. Leaving the
534 critical section happens-before we entered the critical
535 section ourselves, which means that the write to ifaddrs
536 happens-before this point.
537
538 Consequently, all potential writes to ifaddrs (and the data it
539 points to) happens-before this point. */
540
f720d3d2 541 /* Find an address for which we have a direct connection. */
fa0bc87c
RM
542 for (i = 0; hp->h_addr_list[i]; ++i)
543 {
f720d3d2 544 struct in_addr *haddr = (struct in_addr *) hp->h_addr_list[i];
fa0bc87c 545
f463c7b1 546 for (j = 0; j < num_ifs_local; ++j)
fa0bc87c 547 {
f720d3d2
UD
548 u_int32_t if_addr = ifaddrs[j].u.ipv4.addr;
549 u_int32_t if_netmask = ifaddrs[j].u.ipv4.mask;
fa0bc87c 550
f720d3d2 551 if (((haddr->s_addr ^ if_addr) & if_netmask) == 0)
fa0bc87c 552 {
5edb9387 553 void *tmp;
fa0bc87c 554
5edb9387 555 tmp = hp->h_addr_list[i];
fa0bc87c
RM
556 hp->h_addr_list[i] = hp->h_addr_list[0];
557 hp->h_addr_list[0] = tmp;
558 return;
559 }
560 }
561 }
562#endif /* defined(SIOCGIFCONF) && ... */
563}
564
565
566/* If HOSTNAME has a postfix matching any of the trimdomains, trim away
567 that postfix. Notice that HOSTNAME is modified inplace. Also, the
568 original code applied all trimdomains in order, meaning that the
569 same domainname could be trimmed multiple times. I believe this
570 was unintentional. */
571void
5edb9387 572_res_hconf_trim_domain (char *hostname)
fa0bc87c
RM
573{
574 size_t hostname_len, trim_len;
575 int i;
576
5edb9387 577 hostname_len = strlen (hostname);
fa0bc87c
RM
578
579 for (i = 0; i < _res_hconf.num_trimdomains; ++i)
580 {
5edb9387 581 const char *trim = _res_hconf.trimdomain[i];
fa0bc87c 582
5edb9387 583 trim_len = strlen (trim);
fa0bc87c 584 if (hostname_len > trim_len
4c8b8cc3 585 && __strcasecmp (&hostname[hostname_len - trim_len], trim) == 0)
fa0bc87c
RM
586 {
587 hostname[hostname_len - trim_len] = '\0';
588 break;
589 }
590 }
591}
592
593
594/* Trim all hostnames/aliases in HP according to the trimdomain list.
595 Notice that HP is modified inplace! */
596void
5edb9387 597_res_hconf_trim_domains (struct hostent *hp)
fa0bc87c
RM
598{
599 int i;
600
601 if (_res_hconf.num_trimdomains == 0)
602 return;
603
604 _res_hconf_trim_domain (hp->h_name);
605 for (i = 0; hp->h_aliases[i]; ++i)
606 _res_hconf_trim_domain (hp->h_aliases[i]);
607}
1ce7d80d 608#endif