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