1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "alloc-util.h"
4 #include "dns-domain.h"
5 #include "dns-type.h"
6 #include "resolved-dns-question.h"
7
dns_question_new(size_t n)8 DnsQuestion *dns_question_new(size_t n) {
9 DnsQuestion *q;
10
11 if (n > UINT16_MAX) /* We can only place 64K key in an question section at max */
12 n = UINT16_MAX;
13
14 q = malloc0(offsetof(DnsQuestion, items) + sizeof(DnsQuestionItem) * n);
15 if (!q)
16 return NULL;
17
18 q->n_ref = 1;
19 q->n_allocated = n;
20
21 return q;
22 }
23
dns_question_free(DnsQuestion * q)24 static DnsQuestion *dns_question_free(DnsQuestion *q) {
25 DnsResourceKey *key;
26
27 assert(q);
28
29 DNS_QUESTION_FOREACH(key, q)
30 dns_resource_key_unref(key);
31
32 return mfree(q);
33 }
34
35 DEFINE_TRIVIAL_REF_UNREF_FUNC(DnsQuestion, dns_question, dns_question_free);
36
dns_question_add_raw(DnsQuestion * q,DnsResourceKey * key,DnsQuestionFlags flags)37 int dns_question_add_raw(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags) {
38 /* Insert without checking for duplicates. */
39
40 assert(key);
41 assert(q);
42
43 if (q->n_keys >= q->n_allocated)
44 return -ENOSPC;
45
46 q->items[q->n_keys++] = (DnsQuestionItem) {
47 .key = dns_resource_key_ref(key),
48 .flags = flags,
49 };
50 return 0;
51 }
52
dns_question_add(DnsQuestion * q,DnsResourceKey * key,DnsQuestionFlags flags)53 int dns_question_add(DnsQuestion *q, DnsResourceKey *key, DnsQuestionFlags flags) {
54 DnsQuestionItem *item;
55 int r;
56
57 assert(key);
58
59 if (!q)
60 return -ENOSPC;
61
62
63 DNS_QUESTION_FOREACH_ITEM(item, q) {
64 r = dns_resource_key_equal(item->key, key);
65 if (r < 0)
66 return r;
67 if (r > 0 && item->flags == flags)
68 return 0;
69 }
70
71 return dns_question_add_raw(q, key, flags);
72 }
73
dns_question_matches_rr(DnsQuestion * q,DnsResourceRecord * rr,const char * search_domain)74 int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) {
75 DnsResourceKey *key;
76 int r;
77
78 assert(rr);
79
80 if (!q)
81 return 0;
82
83 DNS_QUESTION_FOREACH(key, q) {
84 r = dns_resource_key_match_rr(key, rr, search_domain);
85 if (r != 0)
86 return r;
87 }
88
89 return 0;
90 }
91
dns_question_matches_cname_or_dname(DnsQuestion * q,DnsResourceRecord * rr,const char * search_domain)92 int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) {
93 DnsResourceKey *key;
94 int r;
95
96 assert(rr);
97
98 if (!q)
99 return 0;
100
101 if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME))
102 return 0;
103
104 DNS_QUESTION_FOREACH(key, q) {
105 /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */
106 if (!dns_type_may_redirect(key->type))
107 return 0;
108
109 r = dns_resource_key_match_cname_or_dname(key, rr->key, search_domain);
110 if (r != 0)
111 return r;
112 }
113
114 return 0;
115 }
116
dns_question_is_valid_for_query(DnsQuestion * q)117 int dns_question_is_valid_for_query(DnsQuestion *q) {
118 const char *name;
119 size_t i;
120 int r;
121
122 if (!q)
123 return 0;
124
125 if (q->n_keys <= 0)
126 return 0;
127
128 if (q->n_keys > 65535)
129 return 0;
130
131 name = dns_resource_key_name(q->items[0].key);
132 if (!name)
133 return 0;
134
135 /* Check that all keys in this question bear the same name */
136 for (i = 0; i < q->n_keys; i++) {
137 assert(q->items[i].key);
138
139 if (i > 0) {
140 r = dns_name_equal(dns_resource_key_name(q->items[i].key), name);
141 if (r <= 0)
142 return r;
143 }
144
145 if (!dns_type_is_valid_query(q->items[i].key->type))
146 return 0;
147 }
148
149 return 1;
150 }
151
dns_question_contains_key(DnsQuestion * q,const DnsResourceKey * k)152 int dns_question_contains_key(DnsQuestion *q, const DnsResourceKey *k) {
153 size_t j;
154 int r;
155
156 assert(k);
157
158 if (!q)
159 return 0;
160
161
162 for (j = 0; j < q->n_keys; j++) {
163 r = dns_resource_key_equal(q->items[j].key, k);
164 if (r != 0)
165 return r;
166 }
167
168 return 0;
169 }
170
dns_question_contains_item(DnsQuestion * q,const DnsQuestionItem * i)171 static int dns_question_contains_item(DnsQuestion *q, const DnsQuestionItem *i) {
172 DnsQuestionItem *item;
173 int r;
174
175 assert(i);
176
177 DNS_QUESTION_FOREACH_ITEM(item, q) {
178 if (item->flags != i->flags)
179 continue;
180 r = dns_resource_key_equal(item->key, i->key);
181 if (r != 0)
182 return r;
183 }
184
185 return false;
186 }
187
dns_question_is_equal(DnsQuestion * a,DnsQuestion * b)188 int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) {
189 DnsQuestionItem *item;
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 items in a are also contained b, and vice versa */
201
202 DNS_QUESTION_FOREACH_ITEM(item, a) {
203 r = dns_question_contains_item(b, item);
204 if (r <= 0)
205 return r;
206 }
207 DNS_QUESTION_FOREACH_ITEM(item, b) {
208 r = dns_question_contains_item(a, item);
209 if (r <= 0)
210 return r;
211 }
212
213 return 1;
214 }
215
dns_question_cname_redirect(DnsQuestion * q,const DnsResourceRecord * cname,DnsQuestion ** ret)216 int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) {
217 _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL;
218 DnsResourceKey *key;
219 bool same = true;
220 int r;
221
222 assert(cname);
223 assert(ret);
224 assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME));
225
226 if (dns_question_size(q) <= 0) {
227 *ret = NULL;
228 return 0;
229 }
230
231 DNS_QUESTION_FOREACH(key, q) {
232 _cleanup_free_ char *destination = NULL;
233 const char *d;
234
235 if (cname->key->type == DNS_TYPE_CNAME)
236 d = cname->cname.name;
237 else {
238 r = dns_name_change_suffix(dns_resource_key_name(key), dns_resource_key_name(cname->key), cname->dname.name, &destination);
239 if (r < 0)
240 return r;
241 if (r == 0)
242 continue;
243
244 d = destination;
245 }
246
247 r = dns_name_equal(dns_resource_key_name(key), d);
248 if (r < 0)
249 return r;
250
251 if (r == 0) {
252 same = false;
253 break;
254 }
255 }
256
257 /* Fully the same, indicate we didn't do a thing */
258 if (same) {
259 *ret = NULL;
260 return 0;
261 }
262
263 n = dns_question_new(q->n_keys);
264 if (!n)
265 return -ENOMEM;
266
267 /* Create a new question, and patch in the new name */
268 DNS_QUESTION_FOREACH(key, q) {
269 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL;
270
271 k = dns_resource_key_new_redirect(key, cname);
272 if (!k)
273 return -ENOMEM;
274
275 r = dns_question_add(n, k, 0);
276 if (r < 0)
277 return r;
278 }
279
280 *ret = TAKE_PTR(n);
281
282 return 1;
283 }
284
dns_question_first_name(DnsQuestion * q)285 const char *dns_question_first_name(DnsQuestion *q) {
286
287 if (!q)
288 return NULL;
289
290 if (q->n_keys < 1)
291 return NULL;
292
293 return dns_resource_key_name(q->items[0].key);
294 }
295
dns_question_new_address(DnsQuestion ** ret,int family,const char * name,bool convert_idna)296 int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna) {
297 _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
298 _cleanup_free_ char *buf = NULL;
299 int r;
300
301 assert(ret);
302 assert(name);
303
304 if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
305 return -EAFNOSUPPORT;
306
307 if (convert_idna) {
308 r = dns_name_apply_idna(name, &buf);
309 if (r < 0)
310 return r;
311 if (r > 0 && !streq(name, buf))
312 name = buf;
313 else
314 /* We did not manage to create convert the idna name, or it's
315 * the same as the original name. We assume the caller already
316 * created an unconverted question, so let's not repeat work
317 * unnecessarily. */
318 return -EALREADY;
319 }
320
321 q = dns_question_new(family == AF_UNSPEC ? 2 : 1);
322 if (!q)
323 return -ENOMEM;
324
325 if (family != AF_INET6) {
326 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
327
328 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name);
329 if (!key)
330 return -ENOMEM;
331
332 r = dns_question_add(q, key, 0);
333 if (r < 0)
334 return r;
335 }
336
337 if (family != AF_INET) {
338 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
339
340 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name);
341 if (!key)
342 return -ENOMEM;
343
344 r = dns_question_add(q, key, 0);
345 if (r < 0)
346 return r;
347 }
348
349 *ret = TAKE_PTR(q);
350
351 return 0;
352 }
353
dns_question_new_reverse(DnsQuestion ** ret,int family,const union in_addr_union * a)354 int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) {
355 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
356 _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
357 _cleanup_free_ char *reverse = NULL;
358 int r;
359
360 assert(ret);
361 assert(a);
362
363 if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC))
364 return -EAFNOSUPPORT;
365
366 r = dns_name_reverse(family, a, &reverse);
367 if (r < 0)
368 return r;
369
370 q = dns_question_new(1);
371 if (!q)
372 return -ENOMEM;
373
374 key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse);
375 if (!key)
376 return -ENOMEM;
377
378 reverse = NULL;
379
380 r = dns_question_add(q, key, 0);
381 if (r < 0)
382 return r;
383
384 *ret = TAKE_PTR(q);
385
386 return 0;
387 }
388
dns_question_new_service(DnsQuestion ** ret,const char * service,const char * type,const char * domain,bool with_txt,bool convert_idna)389 int dns_question_new_service(
390 DnsQuestion **ret,
391 const char *service,
392 const char *type,
393 const char *domain,
394 bool with_txt,
395 bool convert_idna) {
396
397 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
398 _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL;
399 _cleanup_free_ char *buf = NULL, *joined = NULL;
400 const char *name;
401 int r;
402
403 assert(ret);
404
405 /* We support three modes of invocation:
406 *
407 * 1. Only a domain is specified, in which case we assume a properly encoded SRV RR name, including service
408 * type and possibly a service name. If specified in this way we assume it's already IDNA converted if
409 * that's necessary.
410 *
411 * 2. Both service type and a domain specified, in which case a normal SRV RR is assumed, without a DNS-SD
412 * style prefix. In this case we'll IDNA convert the domain, if that's requested.
413 *
414 * 3. All three of service name, type and domain are specified, in which case a DNS-SD service is put
415 * together. The service name is never IDNA converted, and the domain is if requested.
416 *
417 * It's not supported to specify a service name without a type, or no domain name.
418 */
419
420 if (!domain)
421 return -EINVAL;
422
423 if (type) {
424 if (convert_idna) {
425 r = dns_name_apply_idna(domain, &buf);
426 if (r < 0)
427 return r;
428 if (r > 0)
429 domain = buf;
430 }
431
432 r = dns_service_join(service, type, domain, &joined);
433 if (r < 0)
434 return r;
435
436 name = joined;
437 } else {
438 if (service)
439 return -EINVAL;
440
441 name = domain;
442 }
443
444 q = dns_question_new(1 + with_txt);
445 if (!q)
446 return -ENOMEM;
447
448 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name);
449 if (!key)
450 return -ENOMEM;
451
452 r = dns_question_add(q, key, 0);
453 if (r < 0)
454 return r;
455
456 if (with_txt) {
457 dns_resource_key_unref(key);
458 key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name);
459 if (!key)
460 return -ENOMEM;
461
462 r = dns_question_add(q, key, 0);
463 if (r < 0)
464 return r;
465 }
466
467 *ret = TAKE_PTR(q);
468
469 return 0;
470 }
471
472 /*
473 * This function is not used in the code base, but is useful when debugging. Do not delete.
474 */
dns_question_dump(DnsQuestion * question,FILE * f)475 void dns_question_dump(DnsQuestion *question, FILE *f) {
476 DnsResourceKey *k;
477
478 if (!f)
479 f = stdout;
480
481 DNS_QUESTION_FOREACH(k, question) {
482 char buf[DNS_RESOURCE_KEY_STRING_MAX];
483
484 fputc('\t', f);
485 fputs(dns_resource_key_to_string(k, buf, sizeof(buf)), f);
486 fputc('\n', f);
487 }
488 }
489