#!/usr/bin/perl
use strict;
use POSIX qw(strftime);
my $VERSION = '0.7.3';
my $COPYRIGHT = 'Copyright (C) 2005-2011 Jonathan Buhacoff <jonathan@buhacoff.net>';
my $LICENSE = 'http://www.gnu.org/licenses/gpl.txt';
my %status = ( 'OK' => 0, 'WARNING' => 1, 'CRITICAL' => 2, 'UNKNOWN' => 3 );

# look for required modules
exit $status{UNKNOWN} unless load_modules(qw/Getopt::Long Net::SMTP/);

BEGIN {
	if( grep { /^--hires$/ } @ARGV ) {
		eval "use Time::HiRes qw(time);";
		warn "Time::HiRes not installed\n" if $@;
	}
}

Getopt::Long::Configure("bundling");
my $verbose = 0;
my $help = "";
my $help_usage = "";
my $show_version = "";
my $smtp_server = "";
my $default_smtp_port = "25";
my $default_smtp_ssl_port = "465";
my $default_smtp_tls_port = "587";
my $smtp_port = "";
my @mailto = ();
my $mailfrom = "";
my @header = ();
my $body = "";
my $stdin = "";
my $template = "";
my $expect_response = "250";
my $warntime = 15;
my $criticaltime = 30;
my $timeout = 60;
my $tls = 0;
my $ssl = 0;
my $auth_method = undef;
my $username = "";
my $password = "";
my $time_hires = "";
my $mx_lookup = 0;
my $ok;
$ok = Getopt::Long::GetOptions(
	"V|version"=>\$show_version,
	"v|verbose+"=>\$verbose,"h|help"=>\$help,"usage"=>\$help_usage,
	"w|warning=i"=>\$warntime,"c|critical=i"=>\$criticaltime,"t|timeout=i"=>\$timeout,
	# smtp settings
	"H|hostname=s"=>\$smtp_server,"p|port=i"=>\$smtp_port,
	"mailto=s"=>\@mailto, "mailfrom=s",\$mailfrom,
	"header=s"=>\@header, "body=s"=>\$body,	"stdin"=>\$stdin,
	"template!"=>\$template,
	# SSL/TLS/auth options
	"tls!"=>\$tls, "ssl!"=>\$ssl, "auth=s"=>\$auth_method,
	"U|username=s"=>\$username,"P|password=s"=>\$password,
	# Server response
	"E|expect-response=s"=>\$expect_response,
	# Time
	"hires"=>\$time_hires,
	);

if( $show_version ) {
	print "$VERSION\n";
	if( $verbose ) {
		print "Default warning threshold: $warntime seconds\n";
		print "Default critical threshold: $criticaltime seconds\n";
		print "Default timeout: $timeout seconds\n";
	}
	exit $status{UNKNOWN};
}

if( $help ) {
	exec "perldoc", $0 or print "Try `perldoc $0`\n";
	exit $status{UNKNOWN};
}

if( $smtp_server eq "" && scalar(@mailto) == 1 ) {
	# no SMTP server specified but one mailto address given means we can look up the MX record
	$mx_lookup = 1;
}

my @required_module = ();
push @required_module, 'Net::SMTP::SSL' if $ssl;
push @required_module, ('MIME::Base64','Authen::SASL') if $ssl && $username;
push @required_module, 'Net::SMTP::TLS' if $tls;
push @required_module, 'Net::SMTP_auth' if $auth_method and not $tls; # whereas if auth_method and tls we use TLS_auth, which is included in this script!
push @required_module, 'Text::Template' if $template;
push @required_module, 'Net::DNS' if $mx_lookup;
push @required_module, 'Email::Address' if $mx_lookup;
exit $status{UNKNOWN} unless load_modules(@required_module);


# split up @mailto if commas were used instead of multiple options
@mailto = split(/,/,join(',',@mailto));

if( $help_usage ||
	(
	($smtp_server eq "" && !$mx_lookup) || scalar(@mailto)==0 || $mailfrom eq ""
	) 
  ) {
	print "Usage: $0 [-H host [-p port]] --mailto recipient\@your.net [--mailto recipient2\@your.net ...] --mailfrom sender\@your.net --body 'some text' [-w <seconds>] [-c <seconds>]\n";
	exit $status{UNKNOWN};
}

# initialize
my $report = new PluginReport;
my $time_start = time;
my $actual_response = undef;
my @warning = ();
my @critical = ();

my $smtp_debug = 0;
$smtp_debug = 1 if $verbose >= 3;

# default date and message id headers
push @header, default_date_header() unless find_header("Date",@header);
push @header, default_messageid_header() unless find_header("Message-ID",@header);

# look up MX server if necessary
if( $mx_lookup ) {
	my $addr = Email::Address->new( undef, $mailto[0] );
	my $mx_domain = $addr->host;
	print "MX lookup " . $mx_domain . "\n" if $verbose > 1;
	my $res = Net::DNS::Resolver->new;
	my @mx = Net::DNS::mx($res, $mx_domain);
	if( @mx ) {
		# use the first server
		foreach my $rr (@mx) {
			print "pref : " . $rr->preference . " exchange: " . $rr->exchange . "\n" if $verbose > 2;			
		}
		$smtp_server = $mx[0]->exchange;
		print "smtp server: $smtp_server\n" if $verbose;
	}
	else {
		print "SMTP SEND CRITICAL - Cannot find MX records for $mx_domain\n";
		exit $status{CRITICAL};			
	}
}

# connect to SMTP server
# create the smtp handle using Net::SMTP, Net::SMTP::SSL, Net::SMTP::TLS, or an authentication variant
my $smtp; 
eval {
	if( $tls and $auth_method ) {
		$smtp_port = $default_smtp_tls_port unless $smtp_port;
		$smtp = TLS_auth->new($smtp_server, Timeout=>$timeout, Port=>$smtp_port, User=>$username, Password=>$password, Auth_Method=>$auth_method);
		if( $smtp ) {
			my $message = oneline($smtp->message());
			die "cannot connect with TLS/$auth_method: $message" if $smtp->code() =~ m/53\d/;
		}
	}
	elsif( $tls ) {
		$smtp_port = $default_smtp_tls_port unless $smtp_port;
		$smtp = Net::SMTP::TLS->new($smtp_server, Timeout=>$timeout, Port=>$smtp_port, User=>$username, Password=>$password);
		if( $smtp ) {
			my $message = oneline($smtp->message());
			die "cannot connect with TLS: $message" if $smtp->code() =~ m/53\d/;
		}
	}
	elsif( $ssl ) {
		$smtp_port = $default_smtp_ssl_port unless $smtp_port;
		$smtp = Net::SMTP::SSL->new($smtp_server, Port => $smtp_port, Timeout=>$timeout,Debug=>$smtp_debug);
		if( $smtp && $username )  {
			$smtp->auth($username, $password);
			my $message = oneline($smtp->message());
			die "cannot connect with SSL/password: $message" if $smtp->code() =~ m/53\d/;
		}	
	}
	elsif( $auth_method ) {
		$smtp_port = $default_smtp_port unless $smtp_port;
		$smtp = Net::SMTP_auth->new($smtp_server, Port=>$smtp_port, Timeout=>$timeout,Debug=>$smtp_debug);	
		if( $smtp ) {
			$smtp->auth($auth_method, $username, $password);
			my $message = oneline($smtp->message());
			die "cannot connect with SSL/$auth_method: $message" if $smtp->code() =~ m/53\d/;
		}			
	}
	else {
		$smtp_port = $default_smtp_port unless $smtp_port;
		$smtp = Net::SMTP->new($smtp_server, Port=>$smtp_port, Timeout=>$timeout,Debug=>$smtp_debug);	
		if( $smtp && $username ) {
			$smtp->auth($username, $password);
			my $message = oneline($smtp->message());
			die "cannot connect with password: $message" if $smtp->code() =~ m/53\d/;
		}	
	}
};
if( $@ ) {
	$@ =~ s/\n/ /g; # the error message can be multiline but we want our output to be just one line
	print "SMTP SEND CRITICAL - $@\n";
	exit $status{CRITICAL};	
}
unless( $smtp ) {
	print "SMTP SEND CRITICAL - Could not connect to $smtp_server port $smtp_port\n";
	exit $status{CRITICAL};
}
my $time_connected = time;

# add the monitored server's banner to the report
if( $tls ) {
	$report->{banner} = "";	
}
elsif( $ssl ) {
	$report->{banner} = $smtp->banner || "";
	chomp $report->{banner};		
}
else {
	$report->{banner} = $smtp->banner || "";
	chomp $report->{banner};	
}


# send email
if( $stdin ) {
	$body = "";
	while(<STDIN>) {
		$body .= $_;
	}
}

# if user wants to use template substitutions, this is the place to process body and headers
if( $template ) {
	foreach my $item (@header,$body) {
		my $t = Text::Template->new(TYPE=>'STRING',SOURCE=>$item,PACKAGE=>'SmtpMessageTemplate');
		$item = $t->fill_in(PREPEND=>q{package SmtpMessageTemplate;});
#		print "item: $item\n";
	}
}


$smtp->mail($mailfrom);
foreach( @mailto ) {
	# the two SMTP modules have different error reporting mechanisms:
	if( $tls ) {
		# Net::SMTP::TLS croaks when the recipient is rejected
		eval {
			$smtp->to($_);			
		};
		if( $@ ) {
			print "SMTP SEND CRITICAL - Could not send to $_\n";
			print "Reason: $@\n" if $verbose;
			exit $status{CRITICAL};			
		}
	}
	else {
		# Net::SMTP returns false when the recipient is rejected
		my $to_returned = $smtp->to($_);
		if( !$to_returned ) { 
			print "SMTP SEND CRITICAL - Could not send to $_\n";
			print "Reason: Recipient rejected or authentication failed\n" if $verbose;
			exit $status{CRITICAL};
		}
	}
}

# Net::SMTP::TLS doesn't implement code() so we need to wrap calls in eval to get our error messages

	# start data transfer (expect response 354)
	$smtp->data();
	
	# send data
	$smtp->datasend("To: ".join(", ",@mailto)."\n");
	$smtp->datasend("From                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      