# status-lib.pl
# Functions for getting the status of services

do '../web-lib.pl';
&init_config();
require '../ui-lib.pl';
%access = &get_module_acl();

$services_dir = "$module_config_directory/services";
$cron_cmd = "$module_config_directory/monitor.pl";

$oldstatus_file = "$module_config_directory/oldstatus";
$fails_file = "$module_config_directory/fails";

%monitor_os_support = ( 'traffic' => { 'os_support' => '*-linux freebsd' },
		      );

# list_services()
# Returns a list of all services this module knows how to get status on.
# If this is the first time the function is called a default set of services
# will be setup.
sub list_services
{
my (%mod, @rv);
if (!-d $services_dir) {
	# setup initial services
	mkdir($module_config_directory, 0700);
	mkdir($services_dir, 0700);
	system("cp services/* $services_dir");
	}
map { $mod{$_}++ } &list_modules();
opendir(DIR, $services_dir);
while($f = readdir(DIR)) {
	next if ($f !~ /^(.*)\.serv$/);
	local $serv = &get_service($1);
	next if (!$serv || !$serv->{'type'} || !$serv->{'id'});
	if ($serv->{'depends'}) {
		local $d;
		map { $d++ if (!$mod{$_}) } split(/\s+/, $serv->{'depends'});
		push(@rv, $serv) if (!$d);
		}
	else {
		push(@rv, $serv);
		}
	}
closedir(DIR);
return @rv;
}

# get_service(id)
sub get_service
{
local %serv;
&read_file("$services_dir/$_[0].serv", \%serv);
$serv{'fails'} = 1 if (!defined($serv{'fails'}));
$serv{'_file'} = "$services_dir/$_[0].serv";
if (!defined($serv{'notify'})) {
	$serv{'notify'} = 'email pager snmp';
	}
$serv{'remote'} = "*" if (!$serv{'remote'});
return $_[0] ne $serv{'id'} ? undef : \%serv;
}

# save_service(&serv)
sub save_service
{
mkdir($services_dir, 0755) if (!-d $services_dir);
&lock_file("$services_dir/$_[0]->{'id'}.serv");
&write_file("$services_dir/$_[0]->{'id'}.serv", $_[0]);
&unlock_file("$services_dir/$_[0]->{'id'}.serv");
}

# delete_service(serv)
sub delete_service
{
&lock_file("$services_dir/$_[0]->{'id'}.serv");
unlink("$services_dir/$_[0]->{'id'}.serv");
&unlock_file("$services_dir/$_[0]->{'id'}.serv");
}

# service_status(&service, [from-cgi])
# Gets the status of a service, possibly on another server. If called in
# an array content, the status of all hosts for this monitor are returned.
sub service_status
{
local $t = $_[0]->{'type'};
local @rv;
foreach $r (split(/\s+/, $_[0]->{'remote'})) {
	local $rv;
	eval {
		local $SIG{'ALRM'} = sub { die "status alarm\n" };
		alarm(60);	# wait at most 60 secs for a result
		if ($r ne "*") {
			# Make a remote call to another webmin server
			&remote_error_setup(\&remote_error);
			$remote_error_msg = undef;
			&remote_foreign_require($r, 'status', 'status-lib.pl')
				if (!$done_remote_status{$r}++);
			if ($remote_error_msg) {
				$rv = { 'up' => -2,
					 'desc' => "$text{'mon_webmin'} : $remote_error_msg" };
				}
			else {
				local %s = %{$_[0]};
				$s{'remote'} = '*';
				($rv) = &remote_foreign_call($r, 'status',
					    'service_status', \%s, $_[1]);
				if ($remote_error_msg) {
					$rv = { 'up' => -2, 'desc' =>
					    "$text{'mon_webmin'} : $remote_error_msg" };
					}
				}
			}
		elsif ($t =~ /^(\S+)::(\S+)$/) {
			# Call to another module
			local ($mod, $mtype) = ($1, $2);
			&foreign_require($mod, "status_monitor.pl");
			$rv = &foreign_call($mod, "status_monitor_status",
					    $mtype, $_[0], $_[1]);
			}
		else {
			# Just include and use the local monitor library
			do "${t}-monitor.pl" if (!$done_monitor{$t}++);
			local $func = "get_${t}_status";
			$rv = &$func($_[0],
				     $_[0]->{'clone'} ? $_[0]->{'clone'} : $t,
				     $_[1]);
			}
		alarm(0);
		};
	if ($@ eq "status alarm\n") {
		push(@rv, { 'up' => -3,
			    'remote' => $r });
		}
	elsif ($@) {
		# A real error happened
		die $@;
		}
	else {
		$rv->{'remote'} = $r;
		push(@rv, $rv);
		}
	}
return wantarray ? @rv : $rv[0];
}

sub remote_error
{
$remote_error_msg = join("", @_);
}

# list_modules()
# Returns a list of all modules available on this system
sub list_modules
{
return map { $_->{'dir'} } grep { &check_os_support($_) }
	&get_all_module_infos();
}

# list_handlers()
# Returns a list of the module's monitor type handlers, and those
# defined in other modules.
sub list_handlers
{
local ($f, @rv);
opendir(DIR, ".");
while($f = readdir(DIR)) {
	if ($f =~ /^(\S+)-monitor\.pl$/) {
		local $m = $1;
		local $oss = $monitor_os_support{$m};
		next if ($oss && !&check_os_support($oss));
		push(@rv, [ $m, $text{"type_$m"} ]);
		}
	}
closedir(DIR);
local $m;
foreach $m (&get_all_module_infos()) {
	local $mdir = defined(&module_root_directory) ?
		&module_root_directory($m->{'dir'}) :
		"$root_directory/$m->{'dir'}";
	if (-r "$mdir/status_monitor.pl" &&
	    &check_os_support($m)) {
		&foreign_require($m->{'dir'}, "status_monitor.pl");
		local @mms = &foreign_call($m->{'dir'}, "status_monitor_list");
		push(@rv, map { [ $m->{'dir'}."::".$_->[0], $_->[1] ] } @mms);
		}
	}
return @rv;
}

# depends_check(&service, [module]+)
sub depends_check
{
return if ($_[0]->{'id'});	# only check for new services
if ($_[0]->{'remote'}) {
	# Check on the remote server
	foreach $m (@_[1..$#_]) {
		&remote_foreign_check($_[0]->{'remote'}, $m) ||
			&error(&text('depends_remote', "<tt>$m</tt>",
				     "<tt>$_[0]->{'remote'}</tt>"));
		}
	}
else {
	# Check on this server
	foreach $m (@_[1..$#_]) {
		local %minfo = &get_module_info($m);
		%minfo || &error(&text('depends_mod', "<tt>$m</tt>"));
		&check_os_support(\%minfo) ||
			&error(&text('depends_os', "<tt>$minfo{'desc'}</tt>"));
		}
	$_[0]->{'depends'} = join(" ", @_[1..$#_]);
	}
}

# find_named_process(regexp)
sub find_named_process
{
foreach $p (&foreign_call("proc", "list_processes")) {
	$p->{'args'} =~ s/\s.*$//; $p->{'args'} =~ s/[\[\]]//g;
	if ($p->{'args'} =~ /$_[0]/) {
		return $p;
		}
	}
return undef;
}

# smtp_command(handle, command)
sub smtp_command
{
local ($m, $c) = @_;
print $m $c;
local $r = <$m>;
if ($r !~ /^[23]\d+/) {
	&error(&text('sched_esmtpcmd', "<tt>$c</tt>", "<tt>$r</tt>"));
	}
}

# setup_cron_job()
# Create a cron job based on the module's configuration
sub setup_cron_job
{
&lock_file($cron_cmd);
&foreign_require("cron", "cron-lib.pl");
local ($j, $job);
foreach $j (&foreign_call("cron", "list_cron_jobs")) {
	$job = $j if ($j->{'user'} eq 'root' && $j->{'command'} eq $cron_cmd);
	}
if ($job) {
	&lock_file(&cron::cron_file($job));
	&foreign_call("cron", "delete_cron_job", $job);
	&unlock_file(&cron::cron_file($job));
	unlink($cron_cmd);
	}
if ($config{'sched_mode'}) {
	# Create the program that cron calls
	&cron::create_wrapper($cron_cmd, $module_name, "monitor.pl");

	# Setup the actual cron job
	local $njob;
	$njob = { 'user' => 'root', 'active' => 1,
		  'hours' => '*', 'days' => '*',
		  'months' => '*', 'weekdays' => '*',
		  'command' => $cron_cmd };
	if ($config{'sched_period'} == 0) {
		$njob->{'mins'} = &make_interval(60);
		}
	elsif ($config{'sched_period'} == 1) {
		$njob->{'hours'} = &make_interval(24);
		$njob->{'mins'} = 0;
		}
	elsif ($config{'sched_period'} == 2) {
		$njob->{'days'} = &make_interval(31, 1);
		$njob->{'hours'} = $njob->{'mins'} = 0;
		}
	elsif ($config{'sched_period'} == 3) {
		$njob->{'months'} = &make_interval(12, 1);
		$njob->{'days'} = 1;
		$njob->{'hours'} = $njob->{'mins'} = 0;
		}
	&lock_file(&cron::cron_file($njob));
	&foreign_call("cron", "create_cron_job", $njob);
	&unlock_file(&cron::cron_file($njob));
	}
&unlock_file($cron_cmd);
}

# make_interval(length, offset2)
sub make_interval
{
local (@rv, $i);
for($i=$config{'sched_offset'}+$_[1]; $i<$_[0]; $i+=$config{'sched_int'}) {
	push(@rv,$i);
	}
return join(",", @rv);
}

# expand_oldstatus(oldstatus, &serv)
# Converts an old-status string like *=1 foo.com=2 into a hash. If the string
# contains just one number, it is assumed to be for just the first remote host
sub expand_oldstatus
{
local ($o, $serv) = @_;
local @remotes = split(/\s+/, $serv->{'remote'});
if ($o =~ /^\-?(\d+)$/) {
	return { $remotes[0] => $o };
	}
else {
	local %rv;
	foreach my $hs (split(/\s+/, $o)) {
		local ($h, $s) = split(/=/, $hs);
		$rv{$h} = $s;
		}
	return \%rv;
	}
}

# nice_remotes(&monitor, [max])
sub nice_remotes
{
local ($s, $max) = @_;
$max ||= 3;
local @remotes = map { $_ eq "*" ? $text{'index_local'}
			         : &html_escape($_) }
		     split(/\s+/, $s->{'remote'});
return @remotes > $max ? join(", ", @remotes[0..$max]).", ..."
		       : join(", ", @remotes);
}

1;

