chiark / gitweb /
resolved: properly process DNAME RRs
[elogind.git] / src / resolve / resolved-dns-zone.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include "list.h"
23
24 #include "resolved-dns-zone.h"
25 #include "resolved-dns-domain.h"
26 #include "resolved-dns-packet.h"
27
28 /* Never allow more than 1K entries */
29 #define ZONE_MAX 1024
30
31 static void dns_zone_item_probe_stop(DnsZoneItem *i) {
32         DnsTransaction *t;
33         assert(i);
34
35         if (!i->probe_transaction)
36                 return;
37
38         t = i->probe_transaction;
39         i->probe_transaction = NULL;
40
41         set_remove(t->zone_items, i);
42         dns_transaction_gc(t);
43 }
44
45 static void dns_zone_item_free(DnsZoneItem *i) {
46         if (!i)
47                 return;
48
49         dns_zone_item_probe_stop(i);
50         dns_resource_record_unref(i->rr);
51
52         free(i);
53 }
54
55 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
56
57 static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
58         DnsZoneItem *first;
59
60         assert(z);
61
62         if (!i)
63                 return;
64
65         first = hashmap_get(z->by_key, i->rr->key);
66         LIST_REMOVE(by_key, first, i);
67         if (first)
68                 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
69         else
70                 hashmap_remove(z->by_key, i->rr->key);
71
72         first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
73         LIST_REMOVE(by_name, first, i);
74         if (first)
75                 assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
76         else
77                 hashmap_remove(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
78
79         dns_zone_item_free(i);
80 }
81
82 void dns_zone_flush(DnsZone *z) {
83         DnsZoneItem *i;
84
85         assert(z);
86
87         while ((i = hashmap_first(z->by_key)))
88                 dns_zone_item_remove_and_free(z, i);
89
90         assert(hashmap_size(z->by_key) == 0);
91         assert(hashmap_size(z->by_name) == 0);
92
93         hashmap_free(z->by_key);
94         z->by_key = NULL;
95
96         hashmap_free(z->by_name);
97         z->by_name = NULL;
98 }
99
100 static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
101         DnsZoneItem *i;
102
103         assert(z);
104         assert(rr);
105
106         LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
107                 if (dns_resource_record_equal(i->rr, rr))
108                         return i;
109
110         return NULL;
111 }
112
113 void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
114         DnsZoneItem *i;
115
116         assert(z);
117         assert(rr);
118
119         i = dns_zone_get(z, rr);
120         if (i)
121                 dns_zone_item_remove_and_free(z, i);
122 }
123
124 static int dns_zone_init(DnsZone *z) {
125         int r;
126
127         assert(z);
128
129         r = hashmap_ensure_allocated(&z->by_key, dns_resource_key_hash_func, dns_resource_key_compare_func);
130         if (r < 0)
131                 return r;
132
133         r = hashmap_ensure_allocated(&z->by_name, dns_name_hash_func, dns_name_compare_func);
134         if (r < 0)
135                 return r;
136
137         return 0;
138 }
139
140 static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
141         DnsZoneItem *first;
142         int r;
143
144         first = hashmap_get(z->by_key, i->rr->key);
145         if (first) {
146                 LIST_PREPEND(by_key, first, i);
147                 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
148         } else {
149                 r = hashmap_put(z->by_key, i->rr->key, i);
150                 if (r < 0)
151                         return r;
152         }
153
154         first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
155         if (first) {
156                 LIST_PREPEND(by_name, first, i);
157                 assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
158         } else {
159                 r = hashmap_put(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key), i);
160                 if (r < 0)
161                         return r;
162         }
163
164         return 0;
165 }
166
167 static int dns_zone_item_probe_start(DnsZoneItem *i)  {
168         _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
169         _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL;
170         DnsTransaction *t;
171         int r;
172
173         assert(i);
174
175         if (i->probe_transaction)
176                 return 0;
177
178         key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key));
179         if (!key)
180                 return -ENOMEM;
181
182         question = dns_question_new(1);
183         if (!question)
184                 return -ENOMEM;
185
186         r = dns_question_add(question, key);
187         if (r < 0)
188                 return r;
189
190         t = dns_scope_find_transaction(i->scope, question);
191         if (!t) {
192                 r = dns_transaction_new(&t, i->scope, question);
193                 if (r < 0)
194                         return r;
195         }
196
197         r = set_ensure_allocated(&t->zone_items, NULL, NULL);
198         if (r < 0)
199                 goto gc;
200
201         r = set_put(t->zone_items, i);
202         if (r < 0)
203                 goto gc;
204
205         i->probe_transaction = t;
206
207         if (t->state == DNS_TRANSACTION_NULL) {
208
209                 i->block_ready++;
210                 r = dns_transaction_go(t);
211                 i->block_ready--;
212
213                 if (r < 0) {
214                         dns_zone_item_probe_stop(i);
215                         return r;
216                 }
217         }
218
219         dns_zone_item_ready(i);
220
221         return 0;
222
223 gc:
224         dns_transaction_gc(t);
225         return r;
226 }
227
228 int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
229         _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
230         DnsZoneItem *existing;
231         int r;
232
233         assert(z);
234         assert(s);
235         assert(rr);
236
237         if (rr->key->class == DNS_CLASS_ANY)
238                 return -EINVAL;
239         if (rr->key->type == DNS_TYPE_ANY)
240                 return -EINVAL;
241
242         existing = dns_zone_get(z, rr);
243         if (existing)
244                 return 0;
245
246         r = dns_zone_init(z);
247         if (r < 0)
248                 return r;
249
250         i = new0(DnsZoneItem, 1);
251         if (!i)
252                 return -ENOMEM;
253
254         i->scope = s;
255         i->rr = dns_resource_record_ref(rr);
256         i->probing_enabled = probe;
257
258         r = dns_zone_link_item(z, i);
259         if (r < 0)
260                 return r;
261
262         if (probe) {
263                 r = dns_zone_item_probe_start(i);
264                 if (r < 0) {
265                         dns_zone_item_remove_and_free(z, i);
266                         i = NULL;
267                         return r;
268                 }
269
270                 i->state = DNS_ZONE_ITEM_PROBING;
271         } else
272                 i->state = DNS_ZONE_ITEM_ESTABLISHED;
273
274         i = NULL;
275         return 0;
276 }
277
278 int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
279         _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
280         unsigned i, n_answer = 0, n_soa = 0;
281         bool tentative = true;
282         int r;
283
284         assert(z);
285         assert(q);
286         assert(ret_answer);
287         assert(ret_soa);
288
289         if (q->n_keys <= 0) {
290                 *ret_answer = NULL;
291                 *ret_soa = NULL;
292
293                 if (ret_tentative)
294                         *ret_tentative = false;
295
296                 return 0;
297         }
298
299         /* First iteration, count what we have */
300         for (i = 0; i < q->n_keys; i++) {
301                 DnsZoneItem *j, *first;
302
303                 if (q->keys[i]->type == DNS_TYPE_ANY ||
304                     q->keys[i]->class == DNS_CLASS_ANY) {
305                         bool found = false, added = false;
306                         int k;
307
308                         /* If this is a generic match, then we have to
309                          * go through the list by the name and look
310                          * for everything manually */
311
312                         first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
313                         LIST_FOREACH(by_name, j, first) {
314                                 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
315                                         continue;
316
317                                 found = true;
318
319                                 k = dns_resource_key_match_rr(q->keys[i], j->rr);
320                                 if (k < 0)
321                                         return k;
322                                 if (k > 0) {
323                                         n_answer++;
324                                         added = true;
325                                 }
326
327                         }
328
329                         if (found && !added)
330                                 n_soa++;
331
332                 } else {
333                         bool found = false;
334
335                         /* If this is a specific match, then look for
336                          * the right key immediately */
337
338                         first = hashmap_get(z->by_key, q->keys[i]);
339                         LIST_FOREACH(by_key, j, first) {
340                                 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
341                                         continue;
342
343                                 found = true;
344                                 n_answer++;
345                         }
346
347                         if (!found) {
348                                 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
349                                 LIST_FOREACH(by_name, j, first) {
350                                         if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
351                                                 continue;
352
353                                         n_soa++;
354                                         break;
355                                 }
356                         }
357                 }
358         }
359
360         if (n_answer <= 0 && n_soa <= 0) {
361                 *ret_answer = NULL;
362                 *ret_soa = NULL;
363
364                 if (ret_tentative)
365                         *ret_tentative = false;
366
367                 return 0;
368         }
369
370         if (n_answer > 0) {
371                 answer = dns_answer_new(n_answer);
372                 if (!answer)
373                         return -ENOMEM;
374         }
375
376         if (n_soa > 0) {
377                 soa = dns_answer_new(n_soa);
378                 if (!soa)
379                         return -ENOMEM;
380         }
381
382         /* Second iteration, actually add the RRs to the answers */
383         for (i = 0; i < q->n_keys; i++) {
384                 DnsZoneItem *j, *first;
385
386                 if (q->keys[i]->type == DNS_TYPE_ANY ||
387                     q->keys[i]->class == DNS_CLASS_ANY) {
388                         bool found = false, added = false;
389                         int k;
390
391                         first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
392                         LIST_FOREACH(by_name, j, first) {
393                                 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
394                                         continue;
395
396                                 found = true;
397
398                                 if (j->state != DNS_ZONE_ITEM_PROBING)
399                                         tentative = false;
400
401                                 k = dns_resource_key_match_rr(q->keys[i], j->rr);
402                                 if (k < 0)
403                                         return k;
404                                 if (k > 0) {
405                                         r = dns_answer_add(answer, j->rr);
406                                         if (r < 0)
407                                                 return r;
408
409                                         added = true;
410                                 }
411                         }
412
413                         if (found && !added) {
414                                 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
415                                 if (r < 0)
416                                         return r;
417                         }
418                 } else {
419                         bool found = false;
420
421                         first = hashmap_get(z->by_key, q->keys[i]);
422                         LIST_FOREACH(by_key, j, first) {
423                                 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
424                                         continue;
425
426                                 found = true;
427
428                                 if (j->state != DNS_ZONE_ITEM_PROBING)
429                                         tentative = false;
430
431                                 r = dns_answer_add(answer, j->rr);
432                                 if (r < 0)
433                                         return r;
434                         }
435
436                         if (!found) {
437                                 bool add_soa = false;
438
439                                 first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
440                                 LIST_FOREACH(by_name, j, first) {
441                                         if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
442                                                 continue;
443
444                                         if (j->state != DNS_ZONE_ITEM_PROBING)
445                                                 tentative = false;
446
447                                         add_soa = true;
448                                 }
449
450                                 if (add_soa) {
451                                         r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]), LLMNR_DEFAULT_TTL);
452                                         if (r < 0)
453                                                 return r;
454                                 }
455                         }
456                 }
457         }
458
459         *ret_answer = answer;
460         answer = NULL;
461
462         *ret_soa = soa;
463         soa = NULL;
464
465         if (ret_tentative)
466                 *ret_tentative = tentative;
467
468         return 1;
469 }
470
471 void dns_zone_item_conflict(DnsZoneItem *i) {
472         _cleanup_free_ char *pretty = NULL;
473
474         assert(i);
475
476         dns_resource_record_to_string(i->rr, &pretty);
477         log_info("Detected conflict on %s", strna(pretty));
478
479         /* Withdraw the conflict item */
480         i->state = DNS_ZONE_ITEM_WITHDRAWN;
481
482         /* Maybe change the hostname */
483         if (dns_name_equal(i->scope->manager->hostname, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0)
484                 manager_next_hostname(i->scope->manager);
485 }
486
487 void dns_zone_item_ready(DnsZoneItem *i) {
488         assert(i);
489         assert(i->probe_transaction);
490
491         if (i->block_ready > 0)
492                 return;
493
494         if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING))
495                 return;
496
497         if (i->probe_transaction->state != DNS_TRANSACTION_SUCCESS) {
498                 _cleanup_free_ char *pretty = NULL;
499
500                 dns_resource_record_to_string(i->rr, &pretty);
501                 log_debug("Record %s successfully probed.", strna(pretty));
502
503                 dns_zone_item_probe_stop(i);
504                 i->state = DNS_ZONE_ITEM_ESTABLISHED;
505
506         } else
507                 dns_zone_item_conflict(i);
508 }