diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index b81b9358d5..d5782e4687 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -361,51 +361,6 @@ finish: } } -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 - * sense in our method call context. Specifically: - * - * 1. Checks that the interface index is either 0 (meaning *all* interfaces) or positive - * - * 2. Only the protocols flags and a bunch of NO_XYZ flags are set, at most. Plus additional flags - * specific to our method, passed in the "ok" parameter. - * - * 3. If zero protocol flags are specified it is automatically turned into *all* protocols. This way - * clients can simply pass 0 as flags and all will work as it should. They can also use this so - * that clients don't have to know all the protocols resolved implements, but can just specify 0 - * to mean "all supported protocols". - */ - - if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL| - SD_RESOLVED_NO_CNAME| - SD_RESOLVED_NO_VALIDATE| - SD_RESOLVED_NO_SYNTHESIZE| - SD_RESOLVED_NO_CACHE| - SD_RESOLVED_NO_ZONE| - SD_RESOLVED_NO_TRUST_ANCHOR| - SD_RESOLVED_NO_NETWORK| - SD_RESOLVED_NO_STALE| - SD_RESOLVED_RELAX_SINGLE_LABEL| - ok)) - return sd_bus_error_set(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; -} - static int parse_as_address(sd_bus_message *m, int ifindex, const char *hostname, int family, uint64_t flags) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ char *canonical = NULL; @@ -519,9 +474,8 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, 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_flags(hostname, &flags, SD_RESOLVED_NO_SEARCH, error); - if (r < 0) - return r; + if (validate_and_mangle_query_flags(m, &flags, hostname, SD_RESOLVED_NO_SEARCH) < 0) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); r = parse_as_address(message, ifindex, hostname, family, flags); if (r != 0) @@ -674,9 +628,8 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s if (ifindex < 0) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); - r = validate_and_mangle_flags(NULL, &flags, 0, error); - if (r < 0) - return r; + if (validate_and_mangle_query_flags(m, &flags, /* name = */ NULL, /* ok = */ 0) < 0) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); r = dns_question_new_reverse(&question, family, &a); if (r < 0) @@ -843,9 +796,8 @@ 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_flags(name, &flags, 0, error); - if (r < 0) - return r; + if (validate_and_mangle_query_flags(m, &flags, name, SD_RESOLVED_NO_SEARCH) < 0) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); question = dns_question_new(1); if (!question) @@ -1378,9 +1330,12 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s if (name && !type) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type."); - r = validate_and_mangle_flags(name, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error); - if (r < 0) - return r; + if (validate_and_mangle_query_flags(m, &flags, name, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS) < 0) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); + + /* Refuse the method if SRV is filtered. */ + if (set_contains(m->refuse_record_types, INT_TO_PTR(DNS_TYPE_SRV))) + return sd_bus_error_set(error, BUS_ERROR_DNS_REFUSED, "DNS query type refused."); r = dns_question_new_service(&question_utf8, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), false); if (r < 0) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 78c57da181..4522328319 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -1440,3 +1440,60 @@ bool dns_query_fully_authoritative(DnsQuery *q) { * explicitly check previous redirects here.) */ return (q->answer_query_flags & SD_RESOLVED_FROM_MASK & ~(SD_RESOLVED_FROM_TRUST_ANCHOR | SD_RESOLVED_FROM_ZONE)) == 0; } + +int validate_and_mangle_query_flags( + Manager *manager, + uint64_t *flags, + const char *name, + uint64_t ok) { + + assert(manager); + assert(flags); + + /* Checks that the client supplied interface index and flags parameter actually are valid and make + * sense in our method call context. Specifically: + * + * 1. Checks that the interface index is either 0 (meaning *all* interfaces) or positive + * + * 2. Only the protocols flags and a bunch of NO_XYZ flags are set, at most. Plus additional flags + * specific to our method, passed in the "ok" parameter. + * + * 3. If zero protocol flags are specified it is automatically turned into *all* protocols. This way + * clients can simply pass 0 as flags and all will work as it should. They can also use this so + * that clients don't have to know all the protocols resolved implements, but can just specify 0 + * to mean "all supported protocols". + */ + + if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL| + SD_RESOLVED_NO_CNAME| + SD_RESOLVED_NO_VALIDATE| + SD_RESOLVED_NO_SYNTHESIZE| + SD_RESOLVED_NO_CACHE| + SD_RESOLVED_NO_ZONE| + SD_RESOLVED_NO_TRUST_ANCHOR| + SD_RESOLVED_NO_NETWORK| + SD_RESOLVED_NO_STALE| + SD_RESOLVED_RELAX_SINGLE_LABEL| + ok)) + return -EINVAL; + + 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; + + /* If both A and AAAA are refused, set SD_RESOLVED_NO_ADDRESS flag if it is allowed. */ + if (set_contains(manager->refuse_record_types, INT_TO_PTR(DNS_TYPE_A)) && + set_contains(manager->refuse_record_types, INT_TO_PTR(DNS_TYPE_AAAA)) && + FLAGS_SET(ok, SD_RESOLVED_NO_ADDRESS)) + *flags |= SD_RESOLVED_NO_ADDRESS; + + /* Similarly, if TXT is refused, set SD_RESOLVED_NO_TXT flag if it is allowed. */ + if (set_contains(manager->refuse_record_types, INT_TO_PTR(DNS_TYPE_TXT)) && + FLAGS_SET(ok, SD_RESOLVED_NO_TXT)) + *flags |= SD_RESOLVED_NO_TXT; + + return 0; +} diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 1097e90dc0..3a93ff7f1a 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -158,6 +158,8 @@ bool dns_query_fully_authenticated(DnsQuery *q); bool dns_query_fully_confidential(DnsQuery *q); bool dns_query_fully_authoritative(DnsQuery *q); +int validate_and_mangle_query_flags(Manager *manager, uint64_t *flags, const char *name, uint64_t ok); + static inline uint64_t dns_query_reply_flags_make(DnsQuery *q) { assert(q); diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index d04bcc332e..61f9f8e646 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -166,52 +166,6 @@ static void vl_on_notification_disconnect(sd_varlink_server *s, sd_varlink *link } } -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 - * it slightly. Specifically: - * - * 1. We check that only the protocol flags and a bunch of NO_XYZ flags are on at most, plus the - * method-specific flags specified in 'ok'. - * - * 2. If no protocols are enabled we automatically convert that to "all protocols are enabled". - * - * The second rule means that clients can just pass 0 as flags for the common case, and all supported - * protocols are enabled. Moreover it's useful so that client's do not have to be aware of all - * protocols implemented in resolved, but can use 0 as protocols flags set as indicator for - * "everything". - */ - - if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL| - SD_RESOLVED_NO_CNAME| - SD_RESOLVED_NO_VALIDATE| - SD_RESOLVED_NO_SYNTHESIZE| - SD_RESOLVED_NO_CACHE| - SD_RESOLVED_NO_ZONE| - SD_RESOLVED_NO_TRUST_ANCHOR| - SD_RESOLVED_NO_NETWORK| - SD_RESOLVED_NO_STALE| - SD_RESOLVED_RELAX_SINGLE_LABEL| - ok)) - return false; - - 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; -} - static int find_addr_records( sd_json_variant **array, DnsQuestion *question, @@ -387,7 +341,7 @@ static int vl_method_resolve_hostname(sd_varlink *link, sd_json_variant *paramet if (!IN_SET(p.family, AF_UNSPEC, AF_INET, AF_INET6)) return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); - if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_SEARCH)) + if (validate_and_mangle_query_flags(m, &p.flags, p.name, SD_RESOLVED_NO_SEARCH) < 0) return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); r = parse_as_address(link, &p); @@ -550,7 +504,7 @@ static int vl_method_resolve_address(sd_varlink *link, sd_json_variant *paramete if (FAMILY_ADDRESS_SIZE(p.family) != p.address_size) return sd_varlink_error(link, "io.systemd.Resolve.BadAddressSize", NULL); - if (!validate_and_mangle_flags(NULL, &p.flags, 0)) + if (validate_and_mangle_query_flags(m, &p.flags, /* name = */ NULL, /* ok = */ 0) < 0) return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); r = dns_question_new_reverse(&question, p.family, &p.address); @@ -1044,9 +998,13 @@ static int vl_method_resolve_service(sd_varlink* link, sd_json_variant* paramete if (p.name && !p.type) /* Service name cannot be specified without service type. */ return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("type")); - if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS)) + if (validate_and_mangle_query_flags(m, &p.flags, p.name, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS) < 0) return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); + /* Refuse the method if SRV is filtered. */ + if (set_contains(m->refuse_record_types, INT_TO_PTR(DNS_TYPE_SRV))) + return sd_varlink_error(link, "io.systemd.Resolve.QueryRefused", NULL); + r = dns_question_new_service(&question_utf8, p.name, p.type, p.domain, !(p.flags & SD_RESOLVED_NO_TXT), false); if (r < 0) return r; @@ -1184,7 +1142,7 @@ static int vl_method_resolve_record(sd_varlink *link, sd_json_variant *parameter if (dns_type_is_obsolete(p.type)) return sd_varlink_error(link, "io.systemd.Resolve.ResourceRecordTypeObsolete", NULL); - if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_SEARCH)) + if (validate_and_mangle_query_flags(m, &p.flags, p.name, SD_RESOLVED_NO_SEARCH) < 0) return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); _cleanup_(dns_question_unrefp) DnsQuestion *question = dns_question_new(1); diff --git a/test/units/TEST-75-RESOLVED.sh b/test/units/TEST-75-RESOLVED.sh index cf78e8b6dd..1b2f317532 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -551,6 +551,7 @@ testcase_08_resolved() { # Check SRV support run resolvectl service _mysvc._tcp signed.test grep -qF "myservice.signed.test:1234" "$RUN_OUT" + grep -qF "This is TXT for myservice" "$RUN_OUT" grep -qF "10.0.0.20" "$RUN_OUT" grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT" grep -qF "authenticated: yes" "$RUN_OUT" @@ -850,7 +851,6 @@ testcase_11_nft() { echo "[Resolve]" echo "StaleRetentionSec=1d" } >/run/systemd/resolved.conf.d/test.conf - ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf systemctl reload systemd-resolved.service run dig stale1.unsigned.test -t A @@ -943,6 +943,7 @@ testcase_12_resolvectl2() { cleanup() { rm -f /run/systemd/resolved.conf.d/reload.conf systemctl reload systemd-resolved.service + resolvectl revert dns0 } trap cleanup RETURN @@ -1024,6 +1025,7 @@ testcase_13_varlink_subscribe_dns_configuration() { echo "==========" rm -f /run/systemd/resolved.conf.d/global-dns.conf restart_resolved + resolvectl revert dns0 } trap cleanup RETURN ERR @@ -1096,10 +1098,6 @@ testcase_14_refuse_record_types() { # shellcheck disable=SC2317 cleanup() { rm -f /run/systemd/resolved.conf.d/refuserecords.conf - if [[ -e /etc/resolv.conf.bak ]]; then - rm -f /etc/resolv.conf - mv /etc/resolv.conf.bak /etc/resolv.conf - fi restart_resolved } trap cleanup RETURN ERR @@ -1109,11 +1107,8 @@ testcase_14_refuse_record_types() { echo "[Resolve]" echo "RefuseRecordTypes=AAAA SRV TXT" } >/run/systemd/resolved.conf.d/refuserecords.conf - if [[ -e /etc/resolv.conf ]]; then - mv /etc/resolv.conf /etc/resolv.conf.bak - fi - ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf systemctl reload systemd-resolved.service + run dig localhost -t AAAA grep -qF "status: REFUSED" "$RUN_OUT" @@ -1141,6 +1136,10 @@ testcase_14_refuse_record_types() { run resolvectl query localhost5 --type=A grep -qF "127.128.0.5" "$RUN_OUT" + (! run resolvectl service _mysvc._tcp signed.test) + (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}') + + # Filter only AAAA { echo "[Resolve]" echo "RefuseRecordTypes=AAAA" @@ -1164,6 +1163,74 @@ testcase_14_refuse_record_types() { (! run resolvectl query localhost5 --type=AAAA) grep -qF "DNS query type refused." "$RUN_OUT" + + run resolvectl service _mysvc._tcp signed.test + grep -qF "myservice.signed.test:1234" "$RUN_OUT" + grep -qF "This is TXT for myservice" "$RUN_OUT" + grep -qF "10.0.0.20" "$RUN_OUT" + (! grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT") + grep -qF "authenticated: yes" "$RUN_OUT" + + run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}' + grep -qF '"services":[{"priority":10,"weight":5,"port":1234,"hostname":"myservice.signed.test","canonicalName":"myservice.signed.test"' "$RUN_OUT" + grep -qF '"addresses":[{"ifindex":' "$RUN_OUT" + grep -qF '"family":2,"address":[10,0,0,20]' "$RUN_OUT" + (! grep -qF '"family":10,"address":[253,0,222,173,190,239,202,254,0,0,0,0,0,0,0,23]' "$RUN_OUT") + grep -qF '"txt":["This is TXT for myservice"]' "$RUN_OUT" + grep -qF '"canonical":{"name":null,"type":"_mysvc._tcp","domain":"signed.test"}' "$RUN_OUT" + + # Filter both A and AAAA + { + echo "[Resolve]" + echo "RefuseRecordTypes=A AAAA" + } >/run/systemd/resolved.conf.d/refuserecords.conf + systemctl reload systemd-resolved.service + + run resolvectl service _mysvc._tcp signed.test + grep -qF "myservice.signed.test:1234" "$RUN_OUT" + grep -qF "This is TXT for myservice" "$RUN_OUT" + (! grep -qF "10.0.0.20" "$RUN_OUT") + (! grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT") + grep -qF "authenticated: yes" "$RUN_OUT" + + run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}' + grep -qF '"services":[{"priority":10,"weight":5,"port":1234,"hostname":"myservice.signed.test"}]' "$RUN_OUT" + (! grep -qF '"addresses":[{"ifindex":' "$RUN_OUT") + (! grep -qF '"family":2,"address":[10,0,0,20]' "$RUN_OUT") + (! grep -qF '"family":10,"address":[253,0,222,173,190,239,202,254,0,0,0,0,0,0,0,23]' "$RUN_OUT") + grep -qF '"txt":["This is TXT for myservice"]' "$RUN_OUT" + grep -qF '"canonical":{"name":null,"type":"_mysvc._tcp","domain":"signed.test"}' "$RUN_OUT" + + # Filter AAAA and TXT + { + echo "[Resolve]" + echo "RefuseRecordTypes=AAAA TXT" + } >/run/systemd/resolved.conf.d/refuserecords.conf + systemctl reload systemd-resolved.service + + run resolvectl service _mysvc._tcp signed.test + grep -qF "myservice.signed.test:1234" "$RUN_OUT" + grep -qF "10.0.0.20" "$RUN_OUT" + (! grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT") + grep -qF "authenticated: yes" "$RUN_OUT" + + run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}' + grep -qF '"services":[{"priority":10,"weight":5,"port":1234,"hostname":"myservice.signed.test","canonicalName":"myservice.signed.test"' "$RUN_OUT" + grep -qF '"addresses":[{"ifindex":' "$RUN_OUT" + grep -qF '"family":2,"address":[10,0,0,20]' "$RUN_OUT" + (! grep -qF '"family":10,"address":[253,0,222,173,190,239,202,254,0,0,0,0,0,0,0,23]' "$RUN_OUT") + (! grep -qF '"txt":["This is TXT for myservice"]' "$RUN_OUT") + grep -qF '"canonical":{"name":null,"type":"_mysvc._tcp","domain":"signed.test"}' "$RUN_OUT" + + # Filter SRV + { + echo "[Resolve]" + echo "RefuseRecordTypes=SRV" + } >/run/systemd/resolved.conf.d/refuserecords.conf + systemctl reload systemd-resolved.service + + (! run resolvectl service _mysvc._tcp signed.test) + (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveService '{"name":"","type":"_mysvc._tcp","domain":"signed.test"}') } # PRE-SETUP