DNS: time-limit cached returns, using TTL. Bug 1395
[exim.git] / test / src / fakens.c
1 /*************************************************
2 * fakens - A Fake Nameserver Program *
3 *************************************************/
4
5 /* This program exists to support the testing of DNS handling code in Exim. It
6 avoids the need to install special zones in a real nameserver. When Exim is
7 running in its (new) test harness, DNS lookups are first passed to this program
8 instead of to the real resolver. (With a few exceptions - see the discussion in
9 the test suite's README file.) The program is also passed the name of the Exim
10 spool directory; it expects to find its "zone files" in dnszones relative to
11 exim config_main_directory. Note that there is little checking in this program. The fake
12 zone files are assumed to be syntactically valid.
13
14 The zones that are handled are found by scanning the dnszones directory. A file
15 whose name is of the form db.ip4.x is a zone file for .x.in-addr.arpa; a file
16 whose name is of the form db.ip6.x is a zone file for .x.ip6.arpa; a file of
17 the form db.anything.else is a zone file for .anything.else. A file of the form
18 qualify.x.y specifies the domain that is used to qualify single-component
19 names, except for the name "dontqualify".
20
21 The arguments to the program are:
22
23 the name of the Exim spool directory
24 the domain name that is being sought
25 the DNS record type that is being sought
26
27 The output from the program is written to stdout. It is supposed to be in
28 exactly the same format as a traditional namserver response (see RFC 1035) so
29 that Exim can process it as normal. At present, no compression is used.
30 Error messages are written to stderr.
31
32 The return codes from the program are zero for success, and otherwise the
33 values that are set in h_errno after a failing call to the normal resolver:
34
35 1 HOST_NOT_FOUND host not found (authoritative)
36 2 TRY_AGAIN server failure
37 3 NO_RECOVERY non-recoverable error
38 4 NO_DATA valid name, no data of requested type
39
40 In a real nameserver, TRY_AGAIN is also used for a non-authoritative not found,
41 but it is not used for that here. There is also one extra return code:
42
43 5 PASS_ON requests Exim to call res_search()
44
45 This is used for zones that fakens does not recognize. It is also used if a
46 line in the zone file contains exactly this:
47
48 PASS ON NOT FOUND
49
50 and the domain is not found. It converts the the result to PASS_ON instead of
51 HOST_NOT_FOUND.
52
53 Any DNS record line in a zone file can be prefixed with "DELAY=" and
54 a number of milliseconds (followed by one space).
55
56 Any DNS record line in a zone file can be prefixed with "DNSSEC ";
57 if all the records found by a lookup are marked
58 as such then the response will have the "AD" bit set.
59
60 Any DNS record line in a zone file can be prefixed with "AA "
61 if all the records found by a lookup are marked
62 as such then the response will have the "AA" bit set.
63
64 Any DNS record line in a zone file can be prefixed with "TTL=" and
65 a number of seconds (followed by one space).
66
67 */
68
69 #include <ctype.h>
70 #include <stdarg.h>
71 #include <stdio.h>
72 #include <stdlib.h>
73 #include <string.h>
74 #include <netdb.h>
75 #include <errno.h>
76 #include <signal.h>
77 #include <arpa/nameser.h>
78 #include <sys/types.h>
79 #include <sys/time.h>
80 #include <dirent.h>
81
82 #define FALSE 0
83 #define TRUE 1
84 #define PASS_ON 5
85
86 typedef int BOOL;
87 typedef unsigned char uschar;
88
89 #define CS (char *)
90 #define CCS (const char *)
91 #define US (unsigned char *)
92
93 #define Ustrcat(s,t) strcat(CS(s),CCS(t))
94 #define Ustrchr(s,n) US strchr(CCS(s),n)
95 #define Ustrcmp(s,t) strcmp(CCS(s),CCS(t))
96 #define Ustrcpy(s,t) strcpy(CS(s),CCS(t))
97 #define Ustrlen(s) (int)strlen(CCS(s))
98 #define Ustrncmp(s,t,n) strncmp(CCS(s),CCS(t),n)
99 #define Ustrncpy(s,t,n) strncpy(CS(s),CCS(t),n)
100
101 typedef struct zoneitem {
102 uschar *zone;
103 uschar *zonefile;
104 } zoneitem;
105
106 typedef struct tlist {
107 uschar *name;
108 int value;
109 } tlist;
110
111 #define DEFAULT_TTL 3600U
112
113 /* On some (older?) operating systems, the standard ns_t_xxx definitions are
114 not available, and only the older T_xxx ones exist in nameser.h. If ns_t_a is
115 not defined, assume we are in this state. A really old system might not even
116 know about AAAA and SRV at all. */
117
118 #ifndef ns_t_a
119 # define ns_t_a T_A
120 # define ns_t_ns T_NS
121 # define ns_t_cname T_CNAME
122 # define ns_t_soa T_SOA
123 # define ns_t_ptr T_PTR
124 # define ns_t_mx T_MX
125 # define ns_t_txt T_TXT
126 # define ns_t_aaaa T_AAAA
127 # define ns_t_srv T_SRV
128 # define ns_t_tlsa T_TLSA
129 # ifndef T_AAAA
130 # define T_AAAA 28
131 # endif
132 # ifndef T_SRV
133 # define T_SRV 33
134 # endif
135 # ifndef T_TLSA
136 # define T_TLSA 52
137 # endif
138 #endif
139
140 static tlist type_list[] = {
141 { US"A", ns_t_a },
142 { US"NS", ns_t_ns },
143 { US"CNAME", ns_t_cname },
144 { US"SOA", ns_t_soa },
145 { US"PTR", ns_t_ptr },
146 { US"MX", ns_t_mx },
147 { US"TXT", ns_t_txt },
148 { US"AAAA", ns_t_aaaa },
149 { US"SRV", ns_t_srv },
150 { US"TLSA", ns_t_tlsa },
151 { NULL, 0 }
152 };
153
154
155
156 /*************************************************
157 * Get memory and sprintf into it *
158 *************************************************/
159
160 /* This is used when building a table of zones and their files.
161
162 Arguments:
163 format a format string
164 ... arguments
165
166 Returns: pointer to formatted string
167 */
168
169 static uschar *
170 fcopystring(uschar *format, ...)
171 {
172 uschar *yield;
173 char buffer[256];
174 va_list ap;
175 va_start(ap, format);
176 vsprintf(buffer, CS format, ap);
177 va_end(ap);
178 yield = (uschar *)malloc(Ustrlen(buffer) + 1);
179 Ustrcpy(yield, buffer);
180 return yield;
181 }
182
183
184 /*************************************************
185 * Pack name into memory *
186 *************************************************/
187
188 /* This function packs a domain name into memory according to DNS rules. At
189 present, it doesn't do any compression.
190
191 Arguments:
192 name the name
193 pk where to put it
194
195 Returns: the updated value of pk
196 */
197
198 static uschar *
199 packname(uschar *name, uschar *pk)
200 {
201 while (*name != 0)
202 {
203 uschar *p = name;
204 while (*p != 0 && *p != '.') p++;
205 *pk++ = (p - name);
206 memmove(pk, name, p - name);
207 pk += p - name;
208 name = (*p == 0)? p : p + 1;
209 }
210 *pk++ = 0;
211 return pk;
212 }
213
214 uschar *
215 bytefield(uschar ** pp, uschar * pk)
216 {
217 unsigned value = 0;
218 uschar * p = *pp;
219
220 while (isdigit(*p)) value = value*10 + *p++ - '0';
221 while (isspace(*p)) p++;
222 *pp = p;
223 *pk++ = value & 255;
224 return pk;
225 }
226
227 uschar *
228 shortfield(uschar ** pp, uschar * pk)
229 {
230 unsigned value = 0;
231 uschar * p = *pp;
232
233 while (isdigit(*p)) value = value*10 + *p++ - '0';
234 while (isspace(*p)) p++;
235 *pp = p;
236 *pk++ = (value >> 8) & 255;
237 *pk++ = value & 255;
238 return pk;
239 }
240
241 uschar *
242 longfield(uschar ** pp, uschar * pk)
243 {
244 unsigned long value = 0;
245 uschar * p = *pp;
246
247 while (isdigit(*p)) value = value*10 + *p++ - '0';
248 while (isspace(*p)) p++;
249 *pp = p;
250 *pk++ = (value >> 24) & 255;
251 *pk++ = (value >> 16) & 255;
252 *pk++ = (value >> 8) & 255;
253 *pk++ = value & 255;
254 return pk;
255 }
256
257
258
259 /*************************************************/
260
261 static void
262 milliwait(struct itimerval *itval)
263 {
264 sigset_t sigmask;
265 sigset_t old_sigmask;
266
267 if (itval->it_value.tv_usec < 100 && itval->it_value.tv_sec == 0)
268 return;
269 (void)sigemptyset(&sigmask); /* Empty mask */
270 (void)sigaddset(&sigmask, SIGALRM); /* Add SIGALRM */
271 (void)sigprocmask(SIG_BLOCK, &sigmask, &old_sigmask); /* Block SIGALRM */
272 (void)setitimer(ITIMER_REAL, itval, NULL); /* Start timer */
273 (void)sigfillset(&sigmask); /* All signals */
274 (void)sigdelset(&sigmask, SIGALRM); /* Remove SIGALRM */
275 (void)sigsuspend(&sigmask); /* Until SIGALRM */
276 (void)sigprocmask(SIG_SETMASK, &old_sigmask, NULL); /* Restore mask */
277 }
278
279 static void
280 millisleep(int msec)
281 {
282 struct itimerval itval;
283 itval.it_interval.tv_sec = 0;
284 itval.it_interval.tv_usec = 0;
285 itval.it_value.tv_sec = msec/1000;
286 itval.it_value.tv_usec = (msec % 1000) * 1000;
287 milliwait(&itval);
288 }
289
290
291 /*************************************************
292 * Scan file for RRs *
293 *************************************************/
294
295 /* This function scans an open "zone file" for appropriate records, and adds
296 any that are found to the output buffer.
297
298 Arguments:
299 f the input FILE
300 zone the current zone name
301 domain the domain we are looking for
302 qtype the type of RR we want
303 qtypelen the length of qtype
304 pkptr points to the output buffer pointer; this is updated
305 countptr points to the record count; this is updated
306 dnssec points to the AD flag indicator; this is updated
307 aa points to the AA flag indicator; this is updated
308
309 Returns: 0 on success, else HOST_NOT_FOUND or NO_DATA or NO_RECOVERY or
310 PASS_ON - the latter if a "PASS ON NOT FOUND" line is seen
311 */
312
313 static int
314 find_records(FILE *f, uschar *zone, uschar *domain, uschar *qtype,
315 int qtypelen, uschar **pkptr, int *countptr, BOOL * dnssec, BOOL * aa)
316 {
317 int yield = HOST_NOT_FOUND;
318 int domainlen = Ustrlen(domain);
319 BOOL pass_on_not_found = FALSE;
320 tlist *typeptr;
321 uschar *pk = *pkptr;
322 uschar buffer[256];
323 uschar rrdomain[256];
324 uschar RRdomain[256];
325
326
327 /* Decode the required type */
328 for (typeptr = type_list; typeptr->name != NULL; typeptr++)
329 { if (Ustrcmp(typeptr->name, qtype) == 0) break; }
330 if (typeptr->name == NULL)
331 {
332 fprintf(stderr, "fakens: unknown record type %s\n", qtype);
333 return NO_RECOVERY;
334 }
335
336 rrdomain[0] = 0; /* No previous domain */
337 (void)fseek(f, 0, SEEK_SET); /* Start again at the beginning */
338
339 if (dnssec) *dnssec = TRUE; /* cancelled by first nonsecure rec found */
340 if (aa) *aa = TRUE; /* cancelled by first non-aa rec found */
341
342 /* Scan for RRs */
343
344 while (fgets(CS buffer, sizeof(buffer), f) != NULL)
345 {
346 uschar *rdlptr;
347 uschar *p, *ep, *pp;
348 BOOL found_cname = FALSE;
349 int i, value;
350 int tvalue = typeptr->value;
351 int qtlen = qtypelen;
352 BOOL rr_sec = FALSE;
353 BOOL rr_aa = FALSE;
354 int delay = 0;
355 uint ttl = DEFAULT_TTL;
356
357 p = buffer;
358 while (isspace(*p)) p++;
359 if (*p == 0 || *p == ';') continue;
360
361 if (Ustrncmp(p, US"PASS ON NOT FOUND", 17) == 0)
362 {
363 pass_on_not_found = TRUE;
364 continue;
365 }
366
367 ep = buffer + Ustrlen(buffer);
368 while (isspace(ep[-1])) ep--;
369 *ep = 0;
370
371 p = buffer;
372 for (;;)
373 {
374 if (Ustrncmp(p, US"DNSSEC ", 7) == 0) /* tagged as secure */
375 {
376 rr_sec = TRUE;
377 p += 7;
378 }
379 else if (Ustrncmp(p, US"AA ", 3) == 0) /* tagged as authoritive */
380 {
381 rr_aa = TRUE;
382 p += 3;
383 }
384 else if (Ustrncmp(p, US"DELAY=", 6) == 0) /* delay before response */
385 {
386 for (p += 6; *p >= '0' && *p <= '9'; p++) delay = delay*10 + *p - '0';
387 if (isspace(*p)) p++;
388 }
389 else if (Ustrncmp(p, US"TTL=", 4) == 0) /* TTL for record */
390 {
391 ttl = 0;
392 for (p += 4; *p >= '0' && *p <= '9'; p++) ttl = ttl*10 + *p - '0';
393 if (isspace(*p)) p++;
394 }
395 else
396 break;
397 }
398
399 if (!isspace(*p)) /* new domain name */
400 {
401 uschar *pp = rrdomain;
402 uschar *PP = RRdomain;
403 while (!isspace(*p))
404 {
405 *pp++ = tolower(*p);
406 *PP++ = *p++;
407 }
408 if (pp[-1] != '.')
409 {
410 Ustrcpy(pp, zone);
411 Ustrcpy(PP, zone);
412 }
413 else
414 {
415 pp[-1] = 0;
416 PP[-1] = 0;
417 }
418 } /* else use previous line's domain name */
419
420 /* Compare domain names; first check for a wildcard */
421
422 if (rrdomain[0] == '*')
423 {
424 int restlen = Ustrlen(rrdomain) - 1;
425 if (domainlen > restlen &&
426 Ustrcmp(domain + domainlen - restlen, rrdomain + 1) != 0) continue;
427 }
428
429 /* Not a wildcard RR */
430
431 else if (Ustrcmp(domain, rrdomain) != 0) continue;
432
433 /* The domain matches */
434
435 if (yield == HOST_NOT_FOUND) yield = NO_DATA;
436
437 /* Compare RR types; a CNAME record is always returned */
438
439 while (isspace(*p)) p++;
440
441 if (Ustrncmp(p, "CNAME", 5) == 0)
442 {
443 tvalue = ns_t_cname;
444 qtlen = 5;
445 found_cname = TRUE;
446 }
447 else if (Ustrncmp(p, qtype, qtypelen) != 0 || !isspace(p[qtypelen])) continue;
448
449 /* Found a relevant record */
450 if (delay)
451 millisleep(delay);
452
453 if (dnssec && !rr_sec)
454 *dnssec = FALSE; /* cancel AD return */
455
456 if (aa && !rr_aa)
457 *aa = FALSE; /* cancel AA return */
458
459 yield = 0;
460 *countptr = *countptr + 1;
461
462 p += qtlen;
463 while (isspace(*p)) p++;
464
465 /* For a wildcard record, use the search name; otherwise use the record's
466 name in its original case because it might contain upper case letters. */
467
468 pk = packname((rrdomain[0] == '*')? domain : RRdomain, pk);
469 *pk++ = (tvalue >> 8) & 255;
470 *pk++ = (tvalue) & 255;
471 *pk++ = 0;
472 *pk++ = 1; /* class = IN */
473
474 *pk++ = (ttl >>24) & 255;
475 *pk++ = (ttl >>16) & 255;
476 *pk++ = (ttl >> 8) & 255;
477 *pk++ = ttl & 255;
478
479 rdlptr = pk; /* remember rdlength field */
480 pk += 2;
481
482 /* The rest of the data depends on the type */
483
484 switch (tvalue)
485 {
486 case ns_t_soa:
487 p = strtok(p, " ");
488 ep = p + strlen(p);
489 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
490 pk = packname(p, pk); /* primary ns */
491 p = strtok(NULL, " ");
492 pk = packname(p , pk); /* responsible mailbox */
493 *(p += strlen(p)) = ' ';
494 while (isspace(*p)) p++;
495 pk = longfield(&p, pk); /* serial */
496 pk = longfield(&p, pk); /* refresh */
497 pk = longfield(&p, pk); /* retry */
498 pk = longfield(&p, pk); /* expire */
499 pk = longfield(&p, pk); /* minimum */
500 break;
501
502 case ns_t_a:
503 for (i = 0; i < 4; i++)
504 {
505 value = 0;
506 while (isdigit(*p)) value = value*10 + *p++ - '0';
507 *pk++ = value;
508 p++;
509 }
510 break;
511
512 /* The only occurrence of a double colon is for ::1 */
513 case ns_t_aaaa:
514 if (Ustrcmp(p, "::1") == 0)
515 {
516 memset(pk, 0, 15);
517 pk += 15;
518 *pk++ = 1;
519 }
520 else for (i = 0; i < 8; i++)
521 {
522 value = 0;
523 while (isxdigit(*p))
524 {
525 value = value * 16 + toupper(*p) - (isdigit(*p)? '0' : '7');
526 p++;
527 }
528 *pk++ = (value >> 8) & 255;
529 *pk++ = value & 255;
530 p++;
531 }
532 break;
533
534 case ns_t_mx:
535 pk = shortfield(&p, pk);
536 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
537 pk = packname(p, pk);
538 break;
539
540 case ns_t_txt:
541 pp = pk++;
542 if (*p == '"') p++; /* Should always be the case */
543 while (*p != 0 && *p != '"') *pk++ = *p++;
544 *pp = pk - pp - 1;
545 break;
546
547 case ns_t_tlsa:
548 pk = bytefield(&p, pk); /* usage */
549 pk = bytefield(&p, pk); /* selector */
550 pk = bytefield(&p, pk); /* match type */
551 while (isxdigit(*p))
552 {
553 value = toupper(*p) - (isdigit(*p) ? '0' : '7') << 4;
554 if (isxdigit(*++p))
555 {
556 value |= toupper(*p) - (isdigit(*p) ? '0' : '7');
557 p++;
558 }
559 *pk++ = value & 255;
560 }
561
562 break;
563
564 case ns_t_srv:
565 for (i = 0; i < 3; i++)
566 {
567 value = 0;
568 while (isdigit(*p)) value = value*10 + *p++ - '0';
569 while (isspace(*p)) p++;
570 *pk++ = (value >> 8) & 255;
571 *pk++ = value & 255;
572 }
573
574 /* Fall through */
575
576 case ns_t_cname:
577 case ns_t_ns:
578 case ns_t_ptr:
579 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
580 pk = packname(p, pk);
581 break;
582 }
583
584 /* Fill in the length, and we are done with this RR */
585
586 rdlptr[0] = ((pk - rdlptr - 2) >> 8) & 255;
587 rdlptr[1] = (pk -rdlptr - 2) & 255;
588 }
589
590 *pkptr = pk;
591 return (yield == HOST_NOT_FOUND && pass_on_not_found)? PASS_ON : yield;
592 }
593
594
595 static void
596 alarmfn(int sig)
597 {
598 }
599
600
601 /*************************************************
602 * Special-purpose domains *
603 *************************************************/
604
605 static int
606 special_manyhome(uschar * packet, uschar * domain)
607 {
608 uschar *pk = packet + 12;
609 uschar *rdlptr;
610 int i, j;
611
612 memset(packet, 0, 12);
613
614 for (i = 104; i <= 111; i++) for (j = 0; j <= 255; j++)
615 {
616 pk = packname(domain, pk);
617 *pk++ = (ns_t_a >> 8) & 255;
618 *pk++ = (ns_t_a) & 255;
619 *pk++ = 0;
620 *pk++ = 1; /* class = IN */
621 pk += 4; /* TTL field; don't care */
622 rdlptr = pk; /* remember rdlength field */
623 pk += 2;
624
625 *pk++ = 10; *pk++ = 250; *pk++ = i; *pk++ = j;
626
627 rdlptr[0] = ((pk - rdlptr - 2) >> 8) & 255;
628 rdlptr[1] = (pk - rdlptr - 2) & 255;
629 }
630
631 packet[6] = (2048 >> 8) & 255;
632 packet[7] = 2048 & 255;
633 packet[10] = 0;
634 packet[11] = 0;
635
636 (void)fwrite(packet, 1, pk - packet, stdout);
637 return 0;
638 }
639
640 static int
641 special_again(uschar * packet, uschar * domain)
642 {
643 int delay = atoi(CCS domain); /* digits at the start of the name */
644 if (delay > 0) sleep(delay);
645 return TRY_AGAIN;
646 }
647
648
649 /*************************************************
650 * Entry point and main program *
651 *************************************************/
652
653 int
654 main(int argc, char **argv)
655 {
656 FILE *f;
657 DIR *d;
658 int domlen, qtypelen;
659 int yield, count;
660 int i;
661 int zonecount = 0;
662 struct dirent *de;
663 zoneitem zones[32];
664 uschar *qualify = NULL;
665 uschar *p, *zone;
666 uschar *zonefile = NULL;
667 uschar domain[256];
668 uschar buffer[256];
669 uschar qtype[12];
670 uschar packet[2048 * 32 + 32];
671 HEADER *header = (HEADER *)packet;
672 uschar *pk = packet;
673 BOOL dnssec;
674 BOOL aa;
675
676 signal(SIGALRM, alarmfn);
677
678 if (argc != 4)
679 {
680 fprintf(stderr, "fakens: expected 3 arguments, received %d\n", argc-1);
681 return NO_RECOVERY;
682 }
683
684 /* Find the zones */
685
686 (void)sprintf(CS buffer, "%s/dnszones", argv[1]);
687
688 d = opendir(CCS buffer);
689 if (d == NULL)
690 {
691 fprintf(stderr, "fakens: failed to opendir %s: %s\n", buffer,
692 strerror(errno));
693 return NO_RECOVERY;
694 }
695
696 while ((de = readdir(d)) != NULL)
697 {
698 uschar *name = US de->d_name;
699 if (Ustrncmp(name, "qualify.", 8) == 0)
700 {
701 qualify = fcopystring(US "%s", name + 7);
702 continue;
703 }
704 if (Ustrncmp(name, "db.", 3) != 0) continue;
705 if (Ustrncmp(name + 3, "ip4.", 4) == 0)
706 zones[zonecount].zone = fcopystring(US "%s.in-addr.arpa", name + 6);
707 else if (Ustrncmp(name + 3, "ip6.", 4) == 0)
708 zones[zonecount].zone = fcopystring(US "%s.ip6.arpa", name + 6);
709 else
710 zones[zonecount].zone = fcopystring(US "%s", name + 2);
711 zones[zonecount++].zonefile = fcopystring(US "%s", name);
712 }
713 (void)closedir(d);
714
715 /* Get the RR type and upper case it, and check that we recognize it. */
716
717 Ustrncpy(qtype, argv[3], sizeof(qtype));
718 qtypelen = Ustrlen(qtype);
719 for (p = qtype; *p != 0; p++) *p = toupper(*p);
720
721 /* Find the domain, lower case it, deal with any specials,
722 check that it is in a zone that we handle,
723 and set up the zone file name. The zone names in the table all start with a
724 dot. */
725
726 domlen = Ustrlen(argv[2]);
727 if (argv[2][domlen-1] == '.') domlen--;
728 Ustrncpy(domain, argv[2], domlen);
729 domain[domlen] = 0;
730 for (i = 0; i < domlen; i++) domain[i] = tolower(domain[i]);
731
732 if (Ustrcmp(domain, "manyhome.test.ex") == 0 && Ustrcmp(qtype, "A") == 0)
733 return special_manyhome(packet, domain);
734 else if (domlen >= 14 && Ustrcmp(domain + domlen - 14, "test.again.dns") == 0)
735 return special_again(packet, domain);
736 else if (domlen >= 13 && Ustrcmp(domain + domlen - 13, "test.fail.dns") == 0)
737 return NO_RECOVERY;
738
739
740 if (Ustrchr(domain, '.') == NULL && qualify != NULL &&
741 Ustrcmp(domain, "dontqualify") != 0)
742 {
743 Ustrcat(domain, qualify);
744 domlen += Ustrlen(qualify);
745 }
746
747 for (i = 0; i < zonecount; i++)
748 {
749 int zlen;
750 zone = zones[i].zone;
751 zlen = Ustrlen(zone);
752 if (Ustrcmp(domain, zone+1) == 0 || (domlen >= zlen &&
753 Ustrcmp(domain + domlen - zlen, zone) == 0))
754 {
755 zonefile = zones[i].zonefile;
756 break;
757 }
758 }
759
760 if (zonefile == NULL)
761 {
762 fprintf(stderr, "fakens: query not in faked zone: domain is: %s\n", domain);
763 return PASS_ON;
764 }
765
766 (void)sprintf(CS buffer, "%s/dnszones/%s", argv[1], zonefile);
767
768 /* Initialize the start of the response packet. We don't have to fake up
769 everything, because we know that Exim will look only at the answer and
770 additional section parts. */
771
772 memset(packet, 0, 12);
773 pk += 12;
774
775 /* Open the zone file. */
776
777 f = fopen(CS buffer, "r");
778 if (f == NULL)
779 {
780 fprintf(stderr, "fakens: failed to open %s: %s\n", buffer, strerror(errno));
781 return NO_RECOVERY;
782 }
783
784 /* Find the records we want, and add them to the result. */
785
786 count = 0;
787 yield = find_records(f, zone, domain, qtype, qtypelen, &pk, &count, &dnssec, &aa);
788 if (yield == NO_RECOVERY) goto END_OFF;
789 header->ancount = htons(count);
790
791 /* If the AA bit should be set (as indicated by the AA prefix in the zone file),
792 we are expected to return some records in the authortive section. Bind9: If
793 there is data in the answer section, the authoritive section contains the NS
794 records, otherwise it contains the SOA record. Currently we mimic this
795 behaviour for the first case (there is some answer record).
796 */
797
798 if (aa)
799 find_records(f, zone, zone[0] == '.' ? zone+1 : zone, US"NS", 2, &pk, &count, NULL, NULL);
800 header->nscount = htons(count - ntohs(header->ancount));
801
802 /* There is no need to return any additional records because Exim no longer
803 (from release 4.61) makes any use of them. */
804 header->arcount = 0;
805
806 if (dnssec)
807 header->ad = 1;
808
809 if (aa)
810 header->aa = 1;
811
812 /* Close the zone file, write the result, and return. */
813
814 END_OFF:
815 (void)fclose(f);
816 (void)fwrite(packet, 1, pk - packet, stdout);
817 return yield;
818 }
819
820 /* vi: aw ai sw=2 sts=2 ts=8 et
821 */
822 /* End of fakens.c */