Taint: reject or log more tainted list metadata elements
[exim.git] / test / src / fakens.c
index 33bfe4f3e91aa07c216fac7eb66ba2b799e58f3e..3d6f513fc8e878117977418b23755a410d0f8066 100644 (file)
@@ -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 <ctype.h>
 #include <stdarg.h>
@@ -66,9 +79,14 @@ as such then the response will have the "AD" bit set. */
 #include <errno.h>
 #include <signal.h>
 #include <arpa/nameser.h>
+#include <arpa/inet.h>
 #include <sys/types.h>
 #include <sys/time.h>
 #include <dirent.h>
+#include <unistd.h>
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#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 */