Merge pull request #2316 from poettering/dnssec14

Fourteenth DNSSEC PR
This commit is contained in:
Tom Gundersen 2016-01-14 17:02:57 +01:00
commit becc96b726
15 changed files with 430 additions and 119 deletions

9
TODO
View file

@ -33,6 +33,15 @@ Janitorial Clean-ups:
Features:
* add systemctl stop --job-mode=triggering that follows TRIGGERED_BY deps and adds them to the same transaction
* coredump logic should use prlimit() to query RLIMIT_CORE of the dumpee and honour it
* Add a MaxRuntimeSec= setting for service units (or units in general) to terminate units after they ran for a certain
amount of time
* Maybe add a way how users can "pin" units into memory, so that they are not subject to automatic GC?
* PID1: find a way how we can reload unit file configuration for
specific units only, without reloading the whole of systemd

View file

@ -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;

View file

@ -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) {

View file

@ -120,6 +120,21 @@ 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_is_dnssec(uint16_t type) {
return IN_SET(type,
DNS_TYPE_DS,

View file

@ -131,6 +131,7 @@ 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_class_is_pseudo(uint16_t class);
bool dns_class_is_valid_rr(uint16_t class);

View file

@ -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;

View file

@ -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);

View file

@ -442,8 +442,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
@ -512,8 +513,9 @@ int dnssec_verify_rrset(
DnsResourceRecord **list, *rr;
gcry_md_hd_t md = NULL;
int r, md_algorithm;
bool wildcard = false;
size_t k, n = 0;
bool wildcard;
const char *source;
assert(key);
assert(rrsig);
@ -542,6 +544,28 @@ int dnssec_verify_rrset(
return 0;
}
/* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */
r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(key), 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(DNS_RESOURCE_KEY_NAME(key), "*");
if (r < 0)
return r;
if (r > 0)
source = DNS_RESOURCE_KEY_NAME(key);
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 +616,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);
@ -1274,8 +1295,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 +1422,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;
@ -1586,7 +1604,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,6 +1618,9 @@ int dnssec_nsec_test_between(DnsAnswer *answer, const char *name, const char *zo
DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) {
bool found = false;
if (rr->key->type != type && type != DNS_TYPE_ANY)
continue;
r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), zone);
if (r < 0)
return r;
@ -1667,6 +1688,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",

View file

@ -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_;

View file

@ -2531,28 +2531,24 @@ 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_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &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)

View file

@ -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++;
@ -522,7 +521,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,8 +531,15 @@ 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 (r > 0)
k = dns_label_undo_idna(la, r, la, sizeof(la));
else
k = 0;
if (q > 0)
w = dns_label_undo_idna(lb, q, lb, sizeof(lb));
else
w = 0;
if (k < 0 || w < 0)
return k - w;
if (k > 0)
@ -541,8 +547,7 @@ int dns_name_compare_func(const void *a, const void *b) {
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;
}
@ -553,54 +558,54 @@ const struct hash_ops dns_name_hash_ops = {
.compare = dns_name_compare_func
};
static int dns_label_unescape_undo_idna(const char **name, char *dest, size_t sz) {
int r, k;
/* Clobbers all arguments on failure... */
r = dns_label_unescape(name, dest, sz);
if (r <= 0)
return r;
k = dns_label_undo_idna(dest, r, dest, sz);
if (k < 0)
return k;
if (k == 0) /* not an IDNA name */
return r;
return k;
}
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];
char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
if (*x == 0 && *y == 0)
return true;
r = dns_label_unescape(&x, la, sizeof(la));
r = dns_label_unescape_undo_idna(&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));
q = dns_label_unescape_undo_idna(&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,41 +614,25 @@ 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));
r = dns_label_unescape_undo_idna(&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;
q = dns_label_unescape(&s, ls, sizeof(ls));
q = dns_label_unescape_undo_idna(&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 +642,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_undo_idna(&p, lp, sizeof(lp));
if (r < 0)
return r;
if (r == 0)
return true;
q = dns_label_unescape_undo_idna(&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,35 +685,21 @@ 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;
r = dns_label_unescape(&n, ln, sizeof(ln));
r = dns_label_unescape_undo_idna(&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;
q = dns_label_unescape(&s, ls, sizeof(ls));
q = dns_label_unescape_undo_idna(&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 +708,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 +876,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 +1101,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 +1121,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;

View file

@ -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);

View file

@ -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);
}

View file

@ -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("."));
@ -567,6 +586,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();

View file

@ -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;
}