1 /* Parse /etc/hosts in multi mode with many addresses/aliases.
2    Copyright (C) 2017-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
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, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 #include <dlfcn.h>
20 #include <errno.h>
21 #include <gnu/lib-names.h>
22 #include <netdb.h>
23 #include <nss.h>
24 #include <stdbool.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <support/check.h>
28 #include <support/check_nss.h>
29 #include <support/namespace.h>
30 #include <support/support.h>
31 #include <support/test-driver.h>
32 #include <support/test-driver.h>
33 #include <support/xmemstream.h>
34 #include <support/xstdio.h>
35 #include <support/xunistd.h>
36 #include <sys/resource.h>
37 
38 struct support_chroot *chroot_env;
39 
40 static void
prepare(int argc,char ** argv)41 prepare (int argc, char **argv)
42 {
43   chroot_env = support_chroot_create
44     ((struct support_chroot_configuration)
45      {
46        .resolv_conf = "",
47        .hosts = "",             /* See write_hosts below.  */
48        .host_conf = "multi on\n",
49      });
50 }
51 
52 /* Create the /etc/hosts file from outside the chroot.  */
53 static void
write_hosts(int count)54 write_hosts (int count)
55 {
56   TEST_VERIFY (count > 0 && count <= 65535);
57   FILE *fp = xfopen (chroot_env->path_hosts, "w");
58   fputs ("127.0.0.1   localhost localhost.localdomain\n"
59          "::1         localhost localhost.localdomain\n",
60          fp);
61   for (int i = 0; i < count; ++i)
62     {
63       fprintf (fp, "10.4.%d.%d www4.example.com\n",
64                (i / 256) & 0xff, i & 0xff);
65       fprintf (fp, "10.46.%d.%d www.example.com\n",
66                (i / 256) & 0xff, i & 0xff);
67       fprintf (fp, "192.0.2.1 alias.example.com v4-%d.example.com\n", i);
68       fprintf (fp, "2001:db8::6:%x www6.example.com\n", i);
69       fprintf (fp, "2001:db8::46:%x www.example.com\n", i);
70       fprintf (fp, "2001:db8::1 alias.example.com v6-%d.example.com\n", i);
71     }
72   xfclose (fp);
73 }
74 
75 /* Parameters of a single test.  */
76 struct test_params
77 {
78   const char *name;             /* Name to query.  */
79   const char *marker;           /* Address marker for the name.  */
80   int count;                    /* Number of addresses/aliases.  */
81   int family;                   /* AF_INET, AF_INET_6 or AF_UNSPEC.  */
82   bool canonname;               /* True if AI_CANONNAME should be enabled.  */
83 };
84 
85 /* Expected result of gethostbyname/gethostbyname2.  */
86 static char *
expected_ghbn(const struct test_params * params)87 expected_ghbn (const struct test_params *params)
88 {
89   TEST_VERIFY (params->family == AF_INET || params->family == AF_INET6);
90 
91   struct xmemstream expected;
92   xopen_memstream (&expected);
93   if (strcmp (params->name, "alias.example.com") == 0)
94     {
95       fprintf (expected.out, "name: %s\n", params->name);
96       char af;
97       if (params->family == AF_INET)
98         af = '4';
99       else
100         af = '6';
101       for (int i = 0; i < params->count; ++i)
102         fprintf (expected.out, "alias: v%c-%d.example.com\n", af, i);
103 
104       for (int i = 0; i < params->count; ++i)
105         if (params->family == AF_INET)
106           fputs ("address: 192.0.2.1\n", expected.out);
107         else
108           fputs ("address: 2001:db8::1\n", expected.out);
109     }
110   else /* www/www4/www6 name.  */
111     {
112       bool do_ipv4 = params->family == AF_INET
113         && strncmp (params->name, "www6", 4) != 0;
114       bool do_ipv6 = params->family == AF_INET6
115         && strncmp (params->name, "www4", 4) != 0;
116       if (do_ipv4 || do_ipv6)
117         {
118           fprintf (expected.out, "name: %s\n", params->name);
119           if (do_ipv4)
120             for (int i = 0; i < params->count; ++i)
121               fprintf (expected.out, "address: 10.%s.%d.%d\n",
122                        params->marker, i / 256, i % 256);
123           if (do_ipv6)
124             for (int i = 0; i < params->count; ++i)
125               fprintf (expected.out, "address: 2001:db8::%s:%x\n",
126                        params->marker, i);
127         }
128       else
129         fputs ("error: HOST_NOT_FOUND\n", expected.out);
130     }
131   xfclose_memstream (&expected);
132   return expected.buffer;
133 }
134 
135 /* Expected result of getaddrinfo.  */
136 static char *
expected_gai(const struct test_params * params)137 expected_gai (const struct test_params *params)
138 {
139   bool do_ipv4 = false;
140   bool do_ipv6 = false;
141   if (params->family == AF_UNSPEC)
142     do_ipv4 = do_ipv6 = true;
143   else if (params->family == AF_INET)
144     do_ipv4 = true;
145   else if (params->family == AF_INET6)
146     do_ipv6 = true;
147 
148   struct xmemstream expected;
149   xopen_memstream (&expected);
150   if (strcmp (params->name, "alias.example.com") == 0)
151     {
152       if (params->canonname)
153         fprintf (expected.out,
154                  "flags: AI_CANONNAME\n"
155                  "canonname: %s\n",
156                  params->name);
157 
158       if (do_ipv4)
159         for (int i = 0; i < params->count; ++i)
160           fputs ("address: STREAM/TCP 192.0.2.1 80\n", expected.out);
161       if (do_ipv6)
162         for (int i = 0; i < params->count; ++i)
163           fputs ("address: STREAM/TCP 2001:db8::1 80\n", expected.out);
164     }
165   else /* www/www4/www6 name.  */
166     {
167       if (strncmp (params->name, "www4", 4) == 0)
168         do_ipv6 = false;
169       else if (strncmp (params->name, "www6", 4) == 0)
170         do_ipv4 = false;
171       /* Otherwise, we have www as the name, so we do both.  */
172 
173       if (do_ipv4 || do_ipv6)
174         {
175           if (params->canonname)
176             fprintf (expected.out,
177                      "flags: AI_CANONNAME\n"
178                      "canonname: %s\n",
179                      params->name);
180 
181           if (do_ipv4)
182             for (int i = 0; i < params->count; ++i)
183               fprintf (expected.out, "address: STREAM/TCP 10.%s.%d.%d 80\n",
184                        params->marker, i / 256, i % 256);
185           if (do_ipv6)
186             for (int i = 0; i < params->count; ++i)
187               fprintf (expected.out,
188                        "address: STREAM/TCP 2001:db8::%s:%x 80\n",
189                        params->marker, i);
190         }
191       else
192         fputs ("error: Name or service not known\n", expected.out);
193     }
194   xfclose_memstream (&expected);
195   return expected.buffer;
196 }
197 
198 static void
run_gbhn_gai(struct test_params * params)199 run_gbhn_gai (struct test_params *params)
200 {
201   char *ctx = xasprintf ("name=%s marker=%s count=%d family=%d",
202                          params->name, params->marker, params->count,
203                          params->family);
204   if (test_verbose > 0)
205     printf ("info: %s\n", ctx);
206 
207   /* Check gethostbyname, gethostbyname2.  */
208   if (params->family == AF_INET)
209     {
210       char *expected = expected_ghbn (params);
211       check_hostent (ctx, gethostbyname (params->name), expected);
212       free (expected);
213     }
214   if (params->family != AF_UNSPEC)
215     {
216       char *expected = expected_ghbn (params);
217       check_hostent (ctx, gethostbyname2 (params->name, params->family),
218                      expected);
219       free (expected);
220     }
221 
222   /* Check getaddrinfo.  */
223   for (int do_canonical = 0; do_canonical < 2; ++do_canonical)
224     {
225       params->canonname = do_canonical;
226       char *expected = expected_gai (params);
227       struct addrinfo hints =
228         {
229           .ai_family = params->family,
230           .ai_socktype = SOCK_STREAM,
231           .ai_protocol = IPPROTO_TCP,
232         };
233       if (do_canonical)
234         hints.ai_flags |= AI_CANONNAME;
235       struct addrinfo *ai;
236       int ret = getaddrinfo (params->name, "80", &hints, &ai);
237       check_addrinfo (ctx, ai, ret, expected);
238       if (ret == 0)
239         freeaddrinfo (ai);
240       free (expected);
241     }
242 
243   free (ctx);
244 }
245 
246 /* Callback for the subprocess which runs the test in a chroot.  */
247 static void
subprocess(void * closure)248 subprocess (void *closure)
249 {
250   struct test_params *params = closure;
251 
252   xchroot (chroot_env->path_chroot);
253 
254   static const int families[] = { AF_INET, AF_INET6, AF_UNSPEC, -1 };
255   static const char *const names[] =
256     {
257       "www.example.com", "www4.example.com", "www6.example.com",
258       "alias.example.com",
259       NULL
260     };
261   static const char *const names_marker[] = { "46", "4", "6", "" };
262 
263   for (int family_idx = 0; families[family_idx] >= 0; ++family_idx)
264     {
265       params->family = families[family_idx];
266       for (int names_idx = 0; names[names_idx] != NULL; ++names_idx)
267         {
268           params->name = names[names_idx];
269           params->marker = names_marker[names_idx];
270           run_gbhn_gai (params);
271         }
272     }
273 }
274 
275 /* Run the test for a specific number of addresses/aliases.  */
276 static void
run_test(int count)277 run_test (int count)
278 {
279   write_hosts (count);
280 
281   struct test_params params =
282     {
283       .count = count,
284     };
285 
286   support_isolate_in_subprocess (subprocess, &params);
287 }
288 
289 static int
do_test(void)290 do_test (void)
291 {
292   support_become_root ();
293   if (!support_can_chroot ())
294     return EXIT_UNSUPPORTED;
295 
296   /* This test should not use gigabytes of memory.   */
297   {
298     struct rlimit limit;
299     if (getrlimit (RLIMIT_AS, &limit) != 0)
300       {
301         printf ("getrlimit (RLIMIT_AS) failed: %m\n");
302         return 1;
303       }
304     long target = 200 * 1024 * 1024;
305     if (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur > target)
306       {
307         limit.rlim_cur = target;
308         if (setrlimit (RLIMIT_AS, &limit) != 0)
309           {
310             printf ("setrlimit (RLIMIT_AS) failed: %m\n");
311             return 1;
312           }
313       }
314   }
315 
316   __nss_configure_lookup ("hosts", "files");
317   if (dlopen (LIBNSS_FILES_SO, RTLD_LAZY) == NULL)
318     FAIL_EXIT1 ("could not load " LIBNSS_DNS_SO ": %s", dlerror ());
319 
320   /* Run the tests with a few different address/alias counts.  */
321   for (int count = 1; count <= 111; ++count)
322     run_test (count);
323   run_test (1111);
324   run_test (22222);
325 
326   support_chroot_free (chroot_env);
327   return 0;
328 }
329 
330 #define TIMEOUT 40
331 #define PREPARE prepare
332 #include <support/test-driver.c>
333