#!__PERL__
# -*- perl -*-

# Copyright (c) 2002 by Jeff Weisberg
# Author: Jeff Weisberg <argus @ tcp4me.com>
# Date: 2002-Nov-06 17:57 (EST)
# Function: remote system monitoring agent
#
# $Id: sys_agent.pl,v 1.9 2005/11/25 17:11:43 jaw Exp $

# run from inetd.conf

# if ip addresses are listed on cmdline, restrict access to those specified
# eg:  argus-agent 10.0.0.1  10.0.0.2  fec0:b80:1fcb:1::1

# uptime, disk used %, load ave, etc.
# shouldn't people be running snmpd instead?

# many of these use highly non-portable means for gathering data
# it probably won't work on your system without customization

use Socket;
BEGIN{ eval{ require Socket6; import Socket6; $HAVE_S6 = 1; }}
verify_access(@ARGV) if @ARGV;

# read request
chop($line = <STDIN>);
$line =~ tr/\r//d;
($what, $arg) = split /\s+/, $line, 2;

if( $what eq 'load' ){
    if( open(F, "/proc/loadavg") ){
	# linux
	my $l = <F>;
	($l) = split /\s+/, $l;
	print "$l\n";
	close F;
    }elsif( open(F, "/kern/loadavg") ){
	# netbsd
	my $l = <F>;
	my(@a) = split /\s+/, $l;
	$l = $a[0] / $a[3];
	print "$l\n";
	close F;
    }else{
	# other
	my $up = output_of_command( 'uptime' );
	$up =~ s/.*:\s*([\d\.]+).*\n?/$1/;
	print "$up\n";
    }
}

if( $what eq 'uptime' ){
    if( open(F, "/proc/uptime") ){
	# linux
	my $l = <F>;
	($l) = split /\s+/, $l;
	print "$l\n";
	close F;
    }elsif( open(F, "/kern/boottime") ){
	# netbsd
	my $l = <F>;
	$l = $^T - $l;
	print "$l\n";
	close F;
    }else{
	# QQQ
	# the format of uptime varies significantly, I am too lazy
	# to write the code at the moment
    }
}

if( $what eq 'disk' ){
    my $df;
    
    if( $^O eq 'solaris' ){
        $df = output_of_command( 'df', '-k', $arg );
        $df =~ s/.*\s+(\d+)%.*\n?/$1/s;
    }else{
        $df = output_of_command( 'df', $arg );
        $df =~ s/.*\s+(\d+)%.*\n?/$1/s;
    }
    print "$df\n";
}

if( $what eq 'netstat' ){
    # arg: interface [in|out]
    # extremely non-portable
    my( $int, $dir ) = split /\s+/, $arg;
    my $r;
    
    if( $^O eq 'solaris' ){
	my $st = output_of_command( '/bin/kstat', '-p', '-n',
	        $int, '-s', ($dir eq 'in' ? 'rbytes' : 'obytes'));
	chomp $st;
	$st =~ s/.*\s+//;
	$r = $st;
    }elsif( $^O eq 'linux' ){
	my $col = ($dir eq 'in' ? 0 : 8);
	open(F, "/proc/net/dev");
	while(<F>){
	    chop;
	    next unless /$int/;
	    s/^.*:\s*//;
	    my @st = split;
	    $r = $st[ $col ];
	    last;
	}
    }else{
	my $st = output_of_command( 'netstat', '-b', '-I', $int );
	# 1st matching line
	foreach my $l (split /\n/, $st){
	    next unless $l =~ /^$int/;
	    my @st = split /\s+/, $l;
	    $r = $st[ ($dir eq 'in') ? 4 : 5 ];
	    last;
	}
    }

    print "$r\n";
}

sub output_of_command {
    my $cmd = shift;
    my @arg = @_;
    my( $f );

    # we don't just `$cmd $arg` in case we have a naughty $arg...
    open(F, "-|") || exec($cmd, @arg);
    while( <F> ){
	$f .= $_;
    }
    close F;
    $f;
}

sub verify_access {
    my @addr = @_;

    # get address of peer
    my $sk = getpeername(STDIN);
    die "getpeername failed: $!\n" unless $sk;
    
    my $af = unpack('xC', $sk);
    my( $srcip, $scrport );
			  
    if( $HAVE_S6 && $af == AF_INET6 ){
	($srcport, $srcip) = unpack_sockaddr_in6($sk);
    }else{
	($srcport, $srcip) = sockaddr_in($sk);
    }
    
    $srcip = xxx_inet_ntoa($srcip);
    
    # check
    foreach my $a (@addr){
	# canonicalize
	$a = xxx_inet_ntoa( resolve($a) );

	return 1 if $a eq $srcip;
    }
    
    die "access denied from $srcip\n";
    
}

sub xxx_inet_ntoa {
    my $n = shift;

    return inet_ntoa($n) if length($n) == 4;
    return undef unless $HAVE_S6;
    return inet_ntop(AF_INET6, $n) if length($n) == 16;
    
    "X.X.X.X";
}

sub resolve {
    my $a = shift;
    my( $opt, $ip, @o );

    if( $a =~ /^\d+\.\d+\.\d+\.\d+$/ ){
	$ip = inet_aton( $a );
    }elsif( $a =~ /^[a-f\d:]+$/i && $HAVE_S6 ){
	$ip = inet_pton(AF_INET6, $a);
    }elsif( $HAVE_S6 ){
	for (0 .. 1){
	    # 1st look for a v6 addr, then v4
	    $ip   = gethostbyname2($a, AF_INET6);
	    $ip ||= gethostbyname2($a, AF_INET);
	    last if length($ip) != 0;
	    sleep 1;
	}
    }else{
	for (0 .. 1){
	    $ip = gethostbyname($a);
	    last if length($ip) != 0;
            sleep 1;
        }
    }

    $ip;
}
