1#!/usr/local/bin/perl -w
2#+##############################################################################
3#                                                                              #
4# File: big_brother.perl                                                       #
5#                                                                              #
6# Description: check the network sockets with lsof to detect new connections   #
7#									       #
8# Contributed by Lionel Cons <Lionel.Cons@cern.ch>			       #
9#                                                                              #
10#-##############################################################################
11
12# @(#)big_brother	1.12 08/14/96 Written by Lionel.Cons@cern.ch
13
14# no waranty! use this at your own risks!
15
16#
17# init & setup
18#
19$verbose = 1;
20$lsof_opt = "-itcp -iudp -Di -FcLPn -r 5";
21$SIG{'HUP'} = \&hangup;
22chop($hostname = `/bin/hostname`);
23$fq_hostname = (gethostbyname($hostname))[0];
24
25# Set path to lsof.
26
27if (($LSOF = &isexec("../lsof")) eq "") {	# Try .. first
28    if (($LSOF = &isexec("lsof")) eq "") {	# Then try . and $PATH
29	print "can't execute $LSOF\n"; exit 1
30    }
31}
32
33#
34# spy forever...
35#
36$| = 1;
37die "$LSOF is not executable\n" unless -x $LSOF;
38while (1) {
39    $lsof_pid = open(PIPE, "$LSOF $lsof_opt 2>&1 |")
40	|| die "can't start $LSOF: $!\n";
41    print "# ", &timestamp, " $LSOF $lsof_opt, pid=$lsof_pid\n"
42	if $verbose;
43    print "#COMMAND     PID     USER P NAME\n";
44    $printed = $hanguped = $pid = $proto = 0;
45    while (<PIPE>) {
46	if (/^lsof: PID \d+, /) {
47	    # fatal error message?
48	    print "*** $_";
49	    last;
50	} elsif (/^lsof: /) {
51	    # warning
52	    warn "* $_";
53	} elsif (/^p(\d+)$/) {
54	    &flush;
55	    $pid = $1;
56	    $proto = 0;
57	} elsif (/^c(.*)$/) {
58	    $command = $1;
59	} elsif (/^L(.*)$/) {
60	    $user = $1;
61	} elsif (/^P(.*)$/) {
62	    &flush;
63	    $proto = $1;
64	} elsif (/^n(.*)$/) {
65	    $name = $1;
66	    # replace local hostname by 'localhost'
67	    $name =~ s/\Q$fq_hostname\E/localhost/g;
68	    $name =~ s/[0-9hms]+ ago//g;
69	} elsif (/^m$/) {
70	    &flush;
71	    &clean;
72	} else {
73	    warn "* bad output ignored: $_";
74	}
75    }
76    kill('INT',  $lsof_pid);
77    kill('KILL', $lsof_pid);
78    close(PIPE);
79}
80
81sub hangup {
82    $hanguped = 1;
83    $SIG{'HUP'} = \&hangup;
84}
85
86sub flush {
87    return unless $pid && $proto;
88    return if &skip;
89    $tag = sprintf("%-9s %5d %8s %1s %s", $command, $pid, $user,
90		   substr($proto, 0, 1), $name);
91    unless (defined($seen{$tag})) {
92	print "+$tag\n";
93	$printed++;
94    }
95    $seen{$tag} = 1;
96}
97
98sub clean {
99    my(@to_delete, $tag);
100
101    if ($hanguped) {
102	$hanguped = 0;
103	@to_delete = keys(%seen);
104	print "# ", &timestamp, " hangup received, rescanning all connections\n"
105	    if $verbose;
106    } else {
107	@to_delete = ();
108	foreach $tag (keys(%seen)) {
109	    if ($seen{$tag} == 0) {
110		# not seen this time: delete it
111		push(@to_delete, $tag);
112		print "-$tag\n";
113		$printed++;
114	    } else {
115		# seen this time: reset the flag
116		$seen{$tag} = 0;
117	    }
118	}
119    }
120    grep(delete($seen{$_}), @to_delete);
121    if ($printed > 10) {
122	print "# ", &timestamp, "\n" if $verbose;
123	$printed = 0;
124    }
125}
126
127sub skip {
128    #
129    # put stuff here to ignore some connections, for instance:
130    #
131
132    # what we get when the socket gets created...
133    return(1) if $name eq '*:0';
134    return(1) if $name =~ /^localhost:(\d+)$/ && $1 > 1000;
135#
136# UDP & TCP stuff
137#
138    #
139    # ignore common daemons
140    #
141    if ($name =~ /^\*:/ && $user eq 'root' && $pid < 300) {
142	return(1) if $command =~ /^inetd(\.afs)?$/;
143	return(1) if $command =~ /^rpc\.(stat|lock)d$/;
144	return(1) if $command eq 'syslogd' && $name eq '*:syslog';
145    }
146    #
147    # forking beasts: portmap, ypbind, inetd
148    #
149    if ($command eq 'portmap' && $user eq 'daemon') {
150	return(1) if $name =~ /^\*:/;
151    } elsif ($command eq 'ypbind') {
152	return(1) if $name =~ /^\*:\d+$/;
153    }
154#
155# TCP-only stuff
156#
157    return(0) unless $proto eq 'TCP';
158    #
159    # outgoing commands: ftp, telnet, r*
160    #
161    if ($command eq 'ftp') {
162	return(1) if $name =~ /:ftp(-data)?$/;
163    } elsif ($command eq 'telnet') {
164	return(1) if $name =~ /:telnet$/;
165    } elsif ($command eq 'remsh') {
166	if ($name =~ /:(\d?\d\d\d)->.+:(\d?\d\d\d)$/) {
167	    return(1) if $1 < 1024 && $1 > 990 && $2 < 1024 && $2 > 990;
168	} elsif ($name =~ /:(\d?\d\d\d)->.+:(shell|ta-rauth)$/) {
169	    return(1) if $1 < 1024 && $1 > 990;
170	} elsif ($name =~ /^\*:(\d?\d\d\d)$/) {
171	    return(1) if $1 < 1024 && $1 > 990;
172	}
173    }
174    return(0);
175}
176
177sub timestamp {
178    my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
179
180    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
181    sprintf("%d/%02d/%02d-%02d:%02d:%02d", $year + 1900, $mon+1, $mday,
182      $hour, $min, $sec);
183}
184
185
186## isexec($path) -- is $path executable
187#
188# $path   = absolute or relative path to file to test for executabiity.
189#	    Paths that begin with neither '/' nor '.' that arent't found as
190#	    simple references are also tested with the path prefixes of the
191#	    PATH environment variable.  
192
193sub
194isexec {
195    my ($path) = @_;
196    my ($i, @P, $PATH);
197
198    $path =~ s/^\s+|\s+$//g;
199    if ($path eq "") { return(""); }
200    if (($path =~ m#^[\/\.]#)) {
201	if (-x $path) { return($path); }
202	return("");
203    }
204    $PATH = $ENV{PATH};
205    @P = split(":", $PATH);
206    for ($i = 0; $i <= $#P; $i++) {
207	if (-x "$P[$i]/$path") { return("$P[$i]/$path"); }
208    }
209    return("");
210}
211