1 | /* Implement suppression of AAAA queries. |
2 | Copyright (C) 2022-2023 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 <string.h> |
21 | #include <resolv-internal.h> |
22 | #include <resolv_context.h> |
23 | #include <arpa/nameser.h> |
24 | |
25 | /* Returns true if the question type at P matches EXPECTED, and the |
26 | class is IN. */ |
27 | static bool |
28 | qtype_matches (const unsigned char *p, int expected) |
29 | { |
30 | /* This assumes that T_A/C_IN constants are less than 256, which |
31 | they are. */ |
32 | return p[0] == 0 && p[1] == expected && p[2] == 0 && p[3] == C_IN; |
33 | } |
34 | |
35 | /* Handle RES_NOAAAA translation of AAAA queries. To produce a Name |
36 | Error (NXDOMAIN) repsonse for domain names that do not exist, it is |
37 | still necessary to send a query. Using question type A is a |
38 | conservative choice. In the returned answer, it is necessary to |
39 | switch back the question type to AAAA. */ |
40 | bool |
41 | __res_handle_no_aaaa (struct resolv_context *ctx, |
42 | const unsigned char *buf, int buflen, |
43 | unsigned char *ans, int anssiz, int *result) |
44 | { |
45 | /* AAAA mode is not active, or the query looks invalid (will not be |
46 | able to be parsed). */ |
47 | if ((ctx->resp->options & RES_NOAAAA) == 0 |
48 | || buflen <= sizeof (HEADER)) |
49 | return false; |
50 | |
51 | /* The replacement A query is produced here. */ |
52 | struct |
53 | { |
54 | HEADER ; |
55 | unsigned char question[NS_MAXCDNAME + 4]; |
56 | } replacement; |
57 | memcpy (&replacement.header, buf, sizeof (replacement.header)); |
58 | |
59 | if (replacement.header.qr |
60 | || replacement.header.opcode != 0 |
61 | || replacement.header.rcode != 0 |
62 | || ntohs (replacement.header.qdcount) != 1 |
63 | || ntohs (replacement.header.ancount) != 0 |
64 | || ntohs (replacement.header.nscount) != 0) |
65 | /* Not a well-formed question. Let the core resolver code produce |
66 | the proper error. */ |
67 | return false; |
68 | |
69 | /* Disable EDNS0. */ |
70 | replacement.header.arcount = htons (0); |
71 | |
72 | /* Extract the QNAME. */ |
73 | int ret = __ns_name_unpack (buf, buf + buflen, buf + sizeof (HEADER), |
74 | replacement.question, NS_MAXCDNAME); |
75 | if (ret < 0) |
76 | /* Format error. */ |
77 | return false; |
78 | |
79 | /* Compute the end of the question name. */ |
80 | const unsigned char *after_question = buf + sizeof (HEADER) + ret; |
81 | |
82 | /* Check that we are dealing with an AAAA query. */ |
83 | if (buf + buflen - after_question < 4 |
84 | || !qtype_matches (after_question, T_AAAA)) |
85 | return false; |
86 | |
87 | /* Find the place to store the type/class data in the replacement |
88 | query. */ |
89 | after_question = replacement.question; |
90 | /* This cannot fail because __ns_name_unpack above produced a valid |
91 | domain name. */ |
92 | (void) __ns_name_skip (&after_question, &replacement.question[NS_MAXCDNAME]); |
93 | unsigned char *start_of_query = (unsigned char *) &replacement; |
94 | const unsigned char *end_of_query = after_question + 4; |
95 | |
96 | /* Produce an A/IN query. */ |
97 | { |
98 | unsigned char *p = (unsigned char *) after_question; |
99 | p[0] = 0; |
100 | p[1] = T_A; |
101 | p[2] = 0; |
102 | p[3] = C_IN; |
103 | } |
104 | |
105 | /* Clear the output buffer, to avoid reading undefined data when |
106 | rewriting the result from A to AAAA. */ |
107 | memset (ans, 0, anssiz); |
108 | |
109 | /* Always perform the message translation, independent of the error |
110 | code. */ |
111 | ret = __res_context_send (ctx, |
112 | start_of_query, end_of_query - start_of_query, |
113 | NULL, 0, ans, anssiz, |
114 | NULL, NULL, NULL, NULL, NULL); |
115 | |
116 | /* Patch in the AAAA question type if there is room and the A query |
117 | type was received. */ |
118 | after_question = ans + sizeof (HEADER); |
119 | if (__ns_name_skip (&after_question, ans + anssiz) == 0 |
120 | && ans + anssiz - after_question >= 4 |
121 | && qtype_matches (after_question, T_A)) |
122 | { |
123 | ((unsigned char *) after_question)[1] = T_AAAA; |
124 | |
125 | /* Create an aligned copy of the header. Hide all data except |
126 | the question from the response. Put back the header. There is |
127 | no need to change the response code. The zero answer count turns |
128 | a positive response with data into a no-data response. */ |
129 | memcpy (&replacement.header, ans, sizeof (replacement.header)); |
130 | replacement.header.ancount = htons (0); |
131 | replacement.header.nscount = htons (0); |
132 | replacement.header.arcount = htons (0); |
133 | memcpy (ans, &replacement.header, sizeof (replacement.header)); |
134 | |
135 | /* Truncate the reply. */ |
136 | if (ret <= 0) |
137 | *result = ret; |
138 | else |
139 | *result = after_question - ans + 4; |
140 | } |
141 | |
142 | return true; |
143 | } |
144 | |