#! /usr/bin/perl
# $Id$

# Copyright (C) 2001-2002 MandrakeSoft
# Yves Duret <yduret at mandrakesoft.com>
# some code is Copyright: (C) 1999, Michael T. Babcock <mikebabcock@pobox.com>
#
# 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# TODO: consider switching from TreeView to gtkhtml

use strict;
use lib qw(/usr/lib/libDrakX);
use standalone;     #- warning, standalone must be loaded very first, for 'explanations'
use common;
use interactive;
use ugtk2 qw(:wrappers :helpers :create);

$::isInstall and die "Not supported during install.\n";

my $in = 'interactive'->vnew('su', 'default');
my $cron_hourly = "/etc/cron.hourly/logdrake_service";

#- parse arguments list.
foreach (@ARGV) {
    /^--explain=(.*)$/ and do { $::isExplain = ($::Explain) = $1; $::isFile = 1; $::File = "/var/log/explanations"; next };
    /^--file=(.*)$/ and do { $::isFile = ($::File) = $1; next };
    /^--word=(.*)$/ and do { $::isWord = ($::Word) = $1; next };
    /^--alert$/ and do { alert_config(); quit() };
}

$::isTail = 1 if $::isFile;
$| = 1 if $::isTail;
my $h = chomp_(`hostname -s`);

my $my_win = ugtk2->new('logdrake');
unless ($::isEmbedded) {
    $my_win->{rwindow}->set_title(N("logdrake"));
    $my_win->{window}->set_border_width(5);
    #$my_win->{rwindow}->set_policy(1, 1, 1);
    #$my_win->{window}->set_default_size(540,460);
}
$my_win->{window}->signal_connect(delete_event => \&quit);

my $cal = gtkset_sensitive(new Gtk2::Calendar(), 0);
my $mday = (localtime(time()))[3];
$cal->select_day($mday);
my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my $cal_mode = 0;
my $cal_butt = gtksignal_connect(new Gtk2::CheckButton(N("Show only for the selected day")), clicked => sub { $cal_mode = !$cal_mode; gtkset_sensitive($cal,$cal_mode) });

### menus definition
# the menus are not shown
# but they provides shiny shortcut like C-q
my @menu_items = (
		  { path => N("/_File"), type => '<Branch>' },
		  { path => N("/File/_New"), accelerator => N("<control>N") },
		  { path => N("/File/_Open"), accelerator => N("<control>O") },
		  { path => N("/File/_Save"), accelerator => N("<control>S"), callback => \&save },
		  { path => N("/File/Save _As") },
		  { path => N("/File/-"),type => '<Separator>' },
		  { path => N("/File/_Quit"), accelerator => N("<control>Q"), callback => \&quit },
		  { path => N("/_Options"), type => '<Branch>' },
		  { path => N("/Options/Test") },
		  { path => N("/_Help"),type => '<LastBranch>' },
		  { path => N("/Help/_About...") } 
		 );
my $menubar = create_factory_menu($my_win->{rwindow}, @menu_items) unless $::isEmbedded;
######### menus end


########## font and colors
my %n = ('font' => ''); # Gtk2::Pango::FontDescription->from_string('Serif 12');#Gtk2::Gdk::Font->fontset_load(N("-misc-fixed-medium-r-*-*-*-100-*-*-*-*-*-*,*"));
my %b = ('font' => 'Bold'); #Gtk2::Pango::FontDescription->from_string('Serif Bold 12');#Gtk2::Gdk::Font->fontset_load(N("-misc-fixed-bold-r-*-*-*-100-*-*-*-*-*-*,*"));

#$black    = "\033[30m";
#$red      = "\033[31m";
#$green    = "\033[32m";
#$yellow   = "\033[33m";
#$blue     = "\033[34m";
#$magenta  = "\033[35m";
#$purple   = "\033[35m";
#$cyan     = "\033[36m";
#$white    = "\033[37m";
#$darkgray = "\033[30m";
#$col_norm =             "\033[00m";
#$col_background =       "\033[07m";
#$col_brighten =         "\033[01m";
#$col_underline =        "\033[04m";
#$col_blink =            "\033[05m";

my $white    = gtkcolor(50400, 655, 20000);
my $black    = gtkcolor(0, 0, 0);
my $red      = gtkcolor(0xFFFF, 655, 655);
my $green    = gtkcolor(0x0, 0x9898,0x0);
my $yellow   = gtkcolor(0xFFFF, 0xD7D7, 0);
my $blue     = gtkcolor(655, 655, 0xFFFF);
my $magenta  = gtkcolor(0xFFFF, 655, 0xFFFF);
my $purple   = gtkcolor(0xA0A0, 0x2020, 0xF0F0);
my $cyan     = gtkcolor(0x0, 0x9898, 0x9898);
my $darkgray = gtkcolor(0x2F2F, 0x4F4F, 0x4F4F);
    
# Define global terms:
# Define good notables:
my @word_good = ("starting\n", "Freeing", "Detected", "starting.", "accepted.\n", "authenticated.\n", "Ready", "active", "reloading", "saved;", "restarting", "ONLINE\n");
my @word_warn = ("dangling", "closed.\n", "Assuming", "root", "root\n", "exiting\n", "missing", "Ignored", "adminalert:", "deleting", "OFFLINE\n");
my @word_bad = ("bad");
my @word_note = ("LOGIN", "DHCP_OFFER", "optimized", "reset:", "unloaded", "disconnected", "connect", "Successful", "registered\n");
my @line_good = ("up", "DHCP_ACK", "Cleaned", "Initializing", "Starting", "success", "successfully", "alive", "found", "ONLINE\n");
my @line_warn = ("warning:", "WARNING:", "invalid", "obsolete", "bad", "Password", "detected", "timeout", "timeout:", "attackalert:", "wrong", "Lame", "FAILED", "failing", "unknown", "obsolete", "stopped.\n", "terminating.", "disabled\n", "disabled", "Lost");
my @line_bad = ("DENY", "lost", "shutting", "dead", "DHCP_NAK", "failure;", "Unable", "inactive", "terminating", "refused", "rejected", "down", "OFFLINE\n", "error\n", "ERROR\n", "ERROR:", "error", "ERROR", "error:", "failed:");

# Define specifics:
my @daemons = ("named");

# Now define what we want to use when:
my $col_good = 'green4';
my $col_warn = 'yellow4';
my $col_bad = 'red';
my $col_note = 'purple';
my $col = 'darkcyan';

######### font and colors end

my %files = (
	     "auth" => { file => "/var/log/auth.log", desc => N("Authentication") },
	     "user" => { file => "/var/log/user.log", desc => N("User") },
	     "messages" => { file => "/var/log/messages", desc => N("Messages") },
	     "syslog" => { file => "/var/log/syslog", desc => N("Syslog") },
	     "explanations" => { file => "/var/log/explanations", desc => N("Mandrake Tools Explanation") }
);

my $yy = gtkset_sensitive(gtksignal_connect(new Gtk2::Button(N("search")) , clicked => \&search),0);
my $log_text = new Gtk2::TextView;
$log_text->set_property('editable', 0);

my $log_buf = $log_text->get_buffer();
my $refcount_search;
#### far from window

my %toggle;

gtkadd($my_win->{window},
       gtkpack_(new Gtk2::VBox(0,0),
		if_(!$::isExplain && !$::isEmbedded, 0, N("A tool to monitor your logs")),
		if_(!$::isFile, 0, gtkadd(new Gtk2::Frame(N("Settings")),
					  gtkpack__(new Gtk2::VBox(0,2),
						    gtkpack__(new Gtk2::VBox(0,2),
							      # N("Show lines"),
							      gtkpack__(new Gtk2::HBox(0,0),
									" " . N("matching") . " ", my $e_yes = new Gtk2::Entry(),
									" " . N("but not matching") . " ", my $e_no = new Gtk2::Entry()
									)
							      ),
						    gtkpack_(new Gtk2::HBox(0,0),
							     1, gtkadd(gtkset_border_width(new Gtk2::Frame(N("Choose file")),2),
								       gtkpack(gtkset_border_width(Gtk2::VBox->new(0,0),0),
										map { $toggle{$_} = gtksignal_connect(new Gtk2::CheckButton($files{$_}{desc}), 
														      clicked => sub {
															  $refcount_search++; 
															  gtkset_sensitive($yy,$refcount_search);
														      }) } keys %files,
										)
								       ),
							     0, gtkadd(gtkset_border_width(new Gtk2::Frame(N("Calendar")),2),
								       gtkpack__(gtkset_border_width(new Gtk2::VBox(0,0),5),
										 $cal_butt, $cal
										 )
								       )
							     ),
						    $yy,
						    )
					  )
		    ),
		!$::isExplain ? (1, gtkadd(new Gtk2::Frame(N("Content of the file")),
					   create_scrolled_window($log_text)
					   )) : (1, create_scrolled_window($log_text)),
		if_(!$::isExplain, 0, gtkadd(gtkset_border_width(gtkset_layout(Gtk2::HButtonBox->new, 'end'), 5),
					      if_(!$::isFile, gtksignal_connect(new Gtk2::Button(N("Mail alert")), 
										clicked => sub { 
                                                      eval { alert_config() };
                                                      if ($@ =~ /wizcancel/) {
                                                          $::Wizard_no_previous = 1;
                                                          $::Wizard_no_cancel = 1;
                                                          $::WizardWindow->destroy if defined $::WizardWindow;
                                                          undef $::WizardWindow;
                                                      } else { print "CRITICAL: \"$@\"\n" }

                                                  })),
					      gtksignal_connect(new Gtk2::Button(N("Save")), clicked => \&save),
					      gtksignal_connect(new Gtk2::Button($::isEmbedded ? N("Cancel") : N("Quit")), clicked => \&quit)
					      )
		    )
		)
       );

$::isFile && !$::isEmbedded and gtkset_size_request($log_text, 400, 500);

$my_win->{window}->realize;
$my_win->{window}->show_all();
search() if $::isFile;
$my_win->main;

sub quit { ugtk2->exit(0) }

#-------------------------------------------------------------
# search functions
#-------------------------------------------------------------
sub search {
#    gtk_text_buffer_delete();
#BUG    $log_text->backward_delete($log_text->get_length()); #BUG
#BUG    $log_text->freeze();
    if ($::isFile) {
	parse_file($::File);
    } else {
	foreach (keys %files) {
	    parse_file($files{$_}{file}) if $toggle{$_}->active;
	}
    }
#BUG    $log_text->thaw();
    $log_text->show();
    gtkflush();
}

local *F;

sub parse_file {
  my ($file) = @_;#$_[0];

  $file =~ s/\.gz$//;
  my ($pbar, $win_pb);
  unless ($::isEmbedded) {
      gtkadd($win_pb = gtkset_modal(new Gtk2::Window('toplevel'), 1),
             gtkpack(new Gtk2::VBox(2,0),
                     new Gtk2::Label(" " . N("please wait, parsing file: %s", $files{$_}{desc}) . " "),
                     $pbar = new Gtk2::ProgressBar()
                     )
             );
      $win_pb->set_transient_for($my_win->{rwindow});
      $win_pb->set_modal(1);
      $win_pb->set_position('center');
      $win_pb->realize();
      $win_pb->show_all();
  }
  my $ey = $e_yes->get_chars(0, -1);
  my $en = $e_no->get_chars(0, -1);
  $ey =~ s/ OR /\|/;
  $ey =~ s/^\*$//;
  $en =~ s/^\*$/.*/;
  $ey = $ey . $::Word if $::isWord;

  if ($cal_mode) {
      my (undef, $month, $day) = $cal->get_date();
      $ey = $months[$month]."\\s{1,2}$day\\s.*$ey.*\n";
  }

  my @all = catMaybeCompressed($file);

  if ($::isExplain) {
      my (@t, $t);
      while (@all) {
	  $t = pop @all;
	  next if $t =~ /logdrake/;
	  last if $t !~ /$::Explain/;
	  push @t, $t;
      }
      @all = reverse @t;
  }

  my $taille = @all;
  my $i = 0;
  foreach (@all) {
      $i++;
      if (!$::isEmbedded && $i % 10) { 
	  $pbar->set_fraction($i/$taille);
	  gtkflush();
      }
      
      if ($en eq "" and /$ey/i)     { logcolorize($_); next }
      if (!  /$en/i and /$ey/i)     { logcolorize($_); next }
      if (!  /$en/i and $ey eq "")  { logcolorize($_); next }
  }
  $win_pb->destroy() unless $::isEmbedded;

  if ($::isTail) {
      close F;
      open F, $file or die "E: $!";
      local $_;
      while (<F>) {}; #to prevent to output the file twice..
#      $log_text->set_point($log_text->get_length());
      my $timer = Gtk2->timeout_add(1000, \&input_callback);
  }     
}

sub input_callback  {
    logcolorize($_) while <F>;
    seek F, 0, 1;
}


##########################################################################################

sub logcolorize {

    # we get date & time if it is date & time (dmesg)
    s/(\D{3} .. (\d\d:\d\d:\d\d ))//;
    my $timestamp = $::isExplain ? $2 : $1;
    my @rec = split;

    log_output($timestamp,  { %b, 'foreground' => 'darkcyan' }); # date & time if any...
    # BUG: $col hasn't yet be reseted
    $::isExplain or log_output("$rec[0] ",  { %b, 'foreground' => $rec[0] eq $h ? 'blue' : $col });  # hostname
    
    if ($rec[1] eq "last") {
	log_output(" last message repeated ",  { %n, 'foreground' => 'green' });
	log_output($rec[4],  { %b, 'foreground' => 'green' });
	log_output(" times\n",  { %n, 'foreground' => 'green' });
	return;
    }
    # Extract PID if present
    if ($rec[1] =~ /\[(\d+)\]\:/) {
	my ($pid) = $1;
	$rec[1] =~ s/\[$1\]\://;
	log_output($rec[1] . "[",  { %n, 'foreground' => 'green' });
	log_output($pid,  { %b, 'foreground' => 'black' });
	log_output("]: ",  { %n, 'foreground' => 'green' });
    }
    else {
	log_output($rec[1] . " ",  { %n, 'foreground' => 'green' });
    }

    foreach my $therest (2 .. $#rec) {
	$col = 'darkcyan';

	# Check for keywords to highlight
	foreach (@word_good) { $col = $col_good if $_ eq $rec[$therest] }
	foreach (@word_warn) { $col = $col_warn if $_ eq $rec[$therest] }
	foreach (@word_bad)  { $col = $col_bad  if $_ eq $rec[$therest] }
	foreach (@word_note) { $col = $col_note if $_ eq $rec[$therest] }
	
 	# Watch for words that indicate entire lines should be highlighted
 	#foreach (@line_good) { $col = $col_good if $_ eq $rec[$therest] }
 	#foreach (@line_warn) { $col = $col_warn if $_ eq $rec[$therest] }
	#foreach (@line_bad)  { $col = $col_bad  if $_ eq $rec[$therest] }
 	
	log_output("$rec[$therest] ",  { %n, 'foreground' => $col });
    }
    log_output("\n",  { %n, 'foreground' => 'black' });
}


#    log_output (Gtk2::TextView, [ [ ... ] ])
sub log_output {
    gtktext_append($log_text, [ \@_ ]);
#    $log_buf->insert($buf->get_end_iter(), @_, -1);
}


#-------------------------------------------------------------
# mail/sms alert
#-------------------------------------------------------------

sub alert_config {
    
    $::isWizard = 1;
    $::Wizard_pix_up = "wiz_logdrake.png";    # FIXME
    $::Wizard_title = N("Mail alert");
    
    my $cron = q(#!/usr/bin/perl
# generated by logdrake
use MDK::Common;
my $r = "*** " . chomp_(`date`) . " ***\n";

);

my $initdir = "/etc/init.d";

    my ($load, $email, $smtp); 
    $load = 3;

  begin:
    $::Wizard_finished = 0;
    $::Wizard_no_previous = 1;
    $in->ask_okcancel(N("Mail alert configuration"),
                      N("Welcome to the mail configuration utility.\n\nHere, you'll be able to set up the alert system.\n"),
		      1) or quit();

  step_service:
    undef $::Wizard_no_previous;
    undef $::Wizard_finished;
    my $service = {
		  httpd => N("Apache World Wide Web Server"), 
		  bind => N("Domain Name Resolver"),
		  ftp => N("Ftp Server"),
		  postfix => N("Postfix Mail Server"),
		  samba => N("Samba Server"),
		  sshd => N("SSH Server"),
		  webmin => N("Webmin Service"),
		  xinetd => N("Xinetd Service")
		 };
    my @installed_d;
    foreach my $serv (keys %$service) {
	-e "$initdir/$serv" && push @installed_d, $serv;
    }
    my %services_to_check;
    $in->ask_from(N("service setting"),
		  N("You will receive an alert if one of the selected services is no longer running"),
		  [ map { { label => $_, val => \$services_to_check{$_}, type => "bool", text => $service->{$_} } } @installed_d
		  ]) or goto begin;

    $cron .= "#- check services\n";
    foreach (keys %services_to_check) {
     next unless $services_to_check{$_};
	$cron .= "\$r .= \"Service $_ ($service->{$_} is not running)\\n\" unless -e \"/var/lock/subsys/$_\";\n";
    }

  step_load:
    undef $::Wizard_finished;
    $in->ask_from(N("load setting"),
		  N("You will receive an alert if the load is higher than this value"),
		  [
		   { label => "load ", val => \$load, type => 'range', min => 1, max => 50 },
		  ]) or goto step_service;

    $cron .= sprintf(<<'EOF', $load);
#- load
my ($load) = split ' ', first(cat_("/proc/loadavg"));
$r .= "Load is huge: $load\n" if $load > %s;

EOF
    
  step_output:
#    $::Wizard_no_previous = 1;
    $::Wizard_finished = 1;
    $in->ask_from(N("alert configuration"),
		  N("Please enter your email address below "),
		  [
		   { label => "" },
		   { label => "Email", val => \$email },
		  ]) or goto step_load;

    $cron .= q(#- report it

my $email = ) . "'$email';\n\n";

	$cron .= q(local *F;
open F, '|/usr/sbin/sendmail -oi -t';
print F q(Subject: logdrake Mail Alert
From: root@localhost
To: ), "$email\n";
print F $r;

# EOF);
	output $cron_hourly, $cron;
	chmod 0755, $cron_hourly;
        
    undef $::isWizard; 
    if (defined $::WizardWindow) {
	$::WizardWindow->destroy;
	undef $::WizardWindow;
    }
}


#-------------------------------------------------------------
# menu callback functions
#-------------------------------------------------------------


sub save {
    $::isWizard = 0;
    $yy = $in->ask_file(N("Save as.."), "/root") or return;
    my $buf = $log_text->get_buffer;
    output($yy, $buf->get_text(($buf->get_bounds), 0));
}