#!/usr/bin/perl
## This Script is free for all. Please feel free to distribute it to anybody! I do not warantee this program
## in any way or for any reason, if it blows up and ruins your entire business database or anything, I accept no responsibility!
## Please email abhidharnoida@yahoo.com with any problems or comments.

## NOTE: This is meant to run on linux with perl on it. It should also have the module "Net::SSH::Perl" installed on it.
##Please install Net::SSH::Perl from CPAN
##using can be easily done by executing the following command.
##	perl -MCPAN -e 'install Net::SSH::Perl'
##
##usage:: proclist --hosts [ip1],[ip2],[ip2] ... --search search-string --kill [signo]
##WARNING!!! USE THE KILL OPTION WITH CARE.

use Getopt::Long;
#use Net::SSH::Perl;
#use Term::ReadKey;
my $VERSION = '1.0';
GetOptions ("hosts=s" => \$H, 
	    "search=s"   => \$S,
	    "kill=s" => \$K);
	    
my @hosts  =split(/,/,$H) ;

if (@hosts == "") {
	#Case 1: when then script is run on the machine itself.
	#find username and uids, Puts them is the usr hash,
	%usr=();
	open(PWD,"/etc/passwd") || die;
	while (<PWD>){
		@P= split(/:/,$_);
		$usr{$P[2]}=$P[0];
		}

	#scans proc for sockets, fds and pids
	opendir (PROC, "/proc") || die "proc";
	for $f (readdir(PROC)){
	    next if (! ($f=~/[0-9]+/) );
	    if (! opendir (PORTS, "/proc/$f/fd")) {
		# print "failed opendir on process $f fds\n";
		closedir PORTS;
		next;
	    }
	    for $g (readdir(PORTS)) {
		next if (! ($g=~/[0-9]+/) );
		$r=readlink("/proc/$f/fd/$g");
		($dev,$ino)=($r=~/^(socket|\[[0-9a-fA-F]+\]):\[?([0-9]+)\]?$/);
		if ($dev == "[0000]" || $dev == "socket") 
		{
		$sock_proc{$ino}=$f.":".$g if $ino != "" ;}
	    }
	    closedir PORTS;
	}
	closedir PROC;

	#for $a (keys(%sock_proc)) {print "$a->$sock_proc{$a}\n";}

	#header
	print "type  port    inode        user        pid     fd  name       local_address        rem_address\n";
	#displays output.
	scheck("tcp");
	scheck("udp");
	scheck("raw");
	print ("\nThe following process are killed using: kill -".$K."\n") if $K ne "" ;

}
else {
	#Case2: to be executed on multiple servers.
	require Net::SSH::Perl || die "Please load Net::SSH::Perl from CPAN.org";
	require Term::ReadKey || die "Please load Term::ReadKey from CPAN.org";
	@passwd =();
	#collect password for hosts
	for (@hosts){
		print "Enter root password ".$_.":";
		Term::ReadKey::ReadMode('noecho');
		chomp(my $pwd = Term::ReadKey::ReadLine(0));
		Term::ReadKey::ReadMode('restore');
		print "\n";
		push @passwd, $pwd;	
	}

	for my $i (0..$#hosts) {
		#ssh every hostname;
		my $ssh = Net::SSH::Perl->new($hosts[$i]);
		$ssh->login("root", $passwd[$i]);
		#change to proclist
		my $c = "/usr/bin/procsocklist";
		$c= $c." --search ".$S if $S ne "";
		$c= $c." --kill ".$K if $K ne "";
		#execute the script.
		my($stdout, $stderr, $exit);
		eval{ ($stdout, $stderr, $exit) = $ssh->cmd($c)};
		if ($@){ print "ERROR:Wrong password for $hosts[$i]\n" ;}
		#display data from host
		else {
		print "#################################$hosts[$i]##################################\n";
		print $stdout;
		print "ERROR:",$stderr if $stderr;
		print "EXIT:",$exit if $exit;
		}
	}

}
exit(0);

sub scheck {
#reads the /proc/net/(tcp|udp|raw) files and extracts inode and other information.
#with the help of $sock_proc and %usr it associates pids and usernames.
    open(FILE,"/proc/net/".$_[0]) || die;
    while (<FILE>) {
        @F=split();
        next if ($F[9]=~/uid/);
        @A=split(":",$F[1]);
        $a=hex($A[1]);
        $local_address = converthex2ip($F[1]);
        $rem_address = converthex2ip($F[2]);
	if (exists $sock_proc{$F[9]}) {       
	        ($pid,$fd)=($sock_proc{$F[9]}=~m.([0-9]*):([0-9]*).) ;
	        }
        $cmd = "";
        if ($pid && open (CMD,"/proc/$pid/status")) {
           $l = <CMD>;
           ($cmd) = ( $l=~/Name:\s*(\S+)/ );
           close(CMD);
	}

        $display = sprintf ("%-3s %6d %10d  %-10.10s  %6d    %4d  %-10.10s %-20.20s %-20.20s\n",$_[0], $a ,$F[9], $usr{$F[7]}, $pid, $fd, $cmd,$local_address,$rem_address);

	#search option 
	#the regex is executed on the row displayed. I think this will make things flexible.
	if ($S ne "")  
		{ if ($display =~ /$S/) 
			{
			print $display ;
			#kill option on the filtered Items only.	
			system ("kill -".$K." ".$pid) if $K ne "" ;
			}
		} 
		else 
		{print $display ;}
        }
        close(FILE);
}

#converts hex format of ips used in /proc/net/(tcp|udp|raw) files to dot-format which people can understand.
sub converthex2ip{
  my $addr=shift @_;

my $pattern = "([0-9A-Z]{2})([0-9A-Z]{2})([0-9A-Z]{2})([0-9A-Z]{2})" .":([0-9A-Z]{2})([0-9A-Z]{2})";
if ($addr =~ m/^$pattern$/)
        {
        my @octets_hex=($4,$3,$2,$1);
        my @port_hex=($5,$6);
        my @octets=map { hex($_) } @octets_hex;
        my $dot_ip=join('.', @octets);
        my $port = hex(join('', @port_hex));
        return "$dot_ip:$port";
        }
return undef;
}




=head1 NAME

Procsocklist.pl 1.0 - Displays users of different servers with there pids and associated sockects to those pids. 

=head1 Author

Abhinav Dhar, abhidharnoida@yahoo.com

=head1 DESCRIPTION

usage:: proclist --hosts [ip1],[ip2],[ip2] ... --search search-string --kill [sig]

procsocklist helps the user to collect information about sockets related to different ips or programs,etc.
Like if a one wants to find users connected to a perticular port or ip in a given number of hosts machines then it can be easily done with the help of this script. We can filter our search and then perform a kill on the listed result (ie with the help of kill option).
WARNING: please view the result without the kill option first. Once you are sure that you want to perform a kill on all these process then add a kill option followed by signal number (say 9).

--hosts : the ips should by separated by commas only. 
--search : regex to match the each output row.
--kill : will perform a kill with the signal number specified on the pids that are listed.

The output is like this:

type  port    inode        user        pid     fd  name       local_address        rem_address
tcp    139     137988  root         19706       9  smbd       0.0.0.0:139          0.0.0.0:0
tcp    139     138176  root         19707      12  smbd       192.168.6.20:139     192.168.6.137:1791
udp  32773     138177  root         19707       5  smbd       127.0.0.1:32773      0.0.0.0:0

type - socket type tcp, udp or raw
port - port number.
inode - is inode.
local_address- The local IP address and port number for the socket.
rem_address- The remote IP address and port number for the socket.
name - program name.

Actually this script will be helpful in many cases. Like suppose users are accessing a database on a particular IP through various programs like apache or sqlplus. This access has to be stopped by the sys-admin. This script will be helpful for him in managing processes trying to connect to that particular IP. 


=head1 PREREQUISITES

This script requires the C<Net::SSH::Perl> & C<Term::ReadKey> modules in the case of using host arguments.  
It also requires C<Getopt::Long>. Apart from that the user *must* have root rights to all hosts.
One more *IMPORTANT* thing procsocklist.pl should be in /usr/bin/procsocklist [note no .pl extension] in every host machine so that it can run as a command.

=pod OSNAMES

Linux

=pod SCRIPT CATEGORIES

Networking

=cut