]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-dns-zone.c
resolved: explicitly refuse zone transfers using the bus API
[thirdparty/systemd.git] / src / resolve / resolved-dns-zone.c
CommitLineData
623a4c97
LP
1/***
2 This file is part of systemd.
3
4 Copyright 2014 Lennart Poettering
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18***/
19
b5efdb8a 20#include "alloc-util.h"
4ad7f276 21#include "dns-domain.h"
07630cea 22#include "list.h"
623a4c97 23#include "resolved-dns-packet.h"
07630cea
LP
24#include "resolved-dns-zone.h"
25#include "string-util.h"
623a4c97
LP
26
27/* Never allow more than 1K entries */
28#define ZONE_MAX 1024
29
3ef64445 30void dns_zone_item_probe_stop(DnsZoneItem *i) {
ec2c5e43
LP
31 DnsTransaction *t;
32 assert(i);
623a4c97 33
ec2c5e43
LP
34 if (!i->probe_transaction)
35 return;
36
37 t = i->probe_transaction;
38 i->probe_transaction = NULL;
39
547973de 40 set_remove(t->notify_zone_items, i);
35aa04e9 41 set_remove(t->notify_zone_items_done, i);
ec2c5e43
LP
42 dns_transaction_gc(t);
43}
623a4c97
LP
44
45static void dns_zone_item_free(DnsZoneItem *i) {
46 if (!i)
47 return;
48
ec2c5e43 49 dns_zone_item_probe_stop(i);
623a4c97 50 dns_resource_record_unref(i->rr);
ec2c5e43 51
623a4c97
LP
52 free(i);
53}
54
55DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free);
56
57static 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
1c02e7ba 72 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
623a4c97
LP
73 LIST_REMOVE(by_name, first, i);
74 if (first)
1c02e7ba 75 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
623a4c97 76 else
1c02e7ba 77 hashmap_remove(z->by_name, dns_resource_key_name(i->rr->key));
623a4c97
LP
78
79 dns_zone_item_free(i);
80}
81
82void 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
525d3cc7
LP
93 z->by_key = hashmap_free(z->by_key);
94 z->by_name = hashmap_free(z->by_name);
623a4c97
LP
95}
96
97static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) {
98 DnsZoneItem *i;
99
100 assert(z);
101 assert(rr);
102
103 LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key))
3ef77d04 104 if (dns_resource_record_equal(i->rr, rr) > 0)
623a4c97
LP
105 return i;
106
107 return NULL;
108}
109
110void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) {
111 DnsZoneItem *i;
112
113 assert(z);
114 assert(rr);
115
116 i = dns_zone_get(z, rr);
117 if (i)
118 dns_zone_item_remove_and_free(z, i);
119}
120
121static int dns_zone_init(DnsZone *z) {
122 int r;
123
124 assert(z);
125
d5099efc 126 r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops);
623a4c97
LP
127 if (r < 0)
128 return r;
129
d5099efc 130 r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops);
623a4c97
LP
131 if (r < 0)
132 return r;
133
134 return 0;
135}
136
137static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) {
138 DnsZoneItem *first;
139 int r;
140
141 first = hashmap_get(z->by_key, i->rr->key);
142 if (first) {
143 LIST_PREPEND(by_key, first, i);
144 assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0);
145 } else {
146 r = hashmap_put(z->by_key, i->rr->key, i);
147 if (r < 0)
148 return r;
149 }
150
1c02e7ba 151 first = hashmap_get(z->by_name, dns_resource_key_name(i->rr->key));
623a4c97
LP
152 if (first) {
153 LIST_PREPEND(by_name, first, i);
1c02e7ba 154 assert_se(hashmap_replace(z->by_name, dns_resource_key_name(first->rr->key), first) >= 0);
623a4c97 155 } else {
1c02e7ba 156 r = hashmap_put(z->by_name, dns_resource_key_name(i->rr->key), i);
623a4c97
LP
157 if (r < 0)
158 return r;
159 }
160
161 return 0;
162}
163
ec2c5e43 164static int dns_zone_item_probe_start(DnsZoneItem *i) {
ec2c5e43
LP
165 DnsTransaction *t;
166 int r;
167
168 assert(i);
169
170 if (i->probe_transaction)
171 return 0;
172
1c02e7ba 173 t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key)), false);
ec2c5e43 174 if (!t) {
1b4f6e79
LP
175 _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL;
176
1c02e7ba 177 key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, dns_resource_key_name(i->rr->key));
1b4f6e79
LP
178 if (!key)
179 return -ENOMEM;
180
f52e61da 181 r = dns_transaction_new(&t, i->scope, key);
ec2c5e43
LP
182 if (r < 0)
183 return r;
184 }
185
547973de 186 r = set_ensure_allocated(&t->notify_zone_items, NULL);
ec2c5e43
LP
187 if (r < 0)
188 goto gc;
189
35aa04e9
LP
190 r = set_ensure_allocated(&t->notify_zone_items_done, NULL);
191 if (r < 0)
192 goto gc;
193
547973de 194 r = set_put(t->notify_zone_items, i);
ec2c5e43
LP
195 if (r < 0)
196 goto gc;
197
198 i->probe_transaction = t;
199
200 if (t->state == DNS_TRANSACTION_NULL) {
201
202 i->block_ready++;
203 r = dns_transaction_go(t);
204 i->block_ready--;
205
206 if (r < 0) {
207 dns_zone_item_probe_stop(i);
208 return r;
209 }
210 }
211
547973de 212 dns_zone_item_notify(i);
ec2c5e43
LP
213 return 0;
214
215gc:
216 dns_transaction_gc(t);
217 return r;
218}
219
220int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) {
623a4c97
LP
221 _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL;
222 DnsZoneItem *existing;
223 int r;
224
225 assert(z);
ec2c5e43 226 assert(s);
623a4c97
LP
227 assert(rr);
228
222148b6 229 if (dns_class_is_pseudo(rr->key->class))
1d3b690f 230 return -EINVAL;
222148b6 231 if (dns_type_is_pseudo(rr->key->type))
1d3b690f
LP
232 return -EINVAL;
233
623a4c97
LP
234 existing = dns_zone_get(z, rr);
235 if (existing)
236 return 0;
237
238 r = dns_zone_init(z);
239 if (r < 0)
240 return r;
241
242 i = new0(DnsZoneItem, 1);
243 if (!i)
244 return -ENOMEM;
245
ec2c5e43 246 i->scope = s;
623a4c97 247 i->rr = dns_resource_record_ref(rr);
ec2c5e43 248 i->probing_enabled = probe;
623a4c97
LP
249
250 r = dns_zone_link_item(z, i);
251 if (r < 0)
252 return r;
253
ec2c5e43 254 if (probe) {
cd1b20f9
LP
255 DnsZoneItem *first, *j;
256 bool established = false;
257
258 /* Check if there's already an RR with the same name
259 * established. If so, it has been probed already, and
260 * we don't ned to probe again. */
261
262 LIST_FIND_HEAD(by_name, i, first);
263 LIST_FOREACH(by_name, j, first) {
264 if (i == j)
265 continue;
266
267 if (j->state == DNS_ZONE_ITEM_ESTABLISHED)
268 established = true;
ec2c5e43
LP
269 }
270
cd1b20f9
LP
271 if (established)
272 i->state = DNS_ZONE_ITEM_ESTABLISHED;
273 else {
60eb3f7c
LP
274 i->state = DNS_ZONE_ITEM_PROBING;
275
cd1b20f9
LP
276 r = dns_zone_item_probe_start(i);
277 if (r < 0) {
278 dns_zone_item_remove_and_free(z, i);
279 i = NULL;
280 return r;
281 }
cd1b20f9 282 }
ec2c5e43
LP
283 } else
284 i->state = DNS_ZONE_ITEM_ESTABLISHED;
285
623a4c97
LP
286 i = NULL;
287 return 0;
288}
289
97ebebbc 290int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, int ifindex, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) {
8bf52d3d 291 _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
5032b16d
LP
292 unsigned n_answer = 0;
293 DnsZoneItem *j, *first;
294 bool tentative = true, need_soa = false;
d5323661 295 int r;
623a4c97 296
97ebebbc
LP
297 /* Note that we don't actually need the ifindex for anything. However when it is passed we'll initialize the
298 * ifindex field in the answer with it */
299
623a4c97 300 assert(z);
5032b16d 301 assert(key);
8bf52d3d 302 assert(ret_answer);
623a4c97 303
5032b16d 304 /* First iteration, count what we have */
ec2c5e43 305
5032b16d
LP
306 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
307 bool found = false, added = false;
308 int k;
623a4c97 309
5032b16d
LP
310 /* If this is a generic match, then we have to
311 * go through the list by the name and look
312 * for everything manually */
623a4c97 313
1c02e7ba 314 first = hashmap_get(z->by_name, dns_resource_key_name(key));
5032b16d
LP
315 LIST_FOREACH(by_name, j, first) {
316 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
317 continue;
d5323661 318
5032b16d 319 found = true;
d5323661 320
801ad6a6 321 k = dns_resource_key_match_rr(key, j->rr, NULL);
5032b16d
LP
322 if (k < 0)
323 return k;
324 if (k > 0) {
325 n_answer++;
326 added = true;
327 }
ec2c5e43 328
5032b16d 329 }
ec2c5e43 330
5032b16d
LP
331 if (found && !added)
332 need_soa = true;
ec2c5e43 333
5032b16d
LP
334 } else {
335 bool found = false;
d5323661 336
5032b16d
LP
337 /* If this is a specific match, then look for
338 * the right key immediately */
ec2c5e43 339
5032b16d
LP
340 first = hashmap_get(z->by_key, key);
341 LIST_FOREACH(by_key, j, first) {
342 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
343 continue;
ec2c5e43 344
5032b16d
LP
345 found = true;
346 n_answer++;
347 }
ec2c5e43 348
5032b16d 349 if (!found) {
1c02e7ba 350 first = hashmap_get(z->by_name, dns_resource_key_name(key));
5032b16d 351 LIST_FOREACH(by_name, j, first) {
ec2c5e43
LP
352 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
353 continue;
354
5032b16d
LP
355 need_soa = true;
356 break;
d5323661 357 }
d5323661 358 }
623a4c97
LP
359 }
360
5032b16d
LP
361 if (n_answer <= 0 && !need_soa)
362 goto return_empty;
623a4c97 363
8bf52d3d
LP
364 if (n_answer > 0) {
365 answer = dns_answer_new(n_answer);
366 if (!answer)
367 return -ENOMEM;
368 }
623a4c97 369
5032b16d
LP
370 if (need_soa) {
371 soa = dns_answer_new(1);
8bf52d3d
LP
372 if (!soa)
373 return -ENOMEM;
374 }
375
376 /* Second iteration, actually add the RRs to the answers */
5032b16d
LP
377 if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) {
378 bool found = false, added = false;
379 int k;
d5323661 380
1c02e7ba 381 first = hashmap_get(z->by_name, dns_resource_key_name(key));
5032b16d
LP
382 LIST_FOREACH(by_name, j, first) {
383 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
384 continue;
ec2c5e43 385
5032b16d 386 found = true;
ec2c5e43 387
5032b16d
LP
388 if (j->state != DNS_ZONE_ITEM_PROBING)
389 tentative = false;
ec2c5e43 390
801ad6a6 391 k = dns_resource_key_match_rr(key, j->rr, NULL);
5032b16d
LP
392 if (k < 0)
393 return k;
394 if (k > 0) {
97ebebbc 395 r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
d5323661
LP
396 if (r < 0)
397 return r;
5032b16d
LP
398
399 added = true;
d5323661 400 }
5032b16d 401 }
d5323661 402
5032b16d 403 if (found && !added) {
97ebebbc 404 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
5032b16d
LP
405 if (r < 0)
406 return r;
407 }
408 } else {
409 bool found = false;
ec2c5e43 410
5032b16d
LP
411 first = hashmap_get(z->by_key, key);
412 LIST_FOREACH(by_key, j, first) {
413 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
414 continue;
ec2c5e43 415
5032b16d 416 found = true;
ec2c5e43 417
5032b16d
LP
418 if (j->state != DNS_ZONE_ITEM_PROBING)
419 tentative = false;
ec2c5e43 420
97ebebbc 421 r = dns_answer_add(answer, j->rr, ifindex, DNS_ANSWER_AUTHENTICATED);
5032b16d
LP
422 if (r < 0)
423 return r;
424 }
ec2c5e43 425
5032b16d
LP
426 if (!found) {
427 bool add_soa = false;
ec2c5e43 428
1c02e7ba 429 first = hashmap_get(z->by_name, dns_resource_key_name(key));
5032b16d
LP
430 LIST_FOREACH(by_name, j, first) {
431 if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING))
432 continue;
ec2c5e43 433
5032b16d
LP
434 if (j->state != DNS_ZONE_ITEM_PROBING)
435 tentative = false;
ec2c5e43 436
5032b16d
LP
437 add_soa = true;
438 }
439
440 if (add_soa) {
97ebebbc 441 r = dns_answer_add_soa(soa, dns_resource_key_name(key), LLMNR_DEFAULT_TTL, ifindex);
5032b16d
LP
442 if (r < 0)
443 return r;
d5323661 444 }
623a4c97
LP
445 }
446 }
447
5032b16d
LP
448 /* If the caller sets ret_tentative to NULL, then use this as
449 * indication to not return tentative entries */
450
451 if (!ret_tentative && tentative)
452 goto return_empty;
453
8bf52d3d 454 *ret_answer = answer;
623a4c97
LP
455 answer = NULL;
456
5032b16d
LP
457 if (ret_soa) {
458 *ret_soa = soa;
459 soa = NULL;
460 }
8bf52d3d 461
ec2c5e43
LP
462 if (ret_tentative)
463 *ret_tentative = tentative;
464
623a4c97 465 return 1;
5032b16d
LP
466
467return_empty:
468 *ret_answer = NULL;
469
470 if (ret_soa)
471 *ret_soa = NULL;
472
473 if (ret_tentative)
474 *ret_tentative = false;
475
476 return 0;
623a4c97 477}
ec2c5e43
LP
478
479void dns_zone_item_conflict(DnsZoneItem *i) {
ec2c5e43
LP
480 assert(i);
481
a4076574
LP
482 if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED))
483 return;
484
7b50eb2e 485 log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr)));
ec2c5e43 486
d84b686f
LP
487 dns_zone_item_probe_stop(i);
488
ec2c5e43
LP
489 /* Withdraw the conflict item */
490 i->state = DNS_ZONE_ITEM_WITHDRAWN;
491
492 /* Maybe change the hostname */
1c02e7ba 493 if (manager_is_own_hostname(i->scope->manager, dns_resource_key_name(i->rr->key)) > 0)
ec2c5e43
LP
494 manager_next_hostname(i->scope->manager);
495}
496
547973de 497void dns_zone_item_notify(DnsZoneItem *i) {
ec2c5e43
LP
498 assert(i);
499 assert(i->probe_transaction);
500
501 if (i->block_ready > 0)
502 return;
503
547973de 504 if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING))
ec2c5e43
LP
505 return;
506
a4076574
LP
507 if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) {
508 bool we_lost = false;
ec2c5e43 509
a4076574
LP
510 /* The probe got a successful reply. If we so far
511 * weren't established we just give up. If we already
512 * were established, and the peer has the
4d91eec4 513 * lexicographically larger IP address we continue
a4076574
LP
514 * and defend it. */
515
2fb3034c
LP
516 if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) {
517 log_debug("Got a successful probe for not yet established RR, we lost.");
a4076574 518 we_lost = true;
2fb3034c 519 } else {
a4076574 520 assert(i->probe_transaction->received);
4d91eec4 521 we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0;
2fb3034c 522 if (we_lost)
4d91eec4 523 log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost.");
a4076574
LP
524 }
525
526 if (we_lost) {
527 dns_zone_item_conflict(i);
528 return;
529 }
530
531 log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost.");
532 }
533
7b50eb2e 534 log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr)));
ec2c5e43 535
a4076574
LP
536 dns_zone_item_probe_stop(i);
537 i->state = DNS_ZONE_ITEM_ESTABLISHED;
538}
539
540static int dns_zone_item_verify(DnsZoneItem *i) {
541 int r;
542
543 assert(i);
544
545 if (i->state != DNS_ZONE_ITEM_ESTABLISHED)
546 return 0;
547
7b50eb2e 548 log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr)));
2fb3034c 549
a4076574
LP
550 i->state = DNS_ZONE_ITEM_VERIFYING;
551 r = dns_zone_item_probe_start(i);
552 if (r < 0) {
da927ba9 553 log_error_errno(r, "Failed to start probing for verifying RR: %m");
ec2c5e43 554 i->state = DNS_ZONE_ITEM_ESTABLISHED;
a4076574
LP
555 return r;
556 }
557
558 return 0;
559}
560
561int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) {
562 DnsZoneItem *i, *first;
bf1594f5 563 int c = 0;
a4076574
LP
564
565 assert(zone);
566 assert(rr);
567
568 /* This checks whether a response RR we received from somebody
569 * else is one that we actually thought was uniquely ours. If
570 * so, we'll verify our RRs. */
571
572 /* No conflict if we don't have the name at all. */
1c02e7ba 573 first = hashmap_get(zone->by_name, dns_resource_key_name(rr->key));
a4076574
LP
574 if (!first)
575 return 0;
576
577 /* No conflict if we have the exact same RR */
578 if (dns_zone_get(zone, rr))
579 return 0;
580
581 /* OK, somebody else has RRs for the same name. Yuck! Let's
582 * start probing again */
583
584 LIST_FOREACH(by_name, i, first) {
585 if (dns_resource_record_equal(i->rr, rr))
586 continue;
587
588 dns_zone_item_verify(i);
589 c++;
590 }
591
592 return c;
593}
594
595int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) {
596 DnsZoneItem *i, *first;
bf1594f5 597 int c = 0;
a4076574
LP
598
599 assert(zone);
600
601 /* Somebody else notified us about a possible conflict. Let's
602 * verify if that's true. */
603
1c02e7ba 604 first = hashmap_get(zone->by_name, dns_resource_key_name(key));
a4076574
LP
605 if (!first)
606 return 0;
607
608 LIST_FOREACH(by_name, i, first) {
609 dns_zone_item_verify(i);
610 c++;
611 }
612
613 return c;
ec2c5e43 614}
902bb5d8
LP
615
616void dns_zone_verify_all(DnsZone *zone) {
617 DnsZoneItem *i;
618 Iterator iterator;
619
620 assert(zone);
621
622 HASHMAP_FOREACH(i, zone->by_key, iterator) {
623 DnsZoneItem *j;
624
625 LIST_FOREACH(by_key, j, i)
626 dns_zone_item_verify(j);
627 }
628}
4d506d6b
LP
629
630void dns_zone_dump(DnsZone *zone, FILE *f) {
631 Iterator iterator;
632 DnsZoneItem *i;
4d506d6b
LP
633
634 if (!zone)
635 return;
636
637 if (!f)
638 f = stdout;
639
640 HASHMAP_FOREACH(i, zone->by_key, iterator) {
641 DnsZoneItem *j;
642
643 LIST_FOREACH(by_key, j, i) {
7b50eb2e 644 const char *t;
4d506d6b 645
7b50eb2e
LP
646 t = dns_resource_record_to_string(j->rr);
647 if (!t) {
4d506d6b
LP
648 log_oom();
649 continue;
650 }
651
652 fputc('\t', f);
653 fputs(t, f);
654 fputc('\n', f);
655 }
656 }
657}
658
659bool dns_zone_is_empty(DnsZone *zone) {
660 if (!zone)
661 return true;
662
663 return hashmap_isempty(zone->by_key);
664}