#    Big Sister network monitor
#    Copyright (C) 1998  Thomas Aeby
#
#    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.
#

package BigSister::common;

# no warnings qw( uninitialized once );

use Platforms;
use strict;
use Getopt::Long;
use Exporter;

@BigSister::common::ISA = qw(Exporter);
@BigSister::common::EXPORT = qw(&proginit &background);


END {
    if( $$ == $BigSister::common::mainpid ) {
	my $pidfile = $BigSister::common::prog;
	$pidfile =~ s/\.exe$//i;
	$pidfile = "$BigSister::common::fs{'run'}/$pidfile.pid";
	if( -f $pidfile ) {
	    unlink( $pidfile );
  	    &log( "notice", "terminating" );
	}
    }
}


sub proginit {
    $BigSister::common::domain_encode = ".";
    %BigSister::common::status_codes = (
	"red" => 0,
	"yellow" => 1,
	"purple" => 2,
	"green" => 3,
	"clear" => 4,
	"blue" => 5,
	"white" => 6
    );

    %BigSister::common::status_texts = (
	0 => "red",
	1 => "yellow",
	2 => "purple",
	3 => "green",
	4 => "clear",
	5 => "blue",
	6 => "white"
    );

    if( ($ENV{"LANG"} =~ /utf-8/i) && ($] <= 5.008) && ($] > 5.006) ) {
        print STDERR "WARNING: your system is set to UTF-8 and your perl version might not support this - this might result in malfunction\n";
    }
    $BigSister::common::log_facility = (Platforms::iswin32)?"Srv":"daemon";
    $BigSister::common::progdir = $0;
    $BigSister::common::prog = $0;
    $BigSister::common::prog =~ s#.*[/\\]##;
    $BigSister::common::progdir =~ s#/[^\/\\]*$##;
    my $bs_instdir="/usr/share/bigsister";
    eval {
	# try to set our name so that even BSD systems will list our effective
	# name in the process list
        $0 = $BigSister::common::prog;
    };

    if( Platforms::isunix() ) {
	eval {
	    require Sys::Syslog;
	    $BigSister::common::syslog = 1;
	};
    }
    if( Platforms::iswin32() ) {
	eval {
	    require Win32::EventLog;
	    $BigSister::common::eventlog = 1;
	};
    }
     
    my $chroot = $ENV{"BIGSISTER_CHROOT"};

    $BigSister::common::root = Platform::override_root();
    $BigSister::common::root = $bs_instdir unless( $BigSister::common::root );
    if( $chroot ) { $BigSister::common::root = $chroot.$BigSister::common::root; }

    $Getopt::Long::ignorecase = 0;  
    GetOptions( "D:i", "b:s", "L:s", @BigSister::common::options ) || &usage;
    $BigSister::common::dl = $BigSister::common::opt_D;
    if( $BigSister::common::opt_b ) {
	$BigSister::common::root = $BigSister::common::opt_b;
    }
    load_resources();
    if( $BigSister::common::resources{"log_facility"} ) {
	$BigSister::common::log_facility = $BigSister::common::resources{"log_facility"};
    }
    if( $BigSister::common::opt_L ) {
	$BigSister::common::log_facility = $BigSister::common::opt_L;
    }

    if( $BigSister::common::resources{"path"} ) {
	$BigSister::common::resources{"path"} =~ /^(.*)$/;

	Platform::mergepath( $1 );
    }

    %BigSister::common::fs = (
        "root" => "$BigSister::common::root",
        "uxmon" => "$BigSister::common::root/uxmon",
        "bin" => "$BigSister::common::root/bin",
        "var" => "$BigSister::common::root/var",
        "etc" => "$BigSister::common::root/etc",
        "adm" => "$BigSister::common::root/adm",
        "web" => "$BigSister::common::root/www",
	"run" => "$BigSister::common::root/adm"
    );
    foreach my $tmp ( $ENV{"TEMP"}, $ENV{"TMPDIR"}, "/tmp", "/temp", "/var/tmp" ) {
        if( -d $tmp ) {
	    $BigSister::common::fs{"tmp"} = $tmp;
	    last;
	}
    }
    foreach my $fs (keys %BigSister::common::fs) {
	next unless( defined $BigSister::common::resources{"fs_$fs"} );
        my $dir = $BigSister::common::resources{"fs_$fs"};
	($dir = $BigSister::common::fs{"root"}."/".$dir) unless( $dir =~ /^\// );
	if( defined $chroot )  {
	    $dir = $chroot.$dir;
        }
	$BigSister::common::fs{$fs} = $dir;
    }
    $BigSister::common::root = $BigSister::common::fs{"root"};
    $BigSister::common::fs{"tmp"} = "/tmp" unless( $BigSister::common::fs{"tmp"} );
    $BigSister::common::fs{"www"} = $BigSister::common::fs{"web"};

    unless( -d $BigSister::common::fs{"tmp"} ) { 
	mkdir( $BigSister::common::fs{"tmp"}, 0700 ) ||
	  BigSister::common::log( "err", "temp directory is missing - some operations may fail" ); 
    }

    unshift( @INC, $BigSister::common::root );
    if( $BigSister::common::log_facility && ! $BigSister::common::syslog && ! $BigSister::common::eventlog ) {
	print STDERR "$BigSister::common::prog: WARNING: syslog not available - falling back to logger\n" if( $BigSister::common::dl );
    }

    if( $BigSister::common::syslog ) {
	# we don't want to see setlogsock() errors
	open( SAVEERR, ">&STDERR" );
	open( STDERR, ">/dev/null" );
	eval {
	    $BigSister::common::syslog = 0;
	    Sys::Syslog::setlogsock( [ 'stream', 'unix', 'inet' ] );
	    Sys::Syslog::openlog( $BigSister::common::prog, "", $BigSister::common::log_facility );
	    $BigSister::common::syslog = 1;
	};
	open( STDERR, ">&SAVEERR" );
	close SAVEERR;
    }
    $BigSister::common::eventlog = Win32::EventLog->new( $BigSister::common::log_facility ) if( $BigSister::common::eventlog );
    &log( "debug", "starting up" );
    $BigSister::common::hostname = Platform::hostname();

    $BigSister::common::mainpid = $$;
}



sub usage {
    print "Usage: ".($BigSister::common::prog)." ".($BigSister::common::Usage)."\n";
    exit 1;
}


sub background {
    &log( "info", "entering daemon mode" );
    if( Platforms::isunix() ) {
	chdir "/";
	close STDIN; close STDOUT; close STDERR;
	if (open(DEVTTY, "/dev/tty")) {
	    ioctl(DEVTTY,0x20007471,0);         # XXX s/b &TIOCNOTTY
	    close DEVTTY;
	}
	open(STDIN,"</dev/null");
	open(STDOUT,">/dev/null");
	open(STDERR,">&STDOUT");
	if( fork ) {
	    $BigSister::common::mainpid = -1;
	    exit;
	}
	$SIG{"HUP"} = $SIG{"ALRM"} = $SIG{"INT"} = $SIG{"PIPE"} = "IGNORE";
	$SIG{"TERM"} = $SIG{"QUIT"} = sub { print STDERR "exitting on signal\n"; exit(1); };
	setpriority( "PRIO_PROCESS", 0, 10 );
    }
    $BigSister::common::mainpid = $$;
    my $pidfile = $BigSister::common::prog;
    $pidfile =~ s/\.exe$//i;
    open( PID, ">$BigSister::common::fs{'run'}/$pidfile.pid" );
    print PID "$$\n";
    close PID;
}


sub load_resources {
    my $file;
    %BigSister::common::resources = ();
    foreach $file( "/etc/resources", "/adm/resources" ) {
	if( -f $BigSister::common::root.$file ) {
	    open( RES, "<$BigSister::common::root$file" );
	    while( <RES> ) {
		chomp;
		if( /^($BigSister::common::prog|\*)\.([_a-zA-Z0-9]+)[\s\t]*=[\s\t]*(.*)/ ) {
		    $BigSister::common::resources{$2} = $3;
		}
	    }
	    close RES;
	}
    }
}


sub log {
    my( $severity, @messages ) = @_;
    my( $message, $level );

    if( $severity eq "warn" ) { $severity = "warning"; }
    foreach $message (@messages) {
	$level = 3;
	($severity =~ /(alert|err|crit)/) && ($level = 1);
	($severity =~ /(warning|notice)/) && ($level = 2);
	($severity =~ /(info)/) && ($level = 3);
	if( ($severity =~ /(alert|err|crit|warning|notice)/)
	    || ( ($BigSister::common::dl>2) && ($severity =~ /(info)/) )
	    || ($BigSister::common::dl>3) ) {
	    print STDERR (scalar localtime).": ".$BigSister::common::prog.": ".$message."\n";
	}
	next unless( $BigSister::common::log_facility );
	if( $BigSister::common::syslog ) {
	    eval {
		$BigSister::common::syslog = 0;
		Sys::Syslog::syslog( $severity, "\%s", $message );
		$BigSister::common::syslog = 1;
	    };

	}
	elsif( $BigSister::common::eventlog ) {
	    my %event = ('EventID' => 100, 
		'EventType' => $level, 
		'Strings' => $BigSister::common::prog.": ".$message, 
		"Source" => "Perl" );

	    $BigSister::common::eventlog->Report( \%event );
	}
	unless( $BigSister::common::eventlog || $BigSister::common::syslog ) {
	    $severity =~ s/\|.*//;
	    system( "logger", "-p", $BigSister::common::log_facility.".".$severity, "-t", $BigSister::common::prog, $message );
	}
    }
}

	    
sub parse {
    my( $str, $specialchars ) = @_;
    my @res = ();
    my $buff = "";

    $specialchars = "\\s" unless( $specialchars );
    $str =~ s/^[\s\t\r\n]+//;
    while( $str =~ /^([^\s\t\r\n\"\'$specialchars]*)(.)/s ) {
	my( $arg, $sep ) = ($1,$2);
	$buff .= $arg;
	$str = $';

	if( $sep =~ /[\s\t\r\n]/ ) {
	    push( @res, $buff );
	    $buff = "";
	    $str =~ s/^[\s\t\r\n]+//s;
	}
	elsif( $sep =~ /[$specialchars]/ ) {
	    push( @res, $buff ) if( $buff );
	    push( @res, $sep );
	    $buff = "";
	    $str =~ s/^[\s\t\r\n]+//s;
	}
	elsif( $sep =~ /[\"\']/ ) {
	    if( $str =~ /^(.*?)\Q$sep\E/ ) {
		my $rest = $1;
		$str = $';
		if( $sep eq '"' ) {
		    my( $prefix );
		    while( $rest =~ /\\(.)/ ) {
			my $next = $1;
			$rest = $';
			$prefix .= $`;
			if( $next eq "n" ) {
			    $next = "\n";
			}
			elsif( $next eq "r" ) {
			    $next = "\r";
			}
			$prefix .= $next;
		    }
		    $rest = $prefix.$rest;
		}
		$buff .= $rest;
	    }
	    else {
		$buff .= $str;
		$str = "";
	    }
	    if( $str =~ /^[\s\t]+/ ) {
		$str = $';
		push( @res, $buff );
		$buff = "";
	    }
	}
	else {
	    push( @res, $buff.$sep );
	    $buff = "";
	    $str =~ s/^[\s\t\r\n]+//s;
	}
    }
    return @res;
}


sub separate_service {
    my( $item ) = @_;

    if( $item =~ /^(.*)\.(.*?)$/ ) {
	return( $1, $2 );
    }
    return( $item );
}


sub find_file {
    my( $file, @path ) = @_;

    unless( @path ) {
        @path = ( "adm", "etc", "root" );
    }
    return( myglob( $file ) ) if( $file =~ /^(\/|[a-zA-Z]:)/ );
    foreach my $fs ( @path ) {
	next unless( defined $BigSister::common::fs{$fs} );
        my $dir = $BigSister::common::fs{$fs};
	my @files = myglob( "$dir/$file" );
	return( wantarray() ? @files : $files[0] ) if( -f $files[0] );
    }
    if( $file =~ /(.*?)\// && defined $BigSister::common::fs{$1} ) {
	return myglob( "$BigSister::common::fs{$1}/$'" );
    }
    return myglob( $BigSister::common::root."/".$file );
}


sub myglob {
    my( $file ) = @_;

    my $dir;
    if( $file =~ /^(.*)\/(.*?)$/ ) {
	( $dir, $file ) = ($1,$2);
    }
    else {
        $dir = ".";
    }
    return( "$dir/$file" ) unless( ($file =~ /\*/) && opendir( _DIR, $dir ) );
    my $expr = $file;
    $expr =~ s/\*/.*/g;
    my @files = ();
    foreach my $match (grep( /^$expr$/, readdir( _DIR ))) {
        next if( $match eq "." || $match eq ".." );
	push( @files, "$dir/$match" ) if( -f "$dir/$match" );
    }
    closedir( _DIR );
    return( "$dir/$file" ) unless( @files );
    return( wantarray() ? @files : $files[0] );
}

1;
