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