diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index b60a0fd1f24..afe67d9e6cc 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -260,7 +260,12 @@ finish: dns_query_free(q); } -static int validate_and_mangle_ifindex_and_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus_error *error) { +static int validate_and_mangle_flags( + const char *name, + uint64_t *flags, + uint64_t ok, + sd_bus_error *error) { + assert(flags); /* Checks that the client supplied interface index and flags parameter actually are valid and make @@ -277,15 +282,16 @@ static int validate_and_mangle_ifindex_and_flags(int ifindex, uint64_t *flags, u * to mean "all supported protocols". */ - if (ifindex < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); - if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ *flags |= SD_RESOLVED_PROTOCOLS_ALL; + /* Imply SD_RESOLVED_NO_SEARCH if permitted and name is dot suffixed. */ + if (name && FLAGS_SET(ok, SD_RESOLVED_NO_SEARCH) && dns_name_dot_suffixed(name) > 0) + *flags |= SD_RESOLVED_NO_SEARCH; + return 0; } @@ -371,10 +377,13 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, if (r < 0) return r; + if (ifindex < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - r = validate_and_mangle_ifindex_and_flags(ifindex, &flags, SD_RESOLVED_NO_SEARCH, error); + r = validate_and_mangle_flags(hostname, &flags, SD_RESOLVED_NO_SEARCH, error); if (r < 0) return r; @@ -527,7 +536,10 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s if (r < 0) return r; - r = validate_and_mangle_ifindex_and_flags(ifindex, &flags, 0, error); + if (ifindex < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); + + r = validate_and_mangle_flags(NULL, &flags, 0, error); if (r < 0) return r; @@ -679,6 +691,9 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd if (r < 0) return r; + if (ifindex < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); + r = dns_name_is_valid(name); if (r < 0) return r; @@ -692,7 +707,7 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd if (dns_type_is_obsolete(type)) return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type); - r = validate_and_mangle_ifindex_and_flags(ifindex, &flags, 0, error); + r = validate_and_mangle_flags(name, &flags, 0, error); if (r < 0) return r; @@ -1205,6 +1220,9 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s if (r < 0) return r; + if (ifindex < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); @@ -1227,7 +1245,7 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s if (name && !type) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type."); - r = validate_and_mangle_ifindex_and_flags(ifindex, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error); + r = validate_and_mangle_flags(name, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error); if (r < 0) return r; diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index 2a65293c2a6..2f11a780911 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -93,7 +93,11 @@ static void vl_on_disconnect(VarlinkServer *s, Varlink *link, void *userdata) { dns_query_complete(q, DNS_TRANSACTION_ABORTED); } -static bool validate_and_mangle_flags(uint64_t *flags, uint64_t ok) { +static bool validate_and_mangle_flags( + const char *name, + uint64_t *flags, + uint64_t ok) { + assert(flags); /* This checks that the specified client-provided flags parameter actually makes sense, and mangles @@ -116,6 +120,12 @@ static bool validate_and_mangle_flags(uint64_t *flags, uint64_t ok) { if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ *flags |= SD_RESOLVED_PROTOCOLS_ALL; + /* If the SD_RESOLVED_NO_SEARCH flag is acceptable, and the query name is dot-suffixed, turn off + * search domains. Note that DNS name normalization drops the dot suffix, hence we propagate this + * into the flags field as early as we can. */ + if (name && FLAGS_SET(ok, SD_RESOLVED_NO_SEARCH) && dns_name_dot_suffixed(name) > 0) + *flags |= SD_RESOLVED_NO_SEARCH; + return true; } @@ -285,7 +295,7 @@ static int vl_method_resolve_hostname(Varlink *link, JsonVariant *parameters, Va if (!IN_SET(p.family, AF_UNSPEC, AF_INET, AF_INET6)) return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); - if (!validate_and_mangle_flags(&p.flags, SD_RESOLVED_NO_SEARCH)) + if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_SEARCH)) return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); r = parse_as_address(link, &p); @@ -460,7 +470,7 @@ static int vl_method_resolve_address(Varlink *link, JsonVariant *parameters, Var if (FAMILY_ADDRESS_SIZE(p.family) != p.address_size) return varlink_error(link, "io.systemd.UserDatabase.BadAddressSize", NULL); - if (!validate_and_mangle_flags(&p.flags, 0)) + if (!validate_and_mangle_flags(NULL, &p.flags, 0)) return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); r = dns_question_new_reverse(&question, p.family, &p.address); diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index b812665315f..00e12e681fe 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -41,8 +41,9 @@ int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags f /* Trailing dash */ return -EINVAL; - if (*n == '.') + if (n[0] == '.' && (n[1] != 0 || !FLAGS_SET(flags, DNS_LABEL_LEAVE_TRAILING_DOT))) n++; + break; } @@ -139,7 +140,7 @@ int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags f return -EINVAL; /* More than one trailing dot? */ - if (*n == '.') + if (n[0] == '.' && !FLAGS_SET(flags, DNS_LABEL_LEAVE_TRAILING_DOT)) return -EINVAL; if (sz >= 1 && d) @@ -1372,3 +1373,19 @@ int dns_name_is_valid_or_address(const char *name) { return dns_name_is_valid(name); } + +int dns_name_dot_suffixed(const char *name) { + const char *p = name; + int r; + + for (;;) { + if (streq(p, ".")) + return true; + + r = dns_label_unescape(&p, NULL, DNS_LABEL_MAX, DNS_LABEL_LEAVE_TRAILING_DOT); + if (r < 0) + return r; + if (r == 0) + return false; + } +} diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index 6ed512c6b16..60de7af2272 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -25,8 +25,9 @@ #define DNS_N_LABELS_MAX 127 typedef enum DNSLabelFlags { - DNS_LABEL_LDH = 1 << 0, /* Follow the "LDH" rule — only letters, digits, and internal hyphens. */ - DNS_LABEL_NO_ESCAPES = 1 << 1, /* Do not treat backslashes specially */ + DNS_LABEL_LDH = 1 << 0, /* Follow the "LDH" rule — only letters, digits, and internal hyphens. */ + DNS_LABEL_NO_ESCAPES = 1 << 1, /* Do not treat backslashes specially */ + DNS_LABEL_LEAVE_TRAILING_DOT = 1 << 2, /* Leave trailing dot in place */ } DNSLabelFlags; int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags); @@ -110,3 +111,5 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret); int dns_name_apply_idna(const char *name, char **ret); int dns_name_is_valid_or_address(const char *name); + +int dns_name_dot_suffixed(const char *name); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index ead5311705d..b73dc564656 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -780,6 +780,20 @@ static void test_dns_name_is_valid_or_address(void) { assert_se(dns_name_is_valid_or_address("::1") > 0); } +static void test_dns_name_dot_suffixed(void) { + log_info("/* %s */", __func__); + + assert_se(dns_name_dot_suffixed("") == 0); + assert_se(dns_name_dot_suffixed(".") > 0); + assert_se(dns_name_dot_suffixed("foo") == 0); + assert_se(dns_name_dot_suffixed("foo.") > 0); + assert_se(dns_name_dot_suffixed("foo\\..") > 0); + assert_se(dns_name_dot_suffixed("foo\\.") == 0); + assert_se(dns_name_dot_suffixed("foo.bar.") > 0); + assert_se(dns_name_dot_suffixed("foo.bar\\.\\.\\..") > 0); + assert_se(dns_name_dot_suffixed("foo.bar\\.\\.\\.\\.") == 0); +} + int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); @@ -810,6 +824,7 @@ int main(int argc, char *argv[]) { test_dns_name_common_suffix(); test_dns_name_apply_idna(); test_dns_name_is_valid_or_address(); + test_dns_name_dot_suffixed(); return 0; }