]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-question.c
tree-wide: use TAKE_PTR() and TAKE_FD() macros
[thirdparty/systemd.git] / src / resolve / resolved-dns-question.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
faa133f3
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
b5efdb8a 21#include "alloc-util.h"
4ad7f276 22#include "dns-domain.h"
0f7091e6 23#include "dns-type.h"
b5efdb8a 24#include "resolved-dns-question.h"
faa133f3
LP
25
26DnsQuestion *dns_question_new(unsigned n) {
27 DnsQuestion *q;
28
29 assert(n > 0);
30
31 q = malloc0(offsetof(DnsQuestion, keys) + sizeof(DnsResourceKey*) * n);
32 if (!q)
33 return NULL;
34
35 q->n_ref = 1;
36 q->n_allocated = n;
37
38 return q;
39}
40
41DnsQuestion *dns_question_ref(DnsQuestion *q) {
42 if (!q)
43 return NULL;
44
45 assert(q->n_ref > 0);
46 q->n_ref++;
47 return q;
48}
49
50DnsQuestion *dns_question_unref(DnsQuestion *q) {
51 if (!q)
52 return NULL;
53
54 assert(q->n_ref > 0);
55
56 if (q->n_ref == 1) {
57 unsigned i;
58
59 for (i = 0; i < q->n_keys; i++)
60 dns_resource_key_unref(q->keys[i]);
61 free(q);
62 } else
63 q->n_ref--;
64
65 return NULL;
66}
67
68int dns_question_add(DnsQuestion *q, DnsResourceKey *key) {
7e8e0422
LP
69 unsigned i;
70 int r;
71
faa133f3
LP
72 assert(key);
73
8013e860
LP
74 if (!q)
75 return -ENOSPC;
76
7e8e0422
LP
77 for (i = 0; i < q->n_keys; i++) {
78 r = dns_resource_key_equal(q->keys[i], key);
79 if (r < 0)
80 return r;
81 if (r > 0)
82 return 0;
83 }
84
faa133f3
LP
85 if (q->n_keys >= q->n_allocated)
86 return -ENOSPC;
87
88 q->keys[q->n_keys++] = dns_resource_key_ref(key);
89 return 0;
90}
91
801ad6a6 92int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) {
faa133f3
LP
93 unsigned i;
94 int r;
95
faa133f3
LP
96 assert(rr);
97
8013e860
LP
98 if (!q)
99 return 0;
100
faa133f3 101 for (i = 0; i < q->n_keys; i++) {
801ad6a6 102 r = dns_resource_key_match_rr(q->keys[i], rr, search_domain);
faa133f3
LP
103 if (r != 0)
104 return r;
105 }
106
107 return 0;
108}
109
542e0c84 110int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) {
faa133f3
LP
111 unsigned i;
112 int r;
113
faa133f3
LP
114 assert(rr);
115
8013e860
LP
116 if (!q)
117 return 0;
118
542e0c84
LP
119 if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME))
120 return 0;
121
faa133f3 122 for (i = 0; i < q->n_keys; i++) {
542e0c84
LP
123 /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */
124 if (!dns_type_may_redirect(q->keys[i]->type))
125 return 0;
126
5d27351f 127 r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain);
faa133f3
LP
128 if (r != 0)
129 return r;
130 }
131
34b9656f 132 return 0;
faa133f3
LP
133}
134
703e4f5e 135int dns_question_is_valid_for_query(DnsQuestion *q) {
faa133f3
LP
136 const char *name;
137 unsigned i;
138 int r;
139
8013e860
LP
140 if (!q)
141 return 0;
faa133f3
LP
142
143 if (q->n_keys <= 0)
144 return 0;
145
146 if (q->n_keys > 65535)
147 return 0;
148
1c02e7ba 149 name = dns_resource_key_name(q->keys[0]);
faa133f3
LP
150 if (!name)
151 return 0;
152
153 /* Check that all keys in this question bear the same name */
0f7091e6 154 for (i = 0; i < q->n_keys; i++) {
34b9656f
LP
155 assert(q->keys[i]);
156
0f7091e6 157 if (i > 0) {
1c02e7ba 158 r = dns_name_equal(dns_resource_key_name(q->keys[i]), name);
0f7091e6
LP
159 if (r <= 0)
160 return r;
161 }
162
163 if (!dns_type_is_valid_query(q->keys[i]->type))
164 return 0;
faa133f3
LP
165 }
166
167 return 1;
168}
169
6a21960c 170int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k) {
1086182d
LP
171 unsigned j;
172 int r;
173
1086182d
LP
174 assert(k);
175
8013e860
LP
176 if (!a)
177 return 0;
178
1086182d
LP
179 for (j = 0; j < a->n_keys; j++) {
180 r = dns_resource_key_equal(a->keys[j], k);
181 if (r != 0)
182 return r;
183 }
184
185 return 0;
186}
187
188int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) {
189 unsigned j;
190 int r;
191
b6800689
LP
192 if (a == b)
193 return 1;
194
8013e860
LP
195 if (!a)
196 return !b || b->n_keys == 0;
197 if (!b)
198 return a->n_keys == 0;
1086182d
LP
199
200 /* Checks if all keys in a are also contained b, and vice versa */
201
202 for (j = 0; j < a->n_keys; j++) {
203 r = dns_question_contains(b, a->keys[j]);
204 if (r <= 0)
205 return r;
206 }
207
208 for (j = 0; j < b->n_keys; j++) {
209 r = dns_question_contains(a, b->keys[j]);
210 if (r <= 0)
211 return r;
212 }
213
214 return 1;
215}
216
36d9205d 217int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) {
faa133f3 218 _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL;
23b298bc 219 DnsResourceKey *key;
faa133f3 220 bool same = true;
faa133f3
LP
221 int r;
222
36d9205d 223 assert(cname);
faa133f3 224 assert(ret);
58db254a 225 assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME));
faa133f3 226
23b298bc
LP
227 if (dns_question_size(q) <= 0) {
228 *ret = NULL;
8013e860
LP
229 return 0;
230 }
231
23b298bc 232 DNS_QUESTION_FOREACH(key, q) {
58db254a
LP
233 _cleanup_free_ char *destination = NULL;
234 const char *d;
235
236 if (cname->key->type == DNS_TYPE_CNAME)
237 d = cname->cname.name;
238 else {
1c02e7ba 239 r = dns_name_change_suffix(dns_resource_key_name(key), dns_resource_key_name(cname->key), cname->dname.name, &destination);
58db254a
LP
240 if (r < 0)
241 return r;
242 if (r == 0)
243 continue;
244
245 d = destination;
246 }
247
1c02e7ba 248 r = dns_name_equal(dns_resource_key_name(key), d);
faa133f3
LP
249 if (r < 0)
250 return r;
251
252 if (r == 0) {
253 same = false;
254 break;
255 }
256 }
257
23b298bc 258 /* Fully the same, indicate we didn't do a thing */
faa133f3 259 if (same) {
23b298bc 260 *ret = NULL;
faa133f3
LP
261 return 0;
262 }
263
264 n = dns_question_new(q->n_keys);
265 if (!n)
266 return -ENOMEM;
267
268 /* Create a new question, and patch in the new name */
23b298bc 269 DNS_QUESTION_FOREACH(key, q) {
faa133f3
LP
270 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
271
23b298bc 272 k = dns_resource_key_new_redirect(key, cname);
faa133f3
LP
273 if (!k)
274 return -ENOMEM;
275
276 r = dns_question_add(n, k);
277 if (r < 0)
278 return r;
279 }
280
1cc6c93a 281 *ret = TAKE_PTR(n);
faa133f3
LP
282
283 return 1;
284}
45ec7efb 285
703e4f5e
LP
286const char *dns_question_first_name(DnsQuestion *q) {
287
288 if (!q)
289 return NULL;
45ec7efb
LP
290
291 if (q->n_keys < 1)
292 return NULL;
293
1c02e7ba 294 return dns_resource_key_name(q->keys[0]);
45ec7efb
LP
295}
296
23b298bc 297int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna) {
45ec7efb 298 _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
23b298bc 299 _cleanup_free_ char *buf = NULL;
45ec7efb
LP
300 int r;
301
302 assert(ret);
303 assert(name);
304
305 if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
306 return -EAFNOSUPPORT;
307
23b298bc
LP
308 if (convert_idna) {
309 r = dns_name_apply_idna(name, &buf);
310 if (r < 0)
311 return r;
ad1f3fe6 312 if (r > 0 && !streq(name, buf))
87057e24 313 name = buf;
ad1f3fe6
ZJS
314 else
315 /* We did not manage to create convert the idna name, or it's
316 * the same as the original name. We assume the caller already
317 * created an uncoverted question, so let's not repeat work
318 * unnecessarily. */
319 return -EALREADY;
23b298bc
LP
320 }
321
45ec7efb
LP
322 q = dns_question_new(family == AF_UNSPEC ? 2 : 1);
323 if (!q)
324 return -ENOMEM;
325
326 if (family != AF_INET6) {
327 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
328
329 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name);
330 if (!key)
331 return -ENOMEM;
332
333 r = dns_question_add(q, key);
334 if (r < 0)
335 return r;
336 }
337
338 if (family != AF_INET) {
339 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
340
341 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name);
342 if (!key)
343 return -ENOMEM;
344
345 r = dns_question_add(q, key);
346 if (r < 0)
347 return r;
348 }
349
1cc6c93a 350 *ret = TAKE_PTR(q);
45ec7efb
LP
351
352 return 0;
353}
354
355int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) {
356 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
357 _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
358 _cleanup_free_ char *reverse = NULL;
359 int r;
360
361 assert(ret);
362 assert(a);
363
364 if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
365 return -EAFNOSUPPORT;
366
367 r = dns_name_reverse(family, a, &reverse);
368 if (r < 0)
369 return r;
370
371 q = dns_question_new(1);
372 if (!q)
373 return -ENOMEM;
374
375 key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse);
376 if (!key)
377 return -ENOMEM;
378
379 reverse = NULL;
380
381 r = dns_question_add(q, key);
382 if (r < 0)
383 return r;
384
1cc6c93a 385 *ret = TAKE_PTR(q);
45ec7efb
LP
386
387 return 0;
388}
389
23b298bc
LP
390int dns_question_new_service(
391 DnsQuestion **ret,
392 const char *service,
393 const char *type,
394 const char *domain,
395 bool with_txt,
396 bool convert_idna) {
397
45ec7efb
LP
398 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
399 _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
23b298bc
LP
400 _cleanup_free_ char *buf = NULL, *joined = NULL;
401 const char *name;
45ec7efb
LP
402 int r;
403
404 assert(ret);
23b298bc
LP
405
406 /* We support three modes of invocation:
407 *
408 * 1. Only a domain is specified, in which case we assume a properly encoded SRV RR name, including service
409 * type and possibly a service name. If specified in this way we assume it's already IDNA converted if
410 * that's necessary.
411 *
412 * 2. Both service type and a domain specified, in which case a normal SRV RR is assumed, without a DNS-SD
413 * style prefix. In this case we'll IDNA convert the domain, if that's requested.
414 *
415 * 3. All three of service name, type and domain are specified, in which case a DNS-SD service is put
416 * together. The service name is never IDNA converted, and the domain is if requested.
417 *
418 * It's not supported to specify a service name without a type, or no domain name.
419 */
420
421 if (!domain)
422 return -EINVAL;
423
424 if (type) {
425 if (convert_idna) {
426 r = dns_name_apply_idna(domain, &buf);
427 if (r < 0)
428 return r;
87057e24
ZJS
429 if (r > 0)
430 domain = buf;
23b298bc
LP
431 }
432
433 r = dns_service_join(service, type, domain, &joined);
434 if (r < 0)
435 return r;
436
437 name = joined;
438 } else {
439 if (service)
440 return -EINVAL;
441
442 name = domain;
443 }
45ec7efb
LP
444
445 q = dns_question_new(1 + with_txt);
446 if (!q)
447 return -ENOMEM;
448
449 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name);
450 if (!key)
451 return -ENOMEM;
452
453 r = dns_question_add(q, key);
454 if (r < 0)
455 return r;
456
457 if (with_txt) {
458 dns_resource_key_unref(key);
459 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name);
460 if (!key)
461 return -ENOMEM;
462
463 r = dns_question_add(q, key);
464 if (r < 0)
465 return r;
466 }
467
1cc6c93a 468 *ret = TAKE_PTR(q);
45ec7efb
LP
469
470 return 0;
471}