summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backlight/backlight.c2
-rw-r--r--src/basic/btrfs-util.c2
-rw-r--r--src/basic/cgroup-util.c6
-rw-r--r--src/basic/clock-util.c4
-rw-r--r--src/basic/errno-list.c5
-rw-r--r--src/basic/escape.c51
-rw-r--r--src/basic/escape.h2
-rw-r--r--src/basic/extract-word.c9
-rw-r--r--src/basic/fileio.c4
-rw-r--r--src/basic/fs-util.c2
-rw-r--r--src/basic/glob-util.c4
-rw-r--r--src/basic/in-addr-util.c4
-rw-r--r--src/basic/parse-util.c22
-rw-r--r--src/basic/path-util.c2
-rw-r--r--src/basic/rm-rf.c2
-rw-r--r--src/basic/string-util.c30
-rw-r--r--src/basic/string-util.h3
-rw-r--r--src/basic/terminal-util.c4
-rw-r--r--src/basic/user-util.c2
-rw-r--r--src/basic/util.c2
-rw-r--r--src/core/dbus-execute.c4
-rw-r--r--src/core/dbus-manager.c5
-rw-r--r--src/core/device.c2
-rw-r--r--src/core/execute.c4
-rw-r--r--src/core/load-dropin.c1
-rw-r--r--src/core/manager.c6
-rw-r--r--src/core/transaction.c5
-rw-r--r--src/firstboot/firstboot.c2
-rw-r--r--src/getty-generator/getty-generator.c2
-rw-r--r--src/import/aufs-util.c2
-rw-r--r--src/journal-remote/journal-gatewayd.c4
-rw-r--r--src/journal-remote/journal-remote.c2
-rw-r--r--src/journal-remote/microhttpd-util.h9
-rw-r--r--src/journal/coredump.c2
-rw-r--r--src/journal/journald-server.c2
-rw-r--r--src/libsystemd-network/sd-ndisc.c38
-rw-r--r--src/libsystemd/sd-bus/bus-error.c55
-rw-r--r--src/libsystemd/sd-bus/bus-kernel.c30
-rw-r--r--src/libsystemd/sd-bus/test-bus-error.c31
-rw-r--r--src/libsystemd/sd-device/sd-device.c2
-rw-r--r--src/libsystemd/sd-event/sd-event.c13
-rw-r--r--src/libsystemd/sd-event/test-event.c28
-rw-r--r--src/libsystemd/sd-login/sd-login.c2
-rw-r--r--src/libsystemd/sd-netlink/netlink-types.c9
-rw-r--r--src/locale/localed.c2
-rw-r--r--src/login/logind-core.c2
-rw-r--r--src/login/logind-dbus.c98
-rw-r--r--src/login/logind-user.c13
-rw-r--r--src/machine/machinectl.c2
-rw-r--r--src/machine/machined-dbus.c8
-rw-r--r--src/resolve/RFCs18
-rw-r--r--src/resolve/dns-type.c26
-rw-r--r--src/resolve/dns-type.h2
-rw-r--r--src/resolve/resolved-bus.c282
-rw-r--r--src/resolve/resolved-dns-answer.c64
-rw-r--r--src/resolve/resolved-dns-answer.h3
-rw-r--r--src/resolve/resolved-dns-cache.c13
-rw-r--r--src/resolve/resolved-dns-dnssec.c702
-rw-r--r--src/resolve/resolved-dns-dnssec.h9
-rw-r--r--src/resolve/resolved-dns-packet.c126
-rw-r--r--src/resolve/resolved-dns-packet.h2
-rw-r--r--src/resolve/resolved-dns-query.c245
-rw-r--r--src/resolve/resolved-dns-query.h15
-rw-r--r--src/resolve/resolved-dns-question.c115
-rw-r--r--src/resolve/resolved-dns-question.h12
-rw-r--r--src/resolve/resolved-dns-rr.c123
-rw-r--r--src/resolve/resolved-dns-rr.h19
-rw-r--r--src/resolve/resolved-dns-server.c157
-rw-r--r--src/resolve/resolved-dns-server.h15
-rw-r--r--src/resolve/resolved-dns-transaction.c347
-rw-r--r--src/resolve/resolved-dns-trust-anchor.c2
-rw-r--r--src/resolve/resolved-gperf.gperf1
-rw-r--r--src/resolve/resolved-link.c16
-rw-r--r--src/resolve/resolved.conf.in1
-rw-r--r--src/resolve/test-dnssec-complex.c238
-rw-r--r--src/resolve/test-dnssec.c4
-rw-r--r--src/shared/ask-password-api.c2
-rw-r--r--src/shared/bus-util.c31
-rw-r--r--src/shared/bus-util.h2
-rw-r--r--src/shared/dns-domain.c296
-rw-r--r--src/shared/dns-domain.h6
-rw-r--r--src/shared/dropin.c2
-rw-r--r--src/systemctl/systemctl.c4
-rw-r--r--src/sysusers/sysusers.c8
-rw-r--r--src/test/test-dns-domain.c62
-rw-r--r--src/test/test-string-util.c46
-rw-r--r--src/tmpfiles/tmpfiles.c2
-rw-r--r--src/udev/udev-builtin-blkid.c2
-rw-r--r--src/udev/udevd.c3
89 files changed, 2628 insertions, 942 deletions
diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c
index b0fa079fec..a59459bc26 100644
--- a/src/backlight/backlight.c
+++ b/src/backlight/backlight.c
@@ -323,7 +323,7 @@ int main(int argc, char *argv[]) {
errno = 0;
device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
if (!device) {
- if (errno != 0)
+ if (errno > 0)
log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
else
log_oom();
diff --git a/src/basic/btrfs-util.c b/src/basic/btrfs-util.c
index acd48f6954..d07d1df5a8 100644
--- a/src/basic/btrfs-util.c
+++ b/src/basic/btrfs-util.c
@@ -2051,7 +2051,7 @@ int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) {
args.key.nr_items = 256;
if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0)
- return -errno;
+ return negative_errno();
if (args.key.nr_items <= 0)
break;
diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c
index 3945d37c8d..f873fb89d3 100644
--- a/src/basic/cgroup-util.c
+++ b/src/basic/cgroup-util.c
@@ -93,7 +93,7 @@ int cg_read_pid(FILE *f, pid_t *_pid) {
if (feof(f))
return 0;
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
}
if (ul <= 0)
@@ -648,7 +648,7 @@ int cg_trim(const char *controller, const char *path, bool delete_root) {
if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0) {
if (errno == ENOENT)
r = 0;
- else if (errno != 0)
+ else if (errno > 0)
r = -errno;
else
r = -EIO;
@@ -2091,7 +2091,7 @@ int cg_kernel_controllers(Set *controllers) {
if (feof(f))
break;
- if (ferror(f) && errno != 0)
+ if (ferror(f) && errno > 0)
return -errno;
return -EBADMSG;
diff --git a/src/basic/clock-util.c b/src/basic/clock-util.c
index 00f549c023..05788a360e 100644
--- a/src/basic/clock-util.c
+++ b/src/basic/clock-util.c
@@ -33,6 +33,7 @@
#include "fd-util.h"
#include "macro.h"
#include "string-util.h"
+#include "util.h"
int clock_get_hwclock(struct tm *tm) {
_cleanup_close_ int fd = -1;
@@ -121,7 +122,8 @@ int clock_set_timezone(int *min) {
* have read from the RTC.
*/
if (settimeofday(tv_null, &tz) < 0)
- return -errno;
+ return negative_errno();
+
if (min)
*min = minutesdelta;
return 0;
diff --git a/src/basic/errno-list.c b/src/basic/errno-list.c
index 0a66902ac9..b4d080103b 100644
--- a/src/basic/errno-list.c
+++ b/src/basic/errno-list.c
@@ -25,7 +25,7 @@
#include "macro.h"
static const struct errno_name* lookup_errno(register const char *str,
- register unsigned int len);
+ register unsigned int len);
#include "errno-from-name.h"
#include "errno-to-name.h"
@@ -48,8 +48,9 @@ int errno_from_name(const char *name) {
sc = lookup_errno(name, strlen(name));
if (!sc)
- return 0;
+ return -EINVAL;
+ assert(sc->id > 0);
return sc->id;
}
diff --git a/src/basic/escape.c b/src/basic/escape.c
index ab282efa3c..5661f36813 100644
--- a/src/basic/escape.c
+++ b/src/basic/escape.c
@@ -119,16 +119,18 @@ char *cescape(const char *s) {
return cescape_length(s, strlen(s));
}
-int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode) {
+int cunescape_one(const char *p, size_t length, uint32_t *ret, bool *eight_bit) {
int r = 1;
assert(p);
assert(*p);
assert(ret);
- /* Unescapes C style. Returns the unescaped character in ret,
- * unless we encountered a \u sequence in which case the full
- * unicode character is returned in ret_unicode, instead. */
+ /* Unescapes C style. Returns the unescaped character in ret.
+ * Sets *eight_bit to true if the escaped sequence either fits in
+ * one byte in UTF-8 or is a non-unicode literal byte and should
+ * instead be copied directly.
+ */
if (length != (size_t) -1 && length < 1)
return -EINVAL;
@@ -190,7 +192,8 @@ int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode
if (a == 0 && b == 0)
return -EINVAL;
- *ret = (char) ((a << 4U) | b);
+ *ret = (a << 4U) | b;
+ *eight_bit = true;
r = 3;
break;
}
@@ -217,16 +220,7 @@ int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode
if (c == 0)
return -EINVAL;
- if (c < 128)
- *ret = c;
- else {
- if (!ret_unicode)
- return -EINVAL;
-
- *ret = 0;
- *ret_unicode = c;
- }
-
+ *ret = c;
r = 5;
break;
}
@@ -258,16 +252,7 @@ int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode
if (!unichar_is_valid(c))
return -EINVAL;
- if (c < 128)
- *ret = c;
- else {
- if (!ret_unicode)
- return -EINVAL;
-
- *ret = 0;
- *ret_unicode = c;
- }
-
+ *ret = c;
r = 9;
break;
}
@@ -309,6 +294,7 @@ int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode
return -EINVAL;
*ret = m;
+ *eight_bit = true;
r = 3;
break;
}
@@ -342,7 +328,7 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi
for (f = s, t = r + pl; f < s + length; f++) {
size_t remaining;
uint32_t u;
- char c;
+ bool eight_bit = false;
int k;
remaining = s + length - f;
@@ -365,7 +351,7 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi
return -EINVAL;
}
- k = cunescape_one(f + 1, remaining - 1, &c, &u);
+ k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit);
if (k < 0) {
if (flags & UNESCAPE_RELAX) {
/* Invalid escape code, let's take it literal then */
@@ -377,14 +363,13 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi
return k;
}
- if (c != 0)
- /* Non-Unicode? Let's encode this directly */
- *(t++) = c;
+ f += k;
+ if (eight_bit)
+ /* One byte? Set directly as specified */
+ *(t++) = u;
else
- /* Unicode? Then let's encode this in UTF-8 */
+ /* Otherwise encode as multi-byte UTF-8 */
t += utf8_encode_unichar(t, u);
-
- f += k;
}
*t = 0;
diff --git a/src/basic/escape.h b/src/basic/escape.h
index c710f01743..d943aa71f5 100644
--- a/src/basic/escape.h
+++ b/src/basic/escape.h
@@ -45,7 +45,7 @@ size_t cescape_char(char c, char *buf);
int cunescape(const char *s, UnescapeFlags flags, char **ret);
int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret);
int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret);
-int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode);
+int cunescape_one(const char *p, size_t length, uint32_t *ret, bool *eight_bit);
char *xescape(const char *s, const char *bad);
diff --git a/src/basic/extract-word.c b/src/basic/extract-word.c
index 7cc2a1de13..090d2a7884 100644
--- a/src/basic/extract-word.c
+++ b/src/basic/extract-word.c
@@ -108,8 +108,9 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
if (flags & EXTRACT_CUNESCAPE) {
uint32_t u;
+ bool eight_bit = false;
- r = cunescape_one(*p, (size_t) -1, &c, &u);
+ r = cunescape_one(*p, (size_t) -1, &u, &eight_bit);
if (r < 0) {
if (flags & EXTRACT_CUNESCAPE_RELAX) {
s[sz++] = '\\';
@@ -119,10 +120,10 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
} else {
(*p) += r - 1;
- if (c != 0)
- s[sz++] = c; /* normal explicit char */
+ if (eight_bit)
+ s[sz++] = u;
else
- sz += utf8_encode_unichar(s + sz, u); /* unicode chars we'll encode as utf8 */
+ sz += utf8_encode_unichar(s + sz, u);
}
} else
s[sz++] = c;
diff --git a/src/basic/fileio.c b/src/basic/fileio.c
index 3a237252b5..5ed5460904 100644
--- a/src/basic/fileio.c
+++ b/src/basic/fileio.c
@@ -165,7 +165,7 @@ int read_one_line_file(const char *fn, char **line) {
if (!fgets(t, sizeof(t), f)) {
if (ferror(f))
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
t[0] = 0;
}
@@ -1064,7 +1064,7 @@ int fflush_and_check(FILE *f) {
fflush(f);
if (ferror(f))
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
return 0;
}
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index fb760abe18..d31bd6e273 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -481,7 +481,7 @@ int get_files_in_directory(const char *path, char ***list) {
errno = 0;
de = readdir(d);
- if (!de && errno != 0)
+ if (!de && errno > 0)
return -errno;
if (!de)
break;
diff --git a/src/basic/glob-util.c b/src/basic/glob-util.c
index a0be0efd40..811ab6ec36 100644
--- a/src/basic/glob-util.c
+++ b/src/basic/glob-util.c
@@ -40,7 +40,7 @@ int glob_exists(const char *path) {
if (k == GLOB_NOSPACE)
return -ENOMEM;
if (k != 0)
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
return !strv_isempty(g.gl_pathv);
}
@@ -58,7 +58,7 @@ int glob_extend(char ***strv, const char *path) {
if (k == GLOB_NOSPACE)
return -ENOMEM;
if (k != 0)
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
if (strv_isempty(g.gl_pathv))
return -ENOENT;
diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c
index 5143dddf8f..8609ffb3c9 100644
--- a/src/basic/in-addr-util.c
+++ b/src/basic/in-addr-util.c
@@ -219,7 +219,7 @@ int in_addr_to_string(int family, const union in_addr_union *u, char **ret) {
errno = 0;
if (!inet_ntop(family, u, x, l)) {
free(x);
- return errno ? -errno : -EINVAL;
+ return errno > 0 ? -errno : -EINVAL;
}
*ret = x;
@@ -236,7 +236,7 @@ int in_addr_from_string(int family, const char *s, union in_addr_union *ret) {
errno = 0;
if (inet_pton(family, s, ret) <= 0)
- return errno ? -errno : -EINVAL;
+ return errno > 0 ? -errno : -EINVAL;
return 0;
}
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
index 618ef5d564..d8de6f90ea 100644
--- a/src/basic/parse-util.c
+++ b/src/basic/parse-util.c
@@ -81,7 +81,7 @@ int parse_mode(const char *s, mode_t *ret) {
errno = 0;
l = strtol(s, &x, 8);
- if (errno != 0)
+ if (errno > 0)
return -errno;
if (!x || x == s || *x)
return -EINVAL;
@@ -176,7 +176,7 @@ int parse_size(const char *t, uint64_t base, uint64_t *size) {
errno = 0;
l = strtoull(p, &e, 10);
- if (errno != 0)
+ if (errno > 0)
return -errno;
if (e == p)
return -EINVAL;
@@ -192,7 +192,7 @@ int parse_size(const char *t, uint64_t base, uint64_t *size) {
char *e2;
l2 = strtoull(e, &e2, 10);
- if (errno != 0)
+ if (errno > 0)
return -errno;
/* Ignore failure. E.g. 10.M is valid */
@@ -330,7 +330,7 @@ int safe_atou(const char *s, unsigned *ret_u) {
errno = 0;
l = strtoul(s, &x, 0);
- if (errno != 0)
+ if (errno > 0)
return -errno;
if (!x || x == s || *x)
return -EINVAL;
@@ -352,7 +352,7 @@ int safe_atoi(const char *s, int *ret_i) {
errno = 0;
l = strtol(s, &x, 0);
- if (errno != 0)
+ if (errno > 0)
return -errno;
if (!x || x == s || *x)
return -EINVAL;
@@ -374,7 +374,7 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) {
errno = 0;
l = strtoull(s, &x, 0);
- if (errno != 0)
+ if (errno > 0)
return -errno;
if (!x || x == s || *x)
return -EINVAL;
@@ -394,7 +394,7 @@ int safe_atolli(const char *s, long long int *ret_lli) {
errno = 0;
l = strtoll(s, &x, 0);
- if (errno != 0)
+ if (errno > 0)
return -errno;
if (!x || x == s || *x)
return -EINVAL;
@@ -414,7 +414,7 @@ int safe_atou8(const char *s, uint8_t *ret) {
errno = 0;
l = strtoul(s, &x, 0);
- if (errno != 0)
+ if (errno > 0)
return -errno;
if (!x || x == s || *x)
return -EINVAL;
@@ -438,7 +438,7 @@ int safe_atou16(const char *s, uint16_t *ret) {
errno = 0;
l = strtoul(s, &x, 0);
- if (errno != 0)
+ if (errno > 0)
return -errno;
if (!x || x == s || *x)
return -EINVAL;
@@ -460,7 +460,7 @@ int safe_atoi16(const char *s, int16_t *ret) {
errno = 0;
l = strtol(s, &x, 0);
- if (errno != 0)
+ if (errno > 0)
return -errno;
if (!x || x == s || *x)
return -EINVAL;
@@ -485,7 +485,7 @@ int safe_atod(const char *s, double *ret_d) {
errno = 0;
d = strtod_l(s, &x, loc);
- if (errno != 0) {
+ if (errno > 0) {
freelocale(loc);
return -errno;
}
diff --git a/src/basic/path-util.c b/src/basic/path-util.c
index 61fab0e087..4837bb2d7d 100644
--- a/src/basic/path-util.c
+++ b/src/basic/path-util.c
@@ -102,7 +102,7 @@ int path_make_absolute_cwd(const char *p, char **ret) {
cwd = get_current_dir_name();
if (!cwd)
- return -errno;
+ return negative_errno();
c = strjoin(cwd, "/", p, NULL);
}
diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c
index 14f8474da0..4807561723 100644
--- a/src/basic/rm-rf.c
+++ b/src/basic/rm-rf.c
@@ -82,7 +82,7 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
errno = 0;
de = readdir(d);
if (!de) {
- if (errno != 0 && ret == 0)
+ if (errno > 0 && ret == 0)
ret = -errno;
return ret;
}
diff --git a/src/basic/string-util.c b/src/basic/string-util.c
index 849e457439..1f95a9abba 100644
--- a/src/basic/string-util.c
+++ b/src/basic/string-util.c
@@ -348,6 +348,36 @@ char *ascii_strlower_n(char *t, size_t n) {
return t;
}
+int ascii_strcasecmp_n(const char *a, const char *b, size_t n) {
+
+ for (; n > 0; a++, b++, n--) {
+ int x, y;
+
+ x = (int) (uint8_t) ascii_tolower(*a);
+ y = (int) (uint8_t) ascii_tolower(*b);
+
+ if (x != y)
+ return x - y;
+ }
+
+ return 0;
+}
+
+int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) {
+ int r;
+
+ r = ascii_strcasecmp_n(a, b, MIN(n, m));
+ if (r != 0)
+ return r;
+
+ if (n < m)
+ return -1;
+ else if (n > m)
+ return 1;
+ else
+ return 0;
+}
+
bool chars_intersect(const char *a, const char *b) {
const char *p;
diff --git a/src/basic/string-util.h b/src/basic/string-util.h
index 1ac6bcd6f8..8ea18f45aa 100644
--- a/src/basic/string-util.h
+++ b/src/basic/string-util.h
@@ -134,6 +134,9 @@ char ascii_tolower(char x);
char *ascii_strlower(char *s);
char *ascii_strlower_n(char *s, size_t n);
+int ascii_strcasecmp_n(const char *a, const char *b, size_t n);
+int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m);
+
bool chars_intersect(const char *a, const char *b) _pure_;
static inline bool _pure_ in_charset(const char *s, const char* charset) {
diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c
index a39764472b..7c9de72bb7 100644
--- a/src/basic/terminal-util.c
+++ b/src/basic/terminal-util.c
@@ -128,7 +128,7 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
errno = 0;
if (!fgets(line, sizeof(line), f))
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
truncate_nl(line);
@@ -212,7 +212,7 @@ int ask_string(char **ret, const char *text, ...) {
errno = 0;
if (!fgets(line, sizeof(line), stdin))
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
if (!endswith(line, "\n"))
putchar('\n');
diff --git a/src/basic/user-util.c b/src/basic/user-util.c
index 56e1a3be48..70a6e1f5e4 100644
--- a/src/basic/user-util.c
+++ b/src/basic/user-util.c
@@ -68,7 +68,7 @@ int parse_uid(const char *s, uid_t *ret) {
if (!uid_is_valid(uid))
return -ENXIO; /* we return ENXIO instead of EINVAL
* here, to make it easy to distuingish
- * invalid numeric uids invalid
+ * invalid numeric uids from invalid
* strings. */
if (ret)
diff --git a/src/basic/util.c b/src/basic/util.c
index 9e0b576283..4434ecfdf6 100644
--- a/src/basic/util.c
+++ b/src/basic/util.c
@@ -513,7 +513,7 @@ int on_ac_power(void) {
errno = 0;
de = readdir(d);
- if (!de && errno != 0)
+ if (!de && errno > 0)
return -errno;
if (!de)
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index c2238c8c43..eae0808f9e 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -141,7 +141,7 @@ static int property_get_nice(
else {
errno = 0;
n = getpriority(PRIO_PROCESS, 0);
- if (errno != 0)
+ if (errno > 0)
n = 0;
}
@@ -1382,7 +1382,7 @@ int bus_exec_context_set_transient_property(
dirs = &c->read_write_dirs;
else if (streq(name, "ReadOnlyDirectories"))
dirs = &c->read_only_dirs;
- else if (streq(name, "InaccessibleDirectories"))
+ else /* "InaccessibleDirectories" */
dirs = &c->inaccessible_dirs;
if (strv_length(l) == 0) {
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index 8a523cc8ac..c5c672a0a2 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -1607,6 +1607,7 @@ static int reply_unit_file_changes_and_free(
if (r < 0)
goto fail;
+ unit_file_changes_free(changes, n_changes);
return sd_bus_send(NULL, reply, NULL);
fail:
@@ -1843,8 +1844,10 @@ static int method_preset_all_unit_files(sd_bus_message *message, void *userdata,
scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER;
r = unit_file_preset_all(scope, runtime, NULL, mm, force, &changes, &n_changes);
- if (r < 0)
+ if (r < 0) {
+ unit_file_changes_free(changes, n_changes);
return r;
+ }
return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes);
}
diff --git a/src/core/device.c b/src/core/device.c
index bcd4d1146b..56ed947089 100644
--- a/src/core/device.c
+++ b/src/core/device.c
@@ -267,7 +267,7 @@ static int device_add_udev_wants(Unit *u, struct udev_device *dev) {
assert(u);
assert(dev);
- property = u->manager->running_as == MANAGER_USER ? "MANAGER_USER_WANTS" : "SYSTEMD_WANTS";
+ property = u->manager->running_as == MANAGER_USER ? "SYSTEMD_USER_WANTS" : "SYSTEMD_WANTS";
wants = udev_device_get_property_value(dev, property);
if (!wants)
return 0;
diff --git a/src/core/execute.c b/src/core/execute.c
index ac91568b63..0028730889 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -2319,7 +2319,7 @@ int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l) {
continue;
strv_free(r);
- return errno ? -errno : -EINVAL;
+ return errno > 0 ? -errno : -EINVAL;
}
count = pglob.gl_pathc;
if (count == 0) {
@@ -2683,7 +2683,7 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
fputc('\n', f);
}
- if (c->syscall_errno != 0)
+ if (c->syscall_errno > 0)
fprintf(f,
"%sSystemCallErrorNumber: %s\n",
prefix, strna(errno_to_name(c->syscall_errno)));
diff --git a/src/core/load-dropin.c b/src/core/load-dropin.c
index 3fa66f91aa..569632e13b 100644
--- a/src/core/load-dropin.c
+++ b/src/core/load-dropin.c
@@ -65,6 +65,7 @@ int unit_load_dropin(Unit *u) {
}
}
+ u->dropin_paths = strv_free(u->dropin_paths);
r = unit_find_dropin_paths(u, &u->dropin_paths);
if (r <= 0)
return 0;
diff --git a/src/core/manager.c b/src/core/manager.c
index 711b0cdcee..a83a8b013a 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -233,7 +233,7 @@ static int have_ask_password(void) {
errno = 0;
de = readdir(dir);
- if (!de && errno != 0)
+ if (!de && errno > 0)
return -errno;
if (!de)
return false;
@@ -989,7 +989,7 @@ Manager* manager_free(Manager *m) {
free(m->switch_root_init);
for (i = 0; i < _RLIMIT_MAX; i++)
- free(m->rlimit[i]);
+ m->rlimit[i] = mfree(m->rlimit[i]);
assert(hashmap_isempty(m->units_requiring_mounts_for));
hashmap_free(m->units_requiring_mounts_for);
@@ -2923,6 +2923,8 @@ int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit) {
assert(m);
for (i = 0; i < _RLIMIT_MAX; i++) {
+ m->rlimit[i] = mfree(m->rlimit[i]);
+
if (!default_rlimit[i])
continue;
diff --git a/src/core/transaction.c b/src/core/transaction.c
index 2f163190e9..bc85cef266 100644
--- a/src/core/transaction.c
+++ b/src/core/transaction.c
@@ -949,9 +949,10 @@ int transaction_add_job_and_dependencies(
SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) {
r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, ignore_order, e);
if (r < 0) {
+ /* unit masked and unit not found are not considered as errors. */
log_unit_full(dep,
- r == -EBADR /* unit masked */ ? LOG_DEBUG : LOG_WARNING, r,
- "Cannot add dependency job, ignoring: %s",
+ r == -EBADR || r == -ENOENT ? LOG_DEBUG : LOG_WARNING,
+ r, "Cannot add dependency job, ignoring: %s",
bus_error_message(e, r));
sd_bus_error_free(e);
}
diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c
index 469ee7af68..cc5e9741fe 100644
--- a/src/firstboot/firstboot.c
+++ b/src/firstboot/firstboot.c
@@ -502,7 +502,7 @@ static int write_root_shadow(const char *path, const struct spwd *p) {
errno = 0;
if (putspent(p, f) != 0)
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
return fflush_and_check(f);
}
diff --git a/src/getty-generator/getty-generator.c b/src/getty-generator/getty-generator.c
index 03df7365b5..bddc0c441a 100644
--- a/src/getty-generator/getty-generator.c
+++ b/src/getty-generator/getty-generator.c
@@ -112,7 +112,7 @@ static int verify_tty(const char *name) {
errno = 0;
if (isatty(fd) <= 0)
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
return 0;
}
diff --git a/src/import/aufs-util.c b/src/import/aufs-util.c
index 82f519958c..b44dbb14ea 100644
--- a/src/import/aufs-util.c
+++ b/src/import/aufs-util.c
@@ -69,7 +69,7 @@ int aufs_resolve(const char *path) {
errno = 0;
r = nftw(path, nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL);
if (r == FTW_STOP)
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
return 0;
}
diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c
index 4e96fb0a4d..7120a08e50 100644
--- a/src/journal-remote/journal-gatewayd.c
+++ b/src/journal-remote/journal-gatewayd.c
@@ -700,7 +700,7 @@ static int request_handler_file(
if (fstat(fd, &st) < 0)
return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
- response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
+ response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, MHD_VERSION);
if (!response)
return respond_oom(connection);
@@ -840,7 +840,7 @@ static int request_handler(
assert(method);
if (!streq(method, "GET"))
- return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+ return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE,
"Unsupported method.\n");
diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c
index 3f93e85232..2126606661 100644
--- a/src/journal-remote/journal-remote.c
+++ b/src/journal-remote/journal-remote.c
@@ -587,7 +587,7 @@ static int request_handler(
*connection_cls);
if (!streq(method, "POST"))
- return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+ return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE,
"Unsupported method.\n");
if (!streq(url, "/upload"))
diff --git a/src/journal-remote/microhttpd-util.h b/src/journal-remote/microhttpd-util.h
index 3e8c4fa6d1..cba57403a3 100644
--- a/src/journal-remote/microhttpd-util.h
+++ b/src/journal-remote/microhttpd-util.h
@@ -26,6 +26,15 @@
#include "macro.h"
+/* Compatiblity with libmicrohttpd < 0.9.38 */
+#ifndef MHD_HTTP_NOT_ACCEPTABLE
+#define MHD_HTTP_NOT_ACCEPTABLE MHD_HTTP_METHOD_NOT_ACCEPTABLE
+#endif
+
+#if MHD_VERSION < 0x00094203
+#define MHD_create_response_from_fd_at_offset64 MHD_create_response_from_fd_at_offset
+#endif
+
void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0);
/* respond_oom() must be usable with return, hence this form. */
diff --git a/src/journal/coredump.c b/src/journal/coredump.c
index f750ddfcbd..869c8fea03 100644
--- a/src/journal/coredump.c
+++ b/src/journal/coredump.c
@@ -527,7 +527,7 @@ static int compose_open_fds(pid_t pid, char **open_fds) {
errno = 0;
stream = safe_fclose(stream);
- if (errno != 0)
+ if (errno > 0)
return -errno;
*open_fds = buffer;
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
index a8a9b72080..cfcc2c4302 100644
--- a/src/journal/journald-server.c
+++ b/src/journal/journald-server.c
@@ -1330,7 +1330,7 @@ static int server_parse_proc_cmdline(Server *s) {
p = line;
for(;;) {
- _cleanup_free_ char *word;
+ _cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, NULL, 0);
if (r < 0)
diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c
index d8154f0587..0ee466b32a 100644
--- a/src/libsystemd-network/sd-ndisc.c
+++ b/src/libsystemd-network/sd-ndisc.c
@@ -1,3 +1,5 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
/***
This file is part of systemd.
@@ -112,7 +114,7 @@ static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) {
}
static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) {
- _cleanup_free_ NDiscPrefix *prefix = NULL;
+ NDiscPrefix *prefix;
assert(ret);
@@ -125,8 +127,6 @@ static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) {
prefix->nd = nd;
*ret = prefix;
- prefix = NULL;
-
return 0;
}
@@ -314,7 +314,6 @@ static int ndisc_prefix_match(sd_ndisc *nd, const struct in6_addr *addr,
LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
if (prefix->valid_until < time_now) {
prefix = ndisc_prefix_unref(prefix);
-
continue;
}
@@ -355,14 +354,13 @@ static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix,
prefix_opt->nd_opt_pi_prefix_len, &prefix);
+ if (r < 0) {
+ if (r != -EADDRNOTAVAIL)
+ return r;
- if (r < 0 && r != -EADDRNOTAVAIL)
- return r;
-
- /* if router advertisment prefix valid timeout is zero, the timeout
- callback will be called immediately to clean up the prefix */
+ /* if router advertisment prefix valid timeout is zero, the timeout
+ callback will be called immediately to clean up the prefix */
- if (r == -EADDRNOTAVAIL) {
r = ndisc_prefix_new(nd, &prefix);
if (r < 0)
return r;
@@ -373,9 +371,9 @@ static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
sizeof(prefix->addr));
log_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
- SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
- prefix->len, lifetime_valid,
- format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
+ SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
+ prefix->len, lifetime_valid,
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
LIST_PREPEND(prefixes, nd->prefixes, prefix);
@@ -386,17 +384,17 @@ static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
log_ndisc(nd, "Prefix length mismatch %d/%d using %d",
- prefix->len,
- prefix_opt->nd_opt_pi_prefix_len,
- prefixlen);
+ prefix->len,
+ prefix_opt->nd_opt_pi_prefix_len,
+ prefixlen);
prefix->len = prefixlen;
}
log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
- SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
- prefix->len, lifetime_valid,
- format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
+ SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
+ prefix->len, lifetime_valid,
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
}
r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
@@ -450,7 +448,7 @@ static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, ssize_t len
nd->mtu = MAX(mtu, IP6_MIN_MTU);
log_ndisc(nd, "Router Advertisement link MTU %d using %d",
- mtu, nd->mtu);
+ mtu, nd->mtu);
}
break;
diff --git a/src/libsystemd/sd-bus/bus-error.c b/src/libsystemd/sd-bus/bus-error.c
index 404eaa3c89..c77eb5fd03 100644
--- a/src/libsystemd/sd-bus/bus-error.c
+++ b/src/libsystemd/sd-bus/bus-error.c
@@ -93,14 +93,14 @@ static int bus_error_name_to_errno(const char *name) {
p = startswith(name, "System.Error.");
if (p) {
r = errno_from_name(p);
- if (r <= 0)
+ if (r < 0)
return EIO;
return r;
}
- if (additional_error_maps) {
- for (map = additional_error_maps; *map; map++) {
+ if (additional_error_maps)
+ for (map = additional_error_maps; *map; map++)
for (m = *map;; m++) {
/* For additional error maps the end marker is actually the end marker */
if (m->code == BUS_ERROR_MAP_END_MARKER)
@@ -109,15 +109,13 @@ static int bus_error_name_to_errno(const char *name) {
if (streq(m->name, name))
return m->code;
}
- }
- }
m = __start_BUS_ERROR_MAP;
while (m < __stop_BUS_ERROR_MAP) {
/* For magic ELF error maps, the end marker might
* appear in the middle of things, since multiple maps
* might appear in the same section. Hence, let's skip
- * over it, but realign the pointer to the netx 8byte
+ * over it, but realign the pointer to the next 8 byte
* boundary, which is the selected alignment for the
* arrays. */
if (m->code == BUS_ERROR_MAP_END_MARKER) {
@@ -258,25 +256,24 @@ int bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_li
if (!name)
return 0;
- if (!e)
- goto finish;
- assert_return(!bus_error_is_dirty(e), -EINVAL);
+ if (e) {
+ assert_return(!bus_error_is_dirty(e), -EINVAL);
- e->name = strdup(name);
- if (!e->name) {
- *e = BUS_ERROR_OOM;
- return -ENOMEM;
- }
+ e->name = strdup(name);
+ if (!e->name) {
+ *e = BUS_ERROR_OOM;
+ return -ENOMEM;
+ }
- /* If we hit OOM on formatting the pretty message, we ignore
- * this, since we at least managed to write the error name */
- if (format)
- (void) vasprintf((char**) &e->message, format, ap);
+ /* If we hit OOM on formatting the pretty message, we ignore
+ * this, since we at least managed to write the error name */
+ if (format)
+ (void) vasprintf((char**) &e->message, format, ap);
- e->_need_free = 1;
+ e->_need_free = 1;
+ }
-finish:
return -bus_error_name_to_errno(name);
}
@@ -582,27 +579,29 @@ const char *bus_error_message(const sd_bus_error *e, int error) {
return strerror(error);
}
+static bool map_ok(const sd_bus_error_map *map) {
+ for (; map->code != BUS_ERROR_MAP_END_MARKER; map++)
+ if (!map->name || map->code <=0)
+ return false;
+ return true;
+}
+
_public_ int sd_bus_error_add_map(const sd_bus_error_map *map) {
const sd_bus_error_map **maps = NULL;
unsigned n = 0;
assert_return(map, -EINVAL);
+ assert_return(map_ok(map), -EINVAL);
- if (additional_error_maps) {
- for (;; n++) {
- if (additional_error_maps[n] == NULL)
- break;
-
+ if (additional_error_maps)
+ for (; additional_error_maps[n] != NULL; n++)
if (additional_error_maps[n] == map)
return 0;
- }
- }
maps = realloc_multiply(additional_error_maps, sizeof(struct sd_bus_error_map*), n + 2);
if (!maps)
return -ENOMEM;
-
maps[n] = map;
maps[n+1] = NULL;
diff --git a/src/libsystemd/sd-bus/bus-kernel.c b/src/libsystemd/sd-bus/bus-kernel.c
index b2d685855e..e7d6170eec 100644
--- a/src/libsystemd/sd-bus/bus-kernel.c
+++ b/src/libsystemd/sd-bus/bus-kernel.c
@@ -270,8 +270,8 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) {
struct bus_body_part *part;
struct kdbus_item *d;
const char *destination;
- bool well_known;
- uint64_t unique;
+ bool well_known = false;
+ uint64_t dst_id;
size_t sz, dl;
unsigned i;
int r;
@@ -288,13 +288,21 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) {
destination = m->destination ?: m->destination_ptr;
if (destination) {
- r = bus_kernel_parse_unique_name(destination, &unique);
+ r = bus_kernel_parse_unique_name(destination, &dst_id);
if (r < 0)
return r;
-
- well_known = r == 0;
+ if (r == 0) {
+ well_known = true;
+
+ /* verify_destination_id will usually be 0, which makes the kernel
+ * driver only look at the provided well-known name. Otherwise,
+ * the kernel will make sure the provided destination id matches
+ * the owner of the provided well-known-name, and fail if they
+ * differ. Currently, this is only needed for bus-proxyd. */
+ dst_id = m->verify_destination_id;
+ }
} else
- well_known = false;
+ dst_id = KDBUS_DST_ID_BROADCAST;
sz = offsetof(struct kdbus_msg, items);
@@ -332,15 +340,7 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) {
((m->header->flags & BUS_MESSAGE_NO_AUTO_START) ? KDBUS_MSG_NO_AUTO_START : 0) |
((m->header->type == SD_BUS_MESSAGE_SIGNAL) ? KDBUS_MSG_SIGNAL : 0);
- if (well_known)
- /* verify_destination_id will usually be 0, which makes the kernel driver only look
- * at the provided well-known name. Otherwise, the kernel will make sure the provided
- * destination id matches the owner of the provided weel-known-name, and fail if they
- * differ. Currently, this is only needed for bus-proxyd. */
- m->kdbus->dst_id = m->verify_destination_id;
- else
- m->kdbus->dst_id = destination ? unique : KDBUS_DST_ID_BROADCAST;
-
+ m->kdbus->dst_id = dst_id;
m->kdbus->payload_type = KDBUS_PAYLOAD_DBUS;
m->kdbus->cookie = m->header->dbus2.cookie;
m->kdbus->priority = m->priority;
diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c
index c52405463e..407fd14555 100644
--- a/src/libsystemd/sd-bus/test-bus-error.c
+++ b/src/libsystemd/sd-bus/test-bus-error.c
@@ -44,7 +44,15 @@ static void test_error(void) {
assert_se(sd_bus_error_is_set(&error));
sd_bus_error_free(&error);
+ /* Check with no error */
assert_se(!sd_bus_error_is_set(&error));
+ assert_se(sd_bus_error_setf(&error, NULL, "yyy %i", -1) == 0);
+ assert_se(error.name == NULL);
+ assert_se(error.message == NULL);
+ assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND));
+ assert_se(sd_bus_error_get_errno(&error) == 0);
+ assert_se(!sd_bus_error_is_set(&error));
+
assert_se(sd_bus_error_setf(&error, SD_BUS_ERROR_FILE_NOT_FOUND, "yyy %i", -1) == -ENOENT);
assert_se(streq(error.name, SD_BUS_ERROR_FILE_NOT_FOUND));
assert_se(streq(error.message, "yyy -1"));
@@ -112,6 +120,16 @@ static void test_error(void) {
assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR));
assert_se(sd_bus_error_get_errno(&error) == EIO);
assert_se(sd_bus_error_is_set(&error));
+ sd_bus_error_free(&error);
+
+ /* Check with no error */
+ assert_se(!sd_bus_error_is_set(&error));
+ assert_se(sd_bus_error_set_errnof(&error, 0, "Waldi %c", 'X') == 0);
+ assert_se(error.name == NULL);
+ assert_se(error.message == NULL);
+ assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR));
+ assert_se(sd_bus_error_get_errno(&error) == 0);
+ assert_se(!sd_bus_error_is_set(&error));
}
extern const sd_bus_error_map __start_BUS_ERROR_MAP[];
@@ -167,6 +185,16 @@ static const sd_bus_error_map test_errors4[] = {
SD_BUS_ERROR_MAP_END
};
+static const sd_bus_error_map test_errors_bad1[] = {
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", 0),
+ SD_BUS_ERROR_MAP_END
+};
+
+static const sd_bus_error_map test_errors_bad2[] = {
+ SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", -1),
+ SD_BUS_ERROR_MAP_END
+};
+
static void test_errno_mapping_custom(void) {
assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error", NULL) == -5);
assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52);
@@ -190,6 +218,9 @@ static void test_errno_mapping_custom(void) {
assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-y", NULL) == -EIO);
assert_se(sd_bus_error_set(NULL, BUS_ERROR_NO_SUCH_UNIT, NULL) == -ENOENT);
+
+ assert_se(sd_bus_error_add_map(test_errors_bad1) == -EINVAL);
+ assert_se(sd_bus_error_add_map(test_errors_bad2) == -EINVAL);
}
int main(int argc, char *argv[]) {
diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c
index f44054a7b5..9633e46ce0 100644
--- a/src/libsystemd/sd-device/sd-device.c
+++ b/src/libsystemd/sd-device/sd-device.c
@@ -494,7 +494,7 @@ static int handle_uevent_line(sd_device *device, const char *key, const char *va
int device_read_uevent_file(sd_device *device) {
_cleanup_free_ char *uevent = NULL;
- const char *syspath, *key, *value, *major = NULL, *minor = NULL;
+ const char *syspath, *key = NULL, *value = NULL, *major = NULL, *minor = NULL;
char *path;
size_t uevent_len;
unsigned i;
diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c
index aeb06ad9a8..11c7330b9b 100644
--- a/src/libsystemd/sd-event/sd-event.c
+++ b/src/libsystemd/sd-event/sd-event.c
@@ -661,8 +661,10 @@ static int event_make_signal_data(
d->priority = priority;
r = hashmap_put(e->signal_data, &d->priority, d);
- if (r < 0)
+ if (r < 0) {
+ free(d);
return r;
+ }
added = true;
}
@@ -2751,6 +2753,12 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) {
assert_return(e, -EINVAL);
assert_return(usec, -EINVAL);
assert_return(!event_pid_changed(e), -ECHILD);
+ assert_return(IN_SET(clock,
+ CLOCK_REALTIME,
+ CLOCK_REALTIME_ALARM,
+ CLOCK_MONOTONIC,
+ CLOCK_BOOTTIME,
+ CLOCK_BOOTTIME_ALARM), -EOPNOTSUPP);
if (!dual_timestamp_is_set(&e->timestamp)) {
/* Implicitly fall back to now() if we never ran
@@ -2770,8 +2778,7 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) {
*usec = e->timestamp.monotonic;
break;
- case CLOCK_BOOTTIME:
- case CLOCK_BOOTTIME_ALARM:
+ default:
*usec = e->timestamp_boottime;
break;
}
diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c
index 9417a8d1d1..c605b18ae9 100644
--- a/src/libsystemd/sd-event/test-event.c
+++ b/src/libsystemd/sd-event/test-event.c
@@ -264,6 +264,30 @@ static void test_basic(void) {
safe_close_pair(k);
}
+static void test_sd_event_now(void) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ uint64_t event_now;
+
+ assert_se(sd_event_new(&e) >= 0);
+ assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) > 0);
+ assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) > 0);
+ assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) > 0);
+ assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) > 0);
+ assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) > 0);
+ assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP);
+ assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP);
+
+ assert_se(sd_event_run(e, 0) == 0);
+
+ assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) == 0);
+ assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) == 0);
+ assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) == 0);
+ assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) == 0);
+ assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) == 0);
+ assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP);
+ assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP);
+}
+
static int last_rtqueue_sigval = 0;
static int n_rtqueue = 0;
@@ -324,7 +348,11 @@ static void test_rtqueue(void) {
int main(int argc, char *argv[]) {
+ log_set_max_level(LOG_DEBUG);
+ log_parse_environment();
+
test_basic();
+ test_sd_event_now();
test_rtqueue();
return 0;
diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c
index 4b46eeb533..ef240c3531 100644
--- a/src/libsystemd/sd-login/sd-login.c
+++ b/src/libsystemd/sd-login/sd-login.c
@@ -810,7 +810,7 @@ _public_ int sd_get_uids(uid_t **users) {
errno = 0;
de = readdir(d);
- if (!de && errno != 0)
+ if (!de && errno > 0)
return -errno;
if (!de)
diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c
index 135354e5f3..be4ab1373d 100644
--- a/src/libsystemd/sd-netlink/netlink-types.c
+++ b/src/libsystemd/sd-netlink/netlink-types.c
@@ -96,15 +96,6 @@ static const NLType rtnl_link_info_data_macvlan_types[] = {
[IFLA_MACVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 },
};
-static const NLType rtnl_link_bridge_management_types[] = {
- [IFLA_BRIDGE_FLAGS] = { .type = NETLINK_TYPE_U16 },
- [IFLA_BRIDGE_MODE] = { .type = NETLINK_TYPE_U16 },
-/*
- [IFLA_BRIDGE_VLAN_INFO] = { .type = NETLINK_TYPE_BINARY,
- .len = sizeof(struct bridge_vlan_info), },
-*/
-};
-
static const NLType rtnl_link_info_data_bridge_types[] = {
[IFLA_BR_FORWARD_DELAY] = { .type = NETLINK_TYPE_U32 },
[IFLA_BR_HELLO_TIME] = { .type = NETLINK_TYPE_U32 },
diff --git a/src/locale/localed.c b/src/locale/localed.c
index 5ca41331bd..8ab845eb80 100644
--- a/src/locale/localed.c
+++ b/src/locale/localed.c
@@ -539,7 +539,7 @@ static int read_next_mapping(const char* filename,
if (!fgets(line, sizeof(line), f)) {
if (ferror(f))
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
return 0;
}
diff --git a/src/login/logind-core.c b/src/login/logind-core.c
index d51330fb85..2e14aa2d95 100644
--- a/src/login/logind-core.c
+++ b/src/login/logind-core.c
@@ -139,7 +139,7 @@ int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) {
errno = 0;
p = getpwuid(uid);
if (!p)
- return errno ? -errno : -ENOENT;
+ return errno > 0 ? -errno : -ENOENT;
return manager_add_user(m, uid, p->pw_gid, p->pw_name, _user);
}
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
index 4631f5fc90..9eda4638e5 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -124,7 +124,6 @@ int manager_get_seat_from_creds(Manager *m, sd_bus_message *message, const char
return r;
seat = session->seat;
-
if (!seat)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SEAT, "Session has no seat.");
} else {
@@ -1111,7 +1110,7 @@ static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bu
errno = 0;
pw = getpwuid(uid);
if (!pw)
- return errno ? -errno : -ENOENT;
+ return errno > 0 ? -errno : -ENOENT;
r = bus_verify_polkit_async(
message,
@@ -1995,7 +1994,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_
r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_AUGMENT|SD_BUS_CREDS_TTY|SD_BUS_CREDS_UID, &creds);
if (r >= 0) {
- const char *tty;
+ const char *tty = NULL;
(void) sd_bus_creds_get_uid(creds, &m->scheduled_shutdown_uid);
(void) sd_bus_creds_get_tty(creds, &tty);
@@ -2752,6 +2751,23 @@ int manager_send_changed(Manager *manager, const char *property, ...) {
l);
}
+static int strdup_job(sd_bus_message *reply, char **job) {
+ const char *j;
+ char *copy;
+ int r;
+
+ r = sd_bus_message_read(reply, "o", &j);
+ if (r < 0)
+ return r;
+
+ copy = strdup(j);
+ if (!copy)
+ return -ENOMEM;
+
+ *job = copy;
+ return 1;
+}
+
int manager_start_slice(
Manager *manager,
const char *slice,
@@ -2767,6 +2783,7 @@ int manager_start_slice(
assert(manager);
assert(slice);
+ assert(job);
r = sd_bus_message_new_method_call(
manager->bus,
@@ -2820,22 +2837,7 @@ int manager_start_slice(
if (r < 0)
return r;
- if (job) {
- const char *j;
- char *copy;
-
- r = sd_bus_message_read(reply, "o", &j);
- if (r < 0)
- return r;
-
- copy = strdup(j);
- if (!copy)
- return -ENOMEM;
-
- *job = copy;
- }
-
- return 1;
+ return strdup_job(reply, job);
}
int manager_start_scope(
@@ -2856,6 +2858,7 @@ int manager_start_scope(
assert(manager);
assert(scope);
assert(pid > 1);
+ assert(job);
r = sd_bus_message_new_method_call(
manager->bus,
@@ -2930,22 +2933,7 @@ int manager_start_scope(
if (r < 0)
return r;
- if (job) {
- const char *j;
- char *copy;
-
- r = sd_bus_message_read(reply, "o", &j);
- if (r < 0)
- return r;
-
- copy = strdup(j);
- if (!copy)
- return -ENOMEM;
-
- *job = copy;
- }
-
- return 1;
+ return strdup_job(reply, job);
}
int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) {
@@ -2954,6 +2942,7 @@ int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error,
assert(manager);
assert(unit);
+ assert(job);
r = sd_bus_call_method(
manager->bus,
@@ -2967,22 +2956,7 @@ int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error,
if (r < 0)
return r;
- if (job) {
- const char *j;
- char *copy;
-
- r = sd_bus_message_read(reply, "o", &j);
- if (r < 0)
- return r;
-
- copy = strdup(j);
- if (!copy)
- return -ENOMEM;
-
- *job = copy;
- }
-
- return 1;
+ return strdup_job(reply, job);
}
int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) {
@@ -2991,6 +2965,7 @@ int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, c
assert(manager);
assert(unit);
+ assert(job);
r = sd_bus_call_method(
manager->bus,
@@ -3005,9 +2980,7 @@ int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, c
if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) ||
sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) {
- if (job)
- *job = NULL;
-
+ *job = NULL;
sd_bus_error_free(error);
return 0;
}
@@ -3015,22 +2988,7 @@ int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, c
return r;
}
- if (job) {
- const char *j;
- char *copy;
-
- r = sd_bus_message_read(reply, "o", &j);
- if (r < 0)
- return r;
-
- copy = strdup(j);
- if (!copy)
- return -ENOMEM;
-
- *job = copy;
- }
-
- return 1;
+ return strdup_job(reply, job);
}
int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *error) {
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
index 4ad9740e5e..98f8ea3c78 100644
--- a/src/login/logind-user.c
+++ b/src/login/logind-user.c
@@ -412,13 +412,12 @@ static int user_start_slice(User *u) {
u->manager->user_tasks_max,
&error,
&job);
- if (r < 0) {
- /* we don't fail due to this, let's try to continue */
- if (!sd_bus_error_has_name(&error, BUS_ERROR_UNIT_EXISTS))
- log_error_errno(r, "Failed to start user slice %s, ignoring: %s (%s)", u->slice, bus_error_message(&error, r), error.name);
- } else {
+ if (r >= 0)
u->slice_job = job;
- }
+ else if (!sd_bus_error_has_name(&error, BUS_ERROR_UNIT_EXISTS))
+ /* we don't fail due to this, let's try to continue */
+ log_error_errno(r, "Failed to start user slice %s, ignoring: %s (%s)",
+ u->slice, bus_error_message(&error, r), error.name);
return 0;
}
@@ -868,7 +867,7 @@ int config_parse_tmpfs_size(
errno = 0;
ul = strtoul(rvalue, &f, 10);
- if (errno != 0 || f != e) {
+ if (errno > 0 || f != e) {
log_syntax(unit, LOG_ERR, filename, line, errno, "Failed to parse percentage value, ignoring: %s", rvalue);
return 0;
}
diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index 685bbafdf1..38a75f85d5 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -1571,7 +1571,7 @@ static int start_machine(int argc, char *argv[], void *userdata) {
return log_oom();
}
- r = bus_wait_for_jobs(w, arg_quiet);
+ r = bus_wait_for_jobs(w, arg_quiet, NULL);
if (r < 0)
return r;
diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c
index c6b5b1ec44..e448dd2035 100644
--- a/src/machine/machined-dbus.c
+++ b/src/machine/machined-dbus.c
@@ -910,7 +910,7 @@ static int method_map_from_machine_user(sd_bus_message *message, void *userdata,
if (k < 0 && feof(f))
break;
if (k != 3) {
- if (ferror(f) && errno != 0)
+ if (ferror(f) && errno > 0)
return -errno;
return -EIO;
@@ -968,7 +968,7 @@ static int method_map_to_machine_user(sd_bus_message *message, void *userdata, s
if (k < 0 && feof(f))
break;
if (k != 3) {
- if (ferror(f) && errno != 0)
+ if (ferror(f) && errno > 0)
return -errno;
return -EIO;
@@ -1028,7 +1028,7 @@ static int method_map_from_machine_group(sd_bus_message *message, void *groupdat
if (k < 0 && feof(f))
break;
if (k != 3) {
- if (ferror(f) && errno != 0)
+ if (ferror(f) && errno > 0)
return -errno;
return -EIO;
@@ -1086,7 +1086,7 @@ static int method_map_to_machine_group(sd_bus_message *message, void *groupdata,
if (k < 0 && feof(f))
break;
if (k != 3) {
- if (ferror(f) && errno != 0)
+ if (ferror(f) && errno > 0)
return -errno;
return -EIO;
diff --git a/src/resolve/RFCs b/src/resolve/RFCs
index 33f4dd9cb6..22004a00cd 100644
--- a/src/resolve/RFCs
+++ b/src/resolve/RFCs
@@ -13,14 +13,14 @@ Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts -- App
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/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
+Y https://tools.ietf.org/html/rfc4033 → DNS Security Introduction and Requirements
+Y https://tools.ietf.org/html/rfc4034 → Resource Records for the DNS Security Extensions
+Y 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
@@ -31,26 +31,26 @@ Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Si
~ 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/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/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)
+~ https://tools.ietf.org/html/rfc6781 → DNSSEC Operational Practices, Version 2
+Y 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/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
diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c
index fb8228048d..058d14009a 100644
--- a/src/resolve/dns-type.c
+++ b/src/resolve/dns-type.c
@@ -120,6 +120,32 @@ bool dns_type_may_redirect(uint16_t type) {
DNS_TYPE_KEY);
}
+bool dns_type_may_wildcard(uint16_t type) {
+
+ /* The following records may not be expanded from wildcard RRsets */
+
+ if (dns_type_is_pseudo(type))
+ return false;
+
+ return !IN_SET(type,
+ DNS_TYPE_NSEC3,
+ DNS_TYPE_SOA,
+
+ /* Prohibited by https://tools.ietf.org/html/rfc4592#section-4.4 */
+ DNS_TYPE_DNAME);
+}
+
+bool dns_type_apex_only(uint16_t type) {
+
+ /* Returns true for all RR types that may only appear signed in a zone apex */
+
+ return IN_SET(type,
+ DNS_TYPE_SOA,
+ DNS_TYPE_NS, /* this one can appear elsewhere, too, but not signed */
+ DNS_TYPE_DNSKEY,
+ DNS_TYPE_NSEC3PARAM);
+}
+
bool dns_type_is_dnssec(uint16_t type) {
return IN_SET(type,
DNS_TYPE_DS,
diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h
index 45080fd243..78ff71b06e 100644
--- a/src/resolve/dns-type.h
+++ b/src/resolve/dns-type.h
@@ -131,6 +131,8 @@ bool dns_type_is_valid_rr(uint16_t type);
bool dns_type_may_redirect(uint16_t type);
bool dns_type_is_dnssec(uint16_t type);
bool dns_type_is_obsolete(uint16_t type);
+bool dns_type_may_wildcard(uint16_t type);
+bool dns_type_apex_only(uint16_t type);
bool dns_class_is_pseudo(uint16_t class);
bool dns_class_is_valid_rr(uint16_t class);
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c
index 41f90dedfd..4593bab5e8 100644
--- a/src/resolve/resolved-bus.c
+++ b/src/resolve/resolved-bus.c
@@ -27,18 +27,6 @@
#include "resolved-def.h"
static int reply_query_state(DnsQuery *q) {
- _cleanup_free_ char *ip = NULL;
- const char *name;
- int r;
-
- if (q->request_address_valid) {
- r = in_addr_to_string(q->request_family, &q->request_address, &ip);
- if (r < 0)
- return r;
-
- name = ip;
- } else
- name = dns_question_first_name(q->question);
switch (q->state) {
@@ -74,7 +62,7 @@ static int reply_query_state(DnsQuery *q) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
if (q->answer_rcode == DNS_RCODE_NXDOMAIN)
- sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", name);
+ sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q));
else {
const char *rc, *n;
char p[3]; /* the rcode is 4 bits long */
@@ -86,7 +74,7 @@ static int reply_query_state(DnsQuery *q) {
}
n = strjoina(_BUS_ERROR_DNS, rc);
- sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", name, rc);
+ sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc);
}
return sd_bus_reply_method_error(q->request, &error);
@@ -156,7 +144,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
r = dns_query_process_cname(q);
if (r == -ELOOP) {
- r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question));
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q));
goto finish;
}
if (r < 0)
@@ -177,7 +165,11 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
int ifindex;
DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
- r = dns_question_matches_rr(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
+ DnsQuestion *question;
+
+ question = dns_query_question_for_protocol(q, q->answer_protocol);
+
+ r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
if (r < 0)
goto finish;
if (r == 0)
@@ -195,7 +187,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) {
}
if (added <= 0) {
- r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question));
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q));
goto finish;
}
@@ -239,7 +231,7 @@ static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus
}
static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) {
- _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
+ _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL;
Manager *m = userdata;
const char *hostname;
int family, ifindex;
@@ -269,11 +261,15 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata,
if (r < 0)
return r;
- r = dns_question_new_address(&question, family, hostname);
+ r = dns_question_new_address(&question_utf8, family, hostname, false);
+ if (r < 0)
+ return r;
+
+ r = dns_question_new_address(&question_idna, family, hostname, true);
if (r < 0)
return r;
- r = dns_query_new(m, &q, question, ifindex, flags);
+ r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags);
if (r < 0)
return r;
@@ -298,6 +294,7 @@ fail:
static void bus_method_resolve_address_complete(DnsQuery *q) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ DnsQuestion *question;
DnsResourceRecord *rr;
unsigned added = 0;
int ifindex, r;
@@ -311,7 +308,7 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
r = dns_query_process_cname(q);
if (r == -ELOOP) {
- r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question));
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q));
goto finish;
}
if (r < 0)
@@ -327,20 +324,20 @@ static void bus_method_resolve_address_complete(DnsQuery *q) {
if (r < 0)
goto finish;
- if (q->answer) {
- DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
- r = dns_question_matches_rr(q->question, rr, NULL);
- if (r < 0)
- goto finish;
- if (r == 0)
- continue;
+ question = dns_query_question_for_protocol(q, q->answer_protocol);
- r = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name);
- if (r < 0)
- goto finish;
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
+ r = dns_question_matches_rr(question, rr, NULL);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
- added ++;
- }
+ r = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name);
+ if (r < 0)
+ goto finish;
+
+ added ++;
}
if (added <= 0) {
@@ -411,7 +408,7 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s
if (r < 0)
return r;
- r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
+ r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
if (r < 0)
return r;
@@ -465,7 +462,10 @@ static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int i
static void bus_method_resolve_record_complete(DnsQuery *q) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ DnsResourceRecord *rr;
+ DnsQuestion *question;
unsigned added = 0;
+ int ifindex;
int r;
assert(q);
@@ -477,7 +477,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) {
r = dns_query_process_cname(q);
if (r == -ELOOP) {
- r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question));
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q));
goto finish;
}
if (r < 0)
@@ -493,27 +493,24 @@ static void bus_method_resolve_record_complete(DnsQuery *q) {
if (r < 0)
goto finish;
- if (q->answer) {
- DnsResourceRecord *rr;
- int ifindex;
+ question = dns_query_question_for_protocol(q, q->answer_protocol);
- DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
- r = dns_question_matches_rr(q->question, rr, NULL);
- if (r < 0)
- goto finish;
- if (r == 0)
- continue;
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
+ r = dns_question_matches_rr(question, rr, NULL);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
- r = bus_message_append_rr(reply, rr, ifindex);
- if (r < 0)
- goto finish;
+ r = bus_message_append_rr(reply, rr, ifindex);
+ if (r < 0)
+ goto finish;
- added ++;
- }
+ added ++;
}
if (added <= 0) {
- r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_question_first_name(q->question));
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_query_string(q));
goto finish;
}
@@ -582,7 +579,7 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd
if (r < 0)
return r;
- r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
+ r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
if (r < 0)
return r;
@@ -622,13 +619,16 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
* record for the SRV record */
LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
DnsResourceRecord *zz;
+ DnsQuestion *question;
if (aux->state != DNS_TRANSACTION_SUCCESS)
continue;
if (aux->auxiliary_result != 0)
continue;
- r = dns_name_equal(dns_question_first_name(aux->question), rr->srv.name);
+ question = dns_query_question_for_protocol(aux, aux->answer_protocol);
+
+ r = dns_name_equal(dns_question_first_name(question), rr->srv.name);
if (r < 0)
return r;
if (r == 0)
@@ -636,7 +636,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
DNS_ANSWER_FOREACH(zz, aux->answer) {
- r = dns_question_matches_rr(aux->question, zz, NULL);
+ r = dns_question_matches_rr(question, zz, NULL);
if (r < 0)
return r;
if (r == 0)
@@ -673,6 +673,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) {
DnsResourceRecord *zz;
+ DnsQuestion *question;
int ifindex;
if (aux->state != DNS_TRANSACTION_SUCCESS)
@@ -680,7 +681,9 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
if (aux->auxiliary_result != 0)
continue;
- r = dns_name_equal(dns_question_first_name(aux->question), rr->srv.name);
+ question = dns_query_question_for_protocol(aux, aux->answer_protocol);
+
+ r = dns_name_equal(dns_question_first_name(question), rr->srv.name);
if (r < 0)
return r;
if (r == 0)
@@ -688,7 +691,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr)
DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) {
- r = dns_question_matches_rr(aux->question, zz, NULL);
+ r = dns_question_matches_rr(question, zz, NULL);
if (r < 0)
return r;
if (r == 0)
@@ -746,8 +749,10 @@ static void resolve_service_all_complete(DnsQuery *q) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
+ DnsQuestion *question;
+ DnsResourceRecord *rr;
+ unsigned added = 0;
DnsQuery *aux;
- unsigned added = false;
int r;
assert(q);
@@ -789,7 +794,7 @@ static void resolve_service_all_complete(DnsQuery *q) {
assert(bad->auxiliary_result != 0);
if (bad->auxiliary_result == -ELOOP) {
- r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(bad->question));
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(bad));
goto finish;
}
@@ -810,31 +815,28 @@ static void resolve_service_all_complete(DnsQuery *q) {
if (r < 0)
goto finish;
- if (q->answer) {
- DnsResourceRecord *rr;
-
- DNS_ANSWER_FOREACH(rr, q->answer) {
- r = dns_question_matches_rr(q->question, rr, NULL);
- if (r < 0)
- goto finish;
- if (r == 0)
- continue;
+ question = dns_query_question_for_protocol(q, q->answer_protocol);
+ DNS_ANSWER_FOREACH(rr, q->answer) {
+ r = dns_question_matches_rr(question, rr, NULL);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
- r = append_srv(q, reply, rr);
- if (r < 0)
- goto finish;
- if (r == 0) /* not an SRV record */
- continue;
+ r = append_srv(q, reply, rr);
+ if (r < 0)
+ goto finish;
+ if (r == 0) /* not an SRV record */
+ continue;
- if (!canonical)
- canonical = dns_resource_record_ref(rr);
+ if (!canonical)
+ canonical = dns_resource_record_ref(rr);
- added++;
- }
+ added++;
}
if (added <= 0) {
- r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question));
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q));
goto finish;
}
@@ -846,20 +848,16 @@ static void resolve_service_all_complete(DnsQuery *q) {
if (r < 0)
goto finish;
- if (q->answer) {
- DnsResourceRecord *rr;
-
- DNS_ANSWER_FOREACH(rr, q->answer) {
- r = dns_question_matches_rr(q->question, rr, NULL);
- if (r < 0)
- goto finish;
- if (r == 0)
- continue;
+ DNS_ANSWER_FOREACH(rr, q->answer) {
+ r = dns_question_matches_rr(question, rr, NULL);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
- r = append_txt(reply, rr);
- if (r < 0)
- goto finish;
- }
+ r = append_txt(reply, rr);
+ if (r < 0)
+ goto finish;
}
r = sd_bus_message_close_container(reply);
@@ -923,11 +921,11 @@ static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifin
/* OK, we found an SRV record for the service. Let's resolve
* the hostname included in it */
- r = dns_question_new_address(&question, q->request_family, rr->srv.name);
+ r = dns_question_new_address(&question, q->request_family, rr->srv.name, false);
if (r < 0)
return r;
- r = dns_query_new(q->manager, &aux, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
+ r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH);
if (r < 0)
return r;
@@ -961,8 +959,11 @@ fail:
}
static void bus_method_resolve_service_complete(DnsQuery *q) {
+ bool has_root_domain = false;
+ DnsResourceRecord *rr;
+ DnsQuestion *question;
unsigned found = 0;
- int r;
+ int ifindex, r;
assert(q);
@@ -973,7 +974,7 @@ static void bus_method_resolve_service_complete(DnsQuery *q) {
r = dns_query_process_cname(q);
if (r == -ELOOP) {
- r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question));
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q));
goto finish;
}
if (r < 0)
@@ -981,53 +982,48 @@ static void bus_method_resolve_service_complete(DnsQuery *q) {
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;
-
- DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
- r = dns_question_matches_rr(q->question, rr, NULL);
- if (r < 0)
- goto finish;
- if (r == 0)
- continue;
+ question = dns_query_question_for_protocol(q, q->answer_protocol);
- if (rr->key->type != DNS_TYPE_SRV)
- continue;
+ DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) {
+ r = dns_question_matches_rr(question, rr, NULL);
+ if (r < 0)
+ goto finish;
+ if (r == 0)
+ continue;
- if (dns_name_is_root(rr->srv.name)) {
- has_root_domain = true;
- continue;
- }
+ if (rr->key->type != DNS_TYPE_SRV)
+ continue;
- if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
- q->block_all_complete ++;
- r = resolve_service_hostname(q, rr, ifindex);
- q->block_all_complete --;
+ if (dns_name_is_root(rr->srv.name)) {
+ has_root_domain = true;
+ continue;
+ }
- if (r < 0)
- goto finish;
- }
+ if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) {
+ q->block_all_complete ++;
+ r = resolve_service_hostname(q, rr, ifindex);
+ q->block_all_complete --;
- found++;
+ if (r < 0)
+ goto finish;
}
- 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;
- }
+ 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_query_string(q));
+ goto finish;
}
if (found <= 0) {
- r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question));
+ r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q));
goto finish;
}
@@ -1045,8 +1041,8 @@ finish:
}
static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) {
- _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
- const char *name, *type, *domain, *joined;
+ _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL;
+ const char *name, *type, *domain;
_cleanup_free_ char *n = NULL;
Manager *m = userdata;
int family, ifindex;
@@ -1068,10 +1064,8 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s
if (isempty(name))
name = NULL;
- else {
- if (!dns_service_name_is_valid(name))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name);
- }
+ else if (!dns_service_name_is_valid(name))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name);
if (isempty(type))
type = NULL;
@@ -1091,23 +1085,15 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s
if (r < 0)
return r;
- if (type) {
- /* If the type is specified, we generate the full domain name to look up ourselves */
- r = dns_service_join(name, type, domain, &n);
- if (r < 0)
- return r;
-
- joined = n;
- } else
- /* If no type is specified, we assume the domain
- * contains the full domain name to lookup already */
- joined = domain;
+ r = dns_question_new_service(&question_utf8, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), false);
+ if (r < 0)
+ return r;
- r = dns_question_new_service(&question, joined, !(flags & SD_RESOLVED_NO_TXT));
+ r = dns_question_new_service(&question_idna, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), true);
if (r < 0)
return r;
- r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH);
+ r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH);
if (r < 0)
return r;
diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c
index b50558e280..f74e440531 100644
--- a/src/resolve/resolved-dns-answer.c
+++ b/src/resolve/resolved-dns-answer.c
@@ -320,6 +320,33 @@ int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) {
return false;
}
+int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone) {
+ DnsResourceRecord *rr;
+ int r;
+
+ /* Checks whether the specified answer contains at least one NSEC3 RR in the specified zone */
+
+ DNS_ANSWER_FOREACH(rr, answer) {
+ const char *p;
+
+ if (rr->key->type != DNS_TYPE_NSEC3)
+ continue;
+
+ p = DNS_RESOURCE_KEY_NAME(rr->key);
+ r = dns_name_parent(&p);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = dns_name_equal(p, zone);
+ if (r != 0)
+ return r;
+ }
+
+ 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;
@@ -794,3 +821,40 @@ void dns_answer_dump(DnsAnswer *answer, FILE *f) {
fputc('\n', f);
}
}
+
+bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname) {
+ DnsResourceRecord *rr;
+ int r;
+
+ assert(cname);
+
+ /* Checks whether the answer contains a DNAME record that indicates that the specified CNAME record is
+ * synthesized from it */
+
+ if (cname->key->type != DNS_TYPE_CNAME)
+ return 0;
+
+ DNS_ANSWER_FOREACH(rr, a) {
+ _cleanup_free_ char *n = NULL;
+
+ if (rr->key->type != DNS_TYPE_DNAME)
+ continue;
+ if (rr->key->class != cname->key->class)
+ continue;
+
+ r = dns_name_change_suffix(cname->cname.name, rr->dname.name, DNS_RESOURCE_KEY_NAME(rr->key), &n);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ r = dns_name_equal(n, DNS_RESOURCE_KEY_NAME(cname->key));
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return 1;
+
+ }
+
+ return 0;
+}
diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h
index 715e487d94..1875fd6136 100644
--- a/src/resolve/resolved-dns-answer.h
+++ b/src/resolve/resolved-dns-answer.h
@@ -64,6 +64,7 @@ int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags
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_contains_zone_nsec3(DnsAnswer *answer, const char *zone);
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);
@@ -82,6 +83,8 @@ 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);
+bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname);
+
static inline unsigned dns_answer_size(DnsAnswer *a) {
return a ? a->n_rrs : 0;
}
diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c
index 301f383809..fdb34d11df 100644
--- a/src/resolve/resolved-dns-cache.c
+++ b/src/resolve/resolved-dns-cache.c
@@ -247,6 +247,19 @@ static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) {
first = hashmap_get(c->by_key, i->key);
if (first) {
+ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
+
+ /* Keep a reference to the original key, while we manipulate the list. */
+ k = dns_resource_key_ref(first->key);
+
+ /* Now, try to reduce the number of keys we keep */
+ dns_resource_key_reduce(&first->key, &i->key);
+
+ if (first->rr)
+ dns_resource_key_reduce(&first->rr->key, &i->key);
+ if (i->rr)
+ dns_resource_key_reduce(&i->rr->key, &i->key);
+
LIST_PREPEND(by_key, first, i);
assert_se(hashmap_replace(c->by_key, first->key, first) >= 0);
} else {
diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c
index 43fcbe1460..1f48f588ce 100644
--- a/src/resolve/resolved-dns-dnssec.c
+++ b/src/resolve/resolved-dns-dnssec.c
@@ -35,17 +35,12 @@
*
* TODO:
*
- * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing)
- * - multi-label zone compatibility
- * - cname/dname compatibility
- * - nxdomain on qname
* - bus calls to override DNSEC setting per interface
* - log all DNSSEC downgrades
+ * - log all RRs that failed validation
* - enable by default
- *
- * - RFC 4035, Section 5.3.4 (When receiving a positive wildcard reply, use NSEC to ensure it actually really applies)
- * - RFC 6840, Section 4.1 (ensure we don't get fed a glue NSEC from the parent zone)
- * - RFC 6840, Section 4.3 (check for CNAME on NSEC too)
+ * - Allow clients to request DNSSEC even if DNSSEC is off
+ * - make sure when getting an NXDOMAIN response through CNAME, we still process the first CNAMEs in the packet
* */
#define VERIFY_RRS_MAX 256
@@ -430,6 +425,57 @@ static void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
gcry_md_write(md, &v, sizeof(v));
}
+static int dnssec_rrsig_prepare(DnsResourceRecord *rrsig) {
+ int n_key_labels, n_signer_labels;
+ const char *name;
+ int r;
+
+ /* Checks whether the specified RRSIG RR is somewhat valid, and initializes the .n_skip_labels_source and
+ * .n_skip_labels_signer fields so that we can use them later on. */
+
+ assert(rrsig);
+ assert(rrsig->key->type == DNS_TYPE_RRSIG);
+
+ /* Check if this RRSIG RR is already prepared */
+ if (rrsig->n_skip_labels_source != (unsigned) -1)
+ return 0;
+
+ if (rrsig->rrsig.inception > rrsig->rrsig.expiration)
+ return -EINVAL;
+
+ name = DNS_RESOURCE_KEY_NAME(rrsig->key);
+
+ n_key_labels = dns_name_count_labels(name);
+ if (n_key_labels < 0)
+ return n_key_labels;
+ if (rrsig->rrsig.labels > n_key_labels)
+ return -EINVAL;
+
+ n_signer_labels = dns_name_count_labels(rrsig->rrsig.signer);
+ if (n_signer_labels < 0)
+ return n_signer_labels;
+ if (n_signer_labels > rrsig->rrsig.labels)
+ return -EINVAL;
+
+ r = dns_name_skip(name, n_key_labels - n_signer_labels, &name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+
+ /* Check if the signer is really a suffix of us */
+ r = dns_name_equal(name, rrsig->rrsig.signer);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+
+ rrsig->n_skip_labels_source = n_key_labels - rrsig->rrsig.labels;
+ rrsig->n_skip_labels_signer = n_key_labels - n_signer_labels;
+
+ return 0;
+}
+
static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
usec_t expiration, inception, skew;
@@ -442,8 +488,9 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
inception = rrsig->rrsig.inception * USEC_PER_SEC;
+ /* Consider inverted validity intervals as expired */
if (inception > expiration)
- return -EKEYREJECTED;
+ return true;
/* Permit a certain amount of clock skew of 10% of the valid
* time range. This takes inspiration from unbound's
@@ -498,6 +545,35 @@ static int algorithm_to_gcrypt_md(uint8_t algorithm) {
}
}
+static void dnssec_fix_rrset_ttl(
+ DnsResourceRecord *list[],
+ unsigned n,
+ DnsResourceRecord *rrsig,
+ usec_t realtime) {
+
+ unsigned k;
+
+ assert(list);
+ assert(n > 0);
+ assert(rrsig);
+
+ for (k = 0; k < n; k++) {
+ DnsResourceRecord *rr = list[k];
+
+ /* 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;
+
+ /* Copy over information about the signer and wildcard source of synthesis */
+ rr->n_skip_labels_source = rrsig->n_skip_labels_source;
+ rr->n_skip_labels_signer = rrsig->n_skip_labels_signer;
+ }
+
+ rrsig->expiry = rrsig->rrsig.expiration * USEC_PER_SEC;
+}
+
int dnssec_verify_rrset(
DnsAnswer *a,
const DnsResourceKey *key,
@@ -507,13 +583,14 @@ int dnssec_verify_rrset(
DnssecResult *result) {
uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
- size_t hash_size;
- void *hash;
DnsResourceRecord **list, *rr;
+ const char *source, *name;
gcry_md_hd_t md = NULL;
int r, md_algorithm;
- bool wildcard = false;
size_t k, n = 0;
+ size_t hash_size;
+ void *hash;
+ bool wildcard;
assert(key);
assert(rrsig);
@@ -534,6 +611,14 @@ int dnssec_verify_rrset(
if (md_algorithm < 0)
return md_algorithm;
+ r = dnssec_rrsig_prepare(rrsig);
+ if (r == -EINVAL) {
+ *result = DNSSEC_INVALID;
+ return r;
+ }
+ if (r < 0)
+ return r;
+
r = dnssec_rrsig_expired(rrsig, realtime);
if (r < 0)
return r;
@@ -542,6 +627,52 @@ int dnssec_verify_rrset(
return 0;
}
+ name = DNS_RESOURCE_KEY_NAME(key);
+
+ /* Some keys may only appear signed in the zone apex, and are invalid anywhere else. (SOA, NS...) */
+ if (dns_type_apex_only(rrsig->rrsig.type_covered)) {
+ r = dns_name_equal(rrsig->rrsig.signer, name);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ *result = DNSSEC_INVALID;
+ return 0;
+ }
+ }
+
+ /* OTOH DS RRs may not appear in the zone apex, but are valid everywhere else. */
+ if (rrsig->rrsig.type_covered == DNS_TYPE_DS) {
+ r = dns_name_equal(rrsig->rrsig.signer, name);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ *result = DNSSEC_INVALID;
+ return 0;
+ }
+ }
+
+ /* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */
+ r = dns_name_suffix(name, rrsig->rrsig.labels, &source);
+ if (r < 0)
+ return r;
+ if (r > 0 && !dns_type_may_wildcard(rrsig->rrsig.type_covered)) {
+ /* We refuse to validate NSEC3 or SOA RRs that are synthesized from wildcards */
+ *result = DNSSEC_INVALID;
+ return 0;
+ }
+ if (r == 1) {
+ /* If we stripped a single label, then let's see if that maybe was "*". If so, we are not really
+ * synthesized from a wildcard, we are the wildcard itself. Treat that like a normal name. */
+ r = dns_name_startswith(name, "*");
+ if (r < 0)
+ return r;
+ if (r > 0)
+ source = name;
+
+ wildcard = r == 0;
+ } else
+ wildcard = r > 0;
+
/* Collect all relevant RRs in a single array, so that we can look at the RRset */
list = newa(DnsResourceRecord *, dns_answer_size(a));
@@ -592,22 +723,19 @@ int dnssec_verify_rrset(
goto finish;
gcry_md_write(md, wire_format_name, r);
+ /* Convert the source of synthesis into wire format */
+ r = dns_name_to_wire_format(source, wire_format_name, sizeof(wire_format_name), true);
+ if (r < 0)
+ goto finish;
+
for (k = 0; k < n; k++) {
- const char *suffix;
size_t l;
+
rr = list[k];
- 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! */ {
+ /* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */
+ if (wildcard)
gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2);
- wildcard = true;
- }
-
- 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);
md_add_uint16(md, rr->key->type);
@@ -654,12 +782,17 @@ int dnssec_verify_rrset(
if (r < 0)
goto finish;
- if (!r)
+ /* Now, fix the ttl, expiry, and remember the synthesizing source and the signer */
+ if (r > 0)
+ dnssec_fix_rrset_ttl(list, n, rrsig, realtime);
+
+ if (r == 0)
*result = DNSSEC_INVALID;
else if (wildcard)
*result = DNSSEC_VALIDATED_WILDCARD;
else
*result = DNSSEC_VALIDATED;
+
r = 0;
finish:
@@ -698,8 +831,6 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske
}
int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) {
- int r;
-
assert(key);
assert(rrsig);
@@ -712,45 +843,9 @@ int dnssec_key_match_rrsig(const 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,
const DnsResourceKey *key,
@@ -820,10 +915,6 @@ int dnssec_verify_rrset_search(
case DNSSEC_VALIDATED_WILDCARD:
/* 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;
-
if (ret_rrsig)
*ret_rrsig = rrsig;
@@ -911,16 +1002,6 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
return r;
if (r == 0)
break;
- if (r > 0) {
- int k;
-
- /* DNSSEC validation is always done on the ASCII version of the label */
- k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
- if (k < 0)
- return k;
- if (k > 0)
- r = k;
- }
if (buffer_max < (size_t) r + 2)
return -ENOBUFS;
@@ -974,7 +1055,7 @@ static int digest_to_gcrypt_md(uint8_t algorithm) {
}
}
-int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
+int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) {
char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
gcry_md_hd_t md = NULL;
size_t hash_size;
@@ -1044,7 +1125,7 @@ finish:
return r;
}
-int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
+int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) {
DnsResourceRecord *ds;
DnsAnswerFlags flags;
int r;
@@ -1061,7 +1142,6 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_
if (ds->key->type != DNS_TYPE_DS)
continue;
-
if (ds->key->class != dnskey->key->class)
continue;
@@ -1071,9 +1151,9 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_
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 */
+ r = dnssec_verify_dnskey_by_ds(dnskey, ds, false);
+ if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP))
+ return 0; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */
if (r < 0)
return r;
if (r > 0)
@@ -1190,6 +1270,13 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) {
if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX)
return 0;
+ /* Ignore NSEC3 RRs generated from wildcards */
+ if (rr->n_skip_labels_source != 0)
+ return 0;
+ /* Ignore NSEC3 RRs that are located anywhere else than one label below the zone */
+ if (rr->n_skip_labels_signer != 1)
+ return 0;
+
if (!nsec3)
return 1;
@@ -1223,6 +1310,7 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) {
if (r == 0)
return 0;
+ /* Make sure both have the same parent */
return dns_name_equal(a, b);
}
@@ -1274,8 +1362,8 @@ static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain
* 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;
+ _cleanup_free_ char *next_closer_domain = NULL, *wildcard_domain = NULL;
+ const char *zone, *p, *pp = NULL, *wildcard;
DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL;
DnsAnswerFlags flags;
int hashed_size, r;
@@ -1401,10 +1489,7 @@ found_closest_encloser:
/* Prove that there is no next closer and whether or not there is a wildcard domain. */
- wildcard = strappend("*.", p);
- if (!wildcard)
- return -ENOMEM;
-
+ wildcard = strjoina("*.", p);
r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain);
if (r < 0)
return r;
@@ -1517,10 +1602,158 @@ found_closest_encloser:
return 0;
}
+static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) {
+ char label[DNS_LABEL_MAX];
+ const char *n;
+ int r;
+
+ assert(rr);
+ assert(rr->key->type == DNS_TYPE_NSEC);
+
+ /* Checks whether the specified RR has a name beginning in "*.", and if the rest is a suffix of our name */
+
+ if (rr->n_skip_labels_source != 1)
+ return 0;
+
+ n = DNS_RESOURCE_KEY_NAME(rr->key);
+ r = dns_label_unescape(&n, label, sizeof(label));
+ if (r <= 0)
+ return r;
+ if (r != 1 || label[0] != '*')
+ return 0;
+
+ return dns_name_endswith(name, n);
+}
+
+static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) {
+ const char *nn, *common_suffix;
+ int r;
+
+ assert(rr);
+ assert(rr->key->type == DNS_TYPE_NSEC);
+
+ /* Checks whether the specified nsec RR indicates that name is an empty non-terminal (ENT)
+ *
+ * A couple of examples:
+ *
+ * NSEC bar → waldo.foo.bar: indicates that foo.bar exists and is an ENT
+ * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that xoo.bar and zzz.xoo.bar exist and are ENTs
+ * NSEC yyy.zzz.xoo.bar → bar: indicates pretty much nothing about ENTs
+ */
+
+ /* First, determine parent of next domain. */
+ nn = rr->nsec.next_domain_name;
+ r = dns_name_parent(&nn);
+ if (r <= 0)
+ return r;
+
+ /* If the name we just determined is not equal or child of the name we are interested in, then we can't say
+ * anything at all. */
+ r = dns_name_endswith(nn, name);
+ if (r <= 0)
+ return r;
+
+ /* If the name we we are interested in is not a prefix of the common suffix of the NSEC RR's owner and next domain names, then we can't say anything either. */
+ r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix);
+ if (r < 0)
+ return r;
+
+ return dns_name_endswith(name, common_suffix);
+}
+
+static int dnssec_nsec_from_parent_zone(DnsResourceRecord *rr, const char *name) {
+ int r;
+
+ assert(rr);
+ assert(rr->key->type == DNS_TYPE_NSEC);
+
+ /* Checks whether this NSEC originates to the parent zone or the child zone. */
+
+ r = dns_name_parent(&name);
+ if (r <= 0)
+ return r;
+
+ r = dns_name_equal(name, DNS_RESOURCE_KEY_NAME(rr->key));
+ if (r <= 0)
+ return r;
+
+ /* DNAME, and NS without SOA is an indication for a delegation. */
+ if (bitmap_isset(rr->nsec.types, DNS_TYPE_DNAME))
+ return 1;
+
+ if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA))
+ return 1;
+
+ return 0;
+}
+
+static int dnssec_nsec_covers(DnsResourceRecord *rr, const char *name) {
+ const char *common_suffix, *p;
+ int r;
+
+ assert(rr);
+ assert(rr->key->type == DNS_TYPE_NSEC);
+
+ /* Checks whether the "Next Closer" is witin the space covered by the specified RR. */
+
+ r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix);
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ p = name;
+ r = dns_name_parent(&name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 0;
+
+ r = dns_name_equal(name, common_suffix);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ break;
+ }
+
+ /* p is now the "Next Closer". */
+
+ return dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), p, rr->nsec.next_domain_name);
+}
+
+static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) {
+ const char *common_suffix, *wc;
+ int r;
+
+ assert(rr);
+ assert(rr->key->type == DNS_TYPE_NSEC);
+
+ /* Checks whether the "Wildcard at the Closest Encloser" is within the space covered by the specified
+ * RR. Specifically, checks whether 'name' has the common suffix of the NSEC RR's owner and next names as
+ * suffix, and whether the NSEC covers the name generated by that suffix prepended with an asterisk label.
+ *
+ * NSEC bar → waldo.foo.bar: indicates that *.bar and *.foo.bar do not exist
+ * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that *.xoo.bar and *.zzz.xoo.bar do not exist (and more ...)
+ * NSEC yyy.zzz.xoo.bar → bar: indicates that a number of wildcards don#t exist either...
+ */
+
+ r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix);
+ if (r < 0)
+ return r;
+
+ /* If the common suffix is not shared by the name we are interested in, it has nothing to say for us. */
+ r = dns_name_endswith(name, common_suffix);
+ if (r <= 0)
+ return r;
+
+ wc = strjoina("*.", common_suffix, NULL);
+ return dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wc, rr->nsec.next_domain_name);
+}
+
int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) {
- DnsResourceRecord *rr;
- bool have_nsec3 = false;
+ bool have_nsec3 = false, covering_rr_authenticated = false, wildcard_rr_authenticated = false;
+ DnsResourceRecord *rr, *covering_rr = NULL, *wildcard_rr = NULL;
DnsAnswerFlags flags;
+ const char *name;
int r;
assert(key);
@@ -1528,53 +1761,117 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
/* Look for any NSEC/NSEC3 RRs that say something about the specified key. */
+ name = DNS_RESOURCE_KEY_NAME(key);
+
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
if (rr->key->class != key->class)
continue;
- switch (rr->key->type) {
+ have_nsec3 = have_nsec3 || (rr->key->type == DNS_TYPE_NSEC3);
- case DNS_TYPE_NSEC:
+ if (rr->key->type != DNS_TYPE_NSEC)
+ continue;
+
+ /* The following checks only make sense for NSEC RRs that are not expanded from a wildcard */
+ r = dns_resource_record_is_synthetic(rr);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
- r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key));
+ /* Check if this is a direct match. If so, we have encountered a NODATA case */
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), name);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ /* If it's not a direct match, maybe it's a wild card match? */
+ r = dnssec_nsec_wildcard_equal(rr, name);
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;
+ }
+ if (r > 0) {
+ if (key->type == DNS_TYPE_DS) {
+ /* If we look for a DS RR and the server sent us the NSEC RR of the child zone
+ * we have a problem. For DS RRs we want the NSEC RR from the parent */
+ if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA))
+ continue;
+ } else {
+ /* For all RR types, ensure that if NS is set SOA is set too, so that we know
+ * we got the child's NSEC. */
+ if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) &&
+ !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA))
+ continue;
}
- 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 (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;
+ if (authenticated)
+ *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+ if (ttl)
+ *ttl = rr->ttl;
- return 0;
- }
- break;
+ return 0;
+ }
- case DNS_TYPE_NSEC3:
- have_nsec3 = true;
- break;
+ /* Check if the name we are looking for is an empty non-terminal within the owner or next name
+ * of the NSEC RR. */
+ r = dnssec_nsec_in_path(rr, name);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ *result = DNSSEC_NSEC_NODATA;
+
+ if (authenticated)
+ *authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+ if (ttl)
+ *ttl = rr->ttl;
+
+ return 0;
}
+
+ /* The following two "covering" checks, are not useful if the NSEC is from the parent */
+ r = dnssec_nsec_from_parent_zone(rr, name);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
+ /* Check if this NSEC RR proves the absence of an explicit RR under this name */
+ r = dnssec_nsec_covers(rr, name);
+ if (r < 0)
+ return r;
+ if (r > 0 && (!covering_rr || !covering_rr_authenticated)) {
+ covering_rr = rr;
+ covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+ }
+
+ /* Check if this NSEC RR proves the absence of a wildcard RR under this name */
+ r = dnssec_nsec_covers_wildcard(rr, name);
+ if (r < 0)
+ return r;
+ if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) {
+ wildcard_rr = rr;
+ wildcard_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED;
+ }
+ }
+
+ if (covering_rr && wildcard_rr) {
+ /* If we could prove that neither the name itself, nor the wildcard at the closest encloser exists, we
+ * proved the NXDOMAIN case. */
+ *result = DNSSEC_NSEC_NXDOMAIN;
+
+ if (authenticated)
+ *authenticated = covering_rr_authenticated && wildcard_rr_authenticated;
+ if (ttl)
+ *ttl = MIN(covering_rr->ttl, wildcard_rr->ttl);
+
+ return 0;
}
/* OK, this was not sufficient. Let's see if NSEC3 can help. */
@@ -1586,7 +1883,7 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
return 0;
}
-int dnssec_nsec_test_between(DnsAnswer *answer, const char *name, const char *zone, bool *authenticated) {
+int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) {
DnsResourceRecord *rr;
DnsAnswerFlags flags;
int r;
@@ -1600,15 +1897,20 @@ int dnssec_nsec_test_between(DnsAnswer *answer, const char *name, const char *zo
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
bool found = false;
- r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), zone);
- if (r < 0)
- return r;
- if (r == 0)
+ if (rr->key->type != type && type != DNS_TYPE_ANY)
continue;
switch (rr->key->type) {
case DNS_TYPE_NSEC:
+
+ /* We only care for NSEC RRs from the indicated zone */
+ r = dns_resource_record_is_signer(rr, zone);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name);
if (r < 0)
return r;
@@ -1619,6 +1921,13 @@ int dnssec_nsec_test_between(DnsAnswer *answer, const char *name, const char *zo
case DNS_TYPE_NSEC3: {
_cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL;
+ /* We only care for NSEC3 RRs from the indicated zone */
+ r = dns_resource_record_is_signer(rr, zone);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
r = nsec3_is_good(rr, NULL);
if (r < 0)
return r;
@@ -1667,6 +1976,145 @@ int dnssec_nsec_test_between(DnsAnswer *answer, const char *name, const char *zo
return 0;
}
+static int dnssec_test_positive_wildcard_nsec3(
+ DnsAnswer *answer,
+ const char *name,
+ const char *source,
+ const char *zone,
+ bool *authenticated) {
+
+ const char *next_closer = NULL;
+ int r;
+
+ /* Run a positive NSEC3 wildcard proof. Specifically:
+ *
+ * A proof that the the "next closer" of the generating wildcard does not exist.
+ *
+ * Note a key difference between the NSEC3 and NSEC versions of the proof. NSEC RRs don't have to exist for
+ * empty non-transients. NSEC3 RRs however have to. This means it's sufficient to check if the next closer name
+ * exists for the NSEC3 RR and we are done.
+ *
+ * To prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f all we have to check is that
+ * c.d.e.f does not exist. */
+
+ for (;;) {
+ next_closer = name;
+ r = dns_name_parent(&name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 0;
+
+ r = dns_name_equal(name, source);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ break;
+ }
+
+ return dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC3, next_closer, zone, authenticated);
+}
+
+static int dnssec_test_positive_wildcard_nsec(
+ DnsAnswer *answer,
+ const char *name,
+ const char *source,
+ const char *zone,
+ bool *_authenticated) {
+
+ bool authenticated = true;
+ int r;
+
+ /* Run a positive NSEC wildcard proof. Specifically:
+ *
+ * A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and
+ * a prefix of the synthesizing source "source" in the zone "zone".
+ *
+ * See RFC 5155, Section 8.8 and RFC 4035, Section 5.3.4
+ *
+ * Note that if we want to prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f, then we
+ * have to prove that none of the following exist:
+ *
+ * 1) a.b.c.d.e.f
+ * 2) *.b.c.d.e.f
+ * 3) b.c.d.e.f
+ * 4) *.c.d.e.f
+ * 5) c.d.e.f
+ *
+ */
+
+ for (;;) {
+ _cleanup_free_ char *wc = NULL;
+ bool a = false;
+
+ /* Check if there's an NSEC or NSEC3 RR that proves that the mame we determined is really non-existing,
+ * i.e between the owner name and the next name of an NSEC RR. */
+ r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, name, zone, &a);
+ if (r <= 0)
+ return r;
+
+ authenticated = authenticated && a;
+
+ /* Strip one label off */
+ r = dns_name_parent(&name);
+ if (r <= 0)
+ return r;
+
+ /* Did we reach the source of synthesis? */
+ r = dns_name_equal(name, source);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* Successful exit */
+ *_authenticated = authenticated;
+ return 1;
+ }
+
+ /* Safety check, that the source of synthesis is still our suffix */
+ r = dns_name_endswith(name, source);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EBADMSG;
+
+ /* Replace the label we stripped off with an asterisk */
+ wc = strappend("*.", name);
+ if (!wc)
+ return -ENOMEM;
+
+ /* And check if the proof holds for the asterisk name, too */
+ r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, wc, zone, &a);
+ if (r <= 0)
+ return r;
+
+ authenticated = authenticated && a;
+ /* In the next iteration we'll check the non-asterisk-prefixed version */
+ }
+}
+
+int dnssec_test_positive_wildcard(
+ DnsAnswer *answer,
+ const char *name,
+ const char *source,
+ const char *zone,
+ bool *authenticated) {
+
+ int r;
+
+ assert(name);
+ assert(source);
+ assert(zone);
+ assert(authenticated);
+
+ r = dns_answer_contains_zone_nsec3(answer, zone);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return dnssec_test_positive_wildcard_nsec3(answer, name, source, zone, authenticated);
+ else
+ return dnssec_test_positive_wildcard_nsec(answer, name, source, zone, authenticated);
+}
+
static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_VALIDATED] = "validated",
[DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard",
diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h
index 8a9bcf5b91..955017e8cb 100644
--- a/src/resolve/resolved-dns-dnssec.h
+++ b/src/resolve/resolved-dns-dnssec.h
@@ -61,8 +61,8 @@ int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig);
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, DnsResourceRecord **rrsig);
-int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke);
-int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds);
+int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke);
+int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds);
int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key);
@@ -83,7 +83,10 @@ typedef enum DnssecNsecResult {
} DnssecNsecResult;
int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl);
-int dnssec_nsec_test_between(DnsAnswer *answer, const char *name, const char *zone, bool *authenticated);
+
+int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated);
+
+int dnssec_test_positive_wildcard(DnsAnswer *a, const char *name, const char *source, const char *zone, bool *authenticated);
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 a8a8632491..9a5223ef01 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -499,7 +499,6 @@ int dns_packet_append_name(
const char *z = name;
char label[DNS_LABEL_MAX];
size_t n = 0;
- int k;
if (allow_compression)
n = PTR_TO_SIZE(hashmap_get(p->names, name));
@@ -519,17 +518,6 @@ int dns_packet_append_name(
if (r < 0)
goto fail;
- if (p->protocol == DNS_PROTOCOL_DNS)
- k = dns_label_apply_idna(label, r, label, sizeof(label));
- else
- k = dns_label_undo_idna(label, r, label, sizeof(label));
- if (k < 0) {
- r = k;
- goto fail;
- }
- if (k > 0)
- r = k;
-
r = dns_packet_append_label(p, label, r, canonical_candidate, &n);
if (r < 0)
goto fail;
@@ -1083,7 +1071,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star
/* Let's calculate the actual data size and update the field */
rdlength = p->size - rdlength_offset - sizeof(uint16_t);
if (rdlength > 0xFFFF) {
- r = ENOSPC;
+ r = -ENOSPC;
goto fail;
}
@@ -2017,6 +2005,48 @@ fail:
return r;
}
+static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) {
+ const uint8_t* p;
+ bool found_dau_dhu_n3u = false;
+ size_t l;
+
+ /* Checks whether the specified OPT RR is well-formed and whether it contains RFC6975 data (which is not OK in
+ * a reply). */
+
+ assert(rr);
+ assert(rr->key->type == DNS_TYPE_OPT);
+
+ /* Check that the version is 0 */
+ if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0)
+ return false;
+
+ p = rr->opt.data;
+ l = rr->opt.size;
+ while (l > 0) {
+ uint16_t option_code, option_length;
+
+ /* At least four bytes for OPTION-CODE and OPTION-LENGTH are required */
+ if (l < 4U)
+ return false;
+
+ option_code = unaligned_read_be16(p);
+ option_length = unaligned_read_be16(p + 2);
+
+ if (l < option_length + 4U)
+ return false;
+
+ /* RFC 6975 DAU, DHU or N3U fields found. */
+ if (IN_SET(option_code, 5, 6, 7))
+ found_dau_dhu_n3u = true;
+
+ p += option_length + 4U;
+ l -= option_length + 4U;
+ }
+
+ *rfc6975 = found_dau_dhu_n3u;
+ return true;
+}
+
int dns_packet_extract(DnsPacket *p) {
_cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
@@ -2064,6 +2094,9 @@ int dns_packet_extract(DnsPacket *p) {
n = DNS_PACKET_RRCOUNT(p);
if (n > 0) {
+ DnsResourceRecord *previous = NULL;
+ bool bad_opt = false;
+
answer = dns_answer_new(n);
if (!answer) {
r = -ENOMEM;
@@ -2078,36 +2111,62 @@ int dns_packet_extract(DnsPacket *p) {
if (r < 0)
goto finish;
+ /* Try to reduce memory usage a bit */
+ if (previous)
+ dns_resource_key_reduce(&rr->key, &previous->key);
+
if (rr->key->type == DNS_TYPE_OPT) {
+ bool has_rfc6975;
+
+ if (p->opt || bad_opt) {
+ /* Multiple OPT RRs? if so, let's ignore all, because there's something wrong
+ * with the server, and if one is valid we wouldn't know which one. */
+ log_debug("Multiple OPT RRs detected, ignoring all.");
+ bad_opt = true;
+ continue;
+ }
if (!dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key))) {
- r = -EBADMSG;
- goto finish;
+ /* If the OPT RR qis not owned by the root domain, then it is bad, let's ignore
+ * it. */
+ log_debug("OPT RR is not owned by root domain, ignoring.");
+ bad_opt = true;
+ continue;
}
- /* Note that we accept the OPT RR in
- * any section, not just in the
- * additional section, as some routers
- * (Belkin!) blindly copy the OPT RR
- * from the query to the reply packet,
- * and don't get the section right. */
+ if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) {
+ /* OPT RR is in the wrong section? Some Belkin routers do this. This is a hint
+ * the EDNS implementation is borked, like the Belkin one is, hence ignore
+ * it. */
+ log_debug("OPT RR in wrong section, ignoring.");
+ bad_opt = true;
+ continue;
+ }
- /* Two OPT RRs? */
- if (p->opt) {
- r = -EBADMSG;
- goto finish;
+ if (!opt_is_good(rr, &has_rfc6975)) {
+ log_debug("Malformed OPT RR, ignoring.");
+ bad_opt = true;
+ continue;
+ }
+
+ if (has_rfc6975) {
+ /* OPT RR contains RFC6975 algorithm data, then this is indication that the
+ * server just copied the OPT it got from us (which contained that data) back
+ * into the reply. If so, then it doesn't properly support EDNS, as RFC6975
+ * makes it very clear that the algorithm data should only be contained in
+ * questions, never in replies. Crappy Belkin copy the OPT data for example,
+ * hence let's detect this so that we downgrade early. */
+ log_debug("OPT RR contained RFC6975 data, ignoring.");
+ bad_opt = true;
+ continue;
}
p->opt = dns_resource_record_ref(rr);
} else {
- /* 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. */
+ /* 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) |
@@ -2116,6 +2175,9 @@ int dns_packet_extract(DnsPacket *p) {
goto finish;
}
}
+
+ if (bad_opt)
+ p->opt = dns_resource_record_unref(p->opt);
}
p->question = question;
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index 6821be73e4..c53431576b 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -247,7 +247,7 @@ DnsProtocol dns_protocol_from_string(const char *s) _pure_;
#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } })
#define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) })
-#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xfb } })
+#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb } })
static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) {
uint64_t f;
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
index 1948d59fc4..fc5bf4020f 100644
--- a/src/resolve/resolved-dns-query.c
+++ b/src/resolve/resolved-dns-query.c
@@ -24,6 +24,7 @@
#include "hostname-util.h"
#include "local-addresses.h"
#include "resolved-dns-query.h"
+#include "string-util.h"
/* How long to wait for the query in total */
#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC)
@@ -217,6 +218,7 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) {
}
static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
+ DnsQuestion *question;
DnsResourceKey *key;
int n = 0, r;
@@ -224,8 +226,10 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) {
dns_query_candidate_stop(c);
+ question = dns_query_question_for_protocol(c->query, c->scope->protocol);
+
/* Create one transaction per question key */
- DNS_QUESTION_FOREACH(key, c->query->question) {
+ DNS_QUESTION_FOREACH(key, question) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL;
if (c->search_domain) {
@@ -305,6 +309,25 @@ static void dns_query_stop(DnsQuery *q) {
dns_query_candidate_stop(c);
}
+static void dns_query_free_candidates(DnsQuery *q) {
+ assert(q);
+
+ while (q->candidates)
+ dns_query_candidate_free(q->candidates);
+}
+
+static void dns_query_reset_answer(DnsQuery *q) {
+ assert(q);
+
+ q->answer = dns_answer_unref(q->answer);
+ q->answer_rcode = 0;
+ q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
+ q->answer_authenticated = false;
+ q->answer_protocol = _DNS_PROTOCOL_INVALID;
+ q->answer_family = AF_UNSPEC;
+ q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain);
+}
+
DnsQuery *dns_query_free(DnsQuery *q) {
if (!q)
return NULL;
@@ -318,16 +341,18 @@ DnsQuery *dns_query_free(DnsQuery *q) {
LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q);
}
- while (q->candidates)
- dns_query_candidate_free(q->candidates);
+ dns_query_free_candidates(q);
- dns_question_unref(q->question);
- dns_answer_unref(q->answer);
- dns_search_domain_unref(q->answer_search_domain);
+ dns_question_unref(q->question_idna);
+ dns_question_unref(q->question_utf8);
+
+ dns_query_reset_answer(q);
sd_bus_message_unref(q->request);
sd_bus_track_unref(q->bus_track);
+ free(q->request_address_string);
+
if (q->manager) {
LIST_REMOVE(queries, q->manager->dns_queries, q);
q->manager->n_dns_queries--;
@@ -338,17 +363,50 @@ DnsQuery *dns_query_free(DnsQuery *q) {
return NULL;
}
-int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex, uint64_t flags) {
+int dns_query_new(
+ Manager *m,
+ DnsQuery **ret,
+ DnsQuestion *question_utf8,
+ DnsQuestion *question_idna,
+ int ifindex, uint64_t flags) {
+
_cleanup_(dns_query_freep) DnsQuery *q = NULL;
- unsigned i;
+ DnsResourceKey *key;
+ bool good = false;
int r;
assert(m);
- assert(question);
- r = dns_question_is_valid_for_query(question);
+ if (dns_question_size(question_utf8) > 0) {
+ r = dns_question_is_valid_for_query(question_utf8);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+
+ good = true;
+ }
+
+ /* If the IDNA and UTF8 questions are the same, merge their references */
+ r = dns_question_is_equal(question_idna, question_utf8);
if (r < 0)
return r;
+ if (r > 0)
+ question_idna = question_utf8;
+ else {
+ if (dns_question_size(question_idna) > 0) {
+ r = dns_question_is_valid_for_query(question_idna);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+
+ good = true;
+ }
+ }
+
+ if (!good) /* don't allow empty queries */
+ return -EINVAL;
if (m->n_dns_queries >= QUERIES_MAX)
return -EBUSY;
@@ -357,20 +415,40 @@ int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex
if (!q)
return -ENOMEM;
- q->question = dns_question_ref(question);
+ q->question_utf8 = dns_question_ref(question_utf8);
+ q->question_idna = dns_question_ref(question_idna);
q->ifindex = ifindex;
q->flags = flags;
- q->answer_family = AF_UNSPEC;
+ q->answer_dnssec_result = _DNSSEC_RESULT_INVALID;
q->answer_protocol = _DNS_PROTOCOL_INVALID;
+ q->answer_family = AF_UNSPEC;
+
+ /* First dump UTF8 question */
+ DNS_QUESTION_FOREACH(key, question_utf8) {
+ _cleanup_free_ char *p = NULL;
+
+ r = dns_resource_key_to_string(key, &p);
+ if (r < 0)
+ return r;
- for (i = 0; i < question->n_keys; i++) {
- _cleanup_free_ char *p;
+ log_debug("Looking up RR for %s.", strstrip(p));
+ }
+
+ /* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */
+ DNS_QUESTION_FOREACH(key, question_idna) {
+ _cleanup_free_ char *p = NULL;
+
+ r = dns_question_contains(question_utf8, key);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
- r = dns_resource_key_to_string(question->keys[i], &p);
+ r = dns_resource_key_to_string(key, &p);
if (r < 0)
return r;
- log_debug("Looking up RR for %s", p);
+ log_debug("Looking up IDNA RR for %s.", strstrip(p));
}
LIST_PREPEND(queries, m->dns_queries, q);
@@ -446,7 +524,7 @@ static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) {
/* If this a single-label domain on DNS, we might append a suitable search domain first. */
if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0) {
- r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question));
+ r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question_idna));
if (r < 0)
goto fail;
if (r > 0) {
@@ -534,7 +612,7 @@ static int dns_type_to_af(uint16_t t) {
}
}
-static int synthesize_localhost_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) {
+static int synthesize_localhost_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
int r;
assert(q);
@@ -590,7 +668,7 @@ static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to,
return dns_answer_add(*answer, rr, ifindex, flags);
}
-static int synthesize_localhost_ptr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) {
+static int synthesize_localhost_ptr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
int r;
assert(q);
@@ -682,7 +760,7 @@ static int answer_add_addresses_ptr(
return 0;
}
-static int synthesize_system_hostname_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) {
+static int synthesize_system_hostname_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
_cleanup_free_ struct local_address *addresses = NULL;
int n = 0, af;
@@ -766,7 +844,7 @@ static int synthesize_system_hostname_ptr(DnsQuery *q, int af, const union in_ad
return answer_add_addresses_ptr(answer, q->manager->mdns_hostname, addresses, n, af, address);
}
-static int synthesize_gateway_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) {
+static int synthesize_gateway_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) {
_cleanup_free_ struct local_address *addresses = NULL;
int n = 0, af;
@@ -801,7 +879,7 @@ static int synthesize_gateway_ptr(DnsQuery *q, int af, const union in_addr_union
static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
- unsigned i;
+ DnsResourceKey *key;
int r;
assert(q);
@@ -816,39 +894,39 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) {
DNS_TRANSACTION_ATTEMPTS_MAX_REACHED))
return 0;
- for (i = 0; i < q->question->n_keys; i++) {
+ DNS_QUESTION_FOREACH(key, q->question_utf8) {
union in_addr_union address;
const char *name;
int af;
- if (q->question->keys[i]->class != DNS_CLASS_IN &&
- q->question->keys[i]->class != DNS_CLASS_ANY)
+ if (key->class != DNS_CLASS_IN &&
+ key->class != DNS_CLASS_ANY)
continue;
- name = DNS_RESOURCE_KEY_NAME(q->question->keys[i]);
+ name = DNS_RESOURCE_KEY_NAME(key);
if (is_localhost(name)) {
- r = synthesize_localhost_rr(q, q->question->keys[i], &answer);
+ r = synthesize_localhost_rr(q, key, &answer);
if (r < 0)
return log_error_errno(r, "Failed to synthesize localhost RRs: %m");
} else if (manager_is_own_hostname(q->manager, name)) {
- r = synthesize_system_hostname_rr(q, q->question->keys[i], &answer);
+ r = synthesize_system_hostname_rr(q, key, &answer);
if (r < 0)
return log_error_errno(r, "Failed to synthesize system hostname RRs: %m");
} else if (is_gateway_hostname(name)) {
- r = synthesize_gateway_rr(q, q->question->keys[i], &answer);
+ r = synthesize_gateway_rr(q, key, &answer);
if (r < 0)
return log_error_errno(r, "Failed to synthesize gateway RRs: %m");
} else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) ||
dns_name_equal(name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) {
- r = synthesize_localhost_ptr(q, q->question->keys[i], &answer);
+ r = synthesize_localhost_ptr(q, key, &answer);
if (r < 0)
return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m");
@@ -884,7 +962,6 @@ int dns_query_go(DnsQuery *q) {
DnsScopeMatch found = DNS_SCOPE_NO;
DnsScope *s, *first = NULL;
DnsQueryCandidate *c;
- const char *name;
int r;
assert(q);
@@ -892,13 +969,13 @@ int dns_query_go(DnsQuery *q) {
if (q->state != DNS_TRANSACTION_NULL)
return 0;
- assert(q->question);
- assert(q->question->n_keys > 0);
-
- name = dns_question_first_name(q->question);
-
LIST_FOREACH(scopes, s, q->manager->dns_scopes) {
DnsScopeMatch match;
+ const char *name;
+
+ name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol));
+ if (!name)
+ continue;
match = dns_scope_good_domain(s, q->ifindex, q->flags, name);
if (match < 0)
@@ -934,6 +1011,11 @@ int dns_query_go(DnsQuery *q) {
LIST_FOREACH(scopes, s, first->scopes_next) {
DnsScopeMatch match;
+ const char *name;
+
+ name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol));
+ if (!name)
+ continue;
match = dns_scope_good_domain(s, q->ifindex, q->flags, name);
if (match < 0)
@@ -1115,33 +1197,60 @@ void dns_query_ready(DnsQuery *q) {
}
static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) {
- _cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL;
- int r;
+ _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL;
+ int r, k;
assert(q);
- log_debug("Following CNAME %s → %s", dns_question_first_name(q->question), cname->cname.name);
-
q->n_cname_redirects ++;
if (q->n_cname_redirects > CNAME_MAX)
return -ELOOP;
- r = dns_question_cname_redirect(q->question, cname, &nq);
+ r = dns_question_cname_redirect(q->question_idna, cname, &nq_idna);
if (r < 0)
return r;
+ else if (r > 0)
+ log_debug("Following CNAME/DNAME %s → %s.", dns_question_first_name(q->question_idna), dns_question_first_name(nq_idna));
- dns_question_unref(q->question);
- q->question = nq;
- nq = NULL;
+ k = dns_question_is_equal(q->question_idna, q->question_utf8);
+ if (k < 0)
+ return r;
+ if (k > 0) {
+ /* Same question? Shortcut new question generation */
+ nq_utf8 = dns_question_ref(nq_idna);
+ k = r;
+ } else {
+ k = dns_question_cname_redirect(q->question_utf8, cname, &nq_utf8);
+ if (k < 0)
+ return k;
+ else if (k > 0)
+ log_debug("Following UTF8 CNAME/DNAME %s → %s.", dns_question_first_name(q->question_utf8), dns_question_first_name(nq_utf8));
+ }
- dns_query_stop(q);
+ if (r == 0 && k == 0) /* No actual cname happened? */
+ return -ELOOP;
+
+ dns_question_unref(q->question_idna);
+ q->question_idna = nq_idna;
+ nq_idna = NULL;
+
+ dns_question_unref(q->question_utf8);
+ q->question_utf8 = nq_utf8;
+ nq_utf8 = NULL;
+
+ dns_query_free_candidates(q);
+ dns_query_reset_answer(q);
q->state = DNS_TRANSACTION_NULL;
+ /* Turn off searching for the new name */
+ q->flags |= SD_RESOLVED_NO_SEARCH;
+
return 0;
}
int dns_query_process_cname(DnsQuery *q) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL;
+ DnsQuestion *question;
DnsResourceRecord *rr;
int r;
@@ -1150,15 +1259,16 @@ int dns_query_process_cname(DnsQuery *q) {
if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL))
return DNS_QUERY_NOMATCH;
- DNS_ANSWER_FOREACH(rr, q->answer) {
+ question = dns_query_question_for_protocol(q, q->answer_protocol);
- r = dns_question_matches_rr(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
+ DNS_ANSWER_FOREACH(rr, q->answer) {
+ r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
if (r < 0)
return r;
if (r > 0)
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));
+ r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain));
if (r < 0)
return r;
if (r > 0 && !cname)
@@ -1219,3 +1329,42 @@ int dns_query_bus_track(DnsQuery *q, sd_bus_message *m) {
return 0;
}
+
+DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) {
+ assert(q);
+
+ switch (protocol) {
+
+ case DNS_PROTOCOL_DNS:
+ return q->question_idna;
+
+ case DNS_PROTOCOL_MDNS:
+ case DNS_PROTOCOL_LLMNR:
+ return q->question_utf8;
+
+ default:
+ return NULL;
+ }
+}
+
+const char *dns_query_string(DnsQuery *q) {
+ const char *name;
+ int r;
+
+ /* Returns a somewhat useful human-readable lookup key string for this query */
+
+ if (q->request_address_string)
+ return q->request_address_string;
+
+ if (q->request_address_valid) {
+ r = in_addr_to_string(q->request_family, &q->request_address, &q->request_address_string);
+ if (r >= 0)
+ return q->request_address_string;
+ }
+
+ name = dns_question_first_name(q->question_utf8);
+ if (name)
+ return name;
+
+ return dns_question_first_name(q->question_idna);
+}
diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h
index 4a0d265a2d..9f618d6f6b 100644
--- a/src/resolve/resolved-dns-query.h
+++ b/src/resolve/resolved-dns-query.h
@@ -59,7 +59,13 @@ struct DnsQuery {
unsigned n_auxiliary_queries;
int auxiliary_result;
- DnsQuestion *question;
+ /* The question, formatted in IDNA for use on classic DNS, and as UTF8 for use in LLMNR or mDNS. Note that even
+ * on classic DNS some labels might use UTF8 encoding. Specifically, DNS-SD service names (in contrast to their
+ * domain suffixes) use UTF-8 encoding even on DNS. Thus, the difference between these two fields is mostly
+ * relevant only for explicit *hostname* lookups as well as the domain suffixes of service lookups. */
+ DnsQuestion *question_idna;
+ DnsQuestion *question_utf8;
+
uint64_t flags;
int ifindex;
@@ -84,6 +90,7 @@ struct DnsQuery {
bool request_address_valid;
union in_addr_union request_address;
unsigned block_all_complete;
+ char *request_address_string;
/* Completion callback */
void (*complete)(DnsQuery* q);
@@ -104,7 +111,7 @@ enum {
DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c);
void dns_query_candidate_notify(DnsQueryCandidate *c);
-int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question, int family, uint64_t flags);
+int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags);
DnsQuery *dns_query_free(DnsQuery *q);
int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for);
@@ -116,4 +123,8 @@ int dns_query_process_cname(DnsQuery *q);
int dns_query_bus_track(DnsQuery *q, sd_bus_message *m);
+DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol);
+
+const char *dns_query_string(DnsQuery *q);
+
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free);
diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c
index 4ed7434d3c..1e41a9aa3c 100644
--- a/src/resolve/resolved-dns-question.c
+++ b/src/resolve/resolved-dns-question.c
@@ -21,6 +21,7 @@
#include "alloc-util.h"
#include "dns-domain.h"
+#include "dns-type.h"
#include "resolved-dns-question.h"
DnsQuestion *dns_question_new(unsigned n) {
@@ -107,7 +108,7 @@ int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *s
return 0;
}
-int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) {
+int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) {
unsigned i;
int r;
@@ -116,7 +117,14 @@ int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char
if (!q)
return 0;
+ if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME))
+ return 0;
+
for (i = 0; i < q->n_keys; i++) {
+ /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */
+ if (!dns_type_may_redirect(q->keys[i]->type))
+ return 0;
+
r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain);
if (r != 0)
return r;
@@ -144,18 +152,23 @@ int dns_question_is_valid_for_query(DnsQuestion *q) {
return 0;
/* Check that all keys in this question bear the same name */
- for (i = 1; i < q->n_keys; i++) {
+ for (i = 0; i < q->n_keys; i++) {
assert(q->keys[i]);
- r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), name);
- if (r <= 0)
- return r;
+ if (i > 0) {
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), name);
+ if (r <= 0)
+ return r;
+ }
+
+ if (!dns_type_is_valid_query(q->keys[i]->type))
+ return 0;
}
return 1;
}
-int dns_question_contains(DnsQuestion *a, DnsResourceKey *k) {
+int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k) {
unsigned j;
int r;
@@ -177,6 +190,9 @@ int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) {
unsigned j;
int r;
+ if (a == b)
+ return 1;
+
if (!a)
return !b || b->n_keys == 0;
if (!b)
@@ -201,32 +217,27 @@ int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) {
int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) {
_cleanup_(dns_question_unrefp) DnsQuestion *n = NULL;
+ DnsResourceKey *key;
bool same = true;
- unsigned i;
int r;
assert(cname);
assert(ret);
assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME));
- if (!q) {
- n = dns_question_new(0);
- if (!n)
- return -ENOMEM;
-
- *ret = n;
- n = 0;
+ if (dns_question_size(q) <= 0) {
+ *ret = NULL;
return 0;
}
- for (i = 0; i < q->n_keys; i++) {
+ DNS_QUESTION_FOREACH(key, q) {
_cleanup_free_ char *destination = NULL;
const char *d;
if (cname->key->type == DNS_TYPE_CNAME)
d = cname->cname.name;
else {
- r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(q->keys[i]), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination);
+ r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination);
if (r < 0)
return r;
if (r == 0)
@@ -235,7 +246,7 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname,
d = destination;
}
- r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), d);
+ r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), d);
if (r < 0)
return r;
@@ -245,9 +256,9 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname,
}
}
+ /* Fully the same, indicate we didn't do a thing */
if (same) {
- /* Shortcut, the names are already right */
- *ret = dns_question_ref(q);
+ *ret = NULL;
return 0;
}
@@ -256,10 +267,10 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname,
return -ENOMEM;
/* Create a new question, and patch in the new name */
- for (i = 0; i < q->n_keys; i++) {
+ DNS_QUESTION_FOREACH(key, q) {
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
- k = dns_resource_key_new_redirect(q->keys[i], cname);
+ k = dns_resource_key_new_redirect(key, cname);
if (!k)
return -ENOMEM;
@@ -285,8 +296,9 @@ const char *dns_question_first_name(DnsQuestion *q) {
return DNS_RESOURCE_KEY_NAME(q->keys[0]);
}
-int dns_question_new_address(DnsQuestion **ret, int family, const char *name) {
+int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna) {
_cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
+ _cleanup_free_ char *buf = NULL;
int r;
assert(ret);
@@ -295,6 +307,14 @@ int dns_question_new_address(DnsQuestion **ret, int family, const char *name) {
if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
return -EAFNOSUPPORT;
+ if (convert_idna) {
+ r = dns_name_apply_idna(name, &buf);
+ if (r < 0)
+ return r;
+
+ name = buf;
+ }
+
q = dns_question_new(family == AF_UNSPEC ? 2 : 1);
if (!q)
return -ENOMEM;
@@ -365,13 +385,60 @@ int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_
return 0;
}
-int dns_question_new_service(DnsQuestion **ret, const char *name, bool with_txt) {
+int dns_question_new_service(
+ DnsQuestion **ret,
+ const char *service,
+ const char *type,
+ const char *domain,
+ bool with_txt,
+ bool convert_idna) {
+
_cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
_cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
+ _cleanup_free_ char *buf = NULL, *joined = NULL;
+ const char *name;
int r;
assert(ret);
- assert(name);
+
+ /* We support three modes of invocation:
+ *
+ * 1. Only a domain is specified, in which case we assume a properly encoded SRV RR name, including service
+ * type and possibly a service name. If specified in this way we assume it's already IDNA converted if
+ * that's necessary.
+ *
+ * 2. Both service type and a domain specified, in which case a normal SRV RR is assumed, without a DNS-SD
+ * style prefix. In this case we'll IDNA convert the domain, if that's requested.
+ *
+ * 3. All three of service name, type and domain are specified, in which case a DNS-SD service is put
+ * together. The service name is never IDNA converted, and the domain is if requested.
+ *
+ * It's not supported to specify a service name without a type, or no domain name.
+ */
+
+ if (!domain)
+ return -EINVAL;
+
+ if (type) {
+ if (convert_idna) {
+ r = dns_name_apply_idna(domain, &buf);
+ if (r < 0)
+ return r;
+
+ domain = buf;
+ }
+
+ r = dns_service_join(service, type, domain, &joined);
+ if (r < 0)
+ return r;
+
+ name = joined;
+ } else {
+ if (service)
+ return -EINVAL;
+
+ name = domain;
+ }
q = dns_question_new(1 + with_txt);
if (!q)
diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h
index 5ffb63e250..98e1f0e366 100644
--- a/src/resolve/resolved-dns-question.h
+++ b/src/resolve/resolved-dns-question.h
@@ -38,22 +38,26 @@ DnsQuestion *dns_question_new(unsigned n);
DnsQuestion *dns_question_ref(DnsQuestion *q);
DnsQuestion *dns_question_unref(DnsQuestion *q);
-int dns_question_new_address(DnsQuestion **ret, int family, const char *name);
+int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna);
int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a);
-int dns_question_new_service(DnsQuestion **ret, const char *name, bool with_txt);
+int dns_question_new_service(DnsQuestion **ret, const char *service, const char *type, const char *domain, bool with_txt, bool convert_idna);
int dns_question_add(DnsQuestion *q, DnsResourceKey *key);
int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain);
-int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain);
+int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain);
int dns_question_is_valid_for_query(DnsQuestion *q);
-int dns_question_contains(DnsQuestion *a, DnsResourceKey *k);
+int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k);
int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b);
int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret);
const char *dns_question_first_name(DnsQuestion *q);
+static inline unsigned dns_question_size(DnsQuestion *q) {
+ return q ? q->n_keys : 0;
+}
+
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref);
#define _DNS_QUESTION_FOREACH(u, key, q) \
diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c
index dbf840157f..7273ef3825 100644
--- a/src/resolve/resolved-dns-rr.c
+++ b/src/resolve/resolved-dns-rr.c
@@ -334,6 +334,46 @@ int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) {
return 0;
}
+bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b) {
+ assert(a);
+ assert(b);
+
+ /* Try to replace one RR key by another if they are identical, thus saving a bit of memory. Note that we do
+ * this only for RR keys, not for RRs themselves, as they carry a lot of additional metadata (where they come
+ * from, validity data, and suchlike), and cannot be replaced so easily by other RRs that have the same
+ * superficial data. */
+
+ if (!*a)
+ return false;
+ if (!*b)
+ return false;
+
+ /* We refuse merging const keys */
+ if ((*a)->n_ref == (unsigned) -1)
+ return false;
+ if ((*b)->n_ref == (unsigned) -1)
+ return false;
+
+ /* Already the same? */
+ if (*a == *b)
+ return true;
+
+ /* Are they really identical? */
+ if (dns_resource_key_equal(*a, *b) <= 0)
+ return false;
+
+ /* Keep the one which already has more references. */
+ if ((*a)->n_ref > (*b)->n_ref) {
+ dns_resource_key_unref(*b);
+ *b = dns_resource_key_ref(*a);
+ } else {
+ dns_resource_key_unref(*a);
+ *a = dns_resource_key_ref(*b);
+ }
+
+ return true;
+}
+
DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) {
DnsResourceRecord *rr;
@@ -344,6 +384,7 @@ DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) {
rr->n_ref = 1;
rr->key = dns_resource_key_ref(key);
rr->expiry = USEC_INFINITY;
+ rr->n_skip_labels_signer = rr->n_skip_labels_source = (unsigned) -1;
return rr;
}
@@ -1085,6 +1126,88 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) {
return 0;
}
+int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) {
+ const char *n;
+ int r;
+
+ assert(rr);
+ assert(ret);
+
+ /* Returns the RRset's signer, if it is known. */
+
+ if (rr->n_skip_labels_signer == (unsigned) -1)
+ return -ENODATA;
+
+ n = DNS_RESOURCE_KEY_NAME(rr->key);
+ r = dns_name_skip(n, rr->n_skip_labels_signer, &n);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+
+ *ret = n;
+ return 0;
+}
+
+int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) {
+ const char *n;
+ int r;
+
+ assert(rr);
+ assert(ret);
+
+ /* Returns the RRset's synthesizing source, if it is known. */
+
+ if (rr->n_skip_labels_source == (unsigned) -1)
+ return -ENODATA;
+
+ n = DNS_RESOURCE_KEY_NAME(rr->key);
+ r = dns_name_skip(n, rr->n_skip_labels_source, &n);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL;
+
+ *ret = n;
+ return 0;
+}
+
+int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) {
+ const char *signer;
+ int r;
+
+ assert(rr);
+
+ r = dns_resource_record_signer(rr, &signer);
+ if (r < 0)
+ return r;
+
+ return dns_name_equal(zone, signer);
+}
+
+int dns_resource_record_is_synthetic(DnsResourceRecord *rr) {
+ int r;
+
+ assert(rr);
+
+ /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */
+
+ if (rr->n_skip_labels_source == (unsigned) -1)
+ return -ENODATA;
+
+ if (rr->n_skip_labels_source == 0)
+ return 0;
+
+ if (rr->n_skip_labels_source > 1)
+ return 1;
+
+ r = dns_name_startswith(DNS_RESOURCE_KEY_NAME(rr->key), "*");
+ if (r < 0)
+ return r;
+
+ return !r;
+}
+
static void dns_resource_record_hash_func(const void *i, struct siphash *state) {
const DnsResourceRecord *rr = i;
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index fe29a41566..d9c31e81c5 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -81,7 +81,7 @@ enum {
};
struct DnsResourceKey {
- unsigned n_ref;
+ unsigned n_ref; /* (unsigned -1) for const keys, see below */
uint16_t class, type;
char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */
};
@@ -108,14 +108,24 @@ struct DnsTxtItem {
struct DnsResourceRecord {
unsigned n_ref;
DnsResourceKey *key;
+
char *to_string;
+
uint32_t ttl;
usec_t expiry; /* RRSIG signature expiry */
+
+ /* How many labels to strip to determine "signer" of the RRSIG (aka, the zone). -1 if not signed. */
+ unsigned n_skip_labels_signer;
+ /* How many labels to strip to determine "synthesizing source" of this RR, i.e. the wildcard's immediate parent. -1 if not signed. */
+ unsigned n_skip_labels_source;
+
bool unparseable:1;
+
bool wire_format_canonical:1;
void *wire_format;
size_t wire_format_size;
size_t wire_format_rdata_offset;
+
union {
struct {
void *data;
@@ -284,6 +294,8 @@ static inline bool dns_key_is_shared(const DnsResourceKey *key) {
return IN_SET(key->type, DNS_TYPE_PTR);
}
+bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b);
+
DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key);
DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name);
DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr);
@@ -296,6 +308,11 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref);
int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical);
+int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret);
+int dns_resource_record_source(DnsResourceRecord *rr, const char **ret);
+int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone);
+int dns_resource_record_is_synthetic(DnsResourceRecord *rr);
+
DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i);
bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b);
diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c
index 0969e31e8a..5a86661807 100644
--- a/src/resolve/resolved-dns-server.c
+++ b/src/resolve/resolved-dns-server.c
@@ -232,7 +232,9 @@ static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) {
return;
if (s->verified_feature_level != level) {
- log_debug("Verified feature level %s.", dns_server_feature_level_to_string(level));
+ log_debug("Verified we get a response at feature level %s from DNS server %s.",
+ dns_server_feature_level_to_string(level),
+ dns_server_string(s));
s->verified_feature_level = level;
}
@@ -246,13 +248,18 @@ void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLeve
if (s->possible_feature_level == level)
s->n_failed_udp = 0;
+ /* If the RRSIG data is missing, then we can only validate EDNS0 at max */
+ if (s->packet_rrsig_missing && level >= DNS_SERVER_FEATURE_LEVEL_DO)
+ level = DNS_SERVER_FEATURE_LEVEL_DO - 1;
+
+ /* If the OPT RR got lost, then we can only validate UDP at max */
+ if (s->packet_bad_opt && level >= DNS_SERVER_FEATURE_LEVEL_EDNS0)
+ level = DNS_SERVER_FEATURE_LEVEL_EDNS0 - 1;
+
+ /* 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 (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. */
- dns_server_verified(s, DNS_SERVER_FEATURE_LEVEL_LARGE - 1);
- else
- /* A successful UDP reply, verifies UDP, ENDS0 and DO levels */
- dns_server_verified(s, level);
+ level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1;
} else if (protocol == IPPROTO_TCP) {
@@ -260,9 +267,11 @@ void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLeve
s->n_failed_tcp = 0;
/* Successful TCP connections are only useful to verify the TCP feature level. */
- dns_server_verified(s, DNS_SERVER_FEATURE_LEVEL_TCP);
+ level = DNS_SERVER_FEATURE_LEVEL_TCP;
}
+ dns_server_verified(s, level);
+
/* Remember the size of the largest UDP packet we received from a server,
we know that we can always announce support for packets with at least
this size. */
@@ -294,7 +303,6 @@ void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel le
void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) {
assert(s);
- assert(s->manager);
/* Invoked whenever we get a FORMERR, SERVFAIL or NOTIMP rcode from a server. */
@@ -306,7 +314,6 @@ void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) {
void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) {
assert(s);
- assert(s->manager);
/* Invoked whenever we get a packet with TC bit set. */
@@ -316,13 +323,30 @@ void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) {
s->packet_truncated = true;
}
-void dns_server_packet_rrsig_missing(DnsServer *s) {
+void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level) {
assert(s);
- assert(s->manager);
- log_warning("DNS server %s does not augment replies with RRSIG records, DNSSEC not available.", dns_server_string(s));
+ if (level < DNS_SERVER_FEATURE_LEVEL_DO)
+ return;
+
+ /* If the RRSIG RRs are missing, we have to downgrade what we previously verified */
+ if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO)
+ s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_DO-1;
- s->rrsig_missing = true;
+ s->packet_rrsig_missing = true;
+}
+
+void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level) {
+ assert(s);
+
+ if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0)
+ return;
+
+ /* If the OPT RR got lost, we have to downgrade what we previously verified */
+ if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0)
+ s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0-1;
+
+ s->packet_bad_opt = true;
}
static bool dns_server_grace_period_expired(DnsServer *s) {
@@ -352,6 +376,17 @@ static void dns_server_reset_counters(DnsServer *s) {
s->packet_failed = false;
s->packet_truncated = false;
s->verified_usec = 0;
+
+ /* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the
+ * grace period ends, but not when lowering the possible feature level, as a lower level feature level should
+ * not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's
+ * indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that
+ * either.
+ *
+ * This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A),
+ * but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not
+ * reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be
+ * incomplete. */
}
DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
@@ -361,11 +396,13 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
dns_server_grace_period_expired(s)) {
s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST;
- s->rrsig_missing = false;
dns_server_reset_counters(s);
- log_info("Grace period over, resuming full feature set (%s) for DNS server %s",
+ s->packet_bad_opt = false;
+ s->packet_rrsig_missing = false;
+
+ log_info("Grace period over, resuming full feature set (%s) for DNS server %s.",
dns_server_feature_level_to_string(s->possible_feature_level),
dns_server_string(s));
@@ -375,46 +412,75 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) {
DnsServerFeatureLevel p = s->possible_feature_level;
if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
- s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP)
+ s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP) {
/* We are at the TCP (lowest) level, and we tried a couple of TCP connections, and it didn't
* work. Upgrade back to UDP again. */
+ log_debug("Reached maximum number of failed TCP connection attempts, trying UDP again...");
s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP;
- else if ((s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
- s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) ||
- (s->packet_failed &&
- s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) ||
- (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
- s->packet_truncated &&
- s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP))
-
- /* Downgrade the feature one level, maybe things will work better then. We do this under any of
- * three conditions:
- *
- * 1. We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If
- * the packets are lost, maybe the server cannot parse them, hence downgrading sounds like a
- * good idea. We might downgrade all the way down to TCP this way.
- *
- * 2. We got a failure packet, and are at a feature level above UDP. Note that in this case we
- * downgrade no further than UDP, under the assumption that a failure packet indicates an
- * incompatible packet contents, but not a problem with the transport.
- *
- * 3. We got too many TCP connection failures in a row, we had at least one truncated packet,
- * and are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or
- * EDNS0 data we hope to make the packet smaller, so that it still works via UDP given that
- * TCP appears not to be a fallback. Note that if we are already at the lowest UDP level, we
- * don't go further down, since that's TCP, and TCP failed too often after all.
- */
+ } else if (s->packet_bad_opt &&
+ s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) {
+
+ /* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to below
+ * EDNS0 levels. After all, some records generate different responses with and without OPT RR
+ * in the request. Example:
+ * https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */
+ log_debug("Server doesn't support EDNS(0) properly, downgrading feature level...");
+ s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP;
+
+ } else if (s->packet_rrsig_missing &&
+ s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) {
+
+ /* RRSIG data was missing on a EDNS0 packet with DO bit set. This means the server doesn't
+ * augment responses with DNSSEC RRs. If so, let's better not ask the server for it anymore,
+ * after all some servers generate different replies depending if an OPT RR is in the query or
+ * not. */
+
+ log_debug("Detected server responses lack RRSIG records, downgrading feature level...");
+ s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0;
+
+ } else if (s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
+ s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) {
+
+ /* We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If the
+ * packets are lost, maybe the server cannot parse them, hence downgrading sounds like a good
+ * idea. We might downgrade all the way down to TCP this way. */
+
+ log_debug("Lost too many UDP packets, downgrading feature level...");
+ s->possible_feature_level--;
+
+ } else if (s->packet_failed &&
+ s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
+
+ /* We got a failure packet, and are at a feature level above UDP. Note that in this case we
+ * downgrade no further than UDP, under the assumption that a failure packet indicates an
+ * incompatible packet contents, but not a problem with the transport. */
+
+ log_debug("Got server failure, downgrading feature level...");
s->possible_feature_level--;
+ } else if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
+ s->packet_truncated &&
+ s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
+
+ /* We got too many TCP connection failures in a row, we had at least one truncated packet, and
+ * are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or EDNS0
+ * data we hope to make the packet smaller, so that it still works via UDP given that TCP
+ * appears not to be a fallback. Note that if we are already at the lowest UDP level, we don't
+ * go further down, since that's TCP, and TCP failed too often after all. */
+
+ log_debug("Got too many failed TCP connection failures and truncated UDP packets, downgrading feature level...");
+ s->possible_feature_level--;
+ }
+
if (p != s->possible_feature_level) {
/* We changed the feature level, reset the counting */
dns_server_reset_counters(s);
- log_warning("Using degraded feature set (%s) for DNS server %s",
+ log_warning("Using degraded feature set (%s) for DNS server %s.",
dns_server_feature_level_to_string(s->possible_feature_level),
dns_server_string(s));
}
@@ -468,7 +534,10 @@ bool dns_server_dnssec_supported(DnsServer *server) {
if (server->possible_feature_level < DNS_SERVER_FEATURE_LEVEL_DO)
return false;
- if (server->rrsig_missing)
+ if (server->packet_bad_opt)
+ return false;
+
+ if (server->packet_rrsig_missing)
return false;
/* DNSSEC servers need to support TCP properly (see RFC5966), if they don't, we assume DNSSEC is borked too */
diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h
index 323f702903..02bd3463a7 100644
--- a/src/resolve/resolved-dns-server.h
+++ b/src/resolve/resolved-dns-server.h
@@ -68,20 +68,20 @@ struct DnsServer {
DnsServerFeatureLevel verified_feature_level;
DnsServerFeatureLevel possible_feature_level;
+
size_t received_udp_packet_max;
+
unsigned n_failed_udp;
unsigned n_failed_tcp;
+
bool packet_failed:1;
bool packet_truncated:1;
+ bool packet_bad_opt:1;
+ bool packet_rrsig_missing:1;
+
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;
@@ -108,7 +108,8 @@ void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLeve
void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec);
void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level);
-void dns_server_packet_rrsig_missing(DnsServer *s);
+void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level);
+void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level);
DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s);
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index 9ee10f21c8..5640cd1d33 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -31,6 +31,8 @@
#include "resolved-llmnr.h"
#include "string-table.h"
+#define TRANSACTIONS_MAX 4096
+
static void dns_transaction_reset_answer(DnsTransaction *t) {
assert(t);
@@ -43,6 +45,17 @@ static void dns_transaction_reset_answer(DnsTransaction *t) {
t->answer_nsec_ttl = (uint32_t) -1;
}
+static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) {
+ DnsTransaction *z;
+
+ assert(t);
+
+ while ((z = set_steal_first(t->dnssec_transactions))) {
+ set_remove(z->notify_transactions, t);
+ dns_transaction_gc(z);
+ }
+}
+
static void dns_transaction_close_connection(DnsTransaction *t) {
assert(t);
@@ -95,10 +108,7 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
set_remove(z->dnssec_transactions, t);
set_free(t->notify_transactions);
- while ((z = set_steal_first(t->dnssec_transactions))) {
- set_remove(z->notify_transactions, t);
- dns_transaction_gc(z);
- }
+ dns_transaction_flush_dnssec_transactions(t);
set_free(t->dnssec_transactions);
dns_answer_unref(t->validated_keys);
@@ -127,6 +137,22 @@ bool dns_transaction_gc(DnsTransaction *t) {
return true;
}
+static uint16_t pick_new_id(Manager *m) {
+ uint16_t new_id;
+
+ /* Find a fresh, unused transaction id. Note that this loop is bounded because there's a limit on the number of
+ * transactions, and it's much lower than the space of IDs. */
+
+ assert_cc(TRANSACTIONS_MAX < 0xFFFF);
+
+ do
+ random_bytes(&new_id, sizeof(new_id));
+ while (new_id == 0 ||
+ hashmap_get(m->dns_transactions, UINT_TO_PTR(new_id)));
+
+ return new_id;
+}
+
int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) {
_cleanup_(dns_transaction_freep) DnsTransaction *t = NULL;
int r;
@@ -145,6 +171,9 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY)
return -EOPNOTSUPP;
+ if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX)
+ return -EBUSY;
+
r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL);
if (r < 0)
return r;
@@ -164,11 +193,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
t->key = dns_resource_key_ref(key);
t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
- /* Find a fresh, unused transaction id */
- do
- random_bytes(&t->id, sizeof(t->id));
- while (t->id == 0 ||
- hashmap_get(s->manager->dns_transactions, UINT_TO_PTR(t->id)));
+ t->id = pick_new_id(s->manager);
r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t);
if (r < 0) {
@@ -195,6 +220,22 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key)
return 0;
}
+static void dns_transaction_shuffle_id(DnsTransaction *t) {
+ uint16_t new_id;
+ assert(t);
+
+ /* Pick a new ID for this transaction. */
+
+ new_id = pick_new_id(t->scope->manager);
+ assert_se(hashmap_remove_and_put(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id), UINT_TO_PTR(new_id), t) >= 0);
+
+ log_debug("Transaction %" PRIu16 " is now %" PRIu16 ".", t->id, new_id);
+ t->id = new_id;
+
+ /* Make sure we generate a new packet with the new ID */
+ t->sent = dns_packet_unref(t->sent);
+}
+
static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
_cleanup_free_ char *pretty = NULL;
DnsZoneItem *z;
@@ -354,6 +395,26 @@ static void dns_transaction_retry(DnsTransaction *t) {
dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
}
+static int dns_transaction_maybe_restart(DnsTransaction *t) {
+ assert(t);
+
+ if (!t->server)
+ return 0;
+
+ if (t->current_feature_level <= dns_server_possible_feature_level(t->server))
+ return 0;
+
+ /* The server's current feature level is lower than when we sent the original query. We learnt something from
+ the response or possibly an auxiliary DNSSEC response that we didn't know before. We take that as reason to
+ restart the whole transaction. This is a good idea to deal with servers that respond rubbish if we include
+ OPT RR or DO bit. One of these cases is documented here, for example:
+ https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */
+
+ log_debug("Server feature level is now lower than when we began our transaction. Restarting with new ID.");
+ dns_transaction_shuffle_id(t);
+ return dns_transaction_go(t);
+}
+
static int on_stream_complete(DnsStream *s, int error) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
DnsTransaction *t;
@@ -529,13 +590,86 @@ static bool dns_transaction_dnssec_is_live(DnsTransaction *t) {
return false;
}
+static int dns_transaction_dnssec_ready(DnsTransaction *t) {
+ DnsTransaction *dt;
+ Iterator i;
+
+ assert(t);
+
+ /* Checks whether the auxiliary DNSSEC transactions of our transaction have completed, or are still
+ * ongoing. Returns 0, if we aren't ready for the DNSSEC validation, positive if we are. */
+
+ SET_FOREACH(dt, t->dnssec_transactions, i) {
+
+ switch (dt->state) {
+
+ case DNS_TRANSACTION_NULL:
+ case DNS_TRANSACTION_PENDING:
+ case DNS_TRANSACTION_VALIDATING:
+ /* Still ongoing */
+ return 0;
+
+ case DNS_TRANSACTION_RCODE_FAILURE:
+ if (dt->answer_rcode != DNS_RCODE_NXDOMAIN) {
+ log_debug("Auxiliary DNSSEC RR query failed with rcode=%s.", dns_rcode_to_string(dt->answer_rcode));
+ goto fail;
+ }
+
+ /* Fall-through: NXDOMAIN is good enough for us. This is because some DNS servers erronously
+ * return NXDOMAIN for empty non-terminals (Akamai...), and we need to handle that nicely, when
+ * asking for parent SOA or similar RRs to make unsigned proofs. */
+
+ case DNS_TRANSACTION_SUCCESS:
+ /* All good. */
+ break;
+
+ case DNS_TRANSACTION_DNSSEC_FAILED:
+ /* We handle DNSSEC failures different from other errors, as we care about the DNSSEC
+ * validationr result */
+
+ log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result));
+ t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+ return 0;
+
+
+ default:
+ log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(dt->state));
+ goto fail;
+ }
+ }
+
+ /* All is ready, we can go and validate */
+ return 1;
+
+fail:
+ t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY;
+ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+ return 0;
+}
+
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 (dns_transaction_dnssec_is_live(t))
+ r = dns_transaction_dnssec_ready(t);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+ if (r == 0) /* We aren't ready yet (or one of our auxiliary transactions failed, and we shouldn't validate now */
+ return;
+
+ /* See if we learnt things from the additional DNSSEC transactions, that we didn't know before, and better
+ * restart the lookup immediately. */
+ r = dns_transaction_maybe_restart(t);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+ if (r > 0) /* Transaction got restarted... */
return;
/* All our auxiliary DNSSEC transactions are complete now. Try
@@ -675,8 +809,6 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
return;
} else if (DNS_PACKET_TC(p))
dns_server_packet_truncated(t->server, t->current_feature_level);
- else
- dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size);
break;
@@ -726,13 +858,30 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
return;
}
- /* Parse message, if it isn't parsed yet. */
+ /* After the superficial checks, actually parse the message. */
r = dns_packet_extract(p);
if (r < 0) {
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return;
}
+ /* Report that the OPT RR was missing */
+ if (t->server) {
+ if (!p->opt)
+ dns_server_packet_bad_opt(t->server, t->current_feature_level);
+
+ dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size);
+ }
+
+ /* See if we know things we didn't know before that indicate we better restart the lookup immediately. */
+ r = dns_transaction_maybe_restart(t);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+ if (r > 0) /* Transaction got restarted... */
+ return;
+
if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) {
/* Only consider responses with equivalent query section to the request */
@@ -818,12 +967,12 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use
return 0;
}
if (r == 0) {
- log_debug("Received inappropriate DNS packet as response, ignoring: %m");
+ log_debug("Received inappropriate DNS packet as response, ignoring.");
return 0;
}
if (DNS_PACKET_ID(p) != t->id) {
- log_debug("Received packet with incorrect transaction ID, ignoring: %m");
+ log_debug("Received packet with incorrect transaction ID, ignoring.");
return 0;
}
@@ -961,6 +1110,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
t->start_usec = ts;
dns_transaction_reset_answer(t);
+ dns_transaction_flush_dnssec_transactions(t);
/* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */
if (t->scope->protocol == DNS_PROTOCOL_DNS) {
@@ -1774,6 +1924,12 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
if (r > 0)
continue;
+ r = dns_answer_has_dname_for_cname(t->answer, rr);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
name = DNS_RESOURCE_KEY_NAME(rr->key);
r = dns_name_parent(&name);
if (r < 0)
@@ -1873,69 +2029,15 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
}
void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) {
- int r;
-
assert(t);
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 copy any RRs from that transaction
- over into our list of validated keys -- but only if the
- answer is authenticated.
-
- 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;
+ /* Invoked whenever any of our auxiliary DNSSEC transactions completed its work. 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. */
-fail:
- t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY;
- dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED);
+ if (t->state == DNS_TRANSACTION_VALIDATING)
+ dns_transaction_process_dnssec(t);
}
static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) {
@@ -1950,7 +2052,7 @@ static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) {
DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) {
- r = dnssec_verify_dnskey_search(rr, t->validated_keys);
+ r = dnssec_verify_dnskey_by_ds_search(rr, t->validated_keys);
if (r < 0)
return r;
if (r == 0)
@@ -2378,6 +2480,31 @@ static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) {
return 0;
}
+static int dns_transaction_copy_validated(DnsTransaction *t) {
+ DnsTransaction *dt;
+ Iterator i;
+ int r;
+
+ assert(t);
+
+ /* Copy all validated RRs from the auxiliary DNSSEC transactions into our set of validated RRs */
+
+ SET_FOREACH(dt, t->dnssec_transactions, i) {
+
+ if (DNS_TRANSACTION_IS_LIVE(dt->state))
+ continue;
+
+ if (!dt->answer_authenticated)
+ continue;
+
+ r = dns_answer_extend(&t->validated_keys, dt->answer);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
int dns_transaction_validate_dnssec(DnsTransaction *t) {
_cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL;
enum {
@@ -2416,7 +2543,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (!dns_transaction_dnssec_supported_full(t)) {
/* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */
t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
- log_debug("Cannot validate response, server lacks DNSSEC support.");
+ log_debug("Not validating response, server lacks DNSSEC support.");
return 0;
}
@@ -2428,13 +2555,18 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (r < 0)
return r;
+ /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */
+ r = dns_transaction_copy_validated(t);
+ if (r < 0)
+ return r;
+
/* Second, see if there are DNSKEYs we already know a
* validated DS for. */
r = dns_transaction_validate_dnskey_by_ds(t);
if (r < 0)
return r;
- /* Third, remove all DNSKEY and DS RRs again that our trust
+ /* Fourth, remove all DNSKEY and DS RRs again that our trust
* anchor says are revoked. After all we might have marked
* some keys revoked above, but they might still be lingering
* in our validated_keys list. */
@@ -2531,28 +2663,22 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (result == DNSSEC_VALIDATED_WILDCARD) {
bool authenticated = false;
- const char *suffix;
+ const char *source;
- /* This RRset validated, but as a wildcard. This means we need to proof via NSEC/NSEC3
- * that no matching non-wildcard RR exists.
- *
- * See RFC 5155, Section 8.8 and RFC 4035, Section 5.3.4*/
+ /* This RRset validated, but as a wildcard. This means we need to prove via NSEC/NSEC3
+ * that no matching non-wildcard RR exists.*/
- r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix);
+ /* First step, determine the source of synthesis */
+ r = dns_resource_record_source(rrsig, &source);
if (r < 0)
return r;
- if (r == 0)
- return -EBADMSG;
- r = dns_name_parent(&suffix);
- if (r < 0)
- return r;
- if (r == 0)
- return -EBADMSG;
-
- r = dnssec_nsec_test_between(validated, DNS_RESOURCE_KEY_NAME(rr->key), suffix, &authenticated);
- if (r < 0)
- return r;
+ r = dnssec_test_positive_wildcard(
+ validated,
+ DNS_RESOURCE_KEY_NAME(rr->key),
+ source,
+ rrsig->rrsig.signer,
+ &authenticated);
/* Unless the NSEC proof showed that the key really doesn't exist something is off. */
if (r == 0)
@@ -2596,7 +2722,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
/* 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);
+ dns_server_packet_rrsig_missing(t->server, t->current_feature_level);
if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) {
@@ -2672,17 +2798,30 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (r < 0)
return r;
if (r > 0) {
- /* This is a primary response
- * to our question, and it
- * failed validation. That's
- * fatal. */
- t->answer_dnssec_result = result;
- return 0;
+
+ /* Look for a matching DNAME for this CNAME */
+ r = dns_answer_has_dname_for_cname(t->answer, rr);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ /* Also look among the stuff we already validated */
+ r = dns_answer_has_dname_for_cname(validated, rr);
+ if (r < 0)
+ return r;
+ }
+
+ if (r == 0) {
+ /* This is a primary response to our question, and it failed validation. That's
+ * fatal. */
+ t->answer_dnssec_result = result;
+ return 0;
+ }
+
+ /* This is a primary response, but we do have a DNAME RR in the RR that can replay this
+ * CNAME, hence rely on that, and we can remove the CNAME in favour of it. */
}
- /* This is just some auxiliary
- * data. 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;
diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c
index 9bee44b5c7..02d7ac91e1 100644
--- a/src/resolve/resolved-dns-trust-anchor.c
+++ b/src/resolve/resolved-dns-trust-anchor.c
@@ -665,7 +665,7 @@ static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceReco
* DS fingerprint will be the one of the
* unrevoked DNSKEY, but the one we got passed
* here has the bit set. */
- r = dnssec_verify_dnskey(revoked_dnskey, anchor, true);
+ r = dnssec_verify_dnskey_by_ds(revoked_dnskey, anchor, true);
if (r < 0)
return r;
if (r == 0)
diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf
index c5ad04afd7..82f26215df 100644
--- a/src/resolve/resolved-gperf.gperf
+++ b/src/resolve/resolved-gperf.gperf
@@ -18,5 +18,4 @@ 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 928307e004..1e8f88024b 100644
--- a/src/resolve/resolved-link.c
+++ b/src/resolve/resolved-link.c
@@ -279,6 +279,7 @@ clear:
static int link_update_dnssec_mode(Link *l) {
_cleanup_free_ char *m = NULL;
+ DnssecMode mode;
int r;
assert(l);
@@ -291,12 +292,23 @@ static int link_update_dnssec_mode(Link *l) {
if (r < 0)
goto clear;
- l->dnssec_mode = dnssec_mode_from_string(m);
- if (l->dnssec_mode < 0) {
+ mode = dnssec_mode_from_string(m);
+ if (mode < 0) {
r = -EINVAL;
goto clear;
}
+ if ((l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) ||
+ (l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) {
+
+ /* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the
+ * allow-downgrade mode to full DNSSEC mode, flush it too. */
+ if (l->unicast_scope)
+ dns_cache_flush(&l->unicast_scope->cache);
+ }
+
+ l->dnssec_mode = mode;
+
return 0;
clear:
diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in
index 0ba572d113..efc9c6733a 100644
--- a/src/resolve/resolved.conf.in
+++ b/src/resolve/resolved.conf.in
@@ -16,5 +16,4 @@
#FallbackDNS=@DNS_SERVERS@
#Domains=
#LLMNR=yes
-#MulticastDNS=no
#DNSSEC=no
diff --git a/src/resolve/test-dnssec-complex.c b/src/resolve/test-dnssec-complex.c
new file mode 100644
index 0000000000..caac251e83
--- /dev/null
+++ b/src/resolve/test-dnssec-complex.c
@@ -0,0 +1,238 @@
+/*-*- 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 <netinet/ip.h>
+
+#include "sd-bus.h"
+
+#include "af-list.h"
+#include "alloc-util.h"
+#include "bus-common-errors.h"
+#include "dns-type.h"
+#include "random-util.h"
+#include "string-util.h"
+#include "time-util.h"
+
+#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC)
+
+static void prefix_random(const char *name, char **ret) {
+ uint64_t i, u;
+ char *m = NULL;
+
+ u = 1 + (random_u64() & 3);
+
+ for (i = 0; i < u; i++) {
+ _cleanup_free_ char *b = NULL;
+ char *x;
+
+ assert_se(asprintf(&b, "x%" PRIu64 "x", random_u64()));
+ x = strjoin(b, ".", name, NULL);
+ assert_se(x);
+
+ free(m);
+ m = x;
+ }
+
+ *ret = m;
+ }
+
+static void test_rr_lookup(sd_bus *bus, const char *name, uint16_t type, const char *result) {
+ _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;
+ _cleanup_free_ char *m = NULL;
+ int r;
+
+ /* If the name starts with a dot, we prefix one to three random labels */
+ if (startswith(name, ".")) {
+ prefix_random(name + 1, &m);
+ name = m;
+ }
+
+ assert_se(sd_bus_message_new_method_call(
+ bus,
+ &req,
+ "org.freedesktop.resolve1",
+ "/org/freedesktop/resolve1",
+ "org.freedesktop.resolve1.Manager",
+ "ResolveRecord") >= 0);
+
+ assert_se(sd_bus_message_append(req, "isqqt", 0, name, DNS_CLASS_IN, type, UINT64_C(0)) >= 0);
+
+ r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
+
+ if (r < 0) {
+ assert_se(result);
+ assert_se(sd_bus_error_has_name(&error, result));
+ log_info("[OK] %s/%s resulted in <%s>.", name, dns_type_to_string(type), error.name);
+ } else {
+ assert_se(!result);
+ log_info("[OK] %s/%s succeeded.", name, dns_type_to_string(type));
+ }
+}
+
+static void test_hostname_lookup(sd_bus *bus, const char *name, int family, const char *result) {
+ _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;
+ _cleanup_free_ char *m = NULL;
+ const char *af;
+ int r;
+
+ af = family == AF_UNSPEC ? "AF_UNSPEC" : af_to_name(family);
+
+ /* If the name starts with a dot, we prefix one to three random labels */
+ if (startswith(name, ".")) {
+ prefix_random(name + 1, &m);
+ name = m;
+ }
+
+ assert_se(sd_bus_message_new_method_call(
+ bus,
+ &req,
+ "org.freedesktop.resolve1",
+ "/org/freedesktop/resolve1",
+ "org.freedesktop.resolve1.Manager",
+ "ResolveHostname") >= 0);
+
+ assert_se(sd_bus_message_append(req, "isit", 0, name, family, UINT64_C(0)) >= 0);
+
+ r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
+
+ if (r < 0) {
+ assert_se(result);
+ assert_se(sd_bus_error_has_name(&error, result));
+ log_info("[OK] %s/%s resulted in <%s>.", name, af, error.name);
+ } else {
+ assert_se(!result);
+ log_info("[OK] %s/%s succeeded.", name, af);
+ }
+
+}
+
+int main(int argc, char* argv[]) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+
+ /* Note that this is a manual test as it requires:
+ *
+ * Full network access
+ * A DNSSEC capable DNS server
+ * That zones contacted are still set up as they were when I wrote this.
+ */
+
+ assert_se(sd_bus_open_system(&bus) >= 0);
+
+ /* Normally signed */
+ test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_A, NULL);
+ test_hostname_lookup(bus, "www.eurid.eu", AF_UNSPEC, NULL);
+
+ test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_A, NULL);
+ test_hostname_lookup(bus, "sigok.verteiltesysteme.net", AF_UNSPEC, NULL);
+
+ /* Normally signed, NODATA */
+ test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
+ test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
+
+ /* Invalid signature */
+ test_rr_lookup(bus, "sigfail.verteiltesysteme.net", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED);
+ test_hostname_lookup(bus, "sigfail.verteiltesysteme.net", AF_INET, BUS_ERROR_DNSSEC_FAILED);
+
+ /* Invalid signature, RSA, wildcard */
+ test_rr_lookup(bus, ".wilda.rhybar.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED);
+ test_hostname_lookup(bus, ".wilda.rhybar.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED);
+
+ /* Invalid signature, ECDSA, wildcard */
+ test_rr_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED);
+ test_hostname_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED);
+
+ /* NXDOMAIN in NSEC domain */
+ test_rr_lookup(bus, "hhh.nasa.gov", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN");
+ test_hostname_lookup(bus, "hhh.nasa.gov", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN");
+
+ /* wildcard, NSEC zone */
+ test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_A, NULL);
+ test_hostname_lookup(bus, ".wilda.nsec.0skar.cz", AF_INET, NULL);
+
+ /* wildcard, NSEC zone, NODATA */
+ test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
+
+ /* wildcard, NSEC3 zone */
+ test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_A, NULL);
+ test_hostname_lookup(bus, ".wilda.0skar.cz", AF_INET, NULL);
+
+ /* wildcard, NSEC3 zone, NODATA */
+ test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
+
+ /* wildcard, NSEC zone, CNAME */
+ test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_A, NULL);
+ test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_UNSPEC, NULL);
+ test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_INET, NULL);
+
+ /* wildcard, NSEC zone, NODATA, CNAME */
+ test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
+
+ /* wildcard, NSEC3 zone, CNAME */
+ test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_A, NULL);
+ test_hostname_lookup(bus, ".wild.0skar.cz", AF_UNSPEC, NULL);
+ test_hostname_lookup(bus, ".wild.0skar.cz", AF_INET, NULL);
+
+ /* wildcard, NSEC3 zone, NODATA, CNAME */
+ test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR);
+
+ /* NODATA due to empty non-terminal in NSEC domain */
+ test_rr_lookup(bus, "herndon.nasa.gov", DNS_TYPE_A, BUS_ERROR_NO_SUCH_RR);
+ test_hostname_lookup(bus, "herndon.nasa.gov", AF_UNSPEC, BUS_ERROR_NO_SUCH_RR);
+ test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET, BUS_ERROR_NO_SUCH_RR);
+ test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET6, BUS_ERROR_NO_SUCH_RR);
+
+ /* NXDOMAIN in NSEC root zone: */
+ test_rr_lookup(bus, "jasdhjas.kjkfgjhfjg", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN");
+ test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN");
+ test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET, _BUS_ERROR_DNS "NXDOMAIN");
+ test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN");
+
+ /* NXDOMAIN in NSEC3 .com zone: */
+ test_rr_lookup(bus, "kjkfgjhfjgsdfdsfd.com", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN");
+ test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET, _BUS_ERROR_DNS "NXDOMAIN");
+ test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN");
+ test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN");
+
+ /* Unsigned A */
+ test_rr_lookup(bus, "poettering.de", DNS_TYPE_A, NULL);
+ test_rr_lookup(bus, "poettering.de", DNS_TYPE_AAAA, NULL);
+ test_hostname_lookup(bus, "poettering.de", AF_UNSPEC, NULL);
+ test_hostname_lookup(bus, "poettering.de", AF_INET, NULL);
+ test_hostname_lookup(bus, "poettering.de", AF_INET6, NULL);
+
+#if HAVE_LIBIDN
+ /* Unsigned A with IDNA conversion necessary */
+ test_hostname_lookup(bus, "pöttering.de", AF_UNSPEC, NULL);
+ test_hostname_lookup(bus, "pöttering.de", AF_INET, NULL);
+ test_hostname_lookup(bus, "pöttering.de", AF_INET6, NULL);
+#endif
+
+ /* DNAME, pointing to NXDOMAIN */
+ test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN");
+ test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_RP, _BUS_ERROR_DNS "NXDOMAIN");
+ test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN");
+ test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET, _BUS_ERROR_DNS "NXDOMAIN");
+ test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN");
+
+ return 0;
+}
diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c
index 0c9efde1fe..45fe1997e2 100644
--- a/src/resolve/test-dnssec.c
+++ b/src/resolve/test-dnssec.c
@@ -270,8 +270,8 @@ static void test_dnssec_verify_dns_key(void) {
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, false) > 0);
- assert_se(dnssec_verify_dnskey(dnskey, ds2, false) > 0);
+ assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds1, false) > 0);
+ assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds2, false) > 0);
}
static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) {
diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c
index bc12f89f02..8de1445a96 100644
--- a/src/shared/ask-password-api.c
+++ b/src/shared/ask-password-api.c
@@ -71,7 +71,7 @@ static int lookup_key(const char *keyname, key_serial_t *ret) {
serial = request_key("user", keyname, NULL, 0);
if (serial == -1)
- return -errno;
+ return negative_errno();
*ret = serial;
return 0;
diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c
index 5c6dc34700..b9a8ee4074 100644
--- a/src/shared/bus-util.c
+++ b/src/shared/bus-util.c
@@ -2041,13 +2041,21 @@ static const struct {
{ "start-limit", "start of the service was attempted too often" }
};
-static void log_job_error_with_service_result(const char* service, const char *result) {
- _cleanup_free_ char *service_shell_quoted = NULL;
+static void log_job_error_with_service_result(const char* service, const char *result, const char *extra_args) {
+ _cleanup_free_ char *service_shell_quoted = NULL, *systemctl_extra_args = NULL;
assert(service);
service_shell_quoted = shell_maybe_quote(service);
+ systemctl_extra_args = strjoin("systemctl ", extra_args, " ", NULL);
+ if (!systemctl_extra_args) {
+ log_oom();
+ return;
+ }
+
+ systemctl_extra_args = strstrip(systemctl_extra_args);
+
if (!isempty(result)) {
unsigned i;
@@ -2056,27 +2064,30 @@ static void log_job_error_with_service_result(const char* service, const char *r
break;
if (i < ELEMENTSOF(explanations)) {
- log_error("Job for %s failed because %s. See \"systemctl status %s\" and \"journalctl -xe\" for details.\n",
+ log_error("Job for %s failed because %s. See \"%s status %s\" and \"journalctl -xe\" for details.\n",
service,
explanations[i].explanation,
+ systemctl_extra_args,
strna(service_shell_quoted));
goto finish;
}
}
- log_error("Job for %s failed. See \"systemctl status %s\" and \"journalctl -xe\" for details.\n",
+ log_error("Job for %s failed. See \"%s status %s\" and \"journalctl -xe\" for details.\n",
service,
+ systemctl_extra_args,
strna(service_shell_quoted));
finish:
/* For some results maybe additional explanation is required */
if (streq_ptr(result, "start-limit"))
- log_info("To force a start use \"systemctl reset-failed %1$s\" followed by \"systemctl start %1$s\" again.",
+ log_info("To force a start use \"%1$s reset-failed %2$s\" followed by \"%1$s start %2$s\" again.",
+ systemctl_extra_args,
strna(service_shell_quoted));
}
-static int check_wait_response(BusWaitForJobs *d, bool quiet) {
+static int check_wait_response(BusWaitForJobs *d, bool quiet, const char *extra_args) {
int r = 0;
assert(d->result);
@@ -2103,7 +2114,7 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet) {
if (q < 0)
log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name);
- log_job_error_with_service_result(d->name, result);
+ log_job_error_with_service_result(d->name, result, extra_args);
} else
log_error("Job failed. See \"journalctl -xe\" for details.");
}
@@ -2127,7 +2138,7 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet) {
return r;
}
-int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet) {
+int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args) {
int r = 0;
assert(d);
@@ -2140,7 +2151,7 @@ int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet) {
return log_error_errno(q, "Failed to wait for response: %m");
if (d->result) {
- q = check_wait_response(d, quiet);
+ q = check_wait_response(d, quiet, extra_args);
/* Return the first error as it is most likely to be
* meaningful. */
if (q < 0 && r == 0)
@@ -2175,7 +2186,7 @@ int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) {
if (r < 0)
return log_oom();
- return bus_wait_for_jobs(d, quiet);
+ return bus_wait_for_jobs(d, quiet, NULL);
}
int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) {
diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h
index a5e3b6a0b5..18fc827754 100644
--- a/src/shared/bus-util.h
+++ b/src/shared/bus-util.h
@@ -182,7 +182,7 @@ typedef struct BusWaitForJobs BusWaitForJobs;
int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret);
void bus_wait_for_jobs_free(BusWaitForJobs *d);
int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path);
-int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet);
+int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args);
int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet);
DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free);
diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c
index 59475115ba..3ad409fc29 100644
--- a/src/shared/dns-domain.c
+++ b/src/shared/dns-domain.c
@@ -263,7 +263,6 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
*(q++) = '0' + (char) ((uint8_t) *p % 10);
sz -= 4;
-
}
p++;
@@ -414,7 +413,6 @@ int dns_name_concat(const char *a, const char *b, char **_ret) {
for (;;) {
char label[DNS_LABEL_MAX];
- int k;
r = dns_label_unescape(&p, label, sizeof(label));
if (r < 0)
@@ -433,12 +431,6 @@ int dns_name_concat(const char *a, const char *b, char **_ret) {
break;
}
- k = dns_label_undo_idna(label, r, label, sizeof(label));
- if (k < 0)
- return k;
- if (k > 0)
- r = k;
-
if (_ret) {
if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
return -ENOMEM;
@@ -488,7 +480,6 @@ void dns_name_hash_func(const void *s, struct siphash *state) {
for (;;) {
char label[DNS_LABEL_MAX+1];
- int k;
r = dns_label_unescape(&p, label, sizeof(label));
if (r < 0)
@@ -496,12 +487,6 @@ void dns_name_hash_func(const void *s, struct siphash *state) {
if (r == 0)
break;
- k = dns_label_undo_idna(label, r, label, sizeof(label));
- if (k < 0)
- break;
- if (k > 0)
- r = k;
-
ascii_strlower_n(label, r);
siphash24_compress(label, r, state);
siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */
@@ -513,7 +498,7 @@ void dns_name_hash_func(const void *s, struct siphash *state) {
int dns_name_compare_func(const void *a, const void *b) {
const char *x, *y;
- int r, q, k, w;
+ int r, q;
assert(a);
assert(b);
@@ -522,7 +507,7 @@ int dns_name_compare_func(const void *a, const void *b) {
y = (const char *) b + strlen(b);
for (;;) {
- char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
+ char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
if (x == NULL && y == NULL)
return 0;
@@ -532,17 +517,7 @@ int dns_name_compare_func(const void *a, const void *b) {
if (r < 0 || q < 0)
return r - q;
- k = dns_label_undo_idna(la, r, la, sizeof(la));
- w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
- if (k < 0 || w < 0)
- return k - w;
- if (k > 0)
- r = k;
- if (w > 0)
- q = w;
-
- la[r] = lb[q] = 0;
- r = strcasecmp(la, lb);
+ r = ascii_strcasecmp_nn(la, r, lb, q);
if (r != 0)
return r;
}
@@ -554,53 +529,35 @@ const struct hash_ops dns_name_hash_ops = {
};
int dns_name_equal(const char *x, const char *y) {
- int r, q, k, w;
+ int r, q;
assert(x);
assert(y);
for (;;) {
- char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1];
-
- if (*x == 0 && *y == 0)
- return true;
+ char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
r = dns_label_unescape(&x, la, sizeof(la));
if (r < 0)
return r;
- if (r > 0) {
- k = dns_label_undo_idna(la, r, la, sizeof(la));
- if (k < 0)
- return k;
- if (k > 0)
- r = k;
- }
q = dns_label_unescape(&y, lb, sizeof(lb));
if (q < 0)
return q;
- if (q > 0) {
- w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
- if (w < 0)
- return w;
- if (w > 0)
- q = w;
- }
- /* If one name had fewer labels than the other, this
- * will show up as empty label here, which the
- * strcasecmp() below will properly consider different
- * from a non-empty label. */
+ if (r != q)
+ return false;
+ if (r == 0)
+ return true;
- la[r] = lb[q] = 0;
- if (strcasecmp(la, lb) != 0)
+ if (ascii_strcasecmp_n(la, lb, r) != 0)
return false;
}
}
int dns_name_endswith(const char *name, const char *suffix) {
const char *n, *s, *saved_n = NULL;
- int r, q, k, w;
+ int r, q;
assert(name);
assert(suffix);
@@ -609,18 +566,11 @@ int dns_name_endswith(const char *name, const char *suffix) {
s = suffix;
for (;;) {
- char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
+ char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
r = dns_label_unescape(&n, ln, sizeof(ln));
if (r < 0)
return r;
- if (r > 0) {
- k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
- if (k < 0)
- return k;
- if (k > 0)
- r = k;
- }
if (!saved_n)
saved_n = n;
@@ -628,22 +578,13 @@ int dns_name_endswith(const char *name, const char *suffix) {
q = dns_label_unescape(&s, ls, sizeof(ls));
if (q < 0)
return q;
- if (q > 0) {
- w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
- if (w < 0)
- return w;
- if (w > 0)
- q = w;
- }
if (r == 0 && q == 0)
return true;
if (r == 0 && saved_n == n)
return false;
- ln[r] = ls[q] = 0;
-
- if (r != q || strcasecmp(ln, ls)) {
+ if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) {
/* Not the same, let's jump back, and try with the next label again */
s = suffix;
@@ -653,9 +594,39 @@ int dns_name_endswith(const char *name, const char *suffix) {
}
}
+int dns_name_startswith(const char *name, const char *prefix) {
+ const char *n, *p;
+ int r, q;
+
+ assert(name);
+ assert(prefix);
+
+ n = name;
+ p = prefix;
+
+ for (;;) {
+ char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX];
+
+ r = dns_label_unescape(&p, lp, sizeof(lp));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return true;
+
+ q = dns_label_unescape(&n, ln, sizeof(ln));
+ if (q < 0)
+ return q;
+
+ if (r != q)
+ return false;
+ if (ascii_strcasecmp_n(ln, lp, r) != 0)
+ return false;
+ }
+}
+
int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) {
const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix;
- int r, q, k, w;
+ int r, q;
assert(name);
assert(old_suffix);
@@ -666,7 +637,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
s = old_suffix;
for (;;) {
- char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1];
+ char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
if (!saved_before)
saved_before = n;
@@ -674,13 +645,6 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
r = dns_label_unescape(&n, ln, sizeof(ln));
if (r < 0)
return r;
- if (r > 0) {
- k = dns_label_undo_idna(ln, r, ln, sizeof(ln));
- if (k < 0)
- return k;
- if (k > 0)
- r = k;
- }
if (!saved_after)
saved_after = n;
@@ -688,13 +652,6 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
q = dns_label_unescape(&s, ls, sizeof(ls));
if (q < 0)
return q;
- if (q > 0) {
- w = dns_label_undo_idna(ls, q, ls, sizeof(ls));
- if (w < 0)
- return w;
- if (w > 0)
- q = w;
- }
if (r == 0 && q == 0)
break;
@@ -703,9 +660,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char
return 0;
}
- ln[r] = ls[q] = 0;
-
- if (r != q || strcasecmp(ln, ls)) {
+ if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) {
/* Not the same, let's jump back, and try with the next label again */
s = old_suffix;
@@ -873,12 +828,11 @@ bool dns_name_is_root(const char *name) {
}
bool dns_name_is_single_label(const char *name) {
- char label[DNS_LABEL_MAX+1];
int r;
assert(name);
- r = dns_label_unescape(&name, label, sizeof(label));
+ r = dns_name_parent(&name);
if (r <= 0)
return false;
@@ -1099,17 +1053,15 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
if (x >= 3 && srv_type_label_is_valid(c, cn)) {
if (dns_service_name_label_is_valid(a, an)) {
-
/* OK, got <name> . <type> . <type2> . <domain> */
name = strndup(a, an);
if (!name)
return -ENOMEM;
- type = new(char, bn+1+cn+1);
+ type = strjoin(b, ".", c, NULL);
if (!type)
return -ENOMEM;
- strcpy(stpcpy(stpcpy(type, b), "."), c);
d = p;
goto finish;
@@ -1121,10 +1073,9 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do
name = NULL;
- type = new(char, an+1+bn+1);
+ type = strjoin(a, ".", b, NULL);
if (!type)
return -ENOMEM;
- strcpy(stpcpy(stpcpy(type, a), "."), b);
d = q;
goto finish;
@@ -1158,22 +1109,20 @@ 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;
+static int dns_name_build_suffix_table(const char *name, const char*table[]) {
const char *p;
+ unsigned n = 0;
int r;
assert(name);
- assert(ret);
+ assert(table);
p = name;
for (;;) {
if (n > DNS_N_LABELS_MAX)
return -EINVAL;
- labels[n] = p;
-
+ table[n] = p;
r = dns_name_parent(&p);
if (r < 0)
return r;
@@ -1183,13 +1132,47 @@ int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
n++;
}
- if (n < n_labels)
+ return (int) n;
+}
+
+int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
+ const char* labels[DNS_N_LABELS_MAX+1];
+ int n;
+
+ assert(name);
+ assert(ret);
+
+ n = dns_name_build_suffix_table(name, labels);
+ if (n < 0)
+ return n;
+
+ if ((unsigned) n < n_labels)
return -EINVAL;
*ret = labels[n - n_labels];
return (int) (n - n_labels);
}
+int dns_name_skip(const char *a, unsigned n_labels, const char **ret) {
+ int r;
+
+ assert(a);
+ assert(ret);
+
+ for (; n_labels > 0; n_labels --) {
+ r = dns_name_parent(&a);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ *ret = "";
+ return 0;
+ }
+ }
+
+ *ret = a;
+ return 1;
+}
+
int dns_name_count_labels(const char *name) {
unsigned n = 0;
const char *p;
@@ -1220,14 +1203,107 @@ int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) {
assert(a);
assert(b);
- while (n_labels > 0) {
+ r = dns_name_skip(a, n_labels, &a);
+ if (r <= 0)
+ return r;
- r = dns_name_parent(&a);
- if (r <= 0)
+ return dns_name_equal(a, b);
+}
+
+int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
+ const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1];
+ int n = 0, m = 0, k = 0, r, q;
+
+ assert(a);
+ assert(b);
+ assert(ret);
+
+ /* Determines the common suffix of domain names a and b */
+
+ n = dns_name_build_suffix_table(a, a_labels);
+ if (n < 0)
+ return n;
+
+ m = dns_name_build_suffix_table(b, b_labels);
+ if (m < 0)
+ return m;
+
+ for (;;) {
+ char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
+ const char *x, *y;
+
+ if (k >= n || k >= m) {
+ *ret = a_labels[n - k];
+ return 0;
+ }
+
+ x = a_labels[n - 1 - k];
+ r = dns_label_unescape(&x, la, sizeof(la));
+ if (r < 0)
return r;
- n_labels --;
+ y = b_labels[m - 1 - k];
+ q = dns_label_unescape(&y, lb, sizeof(lb));
+ if (q < 0)
+ return q;
+
+ if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) {
+ *ret = a_labels[n - k];
+ return 0;
+ }
+
+ k++;
}
+}
- return dns_name_equal(a, b);
+int dns_name_apply_idna(const char *name, char **ret) {
+ _cleanup_free_ char *buf = NULL;
+ size_t n = 0, allocated = 0;
+ bool first = true;
+ int r, q;
+
+ assert(name);
+ assert(ret);
+
+ for (;;) {
+ char label[DNS_LABEL_MAX];
+
+ r = dns_label_unescape(&name, label, sizeof(label));
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ q = dns_label_apply_idna(label, r, label, sizeof(label));
+ if (q < 0)
+ return q;
+ if (q > 0)
+ r = q;
+
+ if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+ return -ENOMEM;
+
+ r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX);
+ if (r < 0)
+ return r;
+
+ if (first)
+ first = false;
+ else
+ buf[n++] = '.';
+
+ n +=r;
+ }
+
+ if (n > DNS_HOSTNAME_MAX)
+ return -EINVAL;
+
+ if (!GREEDY_REALLOC(buf, allocated, n + 1))
+ return -ENOMEM;
+
+ buf[n] = 0;
+ *ret = buf;
+ buf = NULL;
+
+ return (int) n;
}
diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h
index dd8ae3ac98..40c9ee5f27 100644
--- a/src/shared/dns-domain.h
+++ b/src/shared/dns-domain.h
@@ -83,6 +83,7 @@ extern const struct hash_ops dns_name_hash_ops;
int dns_name_between(const char *a, const char *b, const char *c);
int dns_name_equal(const char *x, const char *y);
int dns_name_endswith(const char *name, const char *suffix);
+int dns_name_startswith(const char *name, const char *prefix);
int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret);
@@ -103,4 +104,9 @@ int dns_service_split(const char *joined, char **name, char **type, char **domai
int dns_name_suffix(const char *name, unsigned n_labels, const char **ret);
int dns_name_count_labels(const char *name);
+int dns_name_skip(const char *a, unsigned n_labels, const char **ret);
int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b);
+
+int dns_name_common_suffix(const char *a, const char *b, const char **ret);
+
+int dns_name_apply_idna(const char *name, char **ret);
diff --git a/src/shared/dropin.c b/src/shared/dropin.c
index 692e8b8338..073a8396c5 100644
--- a/src/shared/dropin.c
+++ b/src/shared/dropin.c
@@ -156,7 +156,7 @@ static int iterate_dir(
errno = 0;
de = readdir(d);
- if (!de && errno != 0)
+ if (!de && errno > 0)
return log_error_errno(errno, "Failed to read directory %s: %m", path);
if (!de)
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 1448d974bd..3f2c308b8f 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -1979,7 +1979,7 @@ static void dump_unit_file_changes(const UnitFileChange *changes, unsigned n_cha
for (i = 0; i < n_changes; i++) {
if (changes[i].type == UNIT_FILE_SYMLINK)
- log_info("Created symlink from %s to %s.", changes[i].path, changes[i].source);
+ log_info("Created symlink %s, pointing to %s.", changes[i].path, changes[i].source);
else
log_info("Removed symlink %s.", changes[i].path);
}
@@ -2794,7 +2794,7 @@ static int start_unit(int argc, char *argv[], void *userdata) {
if (!arg_no_block) {
int q;
- q = bus_wait_for_jobs(w, arg_quiet);
+ q = bus_wait_for_jobs(w, arg_quiet, arg_scope != UNIT_FILE_SYSTEM ? "--user" : NULL);
if (q < 0)
return q;
diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c
index 675f94906b..a7e8187f4f 100644
--- a/src/sysusers/sysusers.c
+++ b/src/sysusers/sysusers.c
@@ -280,7 +280,7 @@ static int putgrent_with_members(const struct group *gr, FILE *group) {
errno = 0;
if (putgrent(&t, group) != 0)
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
return 1;
}
@@ -288,7 +288,7 @@ static int putgrent_with_members(const struct group *gr, FILE *group) {
errno = 0;
if (putgrent(gr, group) != 0)
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
return 0;
}
@@ -330,7 +330,7 @@ static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) {
errno = 0;
if (putsgent(&t, gshadow) != 0)
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
return 1;
}
@@ -338,7 +338,7 @@ static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) {
errno = 0;
if (putsgent(sg, gshadow) != 0)
- return errno ? -errno : -EIO;
+ return errno > 0 ? -errno : -EIO;
return 0;
}
diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c
index 6c3c49908f..3b260ee75d 100644
--- a/src/test/test-dns-domain.c
+++ b/src/test/test-dns-domain.c
@@ -276,6 +276,25 @@ static void test_dns_name_endswith(void) {
test_dns_name_endswith_one("x.y\001.z", "waldo", -EINVAL);
}
+static void test_dns_name_startswith_one(const char *a, const char *b, int ret) {
+ assert_se(dns_name_startswith(a, b) == ret);
+}
+
+static void test_dns_name_startswith(void) {
+ test_dns_name_startswith_one("", "", true);
+ test_dns_name_startswith_one("", "xxx", false);
+ test_dns_name_startswith_one("xxx", "", true);
+ test_dns_name_startswith_one("x", "x", true);
+ test_dns_name_startswith_one("x", "y", false);
+ test_dns_name_startswith_one("x.y", "x.y", true);
+ test_dns_name_startswith_one("x.y", "y.x", false);
+ test_dns_name_startswith_one("x.y", "x", true);
+ test_dns_name_startswith_one("x.y", "X", true);
+ test_dns_name_startswith_one("x.y", "y", false);
+ test_dns_name_startswith_one("x.y", "", true);
+ test_dns_name_startswith_one("x.y", "X", true);
+}
+
static void test_dns_name_is_root(void) {
assert_se(dns_name_is_root(""));
assert_se(dns_name_is_root("."));
@@ -559,6 +578,46 @@ static void test_dns_name_compare_func(void) {
assert_se(dns_name_compare_func("de.", "heise.de") != 0);
}
+static void test_dns_name_common_suffix_one(const char *a, const char *b, const char *result) {
+ const char *c;
+
+ assert_se(dns_name_common_suffix(a, b, &c) >= 0);
+ assert_se(streq(c, result));
+}
+
+static void test_dns_name_common_suffix(void) {
+ test_dns_name_common_suffix_one("", "", "");
+ test_dns_name_common_suffix_one("foo", "", "");
+ test_dns_name_common_suffix_one("", "foo", "");
+ test_dns_name_common_suffix_one("foo", "bar", "");
+ test_dns_name_common_suffix_one("bar", "foo", "");
+ test_dns_name_common_suffix_one("foo", "foo", "foo");
+ test_dns_name_common_suffix_one("quux.foo", "foo", "foo");
+ test_dns_name_common_suffix_one("foo", "quux.foo", "foo");
+ test_dns_name_common_suffix_one("this.is.a.short.sentence", "this.is.another.short.sentence", "short.sentence");
+ test_dns_name_common_suffix_one("FOO.BAR", "tEST.bAR", "BAR");
+}
+
+static void test_dns_name_apply_idna_one(const char *s, const char *result) {
+#ifdef HAVE_LIBIDN
+ _cleanup_free_ char *buf = NULL;
+ assert_se(dns_name_apply_idna(s, &buf) >= 0);
+ assert_se(dns_name_equal(buf, result) > 0);
+#endif
+}
+
+static void test_dns_name_apply_idna(void) {
+ test_dns_name_apply_idna_one("", "");
+ test_dns_name_apply_idna_one("foo", "foo");
+ test_dns_name_apply_idna_one("foo.", "foo");
+ test_dns_name_apply_idna_one("foo.bar", "foo.bar");
+ test_dns_name_apply_idna_one("foo.bar.", "foo.bar");
+ test_dns_name_apply_idna_one("föö", "xn--f-1gaa");
+ test_dns_name_apply_idna_one("föö.", "xn--f-1gaa");
+ test_dns_name_apply_idna_one("föö.bär", "xn--f-1gaa.xn--br-via");
+ test_dns_name_apply_idna_one("föö.bär.", "xn--f-1gaa.xn--br-via");
+}
+
int main(int argc, char *argv[]) {
test_dns_label_unescape();
@@ -567,6 +626,7 @@ int main(int argc, char *argv[]) {
test_dns_name_normalize();
test_dns_name_equal();
test_dns_name_endswith();
+ test_dns_name_startswith();
test_dns_name_between();
test_dns_name_is_root();
test_dns_name_is_single_label();
@@ -583,6 +643,8 @@ int main(int argc, char *argv[]) {
test_dns_name_count_labels();
test_dns_name_equal_skip();
test_dns_name_compare_func();
+ test_dns_name_common_suffix();
+ test_dns_name_apply_idna();
return 0;
}
diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c
index 25444c794a..12889ce873 100644
--- a/src/test/test-string-util.c
+++ b/src/test/test-string-util.c
@@ -55,7 +55,53 @@ static void test_string_erase(void) {
assert_se(streq(string_erase(x), "xxxxxxxxx"));
}
+static void test_ascii_strcasecmp_n(void) {
+
+ assert_se(ascii_strcasecmp_n("", "", 0) == 0);
+ assert_se(ascii_strcasecmp_n("", "", 1) == 0);
+ assert_se(ascii_strcasecmp_n("", "a", 1) < 0);
+ assert_se(ascii_strcasecmp_n("", "a", 2) < 0);
+ assert_se(ascii_strcasecmp_n("a", "", 1) > 0);
+ assert_se(ascii_strcasecmp_n("a", "", 2) > 0);
+ assert_se(ascii_strcasecmp_n("a", "a", 1) == 0);
+ assert_se(ascii_strcasecmp_n("a", "a", 2) == 0);
+ assert_se(ascii_strcasecmp_n("a", "b", 1) < 0);
+ assert_se(ascii_strcasecmp_n("a", "b", 2) < 0);
+ assert_se(ascii_strcasecmp_n("b", "a", 1) > 0);
+ assert_se(ascii_strcasecmp_n("b", "a", 2) > 0);
+ assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxYxxxx", 9) == 0);
+ assert_se(ascii_strcasecmp_n("xxxxxxxxx", "xxxxyxxxx", 9) < 0);
+ assert_se(ascii_strcasecmp_n("xxxxXxxxx", "xxxxyxxxx", 9) < 0);
+ assert_se(ascii_strcasecmp_n("xxxxxxxxx", "xxxxYxxxx", 9) < 0);
+ assert_se(ascii_strcasecmp_n("xxxxXxxxx", "xxxxYxxxx", 9) < 0);
+
+ assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxYxxxx", 9) == 0);
+ assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxxxxxx", 9) > 0);
+ assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxXxxxx", 9) > 0);
+ assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxxxxxx", 9) > 0);
+ assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxXxxxx", 9) > 0);
+}
+
+static void test_ascii_strcasecmp_nn(void) {
+ assert_se(ascii_strcasecmp_nn("", 0, "", 0) == 0);
+ assert_se(ascii_strcasecmp_nn("", 0, "", 1) < 0);
+ assert_se(ascii_strcasecmp_nn("", 1, "", 0) > 0);
+ assert_se(ascii_strcasecmp_nn("", 1, "", 1) == 0);
+
+ assert_se(ascii_strcasecmp_nn("aaaa", 4, "aaAa", 4) == 0);
+ assert_se(ascii_strcasecmp_nn("aaa", 3, "aaAa", 4) < 0);
+ assert_se(ascii_strcasecmp_nn("aaa", 4, "aaAa", 4) < 0);
+ assert_se(ascii_strcasecmp_nn("aaaa", 4, "aaA", 3) > 0);
+ assert_se(ascii_strcasecmp_nn("aaaa", 4, "AAA", 4) > 0);
+
+ assert_se(ascii_strcasecmp_nn("aaaa", 4, "bbbb", 4) < 0);
+ assert_se(ascii_strcasecmp_nn("aaAA", 4, "BBbb", 4) < 0);
+ assert_se(ascii_strcasecmp_nn("BBbb", 4, "aaaa", 4) > 0);
+}
+
int main(int argc, char *argv[]) {
test_string_erase();
+ test_ascii_strcasecmp_n();
+ test_ascii_strcasecmp_nn();
return 0;
}
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index f9a759e223..bb81ff5e3a 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -1075,7 +1075,7 @@ static int item_do_children(Item *i, const char *path, action_t action) {
errno = 0;
de = readdir(d);
if (!de) {
- if (errno != 0 && r == 0)
+ if (errno > 0 && r == 0)
r = -errno;
break;
diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c
index 0b1ae706e7..018b4dc596 100644
--- a/src/udev/udev-builtin-blkid.c
+++ b/src/udev/udev-builtin-blkid.c
@@ -124,7 +124,7 @@ static int find_gpt_root(struct udev_device *dev, blkid_probe pr, bool test) {
errno = 0;
pl = blkid_probe_get_partitions(pr);
if (!pl)
- return errno ? -errno : -ENOMEM;
+ return errno > 0 ? -errno : -ENOMEM;
nvals = blkid_partlist_numof_partitions(pl);
for (i = 0; i < nvals; i++) {
diff --git a/src/udev/udevd.c b/src/udev/udevd.c
index 366e7fbb87..8627a81ec2 100644
--- a/src/udev/udevd.c
+++ b/src/udev/udevd.c
@@ -1652,7 +1652,8 @@ exit:
int main(int argc, char *argv[]) {
_cleanup_free_ char *cgroup = NULL;
- int r, fd_ctrl, fd_uevent;
+ _cleanup_close_ int fd_ctrl = -1, fd_uevent = -1;
+ int r;
log_set_target(LOG_TARGET_AUTO);
log_parse_environment();