#!/usr/bin/perl -w
#
# try to connect to a particular port on a bunch of hosts using
# socks proxy to connect through. For use with "mon".
#
# Options are
#   -p <port-num>
#   -t <connect-timeout-in-seconds> (default 15)
#   -s <string to send upon connecting to provoke some output>
#   -e <Perl regexp to expect in response>
#   -q <string to send before closing after parsing response>
#   -d <string to use as line delimiter for regexp matching>
#   -S <socks server to connect to>
#   -P <socks server port> (1080 by default)
#   -v verbose (dump lot of debugging inforations)

# without /-s/-e/-q/, just checks that the socket can be opened
# and closed.

# cheap transformations done on send/quit/delim strings - \r and \n are
# converted to CR and LF.  \\ is not supported - no escape possible.

# sample usage:
#
# smtp:    socksch.monitor -p 25  -e '^220\b' -q 'QUIT\r\n'
# web:     socksch.monitor -p 80  -s 'GET / HTTP/1.0\r\n\r\n' -e '^HTTP.*200 OK'


#
# Jim Trocki, trockij@transmeta.com
# updated August 2000 by Ed Ravin <eravin@panix.com> for send/expect/quit
# updated July 2002 by Dobrica Pavlinusic <dpavlin@rot13.org> to use
# 	socks 4|5 servers
#
#    Copyright (C) 1998, Jim Trocki
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
use Getopt::Std;
use Socket;

use strict;
use Net::SOCKS;

my %opt;
getopts ("d:p:t:s:e:q:S:P:v", \%opt);
my $USAGE= "Usage: socksch.monitor -p port [-t timeout] [-s sendstr] [-e regexp] [-q quitstr] [-d line-delim] [-S socks server] [-P socks port] [-v]\n";

my $PORT = $opt{"p"} || undef;
my $TIMEOUT = $opt{"t"} || 15;

my $SEND=   $opt{"s"} || undef;
my $EXPECT= $opt{"e"} || undef;
my $QUITSTR=$opt{"q"} || undef;
my $DELIM=  $opt{"d"} || "\n";
my $SOCKS_SERVER = $opt{"S"} || "socks.pliva.hr";
my $SOCKS_PORT = $opt{"P"} || 1080;
my $verbose = $opt{"v"} || 0;

if ($DELIM)
{
	$DELIM=~ s/\\n/\n/g;
	$DELIM=~ s/\\r/\r/g;
}

my @failures = ();
my @detail = ();

my $ALARM = 0;

sub checkbuf  # buffer, regexp
{
	my ($buffer, $regexp)= @_;

	return $buffer =~ /$regexp/ if ($DELIM eq '');

	my @lines= split($DELIM, $buffer);

	foreach my $line (@lines)
	{
		if ($line =~ /$regexp/)
		{
			return 1;
		}
	}
	return 0;
}

die $USAGE unless (@ARGV > 0);
die "$0: missing port number\n" unless defined $PORT;

foreach my $host (@ARGV) {

	push @detail, "(debug) testing $host";

	my $sock = new Net::SOCKS(socks_addr => $SOCKS_SERVER,
		socks_port => $SOCKS_PORT,
		#user_id => 'the_user',
		#user_password => 'the_password',
		#force_nonanonymous => 1, 
		protocol_version => 5);

	if (!defined $sock) {
		die "(local err) could not create socks socket: $!\n";
	} elsif ($verbose) {
		push @detail, "(debug) connected to socks server $SOCKS_SERVER:$SOCKS_PORT";
	}

	my $r;

	eval {
		local $SIG{"ALRM"} = sub { die "alarm\n" };

		alarm $TIMEOUT;

	        $r = $sock->connect(peer_addr => $host, peer_port => $PORT);

		push @detail, "(debug) connect status: ".Net::SOCKS::status_message($sock->param('status_num')) if ($verbose);

		if ($sock->param('status_num') != SOCKS_OKAY) {
    			die "(local err) could not connect to peer $host port $PORT: $!\n";
		}

		alarm 0;
	};

	if ($@) {
		push @failures, $host;

		if ($@ eq "alarm\n") {
			push @detail, "$host timeout on connect";
		} else {
			push @detail, "$host interrupted syscall on connect: $!";
		}

		$sock->close();
		next;
	}

	if (!defined $r) {
		push @failures, $host;
		push @detail, "$host: could not connect: $!";
		$sock->close();
		next;
	}

	if (defined($SEND)) {
		my $rc= undef;

		$SEND=~ s/\\n/\n/g;
		$SEND=~ s/\\r/\r/g;
		eval {
			local $SIG{"ALRM"} = sub { die "alarm\n" };

			alarm $TIMEOUT;
			push @detail, "(debug) sending '$SEND'" if ($verbose);
			print $r $SEND;
			alarm 0;
		    };
	if ($@) {
		push @failures, $host;

		if ($@ eq "alarm\n") {
				push @detail, "$host timeout on write";
			} else {
				push @detail, "$host interrupted syscall on write: $!";
			}
		}
	}

	if (defined($EXPECT)) {
		# read and match

		my $alldata= "";

		eval {
			local $SIG{"ALRM"} = sub { die "alarm\n" };

			alarm $TIMEOUT;

			push @detail, "(debug) expecting '$EXPECT'" if ($verbose);
			while (<$r>) {
				$alldata .= $_;
				last if (checkbuf($alldata,  $EXPECT));
			}
			alarm 0;
		};

		if ($@) {
			push @failures, $host;

			if ($@ eq "alarm\n") {
				push @detail, "$host timeout on read";
			} else {
				push @detail, "$host interrupted syscall on read: $!";
			}
		}

		if (! checkbuf($alldata, $EXPECT)) {
			push @failures, $host;
			push @detail, "$host: did not recv expected response";
			$sock->close();
			next;
		}
	}

	if (defined($QUITSTR)) {
		my $rc= undef;

		$QUITSTR=~ s/\\n/\n/g;
		$QUITSTR=~ s/\\r/\r/g;

		eval {
			local $SIG{"ALRM"} = sub { die "alarm\n" };

			alarm $TIMEOUT;

			push @detail, "(debug) sending '$QUITSTR'" if ($verbose);
			print $r $QUITSTR;
			alarm 0;
		    };
	if ($@) {
		push @failures, $host;

		if ($@ eq "alarm\n") {
				push @detail, "$host timeout writing quitstr";
			} else {
				push @detail, "$host interrupted syscall writing quitstr: $!";
			}
		}
	}

	if (! $sock->close()) {
		push @failures, $host;
		push @detail, "$host: could not close socket: $!";
		next;
	}
}

if (@failures > 0 || $verbose) {
	print "@failures\n";
	print "\n", join ("\n", @detail), "\n";
}

if (@failures == 0) {
	exit 0;
}

exit 1;
