#!/usr/bin/perl
# Pull various stats out based on what was requested
# Writen by Dougal Scott <dougal.scott@member.sage-au.org.au>
# $Id$
#  
# Socket code stolen from netsaint_statd

@::allowed_hosts = ("127.0.0.1", "192.168.120.46");

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

BEGIN { $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin:/usr/sbin:/opt/sfw/bin' }

use POSIX;
use Socket;
use strict;
use Time::HiRes qw(tv_interval gettimeofday);
use LWP::UserAgent;

# Forking into a new daemon...

&main;

################################################################################
sub main {
    my($cport,$packed_ip,$paddr,$dotted_quad,$command,$arg,$input);
    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;

    # Extra information needed...
    my $port = shift || 1973;

    # 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 ($paddr = accept(Client,Server)) {
	($cport, $packed_ip) = sockaddr_in($paddr);
	$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;
	    }
	($command,$arg,$input) = undef;
	next unless(defined($input = <Client>));
	if ($input =~ /^(\w*)\s?([\S+]*)/) {
	    $command = $1;
	    $arg = $2 if $2;
	    $command =~ tr/A-Z/a-z/;
	    $command="cmd_".$command;
	    }

    # Call function by name...
	if (defined &$command) {
	    no strict 'refs';
	    &$command($arg);
	    close(Client);
	    }
	else {
	    send(Client,"Unknown command\n",0);
	    close(Client);
	    }
    }
}

################################################################################
# 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.         #
################################################################################
sub verify_ip_list {
    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);
	    }
    }
}

# Time a series of web accesses
################################################################################
sub cmd_webtimer {
    my($tmp)=@_;
    my($t0,$sec,$request,$response,$avg,@arglist);
    my($msg)="cached";
    my($url)="http://www.ibm.com";
    my($count)=10;
    my($proxy)="no proxy";
    my($maxsec)=0;
    my($sumsec)=0;
    my($hdr)=new HTTP::Headers;
    my($ua)=LWP::UserAgent->new;

    @arglist=split(/,/, $tmp);
    foreach (@arglist) {
    	/nocache/ && do {
	    $hdr=new HTTP::Headers Pragma => 'no-cache';
	    $msg="no cache";
	    next;
	    };
    	/url=(.*)/ && do {
	    $url=$1;
	    next;
	    };
    	/proxy=(.*)/ && do {
	    $proxy=$1;
	    $ua->proxy("http","http://$proxy");
	    next;
	    };
    	}

    foreach (1..$count) {
	$t0=[gettimeofday];
	$request=HTTP::Request->new("GET",$url,$hdr);
	$response=$ua->request($request);
	if($response->is_success) {
	    $sec=tv_interval($t0);
	    $sumsec+=$sec;
	    if($sec>$maxsec) { $maxsec=$sec; };
	    };
	};
    $avg=$sumsec/$count;
    print Client "$avg Avg response for $url $msg via $proxy\n";
    print Client "$maxsec Max response for $url $msg via $proxy\n";
}

# In order to retain consistency we don't do this in a sensible way
################################################################################
sub cmd_top {
    my($type)=@_;
    my(%mem);

    open(TOP,"top -u -d 2 -s 1|");
    while (<TOP>) {
    # Memory: 128M real, 6848K free, 135M swap in use, 204M swap free
	/^Memory:\D+(\d+[M|K]?)\D+(\d+[M|K]?)\D+(\d+[M|K]?)\D+(\d+[M|K]?)/ && do {
	    $mem{'real'}= &tobytes($1);
	    $mem{'free'}= &tobytes($2);
	    $mem{'used'}= $mem{'real'} - $mem{'free'};
	    }
    }
    close(TOP);

    if($type eq "mem") {
    	print Client "$mem{'real'} Real Memory\n";
    	print Client "$mem{'free'} Free Memory\n";
    	print Client "$mem{'used'} Used Memory\n";
    	};
}

################################################################################
sub tobytes {
    my $i = $_[0];
    if ($i =~ s/M$//i) {
        $i = int($i) * 1024*1024;
    } elsif ($i =~ s/K$//i) {
        $i = int($i) * 1024;
    }
    return $i;
}

################################################################################
sub cmd_mem {
    &cmd_top("mem");
}

################################################################################
sub cmd_swap {
    my(%swap,$blocks,$free);

    open(SW,"/usr/sbin/swap -l|");
    while(<SW>) {
    	# swapfile             dev  swaplo blocks   free
	# /dev/dsk/c0d0s1     102,1       8 525160 441808
	/.*\s+(\d+)\s+(\d+)\s*$/ && do {
	    $blocks+=$1;
	    $free+=$2;
	    };
	};
    close(SW);

    $swap{'total'}=$blocks*512;
    $swap{'free'}=$free*512;
    $swap{'used'}=$swap{'total'}-$swap{'free'};

    print Client "$swap{'used'} Used Swap\n";
    print Client "$swap{'free'} Free Swap\n";
    print Client "$swap{'total'} Total Swap\n";
}

################################################################################
sub cmd_load {
    my(%load);

    open(W,"/bin/w -u|");
    while(<W>) {
    	#  10:34am  up 6 day(s), 19:13,  1 user,  load average: 0.21, 0.22, 0.41
    	/load average: (.*), (.*), (.*)/ && do {
	    $load{'1'}=$1;	
	    $load{'5'}=$2;	
	    $load{'15'}=$3;	
	    };
    	};
    close(W);
    print Client "$load{'1'} Load Av 1min\n";
    print Client "$load{'5'} Load Av 5min\n";
    print Client "$load{'15'} Load Av 15min\n";
}

################################################################################
sub cmd_cpu {
    my($cpunum)=@_;
    my(%cpu);

    if($cpunum eq "") {
    	$cpunum=0;
	};

    open(MP,"/bin/mpstat 5 2|");
    while(<MP>) {
    	# CPU minf mjf xcal  intr ithr  csw icsw migr smtx  srw syscl  usr sys  wt idl
    	#   0   74   0    0   246  146  329   35    0    0    0  1327    2   1   1  97
    	/^\s+$cpunum\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/ && do {
	    $cpu{'usr'}=$1;
	    $cpu{'sys'}=$2;
	    $cpu{'wt'}=$3;
	    $cpu{'idle'}=$4;
	    };
    	};
    close(MP);

    $cpu{'busy'}=100-$cpu{'idle'};

    print Client "$cpu{'usr'} CPU $cpunum usr percent\n";
    print Client "$cpu{'sys'} CPU $cpunum sys percent\n";
    print Client "$cpu{'wt'} CPU $cpunum wt percent\n";
    print Client "$cpu{'idle'} CPU $cpunum idle percent\n";
    print Client "$cpu{'busy'} CPU $cpunum busy percent\n";
}

# Check the number of processes, if specified then only return the number of
# processes with that name
################################################################################
sub cmd_procs {
    my($procname)=@_;
    my($count)=0;

    open(PS,"/bin/ps -eo args | grep -v grep|");
    while(<PS>) {
    	/^COMMAND/ && do { next; };
    	/$procname/ && do { $count++; };
    	};
    close(PS);

    print Client "$count Number procs $procname\n";
}

# Return the size, in lines, of the file specified
################################################################################
sub cmd_filesize {
    my($filename)=@_;
    my($count);

    if(! -f $filename) {
    	print Client "-1 Doesn't exist: $filename\n";
	return;
	};

    open(WC,"/bin/wc -l $filename|");
    while(<WC>) {
    	/(\d+)/ && do {
	    $count=$1;
	    };
    	};
    close(WC);

    print Client "$count Lines in $filename\n";
}

# Return the size, in lines, of the file specified that contain the string
################################################################################
sub cmd_matchlines {
    my($tmp)=@_;
    my($count,$filename,$string);

    ($filename,$string)=split(/,/, $tmp,2);

    if(! -f $filename) {
    	print Client "-1 Doesn't exist: $filename\n";
	return;
	};
    if($string eq "") {
    	print Client "-1 No string specified\n";
	return;
	};

    open(WC,"/bin/grep $string $filename | /bin/wc -l|");
    while(<WC>) {
    	/(\d+)/ && do {
	    $count=$1;
	    };
    	};
    close(WC);

    print Client "$count Lines matching $string in $filename\n";
}

# Return the used % of the specified filesystem 
################################################################################
sub cmd_diskcap {
    my($fs)=@_;
    my($size,$filesys);

    if($fs eq "") {
    	$fs="/";
	};

    open(DF,"/bin/df -k $fs|");
    while(<DF>) {
    	/^Filesystem/ && do { next; };
	# /dev/dsk/c0d0s0      9323516 3816632 5413649    42%    /
	/\s(\d+)%\s+(\S+)/ && do {
	    $size=$1;
	    $filesys=$2;
	    };
    	};
    close(DF);
    print Client "$size filesystem $filesys capacity\n";
}

# Return some interesting details about physical disk performance
################################################################################
sub cmd_iostat {
    my($disk)=@_;	
    my($svc_t,$pwait,$pbusy)=("U","U","U");

    if($disk eq "") {
    	$disk="sd0";
	};

    open(IOS,"/bin/iostat -x 5 2|");
    while(<IOS>) {
    	# device       r/s    w/s   kr/s   kw/s wait actv  svc_t  %w  %b 
    	# cmdk0        0.5    1.2    3.0    8.4  0.1  0.0  102.1   1   1 
    	/$disk\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ && do {
	    $svc_t=$7;
	    $pwait=$8;
	    $pbusy=$9;
	    };
    	};
    close(IOS);

    print Client "$svc_t disk $disk service time\n";
    print Client "$pwait disk $disk percent wait\n";
    print Client "$pbusy disk $disk percent busy\n";
}

################################################################################
sub cmd_quit {
    print Client "Self terminating\n";
    exit(0);
}

# Return some interesting details about nat performance
################################################################################
sub cmd_nat {
    my($in,$out,$inuse,$added,$expired)=('U','U','U','U','U');

    open(IPN,"/sbin/ipnat -s|");
    while(<IPN>) {
	# mapped  in      48023   out     40405
	/mapped\s+in\s+(\d+)\s+out\s+(\d+)/ && do {
	    $in=$1;
	    $out=$2;
	    next;
	    };
	# added   2271    expired 2010
	/added\s+(\d+)\s+expired\s+(\d+)/ && do {
	    $added=$1;
	    $expired=$2;
	    next;
	    };
	# inuse   261
	/inuse\s+(\d+)/ && do {
	    $inuse=$1;
	    next;
	    };
	# rules   1
    	};
    close(IPN);

    print Client "$in nat connections in\n";
    print Client "$out nat connections out\n";
    print Client "$added nat connections added\n";
    print Client "$expired nat connections expired\n";
    print Client "$inuse nat connections in use\n";
}

#EOF
