CVSing the test suite.
authorPhilip Hazel <ph10@hermes.cam.ac.uk>
Mon, 6 Feb 2006 16:22:56 +0000 (16:22 +0000)
committerPhilip Hazel <ph10@hermes.cam.ac.uk>
Mon, 6 Feb 2006 16:22:56 +0000 (16:22 +0000)
17 files changed:
test/dnszones-src/db.ip4.10 [new file with mode: 0644]
test/dnszones-src/db.ip4.127 [new file with mode: 0644]
test/dnszones-src/db.ip4.V4NET [new file with mode: 0644]
test/dnszones-src/db.ip6.0 [new file with mode: 0644]
test/dnszones-src/db.ip6.V6NET [new file with mode: 0644]
test/dnszones-src/db.test.ex [new file with mode: 0644]
test/dnszones-src/qualify.test.ex [new file with mode: 0644]
test/src/cf.c [new file with mode: 0644]
test/src/checkaccess.c [new file with mode: 0644]
test/src/client.c [new file with mode: 0644]
test/src/fakens.c [new file with mode: 0644]
test/src/fd.c [new file with mode: 0644]
test/src/iefbr14.c [new file with mode: 0644]
test/src/loaded.c [new file with mode: 0644]
test/src/mtpscript.c [new file with mode: 0644]
test/src/server.c [new file with mode: 0644]
test/src/showids.c [new file with mode: 0644]

diff --git a/test/dnszones-src/db.ip4.10 b/test/dnszones-src/db.ip4.10
new file mode 100644 (file)
index 0000000..cac14fc
--- /dev/null
@@ -0,0 +1,19 @@
+; $Cambridge: exim/test/dnszones-src/db.ip4.10,v 1.1 2006/02/06 16:22:56 ph10 Exp $
+
+; This is a testing reverse zone file for use when testing DNS handling in
+; Exim. This is a fake zone of no real use - hence no SOA record. The zone name
+; is 10.in-addr.arpa. This file is passed through the substitution mechanism
+; before being used by the fakens auxiliary program, though in fact there is
+; nothing to substitute.
+
+; This zone exists to handle reverse lookups for the host with a huge number of
+; IP addresses that get manufactured by the fake_gethostbyname() function in
+; Exim. They are hard-wired to use the 10.250.0.0/16 network. Apart from that
+; one use, the test suite shouldn't be using that network, so everything else
+; is passed on to res_search(). The next line triggers this action.
+
+PASS ON NOT FOUND
+
+*.250       PTR    manyhome.test.ex.
+
+; End
diff --git a/test/dnszones-src/db.ip4.127 b/test/dnszones-src/db.ip4.127
new file mode 100644 (file)
index 0000000..1501074
--- /dev/null
@@ -0,0 +1,10 @@
+; $Cambridge: exim/test/dnszones-src/db.ip4.127,v 1.1 2006/02/06 16:22:56 ph10 Exp $
+
+; This is a testing reverse zone file for use when testing DNS handling in
+; Exim. This is a fake zone of no real use - hence no SOA record. The zone name
+; is 127.in-addr.arpa. This file is passed through the substitution mechanism
+; before being used by the fakens auxiliary program.
+
+1.0.0.127.in-addr.arpa.   PTR    localhost.
+
+; End
diff --git a/test/dnszones-src/db.ip4.V4NET b/test/dnszones-src/db.ip4.V4NET
new file mode 100644 (file)
index 0000000..b7419f5
--- /dev/null
@@ -0,0 +1,60 @@
+; $Cambridge: exim/test/dnszones-src/db.ip4.V4NET,v 1.1 2006/02/06 16:22:56 ph10 Exp $
+
+; This is a testing reverse zone file for use when testing DNS handling in
+; Exim. This is a fake zone of no real use - hence no SOA record. The zone name
+; is determined by the V4NET substitution. This file is passed through the
+; substitution mechanism before being used by the fakens auxiliary program.
+
+1.0.0       PTR    ten-1.test.ex.
+2.0.0       PTR    ten-2.test.ex.
+3.0.0       PTR    ten-3-alias.test.ex.
+3.0.0       PTR    ten-3.test.ex.
+4.0.0       PTR    ten-4.test.ex.
+5.0.0       PTR    ten-5-6.test.ex.
+6.0.0       PTR    ten-5-6.test.ex.
+
+; V4NET.0.0.97 is deliberately not reverse registered
+
+98.0.0      PTR    noforward.test.ex.
+99.0.0      PTR    ten-99.TEST.EX.
+
+; V4NET.11.12.13 (black-1) is deliberately not reverse registered
+
+14.12.11    PTR    black-2.test.ex.
+
+10.10.10    PTR    myhost.test.ex.
+
+5.4.12      PTR    other1.test.ex.
+1.3.12      PTR    other2.test.ex.
+2.3.12      PTR    other2.test.ex.
+
+1.0.99      PTR    other99.test.ex.
+
+; The first of these deliberately points back to a different name
+; with a different address - that of the second one.
+
+99.99.99    PTR    badB.test.ex.
+98.99.99    PTR    badB.test.ex.
+
+; This host has multiple names with several components, for
+; testing negative wildcard matching
+
+97.99.99    PTR    x.gov.uk.test.ex.
+97.99.99    PTR    x.co.uk.test.ex.
+
+; This points to a name that will give `try again' when looked
+; up from within the test harness.
+
+96.99.99    PTR    x.test.again.dns.
+
+; This IP number points back to a legitimate host, but also to a name that
+; isn't forward registered
+
+90.99.99    PTR    oneback.test.ex.
+90.99.99    PTR    host1.masq.test.ex.
+
+; This is a duff registration for testing
+
+255.255.255 PTR .
+
+; End
diff --git a/test/dnszones-src/db.ip6.0 b/test/dnszones-src/db.ip6.0
new file mode 100644 (file)
index 0000000..b626611
--- /dev/null
@@ -0,0 +1,11 @@
+; $Cambridge: exim/test/dnszones-src/db.ip6.0,v 1.1 2006/02/06 16:22:56 ph10 Exp $
+
+; This is a testing reverse zone file for use when testing DNS handling in
+; Exim. This is a fake zone of no real use - hence no SOA record. The zone name
+; is 0.0.0.0.ip6.arpa. This file is passed through the substitution mechanism
+; before being used by the fakens auxiliary program, though in fact there is
+; nothing to substitute.
+
+1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0   PTR    localhost.
+
+; End
diff --git a/test/dnszones-src/db.ip6.V6NET b/test/dnszones-src/db.ip6.V6NET
new file mode 100644 (file)
index 0000000..0db200e
--- /dev/null
@@ -0,0 +1,12 @@
+; $Cambridge: exim/test/dnszones-src/db.ip6.V6NET,v 1.1 2006/02/06 16:22:56 ph10 Exp $
+
+; This is a testing reverse zone file for use when testing DNS handling in
+; Exim. This is a fake zone of no real use - hence no SOA record. The zone
+; name is determined by the V6NET substitution. This file is passed through
+; the substitution mechanism before being used by the fakens auxiliary program,
+; though in fact there is nothing to substitute.
+
+2.6.0.a.6.8.e.f.f.f.0.2.0.0.a.0.1.0.0.0.2.1.0.0.0.0.0.0 PTR testptr-arpa.ipv6.test.ex.
+d.0.0.0.c.b.a.0.8.0.0.0.7.0.0.0.6.0.0.0.5.0.0.0.4.3.2.1 PTR test3.ipv6.test.ex.
+
+; End
diff --git a/test/dnszones-src/db.test.ex b/test/dnszones-src/db.test.ex
new file mode 100644 (file)
index 0000000..2e2865e
--- /dev/null
@@ -0,0 +1,360 @@
+; $Cambridge: exim/test/dnszones-src/db.test.ex,v 1.1 2006/02/06 16:22:56 ph10 Exp $
+
+; This is a testing zone file for use when testing DNS handling in Exim. This
+; is a fake zone of no real use - hence no SOA record. The zone name is
+; test.ex. This file is passed through the substitution mechanism before being
+; used by the fakens auxiliary program. This inserts the actual IP addresses
+; of the local host into the zone.
+
+; NOTE (1): apart from ::1, IPv6 addresses must always have 8 components. Do
+; not abbreviate them by using the :: feature. Leading zeros in components may,
+; however, be omitted.
+
+; NOTE (2): the fakens program is very simple and assumes that the buffer into
+; which is puts the response is always going to be big enough. In other words,
+; the expectation is for just a few RRs for each query.
+
+; NOTE (3): the top-level networks for testing addresses are parameterized by
+; the use of V4NET and V6NET. These networks should be such that no real
+; host ever uses them.
+
+test.ex.     NS      exim.test.ex.
+
+test.ex.     TXT     "A TXT record for test.ex."
+
+cname        CNAME   test.ex.
+
+ptr          PTR     data.for.ptr.test.ex.
+
+; Standard localhost handling
+
+localhost    A       127.0.0.1
+localhost    AAAA    ::1
+
+; This name exists only if qualified; it is never automatically qualified
+
+dontqualify  A       V4NET.255.255.254
+
+; A host with upper case letters in its canonical name
+
+UpperCase    A       127.0.0.1
+
+; A host with UTF-8 characters in its name
+
+mx.π        A       V4NET.255.255.255
+
+; A non-standard name for localhost
+
+thishost     A       127.0.0.1
+
+; Another host with both A and AAAA records
+
+46           A       V4NET.0.0.4
+             AAAA    V6NET:ffff:836f:0a00:000a:0800:200a:c031
+
+; And another
+
+46b          A       V4NET.0.0.5
+             AAAA    V6NET:ffff:836f:0a00:000a:0800:200a:c033
+
+; A working IPv4 address and a non-working IPv6 address, with different
+; names so they can have different MX values
+
+46c          AAAA    V6NET:ffff:836f:0a00:000a:0800:200a:c033
+46d          A       HOSTIPV4
+
+; A host with just a non-local IPv6 address
+
+v6           AAAA    V6NET:ffff:836f:0a00:000a:0800:200a:c032
+
+; Alias A and CNAME records for the local host, under the name "eximtesthost"
+
+eximtesthost     A       HOSTIPV4
+alias-eximtesthost CNAME eximtesthost.test.ex.
+
+; A bad CNAME
+
+badcname     CNAME   rhubarb.test.ex.
+
+; Test a name containing an underscore
+
+a_b          A       99.99.99.99
+
+; The reverse registration for this name is an empty string
+
+empty        A       V4NET.255.255.255
+
+; Some IPv6 stuff
+
+eximtesthost.ipv6 AAAA   HOSTIPV6
+test2.ipv6   AAAA    V6NET:2101:12:1:a00:20ff:fe86:a062
+test3.ipv6   AAAA    V6NET:1234:5:6:7:8:abc:0d
+
+; A case of forward and backward pointers disagreeing
+
+badA         A       V4NET.99.99.99
+badB         A       V4NET.99.99.98
+
+; A host with multiple names in different (sub) domains
+; These are intended to be within test.ex - absence of final dots is deliberate
+
+x.gov.uk     A       V4NET.99.99.97
+x.co.uk      A       V4NET.99.99.97
+
+; A host, the reverse lookup of whose IP address gives this name plus another
+; that does not forward resolve to the same address
+
+oneback      A       V4NET.99.99.90
+host1.masq   A       V4NET.90.90.90
+
+; Fake hosts are registered in the V4NET.0.0.0 subnet. In the past, the
+; 10.0.0.0/8 network was used; hence the names of the hosts.
+
+ten-1        A       V4NET.0.0.1
+ten-2        A       V4NET.0.0.2
+ten-3        A       V4NET.0.0.3
+ten-3-alias  A       V4NET.0.0.3
+ten-3xtra    A       V4NET.0.0.3
+ten-4        A       V4NET.0.0.4
+ten-5        A       V4NET.0.0.5
+ten-6        A       V4NET.0.0.6
+ten-5-6      A       V4NET.0.0.5
+             A       V4NET.0.0.6
+
+ten-99       A       V4NET.0.0.99
+
+black-1      A       V4NET.11.12.13
+black-2      A       V4NET.11.12.14
+
+myhost       A       V4NET.10.10.10
+myhost2      A       V4NET.10.10.10
+
+other1       A       V4NET.12.4.5
+other2       A       V4NET.12.3.1
+             A       V4NET.12.3.2
+
+other99      A       V4NET.99.0.1
+
+testsub.sub  A       V4NET.99.0.3
+
+; This one's real name really is recurse.test.ex.test.ex. It is done like
+; this for testing host widening, without getting tangled up in qualify issues.
+
+recurse.test.ex   A  V4NET.99.0.2
+
+; -------- Testing RBL records -------
+
+; V4NET.11.12.13 is deliberately not reverse-registered
+
+13.12.11.V4NET.rbl    A   127.0.0.2
+                      TXT "This is a test blacklisting message"
+14.12.11.V4NET.rbl    A   127.0.0.2
+                      TXT "This is a test blacklisting message"
+15.12.11.V4NET.rbl    A   127.0.0.2
+                      TXT "This is a very long blacklisting message, continuing for ages and ages and certainly being longer than 128 characters which was a previous limit on the length that Exim was prepared to handle."
+
+14.12.11.V4NET.rbl2   A   127.0.0.2
+                      TXT "This is a test blacklisting2 message"
+16.12.11.V4NET.rbl2   A   127.0.0.2
+                      TXT "This is a test blacklisting2 message"
+
+14.12.11.V4NET.rbl3   A   127.0.0.2
+                      TXT "This is a test blacklisting3 message"
+15.12.11.V4NET.rbl3   A   127.0.0.3
+                      TXT "This is a very long blacklisting message, continuing for ages and ages and certainly being longer than 128 characters which was a previous limit on the length that Exim was prepared to handle."
+
+20.12.11.V4NET.rbl4   A   127.0.0.6
+21.12.11.V4NET.rbl4   A   127.0.0.7
+
+1.13.13.V4NET.rbl     CNAME non-exist.test.ex.
+2.13.13.V4NET.rbl     A   127.0.0.1
+                      A   127.0.0.2
+
+; -------- Testing MX records --------
+
+mxcased      MX  5  ten-99.TEST.EX.
+
+; Points to a host with both A and AAAA
+
+mx46         MX  46 46.test.ex.
+
+; Points to two hosts with both kinds of address, equal precedence
+
+mx4646       MX  46 46.test.ex.
+             MX  46 46b.test.ex.
+
+; Ditto, with a third IPv6 host
+
+mx46466      MX  46 46.test.ex.
+             MX  46 46b.test.ex.
+             MX  46 v6.test.ex.
+
+; Points to a host with a working IPv4 and a non-working IPv6 record
+
+mx46cd       MX  10 46c.test.ex.
+             MX  11 46d.test.ex.
+
+; Two equal precedence pointing to a v4 and a v6 host
+
+mx246        MX  10 v6.test.ex.
+             MX  10 ten-1.test.ex.
+
+; Lowest-numbered points to local host
+
+mxt1         MX  5  eximtesthost.test.ex.
+
+; Points only to non-existent hosts
+
+mxt2         MX  5  not-exist.test.ex.
+
+; Points to some non-existent hosts;
+; Lowest numbered existing points to local host
+
+mxt3         MX  5  not-exist.test.ex.
+             MX  6  eximtesthost.test.ex.
+
+; Points to some non-existent hosts;
+; Lowest numbered existing points to non-local host
+
+mxt3r        MX  5  not-exist.test.ex.
+             MX  6  exim.org.
+
+; Points to an alias
+
+mxt4         MX  5  alias-eximtesthost.test.ex.
+
+; Various combinations of precedence and local host
+
+mxt5         MX  5  eximtesthost.test.ex.
+             MX  5  ten-1.test.ex.
+
+mxt6         MX  5  ten-1.test.ex.
+             MX  6  eximtesthost.test.ex.
+             MX  6  ten-2.test.ex.
+
+mxt7         MX  5  ten-2.test.ex.
+             MX  6  ten-3.test.ex.
+             MX  7  eximtesthost.test.ex.
+             MX  8  ten-1.test.ex.
+
+mxt8         MX  5  ten-2.test.ex.
+             MX  6  ten-3.test.ex.
+             MX  7  eximtesthost.test.ex.
+             MX  7  ten-4.test.ex.
+             MX  8  ten-1.test.ex.
+
+; Same host appearing twice; make some variants in different orders to
+; simulate a real nameserver and its round robinning
+
+mxt9         MX  5  ten-1.test.ex.
+             MX  6  ten-2.test.ex.
+             MX  7  ten-3.test.ex.
+             MX  8  ten-1.test.ex.
+
+mxt9a        MX  6  ten-2.test.ex.
+             MX  7  ten-3.test.ex.
+             MX  8  ten-1.test.ex.
+             MX  5  ten-1.test.ex.
+
+mxt9b        MX  7  ten-3.test.ex.
+             MX  8  ten-1.test.ex.
+             MX  5  ten-1.test.ex.
+             MX  6  ten-2.test.ex.
+
+; MX pointing to IP address
+
+mxt10        MX  5  V4NET.0.0.1.
+
+; Several MXs pointing to local host
+
+mxt11        MX  5  localhost.test.ex.
+             MX  6  localhost.test.ex.
+
+mxt11a       MX  5  localhost.test.ex.
+             MX  6  ten-1.test.ex.
+
+mxt12        MX  5  local1.test.ex.
+             MX  6  local2.test.ex.
+
+local1       A   127.0.0.2
+local2       A   127.0.0.2
+
+; Some more
+
+mxt13        MX  4  other1.test.ex.
+             MX  5  other2.test.ex.
+
+; Different hosts with same IP addresses in the list
+
+mxt14        MX  4  ten-5-6.test.ex.
+             MX  5  ten-5.test.ex.
+             MX  6  ten-6.test.ex.
+
+; Large number of IP addresses at one MX value, and then some
+; at another, to check that hosts_max_try tries the MX different
+; values if it can.
+
+mxt99        MX  1  ten-1.test.ex.
+             MX  1  ten-2.test.ex.
+             MX  1  ten-3.test.ex.
+             MX  1  ten-4.test.ex.
+             MX  1  ten-5.test.ex.
+             MX  1  ten-6.test.ex.
+             MX  3  black-1.test.ex.
+             MX  3  black-2.test.ex.
+
+; Special case test for @mx_any (to doublecheck a reported Exim 3 bug isn't
+; in Exim 4). The MX points to two names, each with multiple addresses. The
+; very last address is the local host. When Exim is testing, it will sort
+; these addresses into ascending order.
+
+mxt98        MX  1  98-1.test.ex.
+             MX  2  98-2.test.ex.
+
+98-1         A   V4NET.1.2.3
+             A   V4NET.4.5.6
+
+98-2         A   V4NET.7.8.9
+             A   HOSTIPV4
+
+; IP addresses with the same MX value
+
+mxt97        MX  1  ten-1.test.ex.
+             MX  1  ten-2.test.ex.
+             MX  1  ten-3.test.ex.
+             MX  1  ten-4.test.ex.
+
+; MX pointing to a single-component name that exists if qualified, but not
+; if not. We use the special name dontqualify to stop the fake resolver
+; qualifying it.
+
+mxt1c        MX  1  dontqualify.
+
+; MX with UTF-8 characters in its name
+
+π           MX  0  mx.π.test.ex.
+
+; -------- Testing SRV records --------
+
+_smtp._tcp.srv01    SRV  0 0 25 ten-1.test.ex.
+
+_smtp._tcp.srv02    SRV  1 3 99 ten-1.test.ex.
+                    SRV  1 1 99 ten-2.test.ex.
+                    SRV  3 0 66 ten-3.test.ex.
+
+_smtp._tcp.nosmtp   SRV  0 0 0  .
+
+_smtp2._tcp.srv03   SRV  0 0 88 ten-4.test.ex.
+
+_smtp._tcp.srv27    SRV  0 0 PORT_S localhost
+
+
+; -------- With some for CSA testing plus their A records -------
+
+_client._smtp.csa1  SRV  1 2 0  csa1.test.ex.
+_client._smtp.csa2  SRV  1 1 0  csa2.test.ex.
+
+csa1         A   V4NET.9.8.7
+csa2         A   V4NET.9.8.8
+
+; End
diff --git a/test/dnszones-src/qualify.test.ex b/test/dnszones-src/qualify.test.ex
new file mode 100644 (file)
index 0000000..e8c9f93
--- /dev/null
@@ -0,0 +1,5 @@
+$Cambridge: exim/test/dnszones-src/qualify.test.ex,v 1.1 2006/02/06 16:22:56 ph10 Exp $
+
+The contents of this file are not used. Its name is used as a way of specifying
+the domain that is to be used to qualify unqualified domain names when given to
+the fake DNS resolver.
diff --git a/test/src/cf.c b/test/src/cf.c
new file mode 100644 (file)
index 0000000..4dc3dc0
--- /dev/null
@@ -0,0 +1,715 @@
+/* $Cambridge: exim/test/src/cf.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
+
+/************************************************
+*                  PH-Compare                   *
+************************************************/
+
+/* A program to compare two files line by line.
+
+History:
+
+It was originally written in C, but the C under
+Panos is still a shambles (1986). Translated therefore
+to BCPL -- this explains some of the odd style.
+
+Modified to run on Archimedes, August 1987.
+Modified to run under MSDOS, March 1989.
+Modified to run under CINTERP interpreter, July 1989.
+Modified to run under Unix, October 1989.
+
+Translated back into C, March 1990! */
+
+/* Copyright (c) 1986, 1987, 1989, 1990, 1994, 2001 by Philip Hazel */
+
+/* Previously modified: October 1994*/
+/* Last modified: September 2001 - a long-lived bug fixed! */
+
+
+#include <stdio.h>
+#include <errno.h>
+
+#ifdef __STDC__
+#include <string.h>
+#include <stdlib.h>
+#endif
+
+/* ----- parameters ----- */
+
+#define version            8
+#define defaultstore  100000     /* default recovery buffer size */
+#define minstore         500     /* minimum recovery buffer size */
+
+/* ----- misc defines ----- */
+
+#define FALSE 0
+#define TRUE  1
+
+#ifdef __STDC__
+#define pvoid     void
+#else
+#define pvoid
+#endif
+
+#define EqString(s, t)   (strcmp(s, t) == 0)
+
+/* ----- line structure ----- */
+
+typedef struct line {
+  struct line *next;
+  int  number;
+  char text[999999];
+} line;
+
+
+/* ----- global variables ----- */
+
+FILE *f_one;           /* files */
+FILE *f_two;
+FILE *f_out;
+
+int lines_one = 0;            /* line counts */
+int lines_two = 0;
+int return_code = 0;
+int eof_one = FALSE;          /* eof flags */
+int eof_two = FALSE;
+int exact = FALSE;            /* TRUE => no strip spaces */
+int echo = TRUE;              /* TRUE => show mismatched lines */
+int sync_count = 3;           /* resync count */
+int storesize = defaultstore; /* size of each buffer */
+
+char *name_one = NULL;        /* file names */
+char *name_two = NULL;
+char *to_name = NULL;
+
+char *bufbase_one;            /* start buffer */
+char *bufbase_two;
+char *bufnext_one;            /* next free byte */
+char *bufnext_two;
+char *buftop_one;             /* end buffer */
+char *buftop_two;
+
+line *rootline_one;           /* mis-match point */
+line *rootline_two;
+line *lastline_one;           /* last in store */
+line *lastline_two;
+line *pline_one;              /* working line */
+line *pline_two;
+
+
+/*************************************************
+*             Help Information                   *
+*************************************************/
+
+void givehelp(pvoid)
+{
+printf("PH's CMP v%d\n", version);
+printf("Keywords:\n");
+printf("       <file>    ) files to compare\n");
+printf("       <file>    ) no keywords used\n");
+printf("-to    <file>      output destination\n");
+printf("-exact             include trailing spaces & match tabs\n");
+printf("-noecho            don't echo differences (just give line numbers)\n");
+printf("-s, -sync  <n>     set re-sync count, default 3\n");
+printf("-buffer <n>        buffer size (for each file) default 100000\n");
+printf("-id                give program version id\n");
+printf("-h, -help          give this help\n");
+printf("\nExamples:\n");
+printf("cmp old.f77 new.f77\n");
+printf("cmp first second -noecho -sync 1\n");
+printf("cmp large1 large2 -buffer 200000 -noecho -to diffs\n");
+}
+
+
+
+/************************************************
+*               Errors -- all serious           *
+************************************************/
+
+void moan(code, text)
+int code;
+char *text;
+{
+fprintf(stderr, "\n** ");
+switch (code)
+  {
+  case 1:
+  fprintf(stderr, "Unable to open file \"%s\"", text);
+  if (errno)
+    {
+    fprintf(stderr, " - ");
+    perror(NULL);
+    }
+  else fprintf(stderr, "\n");
+  break;
+
+  case 2:
+  fprintf(stderr, "Buffer overflow for file \"%s\"\n", text);
+  break;
+
+  case 3:
+  fprintf(stderr, "Two file names must be given\n");
+  break;
+
+  default:
+  fprintf(stderr, "Unknown error %d\n", code);
+  break;
+  }
+
+fprintf(stderr, "** CMP abandoned\n");
+exit(99);
+}
+
+
+
+/*************************************************
+*         Write line identification              *
+*************************************************/
+
+void write_id(n1, n2, c, name, p1, p2)
+int n1, n2, c;
+char *name, *p1, *p2;
+{
+if (n2 < 0) n2 = -n2;
+n2 -= 1;
+fprintf(f_out, "%cine", c);
+if (n1 == n2) fprintf(f_out, " %d of \"%s\"%s", n1, name, p1);
+  else fprintf(f_out, "s %d-%d of \"%s\"%s", n1, n2, name, p2);
+}
+
+
+/*************************************************
+*           Write sequence of lines              *
+*************************************************/
+
+void write_lines(s, t)
+line *s, *t;
+{
+while (s != t)
+  {
+  char *p = s->text;
+  while (*p != '\n') fputc(*p++, f_out);
+  fputc('\n', f_out);
+  s = s->next;
+  }
+}
+
+
+
+/*************************************************
+*           Write separator rule                 *
+*************************************************/
+
+void rule(s, l)
+int s, l;
+{
+while (l-- > 0) fprintf(f_out, "%c", s);
+fprintf(f_out, "\n");
+}
+
+
+
+/*************************************************
+*          Write message on re-sync or eof       *
+*************************************************/
+
+void write_message(tline_one, tline_two)
+line *tline_one, *tline_two;
+{
+int s1 = rootline_one->number;
+int t1 = tline_one->number;
+int s2 = rootline_two->number;
+int t2 = tline_two->number;
+if (echo) rule('=', 15);
+
+if (s1 == t1)
+  {
+  write_id(s2, t2, 'L', name_two, " occurs ", " occur ");
+  if (s1 < 0) fprintf(f_out, "at the end");
+    else fprintf(f_out, "before line %d", s1);
+  fprintf(f_out, " of \"%s\".\n", name_one);
+  if (echo)
+    {
+    rule('-', 10);
+    write_lines(rootline_two, tline_two);
+    }
+  }
+
+else if (s2 == t2)
+  {
+  write_id(s1, t1, 'L', name_one, " occurs ", " occur ");
+  if (s2 < 0) fprintf(f_out, "at the end");
+    else fprintf(f_out, "before line %d", s2);
+  fprintf(f_out, " of \"%s\".\n", name_two);
+  if (echo)
+    {
+    rule('-', 10);
+    write_lines(rootline_one, tline_one);
+    }
+  }
+
+else if (t1 < 0 && t2 < 0)
+  {
+  fprintf(f_out, "From line %d of \"%s\" and line %d of \"%s\" ",
+    rootline_one->number, name_one, rootline_two->number, name_two);
+  fprintf(f_out, "the files are different.\n");
+  if (echo)
+    {
+    rule('-', 10);
+    if (-t1-s1 < 21) write_lines(rootline_one, tline_one);
+      else fprintf(f_out, "... <more than 20 lines> ...\n");
+    rule('-', 10);
+    if (-t2-s2 < 21) write_lines(rootline_two, tline_two);
+      else fprintf(f_out, "... <more than 20 lines> ...\n");
+    }
+  }
+
+else
+  {
+  write_id(s1, t1, 'L', name_one, " does ", " do ");
+  fprintf(f_out, "not match ");
+  write_id(s2, t2, 'l', name_two, ".\n", ".\n");
+  if (echo)
+    {
+    rule('-', 10);
+    write_lines(rootline_one, tline_one);
+    rule('-', 10);
+    write_lines(rootline_two, tline_two);
+    }
+  }
+}
+
+
+
+
+/*************************************************
+*           Advance to next line in store        *
+*************************************************/
+
+/* A separate procedure exists for each file, for
+simplicity and efficiency. */
+
+int nextline_one(pvoid)
+{
+if (pline_one == NULL || pline_one->next == NULL) return FALSE;
+pline_one = pline_one->next;
+return TRUE;
+}
+
+int nextline_two(pvoid)
+{
+if (pline_two == NULL || pline_two->next == NULL) return FALSE;
+pline_two = pline_two->next;
+return TRUE;
+}
+
+
+/*************************************************
+*             Read a line into store             *
+*************************************************/
+
+/* A separate procedure exists for each file, for
+simplicity and efficiency. */
+
+void readline_one(pvoid)
+{
+int count = 0;
+int c = fgetc(f_one);
+line *nextline = (line *)bufnext_one;
+
+bufnext_one = nextline->text;
+if (bufnext_one >= buftop_one) moan(2, name_one);
+
+nextline->next = NULL;
+
+lines_one ++;
+if (c == EOF)
+  {
+  eof_one = TRUE;
+  nextline->number = -lines_one;
+  }
+else
+  {
+  nextline->number = lines_one;
+  for (;;)
+    {
+    if (c == EOF) c = '\n';
+    if (c == '\n')
+      {
+      if (!exact)
+        while (bufnext_one > nextline->text)
+          { if (bufnext_one[-1] == ' ') bufnext_one--; else break; }
+      *(bufnext_one++) = '\n';
+      if (bufnext_one >= buftop_one) moan(2, name_one);
+      break;
+      }
+    if (c == '\t' && !exact)
+      do { *(bufnext_one++) = ' '; count++; } while ((count & 7) != 0);
+    else { *(bufnext_one++) = c; count++; }
+    if (bufnext_one >= buftop_one) moan(2, name_one);
+    c = fgetc(f_one);
+    }
+  }
+
+if (lastline_one != NULL) lastline_one->next = nextline;
+lastline_one = nextline;
+pline_one = nextline;
+
+bufnext_one = (char *) (((int)bufnext_one+3) & (-4));
+}
+
+
+
+void readline_two(pvoid)
+{
+int count = 0;
+int c = fgetc(f_two);
+line *nextline = (line *)bufnext_two;
+
+bufnext_two = nextline->text;
+if (bufnext_two >= buftop_two) moan(2, name_two);
+
+nextline->next = NULL;
+
+lines_two ++;
+if (c == EOF)
+  {
+  eof_two = TRUE;
+  nextline->number = -lines_two;
+  }
+else
+  {
+  nextline->number = lines_two;
+  for (;;)
+    {
+    if (c == EOF) c = '\n';
+    if (c == '\n')
+      {
+      if (!exact)
+        while (bufnext_two > nextline->text)
+          { if (bufnext_two[-1] == ' ') bufnext_two--; else break; }
+      *(bufnext_two++) = '\n';
+      if (bufnext_two >= buftop_two) moan(2, name_two);
+      break;
+      }
+    if (c == '\t' && !exact)
+      do { *(bufnext_two++) = ' '; count++; } while ((count & 7) != 0);
+    else { *(bufnext_two++) = c; count++; }
+    if (bufnext_two >= buftop_two) moan(2, name_two);
+    c = fgetc(f_two);
+    }
+  }
+
+if (lastline_two != NULL) lastline_two->next = nextline;
+lastline_two = nextline;
+pline_two = nextline;
+
+bufnext_two = (char *) (((int)bufnext_two+3) & (-4));
+}
+
+
+
+/**************************************************
+*              Compare two lines                  *
+**************************************************/
+
+int compare_lines(a, b)
+line *a, *b;
+{
+int n1 = a->number;
+int n2 = b->number;
+char *s = a->text;
+char *t = b->text;
+
+if (n1 < 0  &&  n2 < 0) return TRUE;
+if (n1 < 0  ||  n2 < 0) return FALSE;
+
+while (*s == *t)
+  {
+  if (*s == '\n') return TRUE;
+  s++; t++;
+  }
+
+return FALSE;
+}
+
+
+/*************************************************
+*             Re-synchronizing code              *
+*************************************************/
+
+int resync(pvoid)
+{
+int i;
+int matched = TRUE;
+line *tline_one = pline_one;
+line *tline_two = pline_two;
+
+if (eof_one || eof_two) matched = FALSE; else
+  {
+  for (i = 1; i < sync_count; i++)
+    {
+    if (!nextline_one()) readline_one();
+    if (!nextline_two()) readline_two();
+    if (!compare_lines(pline_one, pline_two)) { matched = FALSE; break; }
+    if (eof_one || eof_two) { matched = FALSE; break; }
+    }
+  }
+
+if (matched) write_message(tline_one, tline_two); else
+  {
+  pline_one = tline_one;
+  pline_two = tline_two;
+  }
+
+return matched;
+}
+
+
+
+/*************************************************
+*                 Main compare code              *
+*************************************************/
+
+void compare(pvoid)
+{
+int matched = TRUE;
+
+/* Big main loop - exit by return or unmatched at eof */
+
+while (matched)
+  {
+  /* First minor loop, while in step */
+
+  while (matched && !eof_one && !eof_two)
+    {
+    /* Advance or read next lines */
+
+    if (!nextline_one())
+      {
+      bufnext_one = bufbase_one;
+      lastline_one = NULL;
+      readline_one();
+      }
+
+    if (!nextline_two())
+      {
+      bufnext_two = bufbase_two;
+      lastline_two = NULL;
+      readline_two();
+      }
+
+    /* Compare and check for end of file */
+
+    matched = compare_lines(pline_one, pline_two);
+
+    } /* End first minor loop */
+
+  if (matched) return;    /* successful end of file */
+
+  /* There has been a mis-match */
+
+  return_code++;
+  rootline_one = pline_one;   /* Fail point */
+  rootline_two = pline_two;
+
+  /* Second minor loop, trying to regain sync */
+
+  while (!eof_one || !eof_two)
+    {
+    /* Advance one and scan all of two */
+
+    if (!eof_one)
+      {
+      line *zline = pline_two;
+      if (!nextline_one()) readline_one();
+      pline_two = rootline_two;
+      for (;;)
+        {
+        if (compare_lines(pline_one, pline_two))
+          {
+          matched = resync();
+          if (matched) break;
+          }
+        if (pline_two == zline) break;
+        pline_two = pline_two->next;
+        }
+      if (matched) break;
+      }
+
+    /* Advance two and scan all of one */
+
+    if (!eof_two)
+      {
+      line *zline = pline_one;
+      if (!nextline_two()) readline_two();
+      pline_one = rootline_one;
+      for (;;)
+        {
+        if (compare_lines(pline_one, pline_two))
+          {
+          matched = resync();
+          if (matched) break;
+          }
+        if (pline_one == zline) break;
+        pline_one = pline_one->next;
+        }
+      if (matched) break;
+      }
+
+    } /* End second minor loop */
+
+  } /* End of major loop */
+
+write_message(lastline_one, lastline_two);
+}
+
+
+
+
+/*************************************************
+*                   Entry Point                  *
+*************************************************/
+
+int main(argc, argv)
+int argc;
+char **argv;
+{
+int argp = 1;
+int arg_id = FALSE;
+int arg_help = FALSE;
+
+f_out = stdout;
+
+/* Scan argument strings */
+
+while (argp < argc)
+  {
+  char  *arg = argv[argp];
+  char **lv_name = (name_one == NULL)? &name_one:&name_two;  /* default for positional */
+  int   *lv_value = NULL;
+  int    value = TRUE;
+
+  if (arg[0] == '-')
+    {                            /* keyed argument */
+    if (EqString(arg,"-help") || EqString(arg, "-h"))
+      { arg_help = TRUE; value = FALSE; }
+    else if (EqString(arg, "-id"))
+      { arg_id = TRUE; value = FALSE; }
+    else if (EqString(arg, "-exact"))
+      { exact = TRUE; value = FALSE; }
+    else if (EqString(arg, "-noecho"))
+      { echo = FALSE; value = FALSE; }
+    else if (EqString(arg, "-to")) lv_name = &to_name;
+    else if (EqString(arg, "-sync") || EqString(arg, "-s"))
+       lv_value = &sync_count;
+    else if (EqString(arg, "-buffer")) lv_value = &storesize;
+    else { printf("Unknown keyword %s\n", arg); exit(99); }
+
+    if (++argp >= argc && value)
+      { printf("Value for keyword %s missing\n", arg); exit(99); }
+    }
+
+  /* Deal with keys that take values */
+
+  if (value)
+    {
+    if (lv_value == &sync_count || lv_value == &storesize)
+      {
+      int ch;
+      int i = 0;
+      char *argval = argv[argp++];
+      *lv_value = 0;
+      while ((ch = argval[i++]) != 0)
+        {
+        if ('0' <= ch && ch <= '9') *lv_value = 10*(*lv_value) + ch - '0'; else
+          {
+          printf("Number expected after \"%s\" but \"%s\" read\n",
+            arg, argval);
+          exit(99);
+          }
+        }
+      }
+
+    else if (*lv_name != NULL)
+      {
+      printf("Keyword expected but \"%s\" read", arg);
+      printf(" - use \"cmp -h\" for help\n");
+      exit(99);
+      }
+    else *lv_name = argv[argp++];
+    }
+  }
+
+/* Deal with help and id */
+
+if (arg_id && !arg_help)
+  {
+  printf("PH's CMP v%d\n", version);
+  exit(0);
+  }
+
+if (arg_help)
+  {
+  givehelp();
+  exit(0);
+  }
+
+/* Deal with file names */
+
+if (name_one == NULL || name_two == NULL) moan(3, "");
+
+if (to_name != NULL)
+  {
+  f_out = fopen(to_name, "w");
+  if (f_out == NULL) moan(1, to_name);
+  }
+
+/* Further general initialization */
+
+if (storesize < minstore) storesize = defaultstore;
+f_one = fopen(name_one, "r");
+if (f_one == NULL) moan(1, name_one);
+f_two = fopen(name_two, "r");
+if (f_two == NULL) moan(1, name_two);
+
+bufbase_one = (char *)malloc(storesize);
+buftop_one = bufbase_one + storesize;
+bufbase_two = (char *)malloc(storesize);
+buftop_two = bufbase_two + storesize;
+
+/* Do the job */
+
+compare();
+
+/* Final messages */
+
+if (return_code == 0)
+  fprintf(f_out, "\"%s\" and \"%s\" are identical.\n", name_one, name_two);
+else
+  {
+  if (echo) rule('=', 15);
+  fprintf(f_out, "%d difference", return_code);
+  if (return_code != 1) fprintf(f_out, "s");
+  fprintf(f_out, " found.\n");
+
+  lines_one -= 1;
+  fprintf(f_out, "\"%s\" contains %d line", name_one, lines_one);
+  if (lines_one != 1) fprintf(f_out, "s");
+
+  lines_two -= 1;
+  fprintf(f_out, "; \"%s\" contains %d line", name_two, lines_two);
+  if (lines_two != 1) fprintf(f_out, "s");
+  fprintf(f_out, ".\n");
+  }
+
+free(bufbase_one);
+free(bufbase_two);
+
+fclose(f_one);
+fclose(f_two);
+if (f_out != stdout) fclose(f_out);
+
+return return_code;
+}
+
+/* End of PH-Compare. */
diff --git a/test/src/checkaccess.c b/test/src/checkaccess.c
new file mode 100644 (file)
index 0000000..926daa0
--- /dev/null
@@ -0,0 +1,41 @@
+/* $Cambridge: exim/test/src/checkaccess.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
+
+/* This is a baby program that is run as root from the runtest script. It is
+passed the Exim uid and gid as arguments, and the name of a file in the
+test-suite directory. It gives up all supplementary groups, changes to the
+given uid/gid, and then tries to read the file. The yield is 0 if that is
+successful, and non-zero otherwise (use different values to aid debugging). See
+comments in the exim.c source file about the use of setgroups() for getting rid
+of extraneous groups. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include <stdio.h>
+
+
+int main(int argc, char **argv)
+{
+int fd;
+gid_t group_list[10];
+struct passwd *pw = getpwnam(argv[2]);
+struct group *gr = getgrnam(argv[3]);
+
+if (pw == NULL) return 1;
+if (gr == NULL) return 2;
+if (setgroups(0, NULL) != 0 && setgroups(1, group_list) != 0) return 4;
+if (setgid(gr->gr_gid) != 0) return 5;
+if (setuid(pw->pw_uid) != 0) return 6;
+
+fd = open(argv[1], O_RDONLY);
+if (fd < 0) return 7;
+
+close(fd);
+return 0;
+}
+
+/* End */
diff --git a/test/src/client.c b/test/src/client.c
new file mode 100644 (file)
index 0000000..0a21594
--- /dev/null
@@ -0,0 +1,898 @@
+/* $Cambridge: exim/test/src/client.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
+
+/* A little hacked up program that makes a TCP/IP call and reads a script to
+drive it, for testing Exim server code running as a daemon. It's got a bit
+messy with the addition of support for either OpenSSL or GnuTLS. The code for
+those was hacked out of Exim itself. */
+
+/* ANSI C standard includes */
+
+#include <ctype.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/* Unix includes */
+
+#include <errno.h>
+#include <dirent.h>
+#include <sys/types.h>
+
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <utime.h>
+
+#ifdef AF_INET6
+#define HAVE_IPV6 1
+#endif
+
+#ifndef S_ADDR_TYPE
+#define S_ADDR_TYPE u_long
+#endif
+
+typedef unsigned char uschar;
+
+#define CS   (char *)
+#define US   (unsigned char *)
+
+#define FALSE         0
+#define TRUE          1
+
+
+
+static int sigalrm_seen = 0;
+
+
+/* TLS support can be optionally included, either for OpenSSL or GnuTLS. The
+latter needs a whole pile of tables. */
+
+#ifdef HAVE_OPENSSL
+#define HAVE_TLS
+#include <openssl/crypto.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#endif
+
+
+#ifdef HAVE_GNUTLS
+#define HAVE_TLS
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+#define DH_BITS      768
+#define RSA_BITS     512
+
+/* Local static variables for GNUTLS */
+
+static gnutls_rsa_params rsa_params = NULL;
+static gnutls_dh_params dh_params = NULL;
+
+static gnutls_certificate_credentials_t x509_cred = NULL;
+static gnutls_session tls_session = NULL;
+
+static int  ssl_session_timeout = 200;
+
+/* Priorities for TLS algorithms to use. */
+
+static const int protocol_priority[16] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
+
+static const int kx_priority[16] = {
+  GNUTLS_KX_RSA,
+  GNUTLS_KX_DHE_DSS,
+  GNUTLS_KX_DHE_RSA,
+  GNUTLS_KX_RSA_EXPORT,
+  0 };
+
+static int default_cipher_priority[16] = {
+  GNUTLS_CIPHER_AES_256_CBC,
+  GNUTLS_CIPHER_AES_128_CBC,
+  GNUTLS_CIPHER_3DES_CBC,
+  GNUTLS_CIPHER_ARCFOUR_128,
+  0 };
+
+static const int mac_priority[16] = {
+  GNUTLS_MAC_SHA,
+  GNUTLS_MAC_MD5,
+  0 };
+
+static const int comp_priority[16] = { GNUTLS_COMP_NULL, 0 };
+static const int cert_type_priority[16] = { GNUTLS_CRT_X509, 0 };
+
+#endif
+
+
+
+
+/*************************************************
+*            SIGALRM handler - crash out         *
+*************************************************/
+
+static void
+sigalrm_handler_crash(int sig)
+{
+sig = sig;    /* Keep picky compilers happy */
+printf("\nClient timed out\n");
+exit(99);
+}
+
+
+/*************************************************
+*            SIGALRM handler - set flag          *
+*************************************************/
+
+static void
+sigalrm_handler_flag(int sig)
+{
+sig = sig;    /* Keep picky compilers happy */
+sigalrm_seen = 1;
+}
+
+
+
+/****************************************************************************/
+/****************************************************************************/
+
+#ifdef HAVE_OPENSSL
+/*************************************************
+*         Start an OpenSSL TLS session           *
+*************************************************/
+
+int tls_start(int sock, SSL **ssl, SSL_CTX *ctx)
+{
+int rc;
+static const char *sid_ctx = "exim";
+
+RAND_load_file("client.c", -1);   /* Not *very* random! */
+
+*ssl = SSL_new (ctx);
+SSL_set_session_id_context(*ssl, sid_ctx, strlen(sid_ctx));
+SSL_set_fd (*ssl, sock);
+SSL_set_connect_state(*ssl);
+
+signal(SIGALRM, sigalrm_handler_flag);
+sigalrm_seen = 0;
+alarm(5);
+rc = SSL_connect (*ssl);
+alarm(0);
+
+if (sigalrm_seen)
+  {
+  printf("SSL_connect timed out\n");
+  return 0;
+  }
+
+if (rc <= 0)
+  {
+  ERR_print_errors_fp(stdout);
+  return 0;
+  }
+
+printf("SSL connection using %s\n", SSL_get_cipher (*ssl));
+return 1;
+}
+
+
+/*************************************************
+*           SSL Information callback             *
+*************************************************/
+
+static void
+info_callback(SSL *s, int where, int ret)
+{
+where = where;
+ret = ret;
+printf("SSL info: %s\n", SSL_state_string_long(s));
+}
+#endif
+
+
+/****************************************************************************/
+/****************************************************************************/
+
+
+#ifdef HAVE_GNUTLS
+/*************************************************
+*            Handle GnuTLS error                 *
+*************************************************/
+
+/* Called from lots of places when errors occur before actually starting to do
+the TLS handshake, that is, while the session is still in clear.
+
+Argument:
+  prefix    prefix text
+  err       a GnuTLS error number, or 0 if local error
+
+Returns:    doesn't - it dies
+*/
+
+static void
+gnutls_error(uschar *prefix, int err)
+{
+fprintf(stderr, "GnuTLS connection error:%s", prefix);
+if (err != 0) fprintf(stderr, " %s", gnutls_strerror(err));
+fprintf(stderr, "\n");
+exit(1);
+}
+
+
+
+/*************************************************
+*          Setup up RSA and DH parameters        *
+*************************************************/
+
+/* For the test suite, the parameters should always be available in the spool
+directory. */
+
+static void
+init_rsa_dh(void)
+{
+int fd;
+int ret;
+gnutls_datum m;
+uschar filename[200];
+struct stat statbuf;
+
+/* Initialize the data structures for holding the parameters */
+
+ret = gnutls_rsa_params_init(&rsa_params);
+if (ret < 0) gnutls_error(US"init rsa_params", ret);
+
+ret = gnutls_dh_params_init(&dh_params);
+if (ret < 0) gnutls_error(US"init dh_params", ret);
+
+/* Open the cache file for reading and if successful, read it and set up the
+parameters. If we can't set up the RSA parameters, assume that we are dealing
+with an old-style cache file that is in another format, and fall through to
+compute new values. However, if we correctly get RSA parameters, a failure to
+set up D-H parameters is treated as an error. */
+
+fd = open("aux-fixed/gnutls-params", O_RDONLY, 0);
+if (fd < 0)
+  {
+  fprintf(stderr, "Failed to open spool/gnutls-params: %s\n", strerror(errno));
+  exit(1);
+  }
+
+if (fstat(fd, &statbuf) < 0)
+  {
+  (void)close(fd);
+  return gnutls_error(US"TLS cache stat failed", 0);
+  }
+
+m.size = statbuf.st_size;
+m.data = malloc(m.size);
+if (m.data == NULL)
+  return gnutls_error(US"memory allocation failed", 0);
+if (read(fd, m.data, m.size) != m.size)
+  return gnutls_error(US"TLS cache read failed", 0);
+(void)close(fd);
+
+ret = gnutls_rsa_params_import_pkcs1(rsa_params, &m, GNUTLS_X509_FMT_PEM);
+if (ret < 0) return gnutls_error(US"RSA params import", ret);
+ret = gnutls_dh_params_import_pkcs3(dh_params, &m, GNUTLS_X509_FMT_PEM);
+if (ret < 0) return gnutls_error(US"DH params import", ret);
+free(m.data);
+}
+
+
+
+
+/*************************************************
+*            Initialize for GnuTLS               *
+*************************************************/
+
+/*
+Arguments:
+  certificate     certificate file
+  privatekey      private key file
+*/
+
+static void
+tls_init(uschar *certificate, uschar *privatekey)
+{
+int rc;
+
+rc = gnutls_global_init();
+if (rc < 0) gnutls_error(US"gnutls_global_init", rc);
+
+/* Read RSA and D-H parameters from the cache file. */
+
+init_rsa_dh();
+
+/* Create the credentials structure */
+
+rc = gnutls_certificate_allocate_credentials(&x509_cred);
+if (rc < 0) gnutls_error(US"certificate_allocate_credentials", rc);
+
+/* Set the certificate and private keys */
+
+if (certificate != NULL)
+  {
+  rc = gnutls_certificate_set_x509_key_file(x509_cred, CS certificate,
+    CS privatekey, GNUTLS_X509_FMT_PEM);
+  if (rc < 0) gnutls_error("gnutls_certificate", rc);
+  }
+
+/* Associate the parameters with the x509 credentials structure. */
+
+gnutls_certificate_set_dh_params(x509_cred, dh_params);
+gnutls_certificate_set_rsa_export_params(x509_cred, rsa_params);
+}
+
+
+
+/*************************************************
+*        Initialize a single GNUTLS session      *
+*************************************************/
+
+static gnutls_session
+tls_session_init(void)
+{
+gnutls_session session;
+
+gnutls_init(&session, GNUTLS_CLIENT);
+
+gnutls_cipher_set_priority(session, default_cipher_priority);
+gnutls_compression_set_priority(session, comp_priority);
+gnutls_kx_set_priority(session, kx_priority);
+gnutls_protocol_set_priority(session, protocol_priority);
+gnutls_mac_set_priority(session, mac_priority);
+
+gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
+
+gnutls_dh_set_prime_bits(session, DH_BITS);
+gnutls_db_set_cache_expiration(session, ssl_session_timeout);
+
+return session;
+}
+#endif
+
+
+/****************************************************************************/
+/****************************************************************************/
+
+
+
+
+/*************************************************
+*                 Main Program                   *
+*************************************************/
+
+/* Usage: client
+          <IP address>
+          <port>
+          [<outgoing interface>]
+          [<cert file>]
+          [<key file>]
+*/
+
+int main(int argc, char **argv)
+{
+struct sockaddr *s_ptr;
+struct sockaddr_in s_in4;
+char *interface = NULL;
+char *address = NULL;
+char *certfile = NULL;
+char *keyfile = NULL;
+int argi = 1;
+int host_af, port, s_len, rc, sock, save_errno;
+int timeout = 1;
+int tls_active = 0;
+int sent_starttls = 0;
+int tls_on_connect = 0;
+
+#if HAVE_IPV6
+struct sockaddr_in6 s_in6;
+#endif
+
+#ifdef HAVE_OPENSSL
+SSL_CTX* ctx;
+SSL*     ssl;
+#endif
+
+unsigned char outbuffer[10240];
+unsigned char inbuffer[10240];
+unsigned char *inptr = inbuffer;
+
+*inptr = 0;   /* Buffer empty */
+
+/* Options */
+
+while (argc >= argi + 1 && argv[argi][0] == '-')
+  {
+  if (strcmp(argv[argi], "-tls-on-connect") == 0)
+    {
+    tls_on_connect = 1;
+    argi++;
+    }
+  else if (argv[argi][1] == 't' && isdigit(argv[argi][2]))
+    {
+    timeout = atoi(argv[argi]+1);
+    argi++;
+    }
+  else
+    {
+    printf("Unrecognized option %s\n", argv[argi]);
+    exit(1);
+    }
+  }
+
+/* Mandatory 1st arg is IP address */
+
+if (argc < argi+1)
+  {
+  printf("No IP address given\n");
+  exit(1);
+  }
+
+address = argv[argi++];
+host_af = (strchr(address, ':') != NULL)? AF_INET6 : AF_INET;
+
+/* Mandatory 2nd arg is port */
+
+if (argc < argi+1)
+  {
+  printf("No port number given\n");
+  exit(1);
+  }
+
+port = atoi(argv[argi++]);
+
+/* Optional next arg is interface */
+
+if (argc > argi &&
+  (isdigit((unsigned char)argv[argi][0]) || argv[argi][0] == ':'))
+    interface = argv[argi++];
+
+/* Any more arguments are the name of a certificate file and key file */
+
+if (argc > argi) certfile = argv[argi++];
+if (argc > argi) keyfile = argv[argi++];
+
+
+#if HAVE_IPV6
+/* For an IPv6 address, use an IPv6 sockaddr structure. */
+
+if (host_af == AF_INET6)
+  {
+  s_ptr = (struct sockaddr *)&s_in6;
+  s_len = sizeof(s_in6);
+  }
+else
+#endif
+
+/* For an IPv4 address, use an IPv4 sockaddr structure,
+even on an IPv6 system. */
+
+  {
+  s_ptr = (struct sockaddr *)&s_in4;
+  s_len = sizeof(s_in4);
+  }
+
+printf("Connecting to %s port %d ... ", address, port);
+
+sock = socket(host_af, SOCK_STREAM, 0);
+if (sock < 0)
+  {
+  printf("socket creation failed: %s\n", strerror(errno));
+  exit(1);
+  }
+
+/* Bind to a specific interface if requested. On an IPv6 system, this has
+to be of the same family as the address we are calling. On an IPv4 system the
+test is redundant, but it keeps the code tidier. */
+
+if (interface != NULL)
+  {
+  int interface_af = (strchr(interface, ':') != NULL)? AF_INET6 : AF_INET;
+
+  if (interface_af == host_af)
+    {
+    #if HAVE_IPV6
+
+    /* Set up for IPv6 binding */
+
+    if (host_af == AF_INET6)
+      {
+      memset(&s_in6, 0, sizeof(s_in6));
+      s_in6.sin6_family = AF_INET6;
+      s_in6.sin6_port = 0;
+      if (inet_pton(AF_INET6, interface, &s_in6.sin6_addr) != 1)
+        {
+        printf("Unable to parse \"%s\"", interface);
+        exit(1);
+        }
+      }
+    else
+    #endif
+
+    /* Set up for IPv4 binding */
+
+      {
+      memset(&s_in4, 0, sizeof(s_in4));
+      s_in4.sin_family = AF_INET;
+      s_in4.sin_port = 0;
+      s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(interface);
+      }
+
+    /* Bind */
+
+    if (bind(sock, s_ptr, s_len) < 0)
+      {
+      printf("Unable to bind outgoing SMTP call to %s: %s",
+        interface, strerror(errno));
+      exit(1);
+      }
+    }
+  }
+
+/* Set up a remote IPv6 address */
+
+#if HAVE_IPV6
+if (host_af == AF_INET6)
+  {
+  memset(&s_in6, 0, sizeof(s_in6));
+  s_in6.sin6_family = AF_INET6;
+  s_in6.sin6_port = htons(port);
+  if (inet_pton(host_af, address, &s_in6.sin6_addr) != 1)
+    {
+    printf("Unable to parse \"%s\"", address);
+    exit(1);
+    }
+  }
+else
+#endif
+
+/* Set up a remote IPv4 address */
+
+  {
+  memset(&s_in4, 0, sizeof(s_in4));
+  s_in4.sin_family = AF_INET;
+  s_in4.sin_port = htons(port);
+  s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(address);
+  }
+
+/* SIGALRM handler crashes out */
+
+signal(SIGALRM, sigalrm_handler_crash);
+alarm(timeout);
+rc = connect(sock, s_ptr, s_len);
+save_errno = errno;
+alarm(0);
+
+/* A failure whose error code is "Interrupted system call" is in fact
+an externally applied timeout if the signal handler has been run. */
+
+if (rc < 0)
+  {
+  close(sock);
+  printf("failed: %s\n", strerror(save_errno));
+  exit(1);
+  }
+
+printf("connected\n");
+
+
+/* --------------- Set up for OpenSSL --------------- */
+
+#ifdef HAVE_OPENSSL
+SSL_library_init();
+SSL_load_error_strings();
+
+ctx = SSL_CTX_new(SSLv23_method());
+if (ctx == NULL)
+  {
+  printf ("SSL_CTX_new failed\n");
+  exit(1);
+  }
+
+if (certfile != NULL)
+  {
+  if (!SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM))
+    {
+    printf("SSL_CTX_use_certificate_file failed\n");
+    exit(1);
+    }
+  printf("Certificate file = %s\n", certfile);
+  }
+
+if (keyfile != NULL)
+  {
+  if (!SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM))
+    {
+    printf("SSL_CTX_use_PrivateKey_file failed\n");
+    exit(1);
+    }
+  printf("Key file = %s\n", keyfile);
+  }
+
+SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH);
+SSL_CTX_set_timeout(ctx, 200);
+SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
+#endif
+
+
+/* --------------- Set up for GnuTLS --------------- */
+
+#ifdef HAVE_GNUTLS
+if (certfile != NULL) printf("Certificate file = %s\n", certfile);
+if (keyfile != NULL) printf("Key file = %s\n", keyfile);
+tls_init(certfile, keyfile);
+tls_session = tls_session_init();
+gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr)sock);
+
+/* When the server asks for a certificate and the client does not have one,
+there is a SIGPIPE error in the gnutls_handshake() function for some reason
+that is not understood. As luck would have it, this has never hit Exim itself
+because it ignores SIGPIPE errors. Doing the same here allows it all to work as
+one wants. */
+
+signal(SIGPIPE, SIG_IGN);
+#endif
+
+/* ---------------------------------------------- */
+
+
+/* Start TLS session if configured to do so without STARTTLS */
+
+#ifdef HAVE_TLS
+if (tls_on_connect)
+  {
+  printf("Attempting to start TLS\n");
+
+  #ifdef HAVE_OPENSSL
+  tls_active = tls_start(sock, &ssl, ctx);
+  #endif
+
+  #ifdef HAVE_GNUTLS
+  sigalrm_seen = FALSE;
+  alarm(timeout);
+  tls_active = gnutls_handshake(tls_session) >= 0;
+  alarm(0);
+  #endif
+
+  if (!tls_active)
+    printf("Failed to start TLS\n");
+  else
+    printf("Succeeded in starting TLS\n");
+  }
+#endif
+
+while (fgets(outbuffer, sizeof(outbuffer), stdin) != NULL)
+  {
+  int n = (int)strlen(outbuffer);
+  while (n > 0 && isspace(outbuffer[n-1])) n--;
+  outbuffer[n] = 0;
+
+  /* Expect incoming */
+
+  if (strncmp(outbuffer, "??? ", 4) == 0)
+    {
+    unsigned char *lineptr;
+    printf("%s\n", outbuffer);
+
+    if (*inptr == 0)   /* Refill input buffer */
+      {
+      if (tls_active)
+        {
+        #ifdef HAVE_OPENSSL
+        rc = SSL_read (ssl, inbuffer, sizeof(inbuffer) - 1);
+        #endif
+        #ifdef HAVE_GNUTLS
+        rc = gnutls_record_recv(tls_session, CS inbuffer, sizeof(inbuffer) - 1);
+        #endif
+        }
+      else
+        {
+        alarm(timeout);
+        rc = read(sock, inbuffer, sizeof(inbuffer));
+        alarm(0);
+        }
+
+      if (rc < 0)
+        {
+        printf("Read error %s\n", strerror(errno));
+        exit(1)  ;
+        }
+      else if (rc == 0)
+        {
+        printf("Unexpected EOF read\n");
+        close(sock);
+        exit(1);
+        }
+      else
+        {
+        inbuffer[rc] = 0;
+        inptr = inbuffer;
+        }
+      }
+
+    lineptr = inptr;
+    while (*inptr != 0 && *inptr != '\r' && *inptr != '\n') inptr++;
+    if (*inptr != 0)
+      {
+      *inptr++ = 0;
+      if (*inptr == '\n') inptr++;
+      }
+
+    printf("<<< %s\n", lineptr);
+    if (strncmp(lineptr, outbuffer + 4, (int)strlen(outbuffer) - 4) != 0)
+      {
+      printf("\n******** Input mismatch ********\n");
+      exit(1);
+      }
+
+    #ifdef HAVE_TLS
+    if (sent_starttls)
+      {
+      if (lineptr[0] == '2')
+        {
+        printf("Attempting to start TLS\n");
+        fflush(stdout);
+
+        #ifdef HAVE_OPENSSL
+        tls_active = tls_start(sock, &ssl, ctx);
+        #endif
+
+        #ifdef HAVE_GNUTLS
+        sigalrm_seen = FALSE;
+        alarm(timeout);
+        tls_active = gnutls_handshake(tls_session) >= 0;
+        alarm(0);
+        #endif
+
+        if (!tls_active)
+          {
+          printf("Failed to start TLS\n");
+          fflush(stdout);
+          }
+        else
+          printf("Succeeded in starting TLS\n");
+        }
+      else printf("Abandoning TLS start attempt\n");
+      }
+    sent_starttls = 0;
+    #endif
+    }
+
+  /* Wait for a bit before proceeding */
+
+  else if (strncmp(outbuffer, "+++ ", 4) == 0)
+    {
+    printf("%s\n", outbuffer);
+    sleep(atoi(outbuffer + 4));
+    }
+
+  /* Send outgoing, but barf if unconsumed incoming */
+
+  else
+    {
+    unsigned char *escape;
+
+    if (*inptr != 0)
+      {
+      printf("Unconsumed input: %s", inptr);
+      printf("   About to send: %s\n", outbuffer);
+      exit(1);
+      }
+
+    #ifdef HAVE_TLS
+
+    /* Shutdown TLS */
+
+    if (strcmp(outbuffer, "stoptls") == 0 ||
+        strcmp(outbuffer, "STOPTLS") == 0)
+      {
+      if (!tls_active)
+        {
+        printf("STOPTLS read when TLS not active\n");
+        exit(1);
+        }
+      printf("Shutting down TLS encryption\n");
+
+      #ifdef HAVE_OPENSSL
+      SSL_shutdown(ssl);
+      SSL_free(ssl);
+      #endif
+
+      #ifdef HAVE_GNUTLS
+      gnutls_bye(tls_session, GNUTLS_SHUT_WR);
+      gnutls_deinit(tls_session);
+      tls_session = NULL;
+      gnutls_global_deinit();
+      #endif
+
+      tls_active = 0;
+      continue;
+      }
+
+    /* Remember that we sent STARTTLS */
+
+    sent_starttls = (strcmp(outbuffer, "starttls") == 0 ||
+                     strcmp(outbuffer, "STARTTLS") == 0);
+
+    /* Fudge: if the command is "starttls_wait", we send the starttls bit,
+    but we haven't set the flag, so that there is no negotiation. This is for
+    testing the server's timeout. */
+
+    if (strcmp(outbuffer, "starttls_wait") == 0)
+      {
+      outbuffer[8] = 0;
+      n = 8;
+      }
+    #endif
+
+    printf(">>> %s\n", outbuffer);
+    strcpy(outbuffer + n, "\r\n");
+
+    /* Turn "\n" and "\r" into the relevant characters. This is a hack. */
+
+    while ((escape = strstr(outbuffer, "\\r")) != NULL)
+      {
+      *escape = '\r';
+      memmove(escape + 1, escape + 2,  (n + 2) - (escape - outbuffer) - 2);
+      n--;
+      }
+
+    while ((escape = strstr(outbuffer, "\\n")) != NULL)
+      {
+      *escape = '\n';
+      memmove(escape + 1, escape + 2,  (n + 2) - (escape - outbuffer) - 2);
+      n--;
+      }
+
+    /* OK, do it */
+
+    alarm(timeout);
+    if (tls_active)
+      {
+      #ifdef HAVE_OPENSSL
+        rc = SSL_write (ssl, outbuffer, n + 2);
+      #endif
+      #ifdef HAVE_GNUTLS
+        rc = gnutls_record_send(tls_session, CS outbuffer, n + 2);
+        if (rc < 0)
+          {
+          printf("GnuTLS write error: %s\n", gnutls_strerror(rc));
+          exit(1);
+          }
+      #endif
+      }
+    else
+      {
+      rc = write(sock, outbuffer, n + 2);
+      }
+    alarm(0);
+
+    if (rc < 0)
+      {
+      printf("Write error: %s\n", strerror(errno));
+      exit(1);
+      }
+    }
+  }
+
+printf("End of script\n");
+close(sock);
+
+exit(0);
+}
+
+/* End of client.c */
diff --git a/test/src/fakens.c b/test/src/fakens.c
new file mode 100644 (file)
index 0000000..9ab7b7e
--- /dev/null
@@ -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 */
diff --git a/test/src/fd.c b/test/src/fd.c
new file mode 100644 (file)
index 0000000..4616f6f
--- /dev/null
@@ -0,0 +1,104 @@
+/* $Cambridge: exim/test/src/fd.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
+
+/* A program to check on open file descriptors. There are some weird options
+for running it in Exim testing. If -q is given, make output suitable for
+queryprogram. If -f is given, copy the input as for a transport filter. If -s
+is given, add extra output from stat(). */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+/* The way of finding out the maximum file descriptor various between OS.
+Most have sysconf(), but a few don't. */
+
+#ifdef _SC_OPEN_MAX
+  #define mac_maxfd (sysconf(_SC_OPEN_MAX) - 1)
+#elif defined OPEN_MAX
+  #define mac_maxfd (OPEN_MAX - 1)
+#elif defined NOFILE
+  #define mac_maxfd (NOFILE - 1)
+#else
+  #define mac_maxfd 255;    /* just in case */
+#endif
+
+
+int main(int argc, char **argv)
+{
+int fd;
+int qpgm = 0;
+int filter = 0;
+int use_stat = 0;
+struct stat statbuf;
+char buffer[8192];
+char *p = buffer;
+
+while (argc > 1)
+  {
+  char *arg = argv[--argc];
+  if (strcmp(arg, "-q") == 0) qpgm = 1;
+  if (strcmp(arg, "-f") == 0) filter = 1;
+  if (strcmp(arg, "-s") == 0) use_stat = 1;
+  }
+
+if (filter)
+  {
+  int len;
+  while ((len = read(0, buffer, sizeof(buffer))) > 0)
+    write(1, buffer, len);
+  }
+
+p += sprintf(p, "max fd = %d\n", (int)mac_maxfd);
+
+for (fd = 0; fd <= mac_maxfd; fd++)
+  {
+  int options = fcntl(fd, F_GETFD);
+  if (options >= 0)
+    {
+    int status = fcntl(fd, F_GETFL);
+    p += sprintf(p, "%3d opt=%d status=%X ", fd, options, status);
+    switch(status & 3)
+      {
+      case 0: p += sprintf(p, "RDONLY");
+      break;
+      case 1: p += sprintf(p, "WRONLY");
+      break;
+      case 2: p += sprintf(p, "RDWR");
+      break;
+      }
+    if (isatty(fd)) p += sprintf(p, " TTY");
+    if ((status & 8) != 0) p += sprintf(p, " APPEND");
+
+    if (use_stat && fstat(fd, &statbuf) >= 0)
+      {
+      p += sprintf(p, " mode=%o uid=%d size=%d", (int)statbuf.st_mode,
+        (int)statbuf.st_uid, (int)statbuf.st_size);
+      }
+
+    p += sprintf(p, "\n");
+    }
+  else if (errno != EBADF)
+    {
+    p += sprintf(p, "%3d errno=%d %s\n", fd, errno, strerror(errno));
+    }
+  }
+
+if (qpgm)
+  {
+  for (p = buffer; *p != 0; p++)
+    if (*p == '\n') *p = ' ';
+  printf("ACCEPT DATA=\"%s\"\n", buffer);
+  }
+else printf("%s", buffer);
+
+exit(0);
+}
+
+/* End */
diff --git a/test/src/iefbr14.c b/test/src/iefbr14.c
new file mode 100644 (file)
index 0000000..959c370
--- /dev/null
@@ -0,0 +1,11 @@
+/* $Cambridge: exim/test/src/iefbr14.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
+
+/* This is a program that does nothing, and returns zero. It is exactly the
+same as the Unix "true" utility. Why do we need it? Well, not all systems have
+"true" in the same place, and sometimes it's a shell built-in. Given that we
+have little utilities anyway, it's just easier to do this. As for its name,
+those with IBM mainframe memories will know where that comes from. */
+
+int main(void) { return 0; }
+
+/* End */
diff --git a/test/src/loaded.c b/test/src/loaded.c
new file mode 100644 (file)
index 0000000..2527384
--- /dev/null
@@ -0,0 +1,42 @@
+/* $Cambridge: exim/test/src/loaded.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
+
+/* This is a test function for dynamic loading in Exim expansions. It uses the
+number of arguments to control the result. */
+
+/* These lines are taken from local_scan.h in the Exim source: */
+
+/* ========================================================================== */
+/* Return codes from the support functions lss_match_xxx(). These are also the
+codes that dynamically-loaded ${dlfunc functions must return. */
+
+#define  OK            0          /* Successful match */
+#define  DEFER         1          /* Defer - some problem */
+#define  FAIL          2          /* Matching failed */
+#define  ERROR         3          /* Internal or config error */
+
+/* Extra return code for ${dlfunc functions */
+
+#define  FAIL_FORCED   4          /* "Forced" failure */
+/* ========================================================================== */
+
+
+int dltest(unsigned char **yield, int argc, unsigned char *argv[])
+{
+switch (argc)
+  {
+  case 0:
+  return ERROR;
+
+  case 1:
+  *yield = argv[0];
+  return OK;
+
+  case 2:
+  *yield = (unsigned char *)"yield FAIL_FORCED";
+  return FAIL_FORCED;
+
+  default:
+  *yield = (unsigned char *)"yield FAIL";
+  return FAIL;
+  }
+}
diff --git a/test/src/mtpscript.c b/test/src/mtpscript.c
new file mode 100644 (file)
index 0000000..6a0b281
--- /dev/null
@@ -0,0 +1,169 @@
+/* $Cambridge: exim/test/src/mtpscript.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
+
+/* A little hacked up program that allows a script to play the part of a remote
+SMTP/LMTP server on stdin/stdout for testing purposes. Hacked from the more
+complicated version that does it over a socket. */
+
+
+/* ANSI C standard includes */
+
+#include <ctype.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/* Unix includes */
+
+#include <errno.h>
+#include <unistd.h>
+
+
+static FILE *log;
+
+
+/*************************************************
+*            SIGALRM handler - crash out         *
+*************************************************/
+
+static void
+sigalrm_handler(int sig)
+{
+sig = sig;    /* Keep picky compilers happy */
+fprintf(log, "Server timed out\n");
+exit(99);
+}
+
+
+
+/*************************************************
+*                 Main Program                   *
+*************************************************/
+
+int main(int argc, char **argv)
+{
+char *logfile;
+char *logmode = "w";
+FILE *script;
+unsigned char sbuffer[1024];
+unsigned char ibuffer[1024];
+
+if (argc < 3)
+  {
+  fprintf(stdout, "500 Script and log file required\n");
+  exit(1);
+  }
+
+/* Get the script and log open */
+
+script = fopen(argv[1], "r");
+if (script == NULL)
+  {
+  fprintf(stdout, "500 Failed to open script %s: %s\r\n", argv[1],
+    strerror(errno));
+  exit(1);
+  }
+
+logfile = argv[2];
+if (logfile[0] == '+')
+  {
+  logfile++;
+  logmode = "a";
+  }
+
+log = fopen(logfile, logmode);
+if (log == NULL)
+  {
+  fprintf(stdout, "500 Failed to open log %s: %s\r\n", logfile,
+    strerror(errno));
+  exit(1);
+  }
+
+/* SIGALRM handler crashes out */
+
+signal(SIGALRM, sigalrm_handler);
+
+/* Read the script, and do what it says. */
+
+while (fgets(sbuffer, sizeof(sbuffer), script) != NULL)
+  {
+  int n = (int)strlen(sbuffer);
+  while (n > 0 && isspace(sbuffer[n-1])) n--;
+  sbuffer[n] = 0;
+
+  /* If the script line starts with a digit, it is a response line which
+  we are to send. */
+
+  if (isdigit(sbuffer[0]))
+    {
+    fprintf(log, "%s\n", sbuffer);
+    fflush(log);
+    fprintf(stdout, "%s\r\n", sbuffer);
+    fflush(stdout);
+    }
+
+  /* If the script line starts with "*sleep" we just sleep for a while
+  before continuing. Do not write this to the log, as it may not get
+  written at the right place in a log that's being shared. */
+
+  else if (strncmp(sbuffer, "*sleep ", 7) == 0)
+    {
+    sleep(atoi(sbuffer+7));
+    }
+
+  /* Otherwise the script line is the start of an input line we are expecting
+  from the client, or "*eof" indicating we expect the client to close the
+  connection. Read command line or data lines; the latter are indicated
+  by the expected line being just ".". */
+
+  else
+    {
+    int data = strcmp(sbuffer, ".") == 0;
+
+    fprintf(log, "%s\n", sbuffer);
+    fflush(log);
+
+    /* Loop for multiple data lines */
+
+    for (;;)
+      {
+      int n;
+      alarm(5);
+      if (fgets(ibuffer, sizeof(ibuffer), stdin) == NULL)
+        {
+        fprintf(log, "%sxpected EOF read from client\n",
+          (strncmp(sbuffer, "*eof", 4) == 0)? "E" : "Une");
+        goto END_OFF;
+        }
+      alarm(0);
+      n = (int)strlen(ibuffer);
+      while (n > 0 && isspace(ibuffer[n-1])) n--;
+      ibuffer[n] = 0;
+      fprintf(log, "<<< %s\n", ibuffer);
+      if (!data || strcmp(ibuffer, ".") == 0) break;
+      }
+
+    /* Check received what was expected */
+
+    if (strncmp(sbuffer, ibuffer, (int)strlen(sbuffer)) != 0)
+      {
+      fprintf(log, "Comparison failed - bailing out\n");
+      goto END_OFF;
+      }
+    }
+  }
+
+/* This could appear in the wrong place in a shared log, so forgo it. */
+/* fprintf(log, "End of script\n"); */
+
+END_OFF:
+fclose(script);
+fclose(log);
+
+exit(0);
+}
+
+/* End of mtpscript.c */
diff --git a/test/src/server.c b/test/src/server.c
new file mode 100644 (file)
index 0000000..f35d80b
--- /dev/null
@@ -0,0 +1,612 @@
+/* $Cambridge: exim/test/src/server.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
+
+/* A little hacked up program that listens on a given port and allows a script
+to play the part of a remote MTA for testing purposes. This scripted version is
+hacked from my original interactive version. A further hack allows it to listen
+on a Unix domain socket as an alternative to a TCP/IP port.
+
+In an IPv6 world, listening happens on both an IPv6 and an IPv4 socket, always
+on all interfaces, unless the option -noipv6 is given. */
+
+/* ANSI C standard includes */
+
+#include <ctype.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/* Unix includes */
+
+#include <errno.h>
+#include <dirent.h>
+#include <sys/types.h>
+
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#ifdef HAVE_NETINET_IP_VAR_H
+#include <netinet/ip_var.h>
+#endif
+
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <utime.h>
+
+#ifdef AF_INET6
+#define HAVE_IPV6 1
+#endif
+
+#ifndef S_ADDR_TYPE
+#define S_ADDR_TYPE u_long
+#endif
+
+
+typedef struct line {
+  struct line *next;
+  char line[1];
+} line;
+
+
+/*************************************************
+*            SIGALRM handler - crash out         *
+*************************************************/
+
+static void
+sigalrm_handler(int sig)
+{
+sig = sig;    /* Keep picky compilers happy */
+printf("\nServer timed out\n");
+exit(99);
+}
+
+
+/*************************************************
+*          Get textual IP address                *
+*************************************************/
+
+/* This function is copied from Exim */
+
+char *
+host_ntoa(const void *arg, char *buffer)
+{
+char *yield;
+
+/* The new world. It is annoying that we have to fish out the address from
+different places in the block, depending on what kind of address it is. It
+is also a pain that inet_ntop() returns a const char *, whereas the IPv4
+function inet_ntoa() returns just char *, and some picky compilers insist
+on warning if one assigns a const char * to a char *. Hence the casts. */
+
+#if HAVE_IPV6
+char addr_buffer[46];
+int family = ((struct sockaddr *)arg)->sa_family;
+if (family == AF_INET6)
+  {
+  struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
+  yield = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
+    sizeof(addr_buffer));
+  }
+else
+  {
+  struct sockaddr_in *sk = (struct sockaddr_in *)arg;
+  yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
+    sizeof(addr_buffer));
+  }
+
+/* If the result is a mapped IPv4 address, show it in V4 format. */
+
+if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
+
+#else /* HAVE_IPV6 */
+
+/* The old world */
+
+yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
+#endif
+
+strcpy(buffer, yield);
+return buffer;
+}
+
+
+/*************************************************
+*                 Main Program                   *
+*************************************************/
+
+#define v6n 0    /* IPv6 socket number */
+#define v4n 1    /* IPv4 socket number */
+#define udn 2    /* Unix domain socket number */
+#define skn 2    /* Potential number of sockets */
+
+int main(int argc, char **argv)
+{
+int i;
+int port = 0;
+int listen_socket[3] = { -1, -1, -1 };
+int accept_socket;
+int dup_accept_socket;
+int connection_count = 1;
+int count;
+int on = 1;
+int timeout = 5;
+int use_ipv4 = 1;
+int use_ipv6 = 1;
+int debug = 0;
+int na = 1;
+line *script = NULL;
+line *last = NULL;
+line *s;
+FILE *in, *out;
+
+char *sockname = NULL;
+unsigned char buffer[10240];
+
+struct sockaddr_un sockun;            /* don't use "sun" */
+struct sockaddr_un sockun_accepted;
+int sockun_len = sizeof(sockun_accepted);
+
+#if HAVE_IPV6
+struct sockaddr_in6 sin6;
+struct sockaddr_in6 accepted;
+struct in6_addr anyaddr6 =  IN6ADDR_ANY_INIT ;
+#else
+struct sockaddr_in accepted;
+#endif
+
+/* Always need an IPv4 structure */
+
+struct sockaddr_in sin4;
+
+int len = sizeof(accepted);
+
+
+/* Sort out the arguments */
+
+while (na < argc && argv[na][0] == '-')
+  {
+  if (strcmp(argv[na], "-d") == 0) debug = 1;
+  else if (strcmp(argv[na], "-t") == 0) timeout = atoi(argv[++na]);
+  else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
+  else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
+  else
+    {
+    printf("server: unknown option %s\n", argv[na]);
+    exit(1);
+    }
+  na++;
+  }
+
+if (!use_ipv4 && !use_ipv6)
+  {
+  printf("server: -noipv4 and -noipv6 cannot both be given\n");
+  exit(1);
+  }
+
+if (na >= argc)
+  {
+  printf("server: no port number or socket name given\n");
+  exit(1);
+  }
+
+if (argv[na][0] == '/')
+  {
+  sockname = argv[na];
+  unlink(sockname);       /* in case left lying around */
+  }
+else port = atoi(argv[na]);
+na++;
+
+if (na < argc) connection_count = atoi(argv[na]);
+
+
+/* Create sockets */
+
+if (port == 0)  /* Unix domain */
+  {
+  if (debug) printf("Creating Unix domain socket\n");
+  listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
+  if (listen_socket[udn] < 0)
+    {
+    printf("Unix domain socket creation failed: %s\n", strerror(errno));
+    exit(1);
+    }
+  }
+else
+  {
+  #if HAVE_IPV6
+  if (use_ipv6)
+    {
+    if (debug) printf("Creating IPv6 socket\n");
+    listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
+    if (listen_socket[v6n] < 0)
+      {
+      printf("IPv6 socket creation failed: %s\n", strerror(errno));
+      exit(1);
+      }
+
+    /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
+    available. */
+
+    #ifdef IPV6_V6ONLY
+    if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
+          sizeof(on)) < 0)
+      printf("Setting IPV6_V6ONLY on IPv6 wildcard "
+        "socket failed (%s): carrying on without it\n", strerror(errno));
+    #endif  /* IPV6_V6ONLY */
+    }
+  #endif  /* HAVE_IPV6 */
+
+  /* Create an IPv4 socket if required */
+
+  if (use_ipv4)
+    {
+    if (debug) printf("Creating IPv4 socket\n");
+    listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
+    if (listen_socket[v4n] < 0)
+      {
+      printf("IPv4 socket creation failed: %s\n", strerror(errno));
+      exit(1);
+      }
+    }
+  }
+
+
+/* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
+while a connection is being handled - this can happen as old connections lie
+around for a bit while crashed processes are tidied away.  Without this, a
+connection will prevent reuse of the smtp port for listening. */
+
+for (i = v6n; i <= v4n; i++)
+  {
+  if (listen_socket[i] >= 0 &&
+      setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
+        sizeof(on)) < 0)
+    {
+    printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
+    exit(1);
+    }
+  }
+
+
+/* Now bind the sockets to the required port or path. If a path, ensure
+anyone can write to it. */
+
+if (port == 0)
+  {
+  struct stat statbuf;
+  sockun.sun_family = AF_UNIX;
+  if (debug) printf("Binding Unix domain socket\n");
+  sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
+  if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
+    {
+    printf("Unix domain socket bind() failed: %s\n", strerror(errno));
+    exit(1);
+    }
+  (void)stat(sockname, &statbuf);
+  if (debug) printf("Setting Unix domain socket mode: %0x\n",
+    statbuf.st_mode | 0777);
+  if (chmod(sockname, statbuf.st_mode | 0777) < 0)
+    {
+    printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
+    exit(1);
+    }
+  }
+
+else
+  {
+  for (i = 0; i < skn; i++)
+    {
+    if (listen_socket[i] < 0) continue;
+
+    /* For an IPv6 listen, use an IPv6 socket */
+
+    #if HAVE_IPV6
+    if (i == v6n)
+      {
+      memset(&sin6, 0, sizeof(sin6));
+      sin6.sin6_family = AF_INET6;
+      sin6.sin6_port = htons(port);
+      sin6.sin6_addr = anyaddr6;
+      if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
+        {
+        printf("IPv6 socket bind() failed: %s\n", strerror(errno));
+        exit(1);
+        }
+      }
+    else
+    #endif
+
+    /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
+    bind fails EADDRINUSE after IPv6 success, carry on, because it means the
+    IPv6 socket will handle IPv4 connections. */
+
+      {
+      memset(&sin4, 0, sizeof(sin4));
+      sin4.sin_family = AF_INET;
+      sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
+      sin4.sin_port = htons(port);
+      if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
+        {
+        if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
+          {
+          printf("IPv4 socket bind() failed: %s\n", strerror(errno));
+          exit(1);
+          }
+        else
+          {
+          close(listen_socket[i]);
+          listen_socket[i] = -1;
+          }
+        }
+      }
+    }
+  }
+
+
+/* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
+error because it means that the IPv6 socket will handle IPv4 connections. Don't
+output anything, because it will mess up the test output, which will be
+different for systems that do this and those that don't. */
+
+for (i = 0; i <= skn; i++)
+  {
+  if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
+    {
+    if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
+      {
+      printf("listen() failed: %s\n", strerror(errno));
+      exit(1);
+      }
+    }
+  }
+
+
+/* This program handles only a fixed number of connections, in sequence. Before
+waiting for the first connection, read the standard input, which contains the
+script of things to do. A line containing "++++" is treated as end of file.
+This is so that the Perl driving script doesn't have to close the pipe -
+because that would cause it to wait for this process, which it doesn't yet want
+to do. The driving script adds the "++++" automatically - it doesn't actually
+appear in the test script. */
+
+while (fgets(buffer, sizeof(buffer), stdin) != NULL)
+  {
+  line *next;
+  int n = (int)strlen(buffer);
+  while (n > 0 && isspace(buffer[n-1])) n--;
+  buffer[n] = 0;
+  if (strcmp(buffer, "++++") == 0) break;
+  next = malloc(sizeof(line) + n);
+  next->next = NULL;
+  strcpy(next->line, buffer);
+  if (last == NULL) script = last = next;
+    else last->next = next;
+  last = next;
+  }
+
+fclose(stdin);
+
+/* SIGALRM handler crashes out */
+
+signal(SIGALRM, sigalrm_handler);
+
+/* s points to the current place in the script */
+
+s = script;
+
+for (count = 0; count < connection_count; count++)
+  {
+  alarm(timeout);
+  if (port <= 0)
+    {
+    printf("Listening on %s ... ", sockname);
+    fflush(stdout);
+    accept_socket = accept(listen_socket[udn],
+      (struct sockaddr *)&sockun_accepted, &sockun_len);
+    }
+
+  else
+    {
+    int lcount;
+    int max_socket = 0;
+    fd_set select_listen;
+
+    printf("Listening on port %d ... ", port);
+    fflush(stdout);
+
+    FD_ZERO(&select_listen);
+    for (i = 0; i < skn; i++)
+      {
+      if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
+      if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
+      }
+
+    lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL);
+    if (lcount < 0)
+      {
+      printf("Select failed\n");
+      fflush(stdout);
+      continue;
+      }
+
+    accept_socket = -1;
+    for (i = 0; i < skn; i++)
+      {
+      if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
+        {
+        accept_socket = accept(listen_socket[i],
+          (struct sockaddr *)&accepted, &len);
+        FD_CLR(listen_socket[i], &select_listen);
+        break;
+        }
+      }
+    }
+  alarm(0);
+
+  if (accept_socket < 0)
+    {
+    printf("accept() failed: %s\n", strerror(errno));
+    exit(1);
+    }
+
+  out = fdopen(accept_socket, "w");
+
+  dup_accept_socket = dup(accept_socket);
+
+  if (port > 0)
+    printf("\nConnection request from [%s]\n", host_ntoa(&accepted, buffer));
+  else
+    {
+    printf("\nConnection request\n");
+
+    /* Linux supports a feature for acquiring the peer's credentials, but it
+    appears to be Linux-specific. This code is untested and unused, just
+    saved here for reference. */
+
+    /**********--------------------
+    struct ucred cr;
+    int cl=sizeof(cr);
+
+    if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
+      printf("Peer's pid=%d, uid=%d, gid=%d\n",
+              cr.pid, cr.uid, cr.gid);
+    --------------*****************/
+    }
+
+  if (dup_accept_socket < 0)
+    {
+    printf("Couldn't dup socket descriptor\n");
+    printf("421 Connection refused: %s\n", strerror(errno));
+    fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
+    fclose(out);
+    exit(2);
+    }
+
+  in = fdopen(dup_accept_socket, "r");
+
+  /* Loop for handling the conversation(s). For use in SMTP sessions, there are
+  default rules for determining input and output lines: the latter start with
+  digits. This means that the input looks like SMTP dialog. However, this
+  doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
+  '>' flags for input and output as well as the defaults. */
+
+  for (; s != NULL; s = s->next)
+    {
+    char *ss = s->line;
+
+    /* Output lines either start with '>' or a digit. In the '>' case we can
+    fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
+    ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
+    connection closedown by ">*eof". */
+
+    if (ss[0] == '>')
+      {
+      char *end = "\r\n";
+      printf("%s\n", ss++);
+
+      if (strncmp(ss, "*eof", 4) == 0)
+        {
+        s = s->next;
+        goto END_OFF;
+        }
+
+      if (*ss == '>')
+        { end = ""; ss++; }
+      else if (strncmp(ss, "CR>", 3) == 0)
+        { end = "\r"; ss += 3; }
+      else if (strncmp(ss, "LF>", 3) == 0)
+        { end = "\n"; ss += 3; }
+
+      fprintf(out, "%s%s", ss, end);
+      }
+
+    else if (isdigit((unsigned char)ss[0]))
+      {
+      printf("%s\n", ss);
+      fprintf(out, "%s\r\n", ss);
+      }
+
+    /* If the script line starts with "*sleep" we just sleep for a while
+    before continuing. */
+
+    else if (strncmp(ss, "*sleep ", 7) == 0)
+      {
+      int sleepfor = atoi(ss+7);
+      printf("%s\n", ss);
+      fflush(out);
+      sleep(sleepfor);
+      }
+
+    /* Otherwise the script line is the start of an input line we are expecting
+    from the client, or "*eof" indicating we expect the client to close the
+    connection. Read command line or data lines; the latter are indicated
+    by the expected line being just ".". If the line starts with '<', that
+    doesn't form part of the expected input. (This allows for incoming data
+    starting with a digit.) */
+
+    else
+      {
+      int offset;
+      int data = strcmp(ss, ".") == 0;
+
+      if (ss[0] == '<')
+        {
+        buffer[0] = '<';
+        offset = 1;
+        }
+      else offset = 0;
+
+      fflush(out);
+
+      for (;;)
+        {
+        int n;
+        alarm(timeout);
+        if (fgets(buffer+offset, sizeof(buffer)-offset, in) == NULL)
+          {
+          printf("%sxpected EOF read from client\n",
+            (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
+          s = s->next;
+          goto END_OFF;
+          }
+        alarm(0);
+        n = (int)strlen(buffer);
+        while (n > 0 && isspace(buffer[n-1])) n--;
+        buffer[n] = 0;
+        printf("%s\n", buffer);
+        if (!data || strcmp(buffer, ".") == 0) break;
+        }
+
+      if (strncmp(ss, buffer, (int)strlen(ss)) != 0)
+        {
+        printf("Comparison failed - bailing out\n");
+        printf("Expected: %s\n", ss);
+        break;
+        }
+      }
+    }
+
+  END_OFF:
+  fclose(in);
+  fclose(out);
+  }
+
+if (s == NULL) printf("End of script\n");
+
+if (sockname != NULL) unlink(sockname);
+exit(0);
+}
+
+/* End of server.c */
diff --git a/test/src/showids.c b/test/src/showids.c
new file mode 100644 (file)
index 0000000..fd4eb20
--- /dev/null
@@ -0,0 +1,26 @@
+/* $Cambridge: exim/test/src/showids.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+int main(void)
+{
+int count, i;
+gid_t grouplist[100];
+
+printf("uid=%d gid=%d euid=%d egid=%d\n",
+  getuid(), getgid(), geteuid(), getegid());
+
+/* Can no longer use this because on different systems, the supplemental
+groups will be different. */
+
+#ifdef NEVER
+printf("supplemental groups: ");
+count = getgroups(100, grouplist);
+for (i = 0; i < count; i++) printf("%d ", grouplist[i]);
+printf("\n");
+#endif
+
+return 0;
+}