X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/5f3d09836c17cb80a1953196c98371ed70396903..9bf74b9c298f6968417fe88a68ccdf2179db1403:/test/src/fakens.c?ds=sidebyside diff --git a/test/src/fakens.c b/test/src/fakens.c index 33bfe4f3e..3d6f513fc 100644 --- a/test/src/fakens.c +++ b/test/src/fakens.c @@ -25,7 +25,7 @@ The arguments to the program are: 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 +exactly the same format as a traditional nameserver response (see RFC 1035) so that Exim can process it as normal. At present, no compression is used. Error messages are written to stderr. @@ -51,11 +51,24 @@ and the domain is not found. It converts the the result to PASS_ON instead of HOST_NOT_FOUND. Any DNS record line in a zone file can be prefixed with "DELAY=" and -a number of milliseconds (followed by whitespace). +a number of milliseconds (followed by one space). -Any DNS record line in a zone file can be prefixed with "DNSSEC" and -at least one space; if all the records found by a lookup are marked -as such then the response will have the "AD" bit set. */ +Any DNS record line can be prefixed with "DNSSEC "; +if all the records found by a lookup are marked +as such then the response will have the "AD" bit set. + +Any DNS record line can be prefixed with "NXDOMAIN "; +The record will be ignored (but the prefix set still applied); +This lets us return a DNSSEC NXDOMAIN (=> HOST_NOT_FOUND). + +Any DNS record line can be prefixed with "AA " +if all the records found by a lookup are marked +as such then the response will have the "AA" bit set. + +Any DNS record line in a zone file can be prefixed with "TTL=" and +a number of seconds (followed by one space). + +*/ #include #include @@ -66,9 +79,14 @@ as such then the response will have the "AD" bit set. */ #include #include #include +#include #include #include #include +#include +#ifdef HAVE_SYS_SOCKET_H +#include +#endif #define FALSE 0 #define TRUE 1 @@ -88,6 +106,7 @@ typedef unsigned char uschar; #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) +#define Ustrtok(s,t) (uschar*)strtok(CS(s),CCS(t)) typedef struct zoneitem { uschar *zone; @@ -99,6 +118,8 @@ typedef struct tlist { int value; } tlist; +#define DEFAULT_TTL 3600U + /* 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 @@ -292,6 +313,8 @@ Arguments: qtypelen the length of qtype pkptr points to the output buffer pointer; this is updated countptr points to the record count; this is updated + dnssec_p points to the AD flag indicator; this is updated + aa_p points to the AA flag indicator; this is updated 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 @@ -299,19 +322,19 @@ Returns: 0 on success, else HOST_NOT_FOUND or NO_DATA or NO_RECOVERY or static int find_records(FILE *f, uschar *zone, uschar *domain, uschar *qtype, - int qtypelen, uschar **pkptr, int *countptr, BOOL * dnssec) + int qtypelen, uschar **pkptr, int *countptr, BOOL * dnssec_p, BOOL * aa_p) { int yield = HOST_NOT_FOUND; int domainlen = Ustrlen(domain); BOOL pass_on_not_found = FALSE; tlist *typeptr; uschar *pk = *pkptr; -uschar buffer[256]; +uschar buffer[512]; uschar rrdomain[256]; uschar RRdomain[256]; -/* Decode the required type */ +/* Decode the required type */ for (typeptr = type_list; typeptr->name != NULL; typeptr++) { if (Ustrcmp(typeptr->name, qtype) == 0) break; } if (typeptr->name == NULL) @@ -323,8 +346,6 @@ if (typeptr->name == NULL) rrdomain[0] = 0; /* No previous domain */ (void)fseek(f, 0, SEEK_SET); /* Start again at the beginning */ -*dnssec = TRUE; /* cancelled by first nonsecure rec found */ - /* Scan for RRs */ while (fgets(CS buffer, sizeof(buffer), f) != NULL) @@ -336,7 +357,10 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) int tvalue = typeptr->value; int qtlen = qtypelen; BOOL rr_sec = FALSE; + BOOL rr_aa = FALSE; + BOOL rr_ignore = FALSE; int delay = 0; + uint ttl = DEFAULT_TTL; p = buffer; while (isspace(*p)) p++; @@ -354,23 +378,38 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) p = buffer; for (;;) - { - if (Ustrncmp(p, US"DNSSEC ", 7) == 0) /* tagged as secure */ - { - rr_sec = TRUE; - p += 7; - } - else if (Ustrncmp(p, US"DELAY=", 6) == 0) /* delay beforee response */ - { - for (p += 6; *p >= '0' && *p <= '9'; p++) - delay = delay*10 + *p - '0'; - while (isspace(*p)) p++; - } - else - break; - } - - if (!isspace(*p)) + { + if (Ustrncmp(p, US"DNSSEC ", 7) == 0) /* tagged as secure */ + { + rr_sec = TRUE; + p += 7; + } + if (Ustrncmp(p, US"NXDOMAIN ", 9) == 0) /* ignore record content */ + { + rr_ignore = TRUE; + p += 9; + } + else if (Ustrncmp(p, US"AA ", 3) == 0) /* tagged as authoritative */ + { + rr_aa = TRUE; + p += 3; + } + else if (Ustrncmp(p, US"DELAY=", 6) == 0) /* delay before response */ + { + for (p += 6; *p >= '0' && *p <= '9'; p++) delay = delay*10 + *p - '0'; + if (isspace(*p)) p++; + } + else if (Ustrncmp(p, US"TTL=", 4) == 0) /* TTL for record */ + { + ttl = 0; + for (p += 4; *p >= '0' && *p <= '9'; p++) ttl = ttl*10 + *p - '0'; + if (isspace(*p)) p++; + } + else + break; + } + + if (!isspace(*p)) /* new domain name */ { uschar *pp = rrdomain; uschar *PP = RRdomain; @@ -389,7 +428,7 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) pp[-1] = 0; PP[-1] = 0; } - } + } /* else use previous line's domain name */ /* Compare domain names; first check for a wildcard */ @@ -406,7 +445,12 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) /* The domain matches */ - if (yield == HOST_NOT_FOUND) yield = NO_DATA; + if (yield == HOST_NOT_FOUND) + { + yield = NO_DATA; + if (dnssec_p) *dnssec_p = TRUE; /* cancelled by first nonsecure rec found */ + if (aa_p) *aa_p = TRUE; /* cancelled by first non-aa rec found */ + } /* Compare RR types; a CNAME record is always returned */ @@ -421,12 +465,16 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) else if (Ustrncmp(p, qtype, qtypelen) != 0 || !isspace(p[qtypelen])) continue; /* Found a relevant record */ - if (delay) millisleep(delay); - if (!rr_sec) - *dnssec = FALSE; /* cancel AD return */ + if (dnssec_p && !rr_sec) + *dnssec_p = FALSE; /* cancel AD return */ + + if (aa_p && !rr_aa) + *aa_p = FALSE; /* cancel AA return */ + + if (rr_ignore) continue; yield = 0; *countptr = *countptr + 1; @@ -443,7 +491,10 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) *pk++ = 0; *pk++ = 1; /* class = IN */ - pk += 4; /* TTL field; don't care */ + *pk++ = (ttl >>24) & 255; + *pk++ = (ttl >>16) & 255; + *pk++ = (ttl >> 8) & 255; + *pk++ = ttl & 255; rdlptr = pk; /* remember rdlength field */ pk += 2; @@ -453,51 +504,29 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) switch (tvalue) { case ns_t_soa: - p = strtok(p, " "); - ep = p + strlen(p); + p = Ustrtok(p, " "); + ep = p + Ustrlen(p); if (ep[-1] != '.') sprintf(CS ep, "%s.", zone); - pk = packname(p, pk); /* primary ns */ - p = strtok(NULL, " "); - pk = packname(p , pk); /* responsible mailbox */ - *(p += strlen(p)) = ' '; + pk = packname(p, pk); /* primary ns */ + p = Ustrtok(NULL, " "); + pk = packname(p , pk); /* responsible mailbox */ + *(p += Ustrlen(p)) = ' '; while (isspace(*p)) p++; - pk = longfield(&p, pk); /* serial */ - pk = longfield(&p, pk); /* refresh */ - pk = longfield(&p, pk); /* retry */ - pk = longfield(&p, pk); /* expire */ - pk = longfield(&p, pk); /* minimum */ + pk = longfield(&p, pk); /* serial */ + pk = longfield(&p, pk); /* refresh */ + pk = longfield(&p, pk); /* retry */ + pk = longfield(&p, pk); /* expire */ + pk = longfield(&p, pk); /* minimum */ break; case ns_t_a: - for (i = 0; i < 4; i++) - { - value = 0; - while (isdigit(*p)) value = value*10 + *p++ - '0'; - *pk++ = value; - p++; - } + inet_pton(AF_INET, CCS p, pk); /* FIXME: error checking */ + pk += 4; 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++; - } + inet_pton(AF_INET6, CCS p, pk); /* FIXME: error checking */ + pk += 16; break; case ns_t_mx: @@ -507,24 +536,29 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) break; case ns_t_txt: - pp = pk++; - if (*p == '"') p++; /* Should always be the case */ - while (*p != 0 && *p != '"') *pk++ = *p++; - *pp = pk - pp - 1; + while (*p) + { + pp = pk++; /* Skip the size byte in output */ + if (*p == '"') p++; /* Should always be the case */ + while (*p && *p != '"') *pk++ = *p++; + *pp = pk - pp - 1; /* fill in the size */ + p++; /* skip the closing doublequote */ + while (isspace(*p)) p++; /* next chunk start */ + } break; case ns_t_tlsa: - pk = bytefield(&p, pk); /* usage */ - pk = bytefield(&p, pk); /* selector */ - pk = bytefield(&p, pk); /* match type */ + pk = bytefield(&p, pk); /* usage */ + pk = bytefield(&p, pk); /* selector */ + pk = bytefield(&p, pk); /* match type */ while (isxdigit(*p)) { value = toupper(*p) - (isdigit(*p) ? '0' : '7') << 4; if (isxdigit(*++p)) - { - value |= toupper(*p) - (isdigit(*p) ? '0' : '7'); - p++; - } + { + value |= toupper(*p) - (isdigit(*p) ? '0' : '7'); + p++; + } *pk++ = value & 255; } @@ -532,13 +566,13 @@ while (fgets(CS buffer, sizeof(buffer), f) != NULL) 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; - } + { + value = 0; + while (isdigit(*p)) value = value*10 + *p++ - '0'; + while (isspace(*p)) p++; + *pk++ = (value >> 8) & 255; + *pk++ = value & 255; + } /* Fall through */ @@ -566,6 +600,55 @@ alarmfn(int sig) { } + +/************************************************* +* Special-purpose domains * +*************************************************/ + +static int +special_manyhome(uschar * packet, uschar * domain) +{ +uschar *pk = packet + 12; +uschar *rdlptr; +int i, j; + +memset(packet, 0, 12); + +for (i = 104; i <= 111; i++) for (j = 0; j <= 255; j++) + { + pk = packname(domain, pk); + *pk++ = (ns_t_a >> 8) & 255; + *pk++ = (ns_t_a) & 255; + *pk++ = 0; + *pk++ = 1; /* class = IN */ + pk += 4; /* TTL field; don't care */ + rdlptr = pk; /* remember rdlength field */ + pk += 2; + + *pk++ = 10; *pk++ = 250; *pk++ = i; *pk++ = j; + + rdlptr[0] = ((pk - rdlptr - 2) >> 8) & 255; + rdlptr[1] = (pk - rdlptr - 2) & 255; + } + +packet[6] = (2048 >> 8) & 255; +packet[7] = 2048 & 255; +packet[10] = 0; +packet[11] = 0; + +(void)fwrite(packet, 1, pk - packet, stdout); +return 0; +} + +static int +special_again(uschar * packet, uschar * domain) +{ +int delay = atoi(CCS domain); /* digits at the start of the name */ +if (delay > 0) sleep(delay); +return TRY_AGAIN; +} + + /************************************************* * Entry point and main program * *************************************************/ @@ -587,9 +670,11 @@ uschar *zonefile = NULL; uschar domain[256]; uschar buffer[256]; uschar qtype[12]; -uschar packet[512]; +uschar packet[2048 * 32 + 32]; +HEADER *header = (HEADER *)packet; uschar *pk = packet; -BOOL dnssec; +BOOL dnssec = FALSE; +BOOL aa = FALSE; signal(SIGALRM, alarmfn); @@ -603,15 +688,14 @@ if (argc != 4) (void)sprintf(CS buffer, "%s/dnszones", argv[1]); -d = opendir(CCS buffer); -if (d == NULL) +if (!(d = opendir(CCS buffer))) { fprintf(stderr, "fakens: failed to opendir %s: %s\n", buffer, strerror(errno)); return NO_RECOVERY; } -while ((de = readdir(d)) != NULL) +while ((de = readdir(d))) { uschar *name = US de->d_name; if (Ustrncmp(name, "qualify.", 8) == 0) @@ -636,7 +720,8 @@ 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, +/* Find the domain, lower case it, deal with any specials, +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. */ @@ -646,6 +731,14 @@ Ustrncpy(domain, argv[2], domlen); domain[domlen] = 0; for (i = 0; i < domlen; i++) domain[i] = tolower(domain[i]); +if (Ustrcmp(domain, "manyhome.test.ex") == 0 && Ustrcmp(qtype, "A") == 0) + return special_manyhome(packet, domain); +else if (domlen >= 14 && Ustrcmp(domain + domlen - 14, "test.again.dns") == 0) + return special_again(packet, domain); +else if (domlen >= 13 && Ustrcmp(domain + domlen - 13, "test.fail.dns") == 0) + return NO_RECOVERY; + + if (Ustrchr(domain, '.') == NULL && qualify != NULL && Ustrcmp(domain, "dontqualify") != 0) { @@ -674,39 +767,52 @@ if (zonefile == NULL) (void)sprintf(CS 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. */ +/* Initialize the start of the response packet. */ memset(packet, 0, 12); pk += 12; /* Open the zone file. */ -f = fopen(CS buffer, "r"); -if (f == NULL) +if (!(f = fopen(CS buffer, "r"))) { fprintf(stderr, "fakens: failed to open %s: %s\n", buffer, strerror(errno)); return NO_RECOVERY; } +header->qr = 1; /* query */ +header->opcode = QUERY; /* standard query */ +header->tc = 0; /* no truncation */ + /* Find the records we want, and add them to the result. */ count = 0; -yield = find_records(f, zone, domain, qtype, qtypelen, &pk, &count, &dnssec); +yield = find_records(f, zone, domain, qtype, qtypelen, &pk, &count, &dnssec, &aa); if (yield == NO_RECOVERY) goto END_OFF; +header->ancount = htons(count); -packet[6] = (count >> 8) & 255; -packet[7] = count & 255; +/* If the AA bit should be set (as indicated by the AA prefix in the zone file), +we are expected to return some records in the authoritative section. Bind9: If +there is data in the answer section, the authoritative section contains the NS +records, otherwise it contains the SOA record. Mimic that. +*/ + +if (strcmp(qtype, "SOA") != 0 && strcmp(qtype, "NS") != 0) + if (count) + find_records(f, zone, zone[0] == '.' ? zone+1 : zone, US"NS", 2, &pk, &count, NULL, NULL); + else + find_records(f, zone, zone[0] == '.' ? zone+1 : zone, US"SOA", 3, &pk, &count, NULL, NULL); +header->nscount = htons(count - ntohs(header->ancount)); /* There is no need to return any additional records because Exim no longer (from release 4.61) makes any use of them. */ - -packet[10] = 0; -packet[11] = 0; +header->arcount = 0; if (dnssec) - ((HEADER *)packet)->ad = 1; + header->ad = 1; + +if (aa) + header->aa = 1; /* Close the zone file, write the result, and return. */ @@ -716,6 +822,6 @@ END_OFF: return yield; } -/* vi: aw ai sw=2 +/* vi: aw ai sw=2 sts=2 ts=8 et */ /* End of fakens.c */