+/*************************************************
+* Fake DNS resolver *
+*************************************************/
+
+/* This function is called instead of res_search() when Exim is running in its
+test harness. It recognizes some special domain names, and uses them to force
+failure and retry responses (optionally with a delay). Otherwise, it calls an
+external utility that mocks-up a nameserver, if it can find the utility.
+If not, it passes its arguments on to res_search(). The fake nameserver may
+also return a code specifying that the name should be passed on.
+
+Background: the original test suite required a real nameserver to carry the
+test zones, whereas the new test suit has the fake server for portability. This
+code supports both.
+
+Arguments:
+ domain the domain name
+ type the DNS record type
+ answerptr where to put the answer
+ size size of the answer area
+
+Returns: length of returned data, or -1 on error (h_errno set)
+*/
+
+static int
+fakens_search(uschar *domain, int type, uschar *answerptr, int size)
+{
+int len = Ustrlen(domain);
+int asize = size; /* Locally modified */
+uschar *endname;
+uschar name[256];
+uschar utilname[256];
+uschar *aptr = answerptr; /* Locally modified */
+struct stat statbuf;
+
+/* Remove terminating dot. */
+
+if (domain[len - 1] == '.') len--;
+Ustrncpy(name, domain, len);
+name[len] = 0;
+endname = name + len;
+
+/* This code, for forcing TRY_AGAIN and NO_RECOVERY, is here so that it works
+for the old test suite that uses a real nameserver. When the old test suite is
+eventually abandoned, this code could be moved into the fakens utility. */
+
+if (len >= 14 && Ustrcmp(endname - 14, "test.again.dns") == 0)
+ {
+ int delay = Uatoi(name); /* digits at the start of the name */
+ DEBUG(D_dns) debug_printf("Return from DNS lookup of %s (%s) faked for testing\n",
+ name, dns_text_type(type));
+ if (delay > 0)
+ {
+ DEBUG(D_dns) debug_printf("delaying %d seconds\n", delay);
+ sleep(delay);
+ }
+ h_errno = TRY_AGAIN;
+ return -1;
+ }
+
+if (len >= 13 && Ustrcmp(endname - 13, "test.fail.dns") == 0)
+ {
+ DEBUG(D_dns) debug_printf("Return from DNS lookup of %s (%s) faked for testing\n",
+ name, dns_text_type(type));
+ h_errno = NO_RECOVERY;
+ return -1;
+ }
+
+/* Look for the fakens utility, and if it exists, call it. */
+
+(void)string_format(utilname, sizeof(utilname), "%s/../bin/fakens",
+ spool_directory);
+
+if (stat(CS utilname, &statbuf) >= 0)
+ {
+ pid_t pid;
+ int infd, outfd, rc;
+ uschar *argv[5];
+
+ DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) using fakens\n",
+ name, dns_text_type(type));
+
+ argv[0] = utilname;
+ argv[1] = spool_directory;
+ argv[2] = name;
+ argv[3] = dns_text_type(type);
+ argv[4] = NULL;
+
+ pid = child_open(argv, NULL, 0000, &infd, &outfd, FALSE);
+ if (pid < 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to run fakens: %s",
+ strerror(errno));
+
+ len = 0;
+ rc = -1;
+ while (asize > 0 && (rc = read(outfd, aptr, asize)) > 0)
+ {
+ len += rc;
+ aptr += rc; /* Don't modify the actual arguments, because they */
+ asize -= rc; /* may need to be passed on to res_search(). */
+ }
+
+ if (rc < 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "read from fakens failed: %s",
+ strerror(errno));
+
+ switch(child_close(pid, 0))
+ {
+ case 0: return len;
+ case 1: h_errno = HOST_NOT_FOUND; return -1;
+ case 2: h_errno = TRY_AGAIN; return -1;
+ default:
+ case 3: h_errno = NO_RECOVERY; return -1;
+ case 4: h_errno = NO_DATA; return -1;
+ case 5: /* Pass on to res_search() */
+ DEBUG(D_dns) debug_printf("fakens returned PASS_ON\n");
+ }
+ }
+
+/* fakens utility not found, or it returned "pass on" */
+
+DEBUG(D_dns) debug_printf("passing %s on to res_search()\n", domain);
+
+return res_search(CS domain, C_IN, type, answerptr, size);
+}
+
+