#!/usr/local/bin/perl5 -w
#+##############################################################################
#                                                                              #
# File: big_brother                                                            #
#                                                                              #
# Description: check the network sockets with lsof to detect new connections   #
#									       #
# Contributed by Lionel Cons <Lionel.Cons@cern.ch>			       #
#                                                                              #
#-##############################################################################

# @(#)big_brother	1.1	10/05/95	Written by Lionel.Cons@cern.ch

# no waranty! use this at your own risks!

#
# init & setup
#
$verbose = 1;
$lsof = "/usr/local/bin/lsof";
$lsof_opt = "-itcp -iudp -Di -FcLPn -r 5";
$SIG{'HUP'} = \&hangup;
chop($hostname = `/bin/hostname`);
$fq_hostname = (gethostbyname($hostname))[0];

#
# spy forever...
#
$| = 1;
die "$lsof is not executable\n" unless -x $lsof;
while (1) {
    $lsof_pid = open(PIPE, "$lsof $lsof_opt 2>&1 |")
	|| die "can't start $lsof: $!\n";
    print "# ", &timestamp, " $lsof $lsof_opt started, pid = $lsof_pid\n"
	if $verbose;
    print "#COMMAND     PID     USER P NAME\n";
    $printed = $hanguped = $pid = $proto = 0;
    while (<PIPE>) {
	if (/^lsof: PID \d+, /) {
	    # fatal error message?
	    print "*** $_";
	    last;
	} elsif (/^p(\d+)$/) {
	    &flush;
	    $pid = $1;
	    $proto = 0;
	} elsif (/^c(.*)$/) {
	    $command = $1;
	} elsif (/^L(.*)$/) {
	    $user = $1;
	} elsif (/^P(.*)$/) {
	    &flush;
	    $proto = $1;
	} elsif (/^n(.*)$/) {
	    $name = $1;
	} elsif (/^m$/) {
	    &flush;
	    &clean;
	} else {
	    warn "* bad output ignored: $_";
	}
    }
    kill('INT',  $lsof_pid);
    kill('KILL', $lsof_pid);
    close(PIPE);
}

sub hangup {
    $hanguped = 1;
    $SIG{'HUP'} = \&hangup;
}

sub flush {
    return unless $pid && $proto;
    return if &skip;
    $tag = sprintf("%-9s %5d %8s %1s %s", $command, $pid, $user,
		   substr($proto, 0, 1), $name);
    unless (defined($seen{$tag})) {
	print "+$tag\n";
	$printed++;
    }
    $seen{$tag} = 1;
}

sub clean {
    my(@to_delete, $tag);

    if ($hanguped) {
	$hanguped = 0;
	@to_delete = keys(%seen);
	print "# ", &timestamp, " hangup received, rescanning all connections\n"
	    if $verbose;
    } else {
	@to_delete = ();
	foreach $tag (keys(%seen)) {
	    if ($seen{$tag} == 0) {
		# not seen this time: delete it
		push(@to_delete, $tag);
		print "-$tag\n";
		$printed++;
	    } else {
		# seen this time: reset the flag
		$seen{$tag} = 0;
	    }
	}
    }
    grep(delete($seen{$_}), @to_delete);
    if ($printed > 10) {
	print "# ", &timestamp, "\n" if $verbose;
	$printed = 0;
    }
}

sub skip {
    #
    # put stuff here to ignore some connections, for instance
    #

    # ignore common daemons
    if ($name =~ /^\*:/ && $user eq 'root' && $pid < 300) {
	return(1) if $command =~ /^rpc\.(stat|lock)d$/;
	return(1) if $command eq 'syslogd' && $name eq '*:syslog';
    }
    # outgoing commands: ftp, telnet...
    if ($command eq 'ftp') {
	return(1) if $name =~ /:ftp(-data)?$/;
    } elsif ($command eq 'telnet') {
	return(1) if $name =~ /:telnet$/;
    }
    return(0);
}

sub timestamp {
    my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);

    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
    sprintf("%02d/%02d/%02d-%02d:%02d:%02d", $year, $mon+1, $mday, $hour, $min, $sec);
}

