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