#!/usr/local/bin/perl
#
#  $Id: smtpkill,v 1.3 2002/06/11 14:24:57 dgregor Exp $
# 
# Copyright (c) 1998 Daniel J. Gregor, Jr., All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
# 	This product includes software developed by Daniel J. Gregor, Jr.
# 4. The name of Daniel J. Gregor, Jr. may not be used to endorse or promote
#    products derived from this software without specific prior written
#    permission.
# 
# THIS SOFTWARE IS PROVIDED BY DANIEL J. GREGOR, JR. ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL DANIEL J. GREGOR, JR. BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#

use IO::Socket;
use IO::Select;
use FileHandle;
use POSIX;

$usage =
"smtpkill.pl [-p <destination port>] [-s <subject>] [-b <message body>]
	[-i <number of messages per child>] [-w <interval between messages>]
	[-n <number of children>] [-l <length of time to run>] [-v] [-d]
	<host> <from_address> <to_address>";

require 'getopt.pl';
 
chomp($hostname = `hostname`);

$opt_p = 25;
$opt_v = undef; 	# verbose
$opt_d = undef; 	# debug
$opt_q = undef; 	# quiet
$opt_s = "Test message from smtpkill $hostname sent %c (Epoch: %s)";
$opt_b = "Test message";
$opt_B = undef;		# A body file
$opt_w = 0;		# Wait between sending messages
$opt_n = 1;		# Number of children
$opt_l = undef;		# length of time to run
$opt_i = undef;		# Number of messages to send

Getopt('psbwnliB');

die "Invalid number of arguments\nUsage:\t$usage\n" unless
	@ARGV == 3;

if (defined($opt_i) && defined($opt_l)) {
	die "The options -i and -l are mutually exclusive\n";
}

if (defined($opt_B)) {
	open(FILE, "<$opt_B") || die "could not open $opt_B: $!\n";
	$opt_b = join('', <FILE>);
}

$host = shift(@ARGV);
$from_addr = shift(@ARGV);
$to_addr = shift(@ARGV);

die "Number of children be greater than zero\n" unless ($opt_n > 0);

$SIG{'INT'} = \&dokill;

$parentpid = $$;

$parentbegintime = time();

for ($children = 1; $children <= $opt_n; $children++) {
	warn "Starting child ${children}.\n" if $opt_v;
	unless (($childpid = fork())) {	# this is the child
		$begintime = time();

		$SIG{'INT'} = \&statsdie;

		for ($attempts = $count = 0;
				defined($opt_i) ? ($count < $opt_i) : 1;
				$attempts++) {
			sendmessage() && $count++;
		}

		statsdie();
	}

	push(@children, $childpid);
}

warn "All children started.\n" if $opt_v;

warn "*** Hit control-C to end test ***\n";

if ($opt_l) {
	sleep($opt_l);
} else {
	while (@children > 0) {
		wait();
		for ($i = 0; $i < @children; $i++) {
			if (!kill(0, $children[$i])) {
				warn "Child $i [" . $children[$i] . "] gone\n"
					if $opt_d;
				splice(@children, $i, 1);
			}
		}
	}
}

dokill();


sub dokill {
	warn "Killing children\n" if $opt_v;
	kill INT, @children;
	printf "user: %s, system: %s, children user: %s, children system: %s\n",
		times();
	printf "Messages sent in %s seconds\n", time() - $parentbegintime;
	exit(0);
}

sub statsdie {
	$timelength = time() - $begintime;
	print "$$: made $attempts attempts, sent $count messages in $timelength seconds\n";
	exit(0);
}

sub sendmessage {
	if (!defined($smtp = new IO::Socket::INET (
			PeerAddr => $host,
			PeerPort => $opt_p,
			Proto => 'tcp',
			))) {
		warn "[$$] Error creating socket: $!\n";
		sleep (1 + $opt_w);
		return 0;
	}

	$smtp->autoflush(1);

	print("[$$] Connected to host " . $host . ".\n") if $opt_d;

	$status = $smtp->getline();

	chomp($status);
	if (!($status =~ m/^2\d\d/)) {
		warn "[$$] Connect status: $status\n";
		sleep (1 + $opt_w);
		return 0;
	}
	print("[$$] Host said hi: $status\n") if $opt_d;
		
	$hostname = `hostname`;
	chomp($hostname);
	smtpcommand("HELO $hostname", 'Saying HELO') || return 0;
	smtpcommand("MAIL FROM: <$from_addr>", 'MAIL FROM') || return 0;
	smtpcommand("RCPT TO: <$to_addr>", 'RCPT TO') || return 0;

	smtpcommand("DATA", 'DATA') || return 0;
	$smtp->print("From: <$from_addr>\r\n");
	$smtp->print("To: <$to_addr>\r\n");
	$smtp->print("Subject: " . POSIX::strftime($opt_s, localtime()) .
		"\r\n");
	$smtp->print("\r\n");
	$smtp->print($opt_b);
	$smtp->print("\r\n.\r\n");

	smtpcommand("QUIT", 'QUIT') || return 0;

	sleep($opt_w);

	return 1;
}


sub smtpcommand {
	$command = shift(@_);
	$msg = shift(@_);

	$smtp->print("$command\r\n");

	$status = $smtp->getline();
	chomp($status);
	if (!($status =~ m/^[23]\d\d/)) {
		warn "[$$] Error response returned while $msg: $status\nWe said: $command\n";
		return 0;
	}
	print("[$$] $msg: $status\n") if $opt_d;

	return 1;
}
