#!/usr/bin/perl -w

# Copyright 1999, 2000, 2001 (c) Thomas Erskine <thomas.erskine@sourceworks.com>
# See the COPYRIGHT file with the distribution.

# unix-status-collector - a remstats collector for unix-status
# $Id: unix-status-collector.pl,v 1.16 2001/08/31 21:07:20 remstats Exp $

# - - -   Configuration   - - -

use strict;

# What is this program called, for error-messages and file-names
$main::prog = 'unix-status-collector';
# Which collector is this
$main::collector = 'unix-status';
# Where is the default configuration dir
$main::config_dir = '/etc/remstats/config';
# Which port is the unix-status collector sitting on
$main::port = 1957;
# How long to wait for a response
$main::timeout = 10; # seconds

# - - -   Version History   - - -

(undef, $main::version) = split(' ', '$Revision: 1.16 $');

# - - -   Setup   - - -

use lib '.', '/usr/lib/remstats/lib', '/usr/lib/perl5/';
require "remstats.pl";
use Getopt::Std;
use RRDs;
require "socketstuff.pl";

# Parse the command-line
my %opt = ();
my @hosts;
getopts('d:f:FhH:p:t:u', \%opt);

if (defined $opt{'h'}) { &usage; } # no return
if (defined $opt{'d'}) { $main::debug = $opt{'d'}; } else { $main::debug = 0; }
if (defined $opt{'f'}) { $main::config_dir = $opt{'f'}; }
if (defined $opt{'F'}) { $main::force_collection = 1; } else { $main::force_collection = 0; }
if( defined $opt{'H'}) { @hosts = split(',', $opt{'H'}); }
if (defined $opt{'p'}) { $main::port = $opt{'p'}; }
if (defined $opt{'t'}) { $main::timeout = $opt{'t'}+0; }
if (defined $opt{'u'}) { $main::use_uphosts = 0; } else { $main::use_uphosts = 1; }

&read_config_dir($main::config_dir, 'general', 'html', 'oids', 
	'rrds', 'groups', 'host-templates', 'hosts');
%main::uphosts = &get_uphosts if ($main::use_uphosts);

# no buffering when debugging
if ($main::debug) { $| = 1; }

# - - -   Mainline   - - -

my (%data, $host, $ip, $start_time, $run_time);
$start_time = time();
$main::entries_collected = $main::entries_used = $main::requests = 0;
my $tmpfile = $main::config{DATADIR} .'/LAST/'. $main::collector .'.'. $$;
my $lastfile = $main::config{DATADIR} .'/LAST/'. $main::collector;
open (TMP, ">$tmpfile") or &abort("can't open $tmpfile: $!");

unless( @hosts) { @hosts = keys %{$main::config{HOST}}; }

foreach $host (@hosts) {
	next if ($host eq '_remstats_');
	if ($main::use_uphosts and not defined $main::uphosts{$host}) {
		&debug("$host is down(uphosts); skipped") if ($main::debug);
		next;
	}
	$ip = &get_ip($host);
	next unless (defined $ip);
	unless (defined $main::config{HOSTCOLLECTEDBY}{$main::collector}{$host}) {
		&debug("  $host not collected by $main::collector; skipped") if ($main::debug>1);
		next;
	}

# collect the data from the remote collector
	&collect_host($host, $main::port);
}

# Now remstats instrumentation info
my $now = time;
$run_time = $now - $start_time;
print <<"EOD_INSTRUMENTATION";
_remstats_ $now ${main::collector}-collector:requests $main::requests
_remstats_ $now ${main::collector}-collector:collected $main::entries_collected
_remstats_ $now ${main::collector}-collector:used $main::entries_used
_remstats_ $now ${main::collector}-collector:runtime $run_time
EOD_INSTRUMENTATION

close(TMP) or &abort("can't open $tmpfile: $!");
rename $tmpfile, $lastfile or &abort("can't rename $tmpfile to $lastfile: $!");

exit 0;

#----------------------------------------------------------------- usage ---
sub usage {
	print STDERR <<"EOD_USAGE";
$main::prog version $main::version
usage: $main::prog [options]
where options are:
    -d nnn  enable debugging output at level 'nnn'
    -f fff  use 'fff' for config-dir [$main::config_dir]
    -F      force collection even it it's not time yet
    -h      show this help
    -H HHH  only try hosts from 'HHH', a comma-separated list
    -p ppp  connect to server on port 'ppp' [$main::port]
    -t ttt  set timeout to 'ttt' [$main::timeout]
    -u      ignore uphosts file
EOD_USAGE
	exit 0;
}

#----------------------------------------------------------------- debug ---
sub debug {
	my $msg = join('', @_);

	if ($main::debug) { print STDERR "DEBUG: $msg\n"; }
0;
}

#------------------------------------------------------------------ abort ---
sub abort {
	my $msg = join('', @_);
	print STDERR "$main::prog: ABORT: $msg\n";
	exit 1;
}

#------------------------------------------------------- error ---
sub error {
	my $msg = join('', @_);
	print STDERR "$main::prog: ERROR: $msg\n";
}

#------------------------------------------------------------- ifstatus ---
# Convert interface status to a string
sub if_status {
	my $n = shift @_;
	my $status;

	if ($n == 1) { $status = 'UP'; }
	elsif ($n == 2) { $status = 'DOWN'; }
	else { $status = 'UNKNOWN'.$status; }
$status;
}

#----------------------------------------------------------- collect_host ---
sub collect_host {
	my ($host, $port) = @_;
	my ($line, $variable, $value, $os_name, $os_release, $os_version,
		$realrrd, $wildrrd, $extra, %asked, $ds, $socket, $status,
		$section, $pattern, $fixed_name, $ifstatus, $fixedrrd, $now);
	my ($timeout) = $main::timeout;

	&debug("doing host $host") if ($main::debug);
	($socket, $status, $timeout) = &open_socket( $host, $port, $timeout);
	unless ($status == $main::SOCKET_OK) {
		&debug("couldn't connect to $host:$port") if ($main::debug);
		return undef;
	}
	&debug("  connected") if ($main::debug>1);

# Ask for only what we need
	$now = time();
	($status, $timeout) = &write_socket( $socket, "UNAME\nUPTIME\nTIME $now\n", $timeout, 
		"'UNAME+UPTIME+TIME' to $host");
	unless ($status == $main::SOCKET_OK) {
		$socket->close();
		&debug("$host:$port: error sending UNAME+UPTIME+TIME") if ($main::debug);
		return undef;
	};
	&debug("sent UNAME+UPTIME+TIME") if ($main::debug);
	$asked{UNAME} = 1;
	$asked{UPTIME} = 1;
	$asked{TIME} = 1;
	$main::requests += 2;

	foreach $realrrd (@{$main::config{HOST}{$host}{RRDS}}) {
		($wildrrd, undef, undef, $fixedrrd) = &get_rrd($realrrd);
		&debug(" rrd=$realrrd($wildrrd)") if ($main::debug);
		next unless ($main::config{RRD}{$wildrrd}{SOURCE} eq $main::collector);

# Check whether it's at all time to collect data
		unless ($main::force_collection or
				&check_collect_time($host, $wildrrd, $fixedrrd)) {
			&debug("  not time yet for $realrrd($wildrrd): skipped") if ($main::debug>1);
			next;
		}
		&debug("  doing rrd $realrrd($wildrrd)") if ($main::debug);

		foreach $ds ( @{$main::config{RRD}{$wildrrd}{DATANAMES}} ) {
			if (defined $main::config{RRD}{$wildrrd}{$ds}{EXTRA}) {
				$extra = $main::config{RRD}{$wildrrd}{$ds}{EXTRA};
				&debug("  ds=$ds, extra=$extra") if ($main::debug);
			}
			else { next; }

			($section, $pattern) = split(' ',$extra,2);
			$section = lc $section;
			if (defined $pattern and $section eq 'ps') {
				($status, $timeout) = &write_socket( $socket, "$ds ps $pattern\n", 
					$timeout, "'$ds ps $pattern' to $host");
				unless ($status == $main::SOCKET_OK) {
					$socket->close();
					return undef ;
				};
				&debug("sent '$ds ps $pattern'") if ($main::debug)
			}
                        if (defined $pattern and $section eq 'proc') {
                                ($status, $timeout) = &write_socket( $socket, "$ds proc $pattern\n",
                                        $timeout, "'$ds proc $pattern' to $host");
                                unless ($status == $main::SOCKET_OK) {
                                        $socket->close();
                                        return undef ;
                                };
                                &debug("sent '$ds proc $pattern'") if ($main::debug)
                        }

			$section = uc $section;
			if (defined $asked{$section}) { next; }
			else { $asked{$section} = 1; }
			($status, $timeout) = &write_socket( $socket, $section ."\n", $timeout,
				"'$section' to $host");
			unless ($status == $main::SOCKET_OK) {
				 $socket->close();
				 return undef;
			};
			&debug("sent $section") if ($main::debug);
			++$main::requests;
		}
	}

# remote debugging
	if ($main::debug) {
		($status, $timeout) = &write_socket( $socket, "DEBUG $main::debug\n", 
			$timeout, "'DEBUG' to $host");
		unless ($status == $main::SOCKET_OK) {
			$socket->close();
			return undef;
		};
		&debug("sent DEBUG") if ($main::debug);
	}

	($status, $timeout) = &write_socket( $socket, "GO\n", $timeout,
		"'GO' to $host");
	unless ($status == $main::SOCKET_OK) {
		$socket->close();
		return undef;
	};
	&debug("sent GO") if ($main::debug);

# Get the response
	while (($line, $status, $timeout) = &read_socket( $socket, $timeout, 
			"response from $host"), 
			(defined $line and ($status == $main::SOCKET_OK))) {
		$line =~ tr/\015\012//d;
		++$main::entries_collected;
		next if ($line =~ /^#/ or $line =~/^\s*$/);

# Deal with special output
		if ($line =~ /^DEBUG:\s*(.*)/) {
			&debug("REMOTE($host): $1") if ($main::debug);
		}
		elsif ($line =~ /^ERROR:\s*(.*)/) {
			&error("REMOTE($host): $1");
		}
		elsif ($line =~ /^ABORT:\s*(.*)/) {
			&abort("REMOTE($host) $1");
		}
		else {
			print "$host $line\n";
			print TMP "$host $line\n";
			(undef, $variable, $value) = split(' ',$line,3);
			next unless (defined $value);
			if ($variable eq 'machine') {
				&put_status($host, 'HARDWARE', $value);
			}
			elsif ($variable eq 'os_name') {
				$os_name = $value;
			}
			elsif ($variable eq 'os_release') {
				$os_release = $value;
			}
			elsif ($variable eq 'os_version') {
				$os_version = $value;
			}
			elsif ($variable eq 'uptime') {
				&log_reboot( $host, $value, 'UNIX-STATUS');
				&put_status($host, 'UPTIME', $value);
				&put_status($host, 'UPTIME.html', &show_uptime($value));
				if ($value < $main::config{MINUPTIME}) {
					&put_status( $host, 'UPTIMEFLAG.html', $main::config{UPTIMEFLAG});
				}
				else {
					&put_status( $host, 'UPTIMEFLAG.html', '');
				}
			}
			elsif ($variable =~ /^interface_status:(\S+)\s+(\d+)/) {
				$fixed_name = &to_filename($1);
				$ifstatus = &if_status($2);
				&put_status( $host, 'STATUS-if-'.$fixed_name, $ifstatus);
			}
			++$main::entries_used;
		}
	}
	$socket->close();
	if ($status == $main::SOCKET_TIMEOUT) {
		&debug("timeout collecting $host:$port") if ($main::debug);
	}
	elsif ($status == $main::SOCKET_ERROR) {
		&debug("error collecting $host:$port") if ($main::debug);
	}
	else {
		unless (defined $os_name) { $os_name = 'UNKNOWN'; }
		unless (defined $os_release) { $os_release = 'UNKNOWN'; }
		unless (defined $os_version) { $os_version = 'UNKNOWN'; }
		&put_status($host, 'SOFTWARE', "$os_name $os_release $os_version");
	}
}

#--------------------------------------------- keep_strict_happy ---
# "use strict" isn't very smart, so keep it happy by mentioning
# variables from elsewhere.
sub keep_strict_happy {
	$main::SOCKET_TIMEOUT = $main::SOCKET_ERROR = 0;
}
