Testsuite: Fix fakens parser for A and AAAA RRs
[exim.git] / test / src / fakens.c
CommitLineData
c55a77db
PH
1/*************************************************
2* fakens - A Fake Nameserver Program *
3*************************************************/
4
5/* This program exists to support the testing of DNS handling code in Exim. It
6avoids the need to install special zones in a real nameserver. When Exim is
7running in its (new) test harness, DNS lookups are first passed to this program
8instead of to the real resolver. (With a few exceptions - see the discussion in
9the test suite's README file.) The program is also passed the name of the Exim
5f3d0983
HSHR
10spool directory; it expects to find its "zone files" in dnszones relative to
11exim config_main_directory. Note that there is little checking in this program. The fake
c55a77db
PH
12zone files are assumed to be syntactically valid.
13
14The zones that are handled are found by scanning the dnszones directory. A file
15whose name is of the form db.ip4.x is a zone file for .x.in-addr.arpa; a file
16whose name is of the form db.ip6.x is a zone file for .x.ip6.arpa; a file of
17the form db.anything.else is a zone file for .anything.else. A file of the form
18qualify.x.y specifies the domain that is used to qualify single-component
19names, except for the name "dontqualify".
20
21The 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
27The output from the program is written to stdout. It is supposed to be in
28exactly the same format as a traditional namserver response (see RFC 1035) so
29that Exim can process it as normal. At present, no compression is used.
30Error messages are written to stderr.
31
32The return codes from the program are zero for success, and otherwise the
33values 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
40In a real nameserver, TRY_AGAIN is also used for a non-authoritative not found,
41but 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
45This is used for zones that fakens does not recognize. It is also used if a
46line in the zone file contains exactly this:
47
48 PASS ON NOT FOUND
49
50and the domain is not found. It converts the the result to PASS_ON instead of
4d4c2a9b
JH
51HOST_NOT_FOUND.
52
846430d9 53Any DNS record line in a zone file can be prefixed with "DELAY=" and
6aa849d3 54a number of milliseconds (followed by one space).
846430d9 55
6aa849d3
JH
56Any DNS record line in a zone file can be prefixed with "DNSSEC ";
57if all the records found by a lookup are marked
f6584c83 58as such then the response will have the "AD" bit set.
589b3179 59
6aa849d3
JH
60Any DNS record line in a zone file can be prefixed with "AA "
61if all the records found by a lookup are marked
589b3179
HSHR
62as such then the response will have the "AA" bit set.
63
14b3c5bc
JH
64Any DNS record line in a zone file can be prefixed with "TTL=" and
65a number of seconds (followed by one space).
66
589b3179 67*/
c55a77db
PH
68
69#include <ctype.h>
70#include <stdarg.h>
71#include <stdio.h>
e265af1f 72#include <stdlib.h>
c55a77db
PH
73#include <string.h>
74#include <netdb.h>
75#include <errno.h>
9d787f10 76#include <signal.h>
c55a77db 77#include <arpa/nameser.h>
fd8184e3 78#include <arpa/inet.h>
c55a77db 79#include <sys/types.h>
846430d9 80#include <sys/time.h>
c55a77db 81#include <dirent.h>
2c98a555 82#include <unistd.h>
c55a77db
PH
83
84#define FALSE 0
85#define TRUE 1
86#define PASS_ON 5
87
88typedef int BOOL;
89typedef 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
c55a77db
PH
103typedef struct zoneitem {
104 uschar *zone;
105 uschar *zonefile;
106} zoneitem;
107
108typedef struct tlist {
109 uschar *name;
110 int value;
111} tlist;
112
14b3c5bc
JH
113#define DEFAULT_TTL 3600U
114
c55a77db
PH
115/* On some (older?) operating systems, the standard ns_t_xxx definitions are
116not available, and only the older T_xxx ones exist in nameser.h. If ns_t_a is
117not defined, assume we are in this state. A really old system might not even
118know about AAAA and SRV at all. */
119
120#ifndef ns_t_a
b4161d10
JH
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
c55a77db
PH
140#endif
141
142static tlist type_list[] = {
143 { US"A", ns_t_a },
144 { US"NS", ns_t_ns },
145 { US"CNAME", ns_t_cname },
d2a2c69b 146 { US"SOA", ns_t_soa },
c55a77db
PH
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 },
b4161d10 152 { US"TLSA", ns_t_tlsa },
c55a77db
PH
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
164Arguments:
165 format a format string
166 ... arguments
167
168Returns: pointer to formatted string
169*/
170
171static uschar *
172fcopystring(uschar *format, ...)
173{
174uschar *yield;
175char buffer[256];
176va_list ap;
177va_start(ap, format);
e265af1f 178vsprintf(buffer, CS format, ap);
c55a77db
PH
179va_end(ap);
180yield = (uschar *)malloc(Ustrlen(buffer) + 1);
181Ustrcpy(yield, buffer);
182return 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
191present, it doesn't do any compression.
192
193Arguments:
194 name the name
195 pk where to put it
196
197Returns: the updated value of pk
198*/
199
200static uschar *
201packname(uschar *name, uschar *pk)
202{
203while (*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;
213return pk;
214}
215
b4161d10 216uschar *
36b894a6
JH
217bytefield(uschar ** pp, uschar * pk)
218{
219unsigned value = 0;
220uschar * p = *pp;
221
222while (isdigit(*p)) value = value*10 + *p++ - '0';
223while (isspace(*p)) p++;
224*pp = p;
225*pk++ = value & 255;
226return pk;
227}
228
229uschar *
b4161d10
JH
230shortfield(uschar ** pp, uschar * pk)
231{
232unsigned value = 0;
233uschar * p = *pp;
234
235while (isdigit(*p)) value = value*10 + *p++ - '0';
236while (isspace(*p)) p++;
237*pp = p;
238*pk++ = (value >> 8) & 255;
239*pk++ = value & 255;
240return pk;
241}
242
d2a2c69b
JH
243uschar *
244longfield(uschar ** pp, uschar * pk)
245{
246unsigned long value = 0;
247uschar * p = *pp;
248
249while (isdigit(*p)) value = value*10 + *p++ - '0';
250while (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;
256return pk;
257}
258
c55a77db
PH
259
260
846430d9
JH
261/*************************************************/
262
263static void
264milliwait(struct itimerval *itval)
265{
266sigset_t sigmask;
267sigset_t old_sigmask;
268
269if (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
281static void
282millisleep(int msec)
283{
284struct itimerval itval;
285itval.it_interval.tv_sec = 0;
286itval.it_interval.tv_usec = 0;
287itval.it_value.tv_sec = msec/1000;
288itval.it_value.tv_usec = (msec % 1000) * 1000;
289milliwait(&itval);
290}
291
292
c55a77db
PH
293/*************************************************
294* Scan file for RRs *
295*************************************************/
296
297/* This function scans an open "zone file" for appropriate records, and adds
298any that are found to the output buffer.
299
300Arguments:
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
f6584c83
HSHR
308 dnssec points to the AD flag indicator; this is updated
309 aa points to the AA flag indicator; this is updated
c55a77db
PH
310
311Returns: 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
315static int
316find_records(FILE *f, uschar *zone, uschar *domain, uschar *qtype,
589b3179 317 int qtypelen, uschar **pkptr, int *countptr, BOOL * dnssec, BOOL * aa)
c55a77db
PH
318{
319int yield = HOST_NOT_FOUND;
c55a77db
PH
320int domainlen = Ustrlen(domain);
321BOOL pass_on_not_found = FALSE;
322tlist *typeptr;
323uschar *pk = *pkptr;
324uschar buffer[256];
325uschar rrdomain[256];
75e0e026 326uschar RRdomain[256];
c55a77db 327
c55a77db 328
f6584c83 329/* Decode the required type */
c55a77db
PH
330for (typeptr = type_list; typeptr->name != NULL; typeptr++)
331 { if (Ustrcmp(typeptr->name, qtype) == 0) break; }
332if (typeptr->name == NULL)
333 {
334 fprintf(stderr, "fakens: unknown record type %s\n", qtype);
335 return NO_RECOVERY;
336 }
337
338rrdomain[0] = 0; /* No previous domain */
339(void)fseek(f, 0, SEEK_SET); /* Start again at the beginning */
340
f6584c83
HSHR
341if (dnssec) *dnssec = TRUE; /* cancelled by first nonsecure rec found */
342if (aa) *aa = TRUE; /* cancelled by first non-aa rec found */
4d4c2a9b 343
c55a77db
PH
344/* Scan for RRs */
345
346while (fgets(CS buffer, sizeof(buffer), f) != NULL)
347 {
348 uschar *rdlptr;
349 uschar *p, *ep, *pp;
350 BOOL found_cname = FALSE;
d2a2c69b 351 int i, value;
c55a77db
PH
352 int tvalue = typeptr->value;
353 int qtlen = qtypelen;
4d4c2a9b 354 BOOL rr_sec = FALSE;
589b3179 355 BOOL rr_aa = FALSE;
846430d9 356 int delay = 0;
14b3c5bc 357 uint ttl = DEFAULT_TTL;
c55a77db
PH
358
359 p = buffer;
360 while (isspace(*p)) p++;
361 if (*p == 0 || *p == ';') continue;
362
4d4c2a9b 363 if (Ustrncmp(p, US"PASS ON NOT FOUND", 17) == 0)
c55a77db
PH
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;
846430d9 374 for (;;)
1fb7660f 375 {
f6584c83 376 if (Ustrncmp(p, US"DNSSEC ", 7) == 0) /* tagged as secure */
1fb7660f
JH
377 {
378 rr_sec = TRUE;
379 p += 7;
380 }
f6584c83 381 else if (Ustrncmp(p, US"AA ", 3) == 0) /* tagged as authoritive */
589b3179
HSHR
382 {
383 rr_aa = TRUE;
384 p += 3;
385 }
f6584c83 386 else if (Ustrncmp(p, US"DELAY=", 6) == 0) /* delay before response */
1fb7660f
JH
387 {
388 for (p += 6; *p >= '0' && *p <= '9'; p++) delay = delay*10 + *p - '0';
6aa849d3 389 if (isspace(*p)) p++;
1fb7660f 390 }
14b3c5bc
JH
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 }
1fb7660f
JH
397 else
398 break;
399 }
4d4c2a9b 400
6aa849d3 401 if (!isspace(*p)) /* new domain name */
c55a77db
PH
402 {
403 uschar *pp = rrdomain;
75e0e026
PH
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 }
6aa849d3 420 } /* else use previous line's domain name */
c55a77db
PH
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 */
846430d9
JH
452 if (delay)
453 millisleep(delay);
454
f6584c83
HSHR
455 if (dnssec && !rr_sec)
456 *dnssec = FALSE; /* cancel AD return */
4d4c2a9b 457
f6584c83
HSHR
458 if (aa && !rr_aa)
459 *aa = FALSE; /* cancel AA return */
589b3179 460
c55a77db
PH
461 yield = 0;
462 *countptr = *countptr + 1;
463
464 p += qtlen;
465 while (isspace(*p)) p++;
466
75e0e026
PH
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);
c55a77db
PH
471 *pk++ = (tvalue >> 8) & 255;
472 *pk++ = (tvalue) & 255;
473 *pk++ = 0;
474 *pk++ = 1; /* class = IN */
475
14b3c5bc
JH
476 *pk++ = (ttl >>24) & 255;
477 *pk++ = (ttl >>16) & 255;
478 *pk++ = (ttl >> 8) & 255;
479 *pk++ = ttl & 255;
c55a77db
PH
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 {
d2a2c69b
JH
488 case ns_t_soa:
489 p = strtok(p, " ");
490 ep = p + strlen(p);
491 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
f6584c83 492 pk = packname(p, pk); /* primary ns */
d2a2c69b 493 p = strtok(NULL, " ");
f6584c83 494 pk = packname(p , pk); /* responsible mailbox */
d2a2c69b
JH
495 *(p += strlen(p)) = ' ';
496 while (isspace(*p)) p++;
f6584c83
HSHR
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 */
d2a2c69b 502 break;
c55a77db
PH
503
504 case ns_t_a:
fd8184e3
HSHR
505 inet_pton(AF_INET, p, pk); /* FIXME: error checking */
506 pk += 4;
d2a2c69b 507 break;
c55a77db 508
c55a77db 509 case ns_t_aaaa:
fd8184e3
HSHR
510 inet_pton(AF_INET6, p, pk); /* FIXME: error checking */
511 pk += 16;
d2a2c69b 512 break;
c55a77db
PH
513
514 case ns_t_mx:
d2a2c69b
JH
515 pk = shortfield(&p, pk);
516 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
517 pk = packname(p, pk);
518 break;
c55a77db
PH
519
520 case ns_t_txt:
d2a2c69b
JH
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;
c55a77db 526
b4161d10 527 case ns_t_tlsa:
f6584c83
HSHR
528 pk = bytefield(&p, pk); /* usage */
529 pk = bytefield(&p, pk); /* selector */
530 pk = bytefield(&p, pk); /* match type */
d2a2c69b 531 while (isxdigit(*p))
b4161d10
JH
532 {
533 value = toupper(*p) - (isdigit(*p) ? '0' : '7') << 4;
534 if (isxdigit(*++p))
f6584c83
HSHR
535 {
536 value |= toupper(*p) - (isdigit(*p) ? '0' : '7');
537 p++;
538 }
b4161d10
JH
539 *pk++ = value & 255;
540 }
541
d2a2c69b 542 break;
b4161d10 543
c55a77db 544 case ns_t_srv:
d2a2c69b 545 for (i = 0; i < 3; i++)
f6584c83
HSHR
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 }
c55a77db
PH
553
554 /* Fall through */
555
556 case ns_t_cname:
557 case ns_t_ns:
558 case ns_t_ptr:
d2a2c69b
JH
559 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
560 pk = packname(p, pk);
561 break;
c55a77db
PH
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;
c55a77db
PH
568 }
569
570*pkptr = pk;
571return (yield == HOST_NOT_FOUND && pass_on_not_found)? PASS_ON : yield;
572}
573
574
846430d9
JH
575static void
576alarmfn(int sig)
577{
578}
c55a77db 579
dd2a32ad
JH
580
581/*************************************************
582* Special-purpose domains *
583*************************************************/
584
585static int
586special_manyhome(uschar * packet, uschar * domain)
587{
588uschar *pk = packet + 12;
589uschar *rdlptr;
590int i, j;
591
592memset(packet, 0, 12);
593
594for (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
611packet[6] = (2048 >> 8) & 255;
612packet[7] = 2048 & 255;
613packet[10] = 0;
614packet[11] = 0;
615
616(void)fwrite(packet, 1, pk - packet, stdout);
617return 0;
618}
619
620static int
621special_again(uschar * packet, uschar * domain)
622{
623int delay = atoi(CCS domain); /* digits at the start of the name */
624if (delay > 0) sleep(delay);
625return TRY_AGAIN;
626}
627
628
c55a77db
PH
629/*************************************************
630* Entry point and main program *
631*************************************************/
632
633int
634main(int argc, char **argv)
635{
636FILE *f;
637DIR *d;
75e0e026 638int domlen, qtypelen;
c55a77db
PH
639int yield, count;
640int i;
641int zonecount = 0;
c55a77db 642struct dirent *de;
c55a77db
PH
643zoneitem zones[32];
644uschar *qualify = NULL;
645uschar *p, *zone;
646uschar *zonefile = NULL;
647uschar domain[256];
648uschar buffer[256];
649uschar qtype[12];
1fb7660f 650uschar packet[2048 * 32 + 32];
f6584c83 651HEADER *header = (HEADER *)packet;
c55a77db 652uschar *pk = packet;
4d4c2a9b 653BOOL dnssec;
589b3179 654BOOL aa;
c55a77db 655
846430d9
JH
656signal(SIGALRM, alarmfn);
657
c55a77db
PH
658if (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
5f3d0983 666(void)sprintf(CS buffer, "%s/dnszones", argv[1]);
c55a77db
PH
667
668d = opendir(CCS buffer);
669if (d == NULL)
670 {
671 fprintf(stderr, "fakens: failed to opendir %s: %s\n", buffer,
672 strerror(errno));
673 return NO_RECOVERY;
674 }
675
676while ((de = readdir(d)) != NULL)
677 {
e265af1f 678 uschar *name = US de->d_name;
c55a77db
PH
679 if (Ustrncmp(name, "qualify.", 8) == 0)
680 {
e265af1f 681 qualify = fcopystring(US "%s", name + 7);
c55a77db
PH
682 continue;
683 }
684 if (Ustrncmp(name, "db.", 3) != 0) continue;
685 if (Ustrncmp(name + 3, "ip4.", 4) == 0)
e265af1f 686 zones[zonecount].zone = fcopystring(US "%s.in-addr.arpa", name + 6);
c55a77db 687 else if (Ustrncmp(name + 3, "ip6.", 4) == 0)
e265af1f 688 zones[zonecount].zone = fcopystring(US "%s.ip6.arpa", name + 6);
c55a77db 689 else
e265af1f
JH
690 zones[zonecount].zone = fcopystring(US "%s", name + 2);
691 zones[zonecount++].zonefile = fcopystring(US "%s", name);
c55a77db
PH
692 }
693(void)closedir(d);
694
695/* Get the RR type and upper case it, and check that we recognize it. */
696
697Ustrncpy(qtype, argv[3], sizeof(qtype));
698qtypelen = Ustrlen(qtype);
699for (p = qtype; *p != 0; p++) *p = toupper(*p);
700
1fb7660f
JH
701/* Find the domain, lower case it, deal with any specials,
702check that it is in a zone that we handle,
c55a77db
PH
703and set up the zone file name. The zone names in the table all start with a
704dot. */
705
706domlen = Ustrlen(argv[2]);
707if (argv[2][domlen-1] == '.') domlen--;
708Ustrncpy(domain, argv[2], domlen);
709domain[domlen] = 0;
710for (i = 0; i < domlen; i++) domain[i] = tolower(domain[i]);
711
1fb7660f 712if (Ustrcmp(domain, "manyhome.test.ex") == 0 && Ustrcmp(qtype, "A") == 0)
dd2a32ad
JH
713 return special_manyhome(packet, domain);
714else if (domlen >= 14 && Ustrcmp(domain + domlen - 14, "test.again.dns") == 0)
715 return special_again(packet, domain);
716else if (domlen >= 13 && Ustrcmp(domain + domlen - 13, "test.fail.dns") == 0)
717 return NO_RECOVERY;
1fb7660f
JH
718
719
c55a77db
PH
720if (Ustrchr(domain, '.') == NULL && qualify != NULL &&
721 Ustrcmp(domain, "dontqualify") != 0)
722 {
723 Ustrcat(domain, qualify);
724 domlen += Ustrlen(qualify);
725 }
726
727for (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
740if (zonefile == NULL)
741 {
742 fprintf(stderr, "fakens: query not in faked zone: domain is: %s\n", domain);
743 return PASS_ON;
744 }
745
5f3d0983 746(void)sprintf(CS buffer, "%s/dnszones/%s", argv[1], zonefile);
c55a77db
PH
747
748/* Initialize the start of the response packet. We don't have to fake up
749everything, because we know that Exim will look only at the answer and
750additional section parts. */
751
752memset(packet, 0, 12);
753pk += 12;
754
755/* Open the zone file. */
756
42ec9880 757f = fopen(CS buffer, "r");
c55a77db
PH
758if (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
766count = 0;
589b3179 767yield = find_records(f, zone, domain, qtype, qtypelen, &pk, &count, &dnssec, &aa);
c55a77db 768if (yield == NO_RECOVERY) goto END_OFF;
f6584c83 769header->ancount = htons(count);
c55a77db 770
f6584c83
HSHR
771/* If the AA bit should be set (as indicated by the AA prefix in the zone file),
772we are expected to return some records in the authortive section. Bind9: If
773there is data in the answer section, the authoritive section contains the NS
774records, otherwise it contains the SOA record. Currently we mimic this
775behaviour for the first case (there is some answer record).
776*/
777
778if (aa)
779 find_records(f, zone, zone[0] == '.' ? zone+1 : zone, US"NS", 2, &pk, &count, NULL, NULL);
780header->nscount = htons(count - ntohs(header->ancount));
c55a77db 781
75e0e026
PH
782/* There is no need to return any additional records because Exim no longer
783(from release 4.61) makes any use of them. */
f6584c83 784header->arcount = 0;
c55a77db 785
4d4c2a9b 786if (dnssec)
f6584c83 787 header->ad = 1;
4d4c2a9b 788
589b3179 789if (aa)
f6584c83 790 header->aa = 1;
589b3179 791
c55a77db
PH
792/* Close the zone file, write the result, and return. */
793
794END_OFF:
795(void)fclose(f);
796(void)fwrite(packet, 1, pk - packet, stdout);
797return yield;
798}
799
589b3179 800/* vi: aw ai sw=2 sts=2 ts=8 et
4d4c2a9b 801*/
c55a77db 802/* End of fakens.c */