1 | /* Test search/default domain name behavior. |
2 | Copyright (C) 2016-2024 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 <resolv.h> |
20 | #include <stdlib.h> |
21 | #include <string.h> |
22 | #include <support/check.h> |
23 | #include <support/check_nss.h> |
24 | #include <support/resolv_test.h> |
25 | #include <support/support.h> |
26 | #include <support/xmemstream.h> |
27 | |
28 | struct item |
29 | { |
30 | const char *name; |
31 | int response; |
32 | }; |
33 | |
34 | const struct item items[] = |
35 | { |
36 | {"hostname.usersys.example.com" , 1}, |
37 | {"hostname.corp.example.com" , 1}, |
38 | {"hostname.example.com" , 1}, |
39 | |
40 | {"mail.corp.example.com" , 1}, |
41 | {"mail.example.com" , 1}, |
42 | |
43 | {"file.corp.example.com" , 2}, |
44 | {"file.corp" , 1}, |
45 | {"file.example.com" , 1}, |
46 | {"servfail-usersys.usersys.example.com" , -ns_r_servfail}, |
47 | {"servfail-usersys.corp.example.com" , 1}, |
48 | {"servfail-usersys.example.com" , 1}, |
49 | {"servfail-corp.usersys.example.com" , 1}, |
50 | {"servfail-corp.corp.example.com" , -ns_r_servfail}, |
51 | {"servfail-corp.example.com" , 1}, |
52 | {"www.example.com" , 1}, |
53 | {"large.example.com" , 200}, |
54 | |
55 | /* Test query amplification with a SERVFAIL response combined with |
56 | a large RRset. */ |
57 | {"large-servfail.usersys.example.com" , -ns_r_servfail}, |
58 | {"large-servfail.example.com" , 2000}, |
59 | {} |
60 | }; |
61 | |
62 | enum |
63 | { |
64 | name_not_found = -1, |
65 | name_no_data = -2 |
66 | }; |
67 | |
68 | static int |
69 | find_name (const char *name) |
70 | { |
71 | for (int i = 0; items[i].name != NULL; ++i) |
72 | { |
73 | if (strcmp (s1: name, s2: items[i].name) == 0) |
74 | return i; |
75 | } |
76 | if (strcmp (s1: name, s2: "example.com" ) == 0 |
77 | || strcmp (s1: name, s2: "usersys.example.com" ) == 0 |
78 | || strcmp (s1: name, s2: "corp.example.com" ) == 0) |
79 | return name_no_data; |
80 | return name_not_found; |
81 | } |
82 | |
83 | static int rcode_override_server_index = -1; |
84 | static int rcode_override; |
85 | |
86 | static void |
87 | response (const struct resolv_response_context *ctx, |
88 | struct resolv_response_builder *b, |
89 | const char *qname, uint16_t qclass, uint16_t qtype) |
90 | { |
91 | if (ctx->server_index == rcode_override_server_index) |
92 | { |
93 | struct resolv_response_flags flags = {.rcode = rcode_override}; |
94 | resolv_response_init (b, flags); |
95 | resolv_response_add_question (b, name: qname, class: qclass, type: qtype); |
96 | return; |
97 | } |
98 | |
99 | int index = find_name (name: qname); |
100 | struct resolv_response_flags flags = {}; |
101 | if (index == name_not_found) |
102 | flags.rcode = ns_r_nxdomain; |
103 | else if (index >= 0 && items[index].response < 0) |
104 | flags.rcode = -items[index].response; |
105 | else if (index >= 0 && items[index].response > 5 && !ctx->tcp) |
106 | /* Force TCP if more than 5 addresses where requested. */ |
107 | flags.tc = true; |
108 | resolv_response_init (b, flags); |
109 | resolv_response_add_question (b, name: qname, class: qclass, type: qtype); |
110 | |
111 | if (flags.tc || index < 0 || items[index].response < 0) |
112 | return; |
113 | |
114 | resolv_response_section (b, ns_s_an); |
115 | |
116 | for (int i = 0; i < items[index].response; ++i) |
117 | { |
118 | resolv_response_open_record (b, name: qname, class: qclass, type: qtype, ttl: 0); |
119 | |
120 | switch (qtype) |
121 | { |
122 | case T_A: |
123 | { |
124 | char addr[4] = {10, index, i >> 8, i}; |
125 | resolv_response_add_data (b, addr, sizeof (addr)); |
126 | } |
127 | break; |
128 | case T_AAAA: |
129 | { |
130 | char addr[16] |
131 | = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, |
132 | 0, index + 1, (i + 1) >> 8, i + 1}; |
133 | resolv_response_add_data (b, addr, sizeof (addr)); |
134 | } |
135 | break; |
136 | default: |
137 | support_record_failure (); |
138 | printf (format: "error: unexpected QTYPE: %s/%u/%u\n" , |
139 | qname, qclass, qtype); |
140 | } |
141 | resolv_response_close_record (b); |
142 | } |
143 | } |
144 | |
145 | enum output_format |
146 | { |
147 | format_get, format_gai |
148 | }; |
149 | |
150 | static void |
151 | format_expected_1 (FILE *out, int family, enum output_format format, int index) |
152 | { |
153 | for (int i = 0; i < items[index].response; ++i) |
154 | { |
155 | char address[200]; |
156 | switch (family) |
157 | { |
158 | case AF_INET: |
159 | snprintf (s: address, maxlen: sizeof (address), format: "10.%d.%d.%d" , |
160 | index, (i >> 8) & 0xff, i & 0xff); |
161 | break; |
162 | case AF_INET6: |
163 | snprintf (s: address, maxlen: sizeof (address), format: "2001:db8::%x:%x" , |
164 | index + 1, i + 1); |
165 | break; |
166 | default: |
167 | FAIL_EXIT1 ("unreachable" ); |
168 | } |
169 | |
170 | switch (format) |
171 | { |
172 | case format_get: |
173 | fprintf (stream: out, format: "address: %s\n" , address); |
174 | break; |
175 | case format_gai: |
176 | fprintf (stream: out, format: "address: STREAM/TCP %s 80\n" , address); |
177 | } |
178 | } |
179 | } |
180 | |
181 | static char * |
182 | format_expected (const char *fqdn, int family, enum output_format format) |
183 | { |
184 | int index = find_name (name: fqdn); |
185 | TEST_VERIFY_EXIT (index >= 0); |
186 | struct xmemstream stream; |
187 | xopen_memstream (stream: &stream); |
188 | |
189 | TEST_VERIFY_EXIT (items[index].response >= 0); |
190 | if (format == format_get) |
191 | fprintf (stream: stream.out, format: "name: %s\n" , items[index].name); |
192 | if (family == AF_INET || family == AF_UNSPEC) |
193 | format_expected_1 (out: stream.out, AF_INET, format, index); |
194 | if (family == AF_INET6 || family == AF_UNSPEC) |
195 | format_expected_1 (out: stream.out, AF_INET6, format, index); |
196 | |
197 | xfclose_memstream (stream: &stream); |
198 | return stream.buffer; |
199 | } |
200 | |
201 | static void |
202 | do_get (const char *name, const char *fqdn, int family) |
203 | { |
204 | char *expected = format_expected (fqdn, family, format: format_get); |
205 | if (family == AF_INET) |
206 | { |
207 | char *query = xasprintf (format: "gethostbyname (\"%s\")" , name); |
208 | check_hostent (query_description: query, gethostbyname (name: name), expected); |
209 | free (ptr: query); |
210 | } |
211 | char *query = xasprintf (format: "gethostbyname2 (\"%s\", %d)" , name, family); |
212 | check_hostent (query_description: query, gethostbyname2 (name: name, af: family), expected); |
213 | |
214 | /* Test res_search. */ |
215 | int qtype; |
216 | switch (family) |
217 | { |
218 | case AF_INET: |
219 | qtype = T_A; |
220 | break; |
221 | case AF_INET6: |
222 | qtype = T_AAAA; |
223 | break; |
224 | default: |
225 | qtype = -1; |
226 | } |
227 | if (qtype >= 0) |
228 | { |
229 | int sz = 512; |
230 | unsigned char *response = xmalloc (n: sz); |
231 | int ret = res_search (name, C_IN, qtype, response, sz); |
232 | TEST_VERIFY_EXIT (ret >= 0); |
233 | if (ret > sz) |
234 | { |
235 | /* Truncation. Retry with a larger buffer. */ |
236 | sz = 65535; |
237 | unsigned char *newptr = xrealloc (o: response, n: sz); |
238 | response = newptr; |
239 | |
240 | ret = res_search (name, C_IN, qtype, response, sz); |
241 | TEST_VERIFY_EXIT (ret >= 0); |
242 | TEST_VERIFY_EXIT (ret < sz); |
243 | } |
244 | check_dns_packet (query_description: query, response, ret, expected); |
245 | free (ptr: response); |
246 | } |
247 | |
248 | free (ptr: query); |
249 | free (ptr: expected); |
250 | } |
251 | |
252 | static void |
253 | do_gai (const char *name, const char *fqdn, int family) |
254 | { |
255 | struct addrinfo hints = |
256 | { |
257 | .ai_family = family, |
258 | .ai_protocol = IPPROTO_TCP, |
259 | .ai_socktype = SOCK_STREAM |
260 | }; |
261 | struct addrinfo *ai; |
262 | char *query = xasprintf (format: "%s:80 [%d]" , name, family); |
263 | int ret = getaddrinfo (name: name, service: "80" , req: &hints, pai: &ai); |
264 | char *expected = format_expected (fqdn, family, format: format_gai); |
265 | check_addrinfo (query_description: query, ai, ret, expected); |
266 | if (ret == 0) |
267 | freeaddrinfo (ai: ai); |
268 | free (ptr: expected); |
269 | free (ptr: query); |
270 | } |
271 | |
272 | static void |
273 | do_both (const char *name, const char *fqdn) |
274 | { |
275 | do_get (name, fqdn, AF_INET); |
276 | do_get (name, fqdn, AF_INET6); |
277 | do_gai (name, fqdn, AF_INET); |
278 | do_gai (name, fqdn, AF_INET6); |
279 | do_gai (name, fqdn, AF_UNSPEC); |
280 | } |
281 | |
282 | static void |
283 | do_test_all (bool unconnectable_server) |
284 | { |
285 | struct resolv_redirect_config config = |
286 | { |
287 | .response_callback = response, |
288 | .search = {"usersys.example.com" , "corp.example.com" , "example.com" }, |
289 | }; |
290 | struct resolv_test *obj = resolv_test_start (config); |
291 | |
292 | if (unconnectable_server) |
293 | { |
294 | /* 255.255.255.255 results in an immediate connect failure. The |
295 | next server will supply the answer instead. This is a |
296 | triggering condition for bug 19791. */ |
297 | _res.nsaddr_list[0].sin_addr.s_addr = -1; |
298 | _res.nsaddr_list[0].sin_port = htons (53); |
299 | } |
300 | |
301 | do_both (name: "file" , fqdn: "file.corp.example.com" ); |
302 | do_both (name: "www" , fqdn: "www.example.com" ); |
303 | do_both (name: "servfail-usersys" , fqdn: "servfail-usersys.corp.example.com" ); |
304 | do_both (name: "servfail-corp" , fqdn: "servfail-corp.usersys.example.com" ); |
305 | do_both (name: "large" , fqdn: "large.example.com" ); |
306 | do_both (name: "large-servfail" , fqdn: "large-servfail.example.com" ); |
307 | do_both (name: "file.corp" , fqdn: "file.corp" ); |
308 | |
309 | /* Check that SERVFAIL and REFUSED responses do not alter the search |
310 | path resolution. */ |
311 | rcode_override_server_index = 0; |
312 | rcode_override = ns_r_servfail; |
313 | do_both (name: "hostname" , fqdn: "hostname.usersys.example.com" ); |
314 | do_both (name: "large" , fqdn: "large.example.com" ); |
315 | do_both (name: "large-servfail" , fqdn: "large-servfail.example.com" ); |
316 | rcode_override = ns_r_refused; |
317 | do_both (name: "hostname" , fqdn: "hostname.usersys.example.com" ); |
318 | do_both (name: "large" , fqdn: "large.example.com" ); |
319 | do_both (name: "large-servfail" , fqdn: "large-servfail.example.com" ); |
320 | /* Likewise, but with an NXDOMAIN for the first search path |
321 | entry. */ |
322 | rcode_override = ns_r_servfail; |
323 | do_both (name: "mail" , fqdn: "mail.corp.example.com" ); |
324 | rcode_override = ns_r_refused; |
325 | do_both (name: "mail" , fqdn: "mail.corp.example.com" ); |
326 | /* Likewise, but with ndots handling. */ |
327 | rcode_override = ns_r_servfail; |
328 | do_both (name: "file.corp" , fqdn: "file.corp" ); |
329 | rcode_override = ns_r_refused; |
330 | do_both (name: "file.corp" , fqdn: "file.corp" ); |
331 | |
332 | resolv_test_end (obj); |
333 | } |
334 | |
335 | static int |
336 | do_test (void) |
337 | { |
338 | for (int unconnectable_server = 0; unconnectable_server < 2; |
339 | ++unconnectable_server) |
340 | do_test_all (unconnectable_server); |
341 | return 0; |
342 | } |
343 | |
344 | #include <support/test-driver.c> |
345 | |