#!/usr/bin/perl -w
# relaytest.pl -- Test for open relays
# Author: B.J. van Buuren <bvbuuren@bigfoot.com>
# License: GPL v2
# $Id: relaytest.pl,v 1.3 2007/05/31 20:18:47 jacco Exp $
use strict;
use vars qw(@ARGV);
require Net::SMTP;
use Getopt::Std;

# -- Globals & stuff

my(%option);
my($DEBUG) = 0;
my($NOSUMMARY) = 0;
my($VERSION) = '\$Id: relaytest.pl,v 1.3 2007/05/31 20:18:47 jacco Exp $';
my($SCRIPT) = $0;

my($MAILFROMUSER) = "spamtest";
my($TESTADDRFILE);

my($RCPTADDR);
my($RCPTDOMAIN);

my($HOSTNAME);
my($HOSTIPADDR) = "127.0.0.1";
my($TIMEOUT) = 60;
my($SLEEP) = 2;
my(@TESTADDR);

my($BARF) = join '', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64, rand 64, rand 64, rand 64];

# -- Functions

sub usage
{
	print <<__EOF__;
$SCRIPT -- Test a MTA for open relaying

SYNTAX


$SCRIPT [-h] [-d] -H host -I host-ipaddr -r rcpt-name -R rcpt-domain
	[-t timeout] [-u mail-from] [-s sleep] [-f config-file]
	[-b random-string]


PARAMETERS

-h	This message.
-d	Add debugging info.
-H	Name (or ip address) of host to test.
-I	Ip-address of host to test. Normaly you\'d expect hostname
	to suffice, but this allows spoofing. eg. test for a backend
	that is allowed to relay.
	Default: $HOSTIPADDR
-r	Username of person to try to send mail to.
-R	Domainname part of -r.
-t	Time in seconds. When to timeout. Don\'t set this to low.
	Default: $TIMEOUT
-u	The user to blame these tests for. Useful for filtering
	in your e-mail client.
	Default: $MAILFROMUSER
-q	Don\'t summarise the succesful tests at the end.
-s	Time in seconds. Wait between tests.
	Default: $SLEEP
-f	Configuration file to use instead of the hardcoded default
	tests. See FILES below.
-b	A random string used in some tests.


EXAMPLES

Test internal server \'barney\' with address 10.10.10.10 and try
send a message to you\@yoursite.com as mrX

$SCRIPT -H barney -I 10.10.10.10 -r you -R yoursite.com -u mrX


Test server \'194.171.240.20\' with address rfc1918 192.168.1.1 and try
send a message to ray671\@hotmail.com

$SCRIPT -H 194.171.240.20 -I 192.168.1.1 -r ray671 -R hotmail.com


FILES

A configuration file can be used to override the default hardcoded
tests, eg. add whole rfc1918 ranged addresses for really thourogh
testing.

The configuration file format is "sender" "recipient" -based.
There are no space characters allowed in either sender or recepient.
If any of the following keywords are used in a configuration-file,
they will be replaced with values from the commandline:
MAILFROMUSER with that from the -u parameter, HOSTNAME with -H,
HOSTIPADDR with -I, RCPTADDR with -r, RCPTDOMAIN with -R. BARF
is a 5 character random string or replaced with that from -b.
Everything else in the file is taken "as is" and fed to the
SMTP host - provided Net::SMTP can take it.


BUGS

Probably too much to mention here, better take a look at the code.


Author

B.J. van Buuren <bvbuuren\@bigfoot.com>


VERSION

$VERSION

__EOF__
	return;
}

sub parse_testaddress_file
{
	@TESTADDR = ();
	open(FILE,"<$TESTADDRFILE\0") or die "Failed to read $TESTADDRFILE\n$!\n";
	while(<FILE>) {
		chomp();
		s/#.*//;
		s/^\s+//;
		s/\s+$$//;
		next unless length;
		s/HOSTNAME/$HOSTNAME/g;
		s/HOSTIPADDR/$HOSTIPADDR/g;
		s/RCPTADDR/$RCPTADDR/g;
		s/RCPTDOMAIN/$RCPTDOMAIN/g;
		s/MAILFROMUSER/$MAILFROMUSER/g;
		s/BARF/$BARF/g;
		($TESTADDR[++$#TESTADDR], $TESTADDR[++$#TESTADDR]) = split(" ");
	}
	die "Odd number of addresses, missing a sender or recepient" if ( (($#TESTADDR+1) % 2) ne 0 );
	return;
}

# -- Parameter parsing

getopts("hdH:I:r:R:u:t:qf:s:b:", \%option);

usage, exit 0 if ( $option{h} );
$DEBUG = 1 if ( $option{d} );
$HOSTNAME = $option{H} if ( $option{H} );
$HOSTIPADDR = $option{I} if ( $option{I} );
$RCPTADDR = $option{r} if ( $option{r} );
$RCPTDOMAIN = $option{R} if ( $option{R} );
$MAILFROMUSER = $option{u} if ( $option{u} );
$TIMEOUT = $option{t} if ( $option{t} );
$NOSUMMARY = 1 if ( $option{q} );
$TESTADDRFILE = $option{f} if ( $option{f} );
$SLEEP = $option{s} if ( $option{s} );
$BARF = $option{b} if ( $option{b} );

# -- Sanity checking

die "No hostname\n" unless ( defined($HOSTNAME) );
die "No host ip address\n" unless ( defined($HOSTIPADDR) );
die "No recepientdomain\n" unless ( defined($RCPTDOMAIN) );
die "No recepient\n" unless ( defined($RCPTADDR) );
die "Timeout \"$TIMEOUT\" not numeric\n" if ( $TIMEOUT =~ /\D+/ );
die "Sleep \"$SLEEP\" not numeric\n" if ( $SLEEP =~ /\D+/ );

# -- Init

@TESTADDR = (
	"<postmaster\@$HOSTNAME>",		"<$RCPTADDR\@$RCPTDOMAIN>",
	"<$MAILFROMUSER\@[$HOSTIPADDR]>",	"<\@$HOSTNAME:$RCPTADDR\@$RCPTDOMAIN>",
	"<$MAILFROMUSER\@$BARF\.$RCPTDOMAIN>",	"<$RCPTADDR\@$RCPTDOMAIN>",
        "<$MAILFROMUSER\@$RCPTDOMAIN>",		"<$RCPTADDR\@$RCPTDOMAIN>",
        "<$MAILFROMUSER>",			"<$RCPTADDR\@$RCPTDOMAIN>",
        "<>",					"<$RCPTADDR\@$RCPTDOMAIN>",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<$RCPTADDR\@$RCPTDOMAIN>",
        "<$MAILFROMUSER\@[$HOSTIPADDR]>",	"<$RCPTADDR\@$RCPTDOMAIN>",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<$RCPTADDR%$RCPTDOMAIN\@$HOSTNAME>",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<$RCPTADDR%$RCPTDOMAIN\@[$HOSTIPADDR]>",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<\"$RCPTADDR\@$RCPTDOMAIN\">",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<\"$RCPTADDR\%$RCPTDOMAIN\">",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<$RCPTADDR\@$RCPTDOMAIN\@$HOSTNAME>",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<\"$RCPTADDR\@$RCPTDOMAIN\"\@$HOSTNAME>",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<$RCPTADDR\@$RCPTDOMAIN\@[$HOSTIPADDR]>",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<\@$HOSTNAME:$RCPTADDR\@$RCPTDOMAIN>",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<\@[$HOSTIPADDR]:$RCPTADDR\@$RCPTDOMAIN>",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<$RCPTDOMAIN!$RCPTADDR>",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<$RCPTDOMAIN!$RCPTADDR\@$HOSTNAME>",
        "<$MAILFROMUSER\@$HOSTNAME>",		"<$RCPTDOMAIN!$RCPTADDR\@[$HOSTIPADDR]>"
);
parse_testaddress_file if ( defined($TESTADDRFILE) );

# -- Main

my($i,$j,$count) = 0;
my($total) = ($#TESTADDR+1) / 2;
my($smtp);
my($from, $to) = "";
my($banner) = "";
my(@SUCCEEDED);
for($i=0;$i<=$#TESTADDR;$i+=2) {
	++$count;
	$from = $TESTADDR[$i];
	$to = $TESTADDR[$i+1];
	$banner = "Test $count of $total -- ";
	print($banner . "from: $from to: $to\n") if ($DEBUG == 0);
	close($smtp) if (defined($smtp));
	sleep($SLEEP);
	$smtp = Net::SMTP->new("$HOSTNAME",
				Hello => "$RCPTDOMAIN",
				Timeout => $TIMEOUT,
				Debug   => $DEBUG,
			);
	if ( $smtp->mail($from) != 1 ) {
		print($banner . "FAILED -- $HOSTNAME didn\'t accept mail from $from\n");
		next;
	}
	if ( $smtp->to($to) != 1 ) {
		print($banner . "FAILED -- $HOSTNAME didn\'t accept rcpt to $to\n");
		next;
	}
	if ( $smtp->data() != 1 ) {
		print($banner . "FAILED -- $HOSTNAME didn\'t accept init body data\n");
		next;
	}
	if ( $smtp->datasend("Subject: Test $count\n") != 1 ) {
		print($banner . "FAILED -- $HOSTNAME didn\'t accept body data\n");
		next;
	}
	if ( $smtp->datasend("\n") != 1 ) {
		print($banner . "FAILED -- $HOSTNAME didn\'t accept body data\n");
		next;
	}
	if ( $smtp->datasend("spamtest $count: mail from: $from, rcpt to: $to\n") != 1 ) {
		print($banner . "FAILED -- $HOSTNAME didn\'t accept body data\n");
		next;
	}
	if ( $smtp->dataend() != 1 ) {
		print($banner . "FAILED -- $HOSTNAME didn\'t accept end body data\n");
		next;
	}
	if ( $smtp->quit() != 1 ) {
		print($banner . "FAILED -- $HOSTNAME didn\'t accept quit\n");
		next;
	}
	print($banner . "SUCCEEDED\n");
	$SUCCEEDED[++$#SUCCEEDED] = "$count: mail from: $from, rcpt to :$to";
}

print("\n");

if ( $NOSUMMARY != 0 ) {
	print("Done\n");
	exit;
}

if ( $#SUCCEEDED >= 0) {
	print("The following tests were succesful:\n");
	foreach(@SUCCEEDED) {
		print("\t$_\n");
	}
}
else {
	print("All tests performed, no relays accepted.\n");
}
print("Done\n");



# -- End
