DNS: time-limit cached returns, using TTL. Bug 1395
[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
PH
77#include <arpa/nameser.h>
78#include <sys/types.h>
846430d9 79#include <sys/time.h>
c55a77db
PH
80#include <dirent.h>
81
82#define FALSE 0
83#define TRUE 1
84#define PASS_ON 5
85
86typedef int BOOL;
87typedef 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
c55a77db
PH
101typedef struct zoneitem {
102 uschar *zone;
103 uschar *zonefile;
104} zoneitem;
105
106typedef struct tlist {
107 uschar *name;
108 int value;
109} tlist;
110
14b3c5bc
JH
111#define DEFAULT_TTL 3600U
112
c55a77db
PH
113/* On some (older?) operating systems, the standard ns_t_xxx definitions are
114not available, and only the older T_xxx ones exist in nameser.h. If ns_t_a is
115not defined, assume we are in this state. A really old system might not even
116know about AAAA and SRV at all. */
117
118#ifndef ns_t_a
b4161d10
JH
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
c55a77db
PH
138#endif
139
140static tlist type_list[] = {
141 { US"A", ns_t_a },
142 { US"NS", ns_t_ns },
143 { US"CNAME", ns_t_cname },
d2a2c69b 144 { US"SOA", ns_t_soa },
c55a77db
PH
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 },
b4161d10 150 { US"TLSA", ns_t_tlsa },
c55a77db
PH
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
162Arguments:
163 format a format string
164 ... arguments
165
166Returns: pointer to formatted string
167*/
168
169static uschar *
170fcopystring(uschar *format, ...)
171{
172uschar *yield;
173char buffer[256];
174va_list ap;
175va_start(ap, format);
e265af1f 176vsprintf(buffer, CS format, ap);
c55a77db
PH
177va_end(ap);
178yield = (uschar *)malloc(Ustrlen(buffer) + 1);
179Ustrcpy(yield, buffer);
180return 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
189present, it doesn't do any compression.
190
191Arguments:
192 name the name
193 pk where to put it
194
195Returns: the updated value of pk
196*/
197
198static uschar *
199packname(uschar *name, uschar *pk)
200{
201while (*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;
211return pk;
212}
213
36b894a6
JH
214uschar *
215bytefield(uschar ** pp, uschar * pk)
216{
217unsigned value = 0;
218uschar * p = *pp;
219
220while (isdigit(*p)) value = value*10 + *p++ - '0';
221while (isspace(*p)) p++;
222*pp = p;
223*pk++ = value & 255;
224return pk;
225}
226
b4161d10
JH
227uschar *
228shortfield(uschar ** pp, uschar * pk)
229{
230unsigned value = 0;
231uschar * p = *pp;
232
233while (isdigit(*p)) value = value*10 + *p++ - '0';
234while (isspace(*p)) p++;
235*pp = p;
236*pk++ = (value >> 8) & 255;
237*pk++ = value & 255;
238return pk;
239}
240
d2a2c69b
JH
241uschar *
242longfield(uschar ** pp, uschar * pk)
243{
244unsigned long value = 0;
245uschar * p = *pp;
246
247while (isdigit(*p)) value = value*10 + *p++ - '0';
248while (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;
254return pk;
255}
256
c55a77db
PH
257
258
846430d9
JH
259/*************************************************/
260
261static void
262milliwait(struct itimerval *itval)
263{
264sigset_t sigmask;
265sigset_t old_sigmask;
266
267if (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
279static void
280millisleep(int msec)
281{
282struct itimerval itval;
283itval.it_interval.tv_sec = 0;
284itval.it_interval.tv_usec = 0;
285itval.it_value.tv_sec = msec/1000;
286itval.it_value.tv_usec = (msec % 1000) * 1000;
287milliwait(&itval);
288}
289
290
c55a77db
PH
291/*************************************************
292* Scan file for RRs *
293*************************************************/
294
295/* This function scans an open "zone file" for appropriate records, and adds
296any that are found to the output buffer.
297
298Arguments:
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
f6584c83
HSHR
306 dnssec points to the AD flag indicator; this is updated
307 aa points to the AA flag indicator; this is updated
c55a77db
PH
308
309Returns: 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
313static int
314find_records(FILE *f, uschar *zone, uschar *domain, uschar *qtype,
589b3179 315 int qtypelen, uschar **pkptr, int *countptr, BOOL * dnssec, BOOL * aa)
c55a77db
PH
316{
317int yield = HOST_NOT_FOUND;
c55a77db
PH
318int domainlen = Ustrlen(domain);
319BOOL pass_on_not_found = FALSE;
320tlist *typeptr;
321uschar *pk = *pkptr;
322uschar buffer[256];
323uschar rrdomain[256];
75e0e026 324uschar RRdomain[256];
c55a77db 325
c55a77db 326
f6584c83 327/* Decode the required type */
c55a77db
PH
328for (typeptr = type_list; typeptr->name != NULL; typeptr++)
329 { if (Ustrcmp(typeptr->name, qtype) == 0) break; }
330if (typeptr->name == NULL)
331 {
332 fprintf(stderr, "fakens: unknown record type %s\n", qtype);
333 return NO_RECOVERY;
334 }
335
336rrdomain[0] = 0; /* No previous domain */
337(void)fseek(f, 0, SEEK_SET); /* Start again at the beginning */
338
f6584c83
HSHR
339if (dnssec) *dnssec = TRUE; /* cancelled by first nonsecure rec found */
340if (aa) *aa = TRUE; /* cancelled by first non-aa rec found */
4d4c2a9b 341
c55a77db
PH
342/* Scan for RRs */
343
344while (fgets(CS buffer, sizeof(buffer), f) != NULL)
345 {
346 uschar *rdlptr;
347 uschar *p, *ep, *pp;
348 BOOL found_cname = FALSE;
d2a2c69b 349 int i, value;
c55a77db
PH
350 int tvalue = typeptr->value;
351 int qtlen = qtypelen;
4d4c2a9b 352 BOOL rr_sec = FALSE;
589b3179 353 BOOL rr_aa = FALSE;
846430d9 354 int delay = 0;
14b3c5bc 355 uint ttl = DEFAULT_TTL;
c55a77db
PH
356
357 p = buffer;
358 while (isspace(*p)) p++;
359 if (*p == 0 || *p == ';') continue;
360
4d4c2a9b 361 if (Ustrncmp(p, US"PASS ON NOT FOUND", 17) == 0)
c55a77db
PH
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;
846430d9 372 for (;;)
1fb7660f 373 {
f6584c83 374 if (Ustrncmp(p, US"DNSSEC ", 7) == 0) /* tagged as secure */
1fb7660f
JH
375 {
376 rr_sec = TRUE;
377 p += 7;
378 }
f6584c83 379 else if (Ustrncmp(p, US"AA ", 3) == 0) /* tagged as authoritive */
589b3179
HSHR
380 {
381 rr_aa = TRUE;
382 p += 3;
383 }
f6584c83 384 else if (Ustrncmp(p, US"DELAY=", 6) == 0) /* delay before response */
1fb7660f
JH
385 {
386 for (p += 6; *p >= '0' && *p <= '9'; p++) delay = delay*10 + *p - '0';
6aa849d3 387 if (isspace(*p)) p++;
1fb7660f 388 }
14b3c5bc
JH
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 }
1fb7660f
JH
395 else
396 break;
397 }
4d4c2a9b 398
6aa849d3 399 if (!isspace(*p)) /* new domain name */
c55a77db
PH
400 {
401 uschar *pp = rrdomain;
75e0e026
PH
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 }
6aa849d3 418 } /* else use previous line's domain name */
c55a77db
PH
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 */
846430d9
JH
450 if (delay)
451 millisleep(delay);
452
f6584c83
HSHR
453 if (dnssec && !rr_sec)
454 *dnssec = FALSE; /* cancel AD return */
4d4c2a9b 455
f6584c83
HSHR
456 if (aa && !rr_aa)
457 *aa = FALSE; /* cancel AA return */
589b3179 458
c55a77db
PH
459 yield = 0;
460 *countptr = *countptr + 1;
461
462 p += qtlen;
463 while (isspace(*p)) p++;
464
75e0e026
PH
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);
c55a77db
PH
469 *pk++ = (tvalue >> 8) & 255;
470 *pk++ = (tvalue) & 255;
471 *pk++ = 0;
472 *pk++ = 1; /* class = IN */
473
14b3c5bc
JH
474 *pk++ = (ttl >>24) & 255;
475 *pk++ = (ttl >>16) & 255;
476 *pk++ = (ttl >> 8) & 255;
477 *pk++ = ttl & 255;
c55a77db
PH
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 {
d2a2c69b
JH
486 case ns_t_soa:
487 p = strtok(p, " ");
488 ep = p + strlen(p);
489 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
f6584c83 490 pk = packname(p, pk); /* primary ns */
d2a2c69b 491 p = strtok(NULL, " ");
f6584c83 492 pk = packname(p , pk); /* responsible mailbox */
d2a2c69b
JH
493 *(p += strlen(p)) = ' ';
494 while (isspace(*p)) p++;
f6584c83
HSHR
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 */
d2a2c69b 500 break;
c55a77db
PH
501
502 case ns_t_a:
d2a2c69b 503 for (i = 0; i < 4; i++)
f6584c83
HSHR
504 {
505 value = 0;
506 while (isdigit(*p)) value = value*10 + *p++ - '0';
507 *pk++ = value;
508 p++;
509 }
d2a2c69b 510 break;
c55a77db
PH
511
512 /* The only occurrence of a double colon is for ::1 */
513 case ns_t_aaaa:
d2a2c69b 514 if (Ustrcmp(p, "::1") == 0)
f6584c83
HSHR
515 {
516 memset(pk, 0, 15);
517 pk += 15;
518 *pk++ = 1;
519 }
d2a2c69b 520 else for (i = 0; i < 8; i++)
f6584c83
HSHR
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 }
d2a2c69b 532 break;
c55a77db
PH
533
534 case ns_t_mx:
d2a2c69b
JH
535 pk = shortfield(&p, pk);
536 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
537 pk = packname(p, pk);
538 break;
c55a77db
PH
539
540 case ns_t_txt:
d2a2c69b
JH
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;
c55a77db 546
b4161d10 547 case ns_t_tlsa:
f6584c83
HSHR
548 pk = bytefield(&p, pk); /* usage */
549 pk = bytefield(&p, pk); /* selector */
550 pk = bytefield(&p, pk); /* match type */
d2a2c69b 551 while (isxdigit(*p))
b4161d10
JH
552 {
553 value = toupper(*p) - (isdigit(*p) ? '0' : '7') << 4;
554 if (isxdigit(*++p))
f6584c83
HSHR
555 {
556 value |= toupper(*p) - (isdigit(*p) ? '0' : '7');
557 p++;
558 }
b4161d10
JH
559 *pk++ = value & 255;
560 }
561
d2a2c69b 562 break;
b4161d10 563
c55a77db 564 case ns_t_srv:
d2a2c69b 565 for (i = 0; i < 3; i++)
f6584c83
HSHR
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 }
c55a77db
PH
573
574 /* Fall through */
575
576 case ns_t_cname:
577 case ns_t_ns:
578 case ns_t_ptr:
d2a2c69b
JH
579 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
580 pk = packname(p, pk);
581 break;
c55a77db
PH
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;
c55a77db
PH
588 }
589
590*pkptr = pk;
591return (yield == HOST_NOT_FOUND && pass_on_not_found)? PASS_ON : yield;
592}
593
594
846430d9
JH
595static void
596alarmfn(int sig)
597{
598}
c55a77db 599
dd2a32ad
JH
600
601/*************************************************
602* Special-purpose domains *
603*************************************************/
604
605static int
606special_manyhome(uschar * packet, uschar * domain)
607{
608uschar *pk = packet + 12;
609uschar *rdlptr;
610int i, j;
611
612memset(packet, 0, 12);
613
614for (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
631packet[6] = (2048 >> 8) & 255;
632packet[7] = 2048 & 255;
633packet[10] = 0;
634packet[11] = 0;
635
636(void)fwrite(packet, 1, pk - packet, stdout);
637return 0;
638}
639
640static int
641special_again(uschar * packet, uschar * domain)
642{
643int delay = atoi(CCS domain); /* digits at the start of the name */
644if (delay > 0) sleep(delay);
645return TRY_AGAIN;
646}
647
648
c55a77db
PH
649/*************************************************
650* Entry point and main program *
651*************************************************/
652
653int
654main(int argc, char **argv)
655{
656FILE *f;
657DIR *d;
75e0e026 658int domlen, qtypelen;
c55a77db
PH
659int yield, count;
660int i;
661int zonecount = 0;
c55a77db 662struct dirent *de;
c55a77db
PH
663zoneitem zones[32];
664uschar *qualify = NULL;
665uschar *p, *zone;
666uschar *zonefile = NULL;
667uschar domain[256];
668uschar buffer[256];
669uschar qtype[12];
1fb7660f 670uschar packet[2048 * 32 + 32];
f6584c83 671HEADER *header = (HEADER *)packet;
c55a77db 672uschar *pk = packet;
4d4c2a9b 673BOOL dnssec;
589b3179 674BOOL aa;
c55a77db 675
846430d9
JH
676signal(SIGALRM, alarmfn);
677
c55a77db
PH
678if (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
5f3d0983 686(void)sprintf(CS buffer, "%s/dnszones", argv[1]);
c55a77db
PH
687
688d = opendir(CCS buffer);
689if (d == NULL)
690 {
691 fprintf(stderr, "fakens: failed to opendir %s: %s\n", buffer,
692 strerror(errno));
693 return NO_RECOVERY;
694 }
695
696while ((de = readdir(d)) != NULL)
697 {
e265af1f 698 uschar *name = US de->d_name;
c55a77db
PH
699 if (Ustrncmp(name, "qualify.", 8) == 0)
700 {
e265af1f 701 qualify = fcopystring(US "%s", name + 7);
c55a77db
PH
702 continue;
703 }
704 if (Ustrncmp(name, "db.", 3) != 0) continue;
705 if (Ustrncmp(name + 3, "ip4.", 4) == 0)
e265af1f 706 zones[zonecount].zone = fcopystring(US "%s.in-addr.arpa", name + 6);
c55a77db 707 else if (Ustrncmp(name + 3, "ip6.", 4) == 0)
e265af1f 708 zones[zonecount].zone = fcopystring(US "%s.ip6.arpa", name + 6);
c55a77db 709 else
e265af1f
JH
710 zones[zonecount].zone = fcopystring(US "%s", name + 2);
711 zones[zonecount++].zonefile = fcopystring(US "%s", name);
c55a77db
PH
712 }
713(void)closedir(d);
714
715/* Get the RR type and upper case it, and check that we recognize it. */
716
717Ustrncpy(qtype, argv[3], sizeof(qtype));
718qtypelen = Ustrlen(qtype);
719for (p = qtype; *p != 0; p++) *p = toupper(*p);
720
1fb7660f
JH
721/* Find the domain, lower case it, deal with any specials,
722check that it is in a zone that we handle,
c55a77db
PH
723and set up the zone file name. The zone names in the table all start with a
724dot. */
725
726domlen = Ustrlen(argv[2]);
727if (argv[2][domlen-1] == '.') domlen--;
728Ustrncpy(domain, argv[2], domlen);
729domain[domlen] = 0;
730for (i = 0; i < domlen; i++) domain[i] = tolower(domain[i]);
731
1fb7660f 732if (Ustrcmp(domain, "manyhome.test.ex") == 0 && Ustrcmp(qtype, "A") == 0)
dd2a32ad
JH
733 return special_manyhome(packet, domain);
734else if (domlen >= 14 && Ustrcmp(domain + domlen - 14, "test.again.dns") == 0)
735 return special_again(packet, domain);
736else if (domlen >= 13 && Ustrcmp(domain + domlen - 13, "test.fail.dns") == 0)
737 return NO_RECOVERY;
1fb7660f
JH
738
739
c55a77db
PH
740if (Ustrchr(domain, '.') == NULL && qualify != NULL &&
741 Ustrcmp(domain, "dontqualify") != 0)
742 {
743 Ustrcat(domain, qualify);
744 domlen += Ustrlen(qualify);
745 }
746
747for (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
760if (zonefile == NULL)
761 {
762 fprintf(stderr, "fakens: query not in faked zone: domain is: %s\n", domain);
763 return PASS_ON;
764 }
765
5f3d0983 766(void)sprintf(CS buffer, "%s/dnszones/%s", argv[1], zonefile);
c55a77db
PH
767
768/* Initialize the start of the response packet. We don't have to fake up
769everything, because we know that Exim will look only at the answer and
770additional section parts. */
771
772memset(packet, 0, 12);
773pk += 12;
774
775/* Open the zone file. */
776
42ec9880 777f = fopen(CS buffer, "r");
c55a77db
PH
778if (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
786count = 0;
589b3179 787yield = find_records(f, zone, domain, qtype, qtypelen, &pk, &count, &dnssec, &aa);
c55a77db 788if (yield == NO_RECOVERY) goto END_OFF;
f6584c83 789header->ancount = htons(count);
c55a77db 790
f6584c83
HSHR
791/* If the AA bit should be set (as indicated by the AA prefix in the zone file),
792we are expected to return some records in the authortive section. Bind9: If
793there is data in the answer section, the authoritive section contains the NS
794records, otherwise it contains the SOA record. Currently we mimic this
795behaviour for the first case (there is some answer record).
796*/
797
798if (aa)
799 find_records(f, zone, zone[0] == '.' ? zone+1 : zone, US"NS", 2, &pk, &count, NULL, NULL);
800header->nscount = htons(count - ntohs(header->ancount));
c55a77db 801
75e0e026
PH
802/* There is no need to return any additional records because Exim no longer
803(from release 4.61) makes any use of them. */
f6584c83 804header->arcount = 0;
c55a77db 805
4d4c2a9b 806if (dnssec)
f6584c83 807 header->ad = 1;
4d4c2a9b 808
589b3179 809if (aa)
f6584c83 810 header->aa = 1;
589b3179 811
c55a77db
PH
812/* Close the zone file, write the result, and return. */
813
814END_OFF:
815(void)fclose(f);
816(void)fwrite(packet, 1, pk - packet, stdout);
817return yield;
818}
819
589b3179 820/* vi: aw ai sw=2 sts=2 ts=8 et
4d4c2a9b 821*/
c55a77db 822/* End of fakens.c */