#!/usr/bin/perl -w
#
# RMRTG - Remote MRTG Monitoring
#
use strict;
use vars qw(
	$commanddir $command $command_only
	$lockdir $lockfile 
	$request_phase $output $retcode 
	%RETCODES $TRACE $VERSION $CRLF
);
use constant MIN_LOCK_AGE => 1/1440;	# time in days

# Initialize global variables
$| = 1;
$CRLF = "\x0D\x0A";

($VERSION) = ('$Revision: 1.4 $' =~ /([\d\.]+)/ );

%RETCODES = (
	200 => 'OK - Request Successful',
	201 => 'OK - No cryptography, sending clear',
	300 => 'WARNING - Your request was malformed',
	400 => 'ERROR - No Command Issued',
	402 => 'ERROR - Lock is not stale',
	403 => 'ERROR - Command Found but Cannot Execute',
	404 => 'ERROR - External Command not found',
	405 => 'ERROR - Specified Command is not a plain file',
	500 => 'FATAL - Protocol Error',
);

$TRACE = 0;
$request_phase = 1;
$commanddir = '/home/mrtg/bin/';
$lockdir = '/home/mrtg/tmp/';
$retcode = '500';

TRACE("Version $VERSION Starting work");

# Enter the request phase where we read the headers in
while ( $request_phase ) {
	TRACE("Request acceptance while loop");

	my $buffer = <STDIN>;
	chomp $buffer;
	$buffer =~ s/\r//g;
	TRACE("Incoming line <$buffer>");
	
	for ( $buffer ) {
		/^COMMAND (.+)$/ && do {
			my $fullcomm = $1;
			TRACE("Command header - text is <$fullcomm>");
			$retcode = '200';
			# trap nasty characters
			$retcode = '300' if $fullcomm =~ s/\.\.//g;
			$retcode = '300' if $fullcomm =~ s/[^\w\.\/:\s-]//g;
			$command = $fullcomm;
			
			# derive the first word only
			$command_only = $command;
			$command_only =~ s/^(\S+).*/$1/;
			
			# derive the name of the lockfile
			$lockfile = $command_only;
			$lockfile =~ s/\..+$//;
			$lockfile =~ s/\W/_/g;
			$lockfile .= '.lock';
			TRACE("Lockfile is <$lockfile>");
			TRACE("Command name is <$command_only>");
			last;
		};
		/^TRACE (.+)$/ && do {
			my $switch = $1;
			$TRACE = $switch ? 1 : 0;
			TRACE("Trace header - switch is <$switch>");
			last;
		};
		/^$/ && do {
			TRACE("Blank line, ending request phase");
			$request_phase = 0;
			last;
		};
		# we just ignore unrecognized headers
		TRACE("Unrecognized header <$_>");
	}
}

# have we got a command?
if ( not defined $command ) {
	TRACE("command is not defined");
	$retcode = '400';
} else {
	if ((-e $lockdir.$lockfile) && (-M _ < MIN_LOCK_AGE)) {
		TRACE("Lock is too young");
		$retcode = '402';
	} else {	# it's an old lock, or no lock
		if ( ! -e $commanddir.$command_only) {
			$retcode = '404';
		} elsif (-d $commanddir.$command_only || -l $commanddir.$command_only || -p _ || -S _ || -b _ || -c _ || -z _ || ! -f _) {
			TRACE('not a plain normal file');
			$retcode = '405';
		} elsif (-x _) {
			TRACE('command is executable');
			$output = qx($commanddir$command);
			touch_lock( $lockdir.$lockfile );
		} else {
			$retcode = '403';
		}
	}
}

# now to return something to the client
print $retcode . ' ' . $RETCODES{$retcode} . $CRLF . $CRLF;
if ($retcode < 400) {
	print $output;
} else {
	print "0${CRLF}0${CRLF}ERROR CONDITION$CRLF" . $retcode . ' ' . $RETCODES{$retcode} . $CRLF;
}

exit(0);

###### SUBROUTINES ###################################

sub touch_lock {
	my $filename = shift;
	if (-e $filename) {
		TRACE("Updating existing lock");
		my $now = time();
		utime( $now, $now, $filename );	
	} else {
		TRACE("making new lock");
		local *LOCK;
		open(LOCK, "> $filename") or die("Cannot make lockfile: $!");
		print LOCK $$;
		close LOCK;
	}
}

sub TRACE {
	return unless $TRACE;
	my $msg = shift;
	print "Rmrtg-trace: $msg$CRLF";
}

=head1 NAME

rmrtg - server run by inetd to service remote MRTG monitoring requests

=head1 SYNOPSIS

Put it in inetd.conf and let it run.

It is a simple call and response server - the client says some stuff, then sends
a blank line (like after HTTP request headers. The server then sends some headers
followed by a blank line, and then the 4 lines of MRTG output.

=head1 COPYRIGHT

Copyright (c) P Kent 2001. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

A copy of the GPL is supplied in the file 'GPL'

	                            NO WARRANTY

	BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
	FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
	OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
	PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
	OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
	MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
	TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
	PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
	REPAIR OR CORRECTION.

=head1 CVS

$Id: rmrtg,v 1.4 2002/01/30 04:54:53 piers Exp $

=cut
