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