diff options
Diffstat (limited to 'src')
92 files changed, 6093 insertions, 1557 deletions
diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c index 6e460697db..3c9766da04 100644 --- a/src/analyze/analyze-verify.c +++ b/src/analyze/analyze-verify.c @@ -30,6 +30,47 @@ #include "pager.h" #include "path-util.h" #include "strv.h" +#include "unit-name.h" + +static int prepare_filename(const char *filename, char **ret) { + int r; + const char *name; + _cleanup_free_ char *abspath = NULL; + _cleanup_free_ char *dir = NULL; + _cleanup_free_ char *with_instance = NULL; + char *c; + + assert(filename); + assert(ret); + + r = path_make_absolute_cwd(filename, &abspath); + if (r < 0) + return r; + + name = basename(abspath); + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { + r = unit_name_replace_instance(name, "i", &with_instance); + if (r < 0) + return r; + } + + dir = dirname_malloc(abspath); + if (!dir) + return -ENOMEM; + + if (with_instance) + c = path_join(NULL, dir, with_instance); + else + c = path_join(NULL, dir, name); + if (!c) + return -ENOMEM; + + *ret = c; + return 0; +} static int generate_path(char **var, char **filenames) { char **filename; @@ -233,18 +274,19 @@ int verify_units(char **filenames, ManagerRunningAs running_as, bool check_man) log_debug("Loading remaining units from the command line..."); STRV_FOREACH(filename, filenames) { - char fname[UNIT_NAME_MAX + 2 + 1] = "./"; + _cleanup_free_ char *prepared = NULL; log_debug("Handling %s...", *filename); - /* manager_load_unit does not like pure basenames, so prepend - * the local directory, but only for valid names. manager_load_unit - * will print the error for other ones. */ - if (!strchr(*filename, '/') && strlen(*filename) <= UNIT_NAME_MAX) { - strncat(fname + 2, *filename, UNIT_NAME_MAX); - k = manager_load_unit(m, NULL, fname, &err, &units[count]); - } else - k = manager_load_unit(m, NULL, *filename, &err, &units[count]); + k = prepare_filename(*filename, &prepared); + if (k < 0) { + log_error_errno(k, "Failed to prepare filename %s: %m", *filename); + if (r == 0) + r = k; + continue; + } + + k = manager_load_unit(m, NULL, prepared, &err, &units[count]); if (k < 0) { log_error_errno(k, "Failed to load %s: %m", *filename); if (r == 0) diff --git a/src/basic/bitmap.c b/src/basic/bitmap.c index 95f59e400a..50078822a7 100644 --- a/src/basic/bitmap.c +++ b/src/basic/bitmap.c @@ -140,7 +140,8 @@ bool bitmap_isset(Bitmap *b, unsigned n) { bool bitmap_isclear(Bitmap *b) { unsigned i; - assert(b); + if (!b) + return true; for (i = 0; i < b->n_bitmaps; i++) if (b->bitmaps[i] != 0) @@ -150,7 +151,9 @@ bool bitmap_isclear(Bitmap *b) { } void bitmap_clear(Bitmap *b) { - assert(b); + + if (!b) + return; b->bitmaps = mfree(b->bitmaps); b->n_bitmaps = 0; @@ -197,7 +200,10 @@ bool bitmap_equal(Bitmap *a, Bitmap *b) { Bitmap *c; unsigned i; - if (!a ^ !b) + if (a == b) + return true; + + if (!a != !b) return false; if (!a) diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index 75dad228e3..5854caeb51 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -41,6 +41,7 @@ static int files_add(Hashmap *h, const char *root, const char *path, const char *suffix) { _cleanup_closedir_ DIR *dir = NULL; const char *dirpath; + struct dirent *de; int r; assert(path); @@ -55,18 +56,9 @@ static int files_add(Hashmap *h, const char *root, const char *path, const char return -errno; } - for (;;) { - struct dirent *de; + FOREACH_DIRENT(de, dir, return -errno) { char *p; - errno = 0; - de = readdir(dir); - if (!de && errno != 0) - return -errno; - - if (!de) - break; - if (!dirent_is_file_with_suffix(de, suffix)) continue; @@ -116,17 +108,15 @@ static int conf_files_list_strv_internal(char ***strv, const char *suffix, const STRV_FOREACH(p, dirs) { r = files_add(fh, root, *p, suffix); - if (r == -ENOMEM) { + if (r == -ENOMEM) return r; - } else if (r < 0) - log_debug_errno(r, "Failed to search for files in %s: %m", - *p); + if (r < 0) + log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p); } files = hashmap_get_strv(fh); - if (files == NULL) { + if (!files) return -ENOMEM; - } qsort_safe(files, hashmap_size(fh), sizeof(char *), base_cmp); *strv = files; diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c index b3954e3223..286ddfef5b 100644 --- a/src/basic/hashmap.c +++ b/src/basic/hashmap.c @@ -37,6 +37,7 @@ #include "util.h" #ifdef ENABLE_DEBUG_HASHMAP +#include <pthread.h> #include "list.h" #endif diff --git a/src/basic/missing.h b/src/basic/missing.h index d539ed00e4..880e724cb4 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -131,6 +131,10 @@ #define NETLINK_LIST_MEMBERSHIPS 9 #endif +#ifndef SOL_SCTP +#define SOL_SCTP 132 +#endif + #if !HAVE_DECL_PIVOT_ROOT static inline int pivot_root(const char *new_root, const char *put_old) { return syscall(SYS_pivot_root, new_root, put_old); diff --git a/src/basic/string-table.h b/src/basic/string-table.h index 2181a3a767..588404ab5a 100644 --- a/src/basic/string-table.h +++ b/src/basic/string-table.h @@ -47,16 +47,34 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ } +#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ + scope type name##_from_string(const char *s) { \ + int b; \ + b = parse_boolean(s); \ + if (b == 0) \ + return (type) 0; \ + else if (b > 0) \ + return yes; \ + return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ + } + #define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \ _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \ struct __useless_struct_to_allow_trailing_semicolon__ +#define _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ + struct __useless_struct_to_allow_trailing_semicolon__ + #define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,) #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static) #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static) #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static) +#define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,) + /* For string conversions where numbers are also acceptable */ #define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \ int name##_to_string_alloc(type i, char **str) { \ diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 61bd0f8d5f..8a523cc8ac 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -229,7 +229,10 @@ static int property_set_log_level( if (r < 0) return r; - return log_set_max_level_from_string(t); + r = log_set_max_level_from_string(t); + if (r == 0) + log_info("Setting log level to %s.", t); + return r; } static int property_get_n_names( diff --git a/src/core/dbus.c b/src/core/dbus.c index e7ee216f0e..1d89b9e250 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -734,9 +734,11 @@ static int bus_on_connection(sd_event_source *s, int fd, uint32_t revents, void return 0; } -static int bus_list_names(Manager *m, sd_bus *bus) { +int manager_sync_bus_names(Manager *m, sd_bus *bus) { _cleanup_strv_free_ char **names = NULL; - char **i; + const char *name; + Iterator i; + Unit *u; int r; assert(m); @@ -746,15 +748,55 @@ static int bus_list_names(Manager *m, sd_bus *bus) { if (r < 0) return log_error_errno(r, "Failed to get initial list of names: %m"); - /* This is a bit hacky, we say the owner of the name is the - * name itself, because we don't want the extra traffic to - * figure out the real owner. */ - STRV_FOREACH(i, names) { - Unit *u; + /* We have to synchronize the current bus names with the + * list of active services. To do this, walk the list of + * all units with bus names. */ + HASHMAP_FOREACH_KEY(u, name, m->watch_bus, i) { + Service *s = SERVICE(u); + + assert(s); - u = hashmap_get(m->watch_bus, *i); - if (u) - UNIT_VTABLE(u)->bus_name_owner_change(u, *i, NULL, *i); + if (!streq_ptr(s->bus_name, name)) { + log_unit_warning(u, "Bus name has changed from %s → %s, ignoring.", s->bus_name, name); + continue; + } + + /* Check if a service's bus name is in the list of currently + * active names */ + if (strv_contains(names, name)) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + const char *unique; + + /* If it is, determine its current owner */ + r = sd_bus_get_name_creds(bus, name, SD_BUS_CREDS_UNIQUE_NAME, &creds); + if (r < 0) { + log_error_errno(r, "Failed to get bus name owner %s: %m", name); + continue; + } + + r = sd_bus_creds_get_unique_name(creds, &unique); + if (r < 0) { + log_error_errno(r, "Failed to get unique name for %s: %m", name); + continue; + } + + /* Now, let's compare that to the previous bus owner, and + * if it's still the same, all is fine, so just don't + * bother the service. Otherwise, the name has apparently + * changed, so synthesize a name owner changed signal. */ + + if (!streq_ptr(unique, s->bus_name_owner)) + UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, unique); + } else { + /* So, the name we're watching is not on the bus. + * This either means it simply hasn't appeared yet, + * or it was lost during the daemon reload. + * Check if the service has a stored name owner, + * and synthesize a name loss signal in this case. */ + + if (s->bus_name_owner) + UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, NULL); + } } return 0; @@ -808,7 +850,9 @@ static int bus_setup_api(Manager *m, sd_bus *bus) { if (r < 0) return log_error_errno(r, "Failed to register name: %m"); - bus_list_names(m, bus); + r = manager_sync_bus_names(m, bus); + if (r < 0) + return r; log_debug("Successfully connected to API bus."); return 0; diff --git a/src/core/dbus.h b/src/core/dbus.h index 4f06ad11c4..ff761668f3 100644 --- a/src/core/dbus.h +++ b/src/core/dbus.h @@ -34,6 +34,8 @@ void bus_track_serialize(sd_bus_track *t, FILE *f); int bus_track_deserialize_item(char ***l, const char *line); int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l); +int manager_sync_bus_names(Manager *m, sd_bus *bus); + int bus_foreach_bus(Manager *m, sd_bus_track *subscribed2, int (*send_message)(sd_bus *bus, void *userdata), void *userdata); int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error); diff --git a/src/core/manager.c b/src/core/manager.c index 34dd715e93..711b0cdcee 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -380,6 +380,9 @@ static int enable_special_signals(Manager *m) { assert(m); + if (m->test_run) + return 0; + /* Enable that we get SIGINT on control-alt-del. In containers * this will fail with EPERM (older) or EINVAL (newer), so * ignore that. */ @@ -1885,23 +1888,21 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t switch (sfsi.ssi_signo - SIGRTMIN) { case 20: - log_debug("Enabling showing of status."); manager_set_show_status(m, SHOW_STATUS_YES); break; case 21: - log_debug("Disabling showing of status."); manager_set_show_status(m, SHOW_STATUS_NO); break; case 22: log_set_max_level(LOG_DEBUG); - log_notice("Setting log level to debug."); + log_info("Setting log level to debug."); break; case 23: log_set_max_level(LOG_INFO); - log_notice("Setting log level to info."); + log_info("Setting log level to info."); break; case 24: @@ -2576,6 +2577,10 @@ int manager_reload(Manager *m) { /* Third, fire things up! */ manager_coldplug(m); + /* Sync current state of bus names with our set of listening units */ + if (m->api_bus) + manager_sync_bus_names(m, m->api_bus); + assert(m->n_reloading > 0); m->n_reloading--; @@ -2961,6 +2966,9 @@ void manager_set_show_status(Manager *m, ShowStatus mode) { if (m->running_as != MANAGER_SYSTEM) return; + if (m->show_status != mode) + log_debug("%s showing of status.", + mode == SHOW_STATUS_NO ? "Disabling" : "Enabling"); m->show_status = mode; if (mode > 0) diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c index 2b8d590ed1..d73b319c5d 100644 --- a/src/core/mount-setup.c +++ b/src/core/mount-setup.c @@ -304,13 +304,18 @@ int mount_cgroup_controllers(char ***join_controllers) { return log_oom(); r = symlink(options, t); - if (r < 0 && errno != EEXIST) - return log_error_errno(errno, "Failed to create symlink %s: %m", t); + if (r >= 0) { #ifdef SMACK_RUN_LABEL - r = mac_smack_copy(t, options); - if (r < 0 && r != -EOPNOTSUPP) - return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", options, t); + _cleanup_free_ char *src; + src = strappend("/sys/fs/cgroup/", options); + if (!src) + return log_oom(); + r = mac_smack_copy(t, src); + if (r < 0 && r != -EOPNOTSUPP) + return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", src, t); #endif + } else if (errno != EEXIST) + return log_error_errno(errno, "Failed to create symlink %s: %m", t); } } } diff --git a/src/core/service.c b/src/core/service.c index 41a729c421..c5b689a35c 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -323,6 +323,8 @@ static void service_done(Unit *u) { s->bus_name = mfree(s->bus_name); } + s->bus_name_owner = mfree(s->bus_name_owner); + s->bus_endpoint_fd = safe_close(s->bus_endpoint_fd); service_close_socket_fd(s); service_connection_unref(s); @@ -2122,6 +2124,7 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known)); unit_serialize_item(u, f, "bus-name-good", yes_no(s->bus_name_good)); + unit_serialize_item(u, f, "bus-name-owner", s->bus_name_owner); r = unit_serialize_item_escaped(u, f, "status-text", s->status_text); if (r < 0) @@ -2249,6 +2252,10 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, log_unit_debug(u, "Failed to parse bus-name-good value: %s", value); else s->bus_name_good = b; + } else if (streq(key, "bus-name-owner")) { + r = free_and_strdup(&s->bus_name_owner, value); + if (r < 0) + log_unit_error_errno(u, r, "Unable to deserialize current bus owner %s: %m", value); } else if (streq(key, "status-text")) { char *t; @@ -3134,6 +3141,13 @@ static void service_bus_name_owner_change( s->bus_name_good = !!new_owner; + /* Track the current owner, so we can reconstruct changes after a daemon reload */ + r = free_and_strdup(&s->bus_name_owner, new_owner); + if (r < 0) { + log_unit_error_errno(u, r, "Unable to set new bus name owner %s: %m", new_owner); + return; + } + if (s->type == SERVICE_DBUS) { /* service_enter_running() will figure out what to diff --git a/src/core/service.h b/src/core/service.h index d0faad88e0..19efbccfc7 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -172,6 +172,7 @@ struct Service { bool reset_cpu_usage:1; char *bus_name; + char *bus_name_owner; /* unique name of the current owner */ char *status_text; int status_errno; diff --git a/src/core/socket.c b/src/core/socket.c index 7beec3644e..2e4173aabc 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -28,9 +28,9 @@ #include <sys/epoll.h> #include <sys/stat.h> #include <unistd.h> +#include <linux/sctp.h> #include "sd-event.h" - #include "alloc-util.h" #include "bus-error.h" #include "bus-util.h" @@ -156,14 +156,16 @@ static void socket_done(Unit *u) { s->tcp_congestion = mfree(s->tcp_congestion); s->bind_to_device = mfree(s->bind_to_device); - free(s->smack); - free(s->smack_ip_in); - free(s->smack_ip_out); + s->smack = mfree(s->smack); + s->smack_ip_in = mfree(s->smack_ip_in); + s->smack_ip_out = mfree(s->smack_ip_out); strv_free(s->symlinks); - free(s->user); - free(s->group); + s->user = mfree(s->user); + s->group = mfree(s->group); + + s->fdname = mfree(s->fdname); s->timer_event_source = sd_event_source_unref(s->timer_event_source); } @@ -875,8 +877,14 @@ static void socket_apply_socket_options(Socket *s, int fd) { if (s->no_delay) { int b = s->no_delay; - if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &b, sizeof(b)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_NODELAY failed: %m"); + + if (s->socket_protocol == IPPROTO_SCTP) { + if (setsockopt(fd, SOL_SCTP, SCTP_NODELAY, &b, sizeof(b)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SCTP_NODELAY failed: %m"); + } else { + if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &b, sizeof(b)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_NODELAY failed: %m"); + } } if (s->broadcast) { diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index d383041d39..84605fa267 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -212,7 +212,7 @@ try_dmi: unreliable enough, so let's not do any additional guesswork on top of that. - See the SMBIOS Specification 4.0 section 7.4.1 for + See the SMBIOS Specification 3.0 section 7.4.1 for details about the values listed here: https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index b2f5fbf6b4..3f93e85232 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -1181,6 +1181,7 @@ static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode, static int parse_config(void) { const ConfigTableItem items[] = { + { "Remote", "Seal", config_parse_bool, 0, &arg_seal }, { "Remote", "SplitMode", config_parse_write_split_mode, 0, &arg_split_mode }, { "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key }, { "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, diff --git a/src/journal-remote/journal-remote.conf.in b/src/journal-remote/journal-remote.conf.in index 3e32f34def..7122d63362 100644 --- a/src/journal-remote/journal-remote.conf.in +++ b/src/journal-remote/journal-remote.conf.in @@ -1,4 +1,5 @@ [Remote] +# Seal=false # SplitMode=host # ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem # ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem diff --git a/src/journal/compress.c b/src/journal/compress.c index 1a3d2cdd80..1828165894 100644 --- a/src/journal/compress.c +++ b/src/journal/compress.c @@ -58,7 +58,8 @@ static const char* const object_compressed_table[_OBJECT_COMPRESSED_MAX] = { DEFINE_STRING_TABLE_LOOKUP(object_compressed, int); -int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_size) { +int compress_blob_xz(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size) { #ifdef HAVE_XZ static const lzma_options_lzma opt = { 1u << 20u, NULL, 0, LZMA_LC_DEFAULT, LZMA_LP_DEFAULT, @@ -74,6 +75,7 @@ int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_ assert(src); assert(src_size > 0); assert(dst); + assert(dst_alloc_size > 0); assert(dst_size); /* Returns < 0 if we couldn't compress the data or the @@ -83,7 +85,7 @@ int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_ return -ENOBUFS; ret = lzma_stream_buffer_encode((lzma_filter*) filters, LZMA_CHECK_NONE, NULL, - src, src_size, dst, &out_pos, src_size - 1); + src, src_size, dst, &out_pos, dst_alloc_size); if (ret != LZMA_OK) return -ENOBUFS; @@ -94,13 +96,15 @@ int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_ #endif } -int compress_blob_lz4(const void *src, uint64_t src_size, void *dst, size_t *dst_size) { +int compress_blob_lz4(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size) { #ifdef HAVE_LZ4 int r; assert(src); assert(src_size > 0); assert(dst); + assert(dst_alloc_size > 0); assert(dst_size); /* Returns < 0 if we couldn't compress the data or the @@ -109,7 +113,7 @@ int compress_blob_lz4(const void *src, uint64_t src_size, void *dst, size_t *dst if (src_size < 9) return -ENOBUFS; - r = LZ4_compress_limitedOutput(src, dst + 8, src_size, src_size - 8 - 1); + r = LZ4_compress_limitedOutput(src, dst + 8, src_size, (int) dst_alloc_size - 8); if (r <= 0) return -ENOBUFS; @@ -306,6 +310,7 @@ int decompress_startswith_lz4(const void *src, uint64_t src_size, * prefix */ int r; + size_t size; assert(src); assert(src_size > 0); @@ -322,10 +327,18 @@ int decompress_startswith_lz4(const void *src, uint64_t src_size, r = LZ4_decompress_safe_partial(src + 8, *buffer, src_size - 8, prefix_len + 1, *buffer_size); + if (r >= 0) + size = (unsigned) r; + else { + /* lz4 always tries to decode full "sequence", so in + * pathological cases might need to decompress the + * full field. */ + r = decompress_blob_lz4(src, src_size, buffer, buffer_size, &size, 0); + if (r < 0) + return r; + } - if (r < 0) - return -EBADMSG; - if ((unsigned) r >= prefix_len + 1) + if (size >= prefix_len + 1) return memcmp(*buffer, prefix, prefix_len) == 0 && ((const uint8_t*) *buffer)[prefix_len] == extra; else @@ -438,7 +451,7 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { _cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL; _cleanup_free_ char *buf = NULL; char *src = NULL; - size_t size, n, total_in = 0, total_out = 0, offset = 0, frame_size; + size_t size, n, total_in = 0, total_out, offset = 0, frame_size; struct stat st; int r; static const LZ4F_compressOptions_t options = { @@ -461,7 +474,7 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { if (!buf) return -ENOMEM; - n = offset = LZ4F_compressBegin(ctx, buf, size, &preferences); + n = offset = total_out = LZ4F_compressBegin(ctx, buf, size, &preferences); if (LZ4F_isError(n)) return -EINVAL; diff --git a/src/journal/compress.h b/src/journal/compress.h index 9a065eb763..758598730a 100644 --- a/src/journal/compress.h +++ b/src/journal/compress.h @@ -28,17 +28,20 @@ const char* object_compressed_to_string(int compression); int object_compressed_from_string(const char *compression); -int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_size); -int compress_blob_lz4(const void *src, uint64_t src_size, void *dst, size_t *dst_size); +int compress_blob_xz(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size); +int compress_blob_lz4(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size); -static inline int compress_blob(const void *src, uint64_t src_size, void *dst, size_t *dst_size) { +static inline int compress_blob(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size) { int r; #ifdef HAVE_LZ4 - r = compress_blob_lz4(src, src_size, dst, dst_size); + r = compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size); if (r == 0) return OBJECT_COMPRESSED_LZ4; #else - r = compress_blob_xz(src, src_size, dst, dst_size); + r = compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size); if (r == 0) return OBJECT_COMPRESSED_XZ; #endif diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c index 6f09301521..9e362bacae 100644 --- a/src/journal/journal-file.c +++ b/src/journal/journal-file.c @@ -1084,7 +1084,7 @@ static int journal_file_append_data( if (JOURNAL_FILE_COMPRESS(f) && size >= COMPRESSION_SIZE_THRESHOLD) { size_t rsize = 0; - compression = compress_blob(data, size, o->data.payload, &rsize); + compression = compress_blob(data, size, o->data.payload, size - 1, &rsize); if (compression >= 0) { o->object.size = htole64(offsetof(Object, data.payload) + rsize); diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c index 5cde7f17f7..cd5160154a 100644 --- a/src/journal/sd-journal.c +++ b/src/journal/sd-journal.c @@ -1940,10 +1940,14 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** compression = o->object.flags & OBJECT_COMPRESSION_MASK; if (compression) { #if defined(HAVE_XZ) || defined(HAVE_LZ4) - if (decompress_startswith(compression, + r = decompress_startswith(compression, o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, - field, field_length, '=')) { + field, field_length, '='); + if (r < 0) + log_debug_errno(r, "Cannot decompress %s object of length %zu at offset "OFSfmt": %m", + object_compressed_to_string(compression), l, p); + else if (r > 0) { size_t rsize; diff --git a/src/journal/test-compress-benchmark.c b/src/journal/test-compress-benchmark.c index 93ea9c6318..baed0d82a4 100644 --- a/src/journal/test-compress-benchmark.c +++ b/src/journal/test-compress-benchmark.c @@ -27,7 +27,8 @@ #include "string-util.h" #include "util.h" -typedef int (compress_t)(const void *src, uint64_t src_size, void *dst, size_t *dst_size); +typedef int (compress_t)(const void *src, uint64_t src_size, void *dst, + size_t dst_alloc_size, size_t *dst_size); typedef int (decompress_t)(const void *src, uint64_t src_size, void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); @@ -111,8 +112,8 @@ static void test_compress_decompress(const char* label, const char* type, memzero(buf, MIN(size + 1000, MAX_SIZE)); - r = compress(text, size, buf, &j); - /* assume compression must be successful except for small inputs */ + r = compress(text, size, buf, size, &j); + /* assume compression must be successful except for small or random inputs */ assert_se(r == 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random")); /* check for overwrites */ diff --git a/src/journal/test-compress.c b/src/journal/test-compress.c index b9d90a8988..68c9a4d76c 100644 --- a/src/journal/test-compress.c +++ b/src/journal/test-compress.c @@ -17,6 +17,10 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#ifdef HAVE_LZ4 +#include <lz4.h> +#endif + #include "alloc-util.h" #include "compress.h" #include "fd-util.h" @@ -38,7 +42,7 @@ #endif typedef int (compress_blob_t)(const void *src, uint64_t src_size, - void *dst, size_t *dst_size); + void *dst, size_t dst_alloc_size, size_t *dst_size); typedef int (decompress_blob_t)(const void *src, uint64_t src_size, void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); @@ -57,15 +61,14 @@ static void test_compress_decompress(int compression, size_t data_len, bool may_fail) { char compressed[512]; - size_t csize = 512; - size_t usize = 0; + size_t csize, usize = 0; _cleanup_free_ char *decompressed = NULL; int r; log_info("/* testing %s %s blob compression/decompression */", object_compressed_to_string(compression), data); - r = compress(data, data_len, compressed, &csize); + r = compress(data, data_len, compressed, sizeof(compressed), &csize); if (r == -ENOBUFS) { log_info_errno(r, "compression failed: %m"); assert_se(may_fail); @@ -101,43 +104,45 @@ static void test_decompress_startswith(int compression, size_t data_len, bool may_fail) { - char compressed[512]; - size_t csize = 512; - size_t usize = 0; - _cleanup_free_ char *decompressed = NULL; + char *compressed; + _cleanup_free_ char *compressed1 = NULL, *compressed2 = NULL, *decompressed = NULL; + size_t csize, usize = 0, len; int r; - log_info("/* testing decompress_startswith with %s on %s text*/", + log_info("/* testing decompress_startswith with %s on %.20s text*/", object_compressed_to_string(compression), data); - r = compress(data, data_len, compressed, &csize); +#define BUFSIZE_1 512 +#define BUFSIZE_2 20000 + + compressed = compressed1 = malloc(BUFSIZE_1); + assert_se(compressed1); + r = compress(data, data_len, compressed, BUFSIZE_1, &csize); if (r == -ENOBUFS) { log_info_errno(r, "compression failed: %m"); assert_se(may_fail); - return; + + compressed = compressed2 = malloc(BUFSIZE_2); + assert_se(compressed2); + r = compress(data, data_len, compressed, BUFSIZE_2, &csize); + assert(r == 0); } assert_se(r == 0); - assert_se(decompress_sw(compressed, - csize, - (void **) &decompressed, - &usize, - data, strlen(data), '\0') > 0); - assert_se(decompress_sw(compressed, - csize, - (void **) &decompressed, - &usize, - data, strlen(data), 'w') == 0); - assert_se(decompress_sw(compressed, - csize, - (void **) &decompressed, - &usize, - "barbarbar", 9, ' ') == 0); - assert_se(decompress_sw(compressed, - csize, - (void **) &decompressed, - &usize, - data, strlen(data), '\0') > 0); + len = strlen(data); + + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0'); + assert_se(r > 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, 'w'); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, "barbarbar", 9, ' '); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, data[len-1]); + assert_se(r > 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, 'w'); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0'); + assert_se(r > 0); } static void test_compress_stream(int compression, @@ -199,6 +204,44 @@ static void test_compress_stream(int compression, assert_se(unlink(pattern2) == 0); } +#ifdef HAVE_LZ4 +static void test_lz4_decompress_partial(void) { + char buf[20000]; + size_t buf_size = sizeof(buf), compressed; + int r; + _cleanup_free_ char *huge = NULL; + +#define HUGE_SIZE (4096*1024) + huge = malloc(HUGE_SIZE); + memset(huge, 'x', HUGE_SIZE); + memcpy(huge, "HUGE=", 5); + + r = LZ4_compress_limitedOutput(huge, buf, HUGE_SIZE, buf_size); + assert_se(r >= 0); + compressed = r; + log_info("Compressed %i → %zu", HUGE_SIZE, compressed); + + r = LZ4_decompress_safe(buf, huge, r, HUGE_SIZE); + assert_se(r >= 0); + log_info("Decompressed → %i", r); + + r = LZ4_decompress_safe_partial(buf, huge, + compressed, + 12, HUGE_SIZE); + assert_se(r >= 0); + log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE, r); + + /* We expect this to fail, because that's how current lz4 works. If this + * call succeeds, then lz4 has been fixed, and we need to change our code. + */ + r = LZ4_decompress_safe_partial(buf, huge, + compressed, + 12, HUGE_SIZE-1); + assert_se(r < 0); + log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE-1, r); +} +#endif + int main(int argc, char *argv[]) { const char text[] = "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF" @@ -206,6 +249,11 @@ int main(int argc, char *argv[]) { char data[512] = "random\0"; + char huge[4096*1024]; + memset(huge, 'x', sizeof(huge)); + memcpy(huge, "HUGE=", 5); + char_array_0(huge); + log_set_max_level(LOG_DEBUG); random_bytes(data + 7, sizeof(data) - 7); @@ -215,12 +263,17 @@ int main(int argc, char *argv[]) { text, sizeof(text), false); test_compress_decompress(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_blob_xz, data, sizeof(data), true); + test_decompress_startswith(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_startswith_xz, text, sizeof(text), false); test_decompress_startswith(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_startswith_xz, data, sizeof(data), true); + test_decompress_startswith(OBJECT_COMPRESSED_XZ, + compress_blob_xz, decompress_startswith_xz, + huge, sizeof(huge), true); + test_compress_stream(OBJECT_COMPRESSED_XZ, "xzcat", compress_stream_xz, decompress_stream_xz, argv[0]); #else @@ -232,15 +285,21 @@ int main(int argc, char *argv[]) { text, sizeof(text), false); test_compress_decompress(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_blob_lz4, data, sizeof(data), true); + test_decompress_startswith(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_startswith_lz4, text, sizeof(text), false); test_decompress_startswith(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_startswith_lz4, data, sizeof(data), true); + test_decompress_startswith(OBJECT_COMPRESSED_LZ4, + compress_blob_lz4, decompress_startswith_lz4, + huge, sizeof(huge), true); test_compress_stream(OBJECT_COMPRESSED_LZ4, "lz4cat", compress_stream_lz4, decompress_stream_lz4, argv[0]); + + test_lz4_decompress_partial(); #else log_info("/* LZ4 test skipped */"); #endif diff --git a/src/libsystemd-network/sd-lldp.c b/src/libsystemd-network/sd-lldp.c index d3ea74404b..1c696f9ef0 100644 --- a/src/libsystemd-network/sd-lldp.c +++ b/src/libsystemd-network/sd-lldp.c @@ -145,12 +145,9 @@ static int lldp_receive_frame(sd_lldp *lldp, tlv_packet *tlv) { /* 10.3.2 LLDPDU validation: rxProcessFrame() */ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { + bool system_description = false, system_name = false, chassis_id = false; + bool malformed = false, port_id = false, ttl = false, end = false; uint16_t type, len, i, l, t; - bool chassis_id = false; - bool malformed = false; - bool port_id = false; - bool ttl = false; - bool end = false; lldp_port *port; uint8_t *p, *q; sd_lldp *lldp; @@ -163,8 +160,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { lldp = (sd_lldp *) port->userdata; if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) { - log_lldp("Port is disabled : %s . Dropping ...", - lldp->port->ifname); + log_lldp("Port: %s is disabled. Dropping.", lldp->port->ifname); goto out; } @@ -182,8 +178,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { if (type == LLDP_TYPE_END) { if (len != 0) { - log_lldp("TLV type end is not length 0. Length:%d received . Dropping ...", - len); + log_lldp("TLV type end must be length 0 (not %d). Dropping.", len); malformed = true; goto out; @@ -193,8 +188,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { break; } else if (type >=_LLDP_TYPE_MAX) { - log_lldp("TLV type not recognized %d . Dropping ...", - type); + log_lldp("TLV type: %d not recognized. Dropping.", type); malformed = true; goto out; @@ -209,7 +203,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { if (i <= 3) { if (i != type) { - log_lldp("TLV missing or out of order. Dropping ..."); + log_lldp("TLV missing or out of order. Dropping."); malformed = true; goto out; @@ -220,25 +214,22 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { case LLDP_TYPE_CHASSIS_ID: if (len < 2) { - log_lldp("Received malformed Chassis ID TLV len = %d. Dropping", - len); + log_lldp("Received malformed Chassis ID TLV length: %d. Dropping.", len); malformed = true; goto out; } if (chassis_id) { - log_lldp("Duplicate Chassis ID TLV found. Dropping ..."); + log_lldp("Duplicate Chassis ID TLV found. Dropping."); malformed = true; goto out; } /* Look what subtype it has */ - if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED || - *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) { - log_lldp("Unknown subtype: %d found in Chassis ID TLV . Dropping ...", - *q); + if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED || *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) { + log_lldp("Unknown subtype: %d found in Chassis ID TLV. Dropping.", *q); malformed = true; goto out; @@ -251,25 +242,22 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { case LLDP_TYPE_PORT_ID: if (len < 2) { - log_lldp("Received malformed Port ID TLV len = %d. Dropping", - len); + log_lldp("Received malformed Port ID TLV length: %d. Dropping.", len); malformed = true; goto out; } if (port_id) { - log_lldp("Duplicate Port ID TLV found. Dropping ..."); + log_lldp("Duplicate Port ID TLV found. Dropping."); malformed = true; goto out; } /* Look what subtype it has */ - if (*q == LLDP_PORT_SUBTYPE_RESERVED || - *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) { - log_lldp("Unknown subtype: %d found in Port ID TLV . Dropping ...", - *q); + if (*q == LLDP_PORT_SUBTYPE_RESERVED || *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) { + log_lldp("Unknown subtype: %d found in Port ID TLV. Dropping.", *q); malformed = true; goto out; @@ -282,16 +270,14 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { case LLDP_TYPE_TTL: if(len != 2) { - log_lldp( - "Received invalid lenth: %d TTL TLV. Dropping ...", - len); + log_lldp("Received invalid TTL TLV lenth: %d. Dropping.", len); malformed = true; goto out; } if (ttl) { - log_lldp("Duplicate TTL TLV found. Dropping ..."); + log_lldp("Duplicate TTL TLV found. Dropping."); malformed = true; goto out; @@ -300,11 +286,45 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { ttl = true; break; + case LLDP_TYPE_SYSTEM_NAME: + + /* According to RFC 1035 the length of a FQDN is limited to 255 characters */ + if (len > 255) { + log_lldp("Received invalid system name length: %d. Dropping.", len); + malformed = true; + goto out; + } + + if (system_name) { + log_lldp("Duplicate system name found. Dropping."); + malformed = true; + goto out; + } + + system_name = true; + + break; + case LLDP_TYPE_SYSTEM_DESCRIPTION: + + /* 0 <= n <= 255 octets */ + if (len > 255) { + log_lldp("Received invalid system description length: %d. Dropping.", len); + malformed = true; + goto out; + } + + if (system_description) { + log_lldp("Duplicate system description found. Dropping."); + malformed = true; + goto out; + } + + system_description = true; + break; default: if (len == 0) { - log_lldp("TLV type = %d's, length 0 received . Dropping ...", - type); + log_lldp("TLV type: %d length 0 received. Dropping.", type); malformed = true; goto out; @@ -314,7 +334,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { } if(!chassis_id || !port_id || !ttl || !end) { - log_lldp( "One or more mandotory TLV missing . Dropping ..."); + log_lldp("One or more mandatory TLV missing. Dropping."); malformed = true; goto out; @@ -323,7 +343,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { r = tlv_packet_parse_pdu(tlv, length); if (r < 0) { - log_lldp( "Failed to parse the TLV. Dropping ..."); + log_lldp("Failed to parse the TLV. Dropping."); malformed = true; goto out; diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 8d486fcbbd..6e2594d001 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -67,12 +67,14 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_PROCESS, ESRCH), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_NAME_SERVERS, EIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_NAME_SERVERS, ESRCH), SD_BUS_ERROR_MAP(BUS_ERROR_INVALID_REPLY, EINVAL), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_RR, ENOENT), SD_BUS_ERROR_MAP(BUS_ERROR_NO_RESOURCES, ENOMEM), SD_BUS_ERROR_MAP(BUS_ERROR_CNAME_LOOP, EDEADLK), SD_BUS_ERROR_MAP(BUS_ERROR_ABORTED, ECANCELED), + SD_BUS_ERROR_MAP(BUS_ERROR_CONNECTION_FAILURE, ECONNREFUSED), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SERVICE, EUNATCH), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index f2092795f4..9e49725843 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -72,6 +72,10 @@ #define BUS_ERROR_NO_RESOURCES "org.freedesktop.resolve1.NoResources" #define BUS_ERROR_CNAME_LOOP "org.freedesktop.resolve1.CNameLoop" #define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted" +#define BUS_ERROR_CONNECTION_FAILURE "org.freedesktop.resolve1.ConnectionFailure" +#define BUS_ERROR_NO_SUCH_SERVICE "org.freedesktop.resolve1.NoSuchService" +#define BUS_ERROR_DNSSEC_FAILED "org.freedesktop.resolve1.DnssecFailed" +#define BUS_ERROR_NO_TRUST_ANCHOR "org.freedesktop.resolve1.NoTrustAnchor" #define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." #define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer" diff --git a/src/libsystemd/sd-bus/test-bus-cleanup.c b/src/libsystemd/sd-bus/test-bus-cleanup.c index 1c3ccda364..cbc450fdb2 100644 --- a/src/libsystemd/sd-bus/test-bus-cleanup.c +++ b/src/libsystemd/sd-bus/test-bus-cleanup.c @@ -36,7 +36,7 @@ static void test_bus_new(void) { } static int test_bus_open(void) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; r = sd_bus_open_system(&bus); @@ -59,8 +59,8 @@ static void test_bus_new_method_call(void) { printf("after message_new_method_call: refcount %u\n", REFCNT_GET(bus->n_ref)); - sd_bus_unref(bus); - printf("after bus_unref: refcount %u\n", m->n_ref); + sd_bus_flush_close_unref(bus); + printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); } static void test_bus_new_signal(void) { @@ -73,8 +73,8 @@ static void test_bus_new_signal(void) { printf("after message_new_signal: refcount %u\n", REFCNT_GET(bus->n_ref)); - sd_bus_unref(bus); - printf("after bus_unref: refcount %u\n", m->n_ref); + sd_bus_flush_close_unref(bus); + printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); } int main(int argc, char **argv) { diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index 077cc6ddac..b9d1ea5217 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -246,6 +246,8 @@ int main(int argc, char *argv[]) { log_error("%s", error.message); else dbus_message_unref(w); + + dbus_error_free(&error); } #endif diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 3191b458d1..7fccbd1a71 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -37,6 +37,7 @@ #include "process-util.h" #include "set.h" #include "signal-util.h" +#include "string-table.h" #include "string-util.h" #include "time-util.h" #include "util.h" @@ -60,6 +61,23 @@ typedef enum EventSourceType { _SOURCE_EVENT_SOURCE_TYPE_INVALID = -1 } EventSourceType; +static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = { + [SOURCE_IO] = "io", + [SOURCE_TIME_REALTIME] = "realtime", + [SOURCE_TIME_BOOTTIME] = "bootime", + [SOURCE_TIME_MONOTONIC] = "monotonic", + [SOURCE_TIME_REALTIME_ALARM] = "realtime-alarm", + [SOURCE_TIME_BOOTTIME_ALARM] = "boottime-alarm", + [SOURCE_SIGNAL] = "signal", + [SOURCE_CHILD] = "child", + [SOURCE_DEFER] = "defer", + [SOURCE_POST] = "post", + [SOURCE_EXIT] = "exit", + [SOURCE_WATCHDOG] = "watchdog", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); + /* All objects we use in epoll events start with this value, so that * we know how to dispatch it */ typedef enum WakeupType { @@ -482,7 +500,8 @@ static void source_io_unregister(sd_event_source *s) { r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL); if (r < 0) - log_debug_errno(errno, "Failed to remove source %s from epoll: %m", strna(s->description)); + log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll: %m", + strna(s->description), event_source_type_to_string(s->type)); s->io.registered = false; } @@ -2281,12 +2300,9 @@ static int source_dispatch(sd_event_source *s) { s->dispatching = false; - if (r < 0) { - if (s->description) - log_debug_errno(r, "Event source '%s' returned error, disabling: %m", s->description); - else - log_debug_errno(r, "Event source %p returned error, disabling: %m", s); - } + if (r < 0) + log_debug_errno(r, "Event source %s (type %s) returned error, disabling: %m", + strna(s->description), event_source_type_to_string(s->type)); if (s->n_ref == 0) source_free(s); @@ -2319,12 +2335,9 @@ static int event_prepare(sd_event *e) { r = s->prepare(s, s->userdata); s->dispatching = false; - if (r < 0) { - if (s->description) - log_debug_errno(r, "Prepare callback of event source '%s' returned error, disabling: %m", s->description); - else - log_debug_errno(r, "Prepare callback of event source %p returned error, disabling: %m", s); - } + if (r < 0) + log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, disabling: %m", + strna(s->description), event_source_type_to_string(s->type)); if (s->n_ref == 0) source_free(s); diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c index efbceba83d..c1f5867ee4 100644 --- a/src/libsystemd/sd-network/sd-network.c +++ b/src/libsystemd/sd-network/sd-network.c @@ -99,17 +99,17 @@ _public_ int sd_network_get_domains(char ***ret) { return network_get_strv("DOMAINS", ret); } -_public_ int sd_network_link_get_setup_state(int ifindex, char **state) { +static int network_link_get_string(int ifindex, const char *field, char **ret) { _cleanup_free_ char *s = NULL, *p = NULL; int r; assert_return(ifindex > 0, -EINVAL); - assert_return(state, -EINVAL); + assert_return(ret, -EINVAL); - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) + if (asprintf(&p, "/run/systemd/netif/links/%i", ifindex) < 0) return -ENOMEM; - r = parse_env_file(p, NEWLINE, "ADMIN_STATE", &s, NULL); + r = parse_env_file(p, NEWLINE, field, &s, NULL); if (r == -ENOENT) return -ENODATA; if (r < 0) @@ -117,82 +117,72 @@ _public_ int sd_network_link_get_setup_state(int ifindex, char **state) { if (isempty(s)) return -ENODATA; - *state = s; + *ret = s; s = NULL; return 0; } -_public_ int sd_network_link_get_network_file(int ifindex, char **filename) { - _cleanup_free_ char *s = NULL, *p = NULL; +static int network_link_get_strv(int ifindex, const char *key, char ***ret) { + _cleanup_free_ char *p = NULL, *s = NULL; + _cleanup_strv_free_ char **a = NULL; int r; assert_return(ifindex > 0, -EINVAL); - assert_return(filename, -EINVAL); + assert_return(ret, -EINVAL); if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) return -ENOMEM; - r = parse_env_file(p, NEWLINE, "NETWORK_FILE", &s, NULL); + r = parse_env_file(p, NEWLINE, key, &s, NULL); if (r == -ENOENT) return -ENODATA; if (r < 0) return r; - if (isempty(s)) - return -ENODATA; - - *filename = s; - s = NULL; + if (isempty(s)) { + *ret = NULL; + return 0; + } - return 0; -} + a = strv_split(s, " "); + if (!a) + return -ENOMEM; -_public_ int sd_network_link_get_operational_state(int ifindex, char **state) { - _cleanup_free_ char *s = NULL, *p = NULL; - int r; + strv_uniq(a); + r = strv_length(a); - assert_return(ifindex > 0, -EINVAL); - assert_return(state, -EINVAL); + *ret = a; + a = NULL; - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; + return r; +} - r = parse_env_file(p, NEWLINE, "OPER_STATE", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; +_public_ int sd_network_link_get_setup_state(int ifindex, char **state) { + return network_link_get_string(ifindex, "ADMIN_STATE", state); +} - *state = s; - s = NULL; +_public_ int sd_network_link_get_network_file(int ifindex, char **filename) { + return network_link_get_string(ifindex, "NETWORK_FILE", filename); +} - return 0; +_public_ int sd_network_link_get_operational_state(int ifindex, char **state) { + return network_link_get_string(ifindex, "OPER_STATE", state); } _public_ int sd_network_link_get_llmnr(int ifindex, char **llmnr) { - _cleanup_free_ char *s = NULL, *p = NULL; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(llmnr, -EINVAL); - - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; + return network_link_get_string(ifindex, "LLMNR", llmnr); +} - r = parse_env_file(p, NEWLINE, "LLMNR", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; +_public_ int sd_network_link_get_mdns(int ifindex, char **mdns) { + return network_link_get_string(ifindex, "MDNS", mdns); +} - *llmnr = s; - s = NULL; +_public_ int sd_network_link_get_dnssec(int ifindex, char **dnssec) { + return network_link_get_string(ifindex, "DNSSEC", dnssec); +} - return 0; +_public_ int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta) { + return network_link_get_strv(ifindex, "DNSSEC_NTA", nta); } _public_ int sd_network_link_get_lldp(int ifindex, char **lldp) { @@ -221,85 +211,32 @@ _public_ int sd_network_link_get_lldp(int ifindex, char **lldp) { } int sd_network_link_get_timezone(int ifindex, char **ret) { - _cleanup_free_ char *s = NULL, *p = NULL; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(ret, -EINVAL); - - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; - - r = parse_env_file(p, NEWLINE, "TIMEZONE", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - *ret = s; - s = NULL; - return 0; -} - -static int network_get_link_strv(const char *key, int ifindex, char ***ret) { - _cleanup_free_ char *p = NULL, *s = NULL; - _cleanup_strv_free_ char **a = NULL; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(ret, -EINVAL); - - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; - - r = parse_env_file(p, NEWLINE, key, &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) { - *ret = NULL; - return 0; - } - - a = strv_split(s, " "); - if (!a) - return -ENOMEM; - - strv_uniq(a); - r = strv_length(a); - - *ret = a; - a = NULL; - - return r; + return network_link_get_string(ifindex, "TIMEZONE", ret); } _public_ int sd_network_link_get_dns(int ifindex, char ***ret) { - return network_get_link_strv("DNS", ifindex, ret); + return network_link_get_strv(ifindex, "DNS", ret); } _public_ int sd_network_link_get_ntp(int ifindex, char ***ret) { - return network_get_link_strv("NTP", ifindex, ret); + return network_link_get_strv(ifindex, "NTP", ret); } _public_ int sd_network_link_get_domains(int ifindex, char ***ret) { - return network_get_link_strv("DOMAINS", ifindex, ret); + return network_link_get_strv(ifindex, "DOMAINS", ret); } _public_ int sd_network_link_get_carrier_bound_to(int ifindex, char ***ret) { - return network_get_link_strv("CARRIER_BOUND_TO", ifindex, ret); + return network_link_get_strv(ifindex, "CARRIER_BOUND_TO", ret); } _public_ int sd_network_link_get_carrier_bound_by(int ifindex, char ***ret) { - return network_get_link_strv("CARRIER_BOUND_BY", ifindex, ret); + return network_link_get_strv(ifindex, "CARRIER_BOUND_BY", ret); } _public_ int sd_network_link_get_wildcard_domain(int ifindex) { - int r; _cleanup_free_ char *p = NULL, *s = NULL; + int r; assert_return(ifindex > 0, -EINVAL); diff --git a/src/libsystemd/sd-resolve/test-resolve.c b/src/libsystemd/sd-resolve/test-resolve.c index e78a75c9ea..ce97e81ed6 100644 --- a/src/libsystemd/sd-resolve/test-resolve.c +++ b/src/libsystemd/sd-resolve/test-resolve.c @@ -101,11 +101,11 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "sd_resolve_getnameinfo(): %m"); - /* Wait until the two queries are completed */ - while (sd_resolve_query_is_done(q1) == 0 || - sd_resolve_query_is_done(q2) == 0) { - + /* Wait until all queries are completed */ + for (;;) { r = sd_resolve_wait(resolve, (uint64_t) -1); + if (r == 0) + break; if (r < 0) { log_error_errno(r, "sd_resolve_wait(): %m"); assert_not_reached("sd_resolve_wait() failed"); diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index c1643cf41a..4631f5fc90 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -1944,9 +1944,9 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ action_multiple_sessions = "org.freedesktop.login1.halt-multiple-sessions"; action_ignore_inhibit = "org.freedesktop.login1.halt-ignore-inhibit"; } else if (streq(type, "poweroff")) { - action = "org.freedesktop.login1.poweroff"; - action_multiple_sessions = "org.freedesktop.login1.poweroff-multiple-sessions"; - action_ignore_inhibit = "org.freedesktop.login1.poweroff-ignore-inhibit"; + action = "org.freedesktop.login1.power-off"; + action_multiple_sessions = "org.freedesktop.login1.power-off-multiple-sessions"; + action_ignore_inhibit = "org.freedesktop.login1.power-off-ignore-inhibit"; } else return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unsupported shutdown type"); diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 9f7c9952d3..03d32c6ed7 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -922,7 +922,7 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu First, we start by creating a private playground in /tmp, that we can mount MS_SLAVE. (Which is necessary, since - MS_MOUNT cannot be applied to mounts with MS_SHARED parent + MS_MOVE cannot be applied to mounts with MS_SHARED parent mounts.) */ if (!mkdtemp(mount_slave)) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 9811526c6d..4a807bacc3 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2868,6 +2868,26 @@ int link_save(Link *link) { fprintf(f, "LLMNR=%s\n", resolve_support_to_string(link->network->llmnr)); + fprintf(f, "MDNS=%s\n", + resolve_support_to_string(link->network->mdns)); + + if (link->network->dnssec_mode != _DNSSEC_MODE_INVALID) + fprintf(f, "DNSSEC=%s\n", + dnssec_mode_to_string(link->network->dnssec_mode)); + + if (!set_isempty(link->network->dnssec_negative_trust_anchors)) { + const char *n; + + fputs("DNSSEC_NTA=", f); + space = false; + SET_FOREACH(n, link->network->dnssec_negative_trust_anchors, i) { + if (space) + fputc(' ', f); + fputs(n, f); + space = true; + } + fputc('\n', f); + } fputs("ADDRESSES=", f); space = false; @@ -2881,7 +2901,6 @@ int link_save(Link *link) { fprintf(f, "%s%s/%u", space ? " " : "", address_str, a->prefixlen); space = true; } - fputc('\n', f); fputs("ROUTES=", f); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index de2c66d153..2f2a36ccca 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -45,7 +45,10 @@ Network.Address, config_parse_address, Network.Gateway, config_parse_gateway, 0, 0 Network.Domains, config_parse_domains, 0, offsetof(Network, domains) Network.DNS, config_parse_strv, 0, offsetof(Network, dns) -Network.LLMNR, config_parse_resolve, 0, offsetof(Network, llmnr) +Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr) +Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns) +Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode) +Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, offsetof(Network, dnssec_negative_trust_anchors) Network.NTP, config_parse_strv, 0, offsetof(Network, ntp) Network.IPForward, config_parse_address_family_boolean_with_kernel,0, offsetof(Network, ip_forward) Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 29723a852f..c11cb3dcb3 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -32,6 +32,7 @@ #include "networkd-network.h" #include "networkd.h" #include "parse-util.h" +#include "set.h" #include "stat-util.h" #include "string-table.h" #include "string-util.h" @@ -121,6 +122,8 @@ static int network_load_one(Manager *manager, const char *filename) { network->unicast_flood = true; network->llmnr = RESOLVE_SUPPORT_YES; + network->mdns = RESOLVE_SUPPORT_NO; + network->dnssec_mode = _DNSSEC_MODE_INVALID; network->link_local = ADDRESS_FAMILY_IPV6; @@ -275,6 +278,8 @@ void network_free(Network *network) { free(network->dhcp_server_dns); free(network->dhcp_server_ntp); + set_free_free(network->dnssec_negative_trust_anchors); + free(network); } @@ -908,3 +913,55 @@ int config_parse_dhcp_server_ntp( n->dhcp_server_ntp = m; } } + +int config_parse_dnssec_negative_trust_anchors( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + const char *p = rvalue; + Network *n = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + n->dnssec_negative_trust_anchors = set_free_free(n->dnssec_negative_trust_anchors); + return 0; + } + + for (;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, 0); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract negative trust anchor domain, ignoring: %s", rvalue); + break; + } + if (r == 0) + break; + + r = dns_name_is_valid(w); + if (r <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "%s is not a valid domain name, ignoring.", w); + continue; + } + + r = set_put(n->dnssec_negative_trust_anchors, w); + if (r < 0) + return log_oom(); + if (r > 0) + w = NULL; + } + + return 0; +} diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index cb3a50d9ba..b07fa41abc 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -22,6 +22,7 @@ ***/ #include "condition.h" +#include "resolve-util.h" typedef struct Network Network; @@ -144,6 +145,9 @@ struct Network { char **domains, **dns, **ntp, **bind_carrier; ResolveSupport llmnr; + ResolveSupport mdns; + DnssecMode dnssec_mode; + Set *dnssec_negative_trust_anchors; LIST_FIELDS(Network, networks); }; @@ -170,6 +174,7 @@ int config_parse_hostname(const char *unit, const char *filename, unsigned line, int config_parse_timezone(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_dhcp_server_dns(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_dhcp_server_ntp(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_dnssec_negative_trust_anchors(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); /* Legacy IPv4LL support */ int config_parse_ipv4ll(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/network/networkd-util.c b/src/network/networkd-util.c index 2545621a93..93135bb658 100644 --- a/src/network/networkd-util.c +++ b/src/network/networkd-util.c @@ -101,54 +101,3 @@ int config_parse_address_family_boolean_with_kernel( return 0; } - -static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = { - [RESOLVE_SUPPORT_NO] = "no", - [RESOLVE_SUPPORT_YES] = "yes", - [RESOLVE_SUPPORT_RESOLVE] = "resolve", -}; - -DEFINE_STRING_TABLE_LOOKUP(resolve_support, ResolveSupport); - -int config_parse_resolve( - const char* unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ResolveSupport *resolve = data; - int k; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(resolve); - - /* Our enum shall be a superset of booleans, hence first try - * to parse as boolean, and then as enum */ - - k = parse_boolean(rvalue); - if (k > 0) - *resolve = RESOLVE_SUPPORT_YES; - else if (k == 0) - *resolve = RESOLVE_SUPPORT_NO; - else { - ResolveSupport s; - - s = resolve_support_from_string(rvalue); - if (s < 0){ - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse %s= option, ignoring: %s", lvalue, rvalue); - return 0; - } - - *resolve = s; - } - - return 0; -} diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h index cc41aae85a..021ce4b128 100644 --- a/src/network/networkd-util.h +++ b/src/network/networkd-util.h @@ -33,20 +33,8 @@ typedef enum AddressFamilyBoolean { _ADDRESS_FAMILY_BOOLEAN_INVALID = -1, } AddressFamilyBoolean; -typedef enum ResolveSupport { - RESOLVE_SUPPORT_NO, - RESOLVE_SUPPORT_YES, - RESOLVE_SUPPORT_RESOLVE, - _RESOLVE_SUPPORT_MAX, - _RESOLVE_SUPPORT_INVALID = -1, -} ResolveSupport; - -int config_parse_resolve(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_address_family_boolean(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_address_family_boolean_with_kernel(const char* unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -const char* resolve_support_to_string(ResolveSupport i) _const_; -ResolveSupport resolve_support_from_string(const char *s) _pure_; - const char *address_family_boolean_to_string(AddressFamilyBoolean b) _const_; AddressFamilyBoolean address_family_boolean_from_string(const char *s) _const_; diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c index 270bcf010f..3c0e26ea5a 100644 --- a/src/nspawn/nspawn-cgroup.c +++ b/src/nspawn/nspawn-cgroup.c @@ -54,6 +54,7 @@ int chown_cgroup(pid_t pid, uid_t uid_shift) { "tasks", "notify_on_release", "cgroup.procs", + "cgroup.events", "cgroup.clone_children", "cgroup.controllers", "cgroup.subtree_control", diff --git a/src/nspawn/nspawn-setuid.c b/src/nspawn/nspawn-setuid.c index aa6a16309c..014a40b243 100644 --- a/src/nspawn/nspawn-setuid.c +++ b/src/nspawn/nspawn-setuid.c @@ -261,10 +261,10 @@ int change_uid_gid(const char *user, char **_home) { return log_error_errno(errno, "Failed to set auxiliary groups: %m"); if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "setregid() failed: %m"); + return log_error_errno(errno, "setresgid() failed: %m"); if (setresuid(uid, uid, uid) < 0) - return log_error_errno(errno, "setreuid() failed: %m"); + return log_error_errno(errno, "setresuid() failed: %m"); if (_home) { *_home = home; diff --git a/src/resolve-host/resolve-host.c b/src/resolve-host/resolve-host.c index 0f154d9798..2cabfeaefa 100644 --- a/src/resolve-host/resolve-host.c +++ b/src/resolve-host/resolve-host.c @@ -33,16 +33,24 @@ #include "parse-util.h" #include "resolved-def.h" #include "resolved-dns-packet.h" +#include "terminal-util.h" #define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) static int arg_family = AF_UNSPEC; static int arg_ifindex = 0; -static int arg_type = 0; +static uint16_t arg_type = 0; static uint16_t arg_class = 0; static bool arg_legend = true; static uint64_t arg_flags = 0; -static bool arg_resolve_service = false; + +static enum { + MODE_RESOLVE_HOST, + MODE_RESOLVE_RECORD, + MODE_RESOLVE_SERVICE, + MODE_STATISTICS, + MODE_RESET_STATISTICS, +} arg_mode = MODE_RESOLVE_HOST; static void print_source(uint64_t flags, usec_t rtt) { char rtt_str[FORMAT_TIMESTAMP_MAX]; @@ -320,8 +328,7 @@ static int parse_address(const char *s, int *family, union in_addr_union *addres return 0; } -static int resolve_record(sd_bus *bus, const char *name) { - +static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; char ifname[IF_NAMESIZE] = ""; @@ -335,7 +342,7 @@ static int resolve_record(sd_bus *bus, const char *name) { if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(arg_class), dns_type_to_string(arg_type), isempty(ifname) ? "*" : ifname); + log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname); r = sd_bus_message_new_method_call( bus, @@ -347,8 +354,7 @@ static int resolve_record(sd_bus *bus, const char *name) { if (r < 0) return bus_log_create_error(r); - assert((uint16_t) arg_type == arg_type); - r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, arg_class, arg_type, arg_flags); + r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); if (r < 0) return bus_log_create_error(r); @@ -369,7 +375,7 @@ static int resolve_record(sd_bus *bus, const char *name) { while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - _cleanup_free_ char *s = NULL; + const char *s; uint16_t c, t; int ifindex; const void *d; @@ -399,17 +405,13 @@ static int resolve_record(sd_bus *bus, const char *name) { if (r < 0) return log_oom(); - r = dns_packet_read_rr(p, &rr, NULL); - if (r < 0) { - log_error("Failed to parse RR."); - return r; - } + r = dns_packet_read_rr(p, &rr, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse RR: %m"); - r = dns_resource_record_to_string(rr, &s); - if (r < 0) { - log_error("Failed to format RR."); - return r; - } + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); ifname[0] = 0; if (ifindex > 0 && !if_indextoname(ifindex, ifname)) @@ -439,6 +441,127 @@ static int resolve_record(sd_bus *bus, const char *name) { return 0; } +static int resolve_rfc4501(sd_bus *bus, const char *name) { + uint16_t type = 0, class = 0; + const char *p, *q, *n; + int r; + + assert(bus); + assert(name); + assert(startswith(name, "dns:")); + + /* Parse RFC 4501 dns: URIs */ + + p = name + 4; + + if (p[0] == '/') { + const char *e; + + if (p[1] != '/') + goto invalid; + + e = strchr(p + 2, '/'); + if (!e) + goto invalid; + + if (e != p + 2) + log_warning("DNS authority specification not supported; ignoring specified authority."); + + p = e + 1; + } + + q = strchr(p, '?'); + if (q) { + n = strndupa(p, q - p); + q++; + + for (;;) { + const char *f; + + f = startswith_no_case(q, "class="); + if (f) { + _cleanup_free_ char *t = NULL; + const char *e; + + if (class != 0) { + log_error("DNS class specified twice."); + return -EINVAL; + } + + e = strchrnul(f, ';'); + t = strndup(f, e - f); + if (!t) + return log_oom(); + + r = dns_class_from_string(t); + if (r < 0) { + log_error("Unknown DNS class %s.", t); + return -EINVAL; + } + + class = r; + + if (*e == ';') { + q = e + 1; + continue; + } + + break; + } + + f = startswith_no_case(q, "type="); + if (f) { + _cleanup_free_ char *t = NULL; + const char *e; + + if (type != 0) { + log_error("DNS type specified twice."); + return -EINVAL; + } + + e = strchrnul(f, ';'); + t = strndup(f, e - f); + if (!t) + return log_oom(); + + r = dns_type_from_string(t); + if (r < 0) { + log_error("Unknown DNS type %s.", t); + return -EINVAL; + } + + type = r; + + if (*e == ';') { + q = e + 1; + continue; + } + + break; + } + + goto invalid; + } + } else + n = p; + + if (type == 0) + type = arg_type; + if (type == 0) + type = DNS_TYPE_A; + + if (class == 0) + class = arg_class; + if (class == 0) + class = DNS_CLASS_IN; + + return resolve_record(bus, n, class, type); + +invalid: + log_error("Invalid DNS URI: %s", name); + return -EINVAL; +} + static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) { const char *canonical_name, *canonical_type, *canonical_domain; _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; @@ -640,6 +763,125 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons return 0; } +static int show_statistics(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + uint64_t n_current_transactions, n_total_transactions, + cache_size, n_cache_hit, n_cache_miss, + n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; + int r; + + assert(bus); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "TransactionStatistics", + &error, + &reply, + "(tt)"); + if (r < 0) + return log_error_errno(r, "Failed to get transaction statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(tt)", + &n_current_transactions, + &n_total_transactions); + if (r < 0) + return bus_log_parse_error(r); + + printf("%sTransactions%s\n" + "Current Transactions: %" PRIu64 "\n" + " Total Transactions: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + n_current_transactions, + n_total_transactions); + + reply = sd_bus_message_unref(reply); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "CacheStatistics", + &error, + &reply, + "(ttt)"); + if (r < 0) + return log_error_errno(r, "Failed to get cache statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(ttt)", + &cache_size, + &n_cache_hit, + &n_cache_miss); + if (r < 0) + return bus_log_parse_error(r); + + printf("\n%sCache%s\n" + " Current Cache Size: %" PRIu64 "\n" + " Cache Hits: %" PRIu64 "\n" + " Cache Misses: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + cache_size, + n_cache_hit, + n_cache_miss); + + reply = sd_bus_message_unref(reply); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "DNSSECStatistics", + &error, + &reply, + "(tttt)"); + if (r < 0) + return log_error_errno(r, "Failed to get DNSSEC statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(tttt)", + &n_dnssec_secure, + &n_dnssec_insecure, + &n_dnssec_bogus, + &n_dnssec_indeterminate); + if (r < 0) + return bus_log_parse_error(r); + + printf("\n%sDNSSEC%s\n" + " Secure RRsets: %" PRIu64 "\n" + " Insecure RRsets: %" PRIu64 "\n" + " Bogus RRsets: %" PRIu64 "\n" + "Indeterminate RRsets: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + n_dnssec_secure, + n_dnssec_insecure, + n_dnssec_bogus, + n_dnssec_indeterminate); + + return 0; +} + +static int reset_statistics(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = sd_bus_call_method(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResetStatistics", + &error, + NULL, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to reset statistics: %s", bus_error_message(&error, r)); + + return 0; +} + static void help_dns_types(void) { int i; const char *t; @@ -684,6 +926,8 @@ static void help(void) { " --cname=BOOL Do [not] follow CNAME redirects\n" " --search=BOOL Do [not] use search domains\n" " --legend=BOOL Do [not] print column headers\n" + " --statistics Show resolver statistics\n" + " --reset-statistics Reset resolver statistics\n" , program_invocation_short_name, program_invocation_short_name); } @@ -696,20 +940,24 @@ static int parse_argv(int argc, char *argv[]) { ARG_SERVICE_ADDRESS, ARG_SERVICE_TXT, ARG_SEARCH, + ARG_STATISTICS, + ARG_RESET_STATISTICS, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "service", no_argument, NULL, ARG_SERVICE }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "search", required_argument, NULL, ARG_SEARCH }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "type", required_argument, NULL, 't' }, + { "class", required_argument, NULL, 'c' }, + { "legend", required_argument, NULL, ARG_LEGEND }, + { "protocol", required_argument, NULL, 'p' }, + { "cname", required_argument, NULL, ARG_CNAME }, + { "service", no_argument, NULL, ARG_SERVICE }, + { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, + { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, + { "search", required_argument, NULL, ARG_SEARCH }, + { "statistics", no_argument, NULL, ARG_STATISTICS, }, + { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, {} }; @@ -758,13 +1006,15 @@ static int parse_argv(int argc, char *argv[]) { return 0; } - arg_type = dns_type_from_string(optarg); - if (arg_type < 0) { + r = dns_type_from_string(optarg); + if (r < 0) { log_error("Failed to parse RR record type %s", optarg); - return arg_type; + return r; } - assert(arg_type > 0 && (uint16_t) arg_type == arg_type); + arg_type = (uint16_t) r; + assert((int) arg_type == r); + arg_mode = MODE_RESOLVE_RECORD; break; case 'c': @@ -773,11 +1023,13 @@ static int parse_argv(int argc, char *argv[]) { return 0; } - r = dns_class_from_string(optarg, &arg_class); + r = dns_class_from_string(optarg); if (r < 0) { log_error("Failed to parse RR record class %s", optarg); return r; } + arg_class = (uint16_t) r; + assert((int) arg_class == r); break; @@ -806,7 +1058,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_SERVICE: - arg_resolve_service = true; + arg_mode = MODE_RESOLVE_SERVICE; break; case ARG_CNAME: @@ -849,6 +1101,14 @@ static int parse_argv(int argc, char *argv[]) { arg_flags &= ~SD_RESOLVED_NO_SEARCH; break; + case ARG_STATISTICS: + arg_mode = MODE_STATISTICS; + break; + + case ARG_RESET_STATISTICS: + arg_mode = MODE_RESET_STATISTICS; + break; + case '?': return -EINVAL; @@ -861,7 +1121,7 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } - if (arg_type != 0 && arg_resolve_service) { + if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) { log_error("--service and --type= may not be combined."); return -EINVAL; } @@ -869,6 +1129,9 @@ static int parse_argv(int argc, char *argv[]) { if (arg_type != 0 && arg_class == 0) arg_class = DNS_CLASS_IN; + if (arg_class != 0 && arg_type == 0) + arg_type = DNS_TYPE_A; + return 1 /* work to do */; } @@ -883,20 +1146,61 @@ int main(int argc, char **argv) { if (r <= 0) goto finish; - if (optind >= argc) { - log_error("No arguments passed"); - r = -EINVAL; - goto finish; - } - r = sd_bus_open_system(&bus); if (r < 0) { log_error_errno(r, "sd_bus_open_system: %m"); goto finish; } - if (arg_resolve_service) { + switch (arg_mode) { + + case MODE_RESOLVE_HOST: + if (optind >= argc) { + log_error("No arguments passed"); + r = -EINVAL; + goto finish; + } + + while (argv[optind]) { + int family, ifindex, k; + union in_addr_union a; + + if (startswith(argv[optind], "dns:")) + k = resolve_rfc4501(bus, argv[optind]); + else { + k = parse_address(argv[optind], &family, &a, &ifindex); + if (k >= 0) + k = resolve_address(bus, family, &a, ifindex); + else + k = resolve_host(bus, argv[optind]); + } + + if (r == 0) + r = k; + + optind++; + } + break; + + case MODE_RESOLVE_RECORD: + if (optind >= argc) { + log_error("No arguments passed"); + r = -EINVAL; + goto finish; + } + + while (argv[optind]) { + int k; + k = resolve_record(bus, argv[optind], arg_class, arg_type); + if (r == 0) + r = k; + + optind++; + } + break; + + case MODE_RESOLVE_SERVICE: if (argc < optind + 1) { log_error("Domain specification required."); r = -EINVAL; @@ -914,27 +1218,27 @@ int main(int argc, char **argv) { goto finish; } - goto finish; - } + break; - while (argv[optind]) { - int family, ifindex, k; - union in_addr_union a; - - if (arg_type != 0) - k = resolve_record(bus, argv[optind]); - else { - k = parse_address(argv[optind], &family, &a, &ifindex); - if (k >= 0) - k = resolve_address(bus, family, &a, ifindex); - else - k = resolve_host(bus, argv[optind]); + case MODE_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; } - if (r == 0) - r = k; + r = show_statistics(bus); + break; + + case MODE_RESET_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } - optind++; + r = reset_statistics(bus); + break; } finish: diff --git a/src/resolve/RFCs b/src/resolve/RFCs new file mode 100644 index 0000000000..33f4dd9cb6 --- /dev/null +++ b/src/resolve/RFCs @@ -0,0 +1,59 @@ +Y = Comprehensively Implemented, to the point appropriate for resolved +D = Comprehensively Implemented, by a dependency of resolved +! = Missing and something we might want to implement +~ = Needs no explicit support or doesn't apply +? = Is this relevant today? + = We are working on this + +Y https://tools.ietf.org/html/rfc1034 → DOMAIN NAMES - CONCEPTS AND FACILITIES +Y https://tools.ietf.org/html/rfc1035 → DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION +? https://tools.ietf.org/html/rfc1101 → DNS Encoding of Network Names and Other Types +Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts -- Application and Support +~ https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes +Y https://tools.ietf.org/html/rfc1536 → Common DNS Implementation Errors and Suggested Fixes +Y https://tools.ietf.org/html/rfc1876 → A Means for Expressing Location Information in the Domain Name System +Y https://tools.ietf.org/html/rfc2181 → Clarifications to the DNS Specification + https://tools.ietf.org/html/rfc2308 → Negative Caching of DNS Queries (DNS NCACHE) +Y https://tools.ietf.org/html/rfc2782 → A DNS RR for specifying the location of services (DNS SRV) +D https://tools.ietf.org/html/rfc3492 → Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA) +Y https://tools.ietf.org/html/rfc3596 → DNS Extensions to Support IP Version 6 +Y https://tools.ietf.org/html/rfc3597 → Handling of Unknown DNS Resource Record (RR) Types + https://tools.ietf.org/html/rfc4033 → DNS Security Introduction and Requirements + https://tools.ietf.org/html/rfc4034 → Resource Records for the DNS Security Extensions + https://tools.ietf.org/html/rfc4035 → Protocol Modifications for the DNS Security Extensions +! https://tools.ietf.org/html/rfc4183 → A Suggested Scheme for DNS Resolution of Networks and Gateways +Y https://tools.ietf.org/html/rfc4255 → Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints +Y https://tools.ietf.org/html/rfc4343 → Domain Name System (DNS) Case Insensitivity Clarification +~ https://tools.ietf.org/html/rfc4470 → Minimally Covering NSEC Records and DNSSEC On-line Signing +Y https://tools.ietf.org/html/rfc4501 → Domain Name System Uniform Resource Identifiers +Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Signer (DS) Resource Records (RRs) +~ https://tools.ietf.org/html/rfc4592 → The Role of Wildcards in the Domain Name System +~ https://tools.ietf.org/html/rfc4697 → Observed DNS Resolution Misbehavior +Y https://tools.ietf.org/html/rfc4795 → Link-Local Multicast Name Resolution (LLMNR) +Y https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors + https://tools.ietf.org/html/rfc5155 → DNS Security (DNSSEC) Hashed Authenticated Denial of Existence +Y https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers +Y https://tools.ietf.org/html/rfc5702 → Use of SHA-2 Algorithms with RSA in DNSKEY and RRSIG Resource Records for DNSSEC +Y https://tools.ietf.org/html/rfc5890 → Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework +Y https://tools.ietf.org/html/rfc5891 → Internationalized Domain Names in Applications (IDNA): Protocol +Y https://tools.ietf.org/html/rfc5966 → DNS Transport over TCP - Implementation Requirements +Y https://tools.ietf.org/html/rfc6303 → Locally Served DNS Zones + https://tools.ietf.org/html/rfc6604 → xNAME RCODE and Status Bits Clarification +Y https://tools.ietf.org/html/rfc6605 → Elliptic Curve Digital Signature Algorithm (DSA) for DNSSEC + https://tools.ietf.org/html/rfc6672 → DNAME Redirection in the DNS +! https://tools.ietf.org/html/rfc6731 → Improved Recursive DNS Server Selection for Multi-Interfaced Nodes +Y https://tools.ietf.org/html/rfc6761 → Special-Use Domain Names + https://tools.ietf.org/html/rfc6762 → Multicast DNS + https://tools.ietf.org/html/rfc6763 → DNS-Based Service Discovery + https://tools.ietf.org/html/rfc6781 → DNSSEC Operational Practices, Version 2 + https://tools.ietf.org/html/rfc6840 → Clarifications and Implementation Notes for DNS Security (DNSSEC) +Y https://tools.ietf.org/html/rfc6891 → Extension Mechanisms for DNS (EDNS(0)) +Y https://tools.ietf.org/html/rfc6944 → Applicability Statement: DNS Security (DNSSEC) DNSKEY Algorithm Implementation Status +Y https://tools.ietf.org/html/rfc6975 → Signaling Cryptographic Algorithm Understanding in DNS Security Extensions (DNSSEC) + https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS +Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors +~ https://tools.ietf.org/html/rfc7719 → DNS Terminology + +Also relevant: + + https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-dotless-domains-considered-harmful/ diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c index a626ecf01a..0571d65f0b 100644 --- a/src/resolve/dns-type.c +++ b/src/resolve/dns-type.c @@ -20,6 +20,7 @@ ***/ #include "dns-type.h" +#include "string-util.h" typedef const struct { uint16_t type; @@ -44,7 +45,98 @@ int dns_type_from_string(const char *s) { return sc->id; } -/* XXX: find an authoritative list of all pseudo types? */ -bool dns_type_is_pseudo(uint16_t n) { - return IN_SET(n, DNS_TYPE_ANY, DNS_TYPE_AXFR, DNS_TYPE_IXFR, DNS_TYPE_OPT); +bool dns_type_is_pseudo(uint16_t type) { + + /* Checks whether the specified type is a "pseudo-type". What + * a "pseudo-type" precisely is, is defined only very weakly, + * but apparently entails all RR types that are not actually + * stored as RRs on the server and should hence also not be + * cached. We use this list primarily to validate NSEC type + * bitfields, and to verify what to cache. */ + + return IN_SET(type, + 0, /* A Pseudo RR type, according to RFC 2931 */ + DNS_TYPE_ANY, + DNS_TYPE_AXFR, + DNS_TYPE_IXFR, + DNS_TYPE_OPT, + DNS_TYPE_TSIG, + DNS_TYPE_TKEY + ); +} + +bool dns_class_is_pseudo(uint16_t class) { + return class == DNS_TYPE_ANY; +} + +bool dns_type_is_valid_query(uint16_t type) { + + /* The types valid as questions in packets */ + + return !IN_SET(type, + 0, + DNS_TYPE_OPT, + DNS_TYPE_TSIG, + DNS_TYPE_TKEY); +} + +bool dns_type_is_valid_rr(uint16_t type) { + + /* The types valid as RR in packets (but not necessarily + * stored on servers). */ + + return !IN_SET(type, + DNS_TYPE_ANY, + DNS_TYPE_AXFR, + DNS_TYPE_IXFR); +} + +bool dns_class_is_valid_rr(uint16_t class) { + return class != DNS_CLASS_ANY; +} + +bool dns_type_may_redirect(uint16_t type) { + /* The following record types should never be redirected using + * CNAME/DNAME RRs. See + * <https://tools.ietf.org/html/rfc4035#section-2.5>. */ + + if (dns_type_is_pseudo(type)) + return false; + + return !IN_SET(type, + DNS_TYPE_CNAME, + DNS_TYPE_DNAME, + DNS_TYPE_NSEC3, + DNS_TYPE_NSEC, + DNS_TYPE_RRSIG, + DNS_TYPE_NXT, + DNS_TYPE_SIG, + DNS_TYPE_KEY); +} + +const char *dns_class_to_string(uint16_t class) { + + switch (class) { + + case DNS_CLASS_IN: + return "IN"; + + case DNS_CLASS_ANY: + return "ANY"; + } + + return NULL; +} + +int dns_class_from_string(const char *s) { + + if (!s) + return _DNS_CLASS_INVALID; + + if (strcaseeq(s, "IN")) + return DNS_CLASS_IN; + else if (strcaseeq(s, "ANY")) + return DNS_CLASS_ANY; + + return _DNS_CLASS_INVALID; } diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h index 2868025ad7..c3bb26a5ee 100644 --- a/src/resolve/dns-type.h +++ b/src/resolve/dns-type.h @@ -23,10 +23,6 @@ #include "macro.h" -const char *dns_type_to_string(int type); -int dns_type_from_string(const char *s); -bool dns_type_is_pseudo(uint16_t n); - /* DNS record types, taken from * http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. */ @@ -119,3 +115,26 @@ enum { assert_cc(DNS_TYPE_SSHFP == 44); assert_cc(DNS_TYPE_TLSA == 52); assert_cc(DNS_TYPE_ANY == 255); + +/* DNS record classes, see RFC 1035 */ +enum { + DNS_CLASS_IN = 0x01, + DNS_CLASS_ANY = 0xFF, + + _DNS_CLASS_MAX, + _DNS_CLASS_INVALID = -1 +}; + +bool dns_type_is_pseudo(uint16_t type); +bool dns_type_is_valid_query(uint16_t type); +bool dns_type_is_valid_rr(uint16_t type); +bool dns_type_may_redirect(uint16_t type); + +bool dns_class_is_pseudo(uint16_t class); +bool dns_class_is_valid_rr(uint16_t class); + +const char *dns_type_to_string(int type); +int dns_type_from_string(const char *s); + +const char *dns_class_to_string(uint16_t type); +int dns_class_from_string(const char *name); diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 1427638233..7193f639d4 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -57,13 +57,20 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_RESOURCES: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_RESOURCES, "Not enough resources"); + case DNS_TRANSACTION_CONNECTION_FAILURE: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_CONNECTION_FAILURE, "DNS server connection failure"); + case DNS_TRANSACTION_ABORTED: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted"); case DNS_TRANSACTION_DNSSEC_FAILED: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "DNSSEC validation failed"); + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s", + dnssec_result_to_string(q->answer_dnssec_result)); + + case DNS_TRANSACTION_NO_TRUST_ANCHOR: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known"); - case DNS_TRANSACTION_FAILURE: { + case DNS_TRANSACTION_RCODE_FAILURE: { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; if (q->answer_rcode == DNS_RCODE_NXDOMAIN) @@ -153,7 +160,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { } if (r < 0) goto finish; - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; r = sd_bus_message_new_method_return(q->request, &reply); @@ -308,7 +315,7 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { } if (r < 0) goto finish; - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; r = sd_bus_message_new_method_return(q->request, &reply); @@ -474,7 +481,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { } if (r < 0) goto finish; - if (r > 0) /* Following a CNAME */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; r = sd_bus_message_new_method_return(q->request, &reply); @@ -553,6 +560,9 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd if (r == 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name); + if (!dns_type_is_valid_query(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid RR type for query %" PRIu16, type); + r = check_ifindex_flags(ifindex, &flags, 0, error); if (r < 0) return r; @@ -889,7 +899,7 @@ static void resolve_service_hostname_complete(DnsQuery *q) { } r = dns_query_process_cname(q); - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; /* This auxiliary lookup is finished or failed, let's see if all are finished now. */ @@ -965,10 +975,11 @@ static void bus_method_resolve_service_complete(DnsQuery *q) { } if (r < 0) goto finish; - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; if (q->answer) { + bool has_root_domain = false; DnsResourceRecord *rr; int ifindex; @@ -982,6 +993,11 @@ static void bus_method_resolve_service_complete(DnsQuery *q) { if (rr->key->type != DNS_TYPE_SRV) continue; + if (dns_name_is_root(rr->srv.name)) { + has_root_domain = true; + continue; + } + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { q->block_all_complete ++; r = resolve_service_hostname(q, rr, ifindex); @@ -993,6 +1009,18 @@ static void bus_method_resolve_service_complete(DnsQuery *q) { found++; } + + if (has_root_domain && found == 0) { + /* If there's exactly one SRV RR and it uses + * the root domain as host name, then the + * service is explicitly not offered on the + * domain. Report this as a recognizable + * error. See RFC 2782, Section "Usage + * Rules". */ + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_SERVICE, "'%s' does not provide the requested service", dns_question_first_name(q->question)); + goto finish; + } + } if (found <= 0) { @@ -1210,16 +1238,101 @@ static int bus_property_get_search_domains( return sd_bus_message_close_container(reply); } +static int bus_property_get_transaction_statistics( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "(tt)", + (uint64_t) hashmap_size(m->dns_transactions), + (uint64_t) m->n_transactions_total); +} + +static int bus_property_get_cache_statistics( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t size = 0, hit = 0, miss = 0; + Manager *m = userdata; + DnsScope *s; + + assert(reply); + assert(m); + + LIST_FOREACH(scopes, s, m->dns_scopes) { + size += dns_cache_size(&s->cache); + hit += s->cache.n_hit; + miss += s->cache.n_miss; + } + + return sd_bus_message_append(reply, "(ttt)", size, hit, miss); +} + +static int bus_property_get_dnssec_statistics( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "(tttt)", + (uint64_t) m->n_dnssec_secure, + (uint64_t) m->n_dnssec_insecure, + (uint64_t) m->n_dnssec_bogus, + (uint64_t) m->n_dnssec_indeterminate); +} + +static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + DnsScope *s; + + assert(message); + assert(m); + + LIST_FOREACH(scopes, s, m->dns_scopes) + s->cache.n_hit = s->cache.n_miss = 0; + + m->n_transactions_total = 0; + m->n_dnssec_secure = m->n_dnssec_insecure = m->n_dnssec_bogus = m->n_dnssec_indeterminate = 0; + + return sd_bus_reply_method_return(message, NULL); +} + static const sd_bus_vtable resolve_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0), SD_BUS_PROPERTY("DNSServers", "a(iiay)", bus_property_get_dns_servers, 0, 0), SD_BUS_PROPERTY("SearchDomains", "a(is)", bus_property_get_search_domains, 0, 0), + SD_BUS_PROPERTY("TransactionStatistics", "(tt)", bus_property_get_transaction_statistics, 0, 0), + SD_BUS_PROPERTY("CacheStatistics", "(ttt)", bus_property_get_cache_statistics, 0, 0), + SD_BUS_PROPERTY("DNSSECStatistics", "(tttt)", bus_property_get_dnssec_statistics, 0, 0), SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResetStatistics", NULL, NULL, bus_method_reset_statistics, 0), SD_BUS_VTABLE_END, }; diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 1b2f3e336e..88df7534c4 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -200,75 +200,6 @@ int config_parse_search_domains( return 0; } -int config_parse_support( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Support support, *v = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - support = support_from_string(rvalue); - if (support < 0) { - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse support level '%s'. Ignoring.", rvalue); - return 0; - } - - support = r ? SUPPORT_YES : SUPPORT_NO; - } - - *v = support; - return 0; -} - -int config_parse_dnssec( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Manager *m = data; - DnssecMode mode; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - mode = dnssec_mode_from_string(rvalue); - if (mode < 0) { - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNSSEC mode '%s'. Ignoring.", rvalue); - return 0; - } - - mode = r ? DNSSEC_YES : DNSSEC_NO; - } - - m->unicast_scope->dnssec_mode = mode; - return 0; -} - int manager_parse_config_file(Manager *m) { int r; diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index 668ea02bba..b4ef1b0378 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -35,5 +35,4 @@ const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned len int config_parse_dns_servers(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_search_domains(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_support(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_dnssec(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index 55e6ffbad7..b50558e280 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -22,6 +22,7 @@ #include "alloc-util.h" #include "dns-domain.h" #include "resolved-dns-answer.h" +#include "resolved-dns-dnssec.h" #include "string-util.h" DnsAnswer *dns_answer_new(unsigned n) { @@ -73,7 +74,7 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a) { return NULL; } -static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) { +static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { assert(rr); if (!a) @@ -82,19 +83,22 @@ static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) if (a->n_rrs >= a->n_allocated) return -ENOSPC; - a->items[a->n_rrs].rr = dns_resource_record_ref(rr); - a->items[a->n_rrs].ifindex = ifindex; - a->n_rrs++; + a->items[a->n_rrs++] = (DnsAnswerItem) { + .rr = dns_resource_record_ref(rr), + .ifindex = ifindex, + .flags = flags, + }; return 1; } static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { DnsResourceRecord *rr; + DnsAnswerFlags flags; int ifindex, r; - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, source) { - r = dns_answer_add_raw(a, rr, ifindex); + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, source) { + r = dns_answer_add_raw(a, rr, ifindex, flags); if (r < 0) return r; } @@ -102,7 +106,7 @@ static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { return 0; } -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) { +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { unsigned i; int r; @@ -121,28 +125,48 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) { if (r < 0) return r; if (r > 0) { - /* Entry already exists, keep the entry with - * the higher RR, or the one with TTL 0 */ + /* Don't mix contradicting TTLs (see below) */ + if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) + return -EINVAL; - if (rr->ttl == 0 || (rr->ttl > a->items[i].rr->ttl && a->items[i].rr->ttl != 0)) { + /* Entry already exists, keep the entry with + * the higher RR. */ + if (rr->ttl > a->items[i].rr->ttl) { dns_resource_record_ref(rr); dns_resource_record_unref(a->items[i].rr); a->items[i].rr = rr; } + a->items[i].flags |= flags; return 0; } + + r = dns_resource_key_equal(a->items[i].rr->key, rr->key); + if (r < 0) + return r; + if (r > 0) { + /* There's already an RR of the same RRset in + * place! Let's see if the TTLs more or less + * match. We don't really care if they match + * precisely, but we do care whether one is 0 + * and the other is not. See RFC 2181, Section + * 5.2.*/ + + if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) + return -EINVAL; + } } - return dns_answer_add_raw(a, rr, ifindex); + return dns_answer_add_raw(a, rr, ifindex, flags); } static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { DnsResourceRecord *rr; + DnsAnswerFlags flags; int ifindex, r; - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, b) { - r = dns_answer_add(a, rr, ifindex); + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) { + r = dns_answer_add(a, rr, ifindex, flags); if (r < 0) return r; } @@ -150,7 +174,7 @@ static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { return 0; } -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex) { +int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { int r; assert(a); @@ -160,7 +184,7 @@ int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex) { if (r < 0) return r; - return dns_answer_add(*a, rr, ifindex); + return dns_answer_add(*a, rr, ifindex, flags); } int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { @@ -186,85 +210,177 @@ int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { soa->soa.expire = 1; soa->soa.minimum = ttl; - return dns_answer_add(a, soa, 0); + return dns_answer_add(a, soa, 0, DNS_ANSWER_AUTHENTICATED); } -int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key) { +int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; DnsResourceRecord *i; + bool found = false; int r; assert(key); - if (!a) - return 0; - - DNS_ANSWER_FOREACH(i, a) { + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { r = dns_resource_key_match_rr(key, i, NULL); if (r < 0) return r; - if (r > 0) + if (r == 0) + continue; + + if (!ret_flags) return 1; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } } - return 0; + if (ret_flags) + *ret_flags = flags; + + return found; } -int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr) { +int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; DnsResourceRecord *i; + bool found = false; int r; assert(rr); - DNS_ANSWER_FOREACH(i, a) { + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { r = dns_resource_record_equal(i, rr); if (r < 0) return r; - if (r > 0) + if (r == 0) + continue; + + if (!ret_flags) return 1; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } } - return 0; + if (ret_flags) + *ret_flags = flags; + + return found; } -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret) { - DnsResourceRecord *rr; +int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; + DnsResourceRecord *i; + bool found = false; + int r; assert(key); - assert(ret); - if (!a) - return 0; + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { + r = dns_resource_key_equal(i->key, key); + if (r < 0) + return r; + if (r == 0) + continue; + + if (!ret_flags) + return true; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } + } + + if (ret_flags) + *ret_flags = flags; + + return found; +} + +int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) { + DnsResourceRecord *i; + + DNS_ANSWER_FOREACH(i, a) { + if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3)) + return true; + } + + return false; +} + +int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { + DnsResourceRecord *rr, *soa = NULL; + DnsAnswerFlags rr_flags, soa_flags = 0; + int r; + + assert(key); /* For a SOA record we can never find a matching SOA record */ if (key->type == DNS_TYPE_SOA) return 0; - DNS_ANSWER_FOREACH(rr, a) { - if (dns_resource_key_match_soa(key, rr->key)) { - *ret = rr; - return 1; + DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { + r = dns_resource_key_match_soa(key, rr->key); + if (r < 0) + return r; + if (r > 0) { + + if (soa) { + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(soa->key)); + if (r < 0) + return r; + if (r > 0) + continue; + } + + soa = rr; + soa_flags = rr_flags; } } - return 0; + if (!soa) + return 0; + + if (ret) + *ret = soa; + if (flags) + *flags = soa_flags; + + return 1; } -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret) { +int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { DnsResourceRecord *rr; + DnsAnswerFlags rr_flags; + int r; assert(key); - if (!a) - return 0; - /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ - if (key->type == DNS_TYPE_CNAME || key->type == DNS_TYPE_DNAME) + if (!dns_type_may_redirect(key->type)) return 0; - DNS_ANSWER_FOREACH(rr, a) { - if (dns_resource_key_match_cname_or_dname(key, rr->key, NULL)) { + DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { + r = dns_resource_key_match_cname_or_dname(key, rr->key, NULL); + if (r < 0) + return r; + if (r > 0) { if (ret) *ret = rr; + if (flags) + *flags = rr_flags; return 1; } } @@ -356,20 +472,21 @@ int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { if ((*a)->n_ref > 1) { _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; + DnsAnswerFlags flags; int ifindex; copy = dns_answer_new((*a)->n_rrs); if (!copy) return -ENOMEM; - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, *a) { + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { r = dns_resource_key_equal(rr->key, key); if (r < 0) return r; if (r > 0) continue; - r = dns_answer_add_raw(copy, rr, ifindex); + r = dns_answer_add_raw(copy, rr, ifindex, flags); if (r < 0) return r; } @@ -407,16 +524,103 @@ int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { return 1; } -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key) { +int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) { + bool found = false, other = false; + DnsResourceRecord *rr; + unsigned i; + int r; + + assert(a); + assert(rm); + + /* Remove all entries matching the specified RR from *a */ + + DNS_ANSWER_FOREACH(rr, *a) { + r = dns_resource_record_equal(rr, rm); + if (r < 0) + return r; + if (r > 0) + found = true; + else + other = true; + + if (found && other) + break; + } + + if (!found) + return 0; + + if (!other) { + *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ + return 1; + } + + if ((*a)->n_ref > 1) { + _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; + DnsAnswerFlags flags; + int ifindex; + + copy = dns_answer_new((*a)->n_rrs); + if (!copy) + return -ENOMEM; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { + r = dns_resource_record_equal(rr, rm); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_answer_add_raw(copy, rr, ifindex, flags); + if (r < 0) + return r; + } + + dns_answer_unref(*a); + *a = copy; + copy = NULL; + + return 1; + } + + /* Only a single reference, edit in-place */ + + i = 0; + for (;;) { + if (i >= (*a)->n_rrs) + break; + + r = dns_resource_record_equal((*a)->items[i].rr, rm); + if (r < 0) + return r; + if (r > 0) { + /* Kill this entry */ + + dns_resource_record_unref((*a)->items[i].rr); + memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); + (*a)->n_rrs --; + continue; + + } else + /* Keep this entry */ + i++; + } + + return 1; +} + +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) { DnsResourceRecord *rr_source; int ifindex_source, r; + DnsAnswerFlags flags_source; assert(a); assert(key); /* Copy all RRs matching the specified key from source into *a */ - DNS_ANSWER_FOREACH_IFINDEX(rr_source, ifindex_source, source) { + DNS_ANSWER_FOREACH_FULL(rr_source, ifindex_source, flags_source, source) { r = dns_resource_key_equal(rr_source->key, key); if (r < 0) @@ -429,7 +633,7 @@ int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKe if (r < 0) return r; - r = dns_answer_add(*a, rr_source, ifindex_source); + r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags); if (r < 0) return r; } @@ -437,6 +641,20 @@ int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKe return 0; } +int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags) { + int r; + + assert(to); + assert(from); + assert(key); + + r = dns_answer_copy_by_key(to, *from, key, or_flags); + if (r < 0) + return r; + + return dns_answer_remove_by_key(from, key); +} + void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { DnsAnswerItem *items; unsigned i, start, end; @@ -539,3 +757,40 @@ int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) { return 0; } + +void dns_answer_dump(DnsAnswer *answer, FILE *f) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int ifindex; + + if (!f) + f = stdout; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { + const char *t; + + fputc('\t', f); + + t = dns_resource_record_to_string(rr); + if (!t) { + log_oom(); + continue; + } + + fputs(t, f); + + if (ifindex != 0 || flags & (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE|DNS_ANSWER_SHARED_OWNER)) + fputs("\t;", f); + + if (ifindex != 0) + printf(" ifindex=%i", ifindex); + if (flags & DNS_ANSWER_AUTHENTICATED) + fputs(" authenticated", f); + if (flags & DNS_ANSWER_CACHEABLE) + fputs(" cachable", f); + if (flags & DNS_ANSWER_SHARED_OWNER) + fputs(" shared-owner", f); + + fputc('\n', f); + } +} diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index 56b462ed7e..715e487d94 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -32,11 +32,18 @@ typedef struct DnsAnswerItem DnsAnswerItem; * can qualify A and AAAA RRs referring to a local link with the * right ifindex. * - * Note that we usually encode the empty answer as a simple NULL. */ + * Note that we usually encode the the empty DnsAnswer object as a simple NULL. */ + +typedef enum DnsAnswerFlags { + DNS_ANSWER_AUTHENTICATED = 1, /* Item has been authenticated */ + DNS_ANSWER_CACHEABLE = 2, /* Item is subject to caching */ + DNS_ANSWER_SHARED_OWNER = 4, /* For mDNS: RRset may be owner by multiple peers */ +} DnsAnswerFlags; struct DnsAnswerItem { DnsResourceRecord *rr; int ifindex; + DnsAnswerFlags flags; }; struct DnsAnswer { @@ -49,15 +56,17 @@ DnsAnswer *dns_answer_new(unsigned n); DnsAnswer *dns_answer_ref(DnsAnswer *a); DnsAnswer *dns_answer_unref(DnsAnswer *a); -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex); -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex); +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); +int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl); -int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key); -int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr); +int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); +int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *combined_flags); +int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); +int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a); -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret); -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret); +int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); +int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret); int dns_answer_extend(DnsAnswer **a, DnsAnswer *b); @@ -68,12 +77,17 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free); int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free); int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key); +int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); + +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); +int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); static inline unsigned dns_answer_size(DnsAnswer *a) { return a ? a->n_rrs : 0; } +void dns_answer_dump(DnsAnswer *answer, FILE *f); + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); #define _DNS_ANSWER_FOREACH(q, kk, a) \ @@ -93,6 +107,36 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); 0; \ }); \ (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) #define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) _DNS_ANSWER_FOREACH_IFINDEX(UNIQ, kk, ifindex, a) + +#define _DNS_ANSWER_FOREACH_FLAGS(q, kk, fl, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) + +#define DNS_ANSWER_FOREACH_FLAGS(kk, flags, a) _DNS_ANSWER_FOREACH_FLAGS(UNIQ, kk, flags, a) + +#define _DNS_ANSWER_FOREACH_FULL(q, kk, ifi, fl, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ + (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0), \ + (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) + +#define DNS_ANSWER_FOREACH_FULL(kk, ifindex, flags, a) _DNS_ANSWER_FOREACH_FULL(UNIQ, kk, ifindex, flags, a) diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 9ab44400bd..301f383809 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -26,11 +26,12 @@ #include "resolved-dns-packet.h" #include "string-util.h" -/* Never cache more than 1K entries */ -#define CACHE_MAX 1024 +/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to + * leave DNS caches unbounded, but that's crazy. */ +#define CACHE_MAX 4096 -/* We never keep any item longer than 10min in our cache */ -#define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE) +/* We never keep any item longer than 2h in our cache */ +#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) typedef enum DnsCacheItemType DnsCacheItemType; typedef struct DnsCacheItem DnsCacheItem; @@ -42,14 +43,18 @@ enum DnsCacheItemType { }; struct DnsCacheItem { + DnsCacheItemType type; DnsResourceKey *key; DnsResourceRecord *rr; + usec_t until; - DnsCacheItemType type; - unsigned prioq_idx; - bool authenticated; + bool authenticated:1; + bool shared_owner:1; + int owner_family; union in_addr_union owner_address; + + unsigned prioq_idx; LIST_FIELDS(DnsCacheItem, by_key); }; @@ -64,7 +69,7 @@ static void dns_cache_item_free(DnsCacheItem *i) { DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free); -static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) { +static void dns_cache_item_unlink_and_free(DnsCache *c, DnsCacheItem *i) { DnsCacheItem *first; assert(c); @@ -85,34 +90,55 @@ static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) { dns_cache_item_free(i); } -void dns_cache_flush(DnsCache *c) { - DnsCacheItem *i; +static bool dns_cache_remove_by_rr(DnsCache *c, DnsResourceRecord *rr) { + DnsCacheItem *first, *i; + int r; + + first = hashmap_get(c->by_key, rr->key); + LIST_FOREACH(by_key, i, first) { + r = dns_resource_record_equal(i->rr, rr); + if (r < 0) + return r; + if (r > 0) { + dns_cache_item_unlink_and_free(c, i); + return true; + } + } + + return false; +} + +static bool dns_cache_remove_by_key(DnsCache *c, DnsResourceKey *key) { + DnsCacheItem *first, *i, *n; assert(c); + assert(key); - while ((i = hashmap_first(c->by_key))) - dns_cache_item_remove_and_free(c, i); + first = hashmap_remove(c->by_key, key); + if (!first) + return false; - assert(hashmap_size(c->by_key) == 0); - assert(prioq_size(c->by_expiry) == 0); + LIST_FOREACH_SAFE(by_key, i, n, first) { + prioq_remove(c->by_expiry, i, &i->prioq_idx); + dns_cache_item_free(i); + } - c->by_key = hashmap_free(c->by_key); - c->by_expiry = prioq_free(c->by_expiry); + return true; } -static bool dns_cache_remove(DnsCache *c, DnsResourceKey *key) { - DnsCacheItem *i; - bool exist = false; +void dns_cache_flush(DnsCache *c) { + DnsResourceKey *key; assert(c); - assert(key); - while ((i = hashmap_get(c->by_key, key))) { - dns_cache_item_remove_and_free(c, i); - exist = true; - } + while ((key = hashmap_first_key(c->by_key))) + dns_cache_remove_by_key(c, key); - return exist; + assert(hashmap_size(c->by_key) == 0); + assert(prioq_size(c->by_expiry) == 0); + + c->by_key = hashmap_free(c->by_key); + c->by_expiry = prioq_free(c->by_expiry); } static void dns_cache_make_space(DnsCache *c, unsigned add) { @@ -142,7 +168,7 @@ static void dns_cache_make_space(DnsCache *c, unsigned add) { /* Take an extra reference to the key so that it * doesn't go away in the middle of the remove call */ key = dns_resource_key_ref(i->key); - dns_cache_remove(c, key); + dns_cache_remove_by_key(c, key); } } @@ -154,7 +180,6 @@ void dns_cache_prune(DnsCache *c) { /* Remove all entries that are past their TTL */ for (;;) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; DnsCacheItem *i; i = prioq_peek(c->by_expiry); @@ -167,10 +192,19 @@ void dns_cache_prune(DnsCache *c) { if (i->until > t) break; - /* Take an extra reference to the key so that it - * doesn't go away in the middle of the remove call */ - key = dns_resource_key_ref(i->key); - dns_cache_remove(c, key); + /* Depending whether this is an mDNS shared entry + * either remove only this one RR or the whole + * RRset */ + if (i->shared_owner) + dns_cache_item_unlink_and_free(c, i); + else { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + /* Take an extra reference to the key so that it + * doesn't go away in the middle of the remove call */ + key = dns_resource_key_ref(i->key); + dns_cache_remove_by_key(c, key); + } } } @@ -239,10 +273,56 @@ static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { return NULL; } -static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, bool authenticated, usec_t timestamp) { +static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) { + uint32_t ttl; + usec_t u; + + assert(rr); + + ttl = MIN(rr->ttl, nsec_ttl); + if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) { + /* If this is a SOA RR, and it is requested, clamp to + * the SOA's minimum field. This is used when we do + * negative caching, to determine the TTL for the + * negative caching entry. See RFC 2308, Section + * 5. */ + + if (ttl > rr->soa.minimum) + ttl = rr->soa.minimum; + } + + u = ttl * USEC_PER_SEC; + if (u > CACHE_TTL_MAX_USEC) + u = CACHE_TTL_MAX_USEC; + + if (rr->expiry != USEC_INFINITY) { + usec_t left; + + /* Make use of the DNSSEC RRSIG expiry info, if we + * have it */ + + left = LESS_BY(rr->expiry, now(CLOCK_REALTIME)); + if (u > left) + u = left; + } + + return timestamp + u; +} + +static void dns_cache_item_update_positive( + DnsCache *c, + DnsCacheItem *i, + DnsResourceRecord *rr, + bool authenticated, + bool shared_owner, + usec_t timestamp, + int owner_family, + const union in_addr_union *owner_address) { + assert(c); assert(i); assert(rr); + assert(owner_address); i->type = DNS_CACHE_POSITIVE; @@ -259,8 +339,12 @@ static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsReso dns_resource_key_unref(i->key); i->key = dns_resource_key_ref(rr->key); + i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); i->authenticated = authenticated; - i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); + i->shared_owner = shared_owner; + + i->owner_family = owner_family; + i->owner_address = *owner_address; prioq_reshuffle(c->by_expiry, i, &i->prioq_idx); } @@ -269,6 +353,7 @@ static int dns_cache_put_positive( DnsCache *c, DnsResourceRecord *rr, bool authenticated, + bool shared_owner, usec_t timestamp, int owner_family, const union in_addr_union *owner_address) { @@ -282,9 +367,15 @@ static int dns_cache_put_positive( assert(rr); assert(owner_address); - /* New TTL is 0? Delete the entry... */ + /* Never cache pseudo RRs */ + if (dns_class_is_pseudo(rr->key->class)) + return 0; + if (dns_type_is_pseudo(rr->key->type)) + return 0; + + /* New TTL is 0? Delete this specific entry... */ if (rr->ttl <= 0) { - k = dns_cache_remove(c, rr->key); + k = dns_cache_remove_by_rr(c, rr); if (log_get_max_level() >= LOG_DEBUG) { r = dns_resource_key_to_string(rr->key, &key_str); @@ -300,15 +391,18 @@ static int dns_cache_put_positive( return 0; } - if (rr->key->class == DNS_CLASS_ANY) - return 0; - if (rr->key->type == DNS_TYPE_ANY) - return 0; - - /* Entry exists already? Update TTL and timestamp */ + /* Entry exists already? Update TTL, timestamp and owner*/ existing = dns_cache_get(c, rr); if (existing) { - dns_cache_item_update_positive(c, existing, rr, authenticated, timestamp); + dns_cache_item_update_positive( + c, + existing, + rr, + authenticated, + shared_owner, + timestamp, + owner_family, + owner_address); return 0; } @@ -326,11 +420,12 @@ static int dns_cache_put_positive( i->type = DNS_CACHE_POSITIVE; i->key = dns_resource_key_ref(rr->key); i->rr = dns_resource_record_ref(rr); - i->until = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); - i->prioq_idx = PRIOQ_IDX_NULL; + i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); + i->authenticated = authenticated; + i->shared_owner = shared_owner; i->owner_family = owner_family; i->owner_address = *owner_address; - i->authenticated = authenticated; + i->prioq_idx = PRIOQ_IDX_NULL; r = dns_cache_link_item(c, i); if (r < 0) @@ -341,7 +436,7 @@ static int dns_cache_put_positive( if (r < 0) return r; - log_debug("Added cache entry for %s", key_str); + log_debug("Added positive cache entry for %s", key_str); } i = NULL; @@ -353,8 +448,9 @@ static int dns_cache_put_negative( DnsResourceKey *key, int rcode, bool authenticated, + uint32_t nsec_ttl, usec_t timestamp, - uint32_t soa_ttl, + DnsResourceRecord *soa, int owner_family, const union in_addr_union *owner_address) { @@ -364,23 +460,24 @@ static int dns_cache_put_negative( assert(c); assert(key); + assert(soa); assert(owner_address); - dns_cache_remove(c, key); - - if (key->class == DNS_CLASS_ANY) + /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly + * important to filter out as we use this as a pseudo-type for + * NXDOMAIN entries */ + if (dns_class_is_pseudo(key->class)) return 0; - if (key->type == DNS_TYPE_ANY) - /* This is particularly important to filter out as we use this as a - * pseudo-type for NXDOMAIN entries */ + if (dns_type_is_pseudo(key->type)) return 0; - if (soa_ttl <= 0) { + + if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { if (log_get_max_level() >= LOG_DEBUG) { r = dns_resource_key_to_string(key, &key_str); if (r < 0) return r; - log_debug("Not caching negative entry with zero SOA TTL: %s", key_str); + log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", key_str); } return 0; @@ -400,11 +497,11 @@ static int dns_cache_put_negative( return -ENOMEM; i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; - i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); - i->prioq_idx = PRIOQ_IDX_NULL; + i->until = calculate_until(soa, nsec_ttl, timestamp, true); + i->authenticated = authenticated; i->owner_family = owner_family; i->owner_address = *owner_address; - i->authenticated = authenticated; + i->prioq_idx = PRIOQ_IDX_NULL; if (i->type == DNS_CACHE_NXDOMAIN) { /* NXDOMAIN entries should apply equally to all types, so we use ANY as @@ -412,6 +509,14 @@ static int dns_cache_put_negative( i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(key)); if (!i->key) return -ENOMEM; + + /* Make sure to remove any previous entry for this + * specific ANY key. (For non-ANY keys the cache data + * is already cleared by the caller.) Note that we + * don't bother removing positive or NODATA cache + * items in this case, because it would either be slow + * or require explicit indexing by name */ + dns_cache_remove_by_key(c, key); } else i->key = dns_resource_key_ref(key); @@ -431,30 +536,58 @@ static int dns_cache_put_negative( return 0; } +static void dns_cache_remove_previous( + DnsCache *c, + DnsResourceKey *key, + DnsAnswer *answer) { + + DnsResourceRecord *rr; + DnsAnswerFlags flags; + + assert(c); + + /* First, if we were passed a key (i.e. on LLMNR/DNS, but + * not on mDNS), delete all matching old RRs, so that we only + * keep complete by_key in place. */ + if (key) + dns_cache_remove_by_key(c, key); + + /* Second, flush all entries matching the answer, unless this + * is an RR that is explicitly marked to be "shared" between + * peers (i.e. mDNS RRs without the flush-cache bit set). */ + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; + + if (flags & DNS_ANSWER_SHARED_OWNER) + continue; + + dns_cache_remove_by_key(c, rr->key); + } +} + int dns_cache_put( DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, - unsigned max_rrs, bool authenticated, + uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address) { DnsResourceRecord *soa = NULL, *rr; - unsigned cache_keys, i; + DnsAnswerFlags flags; + unsigned cache_keys; int r; assert(c); + assert(owner_address); - if (key) { - /* First, if we were passed a key, delete all matching old RRs, - * so that we only keep complete by_key in place. */ - dns_cache_remove(c, key); - } + dns_cache_remove_previous(c, key, answer); - if (!answer) { + if (dns_answer_size(answer) <= 0) { if (log_get_max_level() >= LOG_DEBUG) { _cleanup_free_ char *key_str = NULL; @@ -468,19 +601,13 @@ int dns_cache_put( return 0; } - DNS_ANSWER_FOREACH(rr, answer) - if (rr->key->cache_flush) - dns_cache_remove(c, rr->key); - /* We only care for positive replies and NXDOMAINs, on all * other replies we will simply flush the respective entries, * and that's it */ - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) return 0; - cache_keys = answer->n_rrs; - + cache_keys = dns_answer_size(answer); if (key) cache_keys ++; @@ -491,19 +618,26 @@ int dns_cache_put( timestamp = now(clock_boottime_or_monotonic()); /* Second, add in positive entries for all contained RRs */ - for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) { - rr = answer->items[i].rr; + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; - r = dns_cache_put_positive(c, rr, authenticated, timestamp, owner_family, owner_address); + r = dns_cache_put_positive( + c, + rr, + flags & DNS_ANSWER_AUTHENTICATED, + flags & DNS_ANSWER_SHARED_OWNER, + timestamp, + owner_family, owner_address); if (r < 0) goto fail; } - if (!key) + if (!key) /* mDNS doesn't know negative caching, really */ return 0; /* Third, add in negative entries if the key has no RR */ - r = dns_answer_match_key(answer, key); + r = dns_answer_match_key(answer, key, NULL); if (r < 0) goto fail; if (r > 0) @@ -512,7 +646,7 @@ int dns_cache_put( /* But not if it has a matching CNAME/DNAME (the negative * caching will be done on the canonical name, not on the * alias) */ - r = dns_answer_find_cname_or_dname(answer, key, NULL); + r = dns_answer_find_cname_or_dname(answer, key, NULL, NULL); if (r < 0) goto fail; if (r > 0) @@ -521,14 +655,26 @@ int dns_cache_put( /* See https://tools.ietf.org/html/rfc2308, which say that a * matching SOA record in the packet is used to to enable * negative caching. */ - - r = dns_answer_find_soa(answer, key, &soa); + r = dns_answer_find_soa(answer, key, &soa, &flags); if (r < 0) goto fail; if (r == 0) return 0; - r = dns_cache_put_negative(c, key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address); + /* Refuse using the SOA data if it is unsigned, but the key is + * signed */ + if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0) + return 0; + + r = dns_cache_put_negative( + c, + key, + rcode, + authenticated, + nsec_ttl, + timestamp, + soa, + owner_family, owner_address); if (r < 0) goto fail; @@ -539,10 +685,14 @@ fail: * added, just in case */ if (key) - dns_cache_remove(c, key); + dns_cache_remove_by_key(c, key); - for (i = 0; i < answer->n_rrs; i++) - dns_cache_remove(c, answer->items[i].rr->key); + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; + + dns_cache_remove_by_key(c, rr->key); + } return r; } @@ -570,11 +720,7 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D if (i && i->type == DNS_CACHE_NXDOMAIN) return i; - /* The following record types should never be redirected. See - * <https://tools.ietf.org/html/rfc4035#section-2.5>. */ - if (!IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME, - DNS_TYPE_NSEC3, DNS_TYPE_NSEC, DNS_TYPE_RRSIG, - DNS_TYPE_NXT, DNS_TYPE_SIG, DNS_TYPE_KEY)) { + if (dns_type_may_redirect(k->type)) { /* Check if we have a CNAME record instead */ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n)); if (i) @@ -582,8 +728,6 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D /* OK, let's look for cached DNAME records. */ for (;;) { - char label[DNS_LABEL_MAX]; - if (isempty(n)) return NULL; @@ -592,13 +736,13 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D return i; /* Jump one label ahead */ - r = dns_label_unescape(&n, label, sizeof(label)); + r = dns_name_parent(&n); if (r <= 0) return NULL; } } - if (k-> type != DNS_TYPE_NSEC) { + if (k->type != DNS_TYPE_NSEC) { /* Check if we have an NSEC record instead for the name. */ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n)); if (i) @@ -637,6 +781,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r log_debug("Ignoring cache for ANY lookup: %s", key_str); } + c->n_miss++; + *ret = NULL; *rcode = DNS_RCODE_SUCCESS; return 0; @@ -654,6 +800,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r log_debug("Cache miss for %s", key_str); } + c->n_miss++; + *ret = NULL; *rcode = DNS_RCODE_SUCCESS; return 0; @@ -691,9 +839,15 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r *rcode = DNS_RCODE_SUCCESS; *authenticated = nsec->authenticated; - return !bitmap_isset(nsec->rr->nsec.types, key->type) && - !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && - !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME); + if (!bitmap_isset(nsec->rr->nsec.types, key->type) && + !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && + !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME)) { + c->n_hit++; + return 1; + } + + c->n_miss++; + return 0; } if (log_get_max_level() >= LOG_DEBUG) { @@ -708,6 +862,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r } if (n <= 0) { + c->n_hit++; + *ret = NULL; *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; *authenticated = have_authenticated && !have_non_authenticated; @@ -722,11 +878,13 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r if (!j->rr) continue; - r = dns_answer_add(answer, j->rr, 0); + r = dns_answer_add(answer, j->rr, 0, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0); if (r < 0) return r; } + c->n_hit++; + *ret = answer; *rcode = DNS_RCODE_SUCCESS; *authenticated = have_authenticated && !have_non_authenticated; @@ -784,12 +942,10 @@ int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { DnsCacheItem *j; LIST_FOREACH(by_key, j, i) { - _cleanup_free_ char *t = NULL; - if (!j->rr) continue; - if (!dns_key_is_shared(j->rr->key)) + if (!j->shared_owner) continue; r = dns_packet_append_rr(p, j->rr, NULL, NULL); @@ -837,13 +993,13 @@ void dns_cache_dump(DnsCache *cache, FILE *f) { DnsCacheItem *j; LIST_FOREACH(by_key, j, i) { - _cleanup_free_ char *t = NULL; fputc('\t', f); if (j->rr) { - r = dns_resource_record_to_string(j->rr, &t); - if (r < 0) { + const char *t; + t = dns_resource_record_to_string(j->rr); + if (!t) { log_oom(); continue; } @@ -851,13 +1007,14 @@ void dns_cache_dump(DnsCache *cache, FILE *f) { fputs(t, f); fputc('\n', f); } else { - r = dns_resource_key_to_string(j->key, &t); + _cleanup_free_ char *z = NULL; + r = dns_resource_key_to_string(j->key, &z); if (r < 0) { log_oom(); continue; } - fputs(t, f); + fputs(z, f); fputs(" -- ", f); fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); fputc('\n', f); @@ -872,3 +1029,10 @@ bool dns_cache_is_empty(DnsCache *cache) { return hashmap_isempty(cache->by_key); } + +unsigned dns_cache_size(DnsCache *cache) { + if (!cache) + return 0; + + return hashmap_size(cache->by_key); +} diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h index 0f28bbe543..e61b285df4 100644 --- a/src/resolve/resolved-dns-cache.h +++ b/src/resolve/resolved-dns-cache.h @@ -29,6 +29,8 @@ typedef struct DnsCache { Hashmap *by_key; Prioq *by_expiry; + unsigned n_hit; + unsigned n_miss; } DnsCache; #include "resolved-dns-answer.h" @@ -39,7 +41,7 @@ typedef struct DnsCache { void dns_cache_flush(DnsCache *c); void dns_cache_prune(DnsCache *c); -int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, unsigned max_rrs, bool authenticated, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); +int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer, bool *authenticated); int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); @@ -47,4 +49,6 @@ int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_ void dns_cache_dump(DnsCache *cache, FILE *f); bool dns_cache_is_empty(DnsCache *cache); +unsigned dns_cache_size(DnsCache *cache); + int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p); diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index df12e86167..51fe710795 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -23,6 +23,7 @@ #include "alloc-util.h" #include "dns-domain.h" +#include "hexdecoct.h" #include "resolved-dns-dnssec.h" #include "resolved-dns-packet.h" #include "string-table.h" @@ -34,16 +35,14 @@ * * TODO: * - * - Iterative validation - * - NSEC proof of non-existance - * - NSEC3 proof of non-existance - * - Make trust anchor store read additional DS+DNSKEY data from disk - * - wildcard zones compatibility + * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing) * - multi-label zone compatibility - * - DNSSEC cname/dname compatibility - * - per-interface DNSSEC setting - * - DSA support - * - EC support? + * - cname/dname compatibility + * - nxdomain on qname + * - workable hack for the .corp, .home, .box case + * - bus calls to override DNSEC setting per interface + * - log all DNSSEC downgrades + * - enable by default * * */ @@ -53,6 +52,9 @@ /* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */ #define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE) +/* Maximum number of NSEC3 iterations we'll do. */ +#define NSEC3_ITERATIONS_MAX 2048 + /* * The DNSSEC Chain of trust: * @@ -64,23 +66,22 @@ * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS */ -static bool dnssec_algorithm_supported(int algorithm) { - return IN_SET(algorithm, - DNSSEC_ALGORITHM_RSASHA1, - DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA256, - DNSSEC_ALGORITHM_RSASHA512); -} +static void initialize_libgcrypt(void) { + const char *p; + + if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) + return; -static bool dnssec_digest_supported(int digest) { - return IN_SET(digest, - DNSSEC_DIGEST_SHA1, - DNSSEC_DIGEST_SHA256); + p = gcry_check_version("1.4.5"); + assert(p); + + gcry_control(GCRYCTL_DISABLE_SECMEM); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); } -uint16_t dnssec_keytag(DnsResourceRecord *dnskey) { +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) { const uint8_t *p; - uint32_t sum; + uint32_t sum, f; size_t i; /* The algorithm from RFC 4034, Appendix B. */ @@ -88,8 +89,12 @@ uint16_t dnssec_keytag(DnsResourceRecord *dnskey) { assert(dnskey); assert(dnskey->key->type == DNS_TYPE_DNSKEY); - sum = (uint32_t) dnskey->dnskey.flags + - ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); + f = (uint32_t) dnskey->dnskey.flags; + + if (mask_revoke) + f &= ~DNSKEY_FLAG_REVOKE; + + sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); p = dnskey->dnskey.key; @@ -115,21 +120,21 @@ static int rr_compare(const void *a, const void *b) { assert(*y); assert((*y)->wire_format); - m = MIN((*x)->wire_format_size, (*y)->wire_format_size); + m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y)); - r = memcmp((*x)->wire_format, (*y)->wire_format, m); + r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m); if (r != 0) return r; - if ((*x)->wire_format_size < (*y)->wire_format_size) + if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) return -1; - else if ((*x)->wire_format_size > (*y)->wire_format_size) + else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) return 1; return 0; } -static int dnssec_rsa_verify( +static int dnssec_rsa_verify_raw( const char *hash_algorithm, const void *signature, size_t signature_size, const void *data, size_t data_size, @@ -219,6 +224,196 @@ finish: return r; } +static int dnssec_rsa_verify( + const char *hash_algorithm, + const void *hash, size_t hash_size, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey) { + + size_t exponent_size, modulus_size; + void *exponent, *modulus; + + assert(hash_algorithm); + assert(hash); + assert(hash_size > 0); + assert(rrsig); + assert(dnskey); + + if (*(uint8_t*) dnskey->dnskey.key == 0) { + /* exponent is > 255 bytes long */ + + exponent = (uint8_t*) dnskey->dnskey.key + 3; + exponent_size = + ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) | + ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]); + + if (exponent_size < 256) + return -EINVAL; + + if (3 + exponent_size >= dnskey->dnskey.key_size) + return -EINVAL; + + modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; + + } else { + /* exponent is <= 255 bytes long */ + + exponent = (uint8_t*) dnskey->dnskey.key + 1; + exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; + + if (exponent_size <= 0) + return -EINVAL; + + if (1 + exponent_size >= dnskey->dnskey.key_size) + return -EINVAL; + + modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; + } + + return dnssec_rsa_verify_raw( + hash_algorithm, + rrsig->rrsig.signature, rrsig->rrsig.signature_size, + hash, hash_size, + exponent, exponent_size, + modulus, modulus_size); +} + +static int dnssec_ecdsa_verify_raw( + const char *hash_algorithm, + const char *curve, + const void *signature_r, size_t signature_r_size, + const void *signature_s, size_t signature_s_size, + const void *data, size_t data_size, + const void *key, size_t key_size) { + + gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; + gcry_mpi_t q = NULL, r = NULL, s = NULL; + gcry_error_t ge; + int k; + + assert(hash_algorithm); + + ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&signature_sexp, + NULL, + "(sig-val (ecdsa (r %m) (s %m)))", + r, + s); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&data_sexp, + NULL, + "(data (flags rfc6979) (hash %s %b))", + hash_algorithm, + (int) data_size, + data); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&public_key_sexp, + NULL, + "(public-key (ecc (curve %s) (q %m)))", + curve, + q); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); + if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) + k = 0; + else if (ge != 0) { + log_debug("ECDSA signature check failed: %s", gpg_strerror(ge)); + k = -EIO; + } else + k = 1; +finish: + if (r) + gcry_mpi_release(r); + if (s) + gcry_mpi_release(s); + if (q) + gcry_mpi_release(q); + + if (public_key_sexp) + gcry_sexp_release(public_key_sexp); + if (signature_sexp) + gcry_sexp_release(signature_sexp); + if (data_sexp) + gcry_sexp_release(data_sexp); + + return k; +} + +static int dnssec_ecdsa_verify( + const char *hash_algorithm, + int algorithm, + const void *hash, size_t hash_size, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey) { + + const char *curve; + size_t key_size; + uint8_t *q; + + assert(hash); + assert(hash_size); + assert(rrsig); + assert(dnskey); + + if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) { + key_size = 32; + curve = "NIST P-256"; + } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) { + key_size = 48; + curve = "NIST P-384"; + } else + return -EOPNOTSUPP; + + if (dnskey->dnskey.key_size != key_size * 2) + return -EINVAL; + + if (rrsig->rrsig.signature_size != key_size * 2) + return -EINVAL; + + q = alloca(key_size*2 + 1); + q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */ + memcpy(q+1, dnskey->dnskey.key, key_size*2); + + return dnssec_ecdsa_verify_raw( + hash_algorithm, + curve, + rrsig->rrsig.signature, key_size, + (uint8_t*) rrsig->rrsig.signature + key_size, key_size, + hash, hash_size, + q, key_size*2+1); +} + static void md_add_uint8(gcry_md_hd_t md, uint8_t v) { gcry_md_write(md, &v, sizeof(v)); } @@ -268,21 +463,54 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { return realtime < inception || realtime > expiration; } +static int algorithm_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC signature algorithm into a gcrypt + * digest identifier. + * + * Note that we implement all algorithms listed as "Must + * implement" and "Recommended to Implement" in RFC6944. We + * don't implement any algorithms that are listed as + * "Optional" or "Must Not Implement". Specifically, we do not + * implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and + * GOST-ECC. */ + + switch (algorithm) { + + case DNSSEC_ALGORITHM_RSASHA1: + case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: + return GCRY_MD_SHA1; + + case DNSSEC_ALGORITHM_RSASHA256: + case DNSSEC_ALGORITHM_ECDSAP256SHA256: + return GCRY_MD_SHA256; + + case DNSSEC_ALGORITHM_ECDSAP384SHA384: + return GCRY_MD_SHA384; + + case DNSSEC_ALGORITHM_RSASHA512: + return GCRY_MD_SHA512; + + default: + return -EOPNOTSUPP; + } +} + int dnssec_verify_rrset( DnsAnswer *a, - DnsResourceKey *key, + const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result) { uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; - size_t exponent_size, modulus_size, hash_size; - void *exponent, *modulus, *hash; + size_t hash_size; + void *hash; DnsResourceRecord **list, *rr; gcry_md_hd_t md = NULL; + int r, md_algorithm; size_t k, n = 0; - int r; assert(key); assert(rrsig); @@ -295,11 +523,13 @@ int dnssec_verify_rrset( * using the signature "rrsig" and the key "dnskey". It's * assumed the RRSIG and DNSKEY match. */ - if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm)) - return -EOPNOTSUPP; - - if (a->n_rrs > VERIFY_RRS_MAX) - return -E2BIG; + md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm); + if (md_algorithm == -EOPNOTSUPP) { + *result = DNSSEC_UNSUPPORTED_ALGORITHM; + return 0; + } + if (md_algorithm < 0) + return md_algorithm; r = dnssec_rrsig_expired(rrsig, realtime); if (r < 0) @@ -325,6 +555,9 @@ int dnssec_verify_rrset( return r; list[n++] = rr; + + if (n > VERIFY_RRS_MAX) + return -E2BIG; } if (n <= 0) @@ -334,28 +567,12 @@ int dnssec_verify_rrset( qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare); /* OK, the RRs are now in canonical order. Let's calculate the digest */ - switch (rrsig->rrsig.algorithm) { - - case DNSSEC_ALGORITHM_RSASHA1: - case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - gcry_md_open(&md, GCRY_MD_SHA1, 0); - hash_size = 20; - break; + initialize_libgcrypt(); - case DNSSEC_ALGORITHM_RSASHA256: - gcry_md_open(&md, GCRY_MD_SHA256, 0); - hash_size = 32; - break; - - case DNSSEC_ALGORITHM_RSASHA512: - gcry_md_open(&md, GCRY_MD_SHA512, 0); - hash_size = 64; - break; - - default: - assert_not_reached("Unknown digest"); - } + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); + gcry_md_open(&md, md_algorithm, 0); if (!md) return -EIO; @@ -373,10 +590,17 @@ int dnssec_verify_rrset( gcry_md_write(md, wire_format_name, r); for (k = 0; k < n; k++) { + const char *suffix; size_t l; rr = list[k]; - r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true); + r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix); + if (r < 0) + goto finish; + if (r > 0) /* This is a wildcard! */ + gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2); + + r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true); if (r < 0) goto finish; gcry_md_write(md, wire_format_name, r); @@ -385,12 +609,11 @@ int dnssec_verify_rrset( md_add_uint16(md, rr->key->class); md_add_uint32(md, rrsig->rrsig.original_ttl); - assert(rr->wire_format_rdata_offset <= rr->wire_format_size); - l = rr->wire_format_size - rr->wire_format_rdata_offset; + l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr); assert(l <= 0xFFFF); md_add_uint16(md, (uint16_t) l); - gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l); + gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l); } hash = gcry_md_read(md, 0); @@ -399,53 +622,30 @@ int dnssec_verify_rrset( goto finish; } - if (*(uint8_t*) dnskey->dnskey.key == 0) { - /* exponent is > 255 bytes long */ - - exponent = (uint8_t*) dnskey->dnskey.key + 3; - exponent_size = - ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) | - ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]); - - if (exponent_size < 256) { - r = -EINVAL; - goto finish; - } - - if (3 + exponent_size >= dnskey->dnskey.key_size) { - r = -EINVAL; - goto finish; - } - - modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; - modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; - - } else { - /* exponent is <= 255 bytes long */ - - exponent = (uint8_t*) dnskey->dnskey.key + 1; - exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; - - if (exponent_size <= 0) { - r = -EINVAL; - goto finish; - } + switch (rrsig->rrsig.algorithm) { - if (1 + exponent_size >= dnskey->dnskey.key_size) { - r = -EINVAL; - goto finish; - } + case DNSSEC_ALGORITHM_RSASHA1: + case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: + case DNSSEC_ALGORITHM_RSASHA256: + case DNSSEC_ALGORITHM_RSASHA512: + r = dnssec_rsa_verify( + gcry_md_algo_name(md_algorithm), + hash, hash_size, + rrsig, + dnskey); + break; - modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; - modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; + case DNSSEC_ALGORITHM_ECDSAP256SHA256: + case DNSSEC_ALGORITHM_ECDSAP384SHA384: + r = dnssec_ecdsa_verify( + gcry_md_algo_name(md_algorithm), + rrsig->rrsig.algorithm, + hash, hash_size, + rrsig, + dnskey); + break; } - r = dnssec_rsa_verify( - gcry_md_algo_name(gcry_md_get_algo(md)), - rrsig->rrsig.signature, rrsig->rrsig.signature_size, - hash, hash_size, - exponent, exponent_size, - modulus, modulus_size); if (r < 0) goto finish; @@ -457,7 +657,7 @@ finish: return r; } -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) { +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { assert(rrsig); assert(dnskey); @@ -474,18 +674,22 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske return 0; if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) return 0; + if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) + return 0; if (dnskey->dnskey.protocol != 3) return 0; if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm) return 0; - if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag) + if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag) return 0; return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer); } -int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) { +int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { + int r; + assert(key); assert(rrsig); @@ -498,24 +702,60 @@ int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) { if (rrsig->rrsig.type_covered != key->type) return 0; + /* Make sure signer is a parent of the RRset */ + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer); + if (r <= 0) + return r; + + /* Make sure the owner name has at least as many labels as the "label" fields indicates. */ + r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key)); + if (r < 0) + return r; + if (r < rrsig->rrsig.labels) + return 0; + return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key)); } +static int dnssec_fix_rrset_ttl(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord *rrsig, usec_t realtime) { + DnsResourceRecord *rr; + int r; + + assert(key); + assert(rrsig); + + DNS_ANSWER_FOREACH(rr, a) { + r = dns_resource_key_equal(key, rr->key); + if (r < 0) + return r; + if (r == 0) + continue; + + /* Pick the TTL as the minimum of the RR's TTL, the + * RR's original TTL according to the RRSIG and the + * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */ + rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl); + rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; + } + + return 0; +} + int dnssec_verify_rrset_search( DnsAnswer *a, - DnsResourceKey *key, + const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result) { - bool found_rrsig = false, found_dnskey = false; + bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false; DnsResourceRecord *rrsig; int r; assert(key); assert(result); - /* Verifies all RRs from "a" that match the key "key", against DNSKEY and DS RRs in "validated_dnskeys" */ + /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */ if (!a || a->n_rrs <= 0) return -ENODATA; @@ -523,7 +763,9 @@ int dnssec_verify_rrset_search( /* Iterate through each RRSIG RR. */ DNS_ANSWER_FOREACH(rrsig, a) { DnsResourceRecord *dnskey; + DnsAnswerFlags flags; + /* Is this an RRSIG RR that applies to RRs matching our key? */ r = dnssec_key_match_rrsig(key, rrsig); if (r < 0) return r; @@ -533,17 +775,19 @@ int dnssec_verify_rrset_search( found_rrsig = true; /* Look for a matching key */ - DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) { + DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) { DnssecResult one_result; - r = dnssec_rrsig_match_dnskey(rrsig, dnskey); + if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) + continue; + + /* Is this a DNSKEY RR that matches they key of our RRSIG? */ + r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false); if (r < 0) return r; if (r == 0) continue; - found_dnskey = true; - /* Take the time here, if it isn't set yet, so * that we do all validations with the same * time. */ @@ -556,22 +800,57 @@ int dnssec_verify_rrset_search( * combination. */ r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result); - if (r < 0 && r != EOPNOTSUPP) + if (r < 0) return r; - if (one_result == DNSSEC_VALIDATED) { + + switch (one_result) { + + case DNSSEC_VALIDATED: + /* Yay, the RR has been validated, + * return immediately, but fix up the expiry */ + r = dnssec_fix_rrset_ttl(a, key, rrsig, realtime); + if (r < 0) + return r; + *result = DNSSEC_VALIDATED; return 0; - } - /* If the signature is invalid, or done using - an unsupported algorithm, let's try another - key and/or signature. After all they - key_tags and stuff are not unique, and - might be shared by multiple keys. */ + case DNSSEC_INVALID: + /* If the signature is invalid, let's try another + key and/or signature. After all they + key_tags and stuff are not unique, and + might be shared by multiple keys. */ + found_invalid = true; + continue; + + case DNSSEC_UNSUPPORTED_ALGORITHM: + /* If the key algorithm is + unsupported, try another + RRSIG/DNSKEY pair, but remember we + encountered this, so that we can + return a proper error when we + encounter nothing better. */ + found_unsupported_algorithm = true; + continue; + + case DNSSEC_SIGNATURE_EXPIRED: + /* If the signature is expired, try + another one, but remember it, so + that we can return this */ + found_expired_rrsig = true; + continue; + + default: + assert_not_reached("Unexpected DNSSEC validation result"); + } } } - if (found_dnskey) + if (found_expired_rrsig) + *result = DNSSEC_SIGNATURE_EXPIRED; + else if (found_unsupported_algorithm) + *result = DNSSEC_UNSUPPORTED_ALGORITHM; + else if (found_invalid) *result = DNSSEC_INVALID; else if (found_rrsig) *result = DNSSEC_MISSING_KEY; @@ -581,6 +860,23 @@ int dnssec_verify_rrset_search( return 0; } +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { + DnsResourceRecord *rr; + int r; + + /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */ + + DNS_ANSWER_FOREACH(rr, a) { + r = dnssec_key_match_rrsig(key, rr); + if (r < 0) + return r; + if (r > 0) + return 1; + } + + return 0; +} + int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { size_t c = 0; int r; @@ -646,11 +942,32 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { return (int) c; } -int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) { - gcry_md_hd_t md = NULL; +static int digest_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */ + + switch (algorithm) { + + case DNSSEC_DIGEST_SHA1: + return GCRY_MD_SHA1; + + case DNSSEC_DIGEST_SHA256: + return GCRY_MD_SHA256; + + case DNSSEC_DIGEST_SHA384: + return GCRY_MD_SHA384; + + default: + return -EOPNOTSUPP; + } +} + +int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX]; + gcry_md_hd_t md = NULL; + size_t hash_size; + int md_algorithm, r; void *result; - int r; assert(dnskey); assert(ds); @@ -663,48 +980,41 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) { return -EINVAL; if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) return -EKEYREJECTED; + if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) + return -EKEYREJECTED; if (dnskey->dnskey.protocol != 3) return -EKEYREJECTED; if (dnskey->dnskey.algorithm != ds->ds.algorithm) return 0; - if (dnssec_keytag(dnskey) != ds->ds.key_tag) + if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag) return 0; - if (!dnssec_digest_supported(ds->ds.digest_type)) - return -EOPNOTSUPP; - - switch (ds->ds.digest_type) { + initialize_libgcrypt(); - case DNSSEC_DIGEST_SHA1: + md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type); + if (md_algorithm < 0) + return md_algorithm; - if (ds->ds.digest_size != 20) - return 0; + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); - gcry_md_open(&md, GCRY_MD_SHA1, 0); - break; - - case DNSSEC_DIGEST_SHA256: - - if (ds->ds.digest_size != 32) - return 0; - - gcry_md_open(&md, GCRY_MD_SHA256, 0); - break; + if (ds->ds.digest_size != hash_size) + return 0; - default: - assert_not_reached("Unknown digest"); - } + r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name)); + if (r < 0) + return r; + gcry_md_open(&md, md_algorithm, 0); if (!md) return -EIO; - r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name)); - if (r < 0) - goto finish; - gcry_md_write(md, owner_name, r); - md_add_uint16(md, dnskey->dnskey.flags); + if (mask_revoke) + md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE); + else + md_add_uint16(md, dnskey->dnskey.flags); md_add_uint8(md, dnskey->dnskey.protocol); md_add_uint8(md, dnskey->dnskey.algorithm); gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size); @@ -724,6 +1034,7 @@ finish: int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { DnsResourceRecord *ds; + DnsAnswerFlags flags; int r; assert(dnskey); @@ -731,12 +1042,26 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ if (dnskey->key->type != DNS_TYPE_DNSKEY) return 0; - DNS_ANSWER_FOREACH(ds, validated_ds) { + DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) { + + if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) + continue; if (ds->key->type != DNS_TYPE_DS) continue; - r = dnssec_verify_dnskey(dnskey, ds); + if (ds->key->class != dnskey->key->class) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_verify_dnskey(dnskey, ds, false); + if (r == -EKEYREJECTED) + return 0; /* The DNSKEY is revoked or otherwise invalid, we won't bless it */ if (r < 0) return r; if (r > 0) @@ -746,20 +1071,514 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ return 0; } -static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = { - [DNSSEC_NO] = "no", - [DNSSEC_TRUST] = "trust", - [DNSSEC_YES] = "yes", -}; -DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode); +static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC NSEC3 hash algorithm into a gcrypt digest identifier */ + + switch (algorithm) { + + case NSEC3_ALGORITHM_SHA1: + return GCRY_MD_SHA1; + + default: + return -EOPNOTSUPP; + } +} + +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { + uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX]; + gcry_md_hd_t md = NULL; + size_t hash_size; + int algorithm; + void *result; + unsigned k; + int r; + + assert(nsec3); + assert(name); + assert(ret); + + if (nsec3->key->type != DNS_TYPE_NSEC3) + return -EINVAL; + + if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) { + log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3)); + return -EOPNOTSUPP; + } + + algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm); + if (algorithm < 0) + return algorithm; + + initialize_libgcrypt(); + + hash_size = gcry_md_get_algo_dlen(algorithm); + assert(hash_size > 0); + + if (nsec3->nsec3.next_hashed_name_size != hash_size) + return -EINVAL; + + r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); + if (r < 0) + return r; + + gcry_md_open(&md, algorithm, 0); + if (!md) + return -EIO; + + gcry_md_write(md, wire_format, r); + gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + + for (k = 0; k < nsec3->nsec3.iterations; k++) { + uint8_t tmp[hash_size]; + memcpy(tmp, result, hash_size); + + gcry_md_reset(md); + gcry_md_write(md, tmp, hash_size); + gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + } + + memcpy(ret, result, hash_size); + r = (int) hash_size; + +finish: + gcry_md_close(md); + return r; +} + +static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourceRecord *nsec3) { + const char *a, *b; + int r; + + assert(rr); + + if (rr->key->type != DNS_TYPE_NSEC3) + return 0; + + /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */ + if (!IN_SET(rr->nsec3.flags, 0, 1)) + return 0; + + /* Ignore NSEC3 RRs whose algorithm we don't know */ + if (nsec3_hash_to_gcrypt_md(rr->nsec3.algorithm) < 0) + return 0; + /* Ignore NSEC3 RRs with an excessive number of required iterations */ + if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX) + return 0; + + if (!nsec3) + return 1; + + /* If a second NSEC3 RR is specified, also check if they are from the same zone. */ + + if (nsec3 == rr) /* Shortcut */ + return 1; + + if (rr->key->class != nsec3->key->class) + return 0; + if (rr->nsec3.algorithm != nsec3->nsec3.algorithm) + return 0; + if (rr->nsec3.iterations != nsec3->nsec3.iterations) + return 0; + if (rr->nsec3.salt_size != nsec3->nsec3.salt_size) + return 0; + if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0) + return 0; + + a = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&a); /* strip off hash */ + if (r < 0) + return r; + if (r == 0) + return 0; + + b = DNS_RESOURCE_KEY_NAME(nsec3->key); + r = dns_name_parent(&b); /* strip off hash */ + if (r < 0) + return r; + if (r == 0) + return 0; + + return dns_name_equal(a, b); +} + +static int nsec3_hashed_domain(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { + _cleanup_free_ char *l = NULL, *hashed_domain = NULL; + uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; + int hashed_size; + + assert(nsec3); + assert(domain); + assert(zone); + assert(ret); + + hashed_size = dnssec_nsec3_hash(nsec3, domain, hashed); + if (hashed_size < 0) + return hashed_size; + + l = base32hexmem(hashed, hashed_size, false); + if (!l) + return -ENOMEM; + + hashed_domain = strjoin(l, ".", zone, NULL); + if (!hashed_domain) + return -ENOMEM; + + *ret = hashed_domain; + hashed_domain = NULL; + + return hashed_size; +} + +/* See RFC 5155, Section 8 + * First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest + * enclosure. Secondly, find a proof that there is no closer enclosure and either a proof that there + * is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that + * matches the wildcard domain. + * + * Based on this we can prove either the existence of the record in @key, or NXDOMAIN or NODATA, or + * that there is no proof either way. The latter is the case if a the proof of non-existence of a given + * name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records + * to conclude anything we indicate this by returning NO_RR. */ +static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + _cleanup_free_ char *next_closer_domain = NULL, *wildcard = NULL, *wildcard_domain = NULL; + const char *zone, *p, *pp = NULL; + DnsResourceRecord *rr, *enclosure_rr, *suffix_rr, *wildcard_rr = NULL; + DnsAnswerFlags flags; + int hashed_size, r; + bool a, no_closer = false, no_wildcard = false, optout = false; + + assert(key); + assert(result); + + /* First step, find the zone name and the NSEC3 parameters of the zone. + * it is sufficient to look for the longest common suffix we find with + * any NSEC3 RR in the response. Any NSEC3 record will do as all NSEC3 + * records from a given zone in a response must use the same + * parameters. */ + zone = DNS_RESOURCE_KEY_NAME(key); + for (;;) { + DNS_ANSWER_FOREACH_FLAGS(suffix_rr, flags, answer) { + r = nsec3_is_good(suffix_rr, flags, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_rr->key), 1, zone); + if (r < 0) + return r; + if (r > 0) + goto found_zone; + } + + /* Strip one label from the front */ + r = dns_name_parent(&zone); + if (r < 0) + return r; + if (r == 0) + break; + } + + *result = DNSSEC_NSEC_NO_RR; + return 0; + +found_zone: + /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */ + p = DNS_RESOURCE_KEY_NAME(key); + for (;;) { + _cleanup_free_ char *hashed_domain = NULL; + + hashed_size = nsec3_hashed_domain(suffix_rr, p, zone, &hashed_domain); + if (hashed_size == -EOPNOTSUPP) { + *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM; + return 0; + } + if (hashed_size < 0) + return hashed_size; + + DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) { + + r = nsec3_is_good(enclosure_rr, flags, suffix_rr); + if (r < 0) + return r; + if (r == 0) + continue; + + if (enclosure_rr->nsec3.next_hashed_name_size != (size_t) hashed_size) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(enclosure_rr->key), hashed_domain); + if (r < 0) + return r; + if (r > 0) { + a = flags & DNS_ANSWER_AUTHENTICATED; + goto found_closest_encloser; + } + } + + /* We didn't find the closest encloser with this name, + * but let's remember this domain name, it might be + * the next closer name */ + + pp = p; + + /* Strip one label from the front */ + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + } + + *result = DNSSEC_NSEC_NO_RR; + return 0; + +found_closest_encloser: + /* We found a closest encloser in 'p'; next closer is 'pp' */ + + /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */ + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME)) + return -EBADMSG; + + /* Ensure that this data is from the delegated domain + * (i.e. originates from the "lower" DNS server), and isn't + * just glue records (i.e. doesn't originate from the "upper" + * DNS server). */ + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && + !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + + if (!pp) { + /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */ + if (bitmap_isset(enclosure_rr->nsec3.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) + *authenticated = a; + if (ttl) + *ttl = enclosure_rr->ttl; + + return 0; + } + + /* Prove that there is no next closer and whether or not there is a wildcard domain. */ + + wildcard = strappend("*.", p); + if (!wildcard) + return -ENOMEM; + + r = nsec3_hashed_domain(enclosure_rr, wildcard, zone, &wildcard_domain); + if (r < 0) + return r; + if (r != hashed_size) + return -EBADMSG; + + r = nsec3_hashed_domain(enclosure_rr, pp, zone, &next_closer_domain); + if (r < 0) + return r; + if (r != hashed_size) + return -EBADMSG; + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + _cleanup_free_ char *label = NULL, *next_hashed_domain = NULL; + + r = nsec3_is_good(rr, flags, suffix_rr); + if (r < 0) + return r; + if (r == 0) + continue; + + label = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); + if (!label) + return -ENOMEM; + + next_hashed_domain = strjoin(label, ".", zone, NULL); + if (!next_hashed_domain) + return -ENOMEM; + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain); + if (r < 0) + return r; + if (r > 0) { + if (rr->nsec3.flags & 1) + optout = true; + + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + no_closer = true; + } + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain); + if (r < 0) + return r; + if (r > 0) { + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + wildcard_rr = rr; + } + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain, next_hashed_domain); + if (r < 0) + return r; + if (r > 0) { + if (rr->nsec3.flags & 1) + /* This only makes sense if we have a wildcard delegation, which is + * very unlikely, see RFC 4592, Section 4.2, but we cannot rely on + * this not happening, so hence cannot simply conclude NXDOMAIN as + * we would wish */ + optout = true; + + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + no_wildcard = true; + } + } + + if (wildcard_rr && no_wildcard) + return -EBADMSG; + + if (!no_closer) { + *result = DNSSEC_NSEC_NO_RR; + return 0; + } + + if (wildcard_rr) { + /* A wildcard exists that matches our query. */ + if (optout) + /* This is not specified in any RFC to the best of my knowledge, but + * if the next closer enclosure is covered by an opt-out NSEC3 RR + * it means that we cannot prove that the source of synthesis is + * correct, as there may be a closer match. */ + *result = DNSSEC_NSEC_OPTOUT; + else if (bitmap_isset(wildcard_rr->nsec3.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(wildcard_rr->nsec3.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + } else { + if (optout) + /* The RFC only specifies that we have to care for optout for NODATA for + * DS records. However, children of an insecure opt-out delegation should + * also be considered opt-out, rather than verified NXDOMAIN. + * Note that we do not require a proof of wildcard non-existence if the + * next closer domain is covered by an opt-out, as that would not provide + * any additional information. */ + *result = DNSSEC_NSEC_OPTOUT; + else if (no_wildcard) + *result = DNSSEC_NSEC_NXDOMAIN; + else { + *result = DNSSEC_NSEC_NO_RR; + + return 0; + } + } + + if (authenticated) + *authenticated = a; + + if (ttl) + *ttl = enclosure_rr->ttl; + + return 0; +} + +int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + DnsResourceRecord *rr; + bool have_nsec3 = false; + DnsAnswerFlags flags; + int r; + + assert(key); + assert(result); + + /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */ + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + + if (rr->key->class != key->class) + continue; + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); + if (r < 0) + return r; + if (r > 0) { + if (bitmap_isset(rr->nsec.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + if (ttl) + *ttl = rr->ttl; + + return 0; + } + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_NSEC_NXDOMAIN; + + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + if (ttl) + *ttl = rr->ttl; + + return 0; + } + break; + + case DNS_TYPE_NSEC3: + have_nsec3 = true; + break; + } + } + + /* OK, this was not sufficient. Let's see if NSEC3 can help. */ + if (have_nsec3) + return dnssec_test_nsec3(answer, key, result, authenticated, ttl); + + /* No approproate NSEC RR found, report this. */ + *result = DNSSEC_NSEC_NO_RR; + return 0; +} static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { [DNSSEC_VALIDATED] = "validated", [DNSSEC_INVALID] = "invalid", - [DNSSEC_UNSIGNED] = "unsigned", + [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", + [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", [DNSSEC_NO_SIGNATURE] = "no-signature", [DNSSEC_MISSING_KEY] = "missing-key", - [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", + [DNSSEC_UNSIGNED] = "unsigned", [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary", + [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch", + [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server", }; DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult); diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h index f0825ba23f..6977faca75 100644 --- a/src/resolve/resolved-dns-dnssec.h +++ b/src/resolve/resolved-dns-dnssec.h @@ -28,49 +28,60 @@ typedef enum DnssecResult DnssecResult; #include "resolved-dns-answer.h" #include "resolved-dns-rr.h" -enum DnssecMode { - /* No DNSSEC validation is done */ - DNSSEC_NO, - - /* Trust the AD bit sent by the server. UNSAFE! */ - DNSSEC_TRUST, - - /* Validate locally, if the server knows DO, but if not, don't. Don't trust the AD bit */ - DNSSEC_YES, - - _DNSSEC_MODE_MAX, - _DNSSEC_MODE_INVALID = -1 -}; - enum DnssecResult { + /* These four are returned by dnssec_verify_rrset() */ DNSSEC_VALIDATED, DNSSEC_INVALID, - DNSSEC_UNSIGNED, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM, + + /* These two are added by dnssec_verify_rrset_search() */ DNSSEC_NO_SIGNATURE, DNSSEC_MISSING_KEY, - DNSSEC_SIGNATURE_EXPIRED, + + /* These two are added by the DnsTransaction logic */ + DNSSEC_UNSIGNED, DNSSEC_FAILED_AUXILIARY, + DNSSEC_NSEC_MISMATCH, + DNSSEC_INCOMPATIBLE_SERVER, + _DNSSEC_RESULT_MAX, _DNSSEC_RESULT_INVALID = -1 }; #define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2) -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey); -int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig); +/* The longest digest we'll ever generate, of all digest algorithms we support */ +#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32)) -int dnssec_verify_rrset(DnsAnswer *answer, DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result); -int dnssec_verify_rrset_search(DnsAnswer *answer, DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result); +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok); +int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig); -int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds); +int dnssec_verify_rrset(DnsAnswer *answer, const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result); +int dnssec_verify_rrset_search(DnsAnswer *answer, const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result); + +int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke); int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); -uint16_t dnssec_keytag(DnsResourceRecord *dnskey); +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key); + +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke); int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); -const char* dnssec_mode_to_string(DnssecMode m) _const_; -DnssecMode dnssec_mode_from_string(const char *s) _pure_; +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); + +typedef enum DnssecNsecResult { + DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ + DNSSEC_NSEC_CNAME, /* Would be NODATA, but for the existence of a CNAME RR */ + DNSSEC_NSEC_UNSUPPORTED_ALGORITHM, + DNSSEC_NSEC_NXDOMAIN, + DNSSEC_NSEC_NODATA, + DNSSEC_NSEC_FOUND, + DNSSEC_NSEC_OPTOUT, +} DnssecNsecResult; + +int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl); const char* dnssec_result_to_string(DnssecResult m) _const_; DnssecResult dnssec_result_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index e90500ce70..4750bf1f5d 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -58,6 +58,7 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { p->size = p->rindex = DNS_PACKET_HEADER_SIZE; p->allocated = a; p->protocol = protocol; + p->opt_start = p->opt_size = (size_t) -1; p->n_ref = 1; *ret = p; @@ -80,7 +81,7 @@ void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool trun h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, 0 /* opcode */, 0 /* c */, - 0/* tc */, + 0 /* tc */, 0 /* t */, 0 /* ra */, 0 /* ad */, @@ -171,8 +172,7 @@ DnsPacket *dns_packet_unref(DnsPacket *p) { assert(p->n_ref > 0); - if (p->more) - dns_packet_unref(p->more); + dns_packet_unref(p->more); if (p->n_ref == 1) dns_packet_free(p); @@ -439,10 +439,15 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_ return 0; } -int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) { +int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) { uint8_t *w; int r; + /* Append a label to a packet. Optionally, does this in DNSSEC + * canonical form, if this label is marked as a candidate for + * it, and the canonical form logic is enabled for the + * packet */ + assert(p); assert(d); @@ -455,7 +460,7 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start *(w++) = (uint8_t) l; - if (p->canonical_form) { + if (p->canonical_form && canonical_candidate) { size_t i; /* Generate in canonical form, as defined by DNSSEC @@ -480,6 +485,7 @@ int dns_packet_append_name( DnsPacket *p, const char *name, bool allow_compression, + bool canonical_candidate, size_t *start) { size_t saved_size; @@ -493,8 +499,8 @@ int dns_packet_append_name( saved_size = p->size; - while (*name) { - _cleanup_free_ char *s = NULL; + while (!dns_name_is_root(name)) { + const char *z = name; char label[DNS_LABEL_MAX]; size_t n = 0; int k; @@ -513,12 +519,6 @@ int dns_packet_append_name( } } - s = strdup(name); - if (!s) { - r = -ENOMEM; - goto fail; - } - r = dns_label_unescape(&name, label, sizeof(label)); if (r < 0) goto fail; @@ -534,11 +534,19 @@ int dns_packet_append_name( if (k > 0) r = k; - r = dns_packet_append_label(p, label, r, &n); + r = dns_packet_append_label(p, label, r, canonical_candidate, &n); if (r < 0) goto fail; if (allow_compression) { + _cleanup_free_ char *s = NULL; + + s = strdup(z); + if (!s) { + r = -ENOMEM; + goto fail; + } + r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops); if (r < 0) goto fail; @@ -575,7 +583,7 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) saved_size = p->size; - r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, NULL); + r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, true, NULL); if (r < 0) goto fail; @@ -597,7 +605,7 @@ fail: return r; } -static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, uint8_t *types, size_t *start) { +static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) { size_t saved_size; int r; @@ -638,7 +646,6 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { int r; assert(p); - assert(types); saved_size = p->size; @@ -654,15 +661,16 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { } window = n >> 8; - entry = n & 255; bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); } - r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); - if (r < 0) - goto fail; + if (bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + } if (start) *start = saved_size; @@ -674,7 +682,7 @@ fail: } /* Append the OPT pseudo-RR described in RFC6891 */ -int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) { +int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) { size_t saved_size; int r; @@ -682,6 +690,11 @@ int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, /* we must never advertise supported packet size smaller than the legacy max */ assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX); + if (p->opt_start != (size_t) -1) + return -EBUSY; + + assert(p->opt_size == (size_t) -1); + saved_size = p->size; /* empty name */ @@ -710,10 +723,48 @@ int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, goto fail; /* RDLENGTH */ - r = dns_packet_append_uint16(p, 0, NULL); + + if (edns0_do) { + /* If DO is on, also append RFC6975 Algorithm data */ + + static const uint8_t rfc6975[] = { + + 0, 5, /* OPTION_CODE: DAU */ + 0, 6, /* LIST_LENGTH */ + DNSSEC_ALGORITHM_RSASHA1, + DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA256, + DNSSEC_ALGORITHM_RSASHA512, + DNSSEC_ALGORITHM_ECDSAP256SHA256, + DNSSEC_ALGORITHM_ECDSAP384SHA384, + + 0, 6, /* OPTION_CODE: DHU */ + 0, 3, /* LIST_LENGTH */ + DNSSEC_DIGEST_SHA1, + DNSSEC_DIGEST_SHA256, + DNSSEC_DIGEST_SHA384, + + 0, 7, /* OPTION_CODE: N3U */ + 0, 1, /* LIST_LENGTH */ + NSEC3_ALGORITHM_SHA1, + }; + + r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL); + } else + r = dns_packet_append_uint16(p, 0, NULL); + if (r < 0) goto fail; + DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) + 1); + + p->opt_start = saved_size; + p->opt_size = p->size - saved_size; + if (start) *start = saved_size; @@ -724,6 +775,27 @@ fail: return r; } +int dns_packet_truncate_opt(DnsPacket *p) { + assert(p); + + if (p->opt_start == (size_t) -1) { + assert(p->opt_size == (size_t) -1); + return 0; + } + + assert(p->opt_size != (size_t) -1); + assert(DNS_PACKET_ARCOUNT(p) > 0); + + if (p->opt_start + p->opt_size != p->size) + return -EBUSY; + + dns_packet_truncate(p, p->opt_start); + DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1); + p->opt_start = p->opt_size = (size_t) -1; + + return 1; +} + int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) { size_t saved_size, rdlength_offset, end, rdlength, rds; int r; @@ -763,14 +835,14 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->srv.name, true, NULL); + r = dns_packet_append_name(p, rr->srv.name, true, false, NULL); break; case DNS_TYPE_PTR: case DNS_TYPE_NS: case DNS_TYPE_CNAME: case DNS_TYPE_DNAME: - r = dns_packet_append_name(p, rr->ptr.name, true, NULL); + r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL); break; case DNS_TYPE_HINFO: @@ -813,11 +885,11 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_SOA: - r = dns_packet_append_name(p, rr->soa.mname, true, NULL); + r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL); if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->soa.rname, true, NULL); + r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL); if (r < 0) goto fail; @@ -845,7 +917,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->mx.exchange, true, NULL); + r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); break; case DNS_TYPE_LOC: @@ -949,7 +1021,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->rrsig.signer, false, NULL); + r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); if (r < 0) goto fail; @@ -957,7 +1029,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_NSEC: - r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, NULL); + r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); if (r < 0) goto fail; @@ -1039,7 +1111,6 @@ fail: return r; } - int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { assert(p); @@ -1449,9 +1520,9 @@ fail: return r; } -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { _cleanup_free_ char *name = NULL; - bool cache_flush = true; + bool cache_flush = false; uint16_t class, type; DnsResourceKey *key; size_t saved_rindex; @@ -1477,10 +1548,10 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { if (p->protocol == DNS_PROTOCOL_MDNS) { /* See RFC6762, Section 10.2 */ - if (class & MDNS_RR_CACHE_FLUSH) + if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) { class &= ~MDNS_RR_CACHE_FLUSH; - else - cache_flush = false; + cache_flush = true; + } } key = dns_resource_key_new_consume(class, type, name); @@ -1489,11 +1560,11 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { goto fail; } - key->cache_flush = cache_flush; - name = NULL; *ret = key; + if (ret_cache_flush) + *ret_cache_flush = cache_flush; if (start) *start = saved_rindex; @@ -1509,11 +1580,12 @@ static bool loc_size_ok(uint8_t size) { return m <= 9 && e <= 9 && (m > 0 || e == 0); } -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; size_t saved_rindex, offset; uint16_t rdlength; + bool cache_flush; int r; assert(p); @@ -1521,14 +1593,12 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { saved_rindex = p->rindex; - r = dns_packet_read_key(p, &key, NULL); + r = dns_packet_read_key(p, &key, &cache_flush, NULL); if (r < 0) goto fail; - if (key->class == DNS_CLASS_ANY || - key->type == DNS_TYPE_ANY || - key->type == DNS_TYPE_AXFR || - key->type == DNS_TYPE_IXFR) { + if (!dns_class_is_valid_rr(key->class)|| + !dns_type_is_valid_rr(key->type)) { r = -EBADMSG; goto fail; } @@ -1543,6 +1613,11 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { if (r < 0) goto fail; + /* RFC 2181, Section 8, suggests to + * treat a TTL with the MSB set as a zero TTL. */ + if (rr->ttl & UINT32_C(0x80000000)) + rr->ttl = 0; + r = dns_packet_read_uint16(p, &rdlength, NULL); if (r < 0) goto fail; @@ -1851,7 +1926,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { case DNS_TYPE_NSEC: { /* - * RFC6762, section 18.14 explicly states mDNS should use name compression. + * RFC6762, section 18.14 explictly states mDNS should use name compression. * This contradicts RFC3845, section 2.1.1 */ @@ -1935,6 +2010,8 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { *ret = rr; rr = NULL; + if (ret_cache_flush) + *ret_cache_flush = cache_flush; if (start) *start = saved_rindex; @@ -1967,11 +2044,22 @@ int dns_packet_extract(DnsPacket *p) { for (i = 0; i < n; i++) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + bool cache_flush; - r = dns_packet_read_key(p, &key, NULL); + r = dns_packet_read_key(p, &key, &cache_flush, NULL); if (r < 0) goto finish; + if (cache_flush) { + r = -EBADMSG; + goto finish; + } + + if (!dns_type_is_valid_query(key->type)) { + r = -EBADMSG; + goto finish; + } + r = dns_question_add(question, key); if (r < 0) goto finish; @@ -1988,18 +2076,45 @@ int dns_packet_extract(DnsPacket *p) { for (i = 0; i < n; i++) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + bool cache_flush; - r = dns_packet_read_rr(p, &rr, NULL); + r = dns_packet_read_rr(p, &rr, &cache_flush, NULL); if (r < 0) goto finish; if (rr->key->type == DNS_TYPE_OPT) { - if (p->opt) - return -EBADMSG; + + if (!dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key))) { + r = -EBADMSG; + goto finish; + } + + /* The OPT RR is only valid in the Additional section */ + if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) { + r = -EBADMSG; + goto finish; + } + + /* Two OPT RRs? */ + if (p->opt) { + r = -EBADMSG; + goto finish; + } p->opt = dns_resource_record_ref(rr); } else { - r = dns_answer_add(answer, rr, p->ifindex); + + /* According to RFC 4795, section + * 2.9. only the RRs from the Answer + * section shall be cached. Hence mark + * only those RRs as cacheable by + * default, but not the ones from the + * Additional or Authority + * sections. */ + + r = dns_answer_add(answer, rr, p->ifindex, + (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) | + (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0)); if (r < 0) goto finish; } diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 5b6a71dc01..6821be73e4 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -76,6 +76,7 @@ struct DnsPacket { size_t size, allocated, rindex; void *_data; /* don't access directly, use DNS_PACKET_DATA()! */ Hashmap *names; /* For name compression */ + size_t opt_start, opt_size; /* Parsed data */ DnsQuestion *question; @@ -118,7 +119,17 @@ static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { #define DNS_PACKET_RA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 7) & 1) #define DNS_PACKET_AD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 5) & 1) #define DNS_PACKET_CD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 4) & 1) -#define DNS_PACKET_RCODE(p) (be16toh(DNS_PACKET_HEADER(p)->flags) & 15) + +static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) { + uint16_t rcode; + + if (p->opt) + rcode = (uint16_t) (p->opt->ttl >> 24); + else + rcode = 0; + + return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 15); +} /* LLMNR defines some bits differently */ #define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p) @@ -169,13 +180,14 @@ int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start); int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start); int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start); int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start); -int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, size_t *start); -int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, size_t *start); +int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start); +int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start); int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start); int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start); -int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start); +int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start); void dns_packet_truncate(DnsPacket *p, size_t sz); +int dns_packet_truncate_opt(DnsPacket *p); int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start); int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start); @@ -185,8 +197,8 @@ int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start); int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start); int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start); int dns_packet_read_name(DnsPacket *p, char **ret, bool allow_compression, size_t *start); -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start); -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start); +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start); +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start); void dns_packet_rewind(DnsPacket *p, size_t idx); @@ -201,6 +213,7 @@ static inline bool DNS_PACKET_SHALL_CACHE(DnsPacket *p) { return in_addr_is_localhost(p->family, &p->sender) == 0; } +/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */ enum { DNS_RCODE_SUCCESS = 0, DNS_RCODE_FORMERR = 1, diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index a6565f2ba2..0f2f2599ab 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -185,6 +185,14 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { switch (t->state) { + case DNS_TRANSACTION_NULL: + /* If there's a NULL transaction pending, then + * this means not all transactions where + * started yet, and we were called from within + * the stackframe that is supposed to start + * remaining transactions. In this case, + * simply claim the candidate is pending. */ + case DNS_TRANSACTION_PENDING: case DNS_TRANSACTION_VALIDATING: /* If there's one transaction currently in @@ -197,9 +205,6 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { state = t->state; break; - case DNS_TRANSACTION_NULL: - assert_not_reached("Transaction not started?"); - default: if (state != DNS_TRANSACTION_SUCCESS) state = t->state; @@ -549,7 +554,7 @@ static int synthesize_localhost_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer * rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); - r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex)); + r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; } @@ -563,7 +568,7 @@ static int synthesize_localhost_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer * rr->aaaa.in6_addr = in6addr_loopback; - r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex)); + r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; } @@ -571,7 +576,7 @@ static int synthesize_localhost_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer * return 0; } -static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex) { +static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from); @@ -582,7 +587,7 @@ static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, if (!rr->ptr.name) return -ENOMEM; - return dns_answer_add(*answer, rr, ifindex); + return dns_answer_add(*answer, rr, ifindex, flags); } static int synthesize_localhost_ptr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { @@ -592,12 +597,12 @@ static int synthesize_localhost_ptr(DnsQuery *q, DnsResourceKey *key, DnsAnswer assert(key); assert(answer); - r = dns_answer_reserve(answer, 1); - if (r < 0) - return r; - if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) { - r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", SYNTHESIZE_IFINDEX(q->ifindex)); + r = dns_answer_reserve(answer, 1); + if (r < 0) + return r; + + r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; } @@ -628,7 +633,7 @@ static int answer_add_addresses_rr( if (r < 0) return r; - r = dns_answer_add(*answer, rr, addresses[j].ifindex); + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; } @@ -669,7 +674,7 @@ static int answer_add_addresses_ptr( if (r < 0) return r; - r = dns_answer_add(*answer, rr, addresses[j].ifindex); + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; } @@ -735,15 +740,15 @@ static int synthesize_system_hostname_ptr(DnsQuery *q, int af, const union in_ad if (r < 0) return r; - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->llmnr_hostname, SYNTHESIZE_IFINDEX(q->ifindex)); + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->llmnr_hostname, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->mdns_hostname, SYNTHESIZE_IFINDEX(q->ifindex)); + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->mdns_hostname, SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", SYNTHESIZE_IFINDEX(q->ifindex)); + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", SYNTHESIZE_IFINDEX(q->ifindex), DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; @@ -805,7 +810,7 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { /* Tries to synthesize localhost RR replies where appropriate */ if (!IN_SET(*state, - DNS_TRANSACTION_FAILURE, + DNS_TRANSACTION_RCODE_FAILURE, DNS_TRANSACTION_NO_SERVERS, DNS_TRANSACTION_TIMEOUT, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED)) @@ -981,6 +986,7 @@ fail: static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; bool has_authenticated = false, has_non_authenticated = false; + DnssecResult dnssec_result_authenticated = _DNSSEC_RESULT_INVALID, dnssec_result_non_authenticated = _DNSSEC_RESULT_INVALID; DnsTransaction *t; Iterator i; int r; @@ -1004,12 +1010,16 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { dns_query_complete(q, DNS_TRANSACTION_RESOURCES); return; } + q->answer_rcode = t->answer_rcode; - if (t->answer_authenticated) + if (t->answer_authenticated) { has_authenticated = true; - else + dnssec_result_authenticated = t->answer_dnssec_result; + } else { has_non_authenticated = true; + dnssec_result_non_authenticated = t->answer_dnssec_result; + } state = DNS_TRANSACTION_SUCCESS; break; @@ -1026,22 +1036,25 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { /* Any kind of failure? Store the data away, * if there's nothing stored yet. */ - if (state != DNS_TRANSACTION_SUCCESS) { - - dns_answer_unref(q->answer); - q->answer = dns_answer_ref(t->answer); - q->answer_rcode = t->answer_rcode; + if (state == DNS_TRANSACTION_SUCCESS) + continue; - state = t->state; - } + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = t->answer_rcode; + q->answer_dnssec_result = t->answer_dnssec_result; + state = t->state; break; } } + if (state == DNS_TRANSACTION_SUCCESS) { + q->answer_authenticated = has_authenticated && !has_non_authenticated; + q->answer_dnssec_result = q->answer_authenticated ? dnssec_result_authenticated : dnssec_result_non_authenticated; + } + q->answer_protocol = c->scope->protocol; q->answer_family = c->scope->family; - q->answer_authenticated = has_authenticated && !has_non_authenticated; dns_search_domain_unref(q->answer_search_domain); q->answer_search_domain = dns_search_domain_ref(c->search_domain); @@ -1132,8 +1145,8 @@ int dns_query_process_cname(DnsQuery *q) { assert(q); - if (q->state != DNS_TRANSACTION_SUCCESS) - return 0; + if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL)) + return DNS_QUERY_NOMATCH; DNS_ANSWER_FOREACH(rr, q->answer) { @@ -1141,7 +1154,7 @@ int dns_query_process_cname(DnsQuery *q) { if (r < 0) return r; if (r > 0) - return 0; /* The answer matches directly, no need to follow cnames */ + return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */ r = dns_question_matches_cname(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); if (r < 0) @@ -1151,7 +1164,7 @@ int dns_query_process_cname(DnsQuery *q) { } if (!cname) - return 0; /* No cname to follow */ + return DNS_QUERY_NOMATCH; /* No match and no cname to follow */ if (q->flags & SD_RESOLVED_NO_CNAME) return -ELOOP; @@ -1163,20 +1176,16 @@ int dns_query_process_cname(DnsQuery *q) { /* Let's see if the answer can already answer the new * redirected question */ - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(q->question, rr, NULL); - if (r < 0) - return r; - if (r > 0) - return 0; /* It can answer it, yay! */ - } + r = dns_query_process_cname(q); + if (r != DNS_QUERY_NOMATCH) + return r; /* OK, it cannot, let's begin with the new query */ r = dns_query_go(q); if (r < 0) return r; - return 1; /* We return > 0, if we restarted the query for a new cname */ + return DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */ } static int on_bus_track(sd_bus_track *t, void *userdata) { diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index d7f96c3ca4..4a0d265a2d 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -72,10 +72,11 @@ struct DnsQuery { /* Discovered data */ DnsAnswer *answer; int answer_rcode; + DnssecResult answer_dnssec_result; + bool answer_authenticated; DnsProtocol answer_protocol; int answer_family; DnsSearchDomain *answer_search_domain; - bool answer_authenticated; /* Bus client information */ sd_bus_message *request; @@ -94,6 +95,12 @@ struct DnsQuery { LIST_FIELDS(DnsQuery, auxiliary_queries); }; +enum { + DNS_QUERY_MATCH, + DNS_QUERY_NOMATCH, + DNS_QUERY_RESTARTED, +}; + DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); void dns_query_candidate_notify(DnsQueryCandidate *c); diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 74c9d87319..76723ec4d0 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -184,7 +184,7 @@ int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { return 1; } -int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain) { +int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain) { int r; assert(key); @@ -261,16 +261,13 @@ int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey * /* Checks whether 'soa' is a SOA record for the specified key. */ - if (soa->class != DNS_CLASS_IN) + if (soa->class != key->class) return 0; if (soa->type != DNS_TYPE_SOA) return 0; - if (!dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa))) - return 0; - - return 1; + return dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa)); } static void dns_resource_key_hash_func(const void *i, struct siphash *state) { @@ -311,9 +308,12 @@ const struct hash_ops dns_resource_key_hash_ops = { int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) { char cbuf[strlen("CLASS") + DECIMAL_STR_MAX(uint16_t)], tbuf[strlen("TYPE") + DECIMAL_STR_MAX(uint16_t)]; - const char *c, *t; + const char *c, *t, *n; char *s; + /* If we cannot convert the CLASS/TYPE into a known string, + use the format recommended by RFC 3597, Section 5. */ + c = dns_class_to_string(key->class); if (!c) { sprintf(cbuf, "CLASS%u", key->class); @@ -326,7 +326,8 @@ int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) { t = tbuf; } - if (asprintf(&s, "%s. %s %-5s", DNS_RESOURCE_KEY_NAME(key), c, t) < 0) + n = DNS_RESOURCE_KEY_NAME(key); + if (asprintf(&s, "%s%s %s %-5s", n, endswith(n, ".") ? "" : ".", c, t) < 0) return -ENOMEM; *ret = s; @@ -342,6 +343,7 @@ DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { rr->n_ref = 1; rr->key = dns_resource_key_ref(key); + rr->expiry = USEC_INFINITY; return rr; } @@ -451,6 +453,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { dns_resource_key_unref(rr->key); } + free(rr->to_string); free(rr); return NULL; @@ -766,16 +769,19 @@ static char *format_txt(DnsTxtItem *first) { return s; } -int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { +const char *dns_resource_record_to_string(DnsResourceRecord *rr) { _cleanup_free_ char *k = NULL, *t = NULL; char *s; int r; assert(rr); + if (rr->to_string) + return rr->to_string; + r = dns_resource_key_to_string(rr->key, &k); if (r < 0) - return r; + return NULL; switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { @@ -787,7 +793,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->srv.port, strna(rr->srv.name)); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_PTR: @@ -796,25 +802,25 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { case DNS_TYPE_DNAME: s = strjoin(k, " ", rr->ptr.name, NULL); if (!s) - return -ENOMEM; + return NULL; break; case DNS_TYPE_HINFO: s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL); if (!s) - return -ENOMEM; + return NULL; break; case DNS_TYPE_SPF: /* exactly the same as TXT */ case DNS_TYPE_TXT: t = format_txt(rr->txt.items); if (!t) - return -ENOMEM; + return NULL; s = strjoin(k, " ", t, NULL); if (!s) - return -ENOMEM; + return NULL; break; case DNS_TYPE_A: { @@ -822,22 +828,22 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x); if (r < 0) - return r; + return NULL; s = strjoin(k, " ", x, NULL); if (!s) - return -ENOMEM; + return NULL; break; } case DNS_TYPE_AAAA: r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &t); if (r < 0) - return r; + return NULL; s = strjoin(k, " ", t, NULL); if (!s) - return -ENOMEM; + return NULL; break; case DNS_TYPE_SOA: @@ -851,7 +857,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->soa.expire, rr->soa.minimum); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_MX: @@ -860,7 +866,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->mx.priority, rr->mx.exchange); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_LOC: @@ -873,17 +879,17 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->loc.horiz_pre, rr->loc.vert_pre); if (!t) - return -ENOMEM; + return NULL; s = strjoin(k, " ", t, NULL); if (!s) - return -ENOMEM; + return NULL; break; case DNS_TYPE_DS: t = hexmem(rr->ds.digest, rr->ds.digest_size); if (!t) - return -ENOMEM; + return NULL; r = asprintf(&s, "%s %u %u %u %s", k, @@ -892,13 +898,13 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->ds.digest_type, t); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_SSHFP: t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); if (!t) - return -ENOMEM; + return NULL; r = asprintf(&s, "%s %u %u %s", k, @@ -906,58 +912,62 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->sshfp.fptype, t); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_DNSKEY: { - const char *alg; + _cleanup_free_ char *alg = NULL; - alg = dnssec_algorithm_to_string(rr->dnskey.algorithm); + r = dnssec_algorithm_to_string_alloc(rr->dnskey.algorithm, &alg); + if (r < 0) + return NULL; t = base64mem(rr->dnskey.key, rr->dnskey.key_size); if (!t) - return -ENOMEM; + return NULL; - r = asprintf(&s, "%s %u %u %.*s%.*u %s", + r = asprintf(&s, "%s %u %u %s %s", k, rr->dnskey.flags, rr->dnskey.protocol, - alg ? -1 : 0, alg, - alg ? 0 : 1, alg ? 0u : (unsigned) rr->dnskey.algorithm, + alg, t); if (r < 0) - return -ENOMEM; + return NULL; break; } case DNS_TYPE_RRSIG: { - const char *type, *alg; + _cleanup_free_ char *alg = NULL; char expiration[strlen("YYYYMMDDHHmmSS") + 1], inception[strlen("YYYYMMDDHHmmSS") + 1]; + const char *type; type = dns_type_to_string(rr->rrsig.type_covered); - alg = dnssec_algorithm_to_string(rr->rrsig.algorithm); + + r = dnssec_algorithm_to_string_alloc(rr->rrsig.algorithm, &alg); + if (r < 0) + return NULL; t = base64mem(rr->rrsig.signature, rr->rrsig.signature_size); if (!t) - return -ENOMEM; + return NULL; r = format_timestamp_dns(expiration, sizeof(expiration), rr->rrsig.expiration); if (r < 0) - return r; + return NULL; r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception); if (r < 0) - return r; + return NULL; /* TYPE?? follows * http://tools.ietf.org/html/rfc3597#section-5 */ - r = asprintf(&s, "%s %s%.*u %.*s%.*u %u %u %s %s %u %s %s", + r = asprintf(&s, "%s %s%.*u %s %u %u %s %s %u %s %s", k, type ?: "TYPE", type ? 0 : 1, type ? 0u : (unsigned) rr->rrsig.type_covered, - alg ? -1 : 0, alg, - alg ? 0 : 1, alg ? 0u : (unsigned) rr->rrsig.algorithm, + alg, rr->rrsig.labels, rr->rrsig.original_ttl, expiration, @@ -966,21 +976,21 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->rrsig.signer, t); if (r < 0) - return -ENOMEM; + return NULL; break; } case DNS_TYPE_NSEC: t = format_types(rr->nsec.types); if (!t) - return -ENOMEM; + return NULL; r = asprintf(&s, "%s %s %s", k, rr->nsec.next_domain_name, t); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_NSEC3: { @@ -989,16 +999,16 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { if (rr->nsec3.salt_size > 0) { salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size); if (!salt) - return -ENOMEM; + return NULL; } hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); if (!hash) - return -ENOMEM; + return NULL; t = format_types(rr->nsec3.types); if (!t) - return -ENOMEM; + return NULL; r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s", k, @@ -1009,7 +1019,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { hash, t); if (r < 0) - return -ENOMEM; + return NULL; break; } @@ -1017,16 +1027,17 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { default: t = hexmem(rr->generic.data, rr->generic.size); if (!t) - return -ENOMEM; + return NULL; + /* Format as documented in RFC 3597, Section 5 */ r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.size, t); if (r < 0) - return -ENOMEM; + return NULL; break; } - *ret = s; - return 0; + rr->to_string = s; + return s; } int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { @@ -1074,34 +1085,6 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { return 0; } -const char *dns_class_to_string(uint16_t class) { - - switch (class) { - - case DNS_CLASS_IN: - return "IN"; - - case DNS_CLASS_ANY: - return "ANY"; - } - - return NULL; -} - -int dns_class_from_string(const char *s, uint16_t *class) { - assert(s); - assert(class); - - if (strcaseeq(s, "IN")) - *class = DNS_CLASS_IN; - else if (strcaseeq(s, "ANY")) - *class = DNS_CLASS_ANY; - else - return -EINVAL; - - return 0; -} - DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) { DnsTxtItem *n; @@ -1135,6 +1118,7 @@ bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { } static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { + /* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", [DNSSEC_ALGORITHM_DH] = "DH", [DNSSEC_ALGORITHM_DSA] = "DSA", @@ -1144,14 +1128,20 @@ static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1", [DNSSEC_ALGORITHM_RSASHA256] = "RSASHA256", [DNSSEC_ALGORITHM_RSASHA512] = "RSASHA512", + [DNSSEC_ALGORITHM_ECC_GOST] = "ECC-GOST", + [DNSSEC_ALGORITHM_ECDSAP256SHA256] = "ECDSAP256SHA256", + [DNSSEC_ALGORITHM_ECDSAP384SHA384] = "ECDSAP384SHA384", [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", }; -DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int); +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_algorithm, int, 255); static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = { - [DNSSEC_DIGEST_SHA1] = "SHA1", - [DNSSEC_DIGEST_SHA256] = "SHA256", + /* Names as listed on https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ + [DNSSEC_DIGEST_SHA1] = "SHA-1", + [DNSSEC_DIGEST_SHA256] = "SHA-256", + [DNSSEC_DIGEST_GOST_R_34_11_94] = "GOST_R_34.11-94", + [DNSSEC_DIGEST_SHA384] = "SHA-384", }; -DEFINE_STRING_TABLE_LOOKUP(dnssec_digest, int); +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_digest, int, 255); diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 632ee59994..26ab36401c 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -33,17 +33,10 @@ typedef struct DnsResourceKey DnsResourceKey; typedef struct DnsResourceRecord DnsResourceRecord; typedef struct DnsTxtItem DnsTxtItem; -/* DNS record classes, see RFC 1035 */ -enum { - DNS_CLASS_IN = 0x01, - DNS_CLASS_ANY = 0xFF, - _DNS_CLASS_MAX, - _DNS_CLASS_INVALID = -1 -}; - /* DNSKEY RR flags */ -#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) #define DNSKEY_FLAG_SEP (UINT16_C(1) << 0) +#define DNSKEY_FLAG_REVOKE (UINT16_C(1) << 7) +#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) /* mDNS RR flags */ #define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15) @@ -59,8 +52,11 @@ enum { DNSSEC_ALGORITHM_RSASHA1, DNSSEC_ALGORITHM_DSA_NSEC3_SHA1, DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */ - DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */ + DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */ + DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */ + DNSSEC_ALGORITHM_ECC_GOST = 12, /* RFC 5933 */ + DNSSEC_ALGORITHM_ECDSAP256SHA256 = 13, /* RFC 6605 */ + DNSSEC_ALGORITHM_ECDSAP384SHA384 = 14, /* RFC 6605 */ DNSSEC_ALGORITHM_INDIRECT = 252, DNSSEC_ALGORITHM_PRIVATEDNS, DNSSEC_ALGORITHM_PRIVATEOID, @@ -71,15 +67,23 @@ enum { * https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ enum { DNSSEC_DIGEST_SHA1 = 1, - DNSSEC_DIGEST_SHA256 = 2, + DNSSEC_DIGEST_SHA256 = 2, /* RFC 4509 */ + DNSSEC_DIGEST_GOST_R_34_11_94 = 3, /* RFC 5933 */ + DNSSEC_DIGEST_SHA384 = 4, /* RFC 6605 */ _DNSSEC_DIGEST_MAX_DEFINED }; +/* DNSSEC NSEC3 hash algorithms, see + * https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */ +enum { + NSEC3_ALGORITHM_SHA1 = 1, + _NSEC3_ALGORITHM_MAX_DEFINED +}; + struct DnsResourceKey { unsigned n_ref; uint16_t class, type; char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */ - bool cache_flush:1; }; /* Creates a temporary resource key. This is only useful to quickly @@ -104,7 +108,9 @@ struct DnsTxtItem { struct DnsResourceRecord { unsigned n_ref; DnsResourceKey *key; + char *to_string; uint32_t ttl; + usec_t expiry; /* RRSIG signature expiry */ bool unparseable:1; bool wire_format_canonical:1; void *wire_format; @@ -159,6 +165,7 @@ struct DnsResourceRecord { char *exchange; } mx; + /* https://tools.ietf.org/html/rfc1876 */ struct { uint8_t version; uint8_t size; @@ -169,14 +176,6 @@ struct DnsResourceRecord { uint32_t altitude; } loc; - struct { - uint16_t key_tag; - uint8_t algorithm; - uint8_t digest_type; - void *digest; - size_t digest_size; - } ds; - /* https://tools.ietf.org/html/rfc4255#section-3.1 */ struct { uint8_t algorithm; @@ -214,6 +213,15 @@ struct DnsResourceRecord { Bitmap *types; } nsec; + /* https://tools.ietf.org/html/rfc4034#section-5.1 */ + struct { + uint16_t key_tag; + uint8_t algorithm; + uint8_t digest_type; + void *digest; + size_t digest_size; + } ds; + struct { uint8_t algorithm; uint8_t flags; @@ -228,7 +236,7 @@ struct DnsResourceRecord { }; static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) { - if (_unlikely_(!key)) + if (!key) return NULL; if (key->_name) @@ -237,6 +245,27 @@ static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) { return (char*) key + sizeof(DnsResourceKey); } +static inline const void* DNS_RESOURCE_RECORD_RDATA(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + if (!rr->wire_format) + return NULL; + + assert(rr->wire_format_rdata_offset <= rr->wire_format_size); + return (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset; +} + +static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) { + if (!rr) + return 0; + if (!rr->wire_format) + return 0; + + assert(rr->wire_format_rdata_offset <= rr->wire_format_size); + return rr->wire_format_size - rr->wire_format_rdata_offset; +} + DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name); DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname); int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name); @@ -245,7 +274,7 @@ DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key); DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key); bool dns_resource_key_is_address(const DnsResourceKey *key); int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); -int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain); +int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain); int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain); int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa); int dns_resource_key_to_string(const DnsResourceKey *key, char **ret); @@ -262,7 +291,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr); int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b); -int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret); +const char* dns_resource_record_to_string(DnsResourceRecord *rr); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref); int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical); @@ -270,13 +299,10 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical); DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i); bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); -const char *dns_class_to_string(uint16_t type); -int dns_class_from_string(const char *name, uint16_t *class); - extern const struct hash_ops dns_resource_key_hash_ops; -const char* dnssec_algorithm_to_string(int i) _const_; +int dnssec_algorithm_to_string_alloc(int i, char **ret); int dnssec_algorithm_from_string(const char *s) _pure_; -const char *dnssec_digest_to_string(int i) _const_; +int dnssec_digest_to_string_alloc(int i, char **ret); int dnssec_digest_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 61bca04b94..c96bed04b0 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -57,6 +57,23 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int s->family = family; s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC; + s->dnssec_mode = _DNSSEC_MODE_INVALID; + + if (protocol == DNS_PROTOCOL_DNS) { + /* Copy DNSSEC mode from the link if it is set there, + * otherwise take the manager's DNSSEC mode. Note that + * we copy this only at scope creation time, and do + * not update it from the on, even if the setting + * changes. */ + + if (l) + s->dnssec_mode = l->dnssec_mode; + if (s->dnssec_mode == _DNSSEC_MODE_INVALID) + s->dnssec_mode = m->dnssec_mode; + if (s->dnssec_mode == _DNSSEC_MODE_INVALID) + s->dnssec_mode = DNSSEC_NO; + } + LIST_PREPEND(scopes, m->dns_scopes, s); dns_scope_llmnr_membership(s, true); @@ -81,7 +98,8 @@ static void dns_scope_abort_transactions(DnsScope *s) { * freed while we still look at it */ t->block_gc++; - dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); + if (DNS_TRANSACTION_IS_LIVE(t->state)) + dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); t->block_gc--; dns_transaction_free(t); @@ -161,17 +179,15 @@ void dns_scope_packet_lost(DnsScope *s, usec_t usec) { s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); } -static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { +static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) { union in_addr_union addr; int ifindex = 0, r; int family; uint32_t mtu; - size_t saved_size = 0; assert(s); assert(p); assert(p->protocol == s->protocol); - assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0)); if (s->link) { mtu = s->link->mtu; @@ -180,30 +196,13 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket mtu = manager_find_mtu(s->manager); switch (s->protocol) { + case DNS_PROTOCOL_DNS: - assert(server); + assert(fd >= 0); if (DNS_PACKET_QDCOUNT(p) > 1) return -EOPNOTSUPP; - if (server->possible_features >= DNS_SERVER_FEATURE_LEVEL_EDNS0) { - bool edns_do; - size_t packet_size; - - edns_do = server->possible_features >= DNS_SERVER_FEATURE_LEVEL_DO; - - if (server->possible_features >= DNS_SERVER_FEATURE_LEVEL_LARGE) - packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX; - else - packet_size = server->received_udp_packet_max; - - r = dns_packet_append_opt_rr(p, packet_size, edns_do, &saved_size); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->arcount = htobe16(be16toh(DNS_PACKET_HEADER(p)->arcount) + 1); - } - if (p->size > DNS_PACKET_UNICAST_SIZE_MAX) return -EMSGSIZE; @@ -214,15 +213,11 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket if (r < 0) return r; - if (saved_size > 0) { - dns_packet_truncate(p, saved_size); - - DNS_PACKET_HEADER(p)->arcount = htobe16(be16toh(DNS_PACKET_HEADER(p)->arcount) - 1); - } - break; case DNS_PROTOCOL_LLMNR: + assert(fd < 0); + if (DNS_PACKET_QDCOUNT(p) > 1) return -EOPNOTSUPP; @@ -249,6 +244,8 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket break; case DNS_PROTOCOL_MDNS: + assert(fd < 0); + if (!ratelimit_test(&s->ratelimit)) return -EBUSY; @@ -278,13 +275,13 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket return 1; } -int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { +int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) { int r; assert(s); assert(p); assert(p->protocol == s->protocol); - assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0)); + assert((s->protocol == DNS_PROTOCOL_DNS) == (fd >= 0)); do { /* If there are multiple linked packets, set the TC bit in all but the last of them */ @@ -293,18 +290,24 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { dns_packet_set_flags(p, true, true); } - r = dns_scope_emit_one(s, fd, server, p); + r = dns_scope_emit_one(s, fd, p); if (r < 0) return r; p = p->more; - } while(p); + } while (p); return 0; } -static int dns_scope_socket(DnsScope *s, int type, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) { - DnsServer *srv = NULL; +static int dns_scope_socket( + DnsScope *s, + int type, + int family, + const union in_addr_union *address, + DnsServer *server, + uint16_t port) { + _cleanup_close_ int fd = -1; union sockaddr_union sa = {}; socklen_t salen; @@ -312,31 +315,27 @@ static int dns_scope_socket(DnsScope *s, int type, int family, const union in_ad int ret, r; assert(s); - assert((family == AF_UNSPEC) == !address); - - if (family == AF_UNSPEC) { - srv = dns_scope_get_dns_server(s); - if (!srv) - return -ESRCH; - - srv->possible_features = dns_server_possible_features(srv); - if (type == SOCK_DGRAM && srv->possible_features < DNS_SERVER_FEATURE_LEVEL_UDP) - return -EAGAIN; + if (server) { + assert(family == AF_UNSPEC); + assert(!address); - sa.sa.sa_family = srv->family; - if (srv->family == AF_INET) { + sa.sa.sa_family = server->family; + if (server->family == AF_INET) { sa.in.sin_port = htobe16(port); - sa.in.sin_addr = srv->address.in; + sa.in.sin_addr = server->address.in; salen = sizeof(sa.in); - } else if (srv->family == AF_INET6) { + } else if (server->family == AF_INET6) { sa.in6.sin6_port = htobe16(port); - sa.in6.sin6_addr = srv->address.in6; + sa.in6.sin6_addr = server->address.in6; sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0; salen = sizeof(sa.in6); } else return -EAFNOSUPPORT; } else { + assert(family != AF_UNSPEC); + assert(address); + sa.sa.sa_family = family; if (family == AF_INET) { @@ -394,21 +393,18 @@ static int dns_scope_socket(DnsScope *s, int type, int family, const union in_ad if (r < 0 && errno != EINPROGRESS) return -errno; - if (server) - *server = srv; - ret = fd; fd = -1; return ret; } -int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server) { - return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, 53, server); +int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port) { + return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port); } -int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) { - return dns_scope_socket(s, SOCK_STREAM, family, address, port, server); +int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port) { + return dns_scope_socket(s, SOCK_STREAM, family, address, server, port); } DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { @@ -506,7 +502,7 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) { if (s->protocol == DNS_PROTOCOL_DNS) { - /* On classic DNS, lookin up non-address RRs is always + /* On classic DNS, looking up non-address RRs is always * fine. (Specifically, we want to permit looking up * DNSKEY and DS records on the root and top-level * domains.) */ @@ -789,7 +785,7 @@ DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, /* Refuse reusing transactions that completed based on cached * data instead of a real packet, if that's requested. */ if (!cache_ok && - IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE) && + IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_RCODE_FAILURE) && t->answer_source != DNS_TRANSACTION_NETWORK) return NULL; @@ -867,7 +863,7 @@ static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata return 0; } - r = dns_scope_emit(scope, -1, NULL, p); + r = dns_scope_emit_udp(scope, -1, p); if (r < 0) log_debug_errno(r, "Failed to send conflict packet: %m"); } diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 2fc2e07deb..a0676bd30e 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -82,9 +82,9 @@ DnsScope* dns_scope_free(DnsScope *s); void dns_scope_packet_received(DnsScope *s, usec_t rtt); void dns_scope_packet_lost(DnsScope *s, usec_t usec); -int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p); -int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server); -int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server); +int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p); +int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port); +int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port); DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain); int dns_scope_good_key(DnsScope *s, DnsResourceKey *key); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index d565f99c09..fbd1c27c14 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -68,8 +68,8 @@ int dns_server_new( s->n_ref = 1; s->manager = m; - s->verified_features = _DNS_SERVER_FEATURE_LEVEL_INVALID; - s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST; + s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC; s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX; s->type = type; @@ -224,23 +224,25 @@ void dns_server_move_back_and_unmark(DnsServer *s) { } } -void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size) { +void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel level, usec_t rtt, size_t size) { assert(s); - if (features == DNS_SERVER_FEATURE_LEVEL_LARGE) { - /* even if we successfully receive a reply to a request announcing - support for large packets, that does not mean we can necessarily - receive large packets. */ - if (s->verified_features < DNS_SERVER_FEATURE_LEVEL_LARGE - 1) { - s->verified_features = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; + if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) { + /* Even if we successfully receive a reply to a + request announcing support for large packets, that + does not mean we can necessarily receive large + packets. */ + + if (s->verified_feature_level < DNS_SERVER_FEATURE_LEVEL_LARGE - 1) { + s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); } - } else if (s->verified_features < features) { - s->verified_features = features; + } else if (s->verified_feature_level < level) { + s->verified_feature_level = level; assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); } - if (s->possible_features == features) + if (s->possible_feature_level == level) s->n_failed_attempts = 0; /* Remember the size of the largest UDP packet we received from a server, @@ -251,15 +253,15 @@ void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, us if (s->max_rtt < rtt) { s->max_rtt = rtt; - s->resend_timeout = MIN(MAX(DNS_TIMEOUT_MIN_USEC, s->max_rtt * 2), DNS_TIMEOUT_MAX_USEC); + s->resend_timeout = CLAMP(s->max_rtt * 2, DNS_TIMEOUT_MIN_USEC, DNS_TIMEOUT_MAX_USEC); } } -void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec) { +void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel level, usec_t usec) { assert(s); assert(s->manager); - if (s->possible_features == features) + if (s->possible_feature_level == level) s->n_failed_attempts ++; if (s->resend_timeout > usec) @@ -268,16 +270,27 @@ void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); } -void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features) { +void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) { assert(s); assert(s->manager); - if (s->possible_features != features) + if (s->possible_feature_level != level) return; s->n_failed_attempts = (unsigned) -1; } +void dns_server_packet_rrsig_missing(DnsServer *s) { + _cleanup_free_ char *ip = NULL; + assert(s); + assert(s->manager); + + in_addr_to_string(s->family, &s->address, &ip); + log_warning("DNS server %s does not augment replies with RRSIG records, DNSSEC not available.", strna(ip)); + + s->rrsig_missing = true; +} + static bool dns_server_grace_period_expired(DnsServer *s) { usec_t ts; @@ -297,35 +310,64 @@ static bool dns_server_grace_period_expired(DnsServer *s) { return true; } -DnsServerFeatureLevel dns_server_possible_features(DnsServer *s) { +DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { assert(s); - if (s->possible_features != DNS_SERVER_FEATURE_LEVEL_BEST && + if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && dns_server_grace_period_expired(s)) { _cleanup_free_ char *ip = NULL; - s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST; + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; s->n_failed_attempts = 0; s->verified_usec = 0; + s->rrsig_missing = false; in_addr_to_string(s->family, &s->address, &ip); log_info("Grace period over, resuming full feature set for DNS server %s", strna(ip)); - } else if (s->possible_features <= s->verified_features) - s->possible_features = s->verified_features; + } else if (s->possible_feature_level <= s->verified_feature_level) + s->possible_feature_level = s->verified_feature_level; else if (s->n_failed_attempts >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_features > DNS_SERVER_FEATURE_LEVEL_WORST) { + s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_WORST) { _cleanup_free_ char *ip = NULL; - s->possible_features --; + s->possible_feature_level --; s->n_failed_attempts = 0; s->verified_usec = 0; in_addr_to_string(s->family, &s->address, &ip); log_warning("Using degraded feature set (%s) for DNS server %s", - dns_server_feature_level_to_string(s->possible_features), strna(ip)); + dns_server_feature_level_to_string(s->possible_feature_level), strna(ip)); } - return s->possible_features; + return s->possible_feature_level; +} + +int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level) { + size_t packet_size; + bool edns_do; + int r; + + assert(server); + assert(packet); + assert(packet->protocol == DNS_PROTOCOL_DNS); + + /* Fix the OPT field in the packet to match our current feature level. */ + + r = dns_packet_truncate_opt(packet); + if (r < 0) + return r; + + if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) + return 0; + + edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO; + + if (level >= DNS_SERVER_FEATURE_LEVEL_LARGE) + packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX; + else + packet_size = server->received_udp_packet_max; + + return dns_packet_append_opt(packet, packet_size, edns_do, NULL); } static void dns_server_hash_func(const void *p, struct siphash *state) { diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index b07fc3af3d..da21e6571a 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -61,18 +61,25 @@ struct DnsServer { int family; union in_addr_union address; - bool marked:1; - usec_t resend_timeout; usec_t max_rtt; - DnsServerFeatureLevel verified_features; - DnsServerFeatureLevel possible_features; + DnsServerFeatureLevel verified_feature_level; + DnsServerFeatureLevel possible_feature_level; size_t received_udp_packet_max; unsigned n_failed_attempts; usec_t verified_usec; usec_t features_grace_period_usec; + /* Indicates whether responses are augmented with RRSIG by + * server or not. Note that this is orthogonal to the feature + * level stuff, as it's only information describing responses, + * and has no effect on how the questions are asked. */ + bool rrsig_missing:1; + + /* Used when GC'ing old DNS servers when configuration changes. */ + bool marked:1; + /* If linked is set, then this server appears in the servers linked list */ bool linked:1; LIST_FIELDS(DnsServer, servers); @@ -92,9 +99,14 @@ DnsServer* dns_server_unref(DnsServer *s); void dns_server_unlink(DnsServer *s); void dns_server_move_back_and_unmark(DnsServer *s); -void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size); -void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec); -void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features); +void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel level, usec_t rtt, size_t size); +void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel level, usec_t usec); +void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level); +void dns_server_packet_rrsig_missing(DnsServer *s); + +DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s); + +int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level); DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr); @@ -110,6 +122,4 @@ void manager_next_dns_server(Manager *m); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); -DnsServerFeatureLevel dns_server_possible_features(DnsServer *s); - extern const struct hash_ops dns_server_hash_ops; diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index 1c501182fb..180f8e0877 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -347,7 +347,6 @@ DnsStream *dns_stream_free(DnsStream *s) { DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { - static const int one = 1; _cleanup_(dns_stream_freep) DnsStream *s = NULL; int r; @@ -364,10 +363,6 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { s->fd = -1; s->protocol = protocol; - r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); - if (r < 0) - return -errno; - r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s); if (r < 0) return r; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index bcf6d5c810..f5171a940f 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -19,6 +19,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sd-messages.h> + #include "af-list.h" #include "alloc-util.h" #include "dns-domain.h" @@ -29,6 +31,32 @@ #include "resolved-llmnr.h" #include "string-table.h" +static void dns_transaction_reset_answer(DnsTransaction *t) { + assert(t); + + t->received = dns_packet_unref(t->received); + t->answer = dns_answer_unref(t->answer); + t->answer_rcode = 0; + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; + t->answer_authenticated = false; + t->answer_nsec_ttl = (uint32_t) -1; +} + +static void dns_transaction_close_connection(DnsTransaction *t) { + assert(t); + + t->stream = dns_stream_free(t->stream); + t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source); + t->dns_udp_fd = safe_close(t->dns_udp_fd); +} + +static void dns_transaction_stop_timeout(DnsTransaction *t) { + assert(t); + + t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); +} + DnsTransaction* dns_transaction_free(DnsTransaction *t) { DnsQueryCandidate *c; DnsZoneItem *i; @@ -37,18 +65,15 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { if (!t) return NULL; - sd_event_source_unref(t->timeout_event_source); + log_debug("Freeing transaction %" PRIu16 ".", t->id); - dns_packet_unref(t->sent); - dns_packet_unref(t->received); - - dns_answer_unref(t->answer); + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); - sd_event_source_unref(t->dns_udp_event_source); - safe_close(t->dns_udp_fd); + dns_packet_unref(t->sent); + dns_transaction_reset_answer(t); dns_server_unref(t->server); - dns_stream_free(t->stream); if (t->scope) { hashmap_remove_value(t->scope->transactions_by_key, t->key, t); @@ -58,8 +83,6 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); } - dns_resource_key_unref(t->key); - while ((c = set_steal_first(t->notify_query_candidates))) set_remove(c->transactions, t); set_free(t->notify_query_candidates); @@ -79,6 +102,8 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { set_free(t->dnssec_transactions); dns_answer_unref(t->validated_keys); + dns_resource_key_unref(t->key); + free(t->key_string); free(t); return NULL; @@ -86,16 +111,20 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); -void dns_transaction_gc(DnsTransaction *t) { +bool dns_transaction_gc(DnsTransaction *t) { assert(t); if (t->block_gc > 0) - return; + return true; if (set_isempty(t->notify_query_candidates) && set_isempty(t->notify_zone_items) && - set_isempty(t->notify_transactions)) + set_isempty(t->notify_transactions)) { dns_transaction_free(t); + return false; + } + + return true; } int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) { @@ -107,11 +136,11 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) assert(key); /* Don't allow looking up invalid or pseudo RRs */ - if (IN_SET(key->type, DNS_TYPE_OPT, 0, DNS_TYPE_TSIG, DNS_TYPE_TKEY)) + if (!dns_type_is_valid_query(key->type)) return -EINVAL; /* We only support the IN class */ - if (key->class != DNS_CLASS_IN) + if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY) return -EOPNOTSUPP; r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); @@ -128,7 +157,8 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) t->dns_udp_fd = -1; t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; - t->dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_nsec_ttl = (uint32_t) -1; t->key = dns_resource_key_ref(key); /* Find a fresh, unused transaction id */ @@ -152,6 +182,8 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) LIST_PREPEND(transactions_by_scope, s->transactions, t); t->scope = s; + s->manager->n_transactions_total ++; + if (ret) *ret = t; @@ -160,16 +192,6 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) return 0; } -static void dns_transaction_stop(DnsTransaction *t) { - assert(t); - - t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); - t->stream = dns_stream_free(t->stream); - - /* Note that we do not drop the UDP socket here, as we want to - * reuse it to repeat the interaction. */ -} - static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { _cleanup_free_ char *pretty = NULL; DnsZoneItem *z; @@ -182,7 +204,9 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { in_addr_to_string(p->family, &p->sender, &pretty); - log_debug("Transaction on scope %s on %s/%s got tentative packet from %s", + log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s got tentative packet from %s.", + t->id, + dns_transaction_key_string(t), dns_protocol_to_string(t->scope->protocol), t->scope->link ? t->scope->link->name : "*", t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), @@ -221,35 +245,97 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { assert(t); assert(!DNS_TRANSACTION_IS_LIVE(state)); + if (state == DNS_TRANSACTION_DNSSEC_FAILED) + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE), + LOG_MESSAGE("DNSSEC validation failed for question %s: %s", dns_transaction_key_string(t), dnssec_result_to_string(t->answer_dnssec_result)), + "DNS_TRANSACTION=%" PRIu16, t->id, + "DNS_QUESTION=%s", dns_transaction_key_string(t), + "DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result), + NULL); + /* Note that this call might invalidate the query. Callers * should hence not attempt to access the query or transaction * after calling this function. */ - log_debug("Transaction on scope %s on %s/%s now complete with <%s> from %s", + log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).", + t->id, + dns_transaction_key_string(t), dns_protocol_to_string(t->scope->protocol), t->scope->link ? t->scope->link->name : "*", t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), dns_transaction_state_to_string(state), - t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source)); + t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), + t->answer_authenticated ? "authenticated" : "unsigned"); t->state = state; - dns_transaction_stop(t); + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); /* Notify all queries that are interested, but make sure the * transaction isn't freed while we are still looking at it */ t->block_gc++; + SET_FOREACH(c, t->notify_query_candidates, i) dns_query_candidate_notify(c); SET_FOREACH(z, t->notify_zone_items, i) dns_zone_item_notify(z); - SET_FOREACH(d, t->notify_transactions, i) - dns_transaction_notify(d, t); - t->block_gc--; + if (!set_isempty(t->notify_transactions)) { + DnsTransaction **nt; + unsigned j, n = 0; + + /* We need to be careful when notifying other + * transactions, as that might destroy other + * transactions in our list. Hence, in order to be + * able to safely iterate through the list of + * transactions, take a GC lock on all of them + * first. Then, in a second loop, notify them, but + * first unlock that specific transaction. */ + + nt = newa(DnsTransaction*, set_size(t->notify_transactions)); + SET_FOREACH(d, t->notify_transactions, i) { + nt[n++] = d; + d->block_gc++; + } + + assert(n == set_size(t->notify_transactions)); + + for (j = 0; j < n; j++) { + if (set_contains(t->notify_transactions, nt[j])) + dns_transaction_notify(nt[j], t); + + nt[j]->block_gc--; + dns_transaction_gc(nt[j]); + } + } + + t->block_gc--; dns_transaction_gc(t); } +static int dns_transaction_pick_server(DnsTransaction *t) { + DnsServer *server; + + assert(t); + assert(t->scope->protocol == DNS_PROTOCOL_DNS); + + server = dns_scope_get_dns_server(t->scope); + if (!server) + return -ESRCH; + + t->current_features = dns_server_possible_feature_level(server); + + if (server == t->server) + return 0; + + dns_server_unref(t->server); + t->server = dns_server_ref(server); + + return 1; +} + static int on_stream_complete(DnsStream *s, int error) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; DnsTransaction *t; @@ -264,6 +350,11 @@ static int on_stream_complete(DnsStream *s, int error) { t->stream = dns_stream_free(t->stream); + if (IN_SET(error, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE)) { + dns_transaction_complete(t, DNS_TRANSACTION_CONNECTION_FAILURE); + return 0; + } + if (error != 0) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); return 0; @@ -281,32 +372,43 @@ static int on_stream_complete(DnsStream *s, int error) { dns_transaction_process_reply(t, p); t->block_gc--; - /* If the response wasn't useful, then complete the transition now */ + /* If the response wasn't useful, then complete the transition + * now. After all, we are the worst feature set now with TCP + * sockets, and there's really no point in retrying. */ if (t->state == DNS_TRANSACTION_PENDING) dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + else + dns_transaction_gc(t); return 0; } static int dns_transaction_open_tcp(DnsTransaction *t) { - DnsServer *server = NULL; _cleanup_close_ int fd = -1; int r; assert(t); - if (t->stream) - return 0; + dns_transaction_close_connection(t); switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: - fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53, &server); + r = dns_transaction_pick_server(t); + if (r < 0) + return r; + + r = dns_server_adjust_opt(t->server, t->sent, t->current_features); + if (r < 0) + return r; + + fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53); break; case DNS_PROTOCOL_LLMNR: /* When we already received a reply to this (but it was truncated), send to its sender address */ if (t->received) - fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port, NULL); + fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port); else { union in_addr_union address; int family = AF_UNSPEC; @@ -323,7 +425,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { if (family != t->scope->family) return -ESRCH; - fd = dns_scope_tcp_socket(t->scope, family, &address, LLMNR_PORT, NULL); + fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT); } break; @@ -338,7 +440,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd); if (r < 0) return r; - fd = -1; r = dns_stream_write_packet(t->stream, t->sent); @@ -347,11 +448,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { return r; } - dns_server_unref(t->server); - t->server = dns_server_ref(server); - t->received = dns_packet_unref(t->received); - t->answer = dns_answer_unref(t->answer); - t->answer_rcode = 0; t->stream->complete = on_stream_complete; t->stream->transaction = t; @@ -361,22 +457,14 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { if (t->scope->link) t->stream->ifindex = t->scope->link->ifindex; - return 0; -} - -static void dns_transaction_next_dns_server(DnsTransaction *t) { - assert(t); + dns_transaction_reset_answer(t); - t->server = dns_server_unref(t->server); - t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source); - t->dns_udp_fd = safe_close(t->dns_udp_fd); + t->tried_stream = true; - dns_scope_next_dns_server(t->scope); + return 0; } static void dns_transaction_cache_answer(DnsTransaction *t) { - unsigned n_cache; - assert(t); /* For mDNS we cache whenever we get the packet, rather than @@ -391,33 +479,37 @@ static void dns_transaction_cache_answer(DnsTransaction *t) { if (!DNS_PACKET_SHALL_CACHE(t->received)) return; - /* According to RFC 4795, section 2.9. only the RRs from the - * answer section shall be cached. However, if we know the - * message is authenticated, we might as well cache - * everything. */ - if (t->answer_authenticated) - n_cache = dns_answer_size(t->answer); - else - n_cache = DNS_PACKET_ANCOUNT(t->received); - dns_cache_put(&t->scope->cache, t->key, t->answer_rcode, t->answer, - n_cache, t->answer_authenticated, + t->answer_nsec_ttl, 0, t->received->family, &t->received->sender); } +static bool dns_transaction_dnssec_is_live(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + + assert(t); + + SET_FOREACH(dt, t->dnssec_transactions, i) + if (DNS_TRANSACTION_IS_LIVE(dt->state)) + return true; + + return false; +} + static void dns_transaction_process_dnssec(DnsTransaction *t) { int r; assert(t); /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */ - if (!set_isempty(t->dnssec_transactions)) + if (dns_transaction_dnssec_is_live(t)) return; /* All our auxiliary DNSSEC transactions are complete now. Try @@ -428,7 +520,19 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) { return; } - if (!IN_SET(t->dnssec_result, _DNSSEC_RESULT_INVALID, DNSSEC_VALIDATED, DNSSEC_NO_SIGNATURE /* FOR NOW! */)) { + if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER && + t->scope->dnssec_mode == DNSSEC_YES) { + /* We are not in automatic downgrade mode, and the + * server is bad, refuse operation. */ + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return; + } + + if (!IN_SET(t->answer_dnssec_result, + _DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */ + DNSSEC_VALIDATED, /* Answer is signed and validated successfully */ + DNSSEC_UNSIGNED, /* Answer is right-fully unsigned */ + DNSSEC_INCOMPATIBLE_SERVER)) { /* Server does not do DNSSEC (Yay, we are downgrade attack vulnerable!) */ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); return; } @@ -438,7 +542,7 @@ static void dns_transaction_process_dnssec(DnsTransaction *t) { if (t->answer_rcode == DNS_RCODE_SUCCESS) dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); else - dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); } void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { @@ -447,10 +551,12 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { assert(t); assert(p); - assert(t->state == DNS_TRANSACTION_PENDING); assert(t->scope); assert(t->scope->manager); + if (t->state != DNS_TRANSACTION_PENDING) + return; + /* Note that this call might invalidate the query. Callers * should hence not attempt to access the query or transaction * after calling this function. */ @@ -494,6 +600,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { break; case DNS_PROTOCOL_DNS: + /* Note that we do not need to verify the + * addresses/port numbers of incoming traffic, as we + * invoked connect() on our UDP socket in which case + * the kernel already does the needed verification for + * us. */ break; default: @@ -580,16 +691,16 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { } /* On DNS, couldn't send? Try immediately again, with a new server */ - dns_transaction_next_dns_server(t); + dns_scope_next_dns_server(t->scope); r = dns_transaction_go(t); if (r < 0) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); return; } - - return; } + + return; } /* Parse message, if it isn't parsed yet. */ @@ -616,9 +727,25 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { dns_answer_unref(t->answer); t->answer = dns_answer_ref(p->answer); t->answer_rcode = DNS_PACKET_RCODE(p); - t->answer_authenticated = t->scope->dnssec_mode == DNSSEC_TRUST && DNS_PACKET_AD(p); + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_authenticated = false; + /* Block GC while starting requests for additional DNSSEC RRs */ + t->block_gc++; r = dns_transaction_request_dnssec_keys(t); + t->block_gc--; + + /* Maybe the transaction is ready for GC'ing now? If so, free it and return. */ + if (!dns_transaction_gc(t)) + return; + + /* Requesting additional keys might have resulted in + * this transaction to fail, since the auxiliary + * request failed for some reason. If so, we are not + * in pending state anymore, and we should exit + * quickly. */ + if (t->state != DNS_TRANSACTION_PENDING) + return; if (r < 0) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); return; @@ -626,6 +753,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { if (r > 0) { /* There are DNSSEC transactions pending now. Update the state accordingly. */ t->state = DNS_TRANSACTION_VALIDATING; + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); return; } } @@ -649,39 +778,54 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use DNS_PACKET_ID(p) == t->id) dns_transaction_process_reply(t, p); else - log_debug("Invalid DNS packet, ignoring."); + log_debug("Invalid DNS UDP packet, ignoring."); return 0; } -static int dns_transaction_emit(DnsTransaction *t) { +static int dns_transaction_emit_udp(DnsTransaction *t) { int r; assert(t); - if (t->scope->protocol == DNS_PROTOCOL_DNS && !t->server) { - DnsServer *server = NULL; - _cleanup_close_ int fd = -1; - - fd = dns_scope_udp_dns_socket(t->scope, &server); - if (fd < 0) - return fd; + if (t->scope->protocol == DNS_PROTOCOL_DNS) { - r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t); + r = dns_transaction_pick_server(t); if (r < 0) return r; - t->dns_udp_fd = fd; - fd = -1; - t->server = dns_server_ref(server); - } + if (t->current_features < DNS_SERVER_FEATURE_LEVEL_UDP) + return -EAGAIN; + + if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */ + int fd; + + dns_transaction_close_connection(t); + + fd = dns_scope_socket_udp(t->scope, t->server, 53); + if (fd < 0) + return fd; + + r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t); + if (r < 0) { + safe_close(fd); + return r; + } + + t->dns_udp_fd = fd; + } + + r = dns_server_adjust_opt(t->server, t->sent, t->current_features); + if (r < 0) + return r; + } else + dns_transaction_close_connection(t); - r = dns_scope_emit(t->scope, t->dns_udp_fd, t->server, t->sent); + r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->sent); if (r < 0) return r; - if (t->server) - t->current_features = t->server->possible_features; + dns_transaction_reset_answer(t); return 0; } @@ -696,17 +840,17 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) { /* Timeout reached? Increase the timeout for the server used */ switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: assert(t->server); - dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec); - break; + case DNS_PROTOCOL_LLMNR: case DNS_PROTOCOL_MDNS: dns_scope_packet_lost(t->scope, usec - t->start_usec); - break; + default: assert_not_reached("Invalid DNS protocol."); } @@ -715,8 +859,10 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat t->initial_jitter_elapsed = true; } + log_debug("Timeout reached on transaction %" PRIu16 ".", t->id); + /* ...and try again with a new server */ - dns_transaction_next_dns_server(t); + dns_scope_next_dns_server(t->scope); r = dns_transaction_go(t); if (r < 0) @@ -730,36 +876,36 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) { assert(t->scope); switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: assert(t->server); - return t->server->resend_timeout; + case DNS_PROTOCOL_MDNS: assert(t->n_attempts > 0); return (1 << (t->n_attempts - 1)) * USEC_PER_SEC; + case DNS_PROTOCOL_LLMNR: return t->scope->resend_timeout; + default: assert_not_reached("Invalid DNS protocol."); } } static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { - bool had_stream; int r; assert(t); - had_stream = !!t->stream; - - dns_transaction_stop(t); + dns_transaction_stop_timeout(t); if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); return 0; } - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && had_stream) { + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && t->tried_stream) { /* If we already tried via a stream, then we don't * retry on LLMNR. See RFC 4795, Section 2.7. */ dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); @@ -768,14 +914,12 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { t->n_attempts++; t->start_usec = ts; - t->received = dns_packet_unref(t->received); - t->answer = dns_answer_unref(t->answer); - t->answer_rcode = 0; - t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; + + dns_transaction_reset_answer(t); /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */ if (t->scope->protocol == DNS_PROTOCOL_DNS) { - r = dns_trust_anchor_lookup(&t->scope->manager->trust_anchor, t->key, &t->answer); + r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, t->key, &t->answer); if (r < 0) return r; if (r > 0) { @@ -785,6 +929,41 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); return 0; } + + if (dns_name_is_root(DNS_RESOURCE_KEY_NAME(t->key)) && + t->key->type == DNS_TYPE_DS) { + + /* Hmm, this is a request for the root DS? A + * DS RR doesn't exist in the root zone, and + * if our trust anchor didn't know it either, + * this means we cannot do any DNSSEC logic + * anymore. */ + + if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { + /* We are in downgrade mode. In this + * case, synthesize an unsigned empty + * response, so that the any lookup + * depending on this one can continue + * assuming there was no DS, and hence + * the root zone was unsigned. */ + + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; + t->answer_authenticated = false; + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + } else + /* If we are not in downgrade mode, + * then fail the lookup, because we + * cannot reasonably answer it. There + * might be DS RRs, but we don't know + * them, and the DNS server won't tell + * them to us (and even if it would, + * we couldn't validate it and trust + * it). */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR); + + return 0; + } } /* Check the zone, but only if this transaction is not used @@ -823,7 +1002,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { if (t->answer_rcode == DNS_RCODE_SUCCESS) dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); else - dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); return 0; } } @@ -843,7 +1022,7 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) { assert(t); assert(t->scope->protocol == DNS_PROTOCOL_MDNS); - /* Discard any previously prepared packet, so we can start over and coaleasce again */ + /* Discard any previously prepared packet, so we can start over and coalesce again */ t->sent = dns_packet_unref(t->sent); r = dns_packet_new_query(&p, t->scope->protocol, 0, false); @@ -945,7 +1124,7 @@ static int dns_transaction_make_packet(DnsTransaction *t) { if (t->sent) return 0; - r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES); + r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO); if (r < 0) return r; @@ -980,17 +1159,12 @@ int dns_transaction_go(DnsTransaction *t) { if (r <= 0) return r; - if (log_get_max_level() >= LOG_DEBUG) { - _cleanup_free_ char *ks = NULL; - - (void) dns_resource_key_to_string(t->key, &ks); - - log_debug("Excercising transaction for <%s> on scope %s on %s/%s", - ks ? strstrip(ks) : "???", - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); - } + log_debug("Excercising transaction %" PRIu16 " for <%s> on scope %s on %s/%s.", + t->id, + dns_transaction_key_string(t), + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); if (!t->initial_jitter_scheduled && (t->scope->protocol == DNS_PROTOCOL_LLMNR || @@ -1005,10 +1179,12 @@ int dns_transaction_go(DnsTransaction *t) { random_bytes(&jitter, sizeof(jitter)); switch (t->scope->protocol) { + case DNS_PROTOCOL_LLMNR: jitter %= LLMNR_JITTER_INTERVAL_USEC; accuracy = LLMNR_JITTER_INTERVAL_USEC; break; + case DNS_PROTOCOL_MDNS: jitter %= MDNS_JITTER_RANGE_USEC; jitter += MDNS_JITTER_MIN_USEC; @@ -1057,7 +1233,7 @@ int dns_transaction_go(DnsTransaction *t) { } else { /* Try via UDP, and if that fails due to large size or lack of * support try via TCP */ - r = dns_transaction_emit(t); + r = dns_transaction_emit_udp(t); if (r == -EMSGSIZE || r == -EAGAIN) r = dns_transaction_open_tcp(t); } @@ -1073,7 +1249,7 @@ int dns_transaction_go(DnsTransaction *t) { } /* Couldn't send? Try immediately again, with a new server */ - dns_transaction_next_dns_server(t); + dns_scope_next_dns_server(t->scope); return dns_transaction_go(t); } @@ -1095,6 +1271,28 @@ int dns_transaction_go(DnsTransaction *t) { return 1; } +static int dns_transaction_find_cyclic(DnsTransaction *t, DnsTransaction *aux) { + DnsTransaction *n; + Iterator i; + int r; + + assert(t); + assert(aux); + + /* Try to find cyclic dependencies between transaction objects */ + + if (t == aux) + return 1; + + SET_FOREACH(n, aux->dnssec_transactions, i) { + r = dns_transaction_find_cyclic(t, n); + if (r != 0) + return r; + } + + return 0; +} + static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) { DnsTransaction *aux; int r; @@ -1113,6 +1311,18 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource *ret = aux; return 0; } + + r = dns_transaction_find_cyclic(t, aux); + if (r < 0) + return r; + if (r > 0) { + log_debug("Detected potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).", + aux->id, + strna(dns_transaction_key_string(aux)), + t->id, + strna(dns_transaction_key_string(t))); + return -ELOOP; + } } r = set_ensure_allocated(&t->dnssec_transactions, NULL); @@ -1150,7 +1360,7 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * assert(key); /* Try to get the data from the trust anchor */ - r = dns_trust_anchor_lookup(&t->scope->manager->trust_anchor, key, &a); + r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a); if (r < 0) return r; if (r > 0) { @@ -1163,6 +1373,8 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * /* This didn't work, ask for it via the network/cache then. */ r = dns_transaction_add_dnssec_transaction(t, key, &aux); + if (r == -ELOOP) /* This would result in a cyclic dependency */ + return 0; if (r < 0) return r; @@ -1172,20 +1384,160 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * return r; } - return 0; + return 1; +} + +static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags *flags) { + int r; + + assert(t); + + /* Checks whether the answer is positive, i.e. either a direct + * answer to the question, or a CNAME/DNAME for it */ + + r = dns_answer_match_key(t->answer, t->key, flags); + if (r != 0) + return r; + + r = dns_answer_find_cname_or_dname(t->answer, t->key, NULL, flags); + if (r != 0) + return r; + + return false; +} + +static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const char *name) { + int r; + + assert(t); + + /* Check whether the specified name is in the the NTA + * database, either in the global one, or the link-local + * one. */ + + r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, name); + if (r != 0) + return r; + + if (!t->scope->link) + return 0; + + return set_contains(t->scope->link->dnssec_negative_trust_anchors, name); +} + +static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) { + int r; + + assert(t); + + /* Checks whether the answer is negative, and lacks NSEC/NSEC3 + * RRs to prove it */ + + r = dns_transaction_has_positive_answer(t, NULL); + if (r < 0) + return r; + if (r > 0) + return false; + + /* Is this key explicitly listed as a negative trust anchor? + * If so, it's nothing we need to care about */ + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + /* The answer does not contain any RRs that match to the + * question. If so, let's see if there are any NSEC/NSEC3 RRs + * included. If not, the answer is unsigned. */ + + r = dns_answer_contains_nsec_or_nsec3(t->answer); + if (r < 0) + return r; + if (r > 0) + return false; + + return true; +} + +static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) { + int r; + + assert(t); + assert(rr); + + /* Check if the specified RR is the "primary" response, + * i.e. either matches the question precisely or is a + * CNAME/DNAME for it, or is any kind of NSEC/NSEC3 RR */ + + r = dns_resource_key_match_rr(t->key, rr, NULL); + if (r != 0) + return r; + + r = dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); + if (r != 0) + return r; + + if (rr->key->type == DNS_TYPE_NSEC3) { + const char *p; + + p = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r > 0) { + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), p); + if (r < 0) + return r; + if (r > 0) + return true; + } + } + + return rr->key->type == DNS_TYPE_NSEC; } int dns_transaction_request_dnssec_keys(DnsTransaction *t) { DnsResourceRecord *rr; + int r; assert(t); - if (t->scope->dnssec_mode != DNSSEC_YES) + /* + * Retrieve all auxiliary RRs for the answer we got, so that + * we can verify signatures or prove that RRs are rightfully + * unsigned. Specifically: + * + * - For RRSIG we get the matching DNSKEY + * - For DNSKEY we get the matching DS + * - For unsigned SOA/NS we get the matching DS + * - For unsigned CNAME/DNAME/DS we get the parent SOA RR + * - For other unsigned RRs we get the matching SOA RR + * - For SOA/NS/DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR + * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR + */ + + if (t->scope->dnssec_mode == DNSSEC_NO) return 0; + if (t->current_features < DNS_SERVER_FEATURE_LEVEL_DO) + return 0; /* Server doesn't do DNSSEC, there's no point in requesting any RRs then. */ + if (t->server && t->server->rrsig_missing) + return 0; /* Server handles DNSSEC requests, but isn't augmenting responses with RRSIGs. No point in trying DNSSEC then. */ + DNS_ANSWER_FOREACH(rr, t->answer) { + if (dns_type_is_pseudo(rr->key->type)) + continue; + + /* If this RR is in the negative trust anchor, we don't need to validate it. */ + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + continue; + switch (rr->key->type) { case DNS_TYPE_RRSIG: { @@ -1204,10 +1556,18 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { continue; } - /* If the signer is not a parent of the owner, - * then the signature is bogus, let's ignore - * it. */ - r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), rr->rrsig.signer); + /* If the signer is not a parent of our + * original query, then this is about an + * auxiliary RRset, but not anything we asked + * for. In this case we aren't interested, + * because we don't want to request additional + * RRs for stuff we didn't really ask for, and + * also to avoid request loops, where + * additional RRs from one transaction result + * in another transaction whose additonal RRs + * point back to the original transaction, and + * we deadlock. */ + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), rr->rrsig.signer); if (r < 0) return r; if (r == 0) @@ -1217,8 +1577,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (!dnskey) return -ENOMEM; - log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (key tag: %" PRIu16 ").", t->id, rr->rrsig.key_tag); - + log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), rr->rrsig.key_tag); r = dns_transaction_request_dnssec_rr(t, dnskey); if (r < 0) return r; @@ -1229,61 +1588,651 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { /* For each DNSKEY we request the matching DS */ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; + /* If the DNSKEY we are looking at is not for + * zone we are interested in, nor any of its + * parents, we aren't interested, and don't + * request it. After all, we don't want to end + * up in request loops, and want to keep + * additional traffic down. */ + + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key)); if (!ds) return -ENOMEM; - log_debug("Requesting DS to validate transaction %" PRIu16" (key tag: %" PRIu16 ").", t->id, dnssec_keytag(rr)); + log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dnssec_keytag(rr, false)); + r = dns_transaction_request_dnssec_rr(t, ds); + if (r < 0) + return r; + + break; + } + + case DNS_TYPE_SOA: + case DNS_TYPE_NS: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; + + /* For an unsigned SOA or NS, try to acquire + * the matching DS RR, as we are at a zone cut + * then, and whether a DS exists tells us + * whether the zone is signed. Do so only if + * this RR matches our original question, + * however. */ + + r = dns_resource_key_match_rr(t->key, rr, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + if (r > 0) + continue; + + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key)); + if (!ds) + return -ENOMEM; + log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned SOA/NS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key)); r = dns_transaction_request_dnssec_rr(t, ds); if (r < 0) return r; break; + } + + case DNS_TYPE_DS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + const char *name; + + /* CNAMEs and DNAMEs cannot be located at a + * zone apex, hence ask for the parent SOA for + * unsigned CNAME/DNAME RRs, maybe that's the + * apex. But do all that only if this is + * actually a response to our original + * question. + * + * Similar for DS RRs, which are signed when + * the parent SOA is signed. */ + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + if (r > 0) + continue; + + name = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + continue; + + soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key)); + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + + break; + } + + default: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + + /* For other unsigned RRsets (including + * NSEC/NSEC3!), look for proof the zone is + * unsigned, by requesting the SOA RR of the + * zone. However, do so only if they are + * directly relevant to our original + * question. */ + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + if (r > 0) + continue; + + soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, DNS_RESOURCE_KEY_NAME(rr->key)); + if (!soa) + return -ENOMEM; + + log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dns_resource_record_to_string(rr)); + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + break; }} } - return !set_isempty(t->dnssec_transactions); + /* Above, we requested everything necessary to validate what + * we got. Now, let's request what we need to validate what we + * didn't get... */ + + r = dns_transaction_has_unsigned_negative_answer(t); + if (r < 0) + return r; + if (r > 0) { + const char *name; + + name = DNS_RESOURCE_KEY_NAME(t->key); + + /* If this was a SOA or NS request, then this + * indicates that we are not at a zone apex, hence ask + * the parent name instead. If this was a DS request, + * then it's signed when the parent zone is signed, + * hence ask the parent in that case, too. */ + + if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) { + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r > 0) + log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned empty SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key)); + else + name = NULL; + } else + log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned empty non-SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key)); + + if (name) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + + soa = dns_resource_key_new(t->key->class, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + } + } + + return dns_transaction_dnssec_is_live(t); } void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) { int r; assert(t); - assert(IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)); assert(source); + if (!IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) + return; + /* Invoked whenever any of our auxiliary DNSSEC transactions - completed its work. We simply copy the answer from that - transaction over. */ + completed its work. We copy any RRs from that transaction + over into our list of validated keys -- but only if the + answer is authenticated. - if (source->state != DNS_TRANSACTION_SUCCESS) { - log_debug("Auxiliary DNSSEC RR query failed."); - t->dnssec_result = DNSSEC_FAILED_AUXILIARY; - } else { - r = dns_answer_extend(&t->validated_keys, source->answer); - if (r < 0) { - log_error_errno(r, "Failed to merge validated DNSSEC key data: %m"); - t->dnssec_result = DNSSEC_FAILED_AUXILIARY; + Note that we fail our transaction if the auxiliary + transaction failed, except on NXDOMAIN. This is because + some broken DNS servers (Akamai...) will return NXDOMAIN + for empty non-terminals. */ + + switch (source->state) { + + case DNS_TRANSACTION_DNSSEC_FAILED: + + log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(source->answer_dnssec_result)); + t->answer_dnssec_result = source->answer_dnssec_result; /* Copy error code over */ + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + break; + + case DNS_TRANSACTION_RCODE_FAILURE: + + if (source->answer_rcode != DNS_RCODE_NXDOMAIN) { + log_debug("Auxiliary DNSSEC RR query failed with rcode=%i.", source->answer_rcode); + goto fail; + } + + /* fall-through: NXDOMAIN is good enough for us */ + + case DNS_TRANSACTION_SUCCESS: + if (source->answer_authenticated) { + r = dns_answer_extend(&t->validated_keys, source->answer); + if (r < 0) { + log_error_errno(r, "Failed to merge validated DNSSEC key data: %m"); + goto fail; + } + } + + /* If the state is still PENDING, we are still in the loop + * that adds further DNSSEC transactions, hence don't check if + * we are ready yet. If the state is VALIDATING however, we + * should check if we are complete now. */ + if (t->state == DNS_TRANSACTION_VALIDATING) + dns_transaction_process_dnssec(t); + break; + + default: + log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(source->state)); + goto fail; + } + + return; + +fail: + t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY; + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); +} + +static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { + DnsResourceRecord *rr; + int ifindex, r; + + assert(t); + + /* Add all DNSKEY RRs from the answer that are validated by DS + * RRs from the list of validated keys to the list of + * validated keys. */ + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { + + r = dnssec_verify_dnskey_search(rr, t->validated_keys); + if (r < 0) + return r; + if (r == 0) + continue; + + /* If so, the DNSKEY is validated too. */ + r = dns_answer_add_extend(&t->validated_keys, rr, ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *rr) { + int r; + + assert(t); + assert(rr); + + /* Checks if the RR we are looking for must be signed with an + * RRSIG. This is used for positive responses. */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return false; + + if (dns_type_is_pseudo(rr->key->type)) + return -EINVAL; + + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + switch (rr->key->type) { + + case DNS_TYPE_RRSIG: + /* RRSIGs are the signatures themselves, they need no signing. */ + return false; + + case DNS_TYPE_SOA: + case DNS_TYPE_NS: { + DnsTransaction *dt; + Iterator i; + + /* For SOA or NS RRs we look for a matching DS transaction */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_DS) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found a DS transactions for the SOA/NS + * RRs we are looking at. If it discovered signed DS + * RRs, then we need to be signed, too. */ + + if (!dt->answer_authenticated) + return false; + + return dns_answer_match_key(dt->answer, dt->key, NULL); + } + + /* We found nothing that proves this is safe to leave + * this unauthenticated, hence ask inist on + * authentication. */ + return true; + } + + case DNS_TYPE_DS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: { + const char *parent = NULL; + DnsTransaction *dt; + Iterator i; + + /* + * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA. + * + * DS RRs are signed if the parent is signed, hence also look at the parent SOA + */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_SOA) + continue; + + if (!parent) { + parent = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&parent); + if (r < 0) + return r; + if (r == 0) { + if (rr->key->type == DNS_TYPE_DS) + return true; + + /* A CNAME/DNAME without a parent? That's sooo weird. */ + log_debug("Transaction %" PRIu16 " claims CNAME/DNAME at root. Refusing.", t->id); + return -EBADMSG; + } + } + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), parent); + if (r < 0) + return r; + if (r == 0) + continue; + + return t->answer_authenticated; + } + + return true; + } + + default: { + DnsTransaction *dt; + Iterator i; + + /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our SOA lookup was authenticated */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_SOA) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found the transaction that was supposed to find + * the SOA RR for us. It was successful, but found no + * RR for us. This means we are not at a zone cut. In + * this case, we require authentication if the SOA + * lookup was authenticated too. */ + return t->answer_authenticated; + } + + return true; + }} +} + +static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKey *key) { + DnsTransaction *dt; + const char *tld; + Iterator i; + int r; + + /* If DNSSEC downgrade mode is on, checks whether the + * specified RR is one level below a TLD we have proven not to + * exist. In such a case we assume that this is a private + * domain, and permit it. + * + * This detects cases like the Fritz!Box router networks. Each + * Fritz!Box router serves a private "fritz.box" zone, in the + * non-existing TLD "box". Requests for the "fritz.box" domain + * are served by the router itself, while requests for the + * "box" domain will result in NXDOMAIN. + * + * Note that this logic is unable to detect cases where a + * router serves a private DNS zone directly under + * non-existing TLD. In such a case we cannot detect whether + * the TLD is supposed to exist or not, as all requests we + * make for it will be answered by the router's zone, and not + * by the root zone. */ + + assert(t); + + if (t->scope->dnssec_mode != DNSSEC_ALLOW_DOWNGRADE) + return false; /* In strict DNSSEC mode what doesn't exist, doesn't exist */ + + tld = DNS_RESOURCE_KEY_NAME(key); + r = dns_name_parent(&tld); + if (r < 0) + return r; + if (r == 0) + return false; /* Already the root domain */ + + if (!dns_name_is_single_label(tld)) + return false; + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != key->class) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), tld); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found an auxiliary lookup we did for the TLD. If + * that returned with NXDOMAIN, we know the TLD didn't + * exist, and hence this might be a private zone. */ + + return dt->answer_rcode == DNS_RCODE_NXDOMAIN; + } + + return false; +} + +static int dns_transaction_requires_nsec(DnsTransaction *t) { + DnsTransaction *dt; + const char *name; + Iterator i; + int r; + + assert(t); + + /* Checks if we need to insist on NSEC/NSEC3 RRs for proving + * this negative reply */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return false; + + if (dns_type_is_pseudo(t->key->type)) + return -EINVAL; + + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + r = dns_transaction_in_private_tld(t, t->key); + if (r < 0) + return r; + if (r > 0) { + /* The lookup is from a TLD that is proven not to + * exist, and we are in downgrade mode, hence ignore + * that fact that we didn't get any NSEC RRs.*/ + + log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.", dns_transaction_key_string(t)); + return false; + } + + name = DNS_RESOURCE_KEY_NAME(t->key); + + if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) { + + /* We got a negative reply for this SOA/NS lookup? If + * so, then we are not at a zone apex, and thus should + * look at the result of the parent SOA lookup. + * + * We got a negative reply for this DS lookup? DS RRs + * are signed when their parent zone is signed, hence + * also check the parent SOA in this case. */ + + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return true; + } + + /* For all other RRs we check the SOA on the same level to see + * if it's signed. */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != t->key->class) + continue; + if (dt->key->type != DNS_TYPE_SOA) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), name); + if (r < 0) + return r; + if (r == 0) + continue; + + return dt->answer_authenticated; + } + + /* If in doubt, require NSEC/NSEC3 */ + return true; +} + +static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRecord *rr) { + DnsResourceRecord *rrsig; + bool found = false; + int r; + + /* Checks whether any of the DNSKEYs used for the RRSIGs for + * the specified RRset is authenticated (i.e. has a matching + * DS RR). */ + + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + DNS_ANSWER_FOREACH(rrsig, t->answer) { + DnsTransaction *dt; + Iterator i; + + r = dnssec_key_match_rrsig(rr->key, rrsig); + if (r < 0) + return r; + if (r == 0) + continue; + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + + if (dt->key->type == DNS_TYPE_DNSKEY) { + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + continue; + + /* OK, we found an auxiliary DNSKEY + * lookup. If that lookup is + * authenticated, report this. */ + + if (dt->answer_authenticated) + return true; + + found = true; + + } else if (dt->key->type == DNS_TYPE_DS) { + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + continue; + + /* OK, we found an auxiliary DS + * lookup. If that lookup is + * authenticated and non-zero, we + * won! */ + + if (!dt->answer_authenticated) + return false; + + return dns_answer_match_key(dt->answer, dt->key, NULL); + } } } - /* Detach us from the DNSSEC transaction. */ - (void) set_remove(t->dnssec_transactions, source); - (void) set_remove(source->notify_transactions, t); + return found ? false : -ENXIO; +} + +static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr) { + assert(t); + assert(rr); + + /* We know that the root domain is signed, hence if it appears + * not to be signed, there's a problem with the DNS server */ - /* If the state is still PENDING, we are still in the loop - * that adds further DNSSEC transactions, hence don't check if - * we are ready yet. If the state is VALIDATING however, we - * should check if we are complete now. */ - if (t->state == DNS_TRANSACTION_VALIDATING) - dns_transaction_process_dnssec(t); + return rr->key->class == DNS_CLASS_IN && + dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key)); } int dns_transaction_validate_dnssec(DnsTransaction *t) { _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL; + bool dnskeys_finalized = false; DnsResourceRecord *rr; - int ifindex, r; + DnsAnswerFlags flags; + int r; assert(t); @@ -1291,43 +2240,40 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { * t->validated_keys, let's see which RRs we can now * authenticate with that. */ - if (t->scope->dnssec_mode != DNSSEC_YES) + if (t->scope->dnssec_mode == DNSSEC_NO) return 0; /* Already validated */ - if (t->dnssec_result != _DNSSEC_RESULT_INVALID) + if (t->answer_dnssec_result != _DNSSEC_RESULT_INVALID) return 0; + /* Our own stuff needs no validation */ if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) { - t->dnssec_result = DNSSEC_VALIDATED; + t->answer_dnssec_result = DNSSEC_VALIDATED; t->answer_authenticated = true; return 0; } - if (log_get_max_level() >= LOG_DEBUG) { - _cleanup_free_ char *ks = NULL; + /* Cached stuff is not affected by validation. */ + if (t->answer_source != DNS_TRANSACTION_NETWORK) + return 0; - (void) dns_resource_key_to_string(t->key, &ks); - log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, ks ? strstrip(ks) : "???"); + if (t->current_features < DNS_SERVER_FEATURE_LEVEL_DO || + (t->server && t->server->rrsig_missing)) { + /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */ + t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; + return 0; } - /* First see if there are DNSKEYs we already known a validated DS for. */ - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { - - r = dnssec_verify_dnskey_search(rr, t->validated_keys); - if (r < 0) - return r; - if (r == 0) - continue; + log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t)); - /* If so, the DNSKEY is validated too. */ - r = dns_answer_add_extend(&t->validated_keys, rr, ifindex); - if (r < 0) - return r; - } + /* First see if there are DNSKEYs we already known a validated DS for. */ + r = dns_transaction_validate_dnskey_by_ds(t); + if (r < 0) + return r; for (;;) { - bool changed = false, missing_key_for_transaction = false; + bool changed = false; DNS_ANSWER_FOREACH(rr, t->answer) { DnssecResult result; @@ -1339,21 +2285,9 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (r < 0) return r; - if (log_get_max_level() >= LOG_DEBUG) { - _cleanup_free_ char *rrs = NULL; - - (void) dns_resource_record_to_string(rr, &rrs); - log_debug("Looking at %s: %s", rrs ? strstrip(rrs) : "???", dnssec_result_to_string(result)); - } + log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result)); - switch (result) { - - case DNSSEC_VALIDATED: - - /* Add the validated RRset to the new list of validated RRsets */ - r = dns_answer_copy_by_key(&validated, t->answer, rr->key); - if (r < 0) - return r; + if (result == DNSSEC_VALIDATED) { if (rr->key->type == DNS_TYPE_DNSKEY) { /* If we just validated a @@ -1362,81 +2296,178 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { * of validated keys for this * transaction. */ - r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key); + r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + /* Maybe warn the user that we + * encountered a revoked + * DNSKEY for a key from our + * trust anchor */ + r = dns_trust_anchor_check_revoked(&t->scope->manager->trust_anchor, t->answer, rr->key); if (r < 0) return r; } - /* Now, remove this RRset from the RRs still to process */ - r = dns_answer_remove_by_key(&t->answer, rr->key); + /* Add the validated RRset to the new + * list of validated RRsets, and + * remove it from the unvalidated + * RRsets. We mark the RRset as + * authenticated and cacheable. */ + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE); if (r < 0) return r; + t->scope->manager->n_dnssec_secure++; + + /* Exit the loop, we dropped something from the answer, start from the beginning */ changed = true; break; - case DNSSEC_INVALID: - case DNSSEC_NO_SIGNATURE: - case DNSSEC_SIGNATURE_EXPIRED: + } else if (dnskeys_finalized) { - /* Is this the RRset that we were looking for? If so, this is fatal for the whole transaction */ - r = dns_resource_key_match_rr(t->key, rr, NULL); - if (r < 0) - return r; - if (r > 0) { - t->dnssec_result = result; - return 0; + /* If we haven't read all DNSKEYs yet + * a negative result of the validation + * is irrelevant, as there might be + * more DNSKEYs coming. */ + + if (result == DNSSEC_NO_SIGNATURE) { + r = dns_transaction_requires_rrsig(t, rr); + if (r < 0) + return r; + if (r == 0) { + /* Data does not require signing. In that case, just copy it over, + * but remember that this is by no means authenticated.*/ + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + t->scope->manager->n_dnssec_insecure++; + + changed = true; + break; + } + + r = dns_transaction_known_signed(t, rr); + if (r < 0) + return r; + if (r > 0) { + /* This is an RR we know has to be signed. If it isn't this means + * the server is not attaching RRSIGs, hence complain. */ + + dns_server_packet_rrsig_missing(t->server); + + if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { + + /* Downgrading is OK? If so, just consider the information unsigned */ + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + t->scope->manager->n_dnssec_insecure++; + changed = true; + break; + } + + /* Otherwise, fail */ + t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; + return 0; + } + + r = dns_transaction_in_private_tld(t, rr->key); + if (r < 0) + return r; + if (r > 0) { + _cleanup_free_ char *s = NULL; + + /* The data is from a TLD that is proven not to exist, and we are in downgrade + * mode, hence ignore the fact that this was not signed. */ + + (void) dns_resource_key_to_string(rr->key, &s); + log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", strna(s ? strstrip(s) : NULL)); + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + t->scope->manager->n_dnssec_insecure++; + changed = true; + break; + } } - /* Is this a CNAME for a record we were looking for? If so, it's also fatal for the whole transaction */ - r = dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); + if (IN_SET(result, + DNSSEC_MISSING_KEY, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM)) { + + r = dns_transaction_dnskey_authenticated(t, rr); + if (r < 0 && r != -ENXIO) + return r; + if (r == 0) { + /* The DNSKEY transaction was not authenticated, this means there's + * no DS for this, which means it's OK if no keys are found for this signature. */ + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + t->scope->manager->n_dnssec_insecure++; + + changed = true; + break; + } + } + + if (IN_SET(result, + DNSSEC_INVALID, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_NO_SIGNATURE)) + t->scope->manager->n_dnssec_bogus++; + else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */ + t->scope->manager->n_dnssec_indeterminate++; + + r = dns_transaction_is_primary_response(t, rr); if (r < 0) return r; if (r > 0) { - t->dnssec_result = result; + /* This is a primary response + * to our question, and it + * failed validation. That's + * fatal. */ + t->answer_dnssec_result = result; return 0; } - /* This is just something auxiliary. Just remove the RRset and continue. */ + /* This is just some auxiliary + * data. Just remove the RRset and + * continue. */ r = dns_answer_remove_by_key(&t->answer, rr->key); if (r < 0) return r; + /* Exit the loop, we dropped something from the answer, start from the beginning */ changed = true; break; - - case DNSSEC_MISSING_KEY: - /* They key is missing? Let's continue - * with the next iteration, maybe - * we'll find it in an DNSKEY RRset - * later on. */ - - r = dns_resource_key_equal(rr->key, t->key); - if (r < 0) - return r; - if (r > 0) - missing_key_for_transaction = true; - - break; - - default: - assert_not_reached("Unexpected DNSSEC result"); } - - if (changed) - break; } if (changed) continue; - /* This didn't work either, there's no point in - * continuing. */ - if (missing_key_for_transaction) { - t->dnssec_result = DNSSEC_MISSING_KEY; - return 0; + if (!dnskeys_finalized) { + /* OK, now we know we have added all DNSKEYs + * we possibly could to our validated + * list. Now run the whole thing once more, + * and strip everything we still cannot + * validate. + */ + dnskeys_finalized = true; + continue; } + /* We're done */ break; } @@ -1444,24 +2475,119 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer = validated; validated = NULL; - t->answer_authenticated = true; - t->dnssec_result = DNSSEC_VALIDATED; + /* At this point the answer only contains validated + * RRsets. Now, let's see if it actually answers the question + * we asked. If so, great! If it doesn't, then see if + * NSEC/NSEC3 can prove this. */ + r = dns_transaction_has_positive_answer(t, &flags); + if (r > 0) { + /* Yes, it answers the question! */ + + if (flags & DNS_ANSWER_AUTHENTICATED) { + /* The answer is fully authenticated, yay. */ + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = true; + } else { + /* The answer is not fully authenticated. */ + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + } + + } else if (r == 0) { + DnssecNsecResult nr; + bool authenticated = false; + + /* Bummer! Let's check NSEC/NSEC3 */ + r = dnssec_test_nsec(t->answer, t->key, &nr, &authenticated, &t->answer_nsec_ttl); + if (r < 0) + return r; + + switch (nr) { + + case DNSSEC_NSEC_NXDOMAIN: + /* NSEC proves the domain doesn't exist. Very good. */ + log_debug("Proved NXDOMAIN via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_NXDOMAIN; + t->answer_authenticated = authenticated; + break; + + case DNSSEC_NSEC_NODATA: + /* NSEC proves that there's no data here, very good. */ + log_debug("Proved NODATA via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = authenticated; + break; + + case DNSSEC_NSEC_OPTOUT: + /* NSEC3 says the data might not be signed */ + log_debug("Data is NSEC3 opt-out via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + break; + + case DNSSEC_NSEC_NO_RR: + /* No NSEC data? Bummer! */ + + r = dns_transaction_requires_nsec(t); + if (r < 0) + return r; + if (r > 0) + t->answer_dnssec_result = DNSSEC_NO_SIGNATURE; + else { + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + } + + break; + + case DNSSEC_NSEC_UNSUPPORTED_ALGORITHM: + /* We don't know the NSEC3 algorithm used? */ + t->answer_dnssec_result = DNSSEC_UNSUPPORTED_ALGORITHM; + break; + + case DNSSEC_NSEC_FOUND: + case DNSSEC_NSEC_CNAME: + /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ + t->answer_dnssec_result = DNSSEC_NSEC_MISMATCH; + break; + + default: + assert_not_reached("Unexpected NSEC result."); + } + } + return 1; } +const char *dns_transaction_key_string(DnsTransaction *t) { + assert(t); + + if (!t->key_string) { + if (dns_resource_key_to_string(t->key, &t->key_string) < 0) + return "n/a"; + } + + return strstrip(t->key_string); +} + static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = { [DNS_TRANSACTION_NULL] = "null", [DNS_TRANSACTION_PENDING] = "pending", [DNS_TRANSACTION_VALIDATING] = "validating", - [DNS_TRANSACTION_FAILURE] = "failure", + [DNS_TRANSACTION_RCODE_FAILURE] = "rcode-failure", [DNS_TRANSACTION_SUCCESS] = "success", [DNS_TRANSACTION_NO_SERVERS] = "no-servers", [DNS_TRANSACTION_TIMEOUT] = "timeout", [DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached", [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", [DNS_TRANSACTION_RESOURCES] = "resources", + [DNS_TRANSACTION_CONNECTION_FAILURE] = "connection-failure", [DNS_TRANSACTION_ABORTED] = "aborted", [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed", + [DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor", }; DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index 2328e7937c..ede33f9547 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -29,15 +29,17 @@ enum DnsTransactionState { DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING, - DNS_TRANSACTION_FAILURE, + DNS_TRANSACTION_RCODE_FAILURE, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NO_SERVERS, DNS_TRANSACTION_TIMEOUT, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, DNS_TRANSACTION_INVALID_REPLY, DNS_TRANSACTION_RESOURCES, + DNS_TRANSACTION_CONNECTION_FAILURE, DNS_TRANSACTION_ABORTED, DNS_TRANSACTION_DNSSEC_FAILED, + DNS_TRANSACTION_NO_TRUST_ANCHOR, _DNS_TRANSACTION_STATE_MAX, _DNS_TRANSACTION_STATE_INVALID = -1 }; @@ -62,23 +64,37 @@ struct DnsTransaction { DnsScope *scope; DnsResourceKey *key; + char *key_string; DnsTransactionState state; - DnssecResult dnssec_result; uint16_t id; - bool initial_jitter_scheduled; - bool initial_jitter_elapsed; + bool tried_stream:1; + + bool initial_jitter_scheduled:1; + bool initial_jitter_elapsed:1; DnsPacket *sent, *received; DnsAnswer *answer; int answer_rcode; + DnssecResult answer_dnssec_result; DnsTransactionSource answer_source; + uint32_t answer_nsec_ttl; + + /* Indicates whether the primary answer is authenticated, + * i.e. whether the RRs from answer which directly match the + * question are authenticated, or, if there are none, whether + * the NODATA or NXDOMAIN case is. It says nothing about + * additional RRs listed in the answer, however they have + * their own DNS_ANSWER_AUTHORIZED FLAGS. Note that this bit + * is defined different than the AD bit in DNS packets, as + * that covers more than just the actual primary answer. */ bool answer_authenticated; - /* Contains DS and DNSKEY RRs we already verified and need to authenticate this reply */ + /* Contains DNSKEY, DS, SOA RRs we already verified and need + * to authenticate this reply */ DnsAnswer *validated_keys; usec_t start_usec; @@ -86,18 +102,19 @@ struct DnsTransaction { sd_event_source *timeout_event_source; unsigned n_attempts; + /* UDP connection logic, if we need it */ int dns_udp_fd; sd_event_source *dns_udp_event_source; + /* TCP connection logic, if we need it */ + DnsStream *stream; + /* The active server */ DnsServer *server; /* The features of the DNS server at time of transaction start */ DnsServerFeatureLevel current_features; - /* TCP connection logic, if we need it */ - DnsStream *stream; - /* Query candidates this transaction is referenced by and that * shall be notified about this specific transaction * completing. */ @@ -125,7 +142,7 @@ struct DnsTransaction { int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); DnsTransaction* dns_transaction_free(DnsTransaction *t); -void dns_transaction_gc(DnsTransaction *t); +bool dns_transaction_gc(DnsTransaction *t); int dns_transaction_go(DnsTransaction *t); void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p); @@ -135,6 +152,8 @@ void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source); int dns_transaction_validate_dnssec(DnsTransaction *t); int dns_transaction_request_dnssec_keys(DnsTransaction *t); +const char *dns_transaction_key_string(DnsTransaction *t); + const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c index e55bdaa1ed..9f8b76ebe2 100644 --- a/src/resolve/resolved-dns-trust-anchor.c +++ b/src/resolve/resolved-dns-trust-anchor.c @@ -19,26 +19,56 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sd-messages.h> + #include "alloc-util.h" +#include "conf-files.h" +#include "def.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "parse-util.h" #include "resolved-dns-trust-anchor.h" +#include "resolved-dns-dnssec.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" + +static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d"); -/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml */ +/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */ static const uint8_t root_digest[] = { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60, 0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 }; -int dns_trust_anchor_load(DnsTrustAnchor *d) { +static bool dns_trust_anchor_knows_domain_positive(DnsTrustAnchor *d, const char *name) { + assert(d); + + /* Returns true if there's an entry for the specified domain + * name in our trust anchor */ + + return + hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, name)) || + hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name)); +} + +static int dns_trust_anchor_add_builtin_positive(DnsTrustAnchor *d) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; int r; assert(d); - r = hashmap_ensure_allocated(&d->by_key, &dns_resource_key_hash_ops); + r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); if (r < 0) return r; - if (hashmap_get(d->by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, "."))) + /* Only add the built-in trust anchor if there's neither a DS + * nor a DNSKEY defined for the root domain. That way users + * have an easy way to override the root domain DS/DNSKEY + * data. */ + if (dns_trust_anchor_knows_domain_positive(d, ".")) return 0; /* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */ @@ -58,11 +88,11 @@ int dns_trust_anchor_load(DnsTrustAnchor *d) { if (!answer) return -ENOMEM; - r = dns_answer_add(answer, rr, 0); + r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; - r = hashmap_put(d->by_key, rr->key, answer); + r = hashmap_put(d->positive_by_key, rr->key, answer); if (r < 0) return r; @@ -70,18 +100,428 @@ int dns_trust_anchor_load(DnsTrustAnchor *d) { return 0; } +static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) { + + static const char private_domains[] = + /* RFC 6761 says that .test is a special domain for + * testing and not to be installed in the root zone */ + "test\0" + + /* RFC 6761 says that these reverse IP lookup ranges + * are for private addresses, and hence should not + * show up in the root zone */ + "10.in-addr.arpa\0" + "16.172.in-addr.arpa\0" + "17.172.in-addr.arpa\0" + "18.172.in-addr.arpa\0" + "19.172.in-addr.arpa\0" + "20.172.in-addr.arpa\0" + "21.172.in-addr.arpa\0" + "22.172.in-addr.arpa\0" + "23.172.in-addr.arpa\0" + "24.172.in-addr.arpa\0" + "25.172.in-addr.arpa\0" + "26.172.in-addr.arpa\0" + "27.172.in-addr.arpa\0" + "28.172.in-addr.arpa\0" + "29.172.in-addr.arpa\0" + "30.172.in-addr.arpa\0" + "31.172.in-addr.arpa\0" + "168.192.in-addr.arpa\0" + + /* RFC 6762 reserves the .local domain for Multicast + * DNS, it hence cannot appear in the root zone. (Note + * that we by default do not route .local traffic to + * DNS anyway, except when a configured search domain + * suggests so.) */ + "local\0" + + /* These two are well known, popular private zone + * TLDs, that are blocked from delegation, according + * to: + * http://icannwiki.com/Name_Collision#NGPC_Resolution + * + * There's also ongoing work on making this official + * in an RRC: + * https://www.ietf.org/archive/id/draft-chapin-additional-reserved-tlds-02.txt */ + "home\0" + "corp\0" + + /* The following four TLDs are suggested for private + * zones in RFC 6762, Appendix G, and are hence very + * unlikely to be made official TLDs any day soon */ + "lan\0" + "intranet\0" + "internal\0" + "private\0"; + + const char *name; + int r; + + assert(d); + + /* Only add the built-in trust anchor if there's no negative + * trust anchor defined at all. This enables easy overriding + * of negative trust anchors. */ + + if (set_size(d->negative_by_name) > 0) + return 0; + + r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); + if (r < 0) + return r; + + /* We add a couple of domains as default negative trust + * anchors, where it's very unlikely they will be installed in + * the root zone. If they exist they must be private, and thus + * unsigned. */ + + NULSTR_FOREACH(name, private_domains) { + + if (dns_trust_anchor_knows_domain_positive(d, name)) + continue; + + r = set_put_strdup(d->negative_by_name, name); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ char *domain = NULL, *class = NULL, *type = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnsAnswer *old_answer = NULL; + const char *p = s; + int r; + + assert(d); + assert(line); + + r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); + if (r < 0) + return log_warning_errno(r, "Unable to parse domain in line %s:%u: %m", path, line); + + if (!dns_name_is_valid(domain)) { + log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); + return -EINVAL; + } + + r = extract_many_words(&p, NULL, 0, &class, &type, NULL); + if (r < 0) + return log_warning_errno(r, "Unable to parse class and type in line %s:%u: %m", path, line); + if (r != 2) { + log_warning("Missing class or type in line %s:%u", path, line); + return -EINVAL; + } + + if (!strcaseeq(class, "IN")) { + log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path, line); + return -EINVAL; + } + + if (strcaseeq(type, "DS")) { + _cleanup_free_ char *key_tag = NULL, *algorithm = NULL, *digest_type = NULL, *digest = NULL; + _cleanup_free_ void *dd = NULL; + uint16_t kt; + int a, dt; + size_t l; + + r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type, &digest, NULL); + if (r < 0) { + log_warning_errno(r, "Failed to parse DS parameters on line %s:%u: %m", path, line); + return -EINVAL; + } + if (r != 4) { + log_warning("Missing DS parameters on line %s:%u", path, line); + return -EINVAL; + } + + r = safe_atou16(key_tag, &kt); + if (r < 0) + return log_warning_errno(r, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag, path, line); + + a = dnssec_algorithm_from_string(algorithm); + if (a < 0) { + log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm, path, line); + return -EINVAL; + } + + dt = dnssec_digest_from_string(digest_type); + if (dt < 0) { + log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type, path, line); + return -EINVAL; + } + + r = unhexmem(digest, strlen(digest), &dd, &l); + if (r < 0) { + log_warning("Failed to parse DS digest %s on line %s:%u", digest, path, line); + return -EINVAL; + } + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, domain); + if (!rr) + return log_oom(); + + rr->ds.key_tag = kt; + rr->ds.algorithm = a; + rr->ds.digest_type = dt; + rr->ds.digest_size = l; + rr->ds.digest = dd; + dd = NULL; + + } else if (strcaseeq(type, "DNSKEY")) { + _cleanup_free_ char *flags = NULL, *protocol = NULL, *algorithm = NULL, *key = NULL; + _cleanup_free_ void *k = NULL; + uint16_t f; + size_t l; + int a; + + r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm, &key, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY parameters on line %s:%u: %m", path, line); + if (r != 4) { + log_warning("Missing DNSKEY parameters on line %s:%u", path, line); + return -EINVAL; + } + + if (!streq(protocol, "3")) { + log_warning("DNSKEY Protocol is not 3 on line %s:%u", path, line); + return -EINVAL; + } + + r = safe_atou16(flags, &f); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY flags field %s on line %s:%u", flags, path, line); + if ((f & DNSKEY_FLAG_ZONE_KEY) == 0) { + log_warning("DNSKEY lacks zone key bit set on line %s:%u", path, line); + return -EINVAL; + } + if ((f & DNSKEY_FLAG_REVOKE)) { + log_warning("DNSKEY is already revoked on line %s:%u", path, line); + return -EINVAL; + } + + a = dnssec_algorithm_from_string(algorithm); + if (a < 0) { + log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm, path, line); + return -EINVAL; + } + + r = unbase64mem(key, strlen(key), &k, &l); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", key, path, line); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, domain); + if (!rr) + return log_oom(); + + rr->dnskey.flags = f; + rr->dnskey.protocol = 3; + rr->dnskey.algorithm = a; + rr->dnskey.key_size = l; + rr->dnskey.key = k; + k = NULL; + + } else { + log_warning("RR type %s is not supported, ignoring line %s:%u.", type, path, line); + return -EINVAL; + } + + if (!isempty(p)) { + log_warning("Trailing garbage on line %s:%u, ignoring line.", path, line); + return -EINVAL; + } + + r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); + if (r < 0) + return log_oom(); + + old_answer = hashmap_get(d->positive_by_key, rr->key); + answer = dns_answer_ref(old_answer); + + r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return log_error_errno(r, "Failed to add trust anchor RR: %m"); + + r = hashmap_replace(d->positive_by_key, rr->key, answer); + if (r < 0) + return log_error_errno(r, "Failed to add answer to trust anchor: %m"); + + old_answer = dns_answer_unref(old_answer); + answer = NULL; + + return 0; +} + +static int dns_trust_anchor_load_negative(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { + _cleanup_free_ char *domain = NULL; + const char *p = s; + int r; + + assert(d); + assert(line); + + r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); + if (r < 0) + return log_warning_errno(r, "Unable to parse line %s:%u: %m", path, line); + + if (!dns_name_is_valid(domain)) { + log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); + return -EINVAL; + } + + if (!isempty(p)) { + log_warning("Trailing garbage at line %s:%u, ignoring line.", path, line); + return -EINVAL; + } + + r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); + if (r < 0) + return log_oom(); + + r = set_put(d->negative_by_name, domain); + if (r < 0) + return log_oom(); + if (r > 0) + domain = NULL; + + return 0; +} + +static int dns_trust_anchor_load_files( + DnsTrustAnchor *d, + const char *suffix, + int (*loader)(DnsTrustAnchor *d, const char *path, unsigned n, const char *line)) { + + _cleanup_strv_free_ char **files = NULL; + char **f; + int r; + + assert(d); + assert(suffix); + assert(loader); + + r = conf_files_list_nulstr(&files, suffix, NULL, trust_anchor_dirs); + if (r < 0) + return log_error_errno(r, "Failed to enumerate %s trust anchor files: %m", suffix); + + STRV_FOREACH(f, files) { + _cleanup_fclose_ FILE *g = NULL; + char line[LINE_MAX]; + unsigned n = 0; + + g = fopen(*f, "r"); + if (!g) { + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to open %s: %m", *f); + continue; + } + + FOREACH_LINE(line, g, log_warning_errno(errno, "Failed to read %s, ignoring: %m", *f)) { + char *l; + + n++; + + l = strstrip(line); + if (isempty(l)) + continue; + + if (*l == ';') + continue; + + (void) loader(d, *f, n, l); + } + } + + return 0; +} + +static int domain_name_cmp(const void *a, const void *b) { + char **x = (char**) a, **y = (char**) b; + + return dns_name_compare_func(*x, *y); +} + +static int dns_trust_anchor_dump(DnsTrustAnchor *d) { + DnsAnswer *a; + Iterator i; + + assert(d); + + if (hashmap_isempty(d->positive_by_key)) + log_info("No positive trust anchors defined."); + else { + log_info("Positive Trust Anchors:"); + HASHMAP_FOREACH(a, d->positive_by_key, i) { + DnsResourceRecord *rr; + + DNS_ANSWER_FOREACH(rr, a) + log_info("%s", dns_resource_record_to_string(rr)); + } + } + + if (set_isempty(d->negative_by_name)) + log_info("No negative trust anchors defined."); + else { + _cleanup_free_ char **l = NULL, *j = NULL; + + l = set_get_strv(d->negative_by_name); + if (!l) + return log_oom(); + + qsort_safe(l, set_size(d->negative_by_name), sizeof(char*), domain_name_cmp); + + j = strv_join(l, " "); + if (!j) + return log_oom(); + + log_info("Negative trust anchors: %s", j); + } + + return 0; +} + +int dns_trust_anchor_load(DnsTrustAnchor *d) { + int r; + + assert(d); + + /* If loading things from disk fails, we don't consider this fatal */ + (void) dns_trust_anchor_load_files(d, ".positive", dns_trust_anchor_load_positive); + (void) dns_trust_anchor_load_files(d, ".negative", dns_trust_anchor_load_negative); + + /* However, if the built-in DS fails, then we have a problem. */ + r = dns_trust_anchor_add_builtin_positive(d); + if (r < 0) + return log_error_errno(r, "Failed to add built-in positive trust anchor: %m"); + + r = dns_trust_anchor_add_builtin_negative(d); + if (r < 0) + return log_error_errno(r, "Failed to add built-in negative trust anchor: %m"); + + dns_trust_anchor_dump(d); + + return 0; +} + void dns_trust_anchor_flush(DnsTrustAnchor *d) { DnsAnswer *a; assert(d); - while ((a = hashmap_steal_first(d->by_key))) + while ((a = hashmap_steal_first(d->positive_by_key))) dns_answer_unref(a); - d->by_key = hashmap_free(d->by_key); + d->positive_by_key = hashmap_free(d->positive_by_key); + d->negative_by_name = set_free_free(d->negative_by_name); } -int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey *key, DnsAnswer **ret) { +int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *key, DnsAnswer **ret) { DnsAnswer *a; assert(d); @@ -92,10 +532,173 @@ int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey *key, DnsAnswer ** if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) return 0; - a = hashmap_get(d->by_key, key); + a = hashmap_get(d->positive_by_key, key); if (!a) return 0; *ret = dns_answer_ref(a); return 1; } + +int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) { + assert(d); + assert(name); + + return set_contains(d->negative_by_name, name); +} + +static int dns_trust_anchor_remove_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { + _cleanup_(dns_answer_unrefp) DnsAnswer *new_answer = NULL; + DnsAnswer *old_answer; + int r; + + old_answer = hashmap_get(d->positive_by_key, rr->key); + if (!old_answer) + return 0; + + new_answer = dns_answer_ref(old_answer); + + r = dns_answer_remove_by_rr(&new_answer, rr); + if (r <= 0) + return r; + + /* We found the key! Warn the user */ + log_struct(LOG_WARNING, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED), + LOG_MESSAGE("DNSSEC Trust anchor %s has been revoked. Please update the trust anchor, or upgrade your operating system."), strna(dns_resource_record_to_string(rr)), + "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr), + NULL); + + if (dns_answer_size(new_answer) <= 0) { + assert_se(hashmap_remove(d->positive_by_key, rr->key) == old_answer); + dns_answer_unref(old_answer); + return 1; + } + + r = hashmap_replace(d->positive_by_key, new_answer->items[0].rr->key, new_answer); + if (r < 0) + return r; + + new_answer = NULL; + dns_answer_unref(old_answer); + return 1; +} + +static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceRecord *revoked_dnskey) { + DnsAnswer *a; + int r; + + assert(d); + assert(revoked_dnskey); + assert(revoked_dnskey->key->type == DNS_TYPE_DNSKEY); + assert(revoked_dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE); + + a = hashmap_get(d->positive_by_key, revoked_dnskey->key); + if (a) { + DnsResourceRecord *anchor; + + /* First, look for the precise DNSKEY in our trust anchor database */ + + DNS_ANSWER_FOREACH(anchor, a) { + + if (anchor->dnskey.protocol != revoked_dnskey->dnskey.protocol) + continue; + + if (anchor->dnskey.algorithm != revoked_dnskey->dnskey.algorithm) + continue; + + if (anchor->dnskey.key_size != revoked_dnskey->dnskey.key_size) + continue; + + if (((anchor->dnskey.flags ^ revoked_dnskey->dnskey.flags) | DNSKEY_FLAG_REVOKE) != DNSKEY_FLAG_REVOKE) + continue; + + if (memcmp(anchor->dnskey.key, revoked_dnskey->dnskey.key, anchor->dnskey.key_size) != 0) + continue; + + dns_trust_anchor_remove_revoked(d, anchor); + break; + } + } + + a = hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(revoked_dnskey->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(revoked_dnskey->key))); + if (a) { + DnsResourceRecord *anchor; + + /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */ + + DNS_ANSWER_FOREACH(anchor, a) { + + r = dnssec_verify_dnskey(revoked_dnskey, anchor, true); + if (r < 0) + return r; + if (r == 0) + continue; + + dns_trust_anchor_remove_revoked(d, anchor); + break; + } + } + + return 0; +} + +int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsAnswer *rrs, const DnsResourceKey *key) { + DnsResourceRecord *dnskey; + int r; + + assert(d); + assert(key); + + /* Looks for self-signed DNSKEY RRs in "rrs" that have been revoked. */ + + if (key->type != DNS_TYPE_DNSKEY) + return 0; + + DNS_ANSWER_FOREACH(dnskey, rrs) { + DnsResourceRecord *rrsig; + DnssecResult result; + + r = dns_resource_key_equal(key, dnskey->key); + if (r < 0) + return r; + if (r == 0) + continue; + + /* Is this DNSKEY revoked? */ + if ((dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE) == 0) + continue; + + /* Could this be interesting to us at all? If not, + * there's no point in looking for and verifying a + * self-signed RRSIG. */ + if (!dns_trust_anchor_knows_domain_positive(d, DNS_RESOURCE_KEY_NAME(dnskey->key))) + continue; + + /* Look for a self-signed RRSIG */ + DNS_ANSWER_FOREACH(rrsig, rrs) { + + if (rrsig->key->type != DNS_TYPE_RRSIG) + continue; + + r = dnssec_rrsig_match_dnskey(rrsig, dnskey, true); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_verify_rrset(rrs, key, rrsig, dnskey, USEC_INFINITY, &result); + if (r < 0) + return r; + if (result != DNSSEC_VALIDATED) + continue; + + /* Bingo! Now, act! */ + r = dns_trust_anchor_check_revoked_one(d, dnskey); + if (r < 0) + return r; + } + } + + return 0; +} diff --git a/src/resolve/resolved-dns-trust-anchor.h b/src/resolve/resolved-dns-trust-anchor.h index 06f3723914..303c4088d1 100644 --- a/src/resolve/resolved-dns-trust-anchor.h +++ b/src/resolve/resolved-dns-trust-anchor.h @@ -30,10 +30,14 @@ typedef struct DnsTrustAnchor DnsTrustAnchor; /* This contains a fixed database mapping domain names to DS or DNSKEY records. */ struct DnsTrustAnchor { - Hashmap *by_key; + Hashmap *positive_by_key; + Set *negative_by_name; }; int dns_trust_anchor_load(DnsTrustAnchor *d); void dns_trust_anchor_flush(DnsTrustAnchor *d); -int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey* key, DnsAnswer **answer); +int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey* key, DnsAnswer **answer); +int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name); + +int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsAnswer *rrs, const DnsResourceKey *key); diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 8046e2ed34..f60b0bddc1 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -223,9 +223,9 @@ int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) { assert(s); assert(rr); - if (rr->key->class == DNS_CLASS_ANY) + if (dns_class_is_pseudo(rr->key->class)) return -EINVAL; - if (rr->key->type == DNS_TYPE_ANY) + if (dns_type_is_pseudo(rr->key->type)) return -EINVAL; existing = dns_zone_get(z, rr); @@ -386,7 +386,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns if (k < 0) return k; if (k > 0) { - r = dns_answer_add(answer, j->rr, 0); + r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; @@ -412,7 +412,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns if (j->state != DNS_ZONE_ITEM_PROBING) tentative = false; - r = dns_answer_add(answer, j->rr, 0); + r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; } @@ -471,15 +471,12 @@ return_empty: } void dns_zone_item_conflict(DnsZoneItem *i) { - _cleanup_free_ char *pretty = NULL; - assert(i); if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED)) return; - dns_resource_record_to_string(i->rr, &pretty); - log_info("Detected conflict on %s", strna(pretty)); + log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr))); dns_zone_item_probe_stop(i); @@ -492,8 +489,6 @@ void dns_zone_item_conflict(DnsZoneItem *i) { } void dns_zone_item_notify(DnsZoneItem *i) { - _cleanup_free_ char *pretty = NULL; - assert(i); assert(i->probe_transaction); @@ -530,15 +525,13 @@ void dns_zone_item_notify(DnsZoneItem *i) { log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost."); } - dns_resource_record_to_string(i->rr, &pretty); - log_debug("Record %s successfully probed.", strna(pretty)); + log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr))); dns_zone_item_probe_stop(i); i->state = DNS_ZONE_ITEM_ESTABLISHED; } static int dns_zone_item_verify(DnsZoneItem *i) { - _cleanup_free_ char *pretty = NULL; int r; assert(i); @@ -546,8 +539,7 @@ static int dns_zone_item_verify(DnsZoneItem *i) { if (i->state != DNS_ZONE_ITEM_ESTABLISHED) return 0; - dns_resource_record_to_string(i->rr, &pretty); - log_debug("Verifying RR %s", strna(pretty)); + log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr))); i->state = DNS_ZONE_ITEM_VERIFYING; r = dns_zone_item_probe_start(i); @@ -632,7 +624,6 @@ void dns_zone_verify_all(DnsZone *zone) { void dns_zone_dump(DnsZone *zone, FILE *f) { Iterator iterator; DnsZoneItem *i; - int r; if (!zone) return; @@ -644,10 +635,10 @@ void dns_zone_dump(DnsZone *zone, FILE *f) { DnsZoneItem *j; LIST_FOREACH(by_key, j, i) { - _cleanup_free_ char *t = NULL; + const char *t; - r = dns_resource_record_to_string(j->rr, &t); - if (r < 0) { + t = dns_resource_record_to_string(j->rr); + if (!t) { log_oom(); continue; } diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index c815eae850..c5ad04afd7 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -14,8 +14,9 @@ struct ConfigPerfItem; %struct-type %includes %% -Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 -Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 -Resolve.Domains, config_parse_search_domains, 0, 0 -Resolve.LLMNR, config_parse_support, 0, offsetof(Manager, llmnr_support) -Resolve.DNSSEC, config_parse_dnssec, 0, 0 +Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 +Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 +Resolve.Domains, config_parse_search_domains, 0, 0 +Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support) +Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support) +Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 84100bd988..30838ef8cc 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -46,7 +46,9 @@ int link_new(Manager *m, Link **ret, int ifindex) { return -ENOMEM; l->ifindex = ifindex; - l->llmnr_support = SUPPORT_YES; + l->llmnr_support = RESOLVE_SUPPORT_YES; + l->mdns_support = RESOLVE_SUPPORT_NO; + l->dnssec_mode = _DNSSEC_MODE_INVALID; r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); if (r < 0) @@ -65,7 +67,7 @@ Link *link_free(Link *l) { if (!l) return NULL; - dns_server_unlink_marked(l->dns_servers); + dns_server_unlink_all(l->dns_servers); dns_search_domain_unlink_all(l->search_domains); while (l->addresses) @@ -80,6 +82,8 @@ Link *link_free(Link *l) { dns_scope_free(l->mdns_ipv4_scope); dns_scope_free(l->mdns_ipv6_scope); + set_free_free(l->dnssec_negative_trust_anchors); + free(l); return NULL; } @@ -99,8 +103,8 @@ static void link_allocate_scopes(Link *l) { l->unicast_scope = dns_scope_free(l->unicast_scope); if (link_relevant(l, AF_INET) && - l->llmnr_support != SUPPORT_NO && - l->manager->llmnr_support != SUPPORT_NO) { + l->llmnr_support != RESOLVE_SUPPORT_NO && + l->manager->llmnr_support != RESOLVE_SUPPORT_NO) { if (!l->llmnr_ipv4_scope) { r = dns_scope_new(l->manager, &l->llmnr_ipv4_scope, l, DNS_PROTOCOL_LLMNR, AF_INET); if (r < 0) @@ -110,8 +114,8 @@ static void link_allocate_scopes(Link *l) { l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope); if (link_relevant(l, AF_INET6) && - l->llmnr_support != SUPPORT_NO && - l->manager->llmnr_support != SUPPORT_NO && + l->llmnr_support != RESOLVE_SUPPORT_NO && + l->manager->llmnr_support != RESOLVE_SUPPORT_NO && socket_ipv6_is_supported()) { if (!l->llmnr_ipv6_scope) { r = dns_scope_new(l->manager, &l->llmnr_ipv6_scope, l, DNS_PROTOCOL_LLMNR, AF_INET6); @@ -122,8 +126,8 @@ static void link_allocate_scopes(Link *l) { l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope); if (link_relevant(l, AF_INET) && - l->mdns_support != SUPPORT_NO && - l->manager->mdns_support != SUPPORT_NO) { + l->mdns_support != RESOLVE_SUPPORT_NO && + l->manager->mdns_support != RESOLVE_SUPPORT_NO) { if (!l->mdns_ipv4_scope) { r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET); if (r < 0) @@ -133,8 +137,8 @@ static void link_allocate_scopes(Link *l) { l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); if (link_relevant(l, AF_INET6) && - l->mdns_support != SUPPORT_NO && - l->manager->mdns_support != SUPPORT_NO) { + l->mdns_support != RESOLVE_SUPPORT_NO && + l->manager->mdns_support != RESOLVE_SUPPORT_NO) { if (!l->mdns_ipv6_scope) { r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6); if (r < 0) @@ -183,6 +187,10 @@ static int link_update_dns_servers(Link *l) { assert(l); r = sd_network_link_get_dns(l->ifindex, &nameservers); + if (r == -ENODATA) { + r = 0; + goto clear; + } if (r < 0) goto clear; @@ -222,25 +230,114 @@ static int link_update_llmnr_support(Link *l) { assert(l); r = sd_network_link_get_llmnr(l->ifindex, &b); + if (r == -ENODATA) { + r = 0; + goto clear; + } if (r < 0) goto clear; - r = parse_boolean(b); - if (r < 0) { - if (streq(b, "resolve")) - l->llmnr_support = SUPPORT_RESOLVE; - else - goto clear; + l->llmnr_support = resolve_support_from_string(b); + if (l->llmnr_support < 0) { + r = -EINVAL; + goto clear; + } - } else if (r > 0) - l->llmnr_support = SUPPORT_YES; - else - l->llmnr_support = SUPPORT_NO; + return 0; + +clear: + l->llmnr_support = RESOLVE_SUPPORT_YES; + return r; +} + +static int link_update_mdns_support(Link *l) { + _cleanup_free_ char *b = NULL; + int r; + + assert(l); + + r = sd_network_link_get_mdns(l->ifindex, &b); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + l->mdns_support = resolve_support_from_string(b); + if (l->mdns_support < 0) { + r = -EINVAL; + goto clear; + } return 0; clear: - l->llmnr_support = SUPPORT_YES; + l->mdns_support = RESOLVE_SUPPORT_NO; + return r; +} + +static int link_update_dnssec_mode(Link *l) { + _cleanup_free_ char *m = NULL; + int r; + + assert(l); + + r = sd_network_link_get_dnssec(l->ifindex, &m); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + l->dnssec_mode = dnssec_mode_from_string(m); + if (l->dnssec_mode < 0) { + r = -EINVAL; + goto clear; + } + + return 0; + +clear: + l->dnssec_mode = _DNSSEC_MODE_INVALID; + return r; +} + +static int link_update_dnssec_negative_trust_anchors(Link *l) { + _cleanup_strv_free_ char **ntas = NULL; + _cleanup_set_free_free_ Set *ns = NULL; + char **i; + int r; + + assert(l); + + r = sd_network_link_get_dnssec_negative_trust_anchors(l->ifindex, &ntas); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + ns = set_new(&dns_name_hash_ops); + if (!ns) + return -ENOMEM; + + STRV_FOREACH(i, ntas) { + r = set_put_strdup(ns, *i); + if (r < 0) + return r; + } + + set_free_free(l->dnssec_negative_trust_anchors); + l->dnssec_negative_trust_anchors = ns; + ns = NULL; + + return 0; + +clear: + l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); return r; } @@ -252,6 +349,11 @@ static int link_update_search_domains(Link *l) { assert(l); r = sd_network_link_get_domains(l->ifindex, &domains); + if (r == -ENODATA) { + /* networkd knows nothing about this interface, and that's fine. */ + r = 0; + goto clear; + } if (r < 0) goto clear; @@ -286,14 +388,31 @@ int link_update_monitor(Link *l) { assert(l); - link_update_dns_servers(l); - link_update_llmnr_support(l); - link_allocate_scopes(l); + r = link_update_dns_servers(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name); + + r = link_update_llmnr_support(l); + if (r < 0) + log_warning_errno(r, "Failed to read LLMNR support for interface %s, ignoring: %m", l->name); + + r = link_update_mdns_support(l); + if (r < 0) + log_warning_errno(r, "Failed to read mDNS support for interface %s, ignoring: %m", l->name); + + r = link_update_dnssec_mode(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNSSEC mode for interface %s, ignoring: %m", l->name); + + r = link_update_dnssec_negative_trust_anchors(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNSSEC negative trust anchors for interface %s, ignoring: %m", l->name); r = link_update_search_domains(l); if (r < 0) log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name); + link_allocate_scopes(l); link_add_rrs(l, false); return 0; @@ -446,8 +565,8 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) { if (!force_remove && link_address_relevant(a) && a->link->llmnr_ipv4_scope && - a->link->llmnr_support == SUPPORT_YES && - a->link->manager->llmnr_support == SUPPORT_YES) { + a->link->llmnr_support == RESOLVE_SUPPORT_YES && + a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { if (!a->link->manager->llmnr_host_ipv4_key) { a->link->manager->llmnr_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->llmnr_hostname); @@ -503,8 +622,8 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) { if (!force_remove && link_address_relevant(a) && a->link->llmnr_ipv6_scope && - a->link->llmnr_support == SUPPORT_YES && - a->link->manager->llmnr_support == SUPPORT_YES) { + a->link->llmnr_support == RESOLVE_SUPPORT_YES && + a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { if (!a->link->manager->llmnr_host_ipv6_key) { a->link->manager->llmnr_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->llmnr_hostname); diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index a3b406bbc2..db0e51da04 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -25,6 +25,7 @@ #include "in-addr-util.h" #include "ratelimit.h" +#include "resolve-util.h" typedef struct Link Link; typedef struct LinkAddress LinkAddress; @@ -66,8 +67,10 @@ struct Link { LIST_HEAD(DnsSearchDomain, search_domains); unsigned n_search_domains; - Support llmnr_support; - Support mdns_support; + ResolveSupport llmnr_support; + ResolveSupport mdns_support; + DnssecMode dnssec_mode; + Set *dnssec_negative_trust_anchors; DnsScope *unicast_scope; DnsScope *llmnr_ipv4_scope; diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c index ed754c3899..dd4d9508ba 100644 --- a/src/resolve/resolved-llmnr.c +++ b/src/resolve/resolved-llmnr.c @@ -47,7 +47,7 @@ int manager_llmnr_start(Manager *m) { assert(m); - if (m->llmnr_support == SUPPORT_NO) + if (m->llmnr_support == RESOLVE_SUPPORT_NO) return 0; r = manager_llmnr_ipv4_udp_fd(m); @@ -80,7 +80,7 @@ int manager_llmnr_start(Manager *m) { eaddrinuse: log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support."); - m->llmnr_support = SUPPORT_NO; + m->llmnr_support = RESOLVE_SUPPORT_NO; manager_llmnr_stop(m); return 0; @@ -117,7 +117,7 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u dns_scope_process_query(scope, NULL, p); } else - log_debug("Invalid LLMNR UDP packet."); + log_debug("Invalid LLMNR UDP packet, ignoring."); return 0; } diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index a2677f442a..b32bad456b 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -476,7 +476,9 @@ int manager_new(Manager **ret) { m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1; m->hostname_fd = -1; - m->llmnr_support = SUPPORT_YES; + m->llmnr_support = RESOLVE_SUPPORT_YES; + m->mdns_support = RESOLVE_SUPPORT_NO; + m->dnssec_mode = DNSSEC_NO; m->read_resolv_conf = true; m->need_builtin_fallbacks = true; @@ -484,6 +486,10 @@ int manager_new(Manager **ret) { if (r < 0) return r; + r = manager_parse_config_file(m); + if (r < 0) + return r; + r = sd_event_default(&m->event); if (r < 0) return r; @@ -772,7 +778,7 @@ static int write_loop(int fd, void *message, size_t length) { int manager_write(Manager *m, int fd, DnsPacket *p) { int r; - log_debug("Sending %s packet with id %u", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p)); + log_debug("Sending %s packet with id %" PRIu16 ".", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p)); r = write_loop(fd, DNS_PACKET_DATA(p), p->size); if (r < 0) @@ -887,7 +893,7 @@ int manager_send(Manager *m, int fd, int ifindex, int family, const union in_add assert(port > 0); assert(p); - log_debug("Sending %s packet with id %u on interface %i/%s", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family)); + log_debug("Sending %s packet with id %" PRIu16 " on interface %i/%s.", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family)); if (family == AF_INET) return manager_ipv4_send(m, fd, ifindex, &addr->in, port, p); @@ -1163,10 +1169,3 @@ int manager_compile_search_domains(Manager *m, OrderedSet **domains) { return 0; } - -static const char* const support_table[_SUPPORT_MAX] = { - [SUPPORT_NO] = "no", - [SUPPORT_YES] = "yes", - [SUPPORT_RESOLVE] = "resolve", -}; -DEFINE_STRING_TABLE_LOOKUP(support, Support); diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index b52273403a..1907d2e1bc 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -28,17 +28,9 @@ #include "hashmap.h" #include "list.h" #include "ordered-set.h" +#include "resolve-util.h" typedef struct Manager Manager; -typedef enum Support Support; - -enum Support { - SUPPORT_NO, - SUPPORT_YES, - SUPPORT_RESOLVE, - _SUPPORT_MAX, - _SUPPORT_INVALID = -1 -}; #include "resolved-dns-query.h" #include "resolved-dns-search-domain.h" @@ -53,8 +45,9 @@ enum Support { struct Manager { sd_event *event; - Support llmnr_support; - Support mdns_support; + ResolveSupport llmnr_support; + ResolveSupport mdns_support; + DnssecMode dnssec_mode; /* Network */ Hashmap *links; @@ -128,6 +121,9 @@ struct Manager { sd_bus_slot *prepare_for_sleep_slot; sd_event_source *sigusr1_event_source; + + unsigned n_transactions_total; + unsigned n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; }; /* Manager */ @@ -162,6 +158,3 @@ int manager_is_own_hostname(Manager *m, const char *name); int manager_compile_dns_servers(Manager *m, OrderedSet **servers); int manager_compile_search_domains(Manager *m, OrderedSet **domains); - -const char* support_to_string(Support p) _const_; -int support_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index d6973a6999..d5b253d4f5 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -42,7 +42,7 @@ int manager_mdns_start(Manager *m) { assert(m); - if (m->mdns_support == SUPPORT_NO) + if (m->mdns_support == RESOLVE_SUPPORT_NO) return 0; r = manager_mdns_ipv4_fd(m); @@ -63,7 +63,7 @@ int manager_mdns_start(Manager *m) { eaddrinuse: log_warning("There appears to be another mDNS responder running. Turning off mDNS support."); - m->mdns_support = SUPPORT_NO; + m->mdns_support = RESOLVE_SUPPORT_NO; manager_mdns_stop(m); return 0; @@ -122,8 +122,7 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us dns_transaction_process_reply(t, p); } - dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, - p->answer->n_rrs, false, 0, p->family, &p->sender); + dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); } else if (dns_packet_validate_query(p) > 0) { log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index be406b71fe..472bb32764 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -81,12 +81,6 @@ int main(int argc, char *argv[]) { goto finish; } - r = manager_parse_config_file(m); - if (r < 0) { - log_error_errno(r, "Failed to parse configuration file: %m"); - goto finish; - } - r = manager_start(m); if (r < 0) { log_error_errno(r, "Failed to start manager: %m"); diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in index efc9c6733a..0ba572d113 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -16,4 +16,5 @@ #FallbackDNS=@DNS_SERVERS@ #Domains= #LLMNR=yes +#MulticastDNS=no #DNSSEC=no diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c index a2118513f1..0c9efde1fe 100644 --- a/src/resolve/test-dnssec.c +++ b/src/resolve/test-dnssec.c @@ -27,6 +27,99 @@ #include "resolved-dns-dnssec.h" #include "resolved-dns-rr.h" #include "string-util.h" +#include "hexdecoct.h" + +static void test_dnssec_verify_rrset2(void) { + + static const uint8_t signature_blob[] = { + 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11, + 0x7d, 0xf1, 0xe6, 0x87, 0xb9, 0x42, 0xd3, 0x8b, 0x9e, 0xaf, 0x92, 0x31, 0x0a, 0x53, 0xad, 0x8b, + 0xa7, 0x5c, 0x83, 0x39, 0x8c, 0x28, 0xac, 0xce, 0x6e, 0x9c, 0x18, 0xe3, 0x31, 0x16, 0x6e, 0xca, + 0x38, 0x31, 0xaf, 0xd9, 0x94, 0xf1, 0x84, 0xb1, 0xdf, 0x5a, 0xc2, 0x73, 0x22, 0xf6, 0xcb, 0xa2, + 0xe7, 0x8c, 0x77, 0x0c, 0x74, 0x2f, 0xc2, 0x13, 0xb0, 0x93, 0x51, 0xa9, 0x4f, 0xae, 0x0a, 0xda, + 0x45, 0xcc, 0xfd, 0x43, 0x99, 0x36, 0x9a, 0x0d, 0x21, 0xe0, 0xeb, 0x30, 0x65, 0xd4, 0xa0, 0x27, + 0x37, 0x3b, 0xe4, 0xc1, 0xc5, 0xa1, 0x2a, 0xd1, 0x76, 0xc4, 0x7e, 0x64, 0x0e, 0x5a, 0xa6, 0x50, + 0x24, 0xd5, 0x2c, 0xcc, 0x6d, 0xe5, 0x37, 0xea, 0xbd, 0x09, 0x34, 0xed, 0x24, 0x06, 0xa1, 0x22, + }; + + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0xc3, 0x7f, 0x1d, 0xd1, 0x1c, 0x97, 0xb1, 0x13, 0x34, 0x3a, 0x9a, 0xea, + 0xee, 0xd9, 0x5a, 0x11, 0x1b, 0x17, 0xc7, 0xe3, 0xd4, 0xda, 0x20, 0xbc, 0x5d, 0xba, 0x74, 0xe3, + 0x37, 0x99, 0xec, 0x25, 0xce, 0x93, 0x7f, 0xbd, 0x22, 0x73, 0x7e, 0x14, 0x71, 0xe0, 0x60, 0x07, + 0xd4, 0x39, 0x8b, 0x5e, 0xe9, 0xba, 0x25, 0xe8, 0x49, 0xe9, 0x34, 0xef, 0xfe, 0x04, 0x5c, 0xa5, + 0x27, 0xcd, 0xa9, 0xda, 0x70, 0x05, 0x21, 0xab, 0x15, 0x82, 0x24, 0xc3, 0x94, 0xf5, 0xd7, 0xb7, + 0xc4, 0x66, 0xcb, 0x32, 0x6e, 0x60, 0x2b, 0x55, 0x59, 0x28, 0x89, 0x8a, 0x72, 0xde, 0x88, 0x56, + 0x27, 0x95, 0xd9, 0xac, 0x88, 0x4f, 0x65, 0x2b, 0x68, 0xfc, 0xe6, 0x41, 0xc1, 0x1b, 0xef, 0x4e, + 0xd6, 0xc2, 0x0f, 0x64, 0x88, 0x95, 0x5e, 0xdd, 0x3a, 0x02, 0x07, 0x50, 0xa9, 0xda, 0xa4, 0x49, + 0x74, 0x62, 0xfe, 0xd7, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnssecResult result; + + nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov"); + assert_se(nsec); + + nsec->nsec.next_domain_name = strdup("3D-Printing.nasa.gov"); + assert_se(nsec->nsec.next_domain_name); + + nsec->nsec.types = bitmap_new(); + assert_se(nsec->nsec.types); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_A) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NS) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_SOA) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_MX) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_TXT) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_RRSIG) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NSEC) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0); + assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0); + + log_info("NSEC: %s", strna(dns_resource_record_to_string(nsec))); + + rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); + assert_se(rrsig); + + rrsig->rrsig.type_covered = DNS_TYPE_NSEC; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rrsig->rrsig.labels = 2; + rrsig->rrsig.original_ttl = 300; + rrsig->rrsig.expiration = 0x5689002f; + rrsig->rrsig.inception = 0x56617230; + rrsig->rrsig.key_tag = 30390; + rrsig->rrsig.signer = strdup("Nasa.Gov."); + assert_se(rrsig->rrsig.signer); + rrsig->rrsig.signature_size = sizeof(signature_blob); + rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); + assert_se(rrsig->rrsig.signature); + + log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 256; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); + + assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); + + answer = dns_answer_new(1); + assert_se(answer); + assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED) >= 0); + + /* Validate the RR as it if was 2015-12-11 today */ + assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0); + assert_se(result == DNSSEC_VALIDATED); +} static void test_dnssec_verify_rrset(void) { @@ -55,7 +148,6 @@ static void test_dnssec_verify_rrset(void) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL; DnssecResult result; a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov"); @@ -63,8 +155,7 @@ static void test_dnssec_verify_rrset(void) { a->a.in_addr.s_addr = inet_addr("52.0.14.116"); - assert_se(dns_resource_record_to_string(a, &x) >= 0); - log_info("A: %s", x); + log_info("A: %s", strna(dns_resource_record_to_string(a))); rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); assert_se(rrsig); @@ -82,8 +173,7 @@ static void test_dnssec_verify_rrset(void) { rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); assert_se(rrsig->rrsig.signature); - assert_se(dns_resource_record_to_string(rrsig, &y) >= 0); - log_info("RRSIG: %s", y); + log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); assert_se(dnskey); @@ -95,16 +185,15 @@ static void test_dnssec_verify_rrset(void) { dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); assert_se(dnskey->dnskey.key); - assert_se(dns_resource_record_to_string(dnskey, &z) >= 0); - log_info("DNSKEY: %s", z); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey)); + log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0); - assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); answer = dns_answer_new(1); assert_se(answer); - assert_se(dns_answer_add(answer, a, 0) >= 0); + assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED) >= 0); /* Validate the RR as it if was 2015-12-2 today */ assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0); @@ -142,7 +231,6 @@ static void test_dnssec_verify_dns_key(void) { }; _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = NULL; - _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL; /* The two DS RRs in effect for nasa.gov on 2015-12-01. */ ds1 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "nasa.gov"); @@ -155,8 +243,7 @@ static void test_dnssec_verify_dns_key(void) { ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size); assert_se(ds1->ds.digest); - assert_se(dns_resource_record_to_string(ds1, &a) >= 0); - log_info("DS1: %s", a); + log_info("DS1: %s", strna(dns_resource_record_to_string(ds1))); ds2 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "NASA.GOV"); assert_se(ds2); @@ -168,8 +255,7 @@ static void test_dnssec_verify_dns_key(void) { ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size); assert_se(ds2->ds.digest); - assert_se(dns_resource_record_to_string(ds2, &b) >= 0); - log_info("DS2: %s", b); + log_info("DS2: %s", strna(dns_resource_record_to_string(ds2))); dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nasa.GOV"); assert_se(dnskey); @@ -181,12 +267,11 @@ static void test_dnssec_verify_dns_key(void) { dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); assert_se(dnskey->dnskey.key); - assert_se(dns_resource_record_to_string(dnskey, &c) >= 0); - log_info("DNSKEY: %s", c); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey)); + log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - assert_se(dnssec_verify_dnskey(dnskey, ds1) > 0); - assert_se(dnssec_verify_dnskey(dnskey, ds2) > 0); + assert_se(dnssec_verify_dnskey(dnskey, ds1, false) > 0); + assert_se(dnssec_verify_dnskey(dnskey, ds2, false) > 0); } static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) { @@ -209,11 +294,45 @@ static void test_dnssec_canonicalize(void) { test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL); } +static void test_dnssec_nsec3_hash(void) { + static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE }; + static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 }; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + uint8_t h[DNSSEC_HASH_SIZE_MAX]; + _cleanup_free_ char *b = NULL; + int k; + + /* The NSEC3 RR for eurid.eu on 2015-12-14. */ + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC3, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM.eurid.eu."); + assert_se(rr); + + rr->nsec3.algorithm = DNSSEC_DIGEST_SHA1; + rr->nsec3.flags = 1; + rr->nsec3.iterations = 1; + rr->nsec3.salt = memdup(salt, sizeof(salt)); + assert_se(rr->nsec3.salt); + rr->nsec3.salt_size = sizeof(salt); + rr->nsec3.next_hashed_name = memdup(next_hashed_name, sizeof(next_hashed_name)); + assert_se(rr->nsec3.next_hashed_name); + rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name); + + log_info("NSEC3: %s", strna(dns_resource_record_to_string(rr))); + + k = dnssec_nsec3_hash(rr, "eurid.eu", &h); + assert_se(k >= 0); + + b = base32hexmem(h, k, false); + assert_se(b); + assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0); +} + int main(int argc, char*argv[]) { test_dnssec_canonicalize(); test_dnssec_verify_dns_key(); test_dnssec_verify_rrset(); + test_dnssec_verify_rrset2(); + test_dnssec_nsec3_hash(); return 0; } diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 0466857042..68404ca9e5 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -48,7 +48,6 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { assert(name); assert(*name); - assert(dest); n = *name; d = dest; @@ -79,9 +78,12 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { else if (*n == '\\' || *n == '.') { /* Escaped backslash or dot */ - *(d++) = *(n++); + + if (d) + *(d++) = *n; sz--; r++; + n++; } else if (n[0] >= '0' && n[0] <= '9') { unsigned k; @@ -96,11 +98,17 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { ((unsigned) (n[1] - '0') * 10) + ((unsigned) (n[2] - '0')); - /* Don't allow CC characters or anything that doesn't fit in 8bit */ - if (k < ' ' || k > 255 || k == 127) + /* Don't allow anything that doesn't + * fit in 8bit. Note that we do allow + * control characters, as some servers + * (e.g. cloudflare) are happy to + * generate labels with them + * inside. */ + if (k > 255) return -EINVAL; - *(d++) = (char) k; + if (d) + *(d++) = (char) k; sz--; r++; @@ -111,9 +119,12 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) { /* Normal character */ - *(d++) = *(n++); + + if (d) + *(d++) = *n; sz--; r++; + n++; } else return -EINVAL; } @@ -122,7 +133,7 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { if (r == 0 && *n) return -EINVAL; - if (sz >= 1) + if (sz >= 1 && d) *d = 0; *name = n; @@ -148,20 +159,24 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha return 0; } - assert(**label_terminal == '.' || **label_terminal == 0); + terminal = *label_terminal; + assert(*terminal == '.' || *terminal == 0); - /* skip current terminal character */ - terminal = *label_terminal - 1; + /* Skip current terminal character (and accept domain names ending it ".") */ + if (*terminal == 0) + terminal--; + if (terminal >= name && *terminal == '.') + terminal--; - /* point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ + /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ for (;;) { if (terminal < name) { - /* reached the first label, so indicate that there are no more */ + /* Reached the first label, so indicate that there are no more */ terminal = NULL; break; } - /* find the start of the last label */ + /* Find the start of the last label */ if (*terminal == '.') { const char *y; unsigned slashes = 0; @@ -170,7 +185,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha slashes ++; if (slashes % 2 == 0) { - /* the '.' was not escaped */ + /* The '.' was not escaped */ name = terminal + 1; break; } else { @@ -235,7 +250,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { *(q++) = *p; sz -= 1; - } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) { + } else { /* Everything else */ @@ -249,8 +264,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { sz -= 4; - } else - return -EINVAL; + } p++; l--; @@ -527,7 +541,7 @@ int dns_name_compare_func(const void *a, const void *b) { if (k > 0) r = k; if (w > 0) - r = w; + q = w; la[r] = lb[q] = 0; r = strcasecmp(la, lb); @@ -1153,3 +1167,77 @@ finish: return 0; } + +int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { + const char* labels[DNS_N_LABELS_MAX+1]; + unsigned n = 0; + const char *p; + int r; + + assert(name); + assert(ret); + + p = name; + for (;;) { + if (n > DNS_N_LABELS_MAX) + return -EINVAL; + + labels[n] = p; + + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + + n++; + } + + if (n < n_labels) + return -EINVAL; + + *ret = labels[n - n_labels]; + return (int) (n - n_labels); +} + +int dns_name_count_labels(const char *name) { + unsigned n = 0; + const char *p; + int r; + + assert(name); + + p = name; + for (;;) { + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + + if (n >= DNS_N_LABELS_MAX) + return -EINVAL; + + n++; + } + + return (int) n; +} + +int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) { + int r; + + assert(a); + assert(b); + + while (n_labels > 0) { + + r = dns_name_parent(&a); + if (r <= 0) + return r; + + n_labels --; + } + + return dns_name_equal(a, b); +} diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index 3f8f621802..dd8ae3ac98 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -42,11 +42,18 @@ /* Maximum length of a full hostname, on the wire, including the final NUL byte */ #define DNS_WIRE_FOMAT_HOSTNAME_MAX 255 +/* Maximum number of labels per valid hostname */ +#define DNS_N_LABELS_MAX 127 + int dns_label_unescape(const char **name, char *dest, size_t sz); int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz); int dns_label_escape(const char *p, size_t l, char *dest, size_t sz); int dns_label_escape_new(const char *p, size_t l, char **ret); +static inline int dns_name_parent(const char **name) { + return dns_label_unescape(name, NULL, DNS_LABEL_MAX); +} + int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); @@ -92,3 +99,8 @@ bool dns_service_name_is_valid(const char *name); int dns_service_join(const char *name, const char *type, const char *domain, char **ret); int dns_service_split(const char *joined, char **name, char **type, char **domain); + +int dns_name_suffix(const char *name, unsigned n_labels, const char **ret); +int dns_name_count_labels(const char *name); + +int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b); diff --git a/src/shared/generator.c b/src/shared/generator.c index 37de3f7cb1..76808cbdd5 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -190,7 +190,6 @@ int generator_write_timeouts( return write_drop_in_format(dir, unit, 50, "device-timeout", "# Automatically generated by %s\n\n" - "[Unit]\nJobTimeoutSec=" USEC_FMT, - program_invocation_short_name, - u / USEC_PER_SEC); + "[Unit]\nJobTimeoutSec=%s", + program_invocation_short_name, timeout); } diff --git a/src/shared/gpt.h b/src/shared/gpt.h index add1df420f..5f4c00ba83 100644 --- a/src/shared/gpt.h +++ b/src/shared/gpt.h @@ -25,7 +25,7 @@ #include "sd-id128.h" -/* We only support root disk discovery for x86, x86-64 and ARM for +/* We only support root disk discovery for x86, x86-64, Itanium and ARM for * now, since EFI for anything else doesn't really exist, and we only * care for root partitions on the same disk as the EFI ESP. */ @@ -33,6 +33,7 @@ #define GPT_ROOT_X86_64 SD_ID128_MAKE(4f,68,bc,e3,e8,cd,4d,b1,96,e7,fb,ca,f9,84,b7,09) #define GPT_ROOT_ARM SD_ID128_MAKE(69,da,d7,10,2c,e4,4e,3c,b1,6c,21,a1,d4,9a,be,d3) #define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae) +#define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97) #define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b) #define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f) @@ -46,6 +47,10 @@ # define GPT_ROOT_NATIVE GPT_ROOT_X86 #endif +#if defined(__ia64__) +# define GPT_ROOT_NATIVE GPT_ROOT_IA64 +#endif + #if defined(__aarch64__) && (__BYTE_ORDER != __BIG_ENDIAN) # define GPT_ROOT_NATIVE GPT_ROOT_ARM_64 # define GPT_ROOT_SECONDARY GPT_ROOT_ARM diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 193dad1943..a1f65d1a88 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -435,8 +435,9 @@ static int output_verbose( r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &value, &size); if (r < 0) - log_debug_errno(r, "_SOURCE_REALTIME_TIMESTAMP invalid: %m"); + return r; else { + assert(r > 0); r = safe_atou64(value, &realtime); if (r < 0) log_debug_errno(r, "Failed to parse realtime timestamp: %m"); diff --git a/src/shared/resolve-util.c b/src/shared/resolve-util.c new file mode 100644 index 0000000000..bf6fc26841 --- /dev/null +++ b/src/shared/resolve-util.c @@ -0,0 +1,41 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "conf-parser.h" +#include "resolve-util.h" +#include "string-table.h" + +DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_support, resolve_support, ResolveSupport, "Failed to parse resolve support setting"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_dnssec_mode, dnssec_mode, DnssecMode, "Failed to parse DNSSEC mode setting"); + +static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = { + [RESOLVE_SUPPORT_NO] = "no", + [RESOLVE_SUPPORT_YES] = "yes", + [RESOLVE_SUPPORT_RESOLVE] = "resolve", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolve_support, ResolveSupport, RESOLVE_SUPPORT_YES); + +static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = { + [DNSSEC_NO] = "no", + [DNSSEC_ALLOW_DOWNGRADE] = "allow-downgrade", + [DNSSEC_YES] = "yes", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dnssec_mode, DnssecMode, DNSSEC_YES); diff --git a/src/shared/resolve-util.h b/src/shared/resolve-util.h new file mode 100644 index 0000000000..fd93a13f73 --- /dev/null +++ b/src/shared/resolve-util.h @@ -0,0 +1,62 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "macro.h" + +typedef enum ResolveSupport ResolveSupport; +typedef enum DnssecMode DnssecMode; + +enum ResolveSupport { + RESOLVE_SUPPORT_NO, + RESOLVE_SUPPORT_YES, + RESOLVE_SUPPORT_RESOLVE, + _RESOLVE_SUPPORT_MAX, + _RESOLVE_SUPPORT_INVALID = -1 +}; + +enum DnssecMode { + /* No DNSSEC validation is done */ + DNSSEC_NO, + + /* Validate locally, if the server knows DO, but if not, + * don't. Don't trust the AD bit. If the server doesn't do + * DNSSEC properly, downgrade to non-DNSSEC operation. Of + * course, we then are vulnerable to a downgrade attack, but + * that's life and what is configured. */ + DNSSEC_ALLOW_DOWNGRADE, + + /* Insist on DNSSEC server support, and rather fail than downgrading. */ + DNSSEC_YES, + + _DNSSEC_MODE_MAX, + _DNSSEC_MODE_INVALID = -1 +}; + +int config_parse_resolve_support(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_dnssec_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); + +const char* resolve_support_to_string(ResolveSupport p) _const_; +ResolveSupport resolve_support_from_string(const char *s) _pure_; + +const char* dnssec_mode_to_string(DnssecMode p) _const_; +DnssecMode dnssec_mode_from_string(const char *s) _pure_; diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index 072832a916..1183df6105 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -86,6 +86,9 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_BOOTCHART SD_ID128_MAKE(9f,26,aa,56,2c,f4,40,c2,b1,6c,77,3d,04,79,b5,18) +#define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d) +#define SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED SD_ID128_MAKE(4d,44,08,cf,d0,d1,44,85,91,84,d1,e6,5d,7c,8a,65) + _SD_END_DECLARATIONS; #endif diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h index 79b4bf9ea3..653c61a162 100644 --- a/src/systemd/sd-network.h +++ b/src/systemd/sd-network.h @@ -111,6 +111,27 @@ int sd_network_link_get_ntp(int ifindex, char ***addr); */ int sd_network_link_get_llmnr(int ifindex, char **llmnr); +/* Indicates whether or not MulticastDNS should be enabled for the + * link. + * Possible levels of support: yes, no, resolve + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_mdns(int ifindex, char **mdns); + +/* Indicates whether or not DNSSEC should be enabled for the link + * Possible levels of support: yes, no, allow-downgrade + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_dnssec(int ifindex, char **dnssec); + +/* Returns the list of per-interface DNSSEC negative trust anchors + * Possible return codes: + * -ENODATA: networkd is not aware of the link, or has no such data + */ +int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta); + int sd_network_link_get_lldp(int ifindex, char **lldp); /* Get the DNS domain names for a given link. */ diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index de003e251c..6c3c49908f 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -140,9 +140,9 @@ static void test_dns_label_unescape_suffix(void) { test_dns_label_unescape_suffix_one("hallo\\", "hallo", "hallo", 20, -EINVAL, -EINVAL); test_dns_label_unescape_suffix_one("hallo\\032 ", "hallo ", "", 20, 7, 0); test_dns_label_unescape_suffix_one(".", "", "", 20, 0, 0); - test_dns_label_unescape_suffix_one("..", "", "", 20, 0, 0); + test_dns_label_unescape_suffix_one("..", "", "", 20, 0, -EINVAL); test_dns_label_unescape_suffix_one(".foobar", "foobar", "", 20, 6, -EINVAL); - test_dns_label_unescape_suffix_one("foobar.", "", "foobar", 20, 0, 6); + test_dns_label_unescape_suffix_one("foobar.", "foobar", "", 20, 6, 0); test_dns_label_unescape_suffix_one("foo\\\\bar", "foo\\bar", "", 20, 7, 0); test_dns_label_unescape_suffix_one("foo.bar", "bar", "foo", 20, 3, 3); test_dns_label_unescape_suffix_one("foo..bar", "bar", "", 20, 3, -EINVAL); @@ -168,7 +168,7 @@ static void test_dns_label_escape_one(const char *what, size_t l, const char *ex static void test_dns_label_escape(void) { test_dns_label_escape_one("", 0, NULL, -EINVAL); test_dns_label_escape_one("hallo", 5, "hallo", 5); - test_dns_label_escape_one("hallo", 6, NULL, -EINVAL); + test_dns_label_escape_one("hallo", 6, "hallo\\000", 9); test_dns_label_escape_one("hallo hallo.foobar,waldi", 24, "hallo\\032hallo\\.foobar\\044waldi", 31); } @@ -190,7 +190,7 @@ static void test_dns_name_normalize(void) { test_dns_name_normalize_one("f", "f", 0); test_dns_name_normalize_one("f.waldi", "f.waldi", 0); test_dns_name_normalize_one("f \\032.waldi", "f\\032\\032.waldi", 0); - test_dns_name_normalize_one("\\000", NULL, -EINVAL); + test_dns_name_normalize_one("\\000", "\\000", 0); test_dns_name_normalize_one("..", NULL, -EINVAL); test_dns_name_normalize_one(".foobar", NULL, -EINVAL); test_dns_name_normalize_one("foobar.", "foobar", 0); @@ -216,7 +216,7 @@ static void test_dns_name_equal(void) { test_dns_name_equal_one("abc.def", "CBA.def", false); test_dns_name_equal_one("", "xxx", false); test_dns_name_equal_one("ab", "a", false); - test_dns_name_equal_one("\\000", "xxxx", -EINVAL); + test_dns_name_equal_one("\\000", "\\000", true); test_dns_name_equal_one(".", "", true); test_dns_name_equal_one(".", ".", true); test_dns_name_equal_one("..", "..", -EINVAL); @@ -475,6 +475,90 @@ static void test_dns_name_change_suffix(void) { test_dns_name_change_suffix_one("a", "b", "c", 0, NULL); } +static void test_dns_name_suffix_one(const char *name, unsigned n_labels, const char *result, int ret) { + const char *p = NULL; + + assert_se(ret == dns_name_suffix(name, n_labels, &p)); + assert_se(streq_ptr(p, result)); +} + +static void test_dns_name_suffix(void) { + test_dns_name_suffix_one("foo.bar", 2, "foo.bar", 0); + test_dns_name_suffix_one("foo.bar", 1, "bar", 1); + test_dns_name_suffix_one("foo.bar", 0, "", 2); + test_dns_name_suffix_one("foo.bar", 3, NULL, -EINVAL); + test_dns_name_suffix_one("foo.bar", 4, NULL, -EINVAL); + + test_dns_name_suffix_one("bar", 1, "bar", 0); + test_dns_name_suffix_one("bar", 0, "", 1); + test_dns_name_suffix_one("bar", 2, NULL, -EINVAL); + test_dns_name_suffix_one("bar", 3, NULL, -EINVAL); + + test_dns_name_suffix_one("", 0, "", 0); + test_dns_name_suffix_one("", 1, NULL, -EINVAL); + test_dns_name_suffix_one("", 2, NULL, -EINVAL); +} + +static void test_dns_name_count_labels_one(const char *name, int n) { + assert_se(dns_name_count_labels(name) == n); +} + +static void test_dns_name_count_labels(void) { + test_dns_name_count_labels_one("foo.bar.quux.", 3); + test_dns_name_count_labels_one("foo.bar.quux", 3); + test_dns_name_count_labels_one("foo.bar.", 2); + test_dns_name_count_labels_one("foo.bar", 2); + test_dns_name_count_labels_one("foo.", 1); + test_dns_name_count_labels_one("foo", 1); + test_dns_name_count_labels_one("", 0); + test_dns_name_count_labels_one(".", 0); + test_dns_name_count_labels_one("..", -EINVAL); +} + +static void test_dns_name_equal_skip_one(const char *a, unsigned n_labels, const char *b, int ret) { + assert_se(dns_name_equal_skip(a, n_labels, b) == ret); +} + +static void test_dns_name_equal_skip(void) { + test_dns_name_equal_skip_one("foo", 0, "bar", 0); + test_dns_name_equal_skip_one("foo", 0, "foo", 1); + test_dns_name_equal_skip_one("foo", 1, "foo", 0); + test_dns_name_equal_skip_one("foo", 2, "foo", 0); + + test_dns_name_equal_skip_one("foo.bar", 0, "foo.bar", 1); + test_dns_name_equal_skip_one("foo.bar", 1, "foo.bar", 0); + test_dns_name_equal_skip_one("foo.bar", 2, "foo.bar", 0); + test_dns_name_equal_skip_one("foo.bar", 3, "foo.bar", 0); + + test_dns_name_equal_skip_one("foo.bar", 0, "bar", 0); + test_dns_name_equal_skip_one("foo.bar", 1, "bar", 1); + test_dns_name_equal_skip_one("foo.bar", 2, "bar", 0); + test_dns_name_equal_skip_one("foo.bar", 3, "bar", 0); + + test_dns_name_equal_skip_one("foo.bar", 0, "", 0); + test_dns_name_equal_skip_one("foo.bar", 1, "", 0); + test_dns_name_equal_skip_one("foo.bar", 2, "", 1); + test_dns_name_equal_skip_one("foo.bar", 3, "", 0); + + test_dns_name_equal_skip_one("", 0, "", 1); + test_dns_name_equal_skip_one("", 1, "", 0); + test_dns_name_equal_skip_one("", 1, "foo", 0); + test_dns_name_equal_skip_one("", 2, "foo", 0); +} + +static void test_dns_name_compare_func(void) { + assert_se(dns_name_compare_func("", "") == 0); + assert_se(dns_name_compare_func("", ".") == 0); + assert_se(dns_name_compare_func(".", "") == 0); + assert_se(dns_name_compare_func("foo", "foo.") == 0); + assert_se(dns_name_compare_func("foo.", "foo") == 0); + assert_se(dns_name_compare_func("foo", "foo") == 0); + assert_se(dns_name_compare_func("foo.", "foo.") == 0); + assert_se(dns_name_compare_func("heise.de", "HEISE.DE.") == 0); + + assert_se(dns_name_compare_func("de.", "heise.de") != 0); +} + int main(int argc, char *argv[]) { test_dns_label_unescape(); @@ -495,6 +579,10 @@ int main(int argc, char *argv[]) { test_dns_service_join(); test_dns_service_split(); test_dns_name_change_suffix(); + test_dns_name_suffix(); + test_dns_name_count_labels(); + test_dns_name_equal_skip(); + test_dns_name_compare_func(); return 0; } diff --git a/src/test/test-rlimit-util.c b/src/test/test-rlimit-util.c index 00d3ecc0de..24bfe7a60e 100644 --- a/src/test/test-rlimit-util.c +++ b/src/test/test-rlimit-util.c @@ -37,7 +37,7 @@ int main(int argc, char *argv[]) { assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); new.rlim_cur = MIN(5U, old.rlim_max); - new.rlim_max = MIN(10U, old.rlim_max); + new.rlim_max = old.rlim_max; assert_se(setrlimit(RLIMIT_NOFILE, &new) >= 0); assert_se(rlimit_from_string("LimitNOFILE") == RLIMIT_NOFILE); @@ -53,7 +53,7 @@ int main(int argc, char *argv[]) { assert_se(old.rlim_max == new.rlim_max); assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); - high = RLIMIT_MAKE_CONST(old.rlim_max + 1); + high = RLIMIT_MAKE_CONST(old.rlim_max == RLIM_INFINITY ? old.rlim_max : old.rlim_max + 1); assert_se(setrlimit_closest(RLIMIT_NOFILE, &high) == 0); assert_se(getrlimit(RLIMIT_NOFILE, &new) == 0); assert_se(new.rlim_max == old.rlim_max); diff --git a/src/udev/udev-builtin-input_id.c b/src/udev/udev-builtin-input_id.c index 1d31829a08..a7aac78def 100644 --- a/src/udev/udev-builtin-input_id.c +++ b/src/udev/udev-builtin-input_id.c @@ -205,12 +205,12 @@ static bool test_pointers(struct udev_device *dev, /* This path is taken by VMware's USB mouse, which has * absolute axes, but no touch/pressure button. */ is_mouse = true; - else if (has_touch) + else if (has_touch || is_direct) is_touchscreen = true; else if (has_joystick_axes_or_buttons) is_joystick = true; } - if (has_mt_coordinates && is_direct) + if (has_mt_coordinates && (is_direct || has_touch)) is_touchscreen = true; if (has_rel_coordinates && has_mouse_button) |