package Monitor::tcp;

require 5.002;
require Exporter;

use strict;
use vars qw(@ISA @EXPORT $VERSION);
use Socket qw( SOCK_STREAM PF_INET inet_aton sockaddr_in );
use FileHandle;
use Carp;

@Monitor::tcp::ISA = qw(Exporter Monitor::Monitor);

%Monitor::tcp::known_ports = (
    "pop3", 110,
    "smtp", 25,
    "ica", 1494,
    "nntp", 119,
    "smb", 139,
    "dns", 53,
    "ssh", 22,
    "telnet", 23,
    "time", 37,
    "bootps", 67,
    "gopher", 70,
    "http", 80,
    "kerberos", 88,
    "sunrpc", 111,
    "auth", 113,
    "ntp", 123,
    "netbios-ns", 137,
    "netbios-dgm", 138,
    "netbios-ssn", 139,
    "ftp", 21
);


my %protocols = (
    "nntp" 	=> [ "", "^20" ],
    "ica"	=> [ "", "ICA" ],
    "pop3"	=> [ "", "\\+OK" ],
    "telnet"	=> [ "", "." ],
    "smtp"	=> [ "", "^22" ],
    "ftp"	=> [ "", "^2" ],
    "imap"	=> [ "", "\\* OK" ],
    "http"	=> [ "HEAD \$spec1 HTTP/1.0\r\n\r\n", "HTTP.[\\.0-9]+ [123]" ]
);



sub new
{
    my ($this, $timeout) = @_;
    my  $class = ref($this) || $this;
    my  $self = {};

    bless($self, $class);

    $self->{"timeout"} = $timeout;

    $self->{"proto_num"} = (getprotobyname('tcp'))[2];
    $self->{"fh"} = FileHandle->new();
    $self->{"settle"} = 2;

    my( @bla)  = ();
    $self->{"htable"} = \@bla;

    return($self);
}


sub add_hosts {
    my( $self ) = shift;
    my( $htable ) = $self->{"htable"};

    push( @$htable, @_ );
}


sub add_any {
    my( $self, $host, $port, $service, $send, $expect, $item, $spec1, $spec2 ) = @_;

    if( ! ($send || $expect) ) {
        my $protocol = $protocols{$service};
	if( $protocol ) {
	    $send = $protocol->[0];
	    $send =~ s/\$spec1/$spec1/g;
	    $send =~ s/\$spec2/$spec2/g;
	    $expect = $protocol->[1];
	    $expect =~ s/\$spec1/$spec1/g;
	    $expect =~ s/\$spec2/$spec2/g;
	}
    }
    ($item = "$host.$port") unless( $item );
    $self->add_hosts( [ $host, $port, $service, $send, $expect, $item, $spec1, $spec2 ] );
}




sub check
{
    my ($self) = @_;
    my ($ip, $i, @list, %result, $timeout, $res, $rbits, $finish_time, $msg, $saddr, $nfound, $aport );
    my( $htable ) = $self->{"htable"};
    my( $host, $port, $nport, $expect, $send, $item, $service );

    $timeout = $self->{"timeout"};
    @list = @$htable;

    for( $i=0; $i<=$#list; $i++ ) {
	undef $msg;
	my ($host,$port,$service,$send,$expect,$item) = @{$list[$i]};
	print STDERR "**** checking tcp($host,$port,$service,$expect,$send)\n";
	print STDERR "checking tcp($host,$port,$service,$expect,$send)\n" if( $BigSister::common::dl>4 );
	$ip = inet_aton($host);
	next unless( $ip );
	$aport = $port;
	$nport = (getservbyname($port, 'tcp'))[2];
  	if( $nport ) { $port = $nport; }
	 else {
	    $nport = $Monitor::tcp::known_ports{$port};
	    if( $nport ) { $port = $nport; }
	}

	socket($self->{"fh"}, &PF_INET(), &SOCK_STREAM(), $self->{"proto_num"}) || next;
	$saddr = sockaddr_in($port, $ip);

	$res = $BigSister::common::status_codes{"red"};
	$finish_time = time + $timeout;
	eval {
	    $SIG{'ALRM'} = sub { die };
	    Platform::bsalarm($timeout); 
		
	    die unless Platform::bsconnect($self->{"fh"}, $saddr);
	    if( $send ) {
		send( $self->{"fh"}, $send, 0 );
	    }
	    if( $expect ) {
		my ($len,$data) = (1,"");
		$timeout = $finish_time - time(); 
		$msg = "";
		$rbits = "";
		vec($rbits, $self->{"fh"}->fileno(), 1) = 1;

		while( ($timeout > 0) && ($res == $BigSister::common::status_codes{"red"}) &&
			($len>0) && (length($msg)<4096) ) {
		    $nfound = select($rbits, undef, undef, $timeout); 
		    $timeout = $finish_time - time(); 
		    die unless( defined $nfound );
		    if( $nfound ) {
			$len = recv( $self->{"fh"}, $data, 1024, 0 );
			$msg .= $data;
			if( $msg =~ /$expect/ ) {
			    $res = $BigSister::common::status_codes{"green"};
			}
		    }
		}
	    }
	    else { 
		Platform::bsalarm(0);
		$res = $BigSister::common::status_codes{"green"};
		sleep $self->{"settle"};
	    }
	    Platform::bsalarm(0);
	};
	Platform::bsalarm(0);
	$self->{"fh"}->close();
	$msg =~ s/[\x00-\x09\x0B\x0C\x0e\x0f<>\x7f-\xff]/./g;
	$self->report( $item, $res, "tcp ".$service." ".($res?"OK":"FAILURE") );
	$self->report( $item, 101, "&html <HR><strong>Session data</strong>:<PRE>\n".$msg."\n</PRE><HR>") if( $msg );
    }

}

1;
