7e93979ecfb529267dcde21dbe386286162f7d47
[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 */