1 /*************************************************
2 * fakens - A Fake Nameserver Program *
3 *************************************************/
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.
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".
21 The arguments to the program are:
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
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.
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:
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
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:
43 5 PASS_ON requests Exim to call res_search()
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:
50 and the domain is not found. It converts the the result to PASS_ON instead of
53 Any DNS record line in a zone file can be prefixed with "DELAY=" and
54 a number of milliseconds (followed by whitespace).
56 Any DNS record line in a zone file can be prefixed with "DNSSEC" and
57 at least one space; if all the records found by a lookup are marked
58 as such then the response will have the "AD" bit set.
60 Any DNS record line in a zone file can be prefixed with "AA" and
61 at least one space; if all the records found by a lookup are marked
62 as such then the response will have the "AA" bit set.
74 #include <arpa/nameser.h>
75 #include <sys/types.h>
84 typedef unsigned char uschar;
87 #define CCS (const char *)
88 #define US (unsigned char *)
90 #define Ustrcat(s,t) strcat(CS(s),CCS(t))
91 #define Ustrchr(s,n) US strchr(CCS(s),n)
92 #define Ustrcmp(s,t) strcmp(CCS(s),CCS(t))
93 #define Ustrcpy(s,t) strcpy(CS(s),CCS(t))
94 #define Ustrlen(s) (int)strlen(CCS(s))
95 #define Ustrncmp(s,t,n) strncmp(CCS(s),CCS(t),n)
96 #define Ustrncpy(s,t,n) strncpy(CS(s),CCS(t),n)
98 typedef struct zoneitem {
103 typedef struct tlist {
108 /* On some (older?) operating systems, the standard ns_t_xxx definitions are
109 not available, and only the older T_xxx ones exist in nameser.h. If ns_t_a is
110 not defined, assume we are in this state. A really old system might not even
111 know about AAAA and SRV at all. */
115 # define ns_t_ns T_NS
116 # define ns_t_cname T_CNAME
117 # define ns_t_soa T_SOA
118 # define ns_t_ptr T_PTR
119 # define ns_t_mx T_MX
120 # define ns_t_txt T_TXT
121 # define ns_t_aaaa T_AAAA
122 # define ns_t_srv T_SRV
123 # define ns_t_tlsa T_TLSA
135 static tlist type_list[] = {
138 { US"CNAME", ns_t_cname },
139 { US"SOA", ns_t_soa },
140 { US"PTR", ns_t_ptr },
142 { US"TXT", ns_t_txt },
143 { US"AAAA", ns_t_aaaa },
144 { US"SRV", ns_t_srv },
145 { US"TLSA", ns_t_tlsa },
151 /*************************************************
152 * Get memory and sprintf into it *
153 *************************************************/
155 /* This is used when building a table of zones and their files.
158 format a format string
161 Returns: pointer to formatted string
165 fcopystring(uschar *format, ...)
170 va_start(ap, format);
171 vsprintf(buffer, CS format, ap);
173 yield = (uschar *)malloc(Ustrlen(buffer) + 1);
174 Ustrcpy(yield, buffer);
179 /*************************************************
180 * Pack name into memory *
181 *************************************************/
183 /* This function packs a domain name into memory according to DNS rules. At
184 present, it doesn't do any compression.
190 Returns: the updated value of pk
194 packname(uschar *name, uschar *pk)
199 while (*p != 0 && *p != '.') p++;
201 memmove(pk, name, p - name);
203 name = (*p == 0)? p : p + 1;
210 bytefield(uschar ** pp, uschar * pk)
215 while (isdigit(*p)) value = value*10 + *p++ - '0';
216 while (isspace(*p)) p++;
223 shortfield(uschar ** pp, uschar * pk)
228 while (isdigit(*p)) value = value*10 + *p++ - '0';
229 while (isspace(*p)) p++;
231 *pk++ = (value >> 8) & 255;
237 longfield(uschar ** pp, uschar * pk)
239 unsigned long value = 0;
242 while (isdigit(*p)) value = value*10 + *p++ - '0';
243 while (isspace(*p)) p++;
245 *pk++ = (value >> 24) & 255;
246 *pk++ = (value >> 16) & 255;
247 *pk++ = (value >> 8) & 255;
254 /*************************************************/
257 milliwait(struct itimerval *itval)
260 sigset_t old_sigmask;
262 if (itval->it_value.tv_usec < 100 && itval->it_value.tv_sec == 0)
264 (void)sigemptyset(&sigmask); /* Empty mask */
265 (void)sigaddset(&sigmask, SIGALRM); /* Add SIGALRM */
266 (void)sigprocmask(SIG_BLOCK, &sigmask, &old_sigmask); /* Block SIGALRM */
267 (void)setitimer(ITIMER_REAL, itval, NULL); /* Start timer */
268 (void)sigfillset(&sigmask); /* All signals */
269 (void)sigdelset(&sigmask, SIGALRM); /* Remove SIGALRM */
270 (void)sigsuspend(&sigmask); /* Until SIGALRM */
271 (void)sigprocmask(SIG_SETMASK, &old_sigmask, NULL); /* Restore mask */
277 struct itimerval itval;
278 itval.it_interval.tv_sec = 0;
279 itval.it_interval.tv_usec = 0;
280 itval.it_value.tv_sec = msec/1000;
281 itval.it_value.tv_usec = (msec % 1000) * 1000;
286 /*************************************************
287 * Scan file for RRs *
288 *************************************************/
290 /* This function scans an open "zone file" for appropriate records, and adds
291 any that are found to the output buffer.
295 zone the current zone name
296 domain the domain we are looking for
297 qtype the type of RR we want
298 qtypelen the length of qtype
299 pkptr points to the output buffer pointer; this is updated
300 countptr points to the record count; this is updated
301 dnssec points to the AD flag indicator; this updated
302 aa points to the AA flag indicator; this updated
304 Returns: 0 on success, else HOST_NOT_FOUND or NO_DATA or NO_RECOVERY or
305 PASS_ON - the latter if a "PASS ON NOT FOUND" line is seen
309 find_records(FILE *f, uschar *zone, uschar *domain, uschar *qtype,
310 int qtypelen, uschar **pkptr, int *countptr, BOOL * dnssec, BOOL * aa)
312 int yield = HOST_NOT_FOUND;
313 int domainlen = Ustrlen(domain);
314 BOOL pass_on_not_found = FALSE;
318 uschar rrdomain[256];
319 uschar RRdomain[256];
321 /* Decode the required type */
323 for (typeptr = type_list; typeptr->name != NULL; typeptr++)
324 { if (Ustrcmp(typeptr->name, qtype) == 0) break; }
325 if (typeptr->name == NULL)
327 fprintf(stderr, "fakens: unknown record type %s\n", qtype);
331 rrdomain[0] = 0; /* No previous domain */
332 (void)fseek(f, 0, SEEK_SET); /* Start again at the beginning */
334 *dnssec = TRUE; /* cancelled by first nonsecure rec found */
335 *aa = TRUE; /* cancelled by first non-authoritive record */
339 while (fgets(CS buffer, sizeof(buffer), f) != NULL)
343 BOOL found_cname = FALSE;
345 int tvalue = typeptr->value;
346 int qtlen = qtypelen;
352 while (isspace(*p)) p++;
353 if (*p == 0 || *p == ';') continue;
355 if (Ustrncmp(p, US"PASS ON NOT FOUND", 17) == 0)
357 pass_on_not_found = TRUE;
361 ep = buffer + Ustrlen(buffer);
362 while (isspace(ep[-1])) ep--;
368 if (Ustrncmp(p, US"DNSSEC ", 7) == 0) /* tagged as secure */
373 else if (Ustrncmp(p, US"AA ", 3) == 0) /* tagged as authoritive */
378 else if (Ustrncmp(p, US"DELAY=", 6) == 0) /* delay before response */
380 for (p += 6; *p >= '0' && *p <= '9'; p++) delay = delay*10 + *p - '0';
381 while (isspace(*p)) p++;
389 uschar *pp = rrdomain;
390 uschar *PP = RRdomain;
408 /* Compare domain names; first check for a wildcard */
410 if (rrdomain[0] == '*')
412 int restlen = Ustrlen(rrdomain) - 1;
413 if (domainlen > restlen &&
414 Ustrcmp(domain + domainlen - restlen, rrdomain + 1) != 0) continue;
417 /* Not a wildcard RR */
419 else if (Ustrcmp(domain, rrdomain) != 0) continue;
421 /* The domain matches */
423 if (yield == HOST_NOT_FOUND) yield = NO_DATA;
425 /* Compare RR types; a CNAME record is always returned */
427 while (isspace(*p)) p++;
429 if (Ustrncmp(p, "CNAME", 5) == 0)
435 else if (Ustrncmp(p, qtype, qtypelen) != 0 || !isspace(p[qtypelen])) continue;
437 /* Found a relevant record */
443 *dnssec = FALSE; /* cancel AD return */
446 *aa = FALSE; /* cancel AA return */
449 *countptr = *countptr + 1;
452 while (isspace(*p)) p++;
454 /* For a wildcard record, use the search name; otherwise use the record's
455 name in its original case because it might contain upper case letters. */
457 pk = packname((rrdomain[0] == '*')? domain : RRdomain, pk);
458 *pk++ = (tvalue >> 8) & 255;
459 *pk++ = (tvalue) & 255;
461 *pk++ = 1; /* class = IN */
463 pk += 4; /* TTL field; don't care */
465 rdlptr = pk; /* remember rdlength field */
468 /* The rest of the data depends on the type */
475 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
476 pk = packname(p, pk); /* primary ns */
477 p = strtok(NULL, " ");
478 pk = packname(p , pk); /* responsible mailbox */
479 *(p += strlen(p)) = ' ';
480 while (isspace(*p)) p++;
481 pk = longfield(&p, pk); /* serial */
482 pk = longfield(&p, pk); /* refresh */
483 pk = longfield(&p, pk); /* retry */
484 pk = longfield(&p, pk); /* expire */
485 pk = longfield(&p, pk); /* minimum */
489 for (i = 0; i < 4; i++)
492 while (isdigit(*p)) value = value*10 + *p++ - '0';
498 /* The only occurrence of a double colon is for ::1 */
500 if (Ustrcmp(p, "::1") == 0)
506 else for (i = 0; i < 8; i++)
511 value = value * 16 + toupper(*p) - (isdigit(*p)? '0' : '7');
514 *pk++ = (value >> 8) & 255;
521 pk = shortfield(&p, pk);
522 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
523 pk = packname(p, pk);
528 if (*p == '"') p++; /* Should always be the case */
529 while (*p != 0 && *p != '"') *pk++ = *p++;
534 pk = bytefield(&p, pk); /* usage */
535 pk = bytefield(&p, pk); /* selector */
536 pk = bytefield(&p, pk); /* match type */
539 value = toupper(*p) - (isdigit(*p) ? '0' : '7') << 4;
542 value |= toupper(*p) - (isdigit(*p) ? '0' : '7');
551 for (i = 0; i < 3; i++)
554 while (isdigit(*p)) value = value*10 + *p++ - '0';
555 while (isspace(*p)) p++;
556 *pk++ = (value >> 8) & 255;
565 if (ep[-1] != '.') sprintf(CS ep, "%s.", zone);
566 pk = packname(p, pk);
570 /* Fill in the length, and we are done with this RR */
572 rdlptr[0] = ((pk - rdlptr - 2) >> 8) & 255;
573 rdlptr[1] = (pk -rdlptr - 2) & 255;
577 return (yield == HOST_NOT_FOUND && pass_on_not_found)? PASS_ON : yield;
586 /*************************************************
587 * Entry point and main program *
588 *************************************************/
591 main(int argc, char **argv)
595 int domlen, qtypelen;
601 uschar *qualify = NULL;
603 uschar *zonefile = NULL;
607 uschar packet[2048 * 32 + 32];
612 signal(SIGALRM, alarmfn);
616 fprintf(stderr, "fakens: expected 3 arguments, received %d\n", argc-1);
622 (void)sprintf(CS buffer, "%s/dnszones", argv[1]);
624 d = opendir(CCS buffer);
627 fprintf(stderr, "fakens: failed to opendir %s: %s\n", buffer,
632 while ((de = readdir(d)) != NULL)
634 uschar *name = US de->d_name;
635 if (Ustrncmp(name, "qualify.", 8) == 0)
637 qualify = fcopystring(US "%s", name + 7);
640 if (Ustrncmp(name, "db.", 3) != 0) continue;
641 if (Ustrncmp(name + 3, "ip4.", 4) == 0)
642 zones[zonecount].zone = fcopystring(US "%s.in-addr.arpa", name + 6);
643 else if (Ustrncmp(name + 3, "ip6.", 4) == 0)
644 zones[zonecount].zone = fcopystring(US "%s.ip6.arpa", name + 6);
646 zones[zonecount].zone = fcopystring(US "%s", name + 2);
647 zones[zonecount++].zonefile = fcopystring(US "%s", name);
651 /* Get the RR type and upper case it, and check that we recognize it. */
653 Ustrncpy(qtype, argv[3], sizeof(qtype));
654 qtypelen = Ustrlen(qtype);
655 for (p = qtype; *p != 0; p++) *p = toupper(*p);
657 /* Find the domain, lower case it, deal with any specials,
658 check that it is in a zone that we handle,
659 and set up the zone file name. The zone names in the table all start with a
662 domlen = Ustrlen(argv[2]);
663 if (argv[2][domlen-1] == '.') domlen--;
664 Ustrncpy(domain, argv[2], domlen);
666 for (i = 0; i < domlen; i++) domain[i] = tolower(domain[i]);
668 if (Ustrcmp(domain, "manyhome.test.ex") == 0 && Ustrcmp(qtype, "A") == 0)
670 uschar *pk = packet + 12;
674 memset(packet, 0, 12);
676 for (i = 104; i <= 111; i++) for (j = 0; j <= 255; j++)
678 pk = packname(domain, pk);
679 *pk++ = (ns_t_a >> 8) & 255;
680 *pk++ = (ns_t_a) & 255;
682 *pk++ = 1; /* class = IN */
683 pk += 4; /* TTL field; don't care */
684 rdlptr = pk; /* remember rdlength field */
687 *pk++ = 10; *pk++ = 250; *pk++ = i; *pk++ = j;
689 rdlptr[0] = ((pk - rdlptr - 2) >> 8) & 255;
690 rdlptr[1] = (pk - rdlptr - 2) & 255;
693 packet[6] = (2048 >> 8) & 255;
694 packet[7] = 2048 & 255;
698 (void)fwrite(packet, 1, pk - packet, stdout);
703 if (Ustrchr(domain, '.') == NULL && qualify != NULL &&
704 Ustrcmp(domain, "dontqualify") != 0)
706 Ustrcat(domain, qualify);
707 domlen += Ustrlen(qualify);
710 for (i = 0; i < zonecount; i++)
713 zone = zones[i].zone;
714 zlen = Ustrlen(zone);
715 if (Ustrcmp(domain, zone+1) == 0 || (domlen >= zlen &&
716 Ustrcmp(domain + domlen - zlen, zone) == 0))
718 zonefile = zones[i].zonefile;
723 if (zonefile == NULL)
725 fprintf(stderr, "fakens: query not in faked zone: domain is: %s\n", domain);
729 (void)sprintf(CS buffer, "%s/dnszones/%s", argv[1], zonefile);
731 /* Initialize the start of the response packet. We don't have to fake up
732 everything, because we know that Exim will look only at the answer and
733 additional section parts. */
735 memset(packet, 0, 12);
738 /* Open the zone file. */
740 f = fopen(CS buffer, "r");
743 fprintf(stderr, "fakens: failed to open %s: %s\n", buffer, strerror(errno));
747 /* Find the records we want, and add them to the result. */
750 yield = find_records(f, zone, domain, qtype, qtypelen, &pk, &count, &dnssec, &aa);
751 if (yield == NO_RECOVERY) goto END_OFF;
753 packet[6] = (count >> 8) & 255;
754 packet[7] = count & 255;
756 /* There is no need to return any additional records because Exim no longer
757 (from release 4.61) makes any use of them. */
763 ((HEADER *)packet)->ad = 1;
766 ((HEADER *)packet)->aa = 1;
768 /* Close the zone file, write the result, and return. */
772 (void)fwrite(packet, 1, pk - packet, stdout);
776 /* vi: aw ai sw=2 sts=2 ts=8 et
778 /* End of fakens.c */