From c55a77db55ebf46a399f136eeb3a928b1e862772 Mon Sep 17 00:00:00 2001 From: Philip Hazel Date: Mon, 6 Feb 2006 16:22:56 +0000 Subject: [PATCH] CVSing the test suite. --- test/dnszones-src/db.ip4.10 | 19 + test/dnszones-src/db.ip4.127 | 10 + test/dnszones-src/db.ip4.V4NET | 60 ++ test/dnszones-src/db.ip6.0 | 11 + test/dnszones-src/db.ip6.V6NET | 12 + test/dnszones-src/db.test.ex | 360 ++++++++++++ test/dnszones-src/qualify.test.ex | 5 + test/src/cf.c | 715 ++++++++++++++++++++++++ test/src/checkaccess.c | 41 ++ test/src/client.c | 898 ++++++++++++++++++++++++++++++ test/src/fakens.c | 588 +++++++++++++++++++ test/src/fd.c | 104 ++++ test/src/iefbr14.c | 11 + test/src/loaded.c | 42 ++ test/src/mtpscript.c | 169 ++++++ test/src/server.c | 612 ++++++++++++++++++++ test/src/showids.c | 26 + 17 files changed, 3683 insertions(+) create mode 100644 test/dnszones-src/db.ip4.10 create mode 100644 test/dnszones-src/db.ip4.127 create mode 100644 test/dnszones-src/db.ip4.V4NET create mode 100644 test/dnszones-src/db.ip6.0 create mode 100644 test/dnszones-src/db.ip6.V6NET create mode 100644 test/dnszones-src/db.test.ex create mode 100644 test/dnszones-src/qualify.test.ex create mode 100644 test/src/cf.c create mode 100644 test/src/checkaccess.c create mode 100644 test/src/client.c create mode 100644 test/src/fakens.c create mode 100644 test/src/fd.c create mode 100644 test/src/iefbr14.c create mode 100644 test/src/loaded.c create mode 100644 test/src/mtpscript.c create mode 100644 test/src/server.c create mode 100644 test/src/showids.c 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 +#include + +#ifdef __STDC__ +#include +#include +#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(" ) files to compare\n"); +printf(" ) no keywords used\n"); +printf("-to 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 set re-sync count, default 3\n"); +printf("-buffer 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, "... ...\n"); + rule('-', 10); + if (-t2-s2 < 21) write_lines(rootline_two, tline_two); + else fprintf(f_out, "... ...\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 +#include +#include +#include +#include +#include + +#include + + +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 +#include +#include +#include +#include +#include +#include +#include + +/* Unix includes */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#endif + + +#ifdef HAVE_GNUTLS +#define HAVE_TLS +#include +#include + +#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 + + + [] + [] + [] +*/ + +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include + + +/* 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 +#include +#include +#include +#include +#include +#include +#include + +/* Unix includes */ + +#include +#include + + +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 +#include +#include +#include +#include +#include +#include +#include + +/* Unix includes */ + +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_NETINET_IP_VAR_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + +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; +} -- 2.25.1