1 | /* Test alias handling (mainly for gethostbyname). |
2 | Copyright (C) 2022-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 <array_length.h> |
20 | #include <arpa/inet.h> |
21 | #include <netdb.h> |
22 | #include <stdbool.h> |
23 | #include <stdio.h> |
24 | #include <stdlib.h> |
25 | #include <string.h> |
26 | #include <support/check.h> |
27 | #include <support/check_nss.h> |
28 | #include <support/resolv_test.h> |
29 | #include <support/support.h> |
30 | |
31 | #include "tst-resolv-maybe_insert_sig.h" |
32 | |
33 | /* QNAME format: |
34 | |
35 | aADDRESSES-cCNAMES.example.net |
36 | |
37 | CNAMES is the length of the CNAME chain, ADDRESSES is the number of |
38 | addresses in the response. The special value 255 means that there |
39 | are no addresses, and the RCODE is NXDOMAIN. */ |
40 | static void |
41 | response (const struct resolv_response_context *ctx, |
42 | struct resolv_response_builder *b, |
43 | const char *qname, uint16_t qclass, uint16_t qtype) |
44 | { |
45 | TEST_COMPARE (qclass, C_IN); |
46 | if (qtype != T_A) |
47 | TEST_COMPARE (qtype, T_AAAA); |
48 | |
49 | unsigned int addresses, cnames; |
50 | char *tail; |
51 | if (sscanf (s: qname, format: "a%u-c%u%ms" , &addresses, &cnames, &tail) == 3) |
52 | { |
53 | if (strcmp (s1: tail, s2: ".example.com" ) == 0 |
54 | || strcmp (s1: tail, s2: ".example.net.example.net" ) == 0 |
55 | || strcmp (s1: tail, s2: ".example.net.example.com" ) == 0) |
56 | /* These only happen after NXDOMAIN. */ |
57 | TEST_VERIFY (addresses == 255); |
58 | else if (strcmp (s1: tail, s2: ".example.net" ) != 0) |
59 | FAIL_EXIT1 ("invalid QNAME: %s" , qname); |
60 | } |
61 | free (ptr: tail); |
62 | |
63 | int rcode; |
64 | if (addresses == 255) |
65 | { |
66 | /* Special case: Use no addresses with NXDOMAIN response. */ |
67 | rcode = ns_r_nxdomain; |
68 | addresses = 0; |
69 | } |
70 | else |
71 | rcode = 0; |
72 | |
73 | struct resolv_response_flags flags = { .rcode = rcode }; |
74 | resolv_response_init (b, flags); |
75 | resolv_response_add_question (b, name: qname, class: qclass, type: qtype); |
76 | resolv_response_section (b, ns_s_an); |
77 | maybe_insert_sig (b, owner: qname); |
78 | |
79 | /* Provide the requested number of CNAME records. */ |
80 | char *previous_name = (char *) qname; |
81 | for (int unique = 0; unique < cnames; ++unique) |
82 | { |
83 | resolv_response_open_record (b, name: previous_name, class: qclass, T_CNAME, ttl: 60); |
84 | char *new_name = xasprintf (format: "%d.alias.example" , unique); |
85 | resolv_response_add_name (b, name: new_name); |
86 | resolv_response_close_record (b); |
87 | |
88 | maybe_insert_sig (b, owner: qname); |
89 | |
90 | if (previous_name != qname) |
91 | free (ptr: previous_name); |
92 | previous_name = new_name; |
93 | } |
94 | |
95 | for (int unique = 0; unique < addresses; ++unique) |
96 | { |
97 | resolv_response_open_record (b, name: previous_name, class: qclass, type: qtype, ttl: 60); |
98 | |
99 | if (qtype == T_A) |
100 | { |
101 | char ipv4[4] = {192, 0, 2, 1 + unique}; |
102 | resolv_response_add_data (b, &ipv4, sizeof (ipv4)); |
103 | } |
104 | else if (qtype == T_AAAA) |
105 | { |
106 | char ipv6[16] = |
107 | { |
108 | 0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
109 | 1 + unique |
110 | }; |
111 | resolv_response_add_data (b, &ipv6, sizeof (ipv6)); |
112 | } |
113 | resolv_response_close_record (b); |
114 | } |
115 | |
116 | if (previous_name != qname) |
117 | free (ptr: previous_name); |
118 | } |
119 | |
120 | static char * |
121 | make_qname (bool do_search, int cnames, int addresses) |
122 | { |
123 | return xasprintf (format: "a%d-c%d%s" , |
124 | addresses, cnames, do_search ? "" : ".example.net" ); |
125 | } |
126 | |
127 | static void |
128 | check_cnames_failure (int af, bool do_search, int cnames, int addresses) |
129 | { |
130 | char *qname = make_qname (do_search, cnames, addresses); |
131 | |
132 | struct hostent *e; |
133 | if (af == AF_UNSPEC) |
134 | e = gethostbyname (name: qname); |
135 | else |
136 | e = gethostbyname2 (name: qname, af: af); |
137 | |
138 | if (addresses == 0) |
139 | check_hostent (query_description: qname, e, expected: "error: NO_RECOVERY\n" ); |
140 | else |
141 | check_hostent (query_description: qname, e, expected: "error: HOST_NOT_FOUND\n" ); |
142 | |
143 | free (ptr: qname); |
144 | } |
145 | |
146 | static void |
147 | check (int af, bool do_search, int cnames, int addresses) |
148 | { |
149 | char *qname = make_qname (do_search, cnames, addresses); |
150 | char *fqdn = make_qname (false, cnames, addresses); |
151 | |
152 | struct hostent *e; |
153 | if (af == AF_UNSPEC) |
154 | e = gethostbyname (name: qname); |
155 | else |
156 | e = gethostbyname2 (name: qname, af: af); |
157 | if (e == NULL) |
158 | FAIL_EXIT1 ("unexpected failure for %d, %d, %d" , af, cnames, addresses); |
159 | |
160 | if (af == AF_UNSPEC || af == AF_INET) |
161 | { |
162 | TEST_COMPARE (e->h_addrtype, AF_INET); |
163 | TEST_COMPARE (e->h_length, 4); |
164 | } |
165 | else |
166 | { |
167 | TEST_COMPARE (e->h_addrtype, AF_INET6); |
168 | TEST_COMPARE (e->h_length, 16); |
169 | } |
170 | |
171 | for (int i = 0; i < addresses; ++i) |
172 | { |
173 | char ipv4[4] = {192, 0, 2, 1 + i}; |
174 | char ipv6[16] = |
175 | { 0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 + i }; |
176 | char *expected = e->h_addrtype == AF_INET ? ipv4 : ipv6; |
177 | TEST_COMPARE_BLOB (e->h_addr_list[i], e->h_length, |
178 | expected, e->h_length); |
179 | } |
180 | TEST_VERIFY (e->h_addr_list[addresses] == NULL); |
181 | |
182 | |
183 | if (cnames == 0) |
184 | { |
185 | /* QNAME is fully qualified. */ |
186 | TEST_COMPARE_STRING (e->h_name, fqdn); |
187 | TEST_VERIFY (e->h_aliases[0] == NULL); |
188 | } |
189 | else |
190 | { |
191 | /* Fully-qualified QNAME is demoted to an aliases. */ |
192 | TEST_COMPARE_STRING (e->h_aliases[0], fqdn); |
193 | |
194 | for (int i = 1; i <= cnames; ++i) |
195 | { |
196 | char *expected = xasprintf (format: "%d.alias.example" , i - 1); |
197 | if (i == cnames) |
198 | TEST_COMPARE_STRING (e->h_name, expected); |
199 | else |
200 | TEST_COMPARE_STRING (e->h_aliases[i], expected); |
201 | free (ptr: expected); |
202 | } |
203 | TEST_VERIFY (e->h_aliases[cnames] == NULL); |
204 | } |
205 | |
206 | free (ptr: fqdn); |
207 | free (ptr: qname); |
208 | } |
209 | |
210 | static int |
211 | do_test (void) |
212 | { |
213 | struct resolv_test *obj = resolv_test_start |
214 | ((struct resolv_redirect_config) |
215 | { |
216 | .response_callback = response, |
217 | .search = { "example.net" , "example.com" }, |
218 | }); |
219 | |
220 | static const int families[] = { AF_UNSPEC, AF_INET, AF_INET6 }; |
221 | |
222 | for (int do_insert_sig = 0; do_insert_sig < 2; ++do_insert_sig) |
223 | { |
224 | insert_sig = do_insert_sig; |
225 | |
226 | /* If do_search is true, a bare host name (for example, a1-c1) |
227 | is used. This exercises search path processing and FQDN |
228 | qualification. */ |
229 | for (int do_search = 0; do_search < 2; ++do_search) |
230 | for (const int *paf = families; paf != array_end (families); ++paf) |
231 | { |
232 | for (int cnames = 0; cnames <= 100; ++cnames) |
233 | { |
234 | check_cnames_failure (af: *paf, do_search, cnames, addresses: 0); |
235 | /* Now with NXDOMAIN responses. */ |
236 | check_cnames_failure (af: *paf, do_search, cnames, addresses: 255); |
237 | } |
238 | |
239 | for (int cnames = 0; cnames <= 10; ++cnames) |
240 | for (int addresses = 1; addresses <= 10; ++addresses) |
241 | check (af: *paf, do_search, cnames, addresses); |
242 | |
243 | /* The current implementation is limited to 47 aliases. |
244 | Addresses do not have such a limit. */ |
245 | check (af: *paf, do_search, cnames: 47, addresses: 60); |
246 | } |
247 | } |
248 | |
249 | resolv_test_end (obj); |
250 | |
251 | return 0; |
252 | } |
253 | |
254 | #include <support/test-driver.c> |
255 | |