blob: cf19eb0f6bd644150ec6d00c4a3fab0c42bb8adb [file] [log] [blame]
Tarun Kundu12e3b2e2024-08-15 16:16:53 -07001/* dnsmasq is Copyright (c) 2000-2024 Simon Kelley
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 dated June, 1991, or
6 (at your option) version 3 dated 29 June, 2007.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17#include "dnsmasq.h"
18
19#ifdef HAVE_CONNTRACK
20
21#define LOG(...) \
22 do { \
23 my_syslog(LOG_DEBUG, __VA_ARGS__); \
24 } while (0)
25
26#define ASSERT(condition) \
27 do { \
28 if (!(condition)) \
29 my_syslog(LOG_ERR, _("[pattern.c:%d] Assertion failure: %s"), __LINE__, #condition); \
30 } while (0)
31
32/**
33 * Determines whether a given string value matches against a glob pattern
34 * which may contain zero-or-more-character wildcards denoted by '*'.
35 *
36 * Based on "Glob Matching Can Be Simple And Fast Too" by Russ Cox,
37 * See https://research.swtch.com/glob
38 *
39 * @param value A string value.
40 * @param num_value_bytes The number of bytes of the string value.
41 * @param pattern A glob pattern.
42 * @param num_pattern_bytes The number of bytes of the glob pattern.
43 *
44 * @return 1 If the provided value matches against the glob pattern.
45 * @return 0 Otherwise.
46 */
47static int is_string_matching_glob_pattern(
48 const char *value,
49 size_t num_value_bytes,
50 const char *pattern,
51 size_t num_pattern_bytes)
52{
53 ASSERT(value);
54 ASSERT(pattern);
55
56 size_t value_index = 0;
57 size_t next_value_index = 0;
58 size_t pattern_index = 0;
59 size_t next_pattern_index = 0;
60 while (value_index < num_value_bytes || pattern_index < num_pattern_bytes)
61 {
62 if (pattern_index < num_pattern_bytes)
63 {
64 char pattern_character = pattern[pattern_index];
65 if ('a' <= pattern_character && pattern_character <= 'z')
66 pattern_character -= 'a' - 'A';
67 if (pattern_character == '*')
68 {
69 /* zero-or-more-character wildcard */
70 /* Try to match at value_index, otherwise restart at value_index + 1 next. */
71 next_pattern_index = pattern_index;
72 pattern_index++;
73 if (value_index < num_value_bytes)
74 next_value_index = value_index + 1;
75 else
76 next_value_index = 0;
77 continue;
78 }
79 else
80 {
81 /* ordinary character */
82 if (value_index < num_value_bytes)
83 {
84 char value_character = value[value_index];
85 if ('a' <= value_character && value_character <= 'z')
86 value_character -= 'a' - 'A';
87 if (value_character == pattern_character)
88 {
89 pattern_index++;
90 value_index++;
91 continue;
92 }
93 }
94 }
95 }
96 if (next_value_index)
97 {
98 pattern_index = next_pattern_index;
99 value_index = next_value_index;
100 continue;
101 }
102 return 0;
103 }
104 return 1;
105}
106
107/**
108 * Determines whether a given string value represents a valid DNS name.
109 *
110 * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels
111 * delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only
112 * ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen.
113 *
114 * - A valid name must be fully qualified, i.e., consist of at least two labels.
115 * The final label must not be fully numeric, and must not be the "local" pseudo-TLD.
116 *
117 * - Examples:
118 * Valid: "example.com"
119 * Invalid: "ipcamera", "ipcamera.local", "8.8.8.8"
120 *
121 * @param value A string value.
122 *
123 * @return 1 If the provided string value is a valid DNS name.
124 * @return 0 Otherwise.
125 */
126int is_valid_dns_name(const char *value)
127{
128 ASSERT(value);
129
130 size_t num_bytes = 0;
131 size_t num_labels = 0;
132 const char *c, *label = NULL;
133 int is_label_numeric = 1;
134 for (c = value;; c++)
135 {
136 if (*c &&
137 *c != '-' && *c != '.' &&
138 (*c < '0' || *c > '9') &&
139 (*c < 'A' || *c > 'Z') &&
140 (*c < 'a' || *c > 'z'))
141 {
142 LOG(_("Invalid DNS name: Invalid character %c."), *c);
143 return 0;
144 }
145 if (*c)
146 num_bytes++;
147 if (!label)
148 {
149 if (!*c || *c == '.')
150 {
151 LOG(_("Invalid DNS name: Empty label."));
152 return 0;
153 }
154 if (*c == '-')
155 {
156 LOG(_("Invalid DNS name: Label starts with hyphen."));
157 return 0;
158 }
159 label = c;
160 }
161 if (*c && *c != '.')
162 {
163 if (*c < '0' || *c > '9')
164 is_label_numeric = 0;
165 }
166 else
167 {
168 if (c[-1] == '-')
169 {
170 LOG(_("Invalid DNS name: Label ends with hyphen."));
171 return 0;
172 }
173 size_t num_label_bytes = (size_t) (c - label);
174 if (num_label_bytes > 63)
175 {
176 LOG(_("Invalid DNS name: Label is too long (%zu)."), num_label_bytes);
177 return 0;
178 }
179 num_labels++;
180 if (!*c)
181 {
182 if (num_labels < 2)
183 {
184 LOG(_("Invalid DNS name: Not enough labels (%zu)."), num_labels);
185 return 0;
186 }
187 if (is_label_numeric)
188 {
189 LOG(_("Invalid DNS name: Final label is fully numeric."));
190 return 0;
191 }
192 if (num_label_bytes == 5 &&
193 (label[0] == 'l' || label[0] == 'L') &&
194 (label[1] == 'o' || label[1] == 'O') &&
195 (label[2] == 'c' || label[2] == 'C') &&
196 (label[3] == 'a' || label[3] == 'A') &&
197 (label[4] == 'l' || label[4] == 'L'))
198 {
199 LOG(_("Invalid DNS name: \"local\" pseudo-TLD."));
200 return 0;
201 }
202 if (num_bytes < 1 || num_bytes > 253)
203 {
204 LOG(_("DNS name has invalid length (%zu)."), num_bytes);
205 return 0;
206 }
207 return 1;
208 }
209 label = NULL;
210 is_label_numeric = 1;
211 }
212 }
213}
214
215/**
216 * Determines whether a given string value represents a valid DNS name pattern.
217 *
218 * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels
219 * delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only
220 * ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen.
221 *
222 * - Patterns follow the syntax of DNS names, but additionally allow the wildcard character "*" to be used up to
223 * twice per label to match 0 or more characters within that label. Note that the wildcard never matches a dot
224 * (e.g., "*.example.com" matches "api.example.com" but not "api.us.example.com").
225 *
226 * - A valid name or pattern must be fully qualified, i.e., consist of at least two labels.
227 * The final label must not be fully numeric, and must not be the "local" pseudo-TLD.
228 * A pattern must end with at least two literal (non-wildcard) labels.
229 *
230 * - Examples:
231 * Valid: "example.com", "*.example.com", "video*.example.com", "api*.*.example.com", "*-prod-*.example.com"
232 * Invalid: "ipcamera", "ipcamera.local", "*", "*.com", "8.8.8.8"
233 *
234 * @param value A string value.
235 *
236 * @return 1 If the provided string value is a valid DNS name pattern.
237 * @return 0 Otherwise.
238 */
239int is_valid_dns_name_pattern(const char *value)
240{
241 ASSERT(value);
242
243 size_t num_bytes = 0;
244 size_t num_labels = 0;
245 const char *c, *label = NULL;
246 int is_label_numeric = 1;
247 size_t num_wildcards = 0;
248 int previous_label_has_wildcard = 1;
249 for (c = value;; c++)
250 {
251 if (*c &&
252 *c != '*' && /* Wildcard. */
253 *c != '-' && *c != '.' &&
254 (*c < '0' || *c > '9') &&
255 (*c < 'A' || *c > 'Z') &&
256 (*c < 'a' || *c > 'z'))
257 {
258 LOG(_("Invalid DNS name pattern: Invalid character %c."), *c);
259 return 0;
260 }
261 if (*c && *c != '*')
262 num_bytes++;
263 if (!label)
264 {
265 if (!*c || *c == '.')
266 {
267 LOG(_("Invalid DNS name pattern: Empty label."));
268 return 0;
269 }
270 if (*c == '-')
271 {
272 LOG(_("Invalid DNS name pattern: Label starts with hyphen."));
273 return 0;
274 }
275 label = c;
276 }
277 if (*c && *c != '.')
278 {
279 if (*c < '0' || *c > '9')
280 is_label_numeric = 0;
281 if (*c == '*')
282 {
283 if (num_wildcards >= 2)
284 {
285 LOG(_("Invalid DNS name pattern: Wildcard character used more than twice per label."));
286 return 0;
287 }
288 num_wildcards++;
289 }
290 }
291 else
292 {
293 if (c[-1] == '-')
294 {
295 LOG(_("Invalid DNS name pattern: Label ends with hyphen."));
296 return 0;
297 }
298 size_t num_label_bytes = (size_t) (c - label) - num_wildcards;
299 if (num_label_bytes > 63)
300 {
301 LOG(_("Invalid DNS name pattern: Label is too long (%zu)."), num_label_bytes);
302 return 0;
303 }
304 num_labels++;
305 if (!*c)
306 {
307 if (num_labels < 2)
308 {
309 LOG(_("Invalid DNS name pattern: Not enough labels (%zu)."), num_labels);
310 return 0;
311 }
312 if (num_wildcards != 0 || previous_label_has_wildcard)
313 {
314 LOG(_("Invalid DNS name pattern: Wildcard within final two labels."));
315 return 0;
316 }
317 if (is_label_numeric)
318 {
319 LOG(_("Invalid DNS name pattern: Final label is fully numeric."));
320 return 0;
321 }
322 if (num_label_bytes == 5 &&
323 (label[0] == 'l' || label[0] == 'L') &&
324 (label[1] == 'o' || label[1] == 'O') &&
325 (label[2] == 'c' || label[2] == 'C') &&
326 (label[3] == 'a' || label[3] == 'A') &&
327 (label[4] == 'l' || label[4] == 'L'))
328 {
329 LOG(_("Invalid DNS name pattern: \"local\" pseudo-TLD."));
330 return 0;
331 }
332 if (num_bytes < 1 || num_bytes > 253)
333 {
334 LOG(_("DNS name pattern has invalid length after removing wildcards (%zu)."), num_bytes);
335 return 0;
336 }
337 return 1;
338 }
339 label = NULL;
340 is_label_numeric = 1;
341 previous_label_has_wildcard = num_wildcards != 0;
342 num_wildcards = 0;
343 }
344 }
345}
346
347/**
348 * Determines whether a given DNS name matches against a DNS name pattern.
349 *
350 * @param name A valid DNS name.
351 * @param pattern A valid DNS name pattern.
352 *
353 * @return 1 If the provided DNS name matches against the DNS name pattern.
354 * @return 0 Otherwise.
355 */
356int is_dns_name_matching_pattern(const char *name, const char *pattern)
357{
358 ASSERT(name);
359 ASSERT(is_valid_dns_name(name));
360 ASSERT(pattern);
361 ASSERT(is_valid_dns_name_pattern(pattern));
362
363 const char *n = name;
364 const char *p = pattern;
365
366 do {
367 const char *name_label = n;
368 while (*n && *n != '.')
369 n++;
370 const char *pattern_label = p;
371 while (*p && *p != '.')
372 p++;
373 if (!is_string_matching_glob_pattern(
374 name_label, (size_t) (n - name_label),
375 pattern_label, (size_t) (p - pattern_label)))
376 break;
377 if (*n)
378 n++;
379 if (*p)
380 p++;
381 } while (*n && *p);
382
383 return !*n && !*p;
384}
385
386#endif