| /* dnsmasq is Copyright (c) 2000-2024 Simon Kelley |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; version 2 dated June, 1991, or |
| (at your option) version 3 dated 29 June, 2007. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "dnsmasq.h" |
| |
| #ifdef HAVE_CONNTRACK |
| |
| #define LOG(...) \ |
| do { \ |
| my_syslog(LOG_DEBUG, __VA_ARGS__); \ |
| } while (0) |
| |
| #define ASSERT(condition) \ |
| do { \ |
| if (!(condition)) \ |
| my_syslog(LOG_ERR, _("[pattern.c:%d] Assertion failure: %s"), __LINE__, #condition); \ |
| } while (0) |
| |
| /** |
| * Determines whether a given string value matches against a glob pattern |
| * which may contain zero-or-more-character wildcards denoted by '*'. |
| * |
| * Based on "Glob Matching Can Be Simple And Fast Too" by Russ Cox, |
| * See https://research.swtch.com/glob |
| * |
| * @param value A string value. |
| * @param num_value_bytes The number of bytes of the string value. |
| * @param pattern A glob pattern. |
| * @param num_pattern_bytes The number of bytes of the glob pattern. |
| * |
| * @return 1 If the provided value matches against the glob pattern. |
| * @return 0 Otherwise. |
| */ |
| static int is_string_matching_glob_pattern( |
| const char *value, |
| size_t num_value_bytes, |
| const char *pattern, |
| size_t num_pattern_bytes) |
| { |
| ASSERT(value); |
| ASSERT(pattern); |
| |
| size_t value_index = 0; |
| size_t next_value_index = 0; |
| size_t pattern_index = 0; |
| size_t next_pattern_index = 0; |
| while (value_index < num_value_bytes || pattern_index < num_pattern_bytes) |
| { |
| if (pattern_index < num_pattern_bytes) |
| { |
| char pattern_character = pattern[pattern_index]; |
| if ('a' <= pattern_character && pattern_character <= 'z') |
| pattern_character -= 'a' - 'A'; |
| if (pattern_character == '*') |
| { |
| /* zero-or-more-character wildcard */ |
| /* Try to match at value_index, otherwise restart at value_index + 1 next. */ |
| next_pattern_index = pattern_index; |
| pattern_index++; |
| if (value_index < num_value_bytes) |
| next_value_index = value_index + 1; |
| else |
| next_value_index = 0; |
| continue; |
| } |
| else |
| { |
| /* ordinary character */ |
| if (value_index < num_value_bytes) |
| { |
| char value_character = value[value_index]; |
| if ('a' <= value_character && value_character <= 'z') |
| value_character -= 'a' - 'A'; |
| if (value_character == pattern_character) |
| { |
| pattern_index++; |
| value_index++; |
| continue; |
| } |
| } |
| } |
| } |
| if (next_value_index) |
| { |
| pattern_index = next_pattern_index; |
| value_index = next_value_index; |
| continue; |
| } |
| return 0; |
| } |
| return 1; |
| } |
| |
| /** |
| * Determines whether a given string value represents a valid DNS name. |
| * |
| * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels |
| * delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only |
| * ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen. |
| * |
| * - A valid name must be fully qualified, i.e., consist of at least two labels. |
| * The final label must not be fully numeric, and must not be the "local" pseudo-TLD. |
| * |
| * - Examples: |
| * Valid: "example.com" |
| * Invalid: "ipcamera", "ipcamera.local", "8.8.8.8" |
| * |
| * @param value A string value. |
| * |
| * @return 1 If the provided string value is a valid DNS name. |
| * @return 0 Otherwise. |
| */ |
| int is_valid_dns_name(const char *value) |
| { |
| ASSERT(value); |
| |
| size_t num_bytes = 0; |
| size_t num_labels = 0; |
| const char *c, *label = NULL; |
| int is_label_numeric = 1; |
| for (c = value;; c++) |
| { |
| if (*c && |
| *c != '-' && *c != '.' && |
| (*c < '0' || *c > '9') && |
| (*c < 'A' || *c > 'Z') && |
| (*c < 'a' || *c > 'z')) |
| { |
| LOG(_("Invalid DNS name: Invalid character %c."), *c); |
| return 0; |
| } |
| if (*c) |
| num_bytes++; |
| if (!label) |
| { |
| if (!*c || *c == '.') |
| { |
| LOG(_("Invalid DNS name: Empty label.")); |
| return 0; |
| } |
| if (*c == '-') |
| { |
| LOG(_("Invalid DNS name: Label starts with hyphen.")); |
| return 0; |
| } |
| label = c; |
| } |
| if (*c && *c != '.') |
| { |
| if (*c < '0' || *c > '9') |
| is_label_numeric = 0; |
| } |
| else |
| { |
| if (c[-1] == '-') |
| { |
| LOG(_("Invalid DNS name: Label ends with hyphen.")); |
| return 0; |
| } |
| size_t num_label_bytes = (size_t) (c - label); |
| if (num_label_bytes > 63) |
| { |
| LOG(_("Invalid DNS name: Label is too long (%zu)."), num_label_bytes); |
| return 0; |
| } |
| num_labels++; |
| if (!*c) |
| { |
| if (num_labels < 2) |
| { |
| LOG(_("Invalid DNS name: Not enough labels (%zu)."), num_labels); |
| return 0; |
| } |
| if (is_label_numeric) |
| { |
| LOG(_("Invalid DNS name: Final label is fully numeric.")); |
| return 0; |
| } |
| if (num_label_bytes == 5 && |
| (label[0] == 'l' || label[0] == 'L') && |
| (label[1] == 'o' || label[1] == 'O') && |
| (label[2] == 'c' || label[2] == 'C') && |
| (label[3] == 'a' || label[3] == 'A') && |
| (label[4] == 'l' || label[4] == 'L')) |
| { |
| LOG(_("Invalid DNS name: \"local\" pseudo-TLD.")); |
| return 0; |
| } |
| if (num_bytes < 1 || num_bytes > 253) |
| { |
| LOG(_("DNS name has invalid length (%zu)."), num_bytes); |
| return 0; |
| } |
| return 1; |
| } |
| label = NULL; |
| is_label_numeric = 1; |
| } |
| } |
| } |
| |
| /** |
| * Determines whether a given string value represents a valid DNS name pattern. |
| * |
| * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels |
| * delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only |
| * ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen. |
| * |
| * - Patterns follow the syntax of DNS names, but additionally allow the wildcard character "*" to be used up to |
| * twice per label to match 0 or more characters within that label. Note that the wildcard never matches a dot |
| * (e.g., "*.example.com" matches "api.example.com" but not "api.us.example.com"). |
| * |
| * - A valid name or pattern must be fully qualified, i.e., consist of at least two labels. |
| * The final label must not be fully numeric, and must not be the "local" pseudo-TLD. |
| * A pattern must end with at least two literal (non-wildcard) labels. |
| * |
| * - Examples: |
| * Valid: "example.com", "*.example.com", "video*.example.com", "api*.*.example.com", "*-prod-*.example.com" |
| * Invalid: "ipcamera", "ipcamera.local", "*", "*.com", "8.8.8.8" |
| * |
| * @param value A string value. |
| * |
| * @return 1 If the provided string value is a valid DNS name pattern. |
| * @return 0 Otherwise. |
| */ |
| int is_valid_dns_name_pattern(const char *value) |
| { |
| ASSERT(value); |
| |
| size_t num_bytes = 0; |
| size_t num_labels = 0; |
| const char *c, *label = NULL; |
| int is_label_numeric = 1; |
| size_t num_wildcards = 0; |
| int previous_label_has_wildcard = 1; |
| for (c = value;; c++) |
| { |
| if (*c && |
| *c != '*' && /* Wildcard. */ |
| *c != '-' && *c != '.' && |
| (*c < '0' || *c > '9') && |
| (*c < 'A' || *c > 'Z') && |
| (*c < 'a' || *c > 'z')) |
| { |
| LOG(_("Invalid DNS name pattern: Invalid character %c."), *c); |
| return 0; |
| } |
| if (*c && *c != '*') |
| num_bytes++; |
| if (!label) |
| { |
| if (!*c || *c == '.') |
| { |
| LOG(_("Invalid DNS name pattern: Empty label.")); |
| return 0; |
| } |
| if (*c == '-') |
| { |
| LOG(_("Invalid DNS name pattern: Label starts with hyphen.")); |
| return 0; |
| } |
| label = c; |
| } |
| if (*c && *c != '.') |
| { |
| if (*c < '0' || *c > '9') |
| is_label_numeric = 0; |
| if (*c == '*') |
| { |
| if (num_wildcards >= 2) |
| { |
| LOG(_("Invalid DNS name pattern: Wildcard character used more than twice per label.")); |
| return 0; |
| } |
| num_wildcards++; |
| } |
| } |
| else |
| { |
| if (c[-1] == '-') |
| { |
| LOG(_("Invalid DNS name pattern: Label ends with hyphen.")); |
| return 0; |
| } |
| size_t num_label_bytes = (size_t) (c - label) - num_wildcards; |
| if (num_label_bytes > 63) |
| { |
| LOG(_("Invalid DNS name pattern: Label is too long (%zu)."), num_label_bytes); |
| return 0; |
| } |
| num_labels++; |
| if (!*c) |
| { |
| if (num_labels < 2) |
| { |
| LOG(_("Invalid DNS name pattern: Not enough labels (%zu)."), num_labels); |
| return 0; |
| } |
| if (num_wildcards != 0 || previous_label_has_wildcard) |
| { |
| LOG(_("Invalid DNS name pattern: Wildcard within final two labels.")); |
| return 0; |
| } |
| if (is_label_numeric) |
| { |
| LOG(_("Invalid DNS name pattern: Final label is fully numeric.")); |
| return 0; |
| } |
| if (num_label_bytes == 5 && |
| (label[0] == 'l' || label[0] == 'L') && |
| (label[1] == 'o' || label[1] == 'O') && |
| (label[2] == 'c' || label[2] == 'C') && |
| (label[3] == 'a' || label[3] == 'A') && |
| (label[4] == 'l' || label[4] == 'L')) |
| { |
| LOG(_("Invalid DNS name pattern: \"local\" pseudo-TLD.")); |
| return 0; |
| } |
| if (num_bytes < 1 || num_bytes > 253) |
| { |
| LOG(_("DNS name pattern has invalid length after removing wildcards (%zu)."), num_bytes); |
| return 0; |
| } |
| return 1; |
| } |
| label = NULL; |
| is_label_numeric = 1; |
| previous_label_has_wildcard = num_wildcards != 0; |
| num_wildcards = 0; |
| } |
| } |
| } |
| |
| /** |
| * Determines whether a given DNS name matches against a DNS name pattern. |
| * |
| * @param name A valid DNS name. |
| * @param pattern A valid DNS name pattern. |
| * |
| * @return 1 If the provided DNS name matches against the DNS name pattern. |
| * @return 0 Otherwise. |
| */ |
| int is_dns_name_matching_pattern(const char *name, const char *pattern) |
| { |
| ASSERT(name); |
| ASSERT(is_valid_dns_name(name)); |
| ASSERT(pattern); |
| ASSERT(is_valid_dns_name_pattern(pattern)); |
| |
| const char *n = name; |
| const char *p = pattern; |
| |
| do { |
| const char *name_label = n; |
| while (*n && *n != '.') |
| n++; |
| const char *pattern_label = p; |
| while (*p && *p != '.') |
| p++; |
| if (!is_string_matching_glob_pattern( |
| name_label, (size_t) (n - name_label), |
| pattern_label, (size_t) (p - pattern_label))) |
| break; |
| if (*n) |
| n++; |
| if (*p) |
| p++; |
| } while (*n && *p); |
| |
| return !*n && !*p; |
| } |
| |
| #endif |