package Monitor::qmqueue;

require 5.002;

#use strict;
use Socket qw( SOCK_STREAM PF_UNIX sockaddr_un );
use FileHandle;

@Monitor::qmqueue::ISA = qw(Monitor::Monitor);

# Pieces of this code were taken from the qmHandle program, which reads the queue directories and reports the amount 
# of mail in the local and the remote queue's.
#
# qmHandle 0.4.1
#
# Copyright(c) 1998 Michele Beltrame <mick@io.com>
#
# You can freely use, modify and redistribute for free this program, as long
# as this notice remains intact. If you plan to resell this program, you have
# to contact me before doing that.
#
# Other than that, the rest of the module was written by Richard Ward, Cape Technikon, the start of our contribution
# back to the open source community.


# create the objects + memory space needed
sub new
{

    my ($this, $method, $queue, $socket) = @_;
    my  $class = ref($this) || $this;
    my  $self = {};

    bless($self, $class);

    # create an array to store the qmail queue limits info
    $self->{"htable"} = [];

    $queue ||= "/var/qmail/queue/";
    ($queue.="/") unless( $queue =~ /\/$/ );
    $self->{"queue"} = $queue;

    $socket ||= "/tmp/.qmail-qstat";
    $self->{"socket"} = $socket;

    $method ||= "direct";
    if (!($method =~ /^(direct|socket)$/)) {
	$method = "direct";
    }
    $self->{"method"} = $method;

    (print "QMQUEUE: directory $queue\n") if ($BigSister::common::dl>3);
    return($self);
}


sub add_check 
{
    my( $self, $item, $warnlevl, $paniclevl, $warnlevr, 
	$paniclevr, $warnlevt, $paniclevt ) = @_;

    my( $htable ) = $self->{"htable"};

    # figure out how to report this data ( server name + monitor name )
    ($item = $uxmon::hostname.".mail") unless( $item );

    # Do this here, since untimately it's our responsibility,
    # not the config script's...
    ($warnlevl = 2000) unless ($warnlevl);
    ($warnlevr = $warnlevl) unless ($warnlevr);
    ($warnlevt = $warnlevl) unless ($warnlevt);

    ($paniclevl = 3500) unless ($paniclevl);
    ($paniclevr = $paniclevl) unless ($paniclevr);
    ($paniclevt = $paniclevl) unless ($paniclevt);

    # report on what's going on - if we're in debug mode
    if( $BigSister::common::dl>3 )
    {
        print "add_check - item: '$item'\n";
	print "levels local($warnlevl,$paniclevl) ",
	      "remote($warnlevr,$paniclevr) ",
	      "todo($warnlevt,$paniclevt)\n";
    }

    # add this info to the configuration array
    push( @$htable, [ $item, $warnlevl, $paniclevl, $warnlevr,
	 $paniclevr, $warnlevt, $paniclevt ] );
}

# Local method: direct
# Checks the qmail queue directories directly
sub direct_check
{
    my ($queue) = @_;

    # report on what's going on - if we're in debug mode
    if( $BigSister::common::dl>3 )
    {
         print "QMAIL: getting data via direct method\n";
    }

    # Store results here
    my %res;
    my $ret;

    # Make list of messages in the queues
    foreach my $i ("local","remote","todo") {
	$ret = find_files("${queue}$i");
	if ($ret) {
	    $res{"$i"} = shift @{$ret};
	    $res{"${i}files"} = $ret;
	} else {
	    # Error occured!
	    $res{"$i"} = -1;
	    $res{"${i}error"} = $!;
	}
	# Report what's going on
	if ($BigSister::common::dl>3) {
	    print "$i queue: '".$res{"$i"}."'\n";
	    if (defined $res{"${i}error"}) {
		print "$i check error: '".$res{"${i}error"}."'\n";
	    }
 	}
    }
    return \%res;
}

# Traverses the given directory 2 levels deep and counts the files
# Returns: undef for error, otherwise an array including the count of 
# files followed by the file names
# (based on code from Franky Van Liedekerke)
sub find_files
{
    my ($dir) = @_;
    my (@dirlist, @filelist, $cnt);
    opendir(DIR,"$dir") || return undef;
    @dirlist = grep !/\./, readdir DIR;
    closedir DIR;
    foreach my $subdir (@dirlist) {
      opendir (SUBDIR,"$dir/$subdir") || return [ undef, $! ];
      my (@files) = grep !/\./, map "$subdir/$_", readdir SUBDIR;
      push @filelist, @files;
      $cnt += $#files+1;
      closedir SUBDIR;
    }

    return [ $cnt, @filelist ];
}

# Local method: socket
# Read UNIX socket linked to qmail-qstat
sub socket_check
{
    my ($socket) = @_;

    # report on what's going on - if we're in debug mode
    if( $BigSister::common::dl>3 )
    {
         print "QMAIL: getting data via socket method\n";
    }

    # Store results here
    my %res;
    my $fh = FileHandle->new();

    unless (socket($fh, &PF_UNIX(), &SOCK_STREAM(), 0)) {
	my $err = "Socket error: $!";
	%res = ( "remote" => -1, "local" => -1, "todo" => -1,
		 "remoteerror" => $err,
		 "localerror" => $err,
		 "todoerror" => $err );
    }
    $saddr = sockaddr_un($socket);
    my $err = "Abnormal Connection Termination";
    %res = ( "remote" => -1, "local" => -1, "todo" => -1,
	     "remoteerror" => $err,
	     "localerror" => $err,
	     "todoerror" => $err );

    my $timeout = 60;
    my $finish_time = time + $timeout;
    eval {
	$SIG{'ALRM'} = sub { die };
	Platform::bsalarm($timeout); 

	die unless Platform::bsconnect($fh, $saddr);
	my ($len,$data) = (1,"");
	$timeout = $finish_time - time(); 
	$msg = "";
	$rbits = "";
	vec($rbits, $fh->fileno(), 1) = 1;

	while( ($timeout > 0) && ($len>0) && (length($msg)<4096) ) {
	    $rbits = "";
	    vec($rbits, $fh->fileno(), 1) = 1;
	    $nfound = select($rbits, undef, undef, $timeout); 
	    die unless( defined $nfound );
	    if( $nfound ) {
		$len = read( $fh, $data, 1024);
		$timeout = $finish_time - time(); 
		(print "Qmqueue data: $data\n") if ($BigSister::common::dl>5);
		$msg .= $data;
		if( $msg =~ /^messages in queue: (\d+)$/sm ) {
		    $res{"remote"} = $res{"local"} = $1;
		    delete $res{"remoteerror"};
		    delete $res{"localerror"};
		    $msg =~ s/^messages in queue: \d+$//sm;
		}
		if( $msg =~ /^messages in remote queue: (\d+)$/sm ) {
		    $res{"remote"} = $1;
		    delete $res{"remoteerror"};
		    $msg =~ s/^messages in remote queue: \d+$//sm;
		}
		if( $msg =~ /^messages in local queue: (\d+)$/sm ) {
		    $res{"local"} = $1;
		    delete $res{"localerror"};
		    $msg =~ s/^messages in local queue: \d+$//sm;
		}
		if( $msg =~ /^messages in queue but not yet preprocessed: (\d+)$/sm ) {
		    $res{"todo"} = $1;
		    delete $res{"todoerror"};
		    $msg =~ s/^messages in queue but not yet preprocessed: \d+//sm;
		}
	    }
	}
    };

    return \%res;
}


sub check
{
    my ($self) = @_;

    my( $htable ) = $self->{"htable"};

    # report on what's going on - if we're in debug mode
    if( $BigSister::common::dl>3 )
    {
         print "QMAIL: getting data via qmHandle functions\n";
    }

    my $ret;
    if ($self->{"method"} eq "direct") {
	$ret = direct_check($self->{"queue"});
    } elsif ($self->{"method"} eq "socket") {
	$ret = socket_check($self->{"socket"});
    } else {
	$ret = { "remote" => -1, "local" => -1, "todo" => -1,
		 "remoteerror" => "unknown method ".$self->{"method"},
		 "localerror" => "unknown method ".$self->{"method"},
		 "todoerror" => "unknown method ".$self->{"method"} };
    }

    my ($l,$r,$t) = ($ret->{"local"}, $ret->{"remote"}, $ret->{"todo"});
    my ($lerr,$rerr,$terr) = ($ret->{"localerror"}||'', 
	$ret->{"remoteerror"}||'', $ret->{"todoerror"}||'');

    $self->set_variable( "", "qmail.remotequeue", $r );
    $self->set_variable( "", "qmail.localqueue", $l );
    $self->set_variable( "", "qmail.todoqueue", $t );

    # loop throught every item to be checked (probably only one per box)
    for( $i=0; $i<=$#$htable; $i++ ) 
    {
	# item 0, warnlevl 1, paniclevl 2, warnlevr 3, paniclevr 4,
	# warnlevt 5, paniclevt 6
	my ($item, $warnlevl, $paniclevl, $warnlevr, $paniclevr,
	    $warnlevt, $paniclevt) = @{$htable->[$i]};

        # report on what's going on - if we're in debug mode
        if( $BigSister::common::dl>3 )
        {
            print "working on '$i' : item: '$item' localwarn:'$warnlevl' localpanic:'$paniclevl' remotewarn:'$warnlevr' remotepanic:'$paniclevr' todowarn:'$warnlevt' todopanic:'$paniclevt'\n";
        }

        #  check for errors
        if (! length($r) )
        {
            # report to main monitoring server the status
            $self->report( $item, $BigSister::common::status_codes{"yellow"}, 
		"Qmail queue size - no data from qmHandle" );
               
            # report on what's going on - if we're in debug mode
            if( $BigSister::common::dl>3 )
            {
                print "Qmail queue error! (couldn't get data from qmHandle)\n";
            }
        }
        else
        {
            # see how to report on message queues - red, yellow or GREEN
	    if( $l < 0) {
		$self->report( $item, $BigSister::common::status_codes{"red"},
			"&red Local Queue check error, $lerr");
	    }
            elsif( (length($paniclevl) && $l>$paniclevl) ) {
                $self->report( $item, $BigSister::common::status_codes{"red"}, 
			"&red Local Queue size $l exceeds PANIC threshold($paniclevl)");
            }
            elsif( (length($warnlevl) && $l>$warnlevl) ) {
                $self->report( $item, $BigSister::common::status_codes{"yellow"}, 
			"&yellow Local Queue size $l exceeds WARNING threshold($warnlevl)");
            }
            else {
		$self->report( $item, $BigSister::common::status_codes{"green"},
			"&green Local Queue size $l OK" );
            }

	    if( $r < 0) {
		$self->report( $item, $BigSister::common::status_codes{"red"},
			"&red Remote Queue check error, $rerr");
	    }
            elsif( (length($paniclevr) && $r>$paniclevr) ) {
                $self->report( $item, $BigSister::common::status_codes{"red"}, 
			"&red Remote Queue size $r exceeds PANIC threshold($paniclevr)");
            }
            elsif( (length($warnlevr) && $r>$warnlevr) ) {
                $self->report( $item, $BigSister::common::status_codes{"yellow"}, 
			"&yellow Remote Queue size $r exceeds WARNING threshold($warnlevr)");
            }
            else {
		$self->report( $item, $BigSister::common::status_codes{"green"},
			"&green Remote Queue size $r OK" );
            }

	    if( $t < 0) {
		$self->report( $item, $BigSister::common::status_codes{"red"},
			"&red Todo Queue check error, $terr");
	    }
            elsif( (length($paniclevt) && $t>$paniclevt) ) {
                $self->report( $item, $BigSister::common::status_codes{"red"}, 
			"&red Todo Queue size $t exceeds PANIC threshold($paniclevt)");
            }
            elsif( (length($warnlevt) && $t>$warnlevt) ) {
                $self->report( $item, $BigSister::common::status_codes{"yellow"}, 
			"&yellow Todo Queue size $t exceeds WARNING threshold($warnlevt)");
            }
            else {
		$self->report( $item, $BigSister::common::status_codes{"green"},
			"&green Todo Queue size $t OK" );
            }
            
            # report on what's going on - if we're in debug mode
            if( $BigSister::common::dl>3 )
            {
                print "Made it through qmqueue.pm::check just fine - ";
                print "qmqueue worked!\n";
            }
        }
#	$self->report( $item, 100, $loads ) if ($loads);
	print "Queue result: $item, 100, $loads\n" if ($BigSister::common::dl && $loads);
    }

}

1;
