diff options
author | Philip Hazel <ph10@hermes.cam.ac.uk> | 2006-02-06 16:22:56 +0000 |
---|---|---|
committer | Philip Hazel <ph10@hermes.cam.ac.uk> | 2006-02-06 16:22:56 +0000 |
commit | c55a77db55ebf46a399f136eeb3a928b1e862772 (patch) | |
tree | c732f478642b0066b751b1034e195a200b43f5c2 /test/src/fakens.c | |
parent | 151b83f867487080e8f0e5cd6179e857dc6b3ccb (diff) |
CVSing the test suite.
Diffstat (limited to 'test/src/fakens.c')
-rw-r--r-- | test/src/fakens.c | 588 |
1 files changed, 588 insertions, 0 deletions
diff --git a/test/src/fakens.c b/test/src/fakens.c new file mode 100644 index 000000000..9ab7b7e2d --- /dev/null +++ b/test/src/fakens.c @@ -0,0 +1,588 @@ +/* $Cambridge: exim/test/src/fakens.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */ + +/************************************************* +* fakens - A Fake Nameserver Program * +*************************************************/ + +/* This program exists to support the testing of DNS handling code in Exim. It +avoids the need to install special zones in a real nameserver. When Exim is +running in its (new) test harness, DNS lookups are first passed to this program +instead of to the real resolver. (With a few exceptions - see the discussion in +the test suite's README file.) The program is also passed the name of the Exim +spool directory; it expects to find its "zone files" in ../dnszones relative to +that directory. Note that there is little checking in this program. The fake +zone files are assumed to be syntactically valid. + +The zones that are handled are found by scanning the dnszones directory. A file +whose name is of the form db.ip4.x is a zone file for .x.in-addr.arpa; a file +whose name is of the form db.ip6.x is a zone file for .x.ip6.arpa; a file of +the form db.anything.else is a zone file for .anything.else. A file of the form +qualify.x.y specifies the domain that is used to qualify single-component +names, except for the name "dontqualify". + +The arguments to the program are: + + the name of the Exim spool directory + the domain name that is being sought + the DNS record type that is being sought + +The output from the program is written to stdout. It is supposed to be in +exactly the same format as a traditional namserver response (see RFC 1035) so +that Exim can process it as normal. At present, no compression is used. +Error messages are written to stderr. + +The return codes from the program are zero for success, and otherwise the +values that are set in h_errno after a failing call to the normal resolver: + + 1 HOST_NOT_FOUND host not found (authoritative) + 2 TRY_AGAIN server failure + 3 NO_RECOVERY non-recoverable error + 4 NO_DATA valid name, no data of requested type + +In a real nameserver, TRY_AGAIN is also used for a non-authoritative not found, +but it is not used for that here. There is also one extra return code: + + 5 PASS_ON requests Exim to call res_search() + +This is used for zones that fakens does not recognize. It is also used if a +line in the zone file contains exactly this: + + PASS ON NOT FOUND + +and the domain is not found. It converts the the result to PASS_ON instead of +HOST_NOT_FOUND. */ + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <netdb.h> +#include <errno.h> +#include <arpa/nameser.h> +#include <sys/types.h> +#include <dirent.h> + +#define FALSE 0 +#define TRUE 1 +#define PASS_ON 5 + +typedef int BOOL; +typedef unsigned char uschar; + +#define CS (char *) +#define CCS (const char *) +#define US (unsigned char *) + +#define Ustrcat(s,t) strcat(CS(s),CCS(t)) +#define Ustrchr(s,n) US strchr(CCS(s),n) +#define Ustrcmp(s,t) strcmp(CCS(s),CCS(t)) +#define Ustrcpy(s,t) strcpy(CS(s),CCS(t)) +#define Ustrlen(s) (int)strlen(CCS(s)) +#define Ustrncmp(s,t,n) strncmp(CCS(s),CCS(t),n) +#define Ustrncpy(s,t,n) strncpy(CS(s),CCS(t),n) + + +typedef struct adomainstr { + struct adomainstr *next; + uschar name[1]; +} adomainstr; + +typedef struct zoneitem { + uschar *zone; + uschar *zonefile; +} zoneitem; + +typedef struct tlist { + uschar *name; + int value; +} tlist; + +/* On some (older?) operating systems, the standard ns_t_xxx definitions are +not available, and only the older T_xxx ones exist in nameser.h. If ns_t_a is +not defined, assume we are in this state. A really old system might not even +know about AAAA and SRV at all. */ + +#ifndef ns_t_a +#define ns_t_a T_A +#define ns_t_ns T_NS +#define ns_t_cname T_CNAME +#define ns_t_soa T_SOA +#define ns_t_ptr T_PTR +#define ns_t_mx T_MX +#define ns_t_txt T_TXT +#define ns_t_aaaa T_AAAA +#define ns_t_srv T_SRV +#ifndef T_AAAA +#define T_AAAA 28 +#endif +#ifndef T_SRV +#define T_SRV 33 +#endif +#endif + +static tlist type_list[] = { + { US"A", ns_t_a }, + { US"NS", ns_t_ns }, + { US"CNAME", ns_t_cname }, +/* { US"SOA", ns_t_soa }, Not currently in use */ + { US"PTR", ns_t_ptr }, + { US"MX", ns_t_mx }, + { US"TXT", ns_t_txt }, + { US"AAAA", ns_t_aaaa }, + { US"SRV", ns_t_srv }, + { NULL, 0 } +}; + + + +/************************************************* +* Get memory and sprintf into it * +*************************************************/ + +/* This is used when building a table of zones and their files. + +Arguments: + format a format string + ... arguments + +Returns: pointer to formatted string +*/ + +static uschar * +fcopystring(uschar *format, ...) +{ +uschar *yield; +char buffer[256]; +va_list ap; +va_start(ap, format); +vsprintf(buffer, format, ap); +va_end(ap); +yield = (uschar *)malloc(Ustrlen(buffer) + 1); +Ustrcpy(yield, buffer); +return yield; +} + + +/************************************************* +* Pack name into memory * +*************************************************/ + +/* This function packs a domain name into memory according to DNS rules. At +present, it doesn't do any compression. + +Arguments: + name the name + pk where to put it + +Returns: the updated value of pk +*/ + +static uschar * +packname(uschar *name, uschar *pk) +{ +while (*name != 0) + { + uschar *p = name; + while (*p != 0 && *p != '.') p++; + *pk++ = (p - name); + memmove(pk, name, p - name); + pk += p - name; + name = (*p == 0)? p : p + 1; + } +*pk++ = 0; +return pk; +} + + + +/************************************************* +* Scan file for RRs * +*************************************************/ + +/* This function scans an open "zone file" for appropriate records, and adds +any that are found to the output buffer. + +Arguments: + f the input FILE + zone the current zone name + domain the domain we are looking for + qtype the type of RR we want + qtypelen the length of qtype + pkptr points to the output buffer pointer; this is updated + countptr points to the record count; this is updated + adomainptr points to where to hang additional domains + +Returns: 0 on success, else HOST_NOT_FOUND or NO_DATA or NO_RECOVERY or + PASS_ON - the latter if a "PASS ON NOT FOUND" line is seen +*/ + +static int +find_records(FILE *f, uschar *zone, uschar *domain, uschar *qtype, + int qtypelen, uschar **pkptr, int *countptr, adomainstr **adomainptr) +{ +int yield = HOST_NOT_FOUND; +int zonelen = Ustrlen(zone); +int domainlen = Ustrlen(domain); +BOOL pass_on_not_found = FALSE; +tlist *typeptr; +uschar *pk = *pkptr; +uschar buffer[256]; +uschar rrdomain[256]; + +/* Decode the required type */ + +for (typeptr = type_list; typeptr->name != NULL; typeptr++) + { if (Ustrcmp(typeptr->name, qtype) == 0) break; } +if (typeptr->name == NULL) + { + fprintf(stderr, "fakens: unknown record type %s\n", qtype); + return NO_RECOVERY; + } + +rrdomain[0] = 0; /* No previous domain */ +(void)fseek(f, 0, SEEK_SET); /* Start again at the beginning */ + +/* Scan for RRs */ + +while (fgets(CS buffer, sizeof(buffer), f) != NULL) + { + uschar *rdlptr; + uschar *p, *ep, *pp; + BOOL found_cname = FALSE; + int i, plen, value; + int tvalue = typeptr->value; + int qtlen = qtypelen; + + p = buffer; + while (isspace(*p)) p++; + if (*p == 0 || *p == ';') continue; + + if (Ustrncmp(p, "PASS ON NOT FOUND", 17) == 0) + { + pass_on_not_found = TRUE; + continue; + } + + ep = buffer + Ustrlen(buffer); + while (isspace(ep[-1])) ep--; + *ep = 0; + + p = buffer; + if (!isspace(*p)) + { + uschar *pp = rrdomain; + while (!isspace(*p)) *pp++ = tolower(*p++); + if (pp[-1] != '.') Ustrcpy(pp, zone); else pp[-1] = 0; + } + + /* Compare domain names; first check for a wildcard */ + + if (rrdomain[0] == '*') + { + int restlen = Ustrlen(rrdomain) - 1; + if (domainlen > restlen && + Ustrcmp(domain + domainlen - restlen, rrdomain + 1) != 0) continue; + } + + /* Not a wildcard RR */ + + else if (Ustrcmp(domain, rrdomain) != 0) continue; + + /* The domain matches */ + + if (yield == HOST_NOT_FOUND) yield = NO_DATA; + + /* Compare RR types; a CNAME record is always returned */ + + while (isspace(*p)) p++; + + if (Ustrncmp(p, "CNAME", 5) == 0) + { + tvalue = ns_t_cname; + qtlen = 5; + found_cname = TRUE; + } + else if (Ustrncmp(p, qtype, qtypelen) != 0 || !isspace(p[qtypelen])) continue; + + /* Found a relevant record */ + + yield = 0; + *countptr = *countptr + 1; + + p += qtlen; + while (isspace(*p)) p++; + + pk = packname(domain, pk); /* Not rrdomain because of wildcard */ + *pk++ = (tvalue >> 8) & 255; + *pk++ = (tvalue) & 255; + *pk++ = 0; + *pk++ = 1; /* class = IN */ + + pk += 4; /* TTL field; don't care */ + + rdlptr = pk; /* remember rdlength field */ + pk += 2; + + /* The rest of the data depends on the type */ + + switch (tvalue) + { + case ns_t_soa: /* Not currently used */ + break; + + case ns_t_a: + for (i = 0; i < 4; i++) + { + value = 0; + while (isdigit(*p)) value = value*10 + *p++ - '0'; + *pk++ = value; + p++; + } + break; + + /* The only occurrence of a double colon is for ::1 */ + case ns_t_aaaa: + if (Ustrcmp(p, "::1") == 0) + { + memset(pk, 0, 15); + pk += 15; + *pk++ = 1; + } + else for (i = 0; i < 8; i++) + { + value = 0; + while (isxdigit(*p)) + { + value = value * 16 + toupper(*p) - (isdigit(*p)? '0' : '7'); + p++; + } + *pk++ = (value >> 8) & 255; + *pk++ = value & 255; + p++; + } + break; + + case ns_t_mx: + value = 0; + while (isdigit(*p)) value = value*10 + *p++ - '0'; + while (isspace(*p)) p++; + *pk++ = (value >> 8) & 255; + *pk++ = value & 255; + goto PACKNAME; + + case ns_t_txt: + pp = pk++; + if (*p == '"') p++; /* Should always be the case */ + while (*p != 0 && *p != '"') *pk++ = *p++; + *pp = pk - pp - 1; + break; + + case ns_t_srv: + for (i = 0; i < 3; i++) + { + value = 0; + while (isdigit(*p)) value = value*10 + *p++ - '0'; + while (isspace(*p)) p++; + *pk++ = (value >> 8) & 255; + *pk++ = value & 255; + } + + /* Fall through */ + + case ns_t_cname: + case ns_t_ns: + case ns_t_ptr: + PACKNAME: + if (ep[-1] != '.') sprintf(ep, "%s.", zone); + pk = packname(p, pk); + plen = Ustrlen(p); + if (adomainptr != NULL && plen > zonelen + 2 && + Ustrncmp(p + plen - zonelen - 1, zone, zonelen) == 0) + { + adomainstr *adomain = (adomainstr *)malloc(sizeof(adomainstr) + plen); + *adomainptr = adomain; + adomainptr = &(adomain->next); + adomain->next = NULL; + Ustrncpy(adomain->name, p, plen - 1); + adomain->name[plen-1] = 0; + } + break; + } + + /* Fill in the length, and we are done with this RR */ + + rdlptr[0] = ((pk - rdlptr - 2) >> 8) & 255; + rdlptr[1] = (pk -rdlptr - 2) & 255; + + /* If we have just yielded a CNAME, we must change the domain name to the + new domain, and re-start the scan from the beginning. */ + + if (found_cname) + { + domain = fcopystring("%s", p); + domainlen = Ustrlen(domain); + domain[domainlen - 1] = 0; /* Removed trailing dot */ + rrdomain[0] = 0; /* No previous domain */ + (void)fseek(f, 0, SEEK_SET); /* Start again at the beginning */ + } + } + +*pkptr = pk; +return (yield == HOST_NOT_FOUND && pass_on_not_found)? PASS_ON : yield; +} + + + +/************************************************* +* Entry point and main program * +*************************************************/ + +int +main(int argc, char **argv) +{ +FILE *f; +DIR *d; +int dirlen, domlen, qtypelen; +int yield, count; +int i; +int zonecount = 0; +tlist *typeptr; +struct dirent *de; +adomainstr *adomain = NULL; +zoneitem zones[32]; +uschar *qualify = NULL; +uschar *p, *zone; +uschar *zonefile = NULL; +uschar domain[256]; +uschar buffer[256]; +uschar qtype[12]; +uschar packet[512]; +uschar *pk = packet; + +if (argc != 4) + { + fprintf(stderr, "fakens: expected 3 arguments, received %d\n", argc-1); + return NO_RECOVERY; + } + +/* Find the zones */ + +(void)sprintf(buffer, "%s/../dnszones", argv[1]); + +d = opendir(CCS buffer); +if (d == NULL) + { + fprintf(stderr, "fakens: failed to opendir %s: %s\n", buffer, + strerror(errno)); + return NO_RECOVERY; + } + +while ((de = readdir(d)) != NULL) + { + uschar *name = de->d_name; + if (Ustrncmp(name, "qualify.", 8) == 0) + { + qualify = fcopystring("%s", name + 7); + continue; + } + if (Ustrncmp(name, "db.", 3) != 0) continue; + if (Ustrncmp(name + 3, "ip4.", 4) == 0) + zones[zonecount].zone = fcopystring("%s.in-addr.arpa", name + 6); + else if (Ustrncmp(name + 3, "ip6.", 4) == 0) + zones[zonecount].zone = fcopystring("%s.ip6.arpa", name + 6); + else + zones[zonecount].zone = fcopystring("%s", name + 2); + zones[zonecount++].zonefile = fcopystring("%s", name); + } +(void)closedir(d); + +/* Get the RR type and upper case it, and check that we recognize it. */ + +Ustrncpy(qtype, argv[3], sizeof(qtype)); +qtypelen = Ustrlen(qtype); +for (p = qtype; *p != 0; p++) *p = toupper(*p); + +/* Find the domain, lower case it, check that it is in a zone that we handle, +and set up the zone file name. The zone names in the table all start with a +dot. */ + +domlen = Ustrlen(argv[2]); +if (argv[2][domlen-1] == '.') domlen--; +Ustrncpy(domain, argv[2], domlen); +domain[domlen] = 0; +for (i = 0; i < domlen; i++) domain[i] = tolower(domain[i]); + +if (Ustrchr(domain, '.') == NULL && qualify != NULL && + Ustrcmp(domain, "dontqualify") != 0) + { + Ustrcat(domain, qualify); + domlen += Ustrlen(qualify); + } + +for (i = 0; i < zonecount; i++) + { + int zlen; + zone = zones[i].zone; + zlen = Ustrlen(zone); + if (Ustrcmp(domain, zone+1) == 0 || (domlen >= zlen && + Ustrcmp(domain + domlen - zlen, zone) == 0)) + { + zonefile = zones[i].zonefile; + break; + } + } + +if (zonefile == NULL) + { + fprintf(stderr, "fakens: query not in faked zone: domain is: %s\n", domain); + return PASS_ON; + } + +(void)sprintf(buffer, "%s/../dnszones/%s", argv[1], zonefile); + +/* Initialize the start of the response packet. We don't have to fake up +everything, because we know that Exim will look only at the answer and +additional section parts. */ + +memset(packet, 0, 12); +pk += 12; + +/* Open the zone file. */ + +f = fopen(buffer, "r"); +if (f == NULL) + { + fprintf(stderr, "fakens: failed to open %s: %s\n", buffer, strerror(errno)); + return NO_RECOVERY; + } + +/* Find the records we want, and add them to the result. */ + +count = 0; +yield = find_records(f, zone, domain, qtype, qtypelen, &pk, &count, &adomain); +if (yield == NO_RECOVERY) goto END_OFF; + +packet[6] = (count >> 8) & 255; +packet[7] = count & 255; + +/* Search for additional records and add them to the result. */ + +count = 0; +for (; adomain != NULL; adomain = adomain->next) + { + (void)find_records(f, zone, adomain->name, US"AAAA", 4, &pk, &count, NULL); + (void)find_records(f, zone, adomain->name, US"A", 1, &pk, &count, NULL); + } + +packet[10] = (count >> 8) & 255; +packet[11] = count & 255; + +/* Close the zone file, write the result, and return. */ + +END_OFF: +(void)fclose(f); +(void)fwrite(packet, 1, pk - packet, stdout); +return yield; +} + +/* End of fakens.c */ |