#!/usr/bin/perl
#
# Copyright (C) 1999 - 2002 Moritz Both
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# 
# The author can be reached via email: moritz@daneben.de, or by
# snail mail: Moritz Both, Im Moore 26, 30167 Hannover,
#             Germany. Phone: +49-511-1610129

# amanda status page. to installed setuid 4555 root 

$rcs_id = '$Id: amhttp.pl,v 1.11 2001/11/29 14:02:43 moritz Exp $';

$amconfdir = "/etc/amanda";
$amdatadir = "/var/lib/amanda";
$amconfig = "DailySet1";
$amrunuser = "operator";
$amrungroup = "disk";

#$dbfile = "/tmp/a";

use CGI qw/:standard/;
use POSIX qw/setsid/;

$ENV{'PATH'} = "/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin";
$< = $>;
$( = $);

$css=<<EOF;
body {
	width: 100%;
	margin: 0pt;
}
P,H1,H2,H3 {
	font-family: Tahoma,Arial,"Sans Serif",Helvetica;
	margin: 0pt;
}
H1 {
	font-size:16pt;
	line-height:18pt;
}
H2 {
	font-size:12pt;
	line-height:14pt;
}
P {
	font-size:10pt;
	line-height: 12pt;
}
PRE {
	font-size:9pt;
	line-height:10pt;
}
EOF


$q = new CGI;
@config_selected = $q->param('configs');
$config_selected = $config_selected[0];
$config_selected =~ /([\w\d]+)/;
$config_selected = $1;
if ($config_selected && -d "$amconfdir/$config_selected") {
	$amconfig = $config_selected;
}
print $q->header,
	$q->start_html(-title=>'Amanda Status and Admin Page',
		-BGCOLOR=>'#b0ffb0',
		-style=>{-code=>$css}),
		$q->h1('Amanda Status and Admin Page'),
		$q->p(scalar localtime),
		$q->startform;

close STDERR;
open(STDERR, ">&STDOUT");
$| = 1;

# get a list of available configurations.
opendir(DIR, $amconfdir);
while(($d = readdir(DIR))) {
	next if ($d =~ /^\./);
	if (-d "$amconfdir/$d") {
		push(@configs, $d) if (-d "$amconfdir/$d");
		if (-f "$amdatadir/$d/amdump") {
			$amdumplog = "$amdatadir/$d/amdump";
			$amdump_is_running = $d;
			$amdump_is_running =~ /^([\w\d\-\_]+)$/;
			$amdump_is_running = $1;
		}
		elsif (-f "$amdatadir/$d/log") {
			$amflush_or_not_cleaned_up = $d;
			$amflush_or_not_cleaned_up =~ /^([\w\d\-\_]+)$/;
			$amflush_or_not_cleaned_up = $1;
		}
	}
}
closedir(DIR);

# check if amverify is running.
opendir(DIR, '/tmp/');
while(($d = readdir(DIR)) && !$amverify_is_running) {
	if ($d =~ /^amverify\.(\d+)$/) {
		$amverify_is_running=1;
		$verify_dir = "/tmp/$d";
	}
}
closedir(DIR);
opendir(DIR, '/tmp/amanda/');
while(($d = readdir(DIR)) && !$amverify_is_running) {
	if ($d =~ /^amverify\.(\d+)$/) {
		$amverify_is_running=1;
		$verify_dir = "/tmp/amanda/$d";
	}
}
closedir(DIR);

$amanda_is_running = $amverify_is_running | $amdump_is_running;
$print_action_buttons = 1;

if ($amflush_or_not_cleaned_up) {
	print $q->h2("Either, amflush is running, or amanda aborted irregulary".
		" in configuration $amflush_or_not_cleaned_up"),
		$q->p("You'll need to wait for amflush to finish or run amclea".
		"nup manually.");
	$amconfig = $amflush_or_not_cleaned_up;
	$print_action_buttons = 0;
}
elsif ($amdump_is_running) {
	print $q->h2("Amanda is currently running (config $amdump_is_running).");
	$amconfig = $amdump_is_running;
}
elsif ($amverify_is_running) {
	my(@report);
	@report = <REPORT> if open(REPORT, "$verify_dir/report");
	close REPORT;
	print $q->h2('Amanda currently runs a tape verify'),
		$q->p("or, at least, there is a dirictory $verify_dir"),
		$q->p('As long as this is so, we cannot do anything else here.'),
		$q->pre(@report);
}
elsif ($q->param('verify_sure') || $q->param('rundump_sure')) {
	# do not display the status, its changing soon.
	;
}
else {
	print $q->h2('Amanda ist currently NOT running.');
}

# display a list of available configurations.
print $q->scrolling_list(-name=>'configs',
			-values=>\@configs,
			-default=>$amconfig,
			-size=>1),
	$q->submit(-name=>'config_submit',
			-value=>'change config / reload status');

# decide what to do.
if ($q->param('verify') && !$amanda_is_running) {
	# verify tape are you sure form
	print $q->p('Hitting this button will immediately start the verify
		process. Make sure a tape is in the drive.'),
		$q->submit(-name=>'verify_sure', -value=>'go for it') ;
	$print_action_buttons = 0;
}
elsif ($q->param('verify_sure') && !$amanda_is_running) {
	# verify.
	print $q->p('Running the verify process in the background...');
	my $ok = start_process({ 'user'=>$amrunuser, 
		'group'=>$amrungroup,
		'cmd'=>"amverify $amconfig"});
	print $q->p($ok ? "...suceeded." : "...FAILED!");
	$print_action_buttons = 0;
}
elsif ($q->param('rundump') && !$amanda_is_running) {
	# run a dump.
	print $q->p("Hitting this button will immediately start the dump
                process using configuration $amconfig. 
		Make sure a tape is in the drive."),
		$q->checkbox(-name=>'rundump_verify',
			-checked=>'checked',
			-value=>'ON',
			-label=>'with verify'),
                $q->submit(-name=>'rundump_sure', -value=>'go for it') ;
	$print_action_buttons = 0;
}
elsif ($q->param('rundump_sure') && !$amanda_is_running) {
	# run the dump.
	print $q->p('Running the amdump process in the background...');
	# perm.: planner needs uid=root or gid=root (x permission)
	#        taper needs gid=disk (accessing tape)
	#	 driver needs uid=operator (hard coded inside)
	my @cmds;
	push(@cmds, { 	'user'=>'nobody',
			'group'=>$amrungroup,
			'cmd'=>'mt lock'
			});
	push(@cmds, { 	'user'=>$amrunuser, 
			'group'=>"$amrungroup root", 
			'cmd'=>"amdump $amconfig" 
			});
	if ($q->param('rundump_verify')) {
		push(@cmds, { 	'user'=>$amrunuser,
				'group'=>$amrungroup,
				'cmd'=>"amverify $amconfig"
				});
	}
	push(@cmds, { 	'user'=>'nobody',
			'group'=>$amrungroup,
			'cmd'=>'mt unlock; mt offline'
			});
	my $ok = start_process(@cmds);
	print $q->p($ok ? "...suceeded." : "...FAILED!");
}
elsif ($q->param('amlabel') && !$amanda_is_running) {
	# label a tape. are we sure?
	my $labeltext = $q->param('label');
	$labeltext =~ /^([\w\d\_\-]+)$/;
	$labeltext = $1;
	print "<p>Label the tape in the drive with label <tt>$labeltext</tt>.
		All data on the tape will be lost.</p>",
		$q->submit(-name=>'amlabel_sure', 
			-value=>'label and erase tape'),
		$q->hidden(-name=>'label', -value=>$labeltext);
	$print_action_buttons = 0;
}
elsif ($q->param('amlabel_sure') && !$amanda_is_running) {
	# label it.
	my $labeltext = $q->param('label');
	$labeltext =~ /^([\w\d\_\-]+)$/;
	$labeltext = $1;
	my $cmd = "su operator -c \"amlabel $amconfig $labeltext\"";
	print "<p>Running command: <tt>$cmd</tt></p>\n",
		$q->p('output of command follows...'), "\n<pre>\n";
	system($cmd);
	print "</pre>\n";
}
else {
	if ($amdump_is_running) {
		print "<p>running <tt>amstatus $amconfig</tt>...</p>\n<pre>\n";
		my @rrr = @ARGV;
		{
			@ARGV = ( $amconfig );
			package amstatus;
			do "/usr/sbin/amstatus";
		}
		@ARGV = @rrr;
		# my $cmd = qq(su $amrunuser -c "/usr/sbin/amstatus $amconfig");
		# print $q->pre(`$cmd`);
		print "</pre>\n";
	}
	elsif (!$amanda_is_running) {
		print $q->p("running amcheck $amconfig...");
		my $cmd = qq(su $amrunuser -c "/usr/sbin/amcheck $amconfig 2>&1");
		print $q->pre(`$cmd`);
	}
}

&start_admin_tasks;
if (!$amanda_is_running && $print_action_buttons)  {
	admin_task(
		"Run a dump manually with configuration $amconfig (can be 
		changed at the top of the page). See the amverify output
		above this to check if everything is ready to go.",
		$q->submit(-name=>'rundump',
			-value=>'Run a Dump')
		); 
	admin_task(
		$q->p("Label a tape. WARNING: THE DATA ON THIS TAPE WILL BE LOST") .
			"<p>label: " .
			textfield(-name=>'label',
				-size=>20,
				-maxlength=>20) .
			"</p>",
			$q->submit(-name=>'amlabel',
                        -value=>'Label Tape'));
	admin_task(
		'Verify a tape. The tape must already be in the tape drive,
		and the result will be emailed to the backup admin. This
		can take a long time (hours).',
		$q->submit(-name=>'verify',
			-value=>'Verify Tape')
		);
}

print '</table>',
	$q->endform,
	$q->hr,
	"\n",
	$q->p("server uptime and load:\n"),
	$q->pre(`uptime`),
	'<address><font size=-2>(debug info following)',
	$q->dump,
	$q->p("amhttp $rcs_id"),
	"</font></address>\n",
	$q->end_html;

sub start_admin_tasks {
	print "\n", 
		$q->br,
		'<table>';
}

sub admin_task {
	my $content_left = shift;
	my $content_right = shift;

	print "<tr><td>$content_left</td><td>$content_right</td></tr>";
}

## double fork a process, run as 
# @return true for sucess
# @param 1... commands to start, each is a ref to a hash. The hash contains
#	parameters: 'cmd' - the command line, 'user' and 'group' - the user
#	and group name to run under.
sub start_process {
	my $pid1 = fork();
	if ($pid1 == 0) {
		# child of fork 1
		my $pid2 = fork();
		if ($pid2 == 0) {
			# child of fork 2 - run the commands in a loop
			close STDERR;
			close STDOUT;
			close STDIN;
			POSIX::setsid();
			my $cmd = shift;
			do {
				my $user = $$cmd{'user'};
				$user = "nobody" if (!$user);
				my ($name,$passwd,$uid,$gid, $quota,$comment,
					$gcos,$dir,$shell,$expire) 
					= getpwnam($user);
				my $group = $$cmd{'group'};
				$group = "nogroup" if (!$group);
				my $othergid = "";
				my $firstgid;
				my ($name,$passwd,$gid,$members);
				foreach(split(/\s+/, $group)) {
					($name,$passwd,$gid,$members) 
						= getgrnam($_);
					huh("add group: $_ ($gid)");
					$othergid .= " " if ($othergid);
					$othergid .= $gid;
					$firstgid = $gid unless ($firstgid);
				}
				huh("nextcmd: user=$$cmd{'user'} uid=$uid group=$$cmd{'group'} gid=$firstgid othergid=$othergid cmd=$$cmd{'cmd'}");
				my $pid3 = fork();
				if ($pid3 == 0) {
					$) = "$firstgid $othergid";
					$( = $firstgid;
					$> = $uid;
					$< = $>;
					huh("running >$$cmd{'cmd'}< with");
					huh("id>`id`<");
					huh("output follows...");
					open(STDERR, ">/dev/null");
					open(STDOUT, ">/dev/null");
					exec $$cmd{'cmd'};
					exit 1;
				}
				waitpid($pid3, 0) if ($pid3 > 0);
			} while(defined($cmd = shift));
			exit 0;
		}
			
		# do not wait for child.
		exit 0;
	}
	# wait for child.
	waitpid($pid1, 0) if ($pid1);
	return ($pid1 > 0);
}

sub huh {
	return unless ($dbfile);
	my $oldumask = umask(0);
	open(A, ">>$dbfile");
	foreach(@_) {
		print A "($$) $_\n";
	}
	close A;
	umask($oldumask);
}
