]> git.ipfire.org Git - people/ms/dnsmasq.git/blob - src/cache.c
Caching of DNSSEC records.
[people/ms/dnsmasq.git] / src / cache.c
1 /* dnsmasq is Copyright (c) 2000-2014 Simon Kelley
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 dated June, 1991, or
6 (at your option) version 3 dated 29 June, 2007.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17 #include "dnsmasq.h"
18
19 static struct crec *cache_head = NULL, *cache_tail = NULL, **hash_table = NULL;
20 #ifdef HAVE_DHCP
21 static struct crec *dhcp_spare = NULL;
22 #endif
23 static struct crec *new_chain = NULL;
24 static int cache_inserted = 0, cache_live_freed = 0, insert_error;
25 static union bigname *big_free = NULL;
26 static int bignames_left, hash_size;
27 static int uid = 1;
28
29 /* type->string mapping: this is also used by the name-hash function as a mixing table. */
30 static const struct {
31 unsigned int type;
32 const char * const name;
33 } typestr[] = {
34 { 1, "A" },
35 { 2, "NS" },
36 { 5, "CNAME" },
37 { 6, "SOA" },
38 { 10, "NULL" },
39 { 11, "WKS" },
40 { 12, "PTR" },
41 { 13, "HINFO" },
42 { 15, "MX" },
43 { 16, "TXT" },
44 { 22, "NSAP" },
45 { 23, "NSAP_PTR" },
46 { 24, "SIG" },
47 { 25, "KEY" },
48 { 28, "AAAA" },
49 { 33, "SRV" },
50 { 35, "NAPTR" },
51 { 36, "KX" },
52 { 37, "CERT" },
53 { 38, "A6" },
54 { 39, "DNAME" },
55 { 41, "OPT" },
56 { 43, "DS" },
57 { 46, "RRSIG" },
58 { 48, "DNSKEY" },
59 { 249, "TKEY" },
60 { 250, "TSIG" },
61 { 251, "IXFR" },
62 { 252, "AXFR" },
63 { 253, "MAILB" },
64 { 254, "MAILA" },
65 { 255, "ANY" }
66 };
67
68 static void cache_free(struct crec *crecp);
69 static void cache_unlink(struct crec *crecp);
70 static void cache_link(struct crec *crecp);
71 static void rehash(int size);
72 static void cache_hash(struct crec *crecp);
73
74 void cache_init(void)
75 {
76 struct crec *crecp;
77 int i;
78
79 bignames_left = daemon->cachesize/10;
80
81 if (daemon->cachesize > 0)
82 {
83 crecp = safe_malloc(daemon->cachesize*sizeof(struct crec));
84
85 for (i=0; i < daemon->cachesize; i++, crecp++)
86 {
87 cache_link(crecp);
88 crecp->flags = 0;
89 crecp->uid = uid++;
90 }
91 }
92
93 /* create initial hash table*/
94 rehash(daemon->cachesize);
95 }
96
97 /* In most cases, we create the hash table once here by calling this with (hash_table == NULL)
98 but if the hosts file(s) are big (some people have 50000 ad-block entries), the table
99 will be much too small, so the hosts reading code calls rehash every 1000 addresses, to
100 expand the table. */
101 static void rehash(int size)
102 {
103 struct crec **new, **old, *p, *tmp;
104 int i, new_size, old_size;
105
106 /* hash_size is a power of two. */
107 for (new_size = 64; new_size < size/10; new_size = new_size << 1);
108
109 /* must succeed in getting first instance, failure later is non-fatal */
110 if (!hash_table)
111 new = safe_malloc(new_size * sizeof(struct crec *));
112 else if (new_size <= hash_size || !(new = whine_malloc(new_size * sizeof(struct crec *))))
113 return;
114
115 for(i = 0; i < new_size; i++)
116 new[i] = NULL;
117
118 old = hash_table;
119 old_size = hash_size;
120 hash_table = new;
121 hash_size = new_size;
122
123 if (old)
124 {
125 for (i = 0; i < old_size; i++)
126 for (p = old[i]; p ; p = tmp)
127 {
128 tmp = p->hash_next;
129 cache_hash(p);
130 }
131 free(old);
132 }
133 }
134
135 static struct crec **hash_bucket(char *name)
136 {
137 unsigned int c, val = 017465; /* Barker code - minimum self-correlation in cyclic shift */
138 const unsigned char *mix_tab = (const unsigned char*)typestr;
139
140 while((c = (unsigned char) *name++))
141 {
142 /* don't use tolower and friends here - they may be messed up by LOCALE */
143 if (c >= 'A' && c <= 'Z')
144 c += 'a' - 'A';
145 val = ((val << 7) | (val >> (32 - 7))) + (mix_tab[(val + c) & 0x3F] ^ c);
146 }
147
148 /* hash_size is a power of two */
149 return hash_table + ((val ^ (val >> 16)) & (hash_size - 1));
150 }
151
152 static void cache_hash(struct crec *crecp)
153 {
154 /* maintain an invariant that all entries with F_REVERSE set
155 are at the start of the hash-chain and all non-reverse
156 immortal entries are at the end of the hash-chain.
157 This allows reverse searches and garbage collection to be optimised */
158
159 struct crec **up = hash_bucket(cache_get_name(crecp));
160
161 if (!(crecp->flags & F_REVERSE))
162 {
163 while (*up && ((*up)->flags & F_REVERSE))
164 up = &((*up)->hash_next);
165
166 if (crecp->flags & F_IMMORTAL)
167 while (*up && !((*up)->flags & F_IMMORTAL))
168 up = &((*up)->hash_next);
169 }
170 crecp->hash_next = *up;
171 *up = crecp;
172 }
173
174 static void cache_free(struct crec *crecp)
175 {
176 crecp->flags &= ~F_FORWARD;
177 crecp->flags &= ~F_REVERSE;
178 crecp->uid = uid++; /* invalidate CNAMES pointing to this. */
179
180 if (uid == -1)
181 uid++;
182
183 if (cache_tail)
184 cache_tail->next = crecp;
185 else
186 cache_head = crecp;
187 crecp->prev = cache_tail;
188 crecp->next = NULL;
189 cache_tail = crecp;
190
191 /* retrieve big name for further use. */
192 if (crecp->flags & F_BIGNAME)
193 {
194 crecp->name.bname->next = big_free;
195 big_free = crecp->name.bname;
196 crecp->flags &= ~F_BIGNAME;
197 }
198 #ifdef HAVE_DNSSEC
199 else if (crecp->flags & (F_DNSKEY | F_DS))
200 blockdata_free(crecp->addr.key.keydata);
201 #endif
202 }
203
204 /* insert a new cache entry at the head of the list (youngest entry) */
205 static void cache_link(struct crec *crecp)
206 {
207 if (cache_head) /* check needed for init code */
208 cache_head->prev = crecp;
209 crecp->next = cache_head;
210 crecp->prev = NULL;
211 cache_head = crecp;
212 if (!cache_tail)
213 cache_tail = crecp;
214 }
215
216 /* remove an arbitrary cache entry for promotion */
217 static void cache_unlink (struct crec *crecp)
218 {
219 if (crecp->prev)
220 crecp->prev->next = crecp->next;
221 else
222 cache_head = crecp->next;
223
224 if (crecp->next)
225 crecp->next->prev = crecp->prev;
226 else
227 cache_tail = crecp->prev;
228 }
229
230 char *cache_get_name(struct crec *crecp)
231 {
232 if (crecp->flags & F_BIGNAME)
233 return crecp->name.bname->name;
234 else if (crecp->flags & F_NAMEP)
235 return crecp->name.namep;
236
237 return crecp->name.sname;
238 }
239
240 char *cache_get_cname_target(struct crec *crecp)
241 {
242 if (crecp->addr.cname.uid != -1)
243 return cache_get_name(crecp->addr.cname.target.cache);
244
245 return crecp->addr.cname.target.int_name->name;
246 }
247
248
249
250 struct crec *cache_enumerate(int init)
251 {
252 static int bucket;
253 static struct crec *cache;
254
255 if (init)
256 {
257 bucket = 0;
258 cache = NULL;
259 }
260 else if (cache && cache->hash_next)
261 cache = cache->hash_next;
262 else
263 {
264 cache = NULL;
265 while (bucket < hash_size)
266 if ((cache = hash_table[bucket++]))
267 break;
268 }
269
270 return cache;
271 }
272
273 static int is_outdated_cname_pointer(struct crec *crecp)
274 {
275 if (!(crecp->flags & F_CNAME) || crecp->addr.cname.uid == -1)
276 return 0;
277
278 /* NB. record may be reused as DS or DNSKEY, where uid is
279 overloaded for something completely different */
280 if (crecp->addr.cname.target.cache &&
281 (crecp->addr.cname.target.cache->flags & (F_IPV4 | F_IPV6 | F_CNAME)) &&
282 crecp->addr.cname.uid == crecp->addr.cname.target.cache->uid)
283 return 0;
284
285 return 1;
286 }
287
288 static int is_expired(time_t now, struct crec *crecp)
289 {
290 if (crecp->flags & F_IMMORTAL)
291 return 0;
292
293 if (difftime(now, crecp->ttd) < 0)
294 return 0;
295
296 return 1;
297 }
298
299 static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags)
300 {
301 /* Scan and remove old entries.
302 If (flags & F_FORWARD) then remove any forward entries for name and any expired
303 entries but only in the same hash bucket as name.
304 If (flags & F_REVERSE) then remove any reverse entries for addr and any expired
305 entries in the whole cache.
306 If (flags == 0) remove any expired entries in the whole cache.
307
308 In the flags & F_FORWARD case, the return code is valid, and returns zero if the
309 name exists in the cache as a HOSTS or DHCP entry (these are never deleted)
310
311 We take advantage of the fact that hash chains have stuff in the order <reverse>,<other>,<immortal>
312 so that when we hit an entry which isn't reverse and is immortal, we're done. */
313
314 struct crec *crecp, **up;
315
316 if (flags & F_FORWARD)
317 {
318 for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next)
319 {
320 if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
321 {
322 *up = crecp->hash_next;
323 if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
324 {
325 cache_unlink(crecp);
326 cache_free(crecp);
327 }
328 continue;
329 }
330
331 if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name))
332 {
333
334 if ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || ((crecp->flags | flags) & F_CNAME))
335 {
336 if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
337 return 0;
338 *up = crecp->hash_next;
339 cache_unlink(crecp);
340 cache_free(crecp);
341 continue;
342 }
343
344 #ifdef HAVE_DNSSEC
345 /* Deletion has to be class-sensitive for DS, DNSKEY, RRSIG, also
346 type-covered sensitive for RRSIG */
347 if (flags & crecp->flags & (F_DNSKEY | F_DS))
348 {
349 int del = 0;
350 switch (flags & (F_DS | F_DNSKEY))
351 {
352 case F_DS:
353 if (crecp->addr.ds.class == addr->addr.dnssec.class)
354 del = 1;
355 break;
356
357 case F_DNSKEY:
358 if (crecp->addr.key.class == addr->addr.dnssec.class)
359 del = 1;
360 break;
361
362 /* Both set -> RRSIG */
363 case F_DS | F_DNSKEY:
364 if (crecp->addr.sig.class == addr->addr.dnssec.class &&
365 crecp->addr.sig.type_covered == addr->addr.dnssec.type)
366 del = 1;
367 break;
368 }
369
370 if (del)
371 {
372 if (crecp->flags & F_CONFIG)
373 return 0;
374 *up = crecp->hash_next;
375 cache_unlink(crecp);
376 cache_free(crecp);
377 continue;
378 }
379 }
380 #endif
381 }
382 up = &crecp->hash_next;
383 }
384 }
385 else
386 {
387 int i;
388 #ifdef HAVE_IPV6
389 int addrlen = (flags & F_IPV6) ? IN6ADDRSZ : INADDRSZ;
390 #else
391 int addrlen = INADDRSZ;
392 #endif
393 for (i = 0; i < hash_size; i++)
394 for (crecp = hash_table[i], up = &hash_table[i];
395 crecp && ((crecp->flags & F_REVERSE) || !(crecp->flags & F_IMMORTAL));
396 crecp = crecp->hash_next)
397 if (is_expired(now, crecp))
398 {
399 *up = crecp->hash_next;
400 if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
401 {
402 cache_unlink(crecp);
403 cache_free(crecp);
404 }
405 }
406 else if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) &&
407 (flags & crecp->flags & F_REVERSE) &&
408 (flags & crecp->flags & (F_IPV4 | F_IPV6)) &&
409 memcmp(&crecp->addr.addr, addr, addrlen) == 0)
410 {
411 *up = crecp->hash_next;
412 cache_unlink(crecp);
413 cache_free(crecp);
414 }
415 else
416 up = &crecp->hash_next;
417 }
418
419 return 1;
420 }
421
422 /* Note: The normal calling sequence is
423 cache_start_insert
424 cache_insert * n
425 cache_end_insert
426
427 but an abort can cause the cache_end_insert to be missed
428 in which can the next cache_start_insert cleans things up. */
429
430 void cache_start_insert(void)
431 {
432 /* Free any entries which didn't get committed during the last
433 insert due to error.
434 */
435 while (new_chain)
436 {
437 struct crec *tmp = new_chain->next;
438 cache_free(new_chain);
439 new_chain = tmp;
440 }
441 new_chain = NULL;
442 insert_error = 0;
443 }
444
445 struct crec *cache_insert(char *name, struct all_addr *addr,
446 time_t now, unsigned long ttl, unsigned short flags)
447 {
448 struct crec *new;
449 union bigname *big_name = NULL;
450 int freed_all = flags & F_REVERSE;
451 int free_avail = 0;
452
453 if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
454 ttl = daemon->max_cache_ttl;
455
456 /* Don't log keys here, done elsewhere */
457 if (flags & (F_IPV4 | F_IPV6 | F_CNAME))
458 log_query(flags | F_UPSTREAM, name, addr, NULL);
459
460 /* if previous insertion failed give up now. */
461 if (insert_error)
462 return NULL;
463
464 /* First remove any expired entries and entries for the name/address we
465 are currently inserting. Fail is we attempt to delete a name from
466 /etc/hosts or DHCP. */
467 if (!cache_scan_free(name, addr, now, flags))
468 {
469 insert_error = 1;
470 return NULL;
471 }
472
473 /* Now get a cache entry from the end of the LRU list */
474 while (1) {
475 if (!(new = cache_tail)) /* no entries left - cache is too small, bail */
476 {
477 insert_error = 1;
478 return NULL;
479 }
480
481 /* End of LRU list is still in use: if we didn't scan all the hash
482 chains for expired entries do that now. If we already tried that
483 then it's time to start spilling things. */
484
485 if (new->flags & (F_FORWARD | F_REVERSE))
486 {
487 /* If free_avail set, we believe that an entry has been freed.
488 Bugs have been known to make this not true, resulting in
489 a tight loop here. If that happens, abandon the
490 insert. Once in this state, all inserts will probably fail. */
491 if (free_avail)
492 {
493 insert_error = 1;
494 return NULL;
495 }
496
497 if (freed_all)
498 {
499 free_avail = 1; /* Must be free space now. */
500 cache_scan_free(cache_get_name(new), &new->addr.addr, now, new->flags);
501 cache_live_freed++;
502 }
503 else
504 {
505 cache_scan_free(NULL, NULL, now, 0);
506 freed_all = 1;
507 }
508 continue;
509 }
510
511 /* Check if we need to and can allocate extra memory for a long name.
512 If that fails, give up now. */
513 if (name && (strlen(name) > SMALLDNAME-1))
514 {
515 if (big_free)
516 {
517 big_name = big_free;
518 big_free = big_free->next;
519 }
520 else if (!bignames_left ||
521 !(big_name = (union bigname *)whine_malloc(sizeof(union bigname))))
522 {
523 insert_error = 1;
524 return NULL;
525 }
526 else
527 bignames_left--;
528
529 }
530
531 /* Got the rest: finally grab entry. */
532 cache_unlink(new);
533 break;
534 }
535
536 new->flags = flags;
537 if (big_name)
538 {
539 new->name.bname = big_name;
540 new->flags |= F_BIGNAME;
541 }
542
543 if (name)
544 strcpy(cache_get_name(new), name);
545 else
546 *cache_get_name(new) = 0;
547
548 if (addr)
549 new->addr.addr = *addr;
550
551 new->ttd = now + (time_t)ttl;
552 new->next = new_chain;
553 new_chain = new;
554
555 return new;
556 }
557
558 /* after end of insertion, commit the new entries */
559 void cache_end_insert(void)
560 {
561 if (insert_error)
562 return;
563
564 while (new_chain)
565 {
566 struct crec *tmp = new_chain->next;
567 /* drop CNAMEs which didn't find a target. */
568 if (is_outdated_cname_pointer(new_chain))
569 cache_free(new_chain);
570 else
571 {
572 cache_hash(new_chain);
573 cache_link(new_chain);
574 cache_inserted++;
575 }
576 new_chain = tmp;
577 }
578 new_chain = NULL;
579 }
580
581 struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned short prot)
582 {
583 struct crec *ans;
584
585 if (crecp) /* iterating */
586 ans = crecp->next;
587 else
588 {
589 /* first search, look for relevant entries and push to top of list
590 also free anything which has expired */
591 struct crec *next, **up, **insert = NULL, **chainp = &ans;
592 unsigned short ins_flags = 0;
593
594 for (up = hash_bucket(name), crecp = *up; crecp; crecp = next)
595 {
596 next = crecp->hash_next;
597
598 if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp))
599 {
600 if ((crecp->flags & F_FORWARD) &&
601 #ifdef HAVE_DNSSEC
602 ((crecp->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) &&
603 #endif
604 (crecp->flags & prot) &&
605 hostname_isequal(cache_get_name(crecp), name))
606 {
607 if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
608 {
609 *chainp = crecp;
610 chainp = &crecp->next;
611 }
612 else
613 {
614 cache_unlink(crecp);
615 cache_link(crecp);
616 }
617
618 /* Move all but the first entry up the hash chain
619 this implements round-robin.
620 Make sure that re-ordering doesn't break the hash-chain
621 order invariants.
622 */
623 if (insert && (crecp->flags & (F_REVERSE | F_IMMORTAL)) == ins_flags)
624 {
625 *up = crecp->hash_next;
626 crecp->hash_next = *insert;
627 *insert = crecp;
628 insert = &crecp->hash_next;
629 }
630 else
631 {
632 if (!insert)
633 {
634 insert = up;
635 ins_flags = crecp->flags & (F_REVERSE | F_IMMORTAL);
636 }
637 up = &crecp->hash_next;
638 }
639 }
640 else
641 /* case : not expired, incorrect entry. */
642 up = &crecp->hash_next;
643 }
644 else
645 {
646 /* expired entry, free it */
647 *up = crecp->hash_next;
648 if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
649 {
650 cache_unlink(crecp);
651 cache_free(crecp);
652 }
653 }
654 }
655
656 *chainp = cache_head;
657 }
658
659 if (ans &&
660 (ans->flags & F_FORWARD) &&
661 #ifdef HAVE_DNSSEC
662 ((ans->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) &&
663 #endif
664 (ans->flags & prot) &&
665 hostname_isequal(cache_get_name(ans), name))
666 return ans;
667
668 return NULL;
669 }
670
671 struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr,
672 time_t now, unsigned short prot)
673 {
674 struct crec *ans;
675 #ifdef HAVE_IPV6
676 int addrlen = (prot == F_IPV6) ? IN6ADDRSZ : INADDRSZ;
677 #else
678 int addrlen = INADDRSZ;
679 #endif
680
681 if (crecp) /* iterating */
682 ans = crecp->next;
683 else
684 {
685 /* first search, look for relevant entries and push to top of list
686 also free anything which has expired. All the reverse entries are at the
687 start of the hash chain, so we can give up when we find the first
688 non-REVERSE one. */
689 int i;
690 struct crec **up, **chainp = &ans;
691
692 for (i=0; i<hash_size; i++)
693 for (crecp = hash_table[i], up = &hash_table[i];
694 crecp && (crecp->flags & F_REVERSE);
695 crecp = crecp->hash_next)
696 if (!is_expired(now, crecp))
697 {
698 if ((crecp->flags & prot) &&
699 memcmp(&crecp->addr.addr, addr, addrlen) == 0)
700 {
701 if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
702 {
703 *chainp = crecp;
704 chainp = &crecp->next;
705 }
706 else
707 {
708 cache_unlink(crecp);
709 cache_link(crecp);
710 }
711 }
712 up = &crecp->hash_next;
713 }
714 else
715 {
716 *up = crecp->hash_next;
717 if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
718 {
719 cache_unlink(crecp);
720 cache_free(crecp);
721 }
722 }
723
724 *chainp = cache_head;
725 }
726
727 if (ans &&
728 (ans->flags & F_REVERSE) &&
729 (ans->flags & prot) &&
730 memcmp(&ans->addr.addr, addr, addrlen) == 0)
731 return ans;
732
733 return NULL;
734 }
735
736 static void add_hosts_cname(struct crec *target)
737 {
738 struct crec *crec;
739 struct cname *a;
740
741 for (a = daemon->cnames; a; a = a->next)
742 if (hostname_isequal(cache_get_name(target), a->target) &&
743 (crec = whine_malloc(sizeof(struct crec))))
744 {
745 crec->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_CONFIG | F_CNAME | F_DNSSECOK;
746 crec->name.namep = a->alias;
747 crec->addr.cname.target.cache = target;
748 crec->addr.cname.uid = target->uid;
749 cache_hash(crec);
750 add_hosts_cname(crec); /* handle chains */
751 }
752 }
753
754 static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrlen,
755 int index, struct crec **rhash, int hashsz)
756 {
757 struct crec *lookup = cache_find_by_name(NULL, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6));
758 int i, nameexists = 0;
759 unsigned int j;
760
761 /* Remove duplicates in hosts files. */
762 if (lookup && (lookup->flags & F_HOSTS))
763 {
764 nameexists = 1;
765 if (memcmp(&lookup->addr.addr, addr, addrlen) == 0)
766 {
767 free(cache);
768 return;
769 }
770 }
771
772 /* Ensure there is only one address -> name mapping (first one trumps)
773 We do this by steam here, The entries are kept in hash chains, linked
774 by ->next (which is unused at this point) held in hash buckets in
775 the array rhash, hashed on address. Note that rhash and the values
776 in ->next are only valid whilst reading hosts files: the buckets are
777 then freed, and the ->next pointer used for other things.
778
779 Only insert each unique address once into this hashing structure.
780
781 This complexity avoids O(n^2) divergent CPU use whilst reading
782 large (10000 entry) hosts files. */
783
784 /* hash address */
785 for (j = 0, i = 0; i < addrlen; i++)
786 j = (j*2 +((unsigned char *)addr)[i]) % hashsz;
787
788 for (lookup = rhash[j]; lookup; lookup = lookup->next)
789 if ((lookup->flags & cache->flags & (F_IPV4 | F_IPV6)) &&
790 memcmp(&lookup->addr.addr, addr, addrlen) == 0)
791 {
792 cache->flags &= ~F_REVERSE;
793 break;
794 }
795
796 /* maintain address hash chain, insert new unique address */
797 if (!lookup)
798 {
799 cache->next = rhash[j];
800 rhash[j] = cache;
801 }
802
803 cache->uid = index;
804 memcpy(&cache->addr.addr, addr, addrlen);
805 cache_hash(cache);
806
807 /* don't need to do alias stuff for second and subsequent addresses. */
808 if (!nameexists)
809 add_hosts_cname(cache);
810 }
811
812 static int eatspace(FILE *f)
813 {
814 int c, nl = 0;
815
816 while (1)
817 {
818 if ((c = getc(f)) == '#')
819 while (c != '\n' && c != EOF)
820 c = getc(f);
821
822 if (c == EOF)
823 return 1;
824
825 if (!isspace(c))
826 {
827 ungetc(c, f);
828 return nl;
829 }
830
831 if (c == '\n')
832 nl = 1;
833 }
834 }
835
836 static int gettok(FILE *f, char *token)
837 {
838 int c, count = 0;
839
840 while (1)
841 {
842 if ((c = getc(f)) == EOF)
843 return (count == 0) ? EOF : 1;
844
845 if (isspace(c) || c == '#')
846 {
847 ungetc(c, f);
848 return eatspace(f);
849 }
850
851 if (count < (MAXDNAME - 1))
852 {
853 token[count++] = c;
854 token[count] = 0;
855 }
856 }
857 }
858
859 static int read_hostsfile(char *filename, int index, int cache_size, struct crec **rhash, int hashsz)
860 {
861 FILE *f = fopen(filename, "r");
862 char *token = daemon->namebuff, *domain_suffix = NULL;
863 int addr_count = 0, name_count = cache_size, lineno = 0;
864 unsigned short flags = 0;
865 struct all_addr addr;
866 int atnl, addrlen = 0;
867
868 if (!f)
869 {
870 my_syslog(LOG_ERR, _("failed to load names from %s: %s"), filename, strerror(errno));
871 return 0;
872 }
873
874 eatspace(f);
875
876 while ((atnl = gettok(f, token)) != EOF)
877 {
878 lineno++;
879
880 if (inet_pton(AF_INET, token, &addr) > 0)
881 {
882 flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_DNSSECOK;
883 addrlen = INADDRSZ;
884 domain_suffix = get_domain(addr.addr.addr4);
885 }
886 #ifdef HAVE_IPV6
887 else if (inet_pton(AF_INET6, token, &addr) > 0)
888 {
889 flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_DNSSECOK;
890 addrlen = IN6ADDRSZ;
891 domain_suffix = get_domain6(&addr.addr.addr6);
892 }
893 #endif
894 else
895 {
896 my_syslog(LOG_ERR, _("bad address at %s line %d"), filename, lineno);
897 while (atnl == 0)
898 atnl = gettok(f, token);
899 continue;
900 }
901
902 addr_count++;
903
904 /* rehash every 1000 names. */
905 if ((name_count - cache_size) > 1000)
906 {
907 rehash(name_count);
908 cache_size = name_count;
909 }
910
911 while (atnl == 0)
912 {
913 struct crec *cache;
914 int fqdn, nomem;
915 char *canon;
916
917 if ((atnl = gettok(f, token)) == EOF)
918 break;
919
920 fqdn = !!strchr(token, '.');
921
922 if ((canon = canonicalise(token, &nomem)))
923 {
924 /* If set, add a version of the name with a default domain appended */
925 if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn &&
926 (cache = whine_malloc(sizeof(struct crec) +
927 strlen(canon)+2+strlen(domain_suffix)-SMALLDNAME)))
928 {
929 strcpy(cache->name.sname, canon);
930 strcat(cache->name.sname, ".");
931 strcat(cache->name.sname, domain_suffix);
932 cache->flags = flags;
933 add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
934 name_count++;
935 }
936 if ((cache = whine_malloc(sizeof(struct crec) + strlen(canon)+1-SMALLDNAME)))
937 {
938 strcpy(cache->name.sname, canon);
939 cache->flags = flags;
940 add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
941 name_count++;
942 }
943 free(canon);
944
945 }
946 else if (!nomem)
947 my_syslog(LOG_ERR, _("bad name at %s line %d"), filename, lineno);
948 }
949 }
950
951 fclose(f);
952 rehash(name_count);
953
954 my_syslog(LOG_INFO, _("read %s - %d addresses"), filename, addr_count);
955
956 return name_count;
957 }
958
959 void cache_reload(void)
960 {
961 struct crec *cache, **up, *tmp;
962 int revhashsz, i, total_size = daemon->cachesize;
963 struct hostsfile *ah;
964 struct host_record *hr;
965 struct name_list *nl;
966 struct cname *a;
967 struct interface_name *intr;
968 #ifdef HAVE_DNSSEC
969 struct dnskey *key;
970 #endif
971
972 cache_inserted = cache_live_freed = 0;
973
974 for (i=0; i<hash_size; i++)
975 for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp)
976 {
977 #ifdef HAVE_DNSSEC
978 if (cache->flags & (F_DNSKEY | F_DS))
979 blockdata_free(cache->addr.key.keydata);
980 #endif
981 tmp = cache->hash_next;
982 if (cache->flags & (F_HOSTS | F_CONFIG))
983 {
984 *up = cache->hash_next;
985 free(cache);
986 }
987 else if (!(cache->flags & F_DHCP))
988 {
989 *up = cache->hash_next;
990 if (cache->flags & F_BIGNAME)
991 {
992 cache->name.bname->next = big_free;
993 big_free = cache->name.bname;
994 }
995 cache->flags = 0;
996 }
997 else
998 up = &cache->hash_next;
999 }
1000
1001 /* Add CNAMEs to interface_names to the cache */
1002 for (a = daemon->cnames; a; a = a->next)
1003 for (intr = daemon->int_names; intr; intr = intr->next)
1004 if (hostname_isequal(a->target, intr->name) &&
1005 ((cache = whine_malloc(sizeof(struct crec)))))
1006 {
1007 cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG | F_DNSSECOK;
1008 cache->name.namep = a->alias;
1009 cache->addr.cname.target.int_name = intr;
1010 cache->addr.cname.uid = -1;
1011 cache_hash(cache);
1012 add_hosts_cname(cache); /* handle chains */
1013 }
1014
1015 #ifdef HAVE_DNSSEC
1016 for (key = daemon->dnskeys; key; key = key->next)
1017 if ((cache = whine_malloc(sizeof(struct crec))) &&
1018 (cache->addr.key.keydata = blockdata_alloc(key->key, key->keylen)))
1019 {
1020 cache->flags = F_FORWARD | F_IMMORTAL | F_DNSKEY | F_CONFIG | F_NAMEP;
1021 cache->name.namep = key->name;
1022 cache->uid = key->keylen;
1023 cache->addr.key.algo = key->algo;
1024 cache->addr.key.flags = key->flags;
1025 cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen);
1026 cache->addr.key.class = C_IN; /* TODO - in option? */
1027 cache_hash(cache);
1028 }
1029 #endif
1030
1031 /* borrow the packet buffer for a temporary by-address hash */
1032 memset(daemon->packet, 0, daemon->packet_buff_sz);
1033 revhashsz = daemon->packet_buff_sz / sizeof(struct crec *);
1034 /* we overwrote the buffer... */
1035 daemon->srv_save = NULL;
1036
1037 /* Do host_records in config. */
1038 for (hr = daemon->host_records; hr; hr = hr->next)
1039 for (nl = hr->names; nl; nl = nl->next)
1040 {
1041 if (hr->addr.s_addr != 0 &&
1042 (cache = whine_malloc(sizeof(struct crec))))
1043 {
1044 cache->name.namep = nl->name;
1045 cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG | F_DNSSECOK;
1046 add_hosts_entry(cache, (struct all_addr *)&hr->addr, INADDRSZ, 0, (struct crec **)daemon->packet, revhashsz);
1047 }
1048 #ifdef HAVE_IPV6
1049 if (!IN6_IS_ADDR_UNSPECIFIED(&hr->addr6) &&
1050 (cache = whine_malloc(sizeof(struct crec))))
1051 {
1052 cache->name.namep = nl->name;
1053 cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG | F_DNSSECOK;
1054 add_hosts_entry(cache, (struct all_addr *)&hr->addr6, IN6ADDRSZ, 0, (struct crec **)daemon->packet, revhashsz);
1055 }
1056 #endif
1057 }
1058
1059 if (option_bool(OPT_NO_HOSTS) && !daemon->addn_hosts)
1060 {
1061 if (daemon->cachesize > 0)
1062 my_syslog(LOG_INFO, _("cleared cache"));
1063 return;
1064 }
1065
1066 if (!option_bool(OPT_NO_HOSTS))
1067 total_size = read_hostsfile(HOSTSFILE, 0, total_size, (struct crec **)daemon->packet, revhashsz);
1068
1069 daemon->addn_hosts = expand_filelist(daemon->addn_hosts);
1070 for (ah = daemon->addn_hosts; ah; ah = ah->next)
1071 if (!(ah->flags & AH_INACTIVE))
1072 total_size = read_hostsfile(ah->fname, ah->index, total_size, (struct crec **)daemon->packet, revhashsz);
1073 }
1074
1075 #ifdef HAVE_DHCP
1076 struct in_addr a_record_from_hosts(char *name, time_t now)
1077 {
1078 struct crec *crecp = NULL;
1079 struct in_addr ret;
1080
1081 while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4)))
1082 if (crecp->flags & F_HOSTS)
1083 return *(struct in_addr *)&crecp->addr;
1084
1085 my_syslog(MS_DHCP | LOG_WARNING, _("No IPv4 address found for %s"), name);
1086
1087 ret.s_addr = 0;
1088 return ret;
1089 }
1090
1091 void cache_unhash_dhcp(void)
1092 {
1093 struct crec *cache, **up;
1094 int i;
1095
1096 for (i=0; i<hash_size; i++)
1097 for (cache = hash_table[i], up = &hash_table[i]; cache; cache = cache->hash_next)
1098 if (cache->flags & F_DHCP)
1099 {
1100 *up = cache->hash_next;
1101 cache->next = dhcp_spare;
1102 dhcp_spare = cache;
1103 }
1104 else
1105 up = &cache->hash_next;
1106 }
1107
1108 static void add_dhcp_cname(struct crec *target, time_t ttd)
1109 {
1110 struct crec *aliasc;
1111 struct cname *a;
1112
1113 for (a = daemon->cnames; a; a = a->next)
1114 if (hostname_isequal(cache_get_name(target), a->target))
1115 {
1116 if ((aliasc = dhcp_spare))
1117 dhcp_spare = dhcp_spare->next;
1118 else /* need new one */
1119 aliasc = whine_malloc(sizeof(struct crec));
1120
1121 if (aliasc)
1122 {
1123 aliasc->flags = F_FORWARD | F_NAMEP | F_DHCP | F_CNAME | F_CONFIG | F_DNSSECOK;
1124 if (ttd == 0)
1125 aliasc->flags |= F_IMMORTAL;
1126 else
1127 aliasc->ttd = ttd;
1128 aliasc->name.namep = a->alias;
1129 aliasc->addr.cname.target.cache = target;
1130 aliasc->addr.cname.uid = target->uid;
1131 cache_hash(aliasc);
1132 add_dhcp_cname(aliasc, ttd);
1133 }
1134 }
1135 }
1136
1137 void cache_add_dhcp_entry(char *host_name, int prot,
1138 struct all_addr *host_address, time_t ttd)
1139 {
1140 struct crec *crec = NULL, *fail_crec = NULL;
1141 unsigned short flags = F_IPV4;
1142 int in_hosts = 0;
1143 size_t addrlen = sizeof(struct in_addr);
1144
1145 #ifdef HAVE_IPV6
1146 if (prot == AF_INET6)
1147 {
1148 flags = F_IPV6;
1149 addrlen = sizeof(struct in6_addr);
1150 }
1151 #endif
1152
1153 inet_ntop(prot, host_address, daemon->addrbuff, ADDRSTRLEN);
1154
1155 while ((crec = cache_find_by_name(crec, host_name, 0, flags | F_CNAME)))
1156 {
1157 /* check all addresses associated with name */
1158 if (crec->flags & (F_HOSTS | F_CONFIG))
1159 {
1160 if (crec->flags & F_CNAME)
1161 my_syslog(MS_DHCP | LOG_WARNING,
1162 _("%s is a CNAME, not giving it to the DHCP lease of %s"),
1163 host_name, daemon->addrbuff);
1164 else if (memcmp(&crec->addr.addr, host_address, addrlen) == 0)
1165 in_hosts = 1;
1166 else
1167 fail_crec = crec;
1168 }
1169 else if (!(crec->flags & F_DHCP))
1170 {
1171 cache_scan_free(host_name, NULL, 0, crec->flags & (flags | F_CNAME | F_FORWARD));
1172 /* scan_free deletes all addresses associated with name */
1173 break;
1174 }
1175 }
1176
1177 /* if in hosts, don't need DHCP record */
1178 if (in_hosts)
1179 return;
1180
1181 /* Name in hosts, address doesn't match */
1182 if (fail_crec)
1183 {
1184 inet_ntop(prot, &fail_crec->addr.addr, daemon->namebuff, MAXDNAME);
1185 my_syslog(MS_DHCP | LOG_WARNING,
1186 _("not giving name %s to the DHCP lease of %s because "
1187 "the name exists in %s with address %s"),
1188 host_name, daemon->addrbuff,
1189 record_source(fail_crec->uid), daemon->namebuff);
1190 return;
1191 }
1192
1193 if ((crec = cache_find_by_addr(NULL, (struct all_addr *)host_address, 0, flags)))
1194 {
1195 if (crec->flags & F_NEG)
1196 {
1197 flags |= F_REVERSE;
1198 cache_scan_free(NULL, (struct all_addr *)host_address, 0, flags);
1199 }
1200 }
1201 else
1202 flags |= F_REVERSE;
1203
1204 if ((crec = dhcp_spare))
1205 dhcp_spare = dhcp_spare->next;
1206 else /* need new one */
1207 crec = whine_malloc(sizeof(struct crec));
1208
1209 if (crec) /* malloc may fail */
1210 {
1211 crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD | F_DNSSECOK;
1212 if (ttd == 0)
1213 crec->flags |= F_IMMORTAL;
1214 else
1215 crec->ttd = ttd;
1216 crec->addr.addr = *host_address;
1217 crec->name.namep = host_name;
1218 crec->uid = uid++;
1219 cache_hash(crec);
1220
1221 add_dhcp_cname(crec, ttd);
1222 }
1223 }
1224 #endif
1225
1226
1227 void dump_cache(time_t now)
1228 {
1229 struct server *serv, *serv1;
1230
1231 my_syslog(LOG_INFO, _("time %lu"), (unsigned long)now);
1232 my_syslog(LOG_INFO, _("cache size %d, %d/%d cache insertions re-used unexpired cache entries."),
1233 daemon->cachesize, cache_live_freed, cache_inserted);
1234 my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"),
1235 daemon->queries_forwarded, daemon->local_answer);
1236 #ifdef HAVE_AUTH
1237 my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->auth_answer);
1238 #endif
1239 #ifdef HAVE_DNSSEC
1240 blockdata_report();
1241 #endif
1242
1243 /* sum counts from different records for same server */
1244 for (serv = daemon->servers; serv; serv = serv->next)
1245 serv->flags &= ~SERV_COUNTED;
1246
1247 for (serv = daemon->servers; serv; serv = serv->next)
1248 if (!(serv->flags &
1249 (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)))
1250 {
1251 int port;
1252 unsigned int queries = 0, failed_queries = 0;
1253 for (serv1 = serv; serv1; serv1 = serv1->next)
1254 if (!(serv1->flags &
1255 (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)) &&
1256 sockaddr_isequal(&serv->addr, &serv1->addr))
1257 {
1258 serv1->flags |= SERV_COUNTED;
1259 queries += serv1->queries;
1260 failed_queries += serv1->failed_queries;
1261 }
1262 port = prettyprint_addr(&serv->addr, daemon->addrbuff);
1263 my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried or failed %u"), daemon->addrbuff, port, queries, failed_queries);
1264 }
1265
1266 if (option_bool(OPT_DEBUG) || option_bool(OPT_LOG))
1267 {
1268 struct crec *cache ;
1269 int i;
1270 my_syslog(LOG_INFO, "Host Address Flags Expires");
1271
1272 for (i=0; i<hash_size; i++)
1273 for (cache = hash_table[i]; cache; cache = cache->hash_next)
1274 {
1275 char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache);
1276 *a = 0;
1277 if (strlen(n) == 0)
1278 n = "<Root>";
1279 p += sprintf(p, "%-40.40s ", n);
1280 if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
1281 a = cache_get_cname_target(cache);
1282 #ifdef HAVE_DNSSEC
1283 else if (cache->flags & F_DS)
1284 {
1285 a = daemon->addrbuff;
1286 sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag,
1287 cache->addr.ds.algo, cache->addr.ds.digest);
1288 }
1289 else if (cache->flags & F_DNSKEY)
1290 {
1291 a = daemon->addrbuff;
1292 sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
1293 cache->addr.key.algo, cache->addr.key.flags);
1294 }
1295 #endif
1296 else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
1297 {
1298 a = daemon->addrbuff;
1299 if (cache->flags & F_IPV4)
1300 inet_ntop(AF_INET, &cache->addr.addr, a, ADDRSTRLEN);
1301 #ifdef HAVE_IPV6
1302 else if (cache->flags & F_IPV6)
1303 inet_ntop(AF_INET6, &cache->addr.addr, a, ADDRSTRLEN);
1304 #endif
1305 }
1306
1307 p += sprintf(p, "%-30.30s %s%s%s%s%s%s%s%s%s%s%s%s%s ", a,
1308 cache->flags & F_IPV4 ? "4" : "",
1309 cache->flags & F_IPV6 ? "6" : "",
1310 cache->flags & F_DNSKEY ? "K" : "",
1311 cache->flags & F_DS ? "S" : "",
1312 cache->flags & F_CNAME ? "C" : "",
1313 cache->flags & F_FORWARD ? "F" : " ",
1314 cache->flags & F_REVERSE ? "R" : " ",
1315 cache->flags & F_IMMORTAL ? "I" : " ",
1316 cache->flags & F_DHCP ? "D" : " ",
1317 cache->flags & F_NEG ? "N" : " ",
1318 cache->flags & F_NXDOMAIN ? "X" : " ",
1319 cache->flags & F_HOSTS ? "H" : " ",
1320 cache->flags & F_DNSSECOK ? "V" : " ");
1321 #ifdef HAVE_BROKEN_RTC
1322 p += sprintf(p, "%lu", cache->flags & F_IMMORTAL ? 0: (unsigned long)(cache->ttd - now));
1323 #else
1324 p += sprintf(p, "%s", cache->flags & F_IMMORTAL ? "\n" : ctime(&(cache->ttd)));
1325 /* ctime includes trailing \n - eat it */
1326 *(p-1) = 0;
1327 #endif
1328 my_syslog(LOG_INFO, daemon->namebuff);
1329 }
1330 }
1331 }
1332
1333 char *record_source(int index)
1334 {
1335 struct hostsfile *ah;
1336
1337 if (index == 0)
1338 return HOSTSFILE;
1339
1340 for (ah = daemon->addn_hosts; ah; ah = ah->next)
1341 if (ah->index == index)
1342 return ah->fname;
1343
1344 return "<unknown>";
1345 }
1346
1347 void querystr(char *desc, char *str, unsigned short type)
1348 {
1349 unsigned int i;
1350
1351 sprintf(str, "%s[type=%d]", desc, type);
1352 for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
1353 if (typestr[i].type == type)
1354 sprintf(str,"%s[%s]", desc, typestr[i].name);
1355 }
1356
1357 void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
1358 {
1359 char *source, *dest = daemon->addrbuff;
1360 char *verb = "is";
1361
1362 if (!option_bool(OPT_LOG))
1363 return;
1364
1365 if (addr)
1366 {
1367 if (flags & F_KEYTAG)
1368 sprintf(daemon->addrbuff, arg, addr->addr.keytag);
1369 else
1370 {
1371 #ifdef HAVE_IPV6
1372 inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
1373 addr, daemon->addrbuff, ADDRSTRLEN);
1374 #else
1375 strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN);
1376 #endif
1377 }
1378 }
1379 else
1380 dest = arg;
1381
1382 if (flags & F_REVERSE)
1383 {
1384 dest = name;
1385 name = daemon->addrbuff;
1386 }
1387
1388 if (flags & F_NEG)
1389 {
1390 if (flags & F_NXDOMAIN)
1391 {
1392 if (flags & F_IPV4)
1393 dest = "NXDOMAIN-IPv4";
1394 else if (flags & F_IPV6)
1395 dest = "NXDOMAIN-IPv6";
1396 else
1397 dest = "NXDOMAIN";
1398 }
1399 else
1400 {
1401 if (flags & F_IPV4)
1402 dest = "NODATA-IPv4";
1403 else if (flags & F_IPV6)
1404 dest = "NODATA-IPv6";
1405 else
1406 dest = "NODATA";
1407 }
1408 }
1409 else if (flags & F_CNAME)
1410 dest = "<CNAME>";
1411 else if (flags & F_RRNAME)
1412 dest = arg;
1413
1414 if (flags & F_CONFIG)
1415 source = "config";
1416 else if (flags & F_DHCP)
1417 source = "DHCP";
1418 else if (flags & F_HOSTS)
1419 source = arg;
1420 else if (flags & F_UPSTREAM)
1421 source = "reply";
1422 else if (flags & F_SECSTAT)
1423 source = "validation";
1424 else if (flags & F_AUTH)
1425 source = "auth";
1426 else if (flags & F_SERVER)
1427 {
1428 source = "forwarded";
1429 verb = "to";
1430 }
1431 else if (flags & F_QUERY)
1432 {
1433 source = arg;
1434 verb = "from";
1435 }
1436 else if (flags & F_DNSSEC)
1437 {
1438 source = arg;
1439 verb = "to";
1440 }
1441 else
1442 source = "cached";
1443
1444 if (strlen(name) == 0)
1445 name = ".";
1446
1447 my_syslog(LOG_INFO, "%s %s %s %s", source, name, verb, dest);
1448 }
1449
1450