]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-question.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / resolve / resolved-dns-question.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
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
21 #include "alloc-util.h"
22 #include "dns-domain.h"
23 #include "dns-type.h"
24 #include "resolved-dns-question.h"
25
26 DnsQuestion *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
41 DnsQuestion *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
50 DnsQuestion *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
68 int dns_question_add(DnsQuestion *q, DnsResourceKey *key) {
69 unsigned i;
70 int r;
71
72 assert(key);
73
74 if (!q)
75 return -ENOSPC;
76
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
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
92 int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) {
93 unsigned i;
94 int r;
95
96 assert(rr);
97
98 if (!q)
99 return 0;
100
101 for (i = 0; i < q->n_keys; i++) {
102 r = dns_resource_key_match_rr(q->keys[i], rr, search_domain);
103 if (r != 0)
104 return r;
105 }
106
107 return 0;
108 }
109
110 int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) {
111 unsigned i;
112 int r;
113
114 assert(rr);
115
116 if (!q)
117 return 0;
118
119 if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME))
120 return 0;
121
122 for (i = 0; i < q->n_keys; i++) {
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
127 r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain);
128 if (r != 0)
129 return r;
130 }
131
132 return 0;
133 }
134
135 int dns_question_is_valid_for_query(DnsQuestion *q) {
136 const char *name;
137 unsigned i;
138 int r;
139
140 if (!q)
141 return 0;
142
143 if (q->n_keys <= 0)
144 return 0;
145
146 if (q->n_keys > 65535)
147 return 0;
148
149 name = dns_resource_key_name(q->keys[0]);
150 if (!name)
151 return 0;
152
153 /* Check that all keys in this question bear the same name */
154 for (i = 0; i < q->n_keys; i++) {
155 assert(q->keys[i]);
156
157 if (i > 0) {
158 r = dns_name_equal(dns_resource_key_name(q->keys[i]), name);
159 if (r <= 0)
160 return r;
161 }
162
163 if (!dns_type_is_valid_query(q->keys[i]->type))
164 return 0;
165 }
166
167 return 1;
168 }
169
170 int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k) {
171 unsigned j;
172 int r;
173
174 assert(k);
175
176 if (!a)
177 return 0;
178
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
188 int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) {
189 unsigned j;
190 int r;
191
192 if (a == b)
193 return 1;
194
195 if (!a)
196 return !b || b->n_keys == 0;
197 if (!b)
198 return a->n_keys == 0;
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
217 int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) {
218 _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL;
219 DnsResourceKey *key;
220 bool same = true;
221 int r;
222
223 assert(cname);
224 assert(ret);
225 assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME));
226
227 if (dns_question_size(q) <= 0) {
228 *ret = NULL;
229 return 0;
230 }
231
232 DNS_QUESTION_FOREACH(key, q) {
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 {
239 r = dns_name_change_suffix(dns_resource_key_name(key), dns_resource_key_name(cname->key), cname->dname.name, &destination);
240 if (r < 0)
241 return r;
242 if (r == 0)
243 continue;
244
245 d = destination;
246 }
247
248 r = dns_name_equal(dns_resource_key_name(key), d);
249 if (r < 0)
250 return r;
251
252 if (r == 0) {
253 same = false;
254 break;
255 }
256 }
257
258 /* Fully the same, indicate we didn't do a thing */
259 if (same) {
260 *ret = NULL;
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 */
269 DNS_QUESTION_FOREACH(key, q) {
270 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
271
272 k = dns_resource_key_new_redirect(key, cname);
273 if (!k)
274 return -ENOMEM;
275
276 r = dns_question_add(n, k);
277 if (r < 0)
278 return r;
279 }
280
281 *ret = n;
282 n = NULL;
283
284 return 1;
285 }
286
287 const char *dns_question_first_name(DnsQuestion *q) {
288
289 if (!q)
290 return NULL;
291
292 if (q->n_keys < 1)
293 return NULL;
294
295 return dns_resource_key_name(q->keys[0]);
296 }
297
298 int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna) {
299 _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
300 _cleanup_free_ char *buf = NULL;
301 int r;
302
303 assert(ret);
304 assert(name);
305
306 if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
307 return -EAFNOSUPPORT;
308
309 if (convert_idna) {
310 r = dns_name_apply_idna(name, &buf);
311 if (r < 0)
312 return r;
313 if (r > 0 && !streq(name, buf))
314 name = buf;
315 else
316 /* We did not manage to create convert the idna name, or it's
317 * the same as the original name. We assume the caller already
318 * created an uncoverted question, so let's not repeat work
319 * unnecessarily. */
320 return -EALREADY;
321 }
322
323 q = dns_question_new(family == AF_UNSPEC ? 2 : 1);
324 if (!q)
325 return -ENOMEM;
326
327 if (family != AF_INET6) {
328 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
329
330 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name);
331 if (!key)
332 return -ENOMEM;
333
334 r = dns_question_add(q, key);
335 if (r < 0)
336 return r;
337 }
338
339 if (family != AF_INET) {
340 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
341
342 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name);
343 if (!key)
344 return -ENOMEM;
345
346 r = dns_question_add(q, key);
347 if (r < 0)
348 return r;
349 }
350
351 *ret = q;
352 q = NULL;
353
354 return 0;
355 }
356
357 int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) {
358 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
359 _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
360 _cleanup_free_ char *reverse = NULL;
361 int r;
362
363 assert(ret);
364 assert(a);
365
366 if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
367 return -EAFNOSUPPORT;
368
369 r = dns_name_reverse(family, a, &reverse);
370 if (r < 0)
371 return r;
372
373 q = dns_question_new(1);
374 if (!q)
375 return -ENOMEM;
376
377 key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse);
378 if (!key)
379 return -ENOMEM;
380
381 reverse = NULL;
382
383 r = dns_question_add(q, key);
384 if (r < 0)
385 return r;
386
387 *ret = q;
388 q = NULL;
389
390 return 0;
391 }
392
393 int dns_question_new_service(
394 DnsQuestion **ret,
395 const char *service,
396 const char *type,
397 const char *domain,
398 bool with_txt,
399 bool convert_idna) {
400
401 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
402 _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
403 _cleanup_free_ char *buf = NULL, *joined = NULL;
404 const char *name;
405 int r;
406
407 assert(ret);
408
409 /* We support three modes of invocation:
410 *
411 * 1. Only a domain is specified, in which case we assume a properly encoded SRV RR name, including service
412 * type and possibly a service name. If specified in this way we assume it's already IDNA converted if
413 * that's necessary.
414 *
415 * 2. Both service type and a domain specified, in which case a normal SRV RR is assumed, without a DNS-SD
416 * style prefix. In this case we'll IDNA convert the domain, if that's requested.
417 *
418 * 3. All three of service name, type and domain are specified, in which case a DNS-SD service is put
419 * together. The service name is never IDNA converted, and the domain is if requested.
420 *
421 * It's not supported to specify a service name without a type, or no domain name.
422 */
423
424 if (!domain)
425 return -EINVAL;
426
427 if (type) {
428 if (convert_idna) {
429 r = dns_name_apply_idna(domain, &buf);
430 if (r < 0)
431 return r;
432 if (r > 0)
433 domain = buf;
434 }
435
436 r = dns_service_join(service, type, domain, &joined);
437 if (r < 0)
438 return r;
439
440 name = joined;
441 } else {
442 if (service)
443 return -EINVAL;
444
445 name = domain;
446 }
447
448 q = dns_question_new(1 + with_txt);
449 if (!q)
450 return -ENOMEM;
451
452 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name);
453 if (!key)
454 return -ENOMEM;
455
456 r = dns_question_add(q, key);
457 if (r < 0)
458 return r;
459
460 if (with_txt) {
461 dns_resource_key_unref(key);
462 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name);
463 if (!key)
464 return -ENOMEM;
465
466 r = dns_question_add(q, key);
467 if (r < 0)
468 return r;
469 }
470
471 *ret = q;
472 q = NULL;
473
474 return 0;
475 }