]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-zone.c
resolved: include SOA records in LLMNR replies for non-existing RRs to allow negative...
[thirdparty/systemd.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_answer, DnsAnswer **ret_soa) {
196 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
197 unsigned i, n_answer = 0, n_soa = 0;
198 int r;
199
200 assert(z);
201 assert(q);
202 assert(ret_answer);
203 assert(ret_soa);
204
205 if (q->n_keys <= 0) {
206 *ret_answer = NULL;
207 *ret_soa = NULL;
208 return 0;
209 }
210
211 /* First iteration, count what we have */
212 for (i = 0; i < q->n_keys; i++) {
213 DnsZoneItem *j;
214
215 if (q->keys[i]->type == DNS_TYPE_ANY ||
216 q->keys[i]->class == DNS_CLASS_ANY) {
217 int k;
218
219 /* If this is a generic match, then we have to
220 * go through the list by the name and look
221 * for everything manually */
222
223 j = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
224 LIST_FOREACH(by_name, j, j) {
225 k = dns_resource_key_match_rr(q->keys[i], j->rr);
226 if (k < 0)
227 return k;
228 if (k == 0)
229 n_soa++;
230 else
231 n_answer++;
232 }
233
234 } else {
235 j = hashmap_get(z->by_key, q->keys[i]);
236 if (j) {
237 LIST_FOREACH(by_key, j, j)
238 n_answer++;
239 } else {
240 if (hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i])))
241 n_soa ++;
242 }
243 }
244 }
245
246 if (n_answer <= 0 && n_soa <= 0) {
247 *ret_answer = NULL;
248 *ret_soa = NULL;
249 return 0;
250 }
251
252 if (n_answer > 0) {
253 answer = dns_answer_new(n_answer);
254 if (!answer)
255 return -ENOMEM;
256 }
257
258 if (n_soa > 0) {
259 soa = dns_answer_new(n_soa);
260 if (!soa)
261 return -ENOMEM;
262 }
263
264 /* Second iteration, actually add the RRs to the answers */
265 for (i = 0; i < q->n_keys; i++) {
266 DnsZoneItem *j;
267
268 if (q->keys[i]->type == DNS_TYPE_ANY ||
269 q->keys[i]->class == DNS_CLASS_ANY) {
270 int k;
271
272 j = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]));
273 LIST_FOREACH(by_name, j, j) {
274 k = dns_resource_key_match_rr(q->keys[i], j->rr);
275 if (k < 0)
276 return k;
277 if (k == 0)
278 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]));
279 else
280 r = dns_answer_add(answer, j->rr);
281 if (r < 0)
282 return r;
283 }
284 } else {
285
286 j = hashmap_get(z->by_key, q->keys[i]);
287 if (j) {
288 LIST_FOREACH(by_key, j, j) {
289 r = dns_answer_add(answer, j->rr);
290 if (r < 0)
291 return r;
292 }
293 } else {
294 if (hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(q->keys[i]))) {
295 r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(q->keys[i]));
296 if (r < 0)
297 return r;
298 }
299 }
300 }
301 }
302
303 *ret_answer = answer;
304 answer = NULL;
305
306 *ret_soa = soa;
307 soa = NULL;
308
309 return 1;
310 }