chiark / gitweb /
resolved: respond to ANY queries from our zone
[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 typedef struct DnsZoneItem DnsZoneItem;
32
33 struct DnsZoneItem {
34         DnsResourceRecord *rr;
35         bool verified;
36         LIST_FIELDS(DnsZoneItem, by_key);
37         LIST_FIELDS(DnsZoneItem, by_name);
38 };
39
40 static void dns_zone_item_free(DnsZoneItem *i) {
41         if (!i)
42                 return;
43
44         dns_resource_record_unref(i->rr);
45         free(i);
46 }
47
48 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
49
50 static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) {
51         DnsZoneItem *first;
52
53         assert(z);
54
55         if (!i)
56                 return;
57
58         first = hashmap_get(z->by_key, i->rr->key);
59         LIST_REMOVE(by_key, first, i);
60         if (first)
61                 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
62         else
63                 hashmap_remove(z->by_key, i->rr->key);
64
65         first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
66         LIST_REMOVE(by_name, first, i);
67         if (first)
68                 assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
69         else
70                 hashmap_remove(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
71
72         dns_zone_item_free(i);
73 }
74
75 void dns_zone_flush(DnsZone *z) {
76         DnsZoneItem *i;
77
78         assert(z);
79
80         while ((i = hashmap_first(z->by_key)))
81                 dns_zone_item_remove_and_free(z, i);
82
83         assert(hashmap_size(z->by_key) == 0);
84         assert(hashmap_size(z->by_name) == 0);
85
86         hashmap_free(z->by_key);
87         z->by_key = NULL;
88
89         hashmap_free(z->by_name);
90         z->by_name = NULL;
91 }
92
93 static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
94         DnsZoneItem *i;
95
96         assert(z);
97         assert(rr);
98
99         LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
100                 if (dns_resource_record_equal(i->rr, rr))
101                         return i;
102
103         return NULL;
104 }
105
106 void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
107         DnsZoneItem *i;
108
109         assert(z);
110         assert(rr);
111
112         i = dns_zone_get(z, rr);
113         if (i)
114                 dns_zone_item_remove_and_free(z, i);
115 }
116
117 static int dns_zone_init(DnsZone *z) {
118         int r;
119
120         assert(z);
121
122         r = hashmap_ensure_allocated(&z->by_key, dns_resource_key_hash_func, dns_resource_key_compare_func);
123         if (r < 0)
124                 return r;
125
126         r = hashmap_ensure_allocated(&z->by_name, dns_name_hash_func, dns_name_compare_func);
127         if (r < 0)
128                 return r;
129
130         return 0;
131 }
132
133 static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
134         DnsZoneItem *first;
135         int r;
136
137         first = hashmap_get(z->by_key, i->rr->key);
138         if (first) {
139                 LIST_PREPEND(by_key, first, i);
140                 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
141         } else {
142                 r = hashmap_put(z->by_key, i->rr->key, i);
143                 if (r < 0)
144                         return r;
145         }
146
147         first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key));
148         if (first) {
149                 LIST_PREPEND(by_name, first, i);
150                 assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0);
151         } else {
152                 r = hashmap_put(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key), i);
153                 if (r < 0)
154                         return r;
155         }
156
157         return 0;
158 }
159
160 int dns_zone_put(DnsZone *z, DnsResourceRecord *rr) {
161         _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
162         DnsZoneItem *existing;
163         int r;
164
165         assert(z);
166         assert(rr);
167
168         if (rr->key->class == DNS_CLASS_ANY)
169                 return -EINVAL;
170         if (rr->key->type == DNS_TYPE_ANY)
171                 return -EINVAL;
172
173         existing = dns_zone_get(z, rr);
174         if (existing)
175                 return 0;
176
177         r = dns_zone_init(z);
178         if (r < 0)
179                 return r;
180
181         i = new0(DnsZoneItem, 1);
182         if (!i)
183                 return -ENOMEM;
184
185         i->rr = dns_resource_record_ref(rr);
186
187         r = dns_zone_link_item(z, i);
188         if (r < 0)
189                 return r;
190
191         i = NULL;
192         return 0;
193 }
194
195 int dns_zone_lookup(DnsZone *z, DnsQuestion *q, DnsAnswer **ret) {
196         _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
197         bool has_other_rrs = false;
198         unsigned i, n = 0;
199         int r;
200
201         assert(z);
202         assert(q);
203         assert(ret);
204
205         if (q->n_keys <= 0) {
206                 *ret = NULL;
207                 return 0;
208         }
209
210         for (i = 0; i < q->n_keys; i++) {
211                 DnsZoneItem *j;
212
213                 if (q->keys[i]->type == DNS_TYPE_ANY ||
214                     q->keys[i]->class == DNS_CLASS_ANY) {
215                         int k;
216
217                         /* If this is a generic match, then we have to
218                          * go through the list by the name and look
219                          * for everything manually */
220
221                         j = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
222                         LIST_FOREACH(by_name, j, j) {
223                                 has_other_rrs = true;
224
225                                 k = dns_resource_key_match_rr(q->keys[i], j->rr);
226                                 if (k < 0)
227                                         return k;
228                                 if (k == 0)
229                                         continue;
230
231                                 n++;
232                         }
233
234                 } else {
235                         j = hashmap_get(z->by_key, q->keys[i]);
236                         if (!j) {
237                                 if (hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i])))
238                                         has_other_rrs = true;
239
240                                 continue;
241                         }
242
243                         LIST_FOREACH(by_key, j, j)
244                                 n++;
245                 }
246         }
247
248         if (n <= 0) {
249                 *ret = NULL;
250                 return has_other_rrs;
251         }
252
253         answer = dns_answer_new(n);
254         if (!answer)
255                 return -ENOMEM;
256
257         for (i = 0; i < q->n_keys; i++) {
258                 DnsZoneItem *j;
259
260                 if (q->keys[i]->type == DNS_TYPE_ANY ||
261                     q->keys[i]->class == DNS_CLASS_ANY) {
262                         int k;
263
264                         j = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
265                         LIST_FOREACH(by_name, j, j) {
266                                 k = dns_resource_key_match_rr(q->keys[i], j->rr);
267                                 if (k < 0)
268                                         return k;
269                                 if (k == 0)
270                                         continue;
271
272                                 r = dns_answer_add(answer, j->rr);
273                                 if (r < 0)
274                                         return r;
275                         }
276                 } else {
277
278                         j = hashmap_get(z->by_key, q->keys[i]);
279                         LIST_FOREACH(by_key, j, j) {
280                                 r = dns_answer_add(answer, j->rr);
281                                 if (r < 0)
282                                         return r;
283                         }
284                 }
285         }
286
287         *ret = answer;
288         answer = NULL;
289
290         return 1;
291 }