#!/usr/bin/perl

################################################################################
#                                                                              #
# Version 2.15 by Nick Reinking, original code by Charlie Cook.                #
# See LICENSE for copyright information                                        #
#                                                                              #
# netsaint_statd [port]                                                        #
#                                                                              #
# netsaint_statd is a daemon which allows for a Netsaint host using            #
# scripts to get information suck as process count, users, disk usage, and     #
# load information.  This daemon does not process the information in anyway.   #
# It merely collects the info and hands it back to the calling script to do    #
# with as it pleases.                                                          #
#                                                                              #
# This script is designed in such a way as to allow for easy porting via the   #
# %commandlist hash.  Adding other checks should also be as easy as making a   #
# minor modification to the %commandlist hash for the wanted command.          #
#                                                                              #
# Function calls have been recently reworked.  This is to allow for *much*     #
# easier extensions of netsaint_statd.  When a client communicates to          #
# netsaint_statd, it sends one line, which contains a request for information  #
# on a particular resource, and arguments for that request.  The new           #
# netsaint_statd will call the function by the resource called, and pass it    #
# the arguments.  For example, client A connections to server B, and sends the #
# line "dog /with/one/tail".  netsaint_statd will then call the function dog() #
# and pass it the parameter /with/one/tail.  This should make adding extra     #
# functionality as easy adding a single function, and modifying an existing    #
# client script.  Note that you must enter these functions in the dispatch     #
# table below. If you need to call commands from the %commandlist for your     #
# functions, they are referenced by $commandlist{$os}{functionname}.  So, if   #
# you want the df command for your current machine, you can get it by          #
# accessing $commandlist{$os}{dfcommand}.  It's that easy.  Sending the client #
# information is a simple 'print Client information'.                          #
#                                                                              #
################################################################################

# Uncomment @allowed_hosts if you want to restrict access to only
# certain hosts.  Leaving it uncommented allows everybody to access
# netsaint_statd.  I recommend placing the IP of your netsaint
# server in here, for security reasons (not that my program has any
# security bugs).  :P

my @allowed_hosts; # = ("127.0.0.1","10.20.30.40","123.123.123.123");

my $os = os_uname();
$os = "IRIX" if $os =~ "IRIX64"; # IRIX can have two different unames.
# my $os = "NEXTSTEP";  # Uncomment this if you have a NeXT machine.

my %commandlist = (
  "HP-UX" =>       {
                   dfcommand => "/bin/bdf -l",
                   whocommand => "/bin/who -q | /usr/bin/grep \"#\"",
                   proccommand => "/bin/ps -el",
                   procstates => "SWRITZX",
                   uptimecommand => "/bin/uptime",
                   },
  "Linux" =>       {
                   dfcommand => "/bin/df -k",
                   whocommand => "/usr/bin/who -q | /bin/grep \"#\"",
                   proccommand => "/bin/ps ax",
                   procstates => "RSTDZ",
                   uptimecommand => "/usr/bin/uptime",
                   },
  "SunOS" =>       {
                   dfcommand => "/bin/df -k",
                 # dfcommand => "/bin/df -kl",   # For only local disks
                   whocommand => "/bin/who -q | /usr/bin/grep \"#\"",
                   proccommand => "ps -e -o \"pid tty s time comm\"",
                   procstates => "SRZTO",
                   uptimecommand => "/usr/bin/uptime",
                   },
  "IRIX" =>        {
                   dfcommand => "/bin/df -kP",
                 # dfcommand => "/bin/df -klP",   # For only local disks
                   whocommand => "/bin/who -q | /usr/bin/grep \"#\"",
                   proccommand => "ps -e -o \"pid tty state time comm\"",
                   procstates => "SRZTIXC0",
                   uptimecommand => "/usr/bsd/uptime",
                   },
  "OSF1" =>        {
                   dfcommand => "/bin/df -k",
                   whocommand => "/bin/who -q | /usr/bin/grep \"Total user\"",
                   proccommand => "ps -e -o \"pid tty state time comm\"",
                   procstates => "RVSITH",
                   uptimecommand => "/bin/uptime",
                   },
  "FreeBSD" =>     {
                   dfcommand => "/bin/df -k",
                   whocommand => "/usr/bin/who | /usr/bin/wc -l",
                   proccommand => "/bin/ps ax",
                   procstates => "RSTDIZ",
                   uptimecommand => "/usr/bin/uptime",
                   },
  "NEXTSTEP" =>    {
                   dfcommand => "/bin/df",
                   whocommand => "/bin/who | /usr/ucb/wc -l",
                   proccommand => "/bin/ps -ax",
                   procstates => "RSTDIZ",
                   uptimecommand => "/usr/bin/uptime",
                   },
  "BSD/OS" =>      {
                   dfcommand => "/bin/df",
                   whocommand => "/usr/bin/who | /usr/bin/wc -l",
                   proccommand => "/bin/ps -ax",
                   procstates => "RSTDIZ",
                   uptimecommand => "/usr/bin/uptime",
                   },
  "OpenBSD" =>     {
                   dfcommand => "/bin/df -k",
                   whocommand => "/usr/bin/who | /usr/bin/wc -l",
                   proccommand => "/bin/ps -ax",
                   procstates => "RIDLIS",
                   uptimecommand => "/usr/bin/uptime",
                   },
  "AIX" =>        {
                  dfcommand => "/usr/bin/df -Ik",
                  whocommand => "/usr/bin/who | /usr/bin/wc -l",
                  proccommand => "/usr/bin/ps x",
                  procstates => "OAWISTZ",
                  uptimecommand => "/usr/bin/uptime",
                  },
  "NetBSD" =>     {
                  dfcommand => "/usr/bin/df -k",
                  whocommand => "/usr/bin/who | /usr/bin/wc -l",
                  proccommand => "/bin/ps ax",
                  procstates => "RSTDIZ",
                  uptimecommand => "/usr/bin/uptime",
                  },
  "UNIXWARE2" =>  {
                  dfcommand => "/usr/ucb/df",
                  whocommand => "/usr/bin/who -q | /bin/grep \"#\"",
                  proccommand => "/usr/bin/ps -el | awk '{printf(\"%6d%9s%2s%5s %s\\n\",\$4,substr(\$0, 61, 8),\$2,substr(\$0,69,5),substr(\$0,75))}",
                  procstates => "OSRIZTX",
                  uptimecommand => "echo `/usr/bin/uptime`, load average: 0.00, `sar | awk '{oldidle=idle;idle=\$5} END {print 100-oldidle}'`,0.00",
                  },
  "SCO-SV" =>     {
                  dfcommand => "/bin/df -Bk",
                  whocommand => "/bin/who -q | /usr/bin/grep \"#\"",
                  proccommand => "ps -el -o \"pid tty s time args\"",
                  procstates => "OSRIZTB",
                  uptimecommand => "/usr/bin/uptime",
                  }
              ); 

# Dispatch table for subroutines

my %subroutines =
	(
	"users"      => \&users,
	"disk"       => \&disk,
	"alldisks"   => \&alldisks,
	"uptime"     => \&uptime,
	"procs"      => \&procs,
	"named_proc" => \&named_proc
	);

################################################################################
################################################################################

require 5.003;
BEGIN { $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin:/usr/sbin' }
use POSIX;
use Socket;
use strict;

# Setup upgly global varialbes
my %restrictions;

# Forking into a new daemon...

my $pid = fork;
exit if $pid;
die "Couldn't fork: $!\n" unless defined($pid);
POSIX::setsid() || die "Cannot spawn new session id: $!\n";

# Verifying IP restriction information...

&verify_ip_list if @allowed_hosts;

# Extra information needed...

my $port = shift || 1040;
chomp($os);

# Setting up server for listening...
my $proto = getprotobyname('tcp');
socket(Server, PF_INET, SOCK_STREAM, $proto) || die "Can't create socket: $!\n";
setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, 1) || die "Can't setsockopt $!\n";
bind(Server, sockaddr_in($port, INADDR_ANY)) || die "Can't bind to socket: $!\n";
listen(Server,SOMAXCONN) || die "Can't listen to socket: $1";

# Infinite loop listening for client connections...

while (my $paddr = accept(Client,Server))
	{
	if (@allowed_hosts)
		{
		my ($cport, $packed_ip) = sockaddr_in($paddr);
		my $dotted_quad = inet_ntoa($packed_ip);
		unless ($restrictions{$dotted_quad})
			{
			send(Client,"Sorry, you ($dotted_quad) are not among the allowed hosts...\n",0);
			close(Client);
			next;
			}
		}
	my ($command,$arg_for_command,$input) = undef;
	next unless(defined($input = <Client>));
	if ($input =~ /^(\w*)\s?([\w\/]*)/)
		{
		$command = $1;
		$arg_for_command = $2 if $2;
 		$command =~ tr/A-Z/a-z/;
		}

# Call function by name (if in dispatch table)...

	if (exists $subroutines{$command})
		{
		my $rsub = $subroutines{$command};
		&$rsub($arg_for_command);
		close(Client);
		}
	else
		{
		send(Client,"Unknown command\n",0);
		close(Client);
		}
	}

sub verify_ip_list
################################################################################
# verify_ip_list scans the @allowed_hosts array, and double checks to make     #
# sure that you didn't put anything that isn't an IP address in there.         #
################################################################################
	{
	for (my $i=0;$i<=$#allowed_hosts;$i++)
		{
		if ($allowed_hosts[$i] =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/)
			{
			$restrictions{$allowed_hosts[$i]} = 1;
			}
		else
			{
			print "Sorry, your allowed hosts list doesn't contain valid IPs.\n";
			exit(0); 
			}
		}
	}

sub os_uname
	{
	my $uname = ( -e '/usr/bin/uname' ) ? '/usr/bin/uname' : '/bin/uname';
	my $os = (`$uname 2>/dev/null`);
	chomp $os;
	return $os ? $os : undef;
	}

sub users
	{
	open(WHOOUT,"$commandlist{$os}{whocommand} |") || die;
	$_ = <WHOOUT>;
	$_ =~ /[users]*[=|:]?\s?(\d+)/;
	my $users = $1;
	print Client "$users ";
	close(WHOOUT);
	}

sub disk
	{
	my $arg = shift;
	my ($disk, $avail, $capper, $mountpt);

	open(DFOUTPUT,"$commandlist{$os}{dfcommand} |") || die;
	$_ = <DFOUTPUT>;
	DFCHECK: while($_ = <DFOUTPUT>)
		{   
		if (/^([\w\/\:\.\-\=]*)\s*\d*\s*\d*\s*(\d*)\s*(\d*)\%\s*([\w\/\-]*)/)
			{
			$disk = $1;
			$avail = $2;
			$capper = $3;
			$mountpt = $4;
			last DFCHECK if ($disk =~ /$arg/);
			}
		} 
	if (($disk =~ $arg) && $mountpt)
		{
		$capper = 100 - $capper;
		print Client "$disk $avail $capper $mountpt ";
		}
	else
		{
		print Client "not found";
		} 
	($disk,$avail,$capper,$mountpt) = undef;
	close(DFOUTPUT);
	}

sub alldisks
	{
	my $disklisting;

	open(DFOUTPUT,"$commandlist{$os}{dfcommand} |") || die;
	$_ = <DFOUTPUT>;
	while($_ = <DFOUTPUT>)
		{
		if (/^[\w\/\:\.\-\=]*\s*\d*\s*\d*\s*\d*\s*(\d*)\%\s*([\w\/\-]*)/)
			{
			$disklisting .= "(".$2.",".$1.")";
			}
		}
	if ($disklisting)
		{
		print Client $disklisting;
		}
	else
		{
		print Client "no disks?";
		}
	close(DFOUTPUT);
	undef $disklisting;
	}
   
sub uptime
	{
	open(UPTIMEOUTPUT,"$commandlist{$os}{uptimecommand} |");
	$_ = <UPTIMEOUTPUT>;
	$_ =~ /up\s*(.*),\s*\d*\s*user[s]?,\s*load average[s]?:\s*[0-9\.]*,\s*([0-9\.]*),/;
	print Client "$1 - $2 ";
	close(UPTIMEOUTPUT);
	} 

sub procs
	{
	my $procs = -1;
	my $procflags = shift || $commandlist{$os}{procstates};
	open(PROCOUT, "$commandlist{$os}{proccommand} |") || die;
	$_ = <PROCOUT>;
	if ($procflags)
		{
		while (defined($_ = <PROCOUT>))
			{
			if ($_ =~ /\s*([$procflags])\w*\s*\d*/)
				{
				$procs++;
				}
			}
		}
	else
		{
		while (<PROCOUT>)
			{
			$procs++;
			}
		}
	print Client "$procs $procflags ";
	($procs, $procflags) = undef;
	close(PROCOUT);
	}

sub named_proc
	{
	my $args = shift;
	open(PROCOUT, "$commandlist{$os}{proccommand} | grep -v grep | grep $args |") || die;
	@_ = <PROCOUT>;
	print Client ++$#_;
	close(PROCOUT);
	}
