"Fossies" - the Fresh Open Source Software Archive

Member "log_analysis-0.46/log_analysis.in" (17 Apr 2012, 296281 Bytes) of package /linux/privat/old/log_analysis-0.46.tar.gz:


As a special service "Fossies" has tried to format the requested text file into HTML format (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 #!@PERL@ -w
    2 # vi: ts=4 autoindent
    3 
    4 # log_analysis, by Mordechai T. Abzug
    5 
    6 # RCD $Id: log_analysis.in,v 1.310 2012/04/17 10:39:50 morty Exp $
    7 
    8 # definitely requires at least perl 5.005.  Written under perl 5.8.8.
    9 require 5.005;
   10 
   11 # specify load location for modules that come with the distro
   12 my $pkgdatadir;
   13 BEGIN { $pkgdatadir="@prefix@/share/@PACKAGE@"; unshift @INC, $pkgdatadir }
   14 
   15 sub debug(@);
   16 sub empty($);
   17 
   18 use strict;
   19 
   20 use POSIX;
   21 use English qw(-no_match_vars);
   22 use Getopt::Std;
   23 use re 'eval';
   24 use File::stat;
   25 use File::Basename;
   26 use FileHandle;
   27 use Sys::Syslog qw(:DEFAULT setlogsock);
   28 use User::grent;
   29 use User::pwent;
   30 
   31 my $prog=basename $0;
   32 my $cwd=getcwd;
   33 
   34 my $authorsfile='@prefix@/share/@PACKAGE@/AUTHORS';
   35 my $URL='http://lug.umbc.edu/~mabzug1/log_analysis.html';
   36 
   37 my @required_import_scalars=qw(
   38 	other_host_message
   39 	date_format
   40 	output_message_one_day
   41 	output_message_all_days
   42 	output_message_all_days_in_range
   43 	real_mode_output_format
   44 	);
   45 
   46 my @optional_import_scalars=qw(
   47 	host_pat zone_pat ip_pat user_pat mail_user_pat file_pat word_pat
   48 	mail_address
   49 	mail_command
   50 	memory_size_command
   51 	PATH
   52 	nodename osname osrelease
   53 	rcs_command
   54 	show_all real_mode days_ago output_file output_file_and_stdout
   55 	suppress_commands suppress_footer
   56 	real_mode_sleep_interval real_mode_check_interval real_mode_backlogs
   57 	real_mode_no_actions_unless_is_daemon
   58 	keep_all_raw_logs
   59 	daemon_mode
   60 	daemon_mode_pid_file
   61 	daemon_mode_foreground
   62 	report_mode_output_node_per_category
   63 	report_mode_combine_nodes
   64 	report_mode_combine_shows_nodes
   65 	report_mode_combine_is_partway
   66 	gui_mode gui_mode_modifier gui_mode_unknowns_pause_at
   67 	default_sort
   68 	default_filter
   69 	default_login_action
   70 	default_action_format
   71 	print_format
   72 	save_format
   73 	default_throttle_format
   74 	gui_mode_config_file
   75 	gui_mode_config_autosave
   76 	gui_mode_config_savelocal
   77 	gui_mode_config_save_does_rcs
   78 	gui_mode_configure_disabled
   79 	print_command
   80 	gui_mode_print_all
   81 	gui_mode_save_all
   82 	gui_mode_save_events_file
   83 	window_command
   84 	process_all_nodenames
   85 	umask
   86 	priority
   87 	domain
   88 	leave_FQDNs_alone
   89 	type_force
   90 	);
   91 
   92 my %import_scalars;
   93 @import_scalars{@optional_import_scalars, @required_import_scalars}=undef;
   94 
   95 my @required_import_arrays=qw(
   96 	log_type_list
   97 	);
   98 
   99 my @optional_import_arrays=qw(
  100 	optional_log_files
  101 	commands_to_run
  102 	ignore_categories
  103 	priority_categories
  104 	allow_nodenames
  105 	filename_ignore_patterns
  106 	gui_mode_configure_deny_users
  107 	gui_mode_configure_deny_groups
  108 	gui_mode_configure_allow_users
  109 	gui_mode_configure_allow_groups
  110 	);
  111 
  112 my @arrays_to_become_hashes=qw(
  113 	decompression_rules
  114 	pgp_rules
  115 	colors
  116 	login_action
  117 	pat
  118 );
  119 
  120 my %import_arrays;
  121 @import_arrays{@required_import_arrays, @optional_import_arrays,
  122 	@arrays_to_become_hashes}=undef;
  123 
  124 my %arrays_to_become_hashes;
  125 @arrays_to_become_hashes{@arrays_to_become_hashes}=undef;
  126 
  127 my @per_log_required_scalar_exts=qw(
  128 	date_pattern
  129 	date_format
  130 	);
  131 
  132 my @per_log_optional_scalar_exts=qw(
  133 	nodename_pattern
  134 	open_command
  135 	open_command_is_continuous
  136 	pipe_decompress_to_open
  137 	);
  138 
  139 my @per_log_required_array_exts=qw(
  140 	filenames
  141 	);
  142 
  143 my @per_log_optional_array_exts=qw(
  144 	pre_date_hook
  145 	pre_skip_list_hook
  146 	skip_list
  147 	raw_rules
  148 	);
  149 
  150 my %dests_minimum=(
  151 	CATEGORY=>[qw(dest_category format)],
  152 	SKIP=>[qw()],
  153 	LAST=>[qw()],
  154 	UNIQUE=>[qw(dest_category format)],
  155 	);
  156 
  157 my %dests_deactivate=(
  158 	CATEGORY=>[qw()],
  159 	SKIP=>[qw(count format use_sprintf delete_if_unique dest_category)],
  160 	LAST=>[qw(format use_sprintf delete_if_unique dest_category)],
  161 	UNIQUE=>[qw(count)],
  162 	);
  163 
  164 my %var2type;
  165 $var2type{$_}="boolean" foreach (qw(show_all output_file_and_stdout 
  166 	keep_all_raw_logs process_all_nodenames leave_FQDNs_alone
  167 	report_mode_output_node_per_category 
  168 	report_mode_combine_nodes report_mode_combine_shows_nodes
  169 	real_mode_backlogs real_mode_no_actions_unless_is_daemon
  170 	gui_mode_config_autosave gui_mode_config_savelocal
  171 	gui_mode_config_save_does_rcs
  172 	gui_mode_print_all gui_mode_save_all 
  173 ));
  174 $var2type{$_}="int" foreach (qw(days_ago priority umask));
  175 my %var2name=(
  176 	gui_mode_config_autosave=>"autosave config",
  177 	gui_mode_config_savelocal=>"save config if locally modified",
  178 	);
  179 
  180 # the next bunch of things are defined in the config, and can be overridden
  181 # in a user-defined config
  182 my (@required_log_files, @optional_log_files);
  183 my (%decompression_rules, %pgp_rules, %colors, @filename_ignore_patterns);
  184 my (
  185 	@gui_mode_configure_deny_users,
  186 	@gui_mode_configure_deny_groups,
  187 	@gui_mode_configure_allow_users,
  188 	@gui_mode_configure_allow_groups,
  189 	);
  190 my (%login_action);
  191 my (%pat, $host_pat, $zone_pat, $ip_pat, $user_pat, $mail_user_pat);
  192 my ($file_pat, $word_pat);
  193 my ($date_format, $other_host_message);
  194 my ($output_message_one_day, $output_message_all_days);
  195 my ($output_message_all_days_in_range);
  196 my ($real_mode_output_format);
  197 my (@ignore_categories, @priority_categories, @unknown_categories);
  198 my $debug=0;
  199 
  200 # regular variables
  201 my (%count, %command_output); # the variables storing data for output
  202 my (%unknowns, %unknowns_raw);
  203 my %nodename_allowed;
  204 my $data_start=tell DATA; # save the position of the DATA start
  205 my $time_start=time;
  206 my @F;
  207 my $config_is_dirty;
  208 my $config_scalar={};
  209 my $config_array={};
  210 my (%patterns, %dests, %categories, %actions, @event_config);
  211 my ($event_change, %event_tree);
  212 my $minimum_version="0.21"; # the first version that this version can understand
  213 my $current_version="@VERSION@";
  214 my $version_string="$prog $current_version";
  215 my $include_depth_limit=16; # recusion limit for includes
  216 my %tags=( '%' => '%' );
  217 my %backslash_tags=( "\\" => "\\", n => "\n", t => "\t");
  218 my ($domain, $leave_FQDNs_alone);
  219 my $PATH='/usr/local/bin:/bin:/usr/bin:/usr/ucb';
  220 my $umask="077";
  221 my @legacy_pats=qw(host zone ip user mail_user file word);
  222 my %in_config; # checks if variables are defined in the config
  223 
  224 # gui mode: assorted packing arrays.  
  225 # t/l=top/left, e/n=expand/noexpand, i=indent/flush
  226 my @Ptnf  = qw(-side top -anchor nw);
  227 my @Ptef  = qw(-side top -anchor nw -expand 1 -fill both);
  228 my @Ptxf  = qw(-side top -anchor nw -expand 1 -fill x);
  229 my @Ptyf  = qw(-side top -anchor nw -expand 1 -fill y);
  230 my @Ptni  = (@Ptnf, -ipadx=>"0.5c");
  231 my @Ptei  = (@Ptef, -ipadx=>"0.5c");
  232 my @Plnf  = (@Ptnf, -side=>"left");
  233 my @Plxf  = (@Ptxf, -side=>"left");
  234 my @Plyf  = (@Ptxf, -side=>"left");
  235 my @Plef  = (@Ptef, -side=>"left");
  236 my @Plni  = (@Ptni, -side=>"left");
  237 my @Prnf  = (@Ptnf, -side=>"right");
  238 
  239 # gui mode: standard args for frames
  240 my @frameargs=qw(-relief raised -borderwidth 2);
  241 my @simpleframe=qw();
  242 
  243 my %tag2name=(c=>"category", h=>"host", d=>"data");
  244 my %name2tag=(category=>"c", host=>"h", data=>"d");
  245 $ENV{PATH}=$PATH;
  246 my %priority_val=(
  247 	EMERG => 0,
  248 	ALERT => 1,
  249 	CRIT => 2,
  250 	ERR => 3,
  251 	WARNING => 4,
  252 	NOTICE => 5,
  253 	INFO => 6,
  254 	DEBUG => 7,
  255 	IGNORE => 10,
  256 );
  257 my %priority_name=(
  258 	EMERG => "Emergency",
  259 	ALERT => "Alert",
  260 	CRIT => "Critical",
  261 	ERR => "Error",
  262 	WARNING => "Warning",
  263 	NOTICE => "Notice",
  264 	INFO => "Informational",
  265 	DEBUG => "Debug",
  266 	IGNORE => "Ignore",
  267 );
  268 my @priorities=qw(EMERG ALERT CRIT ERR WARNING NOTICE INFO DEBUG IGNORE);
  269 
  270 my $appname="$version_string @ ".(uname())[1];
  271 
  272 my %opt;
  273 my $days_ago=1;
  274 my $internal_info="";
  275 my $suppress_commands=0;
  276 my $suppress_footer=0;
  277 my $show_all=0;
  278 my $unknowns_only=0;
  279 my $unknowns_dir=0;
  280 my $gui_mode_unknowns_pause_at;
  281 my $mail_command;
  282 my $mail_address="";
  283 my $pgp_type="";
  284 my $type_force;
  285 my $priority=0;
  286 my $rcs_command;
  287 my $real_mode=0;
  288 my $real_mode_sleep_interval=1;
  289 my $real_mode_check_interval=300;
  290 my $real_mode_backlogs=0;
  291 my $real_mode_no_actions_unless_is_daemon;
  292 my $keep_all_raw_logs=0;
  293 my $remote_file;
  294 my $gui_mode=0;
  295 my $gui_mode_modifier="Alt";
  296 my $gui_mode_configure_disabled;
  297 my $daemon_mode=0;
  298 my $daemon_mode_pid_file="/var/run/$prog.pid";
  299 my $daemon_mode_foreground;
  300 my $report_mode_output_node_per_category;
  301 my $report_mode_combine_nodes;
  302 my $report_mode_combine_shows_nodes;
  303 my $report_mode_combine_is_partway;
  304 my $default_sort="funky";
  305 my $default_filter;
  306 my $default_login_action;
  307 my $default_action_format;
  308 my $print_format;
  309 my $gui_mode_config_autosave;
  310 my $gui_mode_config_savelocal=1;
  311 my $gui_mode_config_save_does_rcs=1;
  312 my $gui_mode_config_file="$ENV{HOME}/.$prog.conf";
  313 my $save_format;
  314 my $default_throttle_format;
  315 my $print_command;
  316 my $gui_mode_print_all;
  317 my $gui_mode_save_all;
  318 my $gui_mode_save_events_file;
  319 my $window_command;
  320 my $memory_size_command;
  321 my @commands_to_run;
  322 my $output_file;
  323 my $output_file_and_stdout;
  324 my $process_all_nodenames;
  325 my @allow_nodenames;
  326 
  327 my $nodename;
  328 my $osname;
  329 my $osrelease;
  330 
  331 # information we track specially in case someone uses -I
  332 my @categories;
  333 my @config_versions;
  334 my @config_files;
  335 
  336 # preprocessor variable namespace
  337 my %VAR;
  338 
  339 # widgets for gui_mode
  340 my $gui_mode_main;
  341 my $gui_mode_hlist;
  342 
  343 # some state variables for real mode
  344 my %real_file_state;
  345 my $real_mode_bypass; # this variable lets us not output in real mode when we
  346 	# initially scan through the files
  347 my $real_mode_before_now;
  348 my $real_mode_last_check_time;
  349 my %real_mode_keep_open;
  350 my %throttle_state;
  351 
  352 # state for gui_mode
  353 my %gui_mode_state;
  354 my %gui_mode_hashref;
  355 my %gui_mode_find_what;
  356 my $gui_mode_status=""; # string to display at bottom of main window
  357 my $gui_mode_raw=0; # count of raw events
  358 my $gui_mode_raw_last_count=0; # count of raw events as of the last second
  359 my $gui_mode_raw_last_time=time; # when we last updated
  360 my $gui_mode_events_per_second=0; # events per second processed
  361 my $gui_mode_total=0;
  362 my $gui_mode_knowns=0;
  363 my $gui_mode_unknowns=0;
  364 my $gui_mode_selected=0;
  365 my $gui_mode_visible=0;
  366 my $gui_mode_hidden=0;
  367 my $gui_mode_paused=0;
  368 my $gui_mode_follow_new=0;
  369 my $gui_mode_status_updated=0;
  370 my $gui_mode_types_redone=0;
  371 my $gui_mode_busy=0; # are we currently in a busy state?
  372 my (%color2style, %color2bell);
  373 my @gui_mode_headers=qw/clear count host category data/;
  374 my %gui_mode_field_width=(
  375 	clear    => 5,   count    =>  8, node     => 15,
  376 	host     => 15,  category => 45, data     => 100,
  377 	priority => 10,  delete   =>  8, config   => 10,
  378 	summary  => 10, logtype   => 10, pattern  => 50,
  379 	local    => 6,
  380 	);
  381 my $selected_pat;
  382 
  383 
  384 # assorted state variables
  385 my %last; # last message processed for file; needed for "last message repeated"
  386 my %multiplier; # multiplier associated with last message for "last message 
  387 	# repeated"
  388 my %incomplete; # if we catch a process in the middle of writing to a file,
  389 	# before it writes the newline, we cache the incomplete line here
  390 my %unique; # used for tracking the special "unique" categories
  391 my %type_of; # track the type of a given file
  392 my (@match_start, @match_end); # get around scope limitations
  393 my %is_local; # value has been modified locally; VERY important!
  394 
  395 # state variable for user-defined use
  396 my %state;
  397 
  398 # get user name
  399 my $user;
  400 {
  401 	my $pwent=getpwuid($EUID) or die "$prog: getpwuid $EUID: $!\n";
  402 	$user=$pwent->name;
  403 }
  404 
  405 # get group names
  406 my @groups;
  407 foreach my $gid (&unique(split /\s+/, $EGID)) { # get group numbers
  408 	my $grent=getgrgid($gid) or die "$prog: group $gid has no name\n";
  409 	push @groups, $grent->name;
  410 }
  411 
  412 ($osname, $nodename, $osrelease)=POSIX::uname;
  413 
  414 $tags{n}=$nodename;
  415 $tags{s}=$osname;
  416 $tags{r}=$osrelease;
  417 
  418 my %do_type;
  419 
  420 my @original_ARGV=@ARGV;
  421 my $optstring="aAbd:D:Ef:FghiI:m:M:n:No:Op:rR:sSt:u:UvZ";
  422 if (!getopts($optstring, \%opt)) {
  423 	die &usage;
  424 }
  425 
  426 
  427 if ($opt{h}) { print &usage; &my_exit(0); }
  428 
  429 if ($opt{v}) {
  430 	print "$version_string\n";
  431 	&my_exit(0);
  432 }
  433 
  434 # if daemon mode is enabled, we should also log any errors to syslog
  435 &daemon_mode_syslog_on if $opt{A};
  436 
  437 $internal_info=$opt{I}           if defined $opt{I};
  438 
  439 if ($internal_info =~ m{^internal[\-\_]config$} || $internal_info eq "all_configs") {
  440 	seek(DATA, $data_start, 0);
  441 	print while (defined($_=<DATA>) && !m{^\=});
  442 	seek(DATA, $data_start, 0);
  443 	&my_exit(0) if $internal_info =~ m{^internal[\-\_]config$};
  444 }
  445 
  446 $VAR{__USE_MINIMAL_CONFIG}=1 if $opt{F};
  447 $VAR{__NO_STD_INCLUDES}   =1 if $opt{i};
  448 
  449 if ($opt{D}) {
  450 	my @vars=split(/\,/, $opt{D});
  451 	foreach my $var (@vars) {
  452 		my $val=1;
  453 		if ($var =~ s{^([^=]+)\=(.+)}{$1}) {
  454 			$val=$2;
  455 		}
  456 		$VAR{$var}=$val;
  457 	}
  458 }
  459 
  460 sub usage {
  461 	return "Usage: $prog [-f config_file] [-n nodename] [-U] [-d days] [-a]\n".
  462 		"      [-m mail-address] [-M mail-prog] [-p pgp-type] [-s] [-S]\n".
  463 		"      [-t type_force] [-N] [-v] [-A] [-b] [logsfiles. . .]\n".
  464 		"\n".
  465 		"      -a                 show all logs, even old stuff\n".
  466 		"      -A                 daemon mode; real mode as a daemon\n".
  467 		"      -b                 show old logs (see -d/days_ago) in real/gui mode\n".
  468 		"      -d days-ago        show only logs from this long ago (def.=1)\n".
  469 		"      -D var1,var2=val   set preprocessor variables\n".
  470 		"      -f config_file     read config_file for additional config\n".
  471 		"      -F                 use minimal default config\n".
  472 		"      -g                 gui mode (if available)\n".
  473 		"      -I categories      output all categories and exit\n".
  474 		"      -I config_versions output all config versions and exit\n".
  475 		"      -I help            show the other -I options\n".
  476 		"      -m mail-address    mail output to this mail-address\n".
  477 		"      -M mail-prog       set the mail program (default='Mail')\n".
  478 		"      -n nodename        use 'nodename' for scanning syslogs\n".
  479 		"      -N                 process all nodenames\n".
  480 		"      -o output_file     output to output_file\n".
  481 		"      -O                 with -o, also output to stdout\n".
  482 		"      -p pgp-type        encrypt mail output in 'pgp-type' style\n".
  483 		"      -r                 'real mode' for continuous output\n".
  484 		"      -s                 suppress running extra commands\n".
  485 		"      -S                 suppress output \"footer\"\n".
  486 		"      -t type_force      force files to be type type_force\n".
  487 		"      -u unknownsdir     use unknownsdir as the log source or output for -U\n".
  488 		"      -U                 write unknowns and exit\n".
  489 		"      -v                 version\n".
  490 		"";
  491 }
  492 
  493 $VAR{_CONFIG_FILE1}=$gui_mode_config_file 
  494 	if $opt{g} && -f $gui_mode_config_file && -r _;
  495 $VAR{_CONFIG_FILE2}=$opt{f} if exists $opt{f};
  496 
  497 &config_parse(\*DATA, "internal-config", $config_scalar, $config_array, 0);
  498 
  499 # done reading in the config.  Let's process it.
  500 my (@log_type_list, %log_scalar, %log_array);
  501 &import_config_vars($config_scalar, $config_array);
  502 umask oct $umask;
  503 
  504 # this group of option processing is in a deliberate order.  Don't mess.
  505 $show_all=1                      if defined $opt{a};
  506 $real_mode=1                     if defined $opt{r};
  507 $gui_mode=1                      if defined $opt{g};
  508 $daemon_mode=1                   if defined $opt{A};
  509 $days_ago=0 if (($show_all || $real_mode || $gui_mode || $daemon_mode) 
  510 	&& !exists $in_config{days_ago}) && !exists $opt{d};
  511 $days_ago=$opt{d}                if defined $opt{d};
  512 
  513 # other options in alpha order
  514 $real_mode_backlogs=1            if defined $opt{b};
  515 $debug=1                         if defined $opt{E};
  516 $mail_address=$opt{m}            if defined $opt{m};
  517 $mail_command=$opt{M}            if defined $opt{M};
  518 $nodename=$opt{n}                if defined $opt{n};
  519 $process_all_nodenames=1         if defined $opt{N};
  520 $output_file=$opt{o}             if defined $opt{o};
  521 $output_file_and_stdout=1        if defined $opt{O};
  522 $pgp_type=$opt{p}                if defined $opt{p};
  523 $remote_file=$opt{R}             if defined $opt{R};
  524 $suppress_commands=1             if defined $opt{s};
  525 $suppress_footer=1               if defined $opt{S};
  526 $type_force=$opt{t}              if defined $opt{t};
  527 $unknowns_dir=$opt{u}            if defined $opt{u};
  528 $unknowns_only=1                 if defined $opt{U};
  529 $daemon_mode_foreground=1        if defined $opt{Z};
  530 
  531 
  532 if (@ARGV && !$remote_file) {
  533 	@required_log_files=@ARGV;
  534 	@optional_log_files=();
  535 }
  536 
  537 $real_mode=1 if $gui_mode;
  538 $real_mode=1 if $daemon_mode;
  539 
  540 if ($remote_file) {
  541 	@optional_log_files=($remote_file);
  542 	$real_mode=1;
  543 	$real_mode_output_format="%R\n";
  544 }
  545 
  546 my ($day_start, $day_end, $is_multiday);
  547 
  548 if ($days_ago=~m{^(\d{4,}_\d{1,2}_\d{1,2})\-(\d{4,}_\d{1,2}_\d{1,2})$} ||
  549 	$days_ago=~m{^(\d+)-(\d+)$} ) {
  550 		$day_start=$1;
  551 		$day_end=$2;
  552 		$is_multiday=1;
  553 } elsif ($real_mode && $days_ago !~ m{\-}) {
  554 		$day_start=$days_ago;
  555 		$day_end="today";
  556 		$is_multiday=1;
  557 } else {
  558 		$day_start = $day_end = $days_ago;
  559 		$is_multiday=0;
  560 }
  561 
  562 my $relday_start = &normalize2relday($day_start);
  563 my $relday_end   = &normalize2relday($day_end);
  564 
  565 my @when_start   = &relday2time($relday_start);
  566 my @when_end     = &relday2time($relday_end);
  567 
  568 die "Start date must be before or on end date\n"
  569 	if $relday_start < $relday_end;
  570 
  571 # if we don't know what domain we're in, let's figure it out.
  572 if (!defined $domain) {
  573 	my $resolvfh=new FileHandle;
  574 	if ($resolvfh->open("</etc/resolv.conf")) {
  575 		while(<$resolvfh>) {
  576 			if (m{^\s*domain\s+([\w\.\-]+)\s*$}i) {
  577 				$domain=$1;
  578 				last;
  579 			}
  580 		}
  581 		$resolvfh->close;
  582 	}
  583 }
  584 
  585 # localize nodename
  586 if (defined $domain && !$leave_FQDNs_alone) {
  587 	$nodename=~s{\.(${domain}|localdomain)$}{};
  588 }
  589 
  590 $tags{n}=$nodename;
  591 $tags{s}=$osname;
  592 $tags{r}=$osrelease;
  593 
  594 my (%when);
  595 my ($evals, $filename_pats)=&build_log_stuff($config_scalar, $config_array);
  596 &build_event_tree;
  597 
  598 if ($internal_info) {
  599 	my $do_exit=1; # assume we should exit unless we're sure
  600 	if ($internal_info eq "evals") {
  601 		foreach my $type (@log_type_list) {
  602 			print "eval for $type is:\n";
  603 			my $i=1;
  604 			print map(sprintf("%5d\t%s\n", $i++, $_), 
  605 				split("\n", $evals->{$type}));
  606 			print "\n";
  607 		}
  608 	} elsif ($internal_info =~ m{^eval:(.*)}) {
  609 		my $type=$1;
  610 		die "No such type '$type'\n" unless exists $evals->{$type};
  611 		print "eval for $type is:\n";
  612 		my $i=1;
  613 		print map(sprintf("%5d\t%s\n", $i++, $_), 
  614 			split("\n", $evals->{$type}));
  615 		print "\n";
  616 	} elsif ($internal_info eq "evals-only") {
  617 		foreach my $type (@log_type_list) {
  618 			print $evals->{$type}, "\n";
  619 		}
  620 	} elsif ($internal_info eq "permissions") {
  621 		print "read/write: ".($gui_mode_configure_disabled?"no":"yes")."\n";
  622 	} elsif ($internal_info eq "categories") {
  623 		print map("$_\n", sort &unique(@categories));
  624 	} elsif ($internal_info eq "actions") {
  625 		print map("$_\n", sort &unique(keys %actions));
  626 	} elsif ($internal_info eq "colors") {
  627 		print map("$_\n", sort &unique(keys %colors));
  628 	} elsif ($internal_info eq "pats") {
  629 		print map("$_\n", sort &unique(keys %pat));
  630 	} elsif ($internal_info eq "nothing" || $internal_info eq "null") {
  631 		# do nothing at all.  Used to check that the code is OK because 
  632 		# perl -c is a whole new command line. . .
  633 	} elsif ($internal_info =~ m{^(config|file)[\-\_]versions$}) {
  634 		print @config_versions;
  635 	} elsif ($internal_info eq "all_configs") {
  636 		foreach my $config_file (@config_files) {
  637 			open (CONFIG, "< $config_file") or die "$prog: open $config_file: $!\n";
  638 			print "#\n# contents of config file: $config_file\n#\n";
  639 			print <CONFIG>;
  640 			close CONFIG;
  641 		}
  642 	} elsif ($internal_info =~ m{^log[\-\_]files$}) {
  643 		# do nothing now; we'll handle this later
  644 		$do_exit=0;
  645 	} elsif ($internal_info eq "patterns") {
  646 		foreach my $type (@log_type_list) {
  647 			print "patterns for $type are:\n";
  648 			print map("\t$_\n", @{$patterns{$type}});
  649 		}
  650 	} elsif ($internal_info =~ m{^log[\-\_]types$}) {
  651 		print map("$_\n", @log_type_list);
  652 	} elsif ($internal_info eq "help") {
  653 		print map("$_\n", sort qw(
  654 			internal_config evals categories config_versions 
  655 			log_files log_types patterns nothing actions colors
  656 			pats configs help
  657 			));
  658 	} else {
  659 		die "$prog: internal info type $internal_info is not known.\n";
  660 	}
  661 	&my_exit(0) if $do_exit;
  662 }
  663 
  664 &run_evals;
  665 
  666 setpriority (0, $$, $priority) || die "$prog: setpriority $priority: $!\n"
  667 	if $priority;
  668 
  669 
  670 eval { &sort_keys ($default_sort); };
  671 die "default sort $default_sort gives an error: $@\n" if $@;
  672 
  673 if ($unknowns_dir) {
  674 	die "$prog: -u isn't compatible with other output options\n"
  675 		if $opt{o} || $opt{m} || $opt{p};
  676 	die "unknowns_dir must be a readable, executable directory\n"
  677 		if -e $unknowns_dir && (!-d _ || !-r _ || !-x _);
  678 	@required_log_files=glob("$unknowns_dir/*") if -e $unknowns_dir;
  679 }
  680 
  681 if ($real_mode) {
  682 	die "$prog: -r/real_mode isn't compatible with -a/show_all\n"
  683 		if $show_all;
  684 }
  685 
  686 
  687 if ($gui_mode) {
  688 
  689 	# load Tk modules only if we're in GUI mode.  Don't do this otherwise.
  690 	my @modules=qw{Tk Tk::HList Tk::Event Tk::Wm Tk::Dialog Tk::ItemStyle
  691 		Tk::FileSelect Tk::ROText Tk::Font Tk::ErrorDialog Tk::BrowseEntry
  692 		Tk::NoteBook Tk::resizebutton Tk::Wm Tk::LabEntry Tk::Pane
  693 	};
  694 	foreach my $module (@modules) {
  695 		my $eval_string="use $module";
  696 		eval $eval_string; # trap any fatal errors
  697 		die qq($prog with -g or gui_mode requires the $module module, which is \n).
  698 	    	qq(generating an error.  Are you sure it's installed?\n).
  699 			qq(To see the error yourself, please run: perl -e "$eval_string"\n)
  700 				if $@;
  701 	}
  702 
  703 	if ($daemon_mode) {
  704 		warn "$prog: daemon mode isn't compatible with gui mode\n";
  705 		undef $daemon_mode;
  706 	}
  707 
  708 	&gui_mode_init;
  709 }
  710 
  711 # done configging.  Let's rock.
  712 
  713 if ($real_mode) {
  714 	&do_real_mode;
  715 	die; # we should never reach this
  716 } else { #report mode
  717 	&do_report_mode;
  718 	# done looking at the log files.  Let's run the usual commands. . .
  719 	if (! $suppress_commands) {
  720 		foreach my $command (@commands_to_run) {
  721 			$command_output{$command}=qx($command);
  722 		}
  723 	}
  724 	&report_mode_output;
  725 }
  726 
  727 &my_exit(0);
  728 # Fini.
  729 
  730 
  731 sub debug(@) {
  732 	warn "@_\n" if $debug;
  733 }
  734 
  735 
  736 sub process_handle {
  737 	my $filename=shift;
  738 	my $handle=shift;
  739 
  740 	my $type=&type($filename);
  741 	die "$prog: Unknown type: $type\n" if ! exists $do_type{$type};
  742 	$do_type{$type}->($filename, $handle);
  743 }
  744 
  745 
  746 sub do_report_mode {
  747 	# expand out any globs
  748 
  749 	die "No log files specified!\n"
  750 		unless @optional_log_files+@required_log_files>0;
  751 
  752 	my @globbed_optional_log_files;
  753 	foreach (@optional_log_files) {
  754 		push @globbed_optional_log_files, glob($_);
  755 	}
  756 	@globbed_optional_log_files=grep(-r, @globbed_optional_log_files);
  757 
  758 	die "none of the log files is readable!\n" 
  759 		unless @globbed_optional_log_files+@required_log_files>0;
  760 
  761 	foreach my $file (@required_log_files) {
  762 		die "can't open log file $file\n" unless -r $file;
  763 	}
  764 
  765 	my @log_files=@required_log_files? @required_log_files 
  766 		: @globbed_optional_log_files;
  767 
  768 	# If it's too old, skip it right away.  No test for too young, because a 
  769 	# recently modified file may contain old logs.
  770 	@log_files=grep(-M $_ <= $relday_start+2, @log_files) 
  771 		unless $show_all && !$is_multiday;
  772 
  773 	if ($internal_info =~ m{^log[\-\_]files$}) {
  774 		print map("$_\n", @log_files);
  775 		&my_exit(0);
  776 	}
  777 
  778 	# if someone hits interrupt, print out what we have
  779 	$SIG{'INT'}=sub { 
  780 		print "\nInterrupt received, dumping output.\n\n";
  781 		&report_mode_output;
  782 		warn "$prog: interrupt received, dumped output.\n";
  783 		&my_exit(0);
  784 	};
  785 
  786 	# OK, let's actually look at the log files
  787 	foreach my $file (@log_files) {
  788 		my $fh=&open($file) || die;
  789 		&process_handle($file, $fh);
  790 
  791 		close $fh;
  792 	}
  793 }
  794 
  795 
  796 sub do_real_mode {
  797 
  798 	# initially, we're scanning through "old" logs (ie. not current.)
  799 	$real_mode_bypass=1 if !$real_mode_backlogs;
  800 	$real_mode_before_now=1;
  801 
  802 	die "$prog: no files to watch!" 
  803 		unless @optional_log_files + @required_log_files;
  804 
  805 	STDOUT->autoflush(1); #otherwise color stuff doesn't work right
  806 
  807 	$real_mode_last_check_time=0; # ie. never
  808 
  809 	# currently, required_log_files and optional_log_files are mutually
  810 	# exclusive, but that could change.  Let's avoid assumptions.
  811 	foreach my $file (@optional_log_files) {
  812 		$real_file_state{$file}{required}=0;
  813 	}
  814 	foreach my $file (@required_log_files) {
  815 		$real_file_state{$file}{required}=1;
  816 	}
  817 
  818 	# daemon mode is a minor variant of real mode
  819 	&daemon_mode_daemonize if $daemon_mode;
  820 
  821 	# gui mode is a variant of real mode
  822 	if ($gui_mode) {
  823 		&real_mode_check_function;
  824 		&Tk::MainLoop;
  825 		&my_exit;
  826 	} else {
  827 		while (1) {
  828 			&real_mode_check_function;
  829 			sleep($real_mode_sleep_interval);
  830 		}
  831 	}
  832 	die; # we should never reach here
  833 }
  834 
  835 sub real_mode_check_function {
  836 	if (time-$real_mode_last_check_time>=$real_mode_check_interval) {
  837 		$real_mode_last_check_time=time;
  838 
  839 		foreach my $file (keys %real_file_state) {
  840 			my $filename;
  841 			if ($real_file_state{$file}{required}) {
  842 				die "Unable to read required file $file\n" if !-r $file;
  843 				$filename=$file;
  844 			} else { # then we also need to glob the filename
  845 				my @globbed=glob($file);
  846 				@globbed=grep(-r, @globbed); # make sure files are readable
  847 
  848 				if (!@globbed) { # nothing to read!
  849 					if (exists $real_file_state{$file}{handle}) {
  850 						my $handle=$real_file_state{$file}{handle};
  851 						my $name=$real_file_state{$file}{name};
  852 						&process_handle($name, $handle)
  853 							while !$handle->eof; # loop for non-blocking
  854 						$handle->close;
  855 						delete $real_file_state{$file}{handle};
  856 					}
  857 					next;
  858 				} elsif (@globbed==1) {
  859 					$filename=shift @globbed;
  860 				} else {
  861 					@globbed=sort modification_sort_helper @globbed;
  862 					$filename=shift @globbed;
  863 					if ($real_mode_before_now && $real_mode_backlogs) {
  864 						foreach my $filename (reverse @globbed) {
  865 							next if -M $filename > $relday_start + 2;
  866 							my $handle=&open($filename);
  867 							&process_handle($filename, $handle)
  868 								while !$handle->eof; # loop for non-blocking
  869 							$handle->close;
  870 						}
  871 					}
  872 				}
  873 			}
  874 
  875 			my $oldhandle = $real_file_state{$file}{handle};
  876 			my $oldino    = $real_file_state{$file}{ino};
  877 
  878 			my $newstat = stat $filename || die "$prog: stat $filename\n";
  879 			my $newino  = $newstat->ino;
  880 
  881 			if ($oldhandle) { # we have an open file.  Is it current?
  882 				if ($newino == $oldino) { # same file
  883 					next; # we don't need to play with it.
  884 				} else {
  885 					# different file.  Let's process the old one one last time 
  886 					# and then close it
  887 					my $oldfilename=$real_file_state{$file}{name};
  888 					my $oldtype=$real_file_state{$file}{type};
  889 					&process_handle($oldfilename, $oldhandle)
  890 						while !$oldhandle->eof; # loop for non-blocking
  891 					$oldhandle->close;
  892 					undef $real_file_state{$file}{handle};
  893 				}
  894 			}
  895 
  896 			# OK, we need to open this sucker.
  897 			my $handle=&open($filename);
  898 
  899 			$real_file_state{$file}{handle} = $handle;
  900 			$real_file_state{$file}{name}   = $filename;
  901 			$real_file_state{$file}{ino}    = $newino;
  902 		}
  903 	}
  904 
  905 	foreach my $file (keys %real_file_state) {
  906 		my $fh=$real_file_state{$file}{handle};
  907 		next if ! defined $fh;
  908 		my $type=$real_file_state{$file}{type};
  909 		my $name=$real_file_state{$file}{name};
  910 		seek ($fh, 0, 1); # straight out of perldoc -f seek
  911 		&process_handle($name, $fh);
  912 	}
  913 	$real_mode_bypass=0;
  914 
  915 	if ($real_mode_before_now) {
  916 		$real_mode_before_now=0;
  917 	}
  918 
  919 	if ($gui_mode) {
  920 		# clear status indicator
  921 		&gui_mode_status;
  922 
  923 		# reschedule checking for data
  924 		$gui_mode_main->after($real_mode_sleep_interval*1000, 
  925 			\&real_mode_check_function);
  926 	}
  927 
  928 }
  929 
  930 
  931 sub modification_sort_helper {
  932 	return -M $a <=> -M $b;
  933 }
  934 
  935 
  936 sub type {
  937 	defined(my $file=shift) || die "Internal error";
  938 
  939 	return $type_of{$file} if exists $type_of{$file};
  940 
  941 	return $type_force if defined $type_force;
  942 
  943 	# what log type is it?
  944 	my @types=grep(&basename($file) =~ m{^$filename_pats->{$_}},
  945 			@log_type_list);
  946 	die "no known log type for file $file\n" if ! @types;
  947 	die "more than one type matches file $file: @types\n" if @types>1;
  948 
  949 	my $type=$types[0];
  950 	$type_of{$file}=$type;
  951 
  952 	return $type;
  953 }
  954 
  955 
  956 sub open_command {
  957 	defined(my $file=shift) || die "Internal error";
  958 	defined(my $type=shift) || die "Internal error";
  959 
  960 	my $open_command;
  961 	$open_command=$log_scalar{$type}{open_command}
  962 			if $log_scalar{$type}{open_command};
  963 
  964 	my $pipe_decompress_to_open;
  965 	$pipe_decompress_to_open=$log_scalar{$type}{pipe_decompress_to_open}
  966 		if $log_scalar{$type}{pipe_decompress_to_open};
  967 
  968 	my $tmpfile=undef;
  969 	# if we have both a decompression rule and we already have an open_command, 
  970 	# we need to worry about two commands.  This shouldn't be a big deal -- 
  971 	# either we pipe the decompression output directly to the open command, or
  972 	# if that won't work, we use a temp file.  Default to the temp file, because
  973 	# it's more likely to work; override by setting pipe_decompress_to_open to
  974 	# true.
  975 	if ($file =~ m{\.([^\.]+)$} && exists $decompression_rules{$1}) {
  976 		if (!$open_command) {
  977 			$open_command=$decompression_rules{$1};
  978 			$open_command=&process_tags($open_command, {%tags, f=>$file});
  979 		} elsif ($pipe_decompress_to_open) {
  980 			my $command=&process_tags($decompression_rules{$1}, 
  981 				{%tags, f=>$file});
  982 			$open_command=&process_tags($open_command, {%tags, f=>"-"});
  983 			$open_command=$command." | ".$open_command;
  984 		} else {
  985 			# it would be nice if we could just assume that the open_command can
  986 			# correctly handle input from a pipe on stdin.
  987 			# Unfortunately, this is not the case for "last -f file" under 
  988 			# Solaris 2.6, OpenBSD 2.4, and various Linuxen.
  989 			# So, we do temp file fun.
  990 			my $command=&process_tags($decompression_rules{$1}, 
  991 				{%tags, f=>$file});
  992 			$tmpfile=&tmpnam;
  993 			$command .= " >$tmpfile";
  994 			die "unable to run '$command'\n" unless !(system($command)/256);
  995 
  996 			$open_command=&process_tags($open_command, {%tags, f=>$tmpfile});
  997 		}
  998 	} elsif ($open_command) { # and no decompression applies
  999 			$open_command=&process_tags($open_command, {%tags, f=>$file});
 1000 	}
 1001 	return ($open_command, $tmpfile);
 1002 }
 1003 
 1004 
 1005 sub open {
 1006 	defined(my $file=shift) || die "Internal error";
 1007 
 1008 	my $type=&type($file);
 1009 	my ($open_command, $tmpfile)=&open_command($file, $type);
 1010 
 1011 	my $fh=new FileHandle;
 1012 
 1013 	if (! $open_command) {
 1014 		$fh->open("<$file") 
 1015 			|| die "$prog unable to open $file for reading: $!\n";
 1016 	} else {
 1017 		$fh->open("$open_command|") 
 1018 			|| die "$prog: unable to run '$open_command': $!\n";
 1019 
 1020 		if ($log_scalar{$type}{open_command_is_continuous}) {
 1021 			if ($real_mode) {
 1022 				$fh->blocking(0) 
 1023 					|| die "$prog: set non-blocking on '$open_command': $!\n"
 1024 			} else {
 1025 				die "$prog: cannot use continuous command for type $type ".
 1026 					"except in real mode\n";
 1027 			}
 1028 		}
 1029 
 1030 		&rm_on_exit($tmpfile) if defined $tmpfile;
 1031 	}
 1032 
 1033 	$multiplier{$file}=1; # init
 1034 
 1035 	$incomplete{$file}=""; # init
 1036 
 1037 	return $fh;
 1038 }
 1039 
 1040 
 1041 # given an array IN, return an array OUT consisting of the unique elements of IN
 1042 sub unique {
 1043 	my @result;
 1044 	my %found;
 1045 	foreach my $elem (@_) {
 1046 		push @result, $elem unless $found{$elem}++;
 1047 	}
 1048 	return @result;
 1049 }
 1050 
 1051 
 1052 # given two array references in, return the set difference of A-B
 1053 sub set_difference {
 1054 	my $A=shift;
 1055 	my $B=shift;
 1056 
 1057 	my (%found, @result);
 1058 	@found{@$B}=(1) x @{$B};
 1059 	foreach my $i (&unique(@$A)) {
 1060 		push @result, $i unless $found{$i};
 1061 	}
 1062 	return @result;
 1063 }
 1064 
 1065 
 1066 # given two array references in, return the set intersection of A&B
 1067 sub set_intersection {
 1068 	my $A=shift;
 1069 	my $B=shift;
 1070 
 1071 	my (%found, @result);
 1072 	@found{@$B}=(1) x @{$B};
 1073 	foreach my $i (&unique(@$A)) {
 1074 		push @result, $i if $found{$i};
 1075 	}
 1076 	return @result;
 1077 }
 1078 
 1079 
 1080 # given a scalar E and an array ref A, return true if E is a member of A
 1081 sub set_is_member {
 1082 	my $E=shift;
 1083 	my $A=shift;
 1084 
 1085 	foreach my $i (@$A) {
 1086 		return 1 if $i eq $E;
 1087 	}
 1088 	return 0;
 1089 }
 1090 
 1091 
 1092 sub report_mode_output {
 1093 
 1094 	$SIG{'INT'}='DEFAULT';
 1095 
 1096 	if ($unknowns_only) {
 1097 		if ($unknowns_dir) {
 1098 			if (-d $unknowns_dir) {
 1099 				system("rm -r $unknowns_dir");
 1100 				die "$prog: rm -r $unknowns_dir: command failed\n" if $? >> 8;
 1101 			}
 1102 			die "No more unknowns!\n" if ! %unknowns;
 1103 			mkdir ($unknowns_dir, 0755) || die "$prog: mkdir $unknowns_dir: $!";
 1104 			foreach my $type (sort keys %unknowns) {
 1105 				my $outfh=new FileHandle(">$unknowns_dir/$type") 
 1106 					|| die "$prog: open $unknowns_dir/$type: $!";
 1107 				print $outfh sort keys %{$unknowns_raw{$type}};
 1108 				close $outfh;
 1109 			}
 1110 		}
 1111 	
 1112 		foreach my $type (sort keys %unknowns) {
 1113 			print "\nType: $type\n\n";
 1114 			print map($_."\n", sort keys %{$unknowns{$type}});
 1115 		}
 1116 	
 1117 		return;
 1118 	} elsif ($show_all) {
 1119 		&report_mode_output_day($relday_end);
 1120 	} else {
 1121 		for (my $day=$relday_start; $day>=$relday_end; $day--) {
 1122 			&report_mode_output_day($day);
 1123 		}
 1124 	}
 1125 }
 1126 
 1127 sub report_mode_output_day {
 1128 	defined(my $relday=shift) || die "$prog: missing arg";
 1129 	
 1130 	my $date       = strftime($date_format, &relday2time($relday));
 1131 	my $date_start = strftime($date_format, @when_start);
 1132 	my $date_end   = strftime($date_format, @when_end);
 1133 
 1134 
 1135 	my $output_message;
 1136 	my @output_commands;
 1137 
 1138 	if (!$show_all) {
 1139 		$output_message=&process_tags($output_message_one_day,
 1140 			{ %tags, d=>$date } );
 1141 	} elsif (!$is_multiday) {
 1142 		$output_message=&process_tags($output_message_all_days,
 1143 			{ %tags, d => $date } );
 1144 	} else {
 1145 		$output_message=&process_tags($output_message_all_days_in_range,
 1146 			{ %tags, s => $date_start, e => $date_end } );
 1147 	}
 1148 
 1149 	if ($output_file) {
 1150 		push @output_commands, "tee ".
 1151 			&process_tags($output_file, { %tags, d => $date } );
 1152 	}
 1153 
 1154 	if ($pgp_type) {
 1155 		die "Unknown PGP type: $pgp_type\n" 
 1156 			unless defined $pgp_rules{$pgp_type};
 1157 		my $pgp_command=&process_tags($pgp_rules{$pgp_type}, 
 1158 			{%tags, m => $mail_address});
 1159 		push @output_commands, $pgp_command;
 1160 	}
 1161 
 1162 	if ($mail_address) {
 1163 		push @output_commands, &process_tags($mail_command, 
 1164 			{%tags, m => $mail_address, o => $output_message });
 1165 	}
 1166 
 1167 	my $outfh;
 1168 	if (@output_commands) {
 1169 		my $output_command=join("|", @output_commands);
 1170 		if ($output_file && !$output_file_and_stdout) {
 1171 			$output_command.=" >/dev/null";
 1172 		}
 1173 		$outfh=new FileHandle("|$output_command")
 1174 			|| die "$prog: run $output_command: $!";
 1175 		select $outfh;
 1176 	}
 1177 
 1178 	print "\n$output_message\n\n";
 1179 	
 1180 	if (! $suppress_commands) {
 1181 		foreach my $command (@commands_to_run) {
 1182 			print "$command output:\n$command_output{$command}\n"
 1183 				if exists $command_output{$command};
 1184 		}
 1185 	}
 1186 	
 1187 	# process unique categories, derived categories, and ignore_categories
 1188 	foreach my $host (sort keys %{$count{$relday}}) {
 1189 		# unique categories
 1190 		my $unique_ref=$unique{$relday}{$host};
 1191 		foreach my $category (keys %$unique_ref) {
 1192 			foreach my $item (keys %{$unique_ref->{$category}}) {
 1193 				my $count=scalar keys %{$unique_ref->{$category}{$item}};
 1194 				$count{$relday}{$host}{$category}{$item}+=$count;
 1195 
 1196 			}
 1197 
 1198 			# apply filter if applicable
 1199 			my $filter=$categories{$category}{filter};
 1200 			$filter=$default_filter 
 1201 				if !defined $filter && defined $default_filter;
 1202 			my %values=%{$count{$relday}{$host}{$category}};
 1203 			my @items=keys %values;
 1204 			@items=&filter($filter, %values) if (defined $filter);
 1205 
 1206 			# delete stuff in delete_if_unique if applicable
 1207 			foreach my $item (@items) {
 1208 				foreach my $key (keys %{$unique_ref->{$category}{$item}}) {
 1209 					my $hashref=$unique_ref->{$category}{$item}{$key};
 1210 					foreach my $l1 (keys %$hashref) {
 1211 						foreach my $l2 (keys %{$hashref->{$l1}}) {
 1212 							delete $count{$relday}{$host}{$l1}{$l2};
 1213 						}
 1214 					}
 1215 				}
 1216 			}
 1217 		}
 1218 
 1219 		# derived categories
 1220 		foreach my $category (keys %categories) {
 1221 			my $derive=$categories{$category}{derive};
 1222 			next if !defined $derive;
 1223 			die "category $category should be derived, but was written to!\n"
 1224 				if defined $count{$relday}{$host}{$category};
 1225 			%{$count{$relday}{$host}{$category}} = 
 1226 				&derive($derive, $relday, $host);
 1227 		}
 1228 
 1229 		# ignore_categories
 1230 		foreach my $category (@ignore_categories) {
 1231 			delete $count{$relday}{$host}{$category};
 1232 		}
 1233 	}
 1234 
 1235 	my %unknown_categories;
 1236 	@unknown_categories{@unknown_categories}=undef;
 1237 	my @categories_output=(@priority_categories,
 1238 		grep (!exists $unknown_categories{$_}, &unique(@categories)),
 1239 		@unknown_categories);
 1240 	
 1241 	# actually do the output thang
 1242 	if ($report_mode_combine_nodes) {
 1243 		foreach my $category (@categories_output) {
 1244 				&report_mode_output_category($relday, $nodename, $category);
 1245 		}
 1246 		print "\n";
 1247 	} elsif ($report_mode_output_node_per_category) {
 1248 		foreach my $category (@categories_output) {
 1249 			foreach my $host (sort keys %{$count{$relday}}) {
 1250 				&report_mode_output_category($relday, $host, $category);
 1251 			}
 1252 		}
 1253 		print "\n";
 1254 	} else {
 1255 		foreach my $host (sort keys %{$count{$relday}}) {
 1256 			print "\n\nLogs found for other hosts.  For host $host:\n" 
 1257 				if (keys %{$count{$relday}}) > 1 || 
 1258 				( (keys %{$count{$relday}}) == 1 && 
 1259 					(keys(%{$count{$relday}}))[0] ne $nodename);
 1260 
 1261 			foreach my $category (@categories_output) {
 1262 				&report_mode_output_category($relday, $host, $category)
 1263 			}
 1264 
 1265 			print "\n";
 1266 		}
 1267 	}
 1268 
 1269 	# make sure everything got output
 1270 	foreach my $host (sort keys %{$count{$relday}}) {
 1271 		my @categories_leftover=keys %{$count{$relday}{$host}};
 1272 		die "$prog: categories leftover for $host: @categories_leftover\n"
 1273 			if @categories_leftover;
 1274 	}
 1275 	
 1276 	if (! $suppress_footer) {
 1277 		print "Program was called as: $0 @original_ARGV\n";
 1278 		print "version: $version_string\n";
 1279 		print "Elapsed time (seconds): ", time-$time_start, "\n";
 1280 		if (!&empty($memory_size_command)) {
 1281 			my $command=&process_tags($memory_size_command, {p=>$$});
 1282 			my $memory_size=qx($command);
 1283 			$memory_size =~ s/^\s+//g;
 1284 			$memory_size =~ s/\s+$//g;
 1285 			$memory_size =~ s/\s+/ /g;
 1286 			print "Memory used: $memory_size\n";
 1287 		}
 1288 		print "\n";
 1289 	}
 1290 
 1291 	close $outfh if @output_commands;
 1292 	return;
 1293 }
 1294 
 1295 sub report_mode_output_category {
 1296 	my ($relday, $host, $category)=@_;
 1297 
 1298 	{ # most of the function is a bare loop so we can use next to go to the end
 1299 		my (@keys, %values, %nodes);
 1300 
 1301 		if ($report_mode_combine_nodes) {
 1302 			foreach my $host (keys %{$count{$relday}}) {
 1303 				foreach my $key (keys %{$count{$relday}{$host}{$category}}) {
 1304 					if (!$report_mode_combine_is_partway) {
 1305 						$values{$key}+=$count{$relday}{$host}{$category}{$key};
 1306 						push @{$nodes{$key}}, $host;
 1307 					} else {
 1308 						$values{"$key ($host)"}+=
 1309 							$count{$relday}{$host}{$category}{$key};
 1310 					}
 1311 				}
 1312 				delete $count{$relday}{$host}{$category};
 1313 			}
 1314 		} else { 
 1315 			next unless defined $count{$relday}{$host}{$category};
 1316 			%values=%{$count{$relday}{$host}{$category}};
 1317 		}
 1318 
 1319 		@keys=keys %values;
 1320 
 1321 		my $filter=$categories{$category}{filter};
 1322 		$filter=$default_filter if !defined $filter && defined $default_filter;
 1323 
 1324 		my $sort=$categories{$category}{sort};
 1325 		$sort=$default_sort if !defined $sort;
 1326 
 1327 		@keys=&filter($filter, %values) if (defined $filter);
 1328 
 1329 		@keys=&sort_keys($sort, \%values, @keys);
 1330 
 1331 		next if !@keys;
 1332 
 1333 		print "\n$category:";
 1334 		print " (host $host)" if $report_mode_output_node_per_category
 1335 			&& !$report_mode_combine_nodes;
 1336 		print " ($filter)" if defined $filter && $filter ne "none";
 1337 		print "\n";
 1338 		foreach my $key (@keys) {
 1339 			if (!$report_mode_combine_nodes || !$report_mode_combine_shows_nodes) {
 1340 				printf "%-10d %s\n", $values{$key}, $key
 1341 			} else {
 1342 				printf "%-10d %s (%s)\n", $values{$key}, $key,
 1343 					join(" ", sort @{$nodes{$key}});
 1344 			}
 1345 		}
 1346 	}
 1347 	delete $count{$relday}{$host}{$category};
 1348 }
 1349 
 1350 
 1351 sub real_mode_out {
 1352 	defined(my $relday   = shift) || die "Internal error";
 1353 	defined(my $entry_tags_ref = shift) || die "Internal error";
 1354 
 1355 	return if $real_mode_bypass;
 1356 
 1357 	$entry_tags_ref->{A}=$entry_tags_ref->{R}."\n" if $keep_all_raw_logs;
 1358 	my $category=$entry_tags_ref->{c};
 1359 
 1360 	# side-effect
 1361 	if (!$real_mode_no_actions_unless_is_daemon || $daemon_mode ) {
 1362 		my $do_action=&config_check($entry_tags_ref, "do_action");
 1363 		&do_action($do_action, $entry_tags_ref) if defined $do_action;
 1364 	}
 1365 
 1366 	# output depends on mode
 1367 	return &gui_mode_out_hook($relday, $entry_tags_ref) if $gui_mode;
 1368 	return if $daemon_mode; # no output
 1369 
 1370 	# regular real mode
 1371 	my $color=&config_check($entry_tags_ref, "color");
 1372 	&color($color, 1) if $color;
 1373 	print &process_tags($real_mode_output_format, $entry_tags_ref);
 1374 	&color("normal", 1) if $color;
 1375 }
 1376 
 1377 
 1378 sub gui_mode_out_hook {
 1379 	defined(my $relday         = shift) || die "Internal error";
 1380 	defined(my $entry_tags_ref = shift) || die "Internal error";
 1381 
 1382 	my $host     = $entry_tags_ref->{'h'};
 1383 	my $count    = $entry_tags_ref->{'#'};
 1384 	my $category = $entry_tags_ref->{'c'};
 1385 	my $data     = $entry_tags_ref->{'d'};
 1386 
 1387 	return if &is_ignored($entry_tags_ref);
 1388 
 1389 	if (exists $gui_mode_state{$host}{$category}{$data}) {
 1390 		my $hashref=$gui_mode_state{$host}{$category}{$data};
 1391 		$hashref->{tags_ref}->{'#'}+=$count;
 1392 		$hashref->{tags_ref}->{A}.=$entry_tags_ref->{R}."\n"
 1393 			if $keep_all_raw_logs;
 1394 		$gui_mode_hlist->itemConfigure( $hashref->{entry}, 1, 
 1395 			-text     => $hashref->{tags_ref}->{'#'},
 1396 			);
 1397 		$gui_mode_hlist->bell if exists $hashref->{tags_ref}->{bell};
 1398 	} else {
 1399 		my $entry=$gui_mode_hlist->addchild("");
 1400 
 1401 		# store metadata in internal structures
 1402 
 1403 		my $hashref={};
 1404 		$gui_mode_state{$host}{$category}{$data}=$hashref;
 1405 		$hashref->{tags_ref}=$entry_tags_ref;
 1406 
 1407 		$hashref->{entry}=$entry;
 1408 
 1409 		# given the entry, we want to be able to get the hashref
 1410 		$gui_mode_hashref{$entry}=$entry_tags_ref;
 1411 
 1412 		# now actually draw the entry
 1413 		my $button=$gui_mode_hlist->Button(
 1414 			-bitmap  => "error",
 1415 			-command => [\&gui_mode_entry_clear, $entry],
 1416 			);
 1417 		
 1418 		$gui_mode_hlist->itemCreate($entry, 0, -itemtype => "window", 
 1419 			-widget   => $button);
 1420 
 1421 		my $i=1;
 1422 		foreach my $tag ('#', 'h', 'c', 'd') {
 1423 			$gui_mode_hlist->itemCreate($entry, $i, -itemtype => "text",
 1424 				-text => $entry_tags_ref->{$tag});
 1425 			$i++;
 1426 		}
 1427 
 1428 		$gui_mode_total++;
 1429 		$gui_mode_visible++; # assume visible until we know otherwise
 1430 		if ($entry_tags_ref->{_u}) {
 1431 		  $gui_mode_unknowns++;
 1432 		} else {
 1433 		  $gui_mode_knowns++;
 1434 		}
 1435 
 1436 		&gui_mode_find_check($entry);
 1437 		&gui_mode_color($entry);
 1438 
 1439 		$gui_mode_hlist->see($entry) if $gui_mode_follow_new;
 1440 	  
 1441 		if ($gui_mode && $entry_tags_ref->{_u} && $gui_mode_unknowns_pause_at && 
 1442 				$gui_mode_unknowns >= $gui_mode_unknowns_pause_at) {
 1443 			&gui_mode_toggle_pause;
 1444 		}
 1445 	}
 1446 }
 1447 
 1448 
 1449 # implement color for GUI mode
 1450 sub gui_mode_color {
 1451 	my ($entry, $is_refresh)=@_;
 1452 
 1453 	my $entry_tags_ref=$gui_mode_hashref{$entry};
 1454 
 1455 	my $color=&config_check($entry_tags_ref, "color") || "";
 1456 
 1457 	if (exists $entry_tags_ref->{color} && $entry_tags_ref->{color} eq $color) {
 1458 		return;
 1459 	}
 1460 
 1461 	$entry_tags_ref->{color} = $color;
 1462 	delete $entry_tags_ref->{bell};
 1463 
 1464 	if (! exists $color2style{$color}) {
 1465 		my $style=$gui_mode_hlist->ItemStyle("text");
 1466 		$color2style{$color}=$style;
 1467 		my @colors=split(/\s+/, $color);
 1468 		my @known_colors=qw/black red green yellow blue magenta cyan white/;
 1469 		my $known_colors=join("|", @known_colors);
 1470 		foreach (@colors) {
 1471 			if (m{^($known_colors)$}o) {
 1472 				$style->configure(-foreground=>$1);
 1473 			} elsif (m{^($known_colors)_bg$}o) {
 1474 				$style->configure(-background=>$1);
 1475 			} elsif ($_ eq "bell") {
 1476 				$color2bell{$color}=1;
 1477 			} elsif (exists $colors{$_}) {
 1478 				# valid for real mode, but not here, so do nothing
 1479 			} else {
 1480 				&gui_mode_user_error("Unknown color $_");
 1481 			}
 1482 		}
 1483 	}
 1484 
 1485 	my $style=$color2style{$color};
 1486 	if (exists $color2bell{$color}) {
 1487 		$entry_tags_ref->{bell}=1;
 1488 		$gui_mode_hlist->bell if !$is_refresh;
 1489 	}
 1490 
 1491 	my $columns=$gui_mode_hlist->cget('-columns');
 1492 	for (my $col=1; $col<$columns; $col++) {
 1493 		$gui_mode_hlist->itemConfigure($entry, $col, -style=>$style);
 1494 	}
 1495 }
 1496 
 1497 
 1498 sub gui_mode_popup {
 1499 	my $entry=shift;
 1500 
 1501 	warn "In gui_mode_popup\n" if $debug;
 1502 
 1503 	my $hashref=$gui_mode_hashref{$entry};
 1504 	my $host=$hashref->{h};
 1505 
 1506 	my $menu=$gui_mode_main->Menu(-tearoff=>0);
 1507 	$menu->command(
 1508 		-label => "Debug: Output hashref contents",
 1509 		-command => sub {
 1510 			print "\n\nEntry $entry\n\n";
 1511 			foreach my $key (keys %{$gui_mode_hashref{$entry}}) {
 1512 				print "$key=${$gui_mode_hashref{$entry}}{$key}\n";
 1513 			}
 1514 		}) if $debug;
 1515 	$menu->command(
 1516 		-label => "Clear this count and remove line",
 1517 		-command => [\&gui_mode_entry_clear, $entry]);
 1518 	$menu->command(
 1519 		-label => "Description of event",
 1520 		-command => [\&gui_mode_description, $entry]);
 1521 	$menu->command(
 1522 		-label => "Save event",
 1523 		-command => [\&gui_mode_save_event, $entry]);
 1524 	$menu->command(
 1525 		-label => "Print event",
 1526 		-command => [\&gui_mode_print, $entry]);
 1527 	$menu->command(
 1528 		-label => "Show first raw log entry",
 1529 		-command => [\&gui_mode_show, "%R\n", $entry]);
 1530 	$menu->command(
 1531 		-label => "Show all raw log entries",
 1532 		-command => [\&gui_mode_show, "%A", $entry],
 1533 		-state=> $keep_all_raw_logs? "normal" : "disabled");
 1534 	$menu->command(
 1535 		-label => "Configure pattern",
 1536 		-command => [\&gui_mode_configure_event_pattern, $entry],
 1537 		&gui_mode_configure_state);
 1538 	$menu->command(
 1539 		-label => "Configure event",
 1540 		-command => [\&gui_mode_event_config, $entry],
 1541 		&gui_mode_configure_state);
 1542 	my $cascade=$menu->cascade(-tearoff=>0,
 1543 		-label => "Select events with selected. . .");
 1544 	$cascade->command(-label=>"category", 
 1545 		-command => [\&gui_mode_select_like, ["c"], $entry]);
 1546 	$cascade->command(-label=>"category and data", 
 1547 		-command => [\&gui_mode_select_like, ["c", "d"], $entry]);
 1548 	$cascade->command(-label=>"host", 
 1549 		-command => [\&gui_mode_select_like, ["h"], $entry]);
 1550 	$cascade->command(-label=>"category and host", 
 1551 		-command => [\&gui_mode_select_like, ["c", "h"], $entry]);
 1552 	$cascade->command(-label=>"category, data, and host", 
 1553 		-command => [\&gui_mode_select_like, ["c", "d", "h"], $entry]);
 1554 	$cascade=$menu->cascade(-tearoff=>0,
 1555 		-label => "Ignore events with selected. . .",
 1556 		&gui_mode_configure_state);
 1557 	$cascade->command(-label=>"category", 
 1558 		-command => [\&gui_mode_ignore, ["c"], $entry]);
 1559 	$cascade->command(-label=>"category and data", 
 1560 		-command => [\&gui_mode_ignore, ["c", "d"], $entry]);
 1561 	$cascade->command(-label=>"host", 
 1562 		-command => [\&gui_mode_ignore, ["h"], $entry]);
 1563 	$cascade->command(-label=>"category and host", 
 1564 		-command => [\&gui_mode_ignore, ["c", "h"], $entry]);
 1565 	$cascade->command(-label=>"category, data, and host", 
 1566 		-command => [\&gui_mode_ignore, ["c", "d", "h"], $entry]);
 1567 	$menu->command(
 1568 		-label => "Login to $host", 
 1569 		-command => [\&gui_mode_login, $entry]);
 1570 	my $c=$menu->cascade(-label => "Do action", -tearoff=>0);
 1571 	foreach my $action (sort keys %actions) {
 1572 		$c->command(-label => $action, 
 1573 		-command => [\&gui_mode_do_action, $action, $entry]);
 1574 	}
 1575 	$menu->post($gui_mode_main->pointerxy);
 1576 	$menu->grab;
 1577 }
 1578 
 1579 
 1580 sub gui_mode_entry_clear {
 1581 	foreach my $entry (@_) {
 1582 
 1583 		my $host=$gui_mode_hashref{$entry}{h};
 1584 		my $category=$gui_mode_hashref{$entry}{c};
 1585 		my $data=$gui_mode_hashref{$entry}{d};
 1586 
 1587 		die "$prog: internal error: no gui_mode_state for $host, $category, $data"
 1588 			if !exists $gui_mode_state{$host}{$category}{$data};
 1589 
 1590 		if ($gui_mode_hlist->info("hidden", $entry)) {
 1591 		  $gui_mode_hidden--;
 1592 		} else {
 1593 		  $gui_mode_visible--;
 1594 		}
 1595 		$gui_mode_hlist->delete("entry", $entry);
 1596 		$gui_mode_total--;
 1597 		if ($gui_mode_hashref{$entry}{_u}) {
 1598 		  $gui_mode_unknowns--;
 1599 		} else {
 1600 		  $gui_mode_knowns--;
 1601 		}
 1602 		delete $gui_mode_state{$host}{$category}{$data};
 1603 		delete $gui_mode_hashref{$entry};
 1604 	}
 1605 	&gui_mode_select_update;
 1606 }
 1607 
 1608 
 1609 sub gui_mode_do_action {
 1610 	my $action=shift;
 1611 
 1612 	return &gui_mode_user_error("No such action: $action")
 1613 		if ! exists $actions{$action};
 1614 
 1615 	foreach my $entry (@_) {
 1616 		&do_action($action, $gui_mode_hashref{$entry}, 1);
 1617 	}
 1618 }
 1619 
 1620 
 1621 sub gui_mode_ignore {
 1622 	my $ignore_what_ref=shift;
 1623 	my @events=@_;
 1624 
 1625 	foreach my $event (@events) {
 1626 		my $hashref=$gui_mode_hashref{$event};
 1627 		my $eventref={};
 1628 		$eventref->{val}{is_local}=1;
 1629 		$eventref->{val}{priority}="IGNORE";
 1630 		push @event_config, $eventref;
 1631 		foreach my $what (@$ignore_what_ref) {
 1632 			$eventref->{$what}=$hashref->{$what};
 1633 		}
 1634 	}
 1635 	&gui_mode_event_config_apply;
 1636 }
 1637 
 1638 
 1639 sub gui_mode_select_like {
 1640 	my $select_what_ref=shift;
 1641 	my @events=@_;
 1642 
 1643 	my @entries=$gui_mode_hlist->info("children");
 1644 	return if !@entries;
 1645 
 1646 	foreach my $event (@events) {
 1647 		my $hashref=$gui_mode_hashref{$event};
 1648 		ENTRY:
 1649 		foreach my $entry (@entries) {
 1650 			my $entry_hashref=$gui_mode_hashref{$entry};
 1651 			foreach my $what (@$select_what_ref) {
 1652 				if (&empty($hashref->{$what}) or
 1653 							&empty($entry_hashref->{$what}) or
 1654 							$hashref->{$what} ne $entry_hashref->{$what}) {
 1655 					next ENTRY;
 1656 				}
 1657 			}
 1658 			$gui_mode_hlist->selectionSet($entry, $entry);
 1659 		}
 1660 	}
 1661 
 1662 	&gui_mode_select_update;
 1663 
 1664 	return;
 1665 }
 1666 
 1667 
 1668 # apply changes to the event tree to the live window
 1669 sub gui_mode_event_config_apply {
 1670 	&build_event_tree;
 1671 	&gui_mode_config_dirty;
 1672 	foreach my $entry ($gui_mode_hlist->info("children")) {
 1673 		my $hashref=$gui_mode_hashref{$entry};
 1674 		&gui_mode_color($entry, 1);
 1675 		&gui_mode_entry_clear($entry) if &is_ignored($hashref);
 1676 	}
 1677 }
 1678 
 1679 sub config_check {
 1680 	my $hashref=shift;
 1681 	my $key=shift;
 1682 
 1683 	my $ret;
 1684 
 1685 	$ret=&event_config_helper($hashref, $key, \%event_tree) if %event_tree;
 1686 	return $ret if defined $ret;
 1687 
 1688 	my $destref=$dests{$hashref->{_t}}{$hashref->{_p}}[$hashref->{_w}]
 1689 		if exists $hashref->{_p};
 1690 	$ret=$destref->{$key} if defined $destref;
 1691 	return $ret if defined $ret;
 1692 
 1693 	$ret=$categories{$hashref->{c}}{$key};
 1694 	return $ret if defined $ret;
 1695 
 1696 	return undef;
 1697 }
 1698 
 1699 
 1700 sub event_config_helper {
 1701 	my $hashref=shift;
 1702 	my $key=shift;
 1703 	my $posref=shift;
 1704 
 1705 	return $posref->{val}{$key}
 1706 		if exists $posref->{val} && exists $posref->{val}{$key};
 1707 
 1708 	foreach my $tag (sort keys %{$posref}) {
 1709 		next if !exists $hashref->{$tag};
 1710 		my $val=$hashref->{$tag};
 1711 		next if !exists $posref->{$tag}{$val};
 1712 		my $ret=&event_config_helper($hashref, $key, $posref->{$tag}{$val});
 1713 		return $ret if defined $ret;
 1714 	}
 1715 
 1716 	return undef;
 1717 }
 1718 
 1719 
 1720 sub is_ignored {
 1721 	my $hashref=shift;
 1722 
 1723 	my $priority=&config_check($hashref, "priority");
 1724 	return 0 if !defined $priority or uc $priority ne "IGNORE";
 1725 	return 1;
 1726 }
 1727 
 1728 
 1729 sub gui_mode_window {
 1730 	my $name=shift;
 1731 	my $widget=shift;
 1732 
 1733 	$name=~s{\b(\w)}{\U$1}g;
 1734 
 1735 	my $window=$widget->Toplevel;
 1736 	$window->bind("<$gui_mode_modifier-w>"=>sub{$window->destroy});
 1737 	$window->bind("<Escape>"=>sub{$window->destroy});
 1738 	$window->title("$appname / $name");
 1739 
 1740 	my $font=$gui_mode_main->Font(-size=>24);
 1741 
 1742 	my $frame=$window->Frame(@frameargs); $frame->pack(@Ptef, -fill=>"x");
 1743 
 1744 	$frame->Label(-text=>$name, -font=>$font)->pack(@Ptnf, -anchor=>"n");
 1745 
 1746 	return $window;
 1747 }
 1748 
 1749 
 1750 sub gui_mode_show {
 1751 	my $format=shift;
 1752 	my @events=@_;
 1753 
 1754 	my $window=&gui_mode_window("show logs", $gui_mode_main);
 1755 
 1756 	my $text=$window->Scrolled(qw/ROText/); $text->pack(@Ptef);
 1757 
 1758 	foreach my $event (@events) {
 1759 		$text->insert("end",&process_tags($format, $gui_mode_hashref{$event}));
 1760 	}
 1761 
 1762 	$window->Button(-text=>"OK", -command=>sub{$window->destroy})
 1763 		->pack(-side=>"bottom");
 1764 }
 1765 
 1766 
 1767 sub gui_mode_widget_format {
 1768 	my $window=shift;
 1769 	my $what=shift;
 1770 	my $varhashref=shift;
 1771 	my $varname=shift;
 1772 
 1773 	my $frame=$window->Frame(@frameargs);
 1774 
 1775 	# this will be an invisible frame on purpose.  In particular, we want to
 1776 	# use gui_mode_variable_config to get the varref and update varhashref,
 1777 	# but we don't need the actual widget
 1778 	my $varref=&gui_mode_variable_config($frame, $varhashref, $varname);
 1779 
 1780 	$frame=$window->Frame(@frameargs);
 1781 
 1782 	$frame->Label(-text=>"$what in what format?")->pack(@Ptnf);
 1783 
 1784 
 1785 	$frame->Radiobutton(-text=>"$what in category, count, data format",
 1786 		-variable=>$varref, -value=>'%c\n%#\t%d\n')->pack(@Ptnf);
 1787 	$frame->Radiobutton(-text=>"$what first raw log entry per event",
 1788 		-variable=>$varref, -value=>'%R\n')->pack(@Ptnf);
 1789 	$frame->Radiobutton(-text=>"$what all raw log entries per event",
 1790 		-variable=>$varref, -value=>'%A', 
 1791 		-state=> $keep_all_raw_logs? "normal" : "disabled",
 1792 		)->pack(@Ptnf);
 1793 	$frame->Label(-text=>"$what raw format:")->pack(@Plnf, -anchor=>"w");
 1794 	$frame->Entry(-textvariable=>$varref)->pack(@Plef, -anchor=>"w", 
 1795 		-fill=>"x");
 1796 
 1797 	return $frame;
 1798 }
 1799 
 1800 
 1801 sub gui_mode_widget_selected {
 1802 	my $window=shift;
 1803 	my $what=shift;
 1804 	my $varhashref=shift;
 1805 	my $varname=shift;
 1806 
 1807 	my $frame=$window->Frame(@frameargs);
 1808 	
 1809 	# this will be an invisible frame on purpose.  In particular, we want to
 1810 	# use gui_mode_variable_config to get the varref and update varhashref,
 1811 	# but we don't need the actual widget
 1812 	my $varref=&gui_mode_variable_config($frame, $varhashref, $varname);
 1813 
 1814 	$frame=$window->Frame(@frameargs);
 1815 
 1816 	$frame->Label(-text=>"$what selected entries or all entries?")->pack(@Ptnf);
 1817 
 1818 	$frame->Radiobutton(-text=>"$what selected events",
 1819 		-variable=>$varref, -value=>0)->pack(@Ptni);
 1820 	$frame->Radiobutton(-text=>"$what all events",
 1821 		-variable=>$varref, -value=>1)->pack(@Ptni);
 1822 
 1823 	return $frame;
 1824 }
 1825 
 1826 
 1827 sub gui_mode_widget_do_action {
 1828 	my $window=shift;
 1829 	my $varref=shift;
 1830 
 1831 	my $frame=$window->Frame(@frameargs);
 1832 	$frame->Label(-text=>"What action do you want this to take?")->pack(@Ptef);
 1833 	$frame->Radiobutton(-text=>"No action",
 1834 		-val=>"", -variable=>$varref)->pack(@Ptni);
 1835 	$frame->Label(-text=>"Or pick one:"
 1836 		)->pack(@Plni);
 1837 	$frame->BrowseEntry(-variable=>$varref, -choices=>[keys %actions],
 1838 		-state=>"readonly",
 1839 		)->pack(@Plni);
 1840 
 1841 	return $frame;
 1842 }
 1843 
 1844 
 1845 sub gui_mode_widget_description {
 1846 	my $window=shift;
 1847 	my $varref=shift;
 1848 
 1849 	my $frame=$window->Frame(@frameargs);
 1850 
 1851 	$frame->Label(-text=>"What description would you give this?")->pack(@Ptef);
 1852 
 1853 	$frame->Entry(-textvariable=>$varref)->pack(@Ptef);
 1854 
 1855 	return $frame;
 1856 }
 1857 
 1858 
 1859 sub gui_mode_widget_priority {
 1860 	my $window=shift;
 1861 	my $varref=shift;
 1862 
 1863 	my $frame=$window->Frame(@frameargs);
 1864 	$frame->Label(-text=>"What priority should this be given?")->pack(@Ptef);
 1865 	$frame->Radiobutton(-text=>"No special priority", 
 1866 		-val=>"", -variable=>$varref)->pack(@Ptni);
 1867 	foreach my $priority (reverse @priorities) {
 1868 		$frame->Radiobutton(-text=>$priority_name{$priority}, 
 1869 			-val=>$priority, -variable=>$varref)->pack(@Ptni);
 1870 	}
 1871 	return $frame;
 1872 }
 1873 
 1874 
 1875 sub gui_mode_widget_color {
 1876 	my $window=shift;
 1877 	my $varref=shift;
 1878 
 1879 	my %is_selected;
 1880 	my ($foreground, $background);
 1881 
 1882 	my $config_sub = sub {
 1883 		$$varref=join(" ", grep $is_selected{$_}, sort keys %is_selected);
 1884 		$$varref.=" $foreground" if !&empty($foreground);
 1885 		$$varref.=" $background" if !&empty($background);
 1886 
 1887 		$$varref=~s{^\s+}{}; $$varref=~s{\s\s+}{ }g;
 1888 	};
 1889 	my @c=(-command=>$config_sub);
 1890 
 1891 	my $frame=$window->Frame(@frameargs);
 1892 
 1893 	my @colors=sort keys %colors;
 1894 	my @real_colors=grep(exists $colors{$_."_bg"}, @colors);
 1895 	my @non_colors=grep(!m{_bg$} && !exists $colors{$_."_bg"}, @colors);
 1896 
 1897 	my %pre_selected;
 1898 	@pre_selected{split /\s+/, $$varref}=undef if defined $$varref;
 1899 	$is_selected{$_}=1 foreach (grep exists $pre_selected{$_}, @non_colors);
 1900 	$foreground=$_ foreach (grep exists $pre_selected{$_}, @real_colors);
 1901 	$background=$_."_bg" foreach (grep exists $pre_selected{$_."_bg"}, 
 1902 		@real_colors);
 1903 	
 1904 	$frame->Label(-text=>"What color attributes should this have?")
 1905 		->pack(@Ptef);
 1906 
 1907 	my @frame;
 1908 	for (my $i=0; $i<3; $i++) {
 1909 		$frame[$i]=$frame->Frame;
 1910 		$frame[$i]->pack(@Plef);
 1911 	}
 1912 
 1913 	foreach my $color (@non_colors) {
 1914 		$frame[0]->Checkbutton(-text=>$color, 
 1915 			-variable=>\$is_selected{$color}, @c,
 1916 			)->pack(@Ptni);
 1917 	}
 1918 
 1919 	$frame[1]->Radiobutton(-text=>"No foreground",
 1920 		-variable=>\$foreground, -val=>"", @c,
 1921 		)->pack(@Ptni);
 1922 	$frame[2]->Radiobutton(-text=>"No background",
 1923  		-variable=>\$background, -val=>"", @c,
 1924 		)->pack(@Ptni);
 1925 
 1926 	foreach my $color (@real_colors) {
 1927 		$frame[1]->Radiobutton(-text=>"$color foreground",
 1928 			-variable=>\$foreground, -val=>$color, @c
 1929 			)->pack(@Ptni);
 1930 		$frame[2]->Radiobutton(-text=>"$color background",
 1931  			-variable=>\$background, -val=>$color."_bg", @c
 1932 			)->pack(@Ptni);
 1933 	}
 1934 	return $frame;
 1935 }
 1936 
 1937 
 1938 sub gui_mode_properties {
 1939 	my ($widget, $varref)=@_;
 1940 
 1941 	my %localhash=%$varref;
 1942 
 1943 	my $window=&gui_mode_window("properties", $widget);
 1944 
 1945 	my $frame;
 1946 	$frame=$window->Frame; $frame->pack(@Ptef);
 1947 
 1948 	&gui_mode_widget_priority($frame, \$localhash{priority})->pack(@Plef);
 1949 	&gui_mode_widget_color($frame, \$localhash{color})->pack(@Plef);
 1950 
 1951 	$frame=$window->Frame; $frame->pack(@Ptef);
 1952 
 1953 	&gui_mode_widget_do_action($frame, \$localhash{do_action})
 1954 		->pack(@Plef);
 1955 	&gui_mode_widget_description($frame, \$localhash{description})
 1956 		->pack(@Plef);
 1957 
 1958 	my $ok_sub = sub {
 1959 		foreach (qw(do_action priority color description)) {
 1960 			if (!&empty($localhash{$_})) {
 1961 				$varref->{$_}=$localhash{$_};
 1962 			} else {
 1963 				delete $varref->{$_};
 1964 			}
 1965 		}
 1966 		$window->destroy;
 1967 	};
 1968 
 1969 	&gui_mode_widget_actions($window, OK => $ok_sub)->pack(-side=>"bottom");
 1970 
 1971 	$window->grab;
 1972 	$window->waitWindow;
 1973 }
 1974 
 1975 
 1976 sub gui_mode_widget_actions {
 1977 	my $window=shift;
 1978 	my @actions=@_;
 1979 
 1980 	my $frame=$window->Frame(@frameargs); $frame->pack(@Ptef);
 1981 
 1982 	my $is_first=1;
 1983 	while (@actions) {
 1984 		my $what=shift @actions;
 1985 		my $sub=shift @actions;
 1986 
 1987 		my $button=$frame->Button(-text=>$what, -command => $sub);
 1988 		if ($is_first) {
 1989 			$button->configure(-default=>"active");
 1990 			$window->bind("<Return>", $sub);
 1991 		}
 1992 		$button->pack(-side=>"left");
 1993 
 1994 		$is_first=0;
 1995 	}
 1996 
 1997 	$frame->Button(-text=>"Cancel", -command => sub { $window->destroy})
 1998 		->pack(-side=>"right");
 1999 	return $frame;
 2000 }
 2001 
 2002 sub gui_mode_print {
 2003 	my @events=@_;
 2004 
 2005 	my $window=&gui_mode_window("print", $gui_mode_main);
 2006 
 2007 	my $varhashref={};
 2008 
 2009 	my $what="Print";
 2010 
 2011 	# print command
 2012 	my $frame=$window->Frame(@frameargs); $frame->pack(@Ptef);
 2013 
 2014 	&gui_mode_variable_config($frame, $varhashref, "print_command");
 2015 
 2016 	# print all/selected
 2017 	&gui_mode_widget_selected($window, $what, $varhashref, "gui_mode_print_all")
 2018 		->pack(@Ptef, -fill=>"x");
 2019 	
 2020 	# print format
 2021 	&gui_mode_widget_format($window, $what, $varhashref, "print_format")
 2022 		->pack(@Ptef, -fill=>"x");
 2023 
 2024 	# actions
 2025 	&gui_mode_widget_actions($window, $what, sub { 
 2026 		&gui_mode_variable_done($varhashref);
 2027 		&gui_mode_print_helper($window, @events)})
 2028 		->pack(-side=>"bottom");
 2029 }
 2030 
 2031 
 2032 sub gui_mode_print_helper {
 2033 	my $window=shift;
 2034 	my @entries=@_;
 2035 
 2036 	# we do this here instead of in the original menu call because the 
 2037 	# selection may have changed since the original menu was made
 2038 	@entries=$gui_mode_hlist->selectionGet if !@entries;
 2039 
 2040 	if (!defined $print_command or $print_command eq "") {
 2041 		&gui_mode_user_error("No print command specified");
 2042 		return;
 2043 	} elsif (!$gui_mode_print_all && !@entries) {
 2044 		&gui_mode_user_error("You said to print selected ".
 2045 			"events, but none were selected");
 2046 		return;
 2047 	}
 2048 
 2049 	my $printfd=new FileHandle("|$print_command");
 2050 	if (!$printfd) {
 2051 		&gui_mode_user_error("$prog: run $print_command: $!\n");
 2052 		return;
 2053 	}
 2054 
 2055 	@entries=$gui_mode_hlist->info("children") if $gui_mode_print_all;
 2056 
 2057 	foreach my $entry (@entries) {
 2058 		my $hashref=$gui_mode_hashref{$entry};
 2059 		if (!defined $hashref) {
 2060 			warn "No hashref for entry $entry, class ".$entry->Class."\n";
 2061 			next;
 2062 		}
 2063 		print $printfd &process_tags($print_format, $hashref);
 2064 	}
 2065 	close $printfd;
 2066 	$window->destroy;
 2067 	&gui_mode_config_dirty;
 2068 }
 2069 
 2070 
 2071 sub gui_mode_pattern_test {
 2072 	my ($line, $logtyperef, $patternref, $destsref, $match_widgetref,
 2073 		$match_vars_ref)=@_;
 2074 	
 2075 	my $problem;
 2076 
 2077 	{ # make this a bare loop so we can use last
 2078 		$problem="No logtype" if &empty($$logtyperef);
 2079 		last if !&empty($problem);
 2080 
 2081 		$problem="No pattern" if &empty($$patternref);
 2082 		last if !&empty($problem);
 2083 
 2084 		$problem=&gui_mode_pattern_helper_test($line, $patternref,
 2085 			$match_vars_ref);
 2086 		last if !&empty($problem);
 2087 
 2088 		$problem=&dests_test($line, $logtyperef, $patternref, $destsref);
 2089 	}
 2090 
 2091 	if (!&empty($problem)) {
 2092 		$$match_widgetref->configure(-bg=>"red", -text=>$problem);
 2093 	} else {
 2094 		$$match_widgetref->configure(-bg=>"green", -text=>"OK");
 2095 	}
 2096 }
 2097 
 2098 sub gui_mode_pattern_helper_test {
 2099 	my ($line, $patternref, $match_vars_ref)=@_;
 2100 	
 2101 	my $match_test=&pattern_test($$patternref, defined $line?$line:"");
 2102 
 2103 	if ($match_test==0) {
 2104 		# all is good
 2105 	} elsif ($match_test==1) {
 2106 		return "Pattern does not match" if defined $line;
 2107 		return "";
 2108 	} elsif ($match_test==-1) {
 2109 		return "Pattern is bad";
 2110 	} else {
 2111 		die "$prog: unknown match_test: $match_test\n";
 2112 	}
 2113 
 2114 	# display match variables
 2115 	my $match_vars="";
 2116 	for (my $i=1; $i<=$#match_end; $i++) {
 2117 		if (defined $match_end[$i]) {
 2118 			$match_vars.="\$$i = ".
 2119 				substr($line, $match_start[$i], 
 2120 					$match_end[$i]-$match_start[$i])."\n";
 2121 		}
 2122 	}
 2123 	debug "match vars ($#match_end):\n$match_vars";
 2124 	$$match_vars_ref=$match_vars;
 2125 	return "";
 2126 }
 2127 
 2128 
 2129 sub gui_mode_configure_event_pattern {
 2130 	my @entries=@_;
 2131 
 2132 	my ($hashref, $logtype, $pattern, $line);
 2133 
 2134 	if (@entries) {
 2135 		&gui_mode_user_error("Configuring pattern for first selected event")
 2136 			if @entries>1;
 2137 		my $entry=shift @entries; # entries can contain at most one item
 2138 		$hashref=$gui_mode_hashref{$entry};
 2139 	}
 2140 
 2141 	if ($hashref) {
 2142 		$logtype=$hashref->{_t} if !&empty($hashref->{_t});
 2143 		$line   =$hashref->{_l} if !&empty($hashref->{_l});
 2144 		$pattern=$hashref->{_p} if !&empty($hashref->{_p});
 2145 	}
 2146 
 2147 	&gui_mode_configure_pattern($logtype, $pattern, $line);
 2148 }
 2149 
 2150 
 2151 sub gui_mode_configure_pattern {
 2152 	my ($oldlogtype, $oldpattern, $line)=@_;
 2153 
 2154 	my ($pattern, @my_dests, $logtype, $match_vars, $is_local);
 2155 	my $window;
 2156 	my ($pattern_widget, $match_widget, $frame, $dests_frame);
 2157 	my ($suggest_sub, $validate_pattern_sub, $validate_logtype_sub);
 2158 
 2159 	$logtype=$oldlogtype; # init
 2160 
 2161 	if (!&empty($oldpattern)) {
 2162 		$pattern=$oldpattern;
 2163 		$is_local=$is_local{pattern}{$logtype}{$pattern};
 2164 		my $destsref=$dests{$logtype}{$pattern};
 2165 		for (my $which=0; $which<@$destsref; $which++) {
 2166 			my $destref=$destsref->[$which];
 2167 			foreach my $key (keys %$destref) {
 2168 				my $val=$destref->{$key};
 2169 				if ($key eq "dest") {
 2170 					if ($val=~m{^(UNIQUE|CATEGORY)\s+(.*)}) {
 2171 						$my_dests[$which]{dest_type}=$1;
 2172 						$my_dests[$which]{dest_category}=$2;
 2173 					} elsif ($val=~m{^(SKIP|LAST)$}) {
 2174 						$my_dests[$which]{dest_type}=$1;
 2175 					} else {
 2176 						$my_dests[$which]{dest_type}="CATEGORY";
 2177 						$my_dests[$which]{dest_category}=$val;
 2178 					}
 2179 					next;
 2180 				} else {
 2181 					$my_dests[$which]{$key}=$val;
 2182 				}
 2183 			}
 2184 		}
 2185 	} elsif (defined $line && length $line) {
 2186 		$pattern=$line;
 2187 		$pattern=~s{\s*$}{};
 2188 		$pattern=quotemeta($pattern);
 2189 		# dequote stuff that doesn't need quoting:
 2190 		$pattern=~s{\\( |:)}{$1}ig;
 2191 		@my_dests=({}); # init
 2192 		$is_local=1; # definitely, since we just made it
 2193 	} else {
 2194 		$pattern="";
 2195 		@my_dests=({}); # init
 2196 		$is_local=1; # definitely, since we just made it
 2197 	}
 2198 
 2199 	$suggest_sub=sub {
 2200 		if (!$pattern_widget->selectionPresent) {
 2201 			&gui_mode_user_error("Highlight a variable part of the pattern");
 2202 			return;
 2203 		}
 2204 
 2205 		# find the user selection
 2206 		my $first=$pattern_widget->index("sel.first");
 2207 		my $last =$pattern_widget->index("sel.last");
 2208 		warn "selection first=$first last=$last\n" if $debug;
 2209 		my $pattern_before=$pattern_widget->get;
 2210 		my $selection=substr($pattern_before, $first, $last-$first);
 2211 		$selection=~s{\\(.)}{$1}g; # dequote
 2212 		warn "selection=$selection\n" if $debug;
 2213 
 2214 		# find canned subpatterns (AKA pats) that match the user selection
 2215 		my @relevant_pats;
 2216 		foreach my $pat (sort keys %pat) {
 2217 			if ($selection=~m{^$pat{$pat}$}) {
 2218 				push @relevant_pats, $pat;
 2219 			}
 2220 		}
 2221 
 2222 		# let the user select a pattern
 2223 		my $selected_pat;
 2224 		my $menu=$window->Menu(-tearoff=>0);
 2225 		foreach my $pat (@relevant_pats) {
 2226 			$menu->radiobutton(-label=>$pat, -variable=>\$selected_pat);
 2227 		}
 2228 		$menu->separator;
 2229 		$menu->radiobutton(-label=>"None applicable, edit manually",
 2230 			-variable=>\$selected_pat, -value=>"");
 2231 		$menu->post($window->pointerxy);
 2232 		$menu->grab;
 2233 		$menu->waitVariable(\$selected_pat);
 2234 		warn "selected pattern = $selected_pat\n" if $selected_pat && $debug;
 2235 
 2236 		# replace the user selection if applicable
 2237 		return if !defined $selected_pat or !length $selected_pat;
 2238 		die "Unknown pat selected\n" if ! exists $pat{$selected_pat};
 2239 		$pattern_widget->selectionClear;
 2240 		$pattern_widget->delete($first, $last);
 2241 		$pattern_widget->insert($first, "(\$pat{$selected_pat})");
 2242 	};
 2243 
 2244 	$validate_pattern_sub=sub{
 2245 			my ($newpattern, $chars, $currentpattern, $index, $action)=@_;
 2246 			&gui_mode_pattern_test($line, \$logtype, \$newpattern, 
 2247 				\@my_dests, \$match_widget, \$match_vars);
 2248 			return 1;
 2249 		};
 2250 
 2251 	$validate_logtype_sub=sub{
 2252 			my ($newlogtype, $chars, $currentpattern, $index, $action)=@_;
 2253 			&gui_mode_pattern_test($line, \$newlogtype, \$pattern, 
 2254 				\@my_dests, \$match_widget, \$match_vars);
 2255 			return 1;
 2256 		};
 2257 
 2258 	my $test_sub=sub { 
 2259 		&gui_mode_pattern_test($line, \$logtype, \$pattern, 
 2260 			\@my_dests, \$match_widget, \$match_vars);
 2261 	};
 2262 
 2263 	# actually build window and widgets
 2264 	$window=&gui_mode_window("Configure Pattern", $gui_mode_main);
 2265 
 2266 	# Frame for match status widget
 2267 	$frame=$window->Frame(@frameargs);
 2268 	$frame->pack(@Ptxf);
 2269 
 2270 	$frame->Label(-text=>"Pattern status:")->pack(@Plnf);
 2271 
 2272 	$match_widget=$frame->Label;
 2273 	$match_widget->pack(@Plnf);
 2274 
 2275 	# logtype selector
 2276 	$window->BrowseEntry(-label=>"Logtype",
 2277 		-textvariable=>\$logtype,
 2278 		-choices=>\@log_type_list,
 2279 		-validate=>"key", -validatecommand=>$validate_logtype_sub,
 2280 		-state=>defined($oldlogtype)?"disabled":"readonly",
 2281 		)->pack(@Ptxf);
 2282 
 2283 	$window->Checkbutton(
 2284 		-text=>"save this pattern and dests when saving local changes",
 2285 		-variable=>\$is_local,
 2286 		)->pack(@Ptxf);
 2287 
 2288 	# frame for line, if applicable
 2289 	if (defined $line && length $line) {
 2290 		$frame=$window->Frame(@frameargs);
 2291 		$frame->pack(@Ptxf);
 2292 		$frame->Scrolled("Label", -text=>"Line to match: $line")->pack(@Ptxf);
 2293 	}
 2294 
 2295 	# frame: suggest
 2296 	$frame=$window->Frame(@frameargs);
 2297 	$frame->pack(@Ptxf);
 2298 
 2299 	$frame->Button(-text=>"Suggest Subpattern. . .", -command=>$suggest_sub,
 2300 		)->pack(@Plnf);
 2301 
 2302 	# frame: pattern
 2303 	$frame=$window->Frame(@frameargs);
 2304 	$frame->pack(@Ptxf);
 2305 
 2306 	$frame->Label(-text=>"Pattern:")->pack(@Plnf);
 2307 
 2308 	$pattern_widget=$frame->Scrolled("Entry", -textvariable=>\$pattern,
 2309 		-width=>$gui_mode_field_width{pattern},
 2310 		-validate=>"key", -validatecommand=>$validate_pattern_sub,
 2311 		);
 2312 	$pattern_widget->pack(@Plxf);
 2313 
 2314 	# frame: unknown showing buttons
 2315 	$frame=$window->Frame(@frameargs);
 2316 	$frame->pack(@Ptnf);
 2317 
 2318 	$frame->Button(-text=>"Show All Unknowns And Matches. . .", -command=>
 2319 		sub{&gui_mode_unknowns(\$logtype, \$pattern, 1)})->pack(@Plnf);
 2320 
 2321 	$frame->Button(-text=>"Show Matching Unknowns. . .", -command=>
 2322 		sub{&gui_mode_unknowns(\$logtype, \$pattern)})->pack(@Plnf);
 2323 
 2324 	$frame->Button(-text=>"Show All Unknowns. . .", -command=>
 2325 		sub{&gui_mode_unknowns(\$logtype)})->pack(@Plnf);
 2326 
 2327 	# frame: match variables
 2328 	$frame=$window->Frame(@frameargs);
 2329 	$frame->pack(@Ptef) if !&empty($line);
 2330 
 2331 	$frame->Label(-text=>"Match Variables:")->pack(@Ptnf);
 2332 	$frame->Label(-textvariable=>\$match_vars)->pack(@Ptnf);
 2333 
 2334 	# frame for dests
 2335 	$frame=$window->Frame(@frameargs);
 2336 	$frame->pack(@Ptef);
 2337 
 2338 	$frame->Label(-text=>"Destinations")->pack(@Ptxf);
 2339 
 2340 	&gui_mode_dests_refresh(\@my_dests, \$dests_frame, \$frame, 
 2341 		$test_sub);
 2342 
 2343 	&gui_mode_widget_actions($window,
 2344 		Done=>[\&gui_mode_pattern_done, $window, \$oldlogtype, \$logtype,
 2345 			\$oldpattern, \$pattern, \@my_dests, \$is_local, $line],
 2346 		)->pack(-side=>"bottom");
 2347 }
 2348 
 2349 
 2350 sub gui_mode_pattern_done {
 2351 	my ($window, $oldlogtyperef, $logtyperef, $oldpatternref, $patternref, 
 2352 		$gui_destsref, $is_localref, $line)=@_;
 2353 	my $oldlogtype=$$oldlogtyperef;
 2354 	my $logtype=$$logtyperef;
 2355 	my $oldpattern=$$oldpatternref;
 2356 	my $pattern=$$patternref;
 2357 	my $is_local=$$is_localref;
 2358 
 2359 	# sanity check for all sorts of broken things
 2360 	return &gui_mode_user_error("No Logtype Selected") 
 2361 		if !defined $logtype or !length $logtype;
 2362 	return &gui_mode_user_error("Unknown logtype $logtype")
 2363 		if !set_is_member($logtype, \@log_type_list);
 2364 	return &gui_mode_user_error("Logtype does not match original line logtype")
 2365 		if defined $logtype && defined $oldlogtype && $logtype ne $oldlogtype;
 2366 	
 2367 	# validate the pattern
 2368 	return &gui_mode_user_error("No pattern specified")
 2369 		if !defined $pattern or !length $pattern;
 2370 	my $match_test=&pattern_test($pattern, defined $line?$line:"");
 2371 	if ($match_test==-1) {
 2372 		return &gui_mode_user_error("Pattern is bad!");
 2373 	} elsif (defined $line && $match_test==1) {
 2374 		return &gui_mode_user_error("Pattern No Longer Matches");
 2375 	}
 2376 
 2377 	# validate the dests
 2378 	my $dests_test=&dests_test($line, $logtyperef, $patternref, $gui_destsref);
 2379 	return &gui_mode_user_error($dests_test) if !&empty($dests_test);
 2380 
 2381 	# convert the GUI destsref to a real destsref, ie. merging dest_type and
 2382 	# dest_category, stripping out useless stuff, etc.
 2383 	my $real_destsref=&gui_dests_to_real_dests($gui_destsref);
 2384 	return &gui_mode_user_error("Unspecified destination error")
 2385 		if !defined $real_destsref;
 2386 
 2387 	# apply new pattern to existing unknowns
 2388 	my $eval_string=&build_pattern_string($logtype, $pattern, 
 2389 		$real_destsref, 1);
 2390 	debug "eval_string: \n$eval_string\n";
 2391 	my @matches_to_clear;
 2392 	foreach my $entry ($gui_mode_hlist->info("children")) {
 2393 		my $hashref=$gui_mode_hashref{$entry};
 2394 		next if not $hashref->{_u}; # if not an unknown, skip
 2395 		my $logtype =$hashref->{_t};
 2396 		my $line    =$hashref->{_l};
 2397 		my $raw_line=$hashref->{R};
 2398 		my $host    =$hashref->{h};
 2399 		$multiplier{__INTERNAL}=$hashref->{'#'};
 2400 		local $_    =$line;
 2401 		next if $$logtyperef ne $logtype; # if it doesn't match, skip
 2402 		next if &pattern_test($pattern, $line)!=0; # if it doesn't match, skip
 2403 
 2404 		eval "$eval_string";
 2405 		debug "error: $@" if $@;
 2406 		return &gui_mode_user_error("got error from destinations") if $@;
 2407 
 2408 		push @matches_to_clear, $entry;
 2409 	}
 2410 	&gui_mode_entry_clear(@matches_to_clear); # clear any relevant entries
 2411 
 2412 	# delete old pattern, if necessary
 2413 	if (defined $oldpattern && length $oldpattern && 
 2414 			defined $oldlogtype && length $oldlogtype) {
 2415 		debug "deleting old pattern";
 2416 		return &gui_mode_user_error("Cannot find old pattern; already deleted?")
 2417 			if ! &set_is_member($oldpattern, $patterns{$oldlogtype});
 2418 		return &gui_mode_user_error("Cannot find old dest; already deleted?")
 2419 			if ! exists $dests{$oldlogtype}{$oldpattern};
 2420 		$patterns{$oldlogtype}=[&set_difference($patterns{$oldlogtype},
 2421 			[$oldpattern])];
 2422 		delete $dests{$oldlogtype}{$oldpattern};
 2423 	}
 2424 
 2425 	# add new pattern
 2426 	unshift @{$patterns{$logtype}}, $pattern;
 2427 	$dests{$logtype}{$pattern}=$real_destsref;
 2428 
 2429 	# rebuild internal data structures
 2430 	($evals, $filename_pats)=&build_log_stuff;
 2431 	&run_evals;
 2432 
 2433 	# save is_local state
 2434 	$is_local{pattern}{$logtype}{$pattern}=$is_local;
 2435 
 2436 	# if we got this far, all is well
 2437 	$window->destroy;
 2438 	&gui_mode_config_dirty;
 2439 	$gui_mode_types_redone=1;
 2440 }
 2441 
 2442 
 2443 sub gui_mode_dests_refresh {
 2444 	my @args=@_; # save them before messing with them
 2445 	my ($destsref, $widgetref, $parentwidgetref, $test_sub)=@_;
 2446 
 2447 	$$widgetref->destroy if $$widgetref;
 2448 	$$widgetref=$$parentwidgetref->Scrolled("Canvas");
 2449 	$$widgetref->pack(@Ptef);
 2450 
 2451 	# this evil hack is required to get around the fact that validate
 2452 	# occurs BEFORE the new value is applied  :(
 2453 	# what we do is we actually edit a dummy variable, and apply the change
 2454 	# to the real variable just before testing.
 2455 	# We can't just edit the real variable in-place and then put it back
 2456 	# because that makes Tk unhappy, and the Tk::Entry manpage specifically
 2457 	# says not to do that.
 2458 	my $test_entry_sub=sub {
 2459 		my ($varref, $newval, $chars, $currentpattern, $index, $action)=@_;
 2460 		debug "newval=$newval";
 2461 		$$varref=$newval;
 2462 		&$test_sub;
 2463 		return 1;
 2464 	};
 2465 
 2466 	my $new_dest_sub=sub {
 2467 		my $dest=shift;
 2468 		splice @$destsref, $dest, 0, {};
 2469 		&gui_mode_dests_refresh(@args);
 2470 	};
 2471 
 2472 	my $delete_dest_sub=sub {
 2473 		my $dest=shift;
 2474 		splice @$destsref, $dest, 1;
 2475 		&gui_mode_dests_refresh(@args);
 2476 	};
 2477 
 2478 	my $move_dest_sub=sub {
 2479 		my ($dest, $to)=@_;
 2480 		return &gui_mode_user_error("Cannot move to there")
 2481 			if ($to<0 or $to>$#{$destsref});
 2482 		my ($item)=splice @$destsref, $dest, 1;
 2483 		splice @$destsref, $to, 0, $item;
 2484 		&gui_mode_dests_refresh(@args);
 2485 	};
 2486 
 2487 	$$widgetref->Label(-text=>"dests: ".scalar(@$destsref))->pack(@Ptnf)
 2488 		if $debug;
 2489 
 2490 	my $dest_type_browse_sub=sub {
 2491 		my ($widgetsref, $browsewidget, $dest_type)=@_;
 2492 		return &gui_mode_user_error("$prog: unknown dest type: $dest_type")
 2493 			if !exists $dests_deactivate{$dest_type};
 2494 		my $bg=$browsewidget->cget("-background");
 2495 		foreach my $key (keys %$widgetsref) {
 2496 			next if $key eq "dest_type";
 2497 			my $widget=$$widgetsref{$key};
 2498 			my $deactivate=&set_is_member($key, $dests_deactivate{$dest_type});
 2499 			print "decativate $key? $deactivate\n" if $debug;
 2500 			$widget->configure(-state=>$deactivate?"disabled":"normal",
 2501 				-background=>$deactivate?"grey":$bg);
 2502 		}
 2503 		&$test_sub;
 2504 	};
 2505 
 2506 	for (my ($dest)=0; $dest<@$destsref ;$dest++) {
 2507 		print "Doing dest: $dest\n" if $debug;
 2508 		my $destref=$destsref->[$dest];
 2509 
 2510 		my $destframe=$$widgetref->Frame(@frameargs);
 2511 		$destframe->pack(@Ptef);
 2512 		$destframe->Label(-text=>"Destination ".($dest+1))->pack(@Ptnf);
 2513 
 2514 		# buttons
 2515 		my $buttonframe=$destframe->Frame(@simpleframe);
 2516 		$buttonframe->pack(@Plyf);
 2517 		$buttonframe->Button(-text=>"new dest (insert here)", 
 2518 			-command=>[$new_dest_sub, $dest],
 2519 			)->pack(@Ptef);
 2520 		$buttonframe->Button(-text=>"delete", 
 2521 			-command=>[$delete_dest_sub, $dest],
 2522 			)->pack(@Ptef);
 2523 		$buttonframe->Button(-text=>"move up",
 2524 			-command=>[$move_dest_sub, $dest, $dest-1],
 2525 			)->pack(@Ptef);
 2526 		$buttonframe->Button(-text=>"move down",
 2527 			-command=>[$move_dest_sub, $dest, $dest+1],
 2528 			)->pack(@Ptef);
 2529 
 2530 		# entries
 2531 		my $dataframe=$destframe->Frame(@simpleframe);
 2532 		$dataframe->pack(@Plef);
 2533 		my %widgets;
 2534 		$widgets{dest_type}=$dataframe->BrowseEntry(
 2535 			-textvariable=>\$destref->{dest_type},
 2536 			-label=>"Destination Type:",
 2537 			-choices=>[qw(CATEGORY SKIP LAST UNIQUE)], 
 2538 			-browsecmd=>[$dest_type_browse_sub, \%widgets],
 2539 			-state=>"readonly",
 2540 			); $widgets{dest_type}->pack(@Ptxf);
 2541 
 2542 		$widgets{dest_category}=$dataframe->BrowseEntry(
 2543 			-textvariable=>\$destref->{dest_category},
 2544 			-label=>"Destination Category:",
 2545 			-choices=>[sort string_nocase_sort_helper @categories],
 2546 			); $widgets{dest_category}->pack(@Ptxf);
 2547 
 2548 		# see comment for $test_entry_sub for why we do this
 2549 		my $format_temp=$destref->{format};
 2550 
 2551 		$widgets{format}=$dataframe->LabEntry(
 2552 			-textvariable=>\$format_temp,
 2553 			-labelPack=>[-side=>"left"],
 2554 			-label=>"Format:",
 2555 			-validatecommand=>[$test_entry_sub, \$destref->{format}],
 2556 			-validate=>"key",
 2557 			); $widgets{format}->pack(@Ptxf);
 2558 
 2559 		# see comment for $test_entry_sub for why we do this
 2560 		my $count_temp=$destref->{count};
 2561 
 2562 		$widgets{count}=$dataframe->LabEntry(
 2563 			-textvariable=>\$count_temp,
 2564 			-labelPack=>[-side=>"left"],
 2565 			-label=>"Count:",
 2566 			-validatecommand=>[$test_entry_sub, \$destref->{count}],
 2567 			-validate=>"key",
 2568 			); $widgets{count}->pack(@Ptxf);
 2569 
 2570 		$destref->{use_sprintf}=1 
 2571 			if defined $destref->{use_sprintf};
 2572 		$widgets{use_sprintf}=$dataframe->Checkbutton(
 2573 			-text=>"Use sprintf", 
 2574 			-variable=>\$destref->{use_sprintf},
 2575 			-command=>$test_sub,
 2576 			-offvalue=>undef,
 2577 			); $widgets{use_sprintf}->pack(@Ptxf);
 2578 
 2579 		$destref->{delete_if_unique}=1 
 2580 			if defined $destref->{delete_if_unique};
 2581 		$widgets{delete_if_unique}=$dataframe->Checkbutton(
 2582 			-text=>"Delete if 'Unique' Correlation", 
 2583 			-variable=>\$destref->{delete_if_unique},
 2584 			-command=>$test_sub,
 2585 			-offvalue=>undef,
 2586 			); $widgets{delete_if_unique}->pack(@Ptxf);
 2587 
 2588 		&$dest_type_browse_sub(\%widgets, $widgets{dest_type}, 
 2589 			$destref->{dest_type}) if !&empty($destref->{dest_type});
 2590 	}
 2591 	$$widgetref->Button(-text=>"new dest (append)", 
 2592 		-command=>[$new_dest_sub, $#{$destsref}+1],
 2593 		)->pack(@Ptnf);
 2594 	&$test_sub;
 2595 }
 2596 
 2597 
 2598 
 2599 sub gui_mode_view_all_patterns {
 2600 	my $window=&gui_mode_window("patterns config", $gui_mode_main);
 2601 
 2602 	my @gui_mode_pattern_column_headers=
 2603 		qw(delete dests summary logtype pattern);
 2604 
 2605 	my $hlist;
 2606 
 2607 	my ($select_sub, $refresh_sub, $delete_sub, $dests_sub, $logtype_to_view);
 2608 
 2609 	$select_sub=sub{};
 2610 
 2611 	$dests_sub=sub {
 2612 		my ($logtype, $num)=@_;
 2613 		my $pattern=$patterns{$logtype}[$num];
 2614 		&gui_mode_configure_pattern($logtype, $pattern, undef);
 2615 		&$refresh_sub;
 2616 	};
 2617 
 2618 	$delete_sub=sub {
 2619 		my ($logtype, $num)=@_;
 2620 		my $pattern=$patterns{$logtype}[$num];
 2621 		splice(@{$patterns{$logtype}}, $num, 1);
 2622 		delete $dests{$logtype}{$pattern};
 2623 
 2624 		# rebuild internal data structures
 2625 		($evals, $filename_pats)=&build_log_stuff;
 2626 		&run_evals;
 2627 
 2628 		&gui_mode_config_dirty;
 2629 
 2630 		&$refresh_sub;
 2631 	};
 2632 
 2633 	$refresh_sub=sub{
 2634 		return 1 if ! defined $hlist;
 2635 
 2636 		$hlist->delete("all");
 2637 
 2638 		foreach my $logtype (sort keys %patterns) {
 2639 			next if (!&empty($logtype_to_view) && $logtype_to_view ne "all" &&
 2640 					$logtype_to_view ne $logtype);
 2641 			for (my $i=0; $i<@{$patterns{$logtype}}; $i++) {
 2642 				my $pattern=$patterns{$logtype}[$i];
 2643 				my $entry=$hlist->addchild("");
 2644 
 2645 				# "Delete" button
 2646 				my $button=$hlist->Button(-text=>"Delete",
 2647 					-command=>[$delete_sub, $logtype, $i]);
 2648 				$hlist->itemCreate($entry, 0, -itemtype=>"window",
 2649 					-widget=>$button);
 2650 
 2651 				# "Properties" button
 2652 				$button=$hlist->Button(-text=>"Dests...",
 2653 					-command=>[$dests_sub, $logtype, $i]);
 2654 				$hlist->itemCreate($entry, 1, -itemtype=>"window",
 2655 					-widget=>$button);
 2656 
 2657 				# logtype
 2658 				$hlist->itemCreate($entry, 3,
 2659 					-itemtype=>"text", -text=>$logtype);
 2660 
 2661 				# pattern
 2662 				$hlist->itemCreate($entry, 4,
 2663 					-itemtype=>"text", -text=>$pattern);
 2664 
 2665 				# "summary" label
 2666 				foreach my $destref (@{$dests{$logtype}{$pattern}}) {
 2667 					my $dest=$destref->{dest};
 2668 					next if !defined $dest;
 2669 					next if $dest ne "SKIP" && $dest ne "LAST";
 2670 					$hlist->itemCreate($entry, 2,
 2671 						-itemtype=>"text", -text=>$dest);
 2672 				}
 2673 			}
 2674 		}
 2675 		1;
 2676 	};
 2677 
 2678 	# logtype selector
 2679 	$window->BrowseEntry(-label=>"Logtype",
 2680 		-textvariable=>\$logtype_to_view,
 2681 		-choices=>["all", @log_type_list],
 2682 		-validate=>"key", -validatecommand=>$refresh_sub,
 2683 		-state=>"readonly",
 2684 		)->pack(@Ptxf);
 2685 
 2686 	# build the pattern selection widget
 2687 	$hlist=$window->Scrolled('HList', -header=>1,
 2688 		-columns => 5, -padx=>1, -pady=>1,
 2689 		-scrollbars => "se", -selectmode=>"single",,
 2690 		-width=>150, -height=>10, -command=>$select_sub);
 2691 	$hlist->pack(@Ptef);
 2692 
 2693 	# put headers on the columns
 2694 	my $col=0;
 2695 	foreach my $name (@gui_mode_pattern_column_headers) {
 2696 		my $header=$hlist->resizeButton(-text=>$name, -column=>$col,
 2697 			-widget=>\$hlist);
 2698 		$hlist->header("create", $col, -itemtype=>"window", -widget=>$header);
 2699 		$hlist->columnWidth($col, -char=>$gui_mode_field_width{$name});
 2700 		$col++;
 2701 	}
 2702 
 2703 	# actions
 2704 	&gui_mode_widget_actions($window, Done => sub { $window->destroy }, 
 2705 		"Refresh" => $refresh_sub,
 2706 		)->pack(-side=>"bottom");
 2707 
 2708 	# populate the pattern table
 2709 	&$refresh_sub;
 2710 }
 2711 
 2712 
 2713 sub gui_mode_event_config {
 2714 	# we can handle being passed 0 or 1 events.  More than that is wasted.
 2715 	my @events=@_;
 2716 
 2717 	my @properties=qw(priority color do_action description);
 2718 
 2719 	my $col_matches=3; # columns before we start having properties
 2720 	my @matches=qw(host category data); # not just keys; we care about order
 2721 	my @match_tags=map $name2tag{$_}, @matches;
 2722 
 2723 	my $window=&gui_mode_window("event config", $gui_mode_main);
 2724 
 2725 	my ($hlist, %eventref, %user_event);
 2726 	my ($add_sub, $delete_sub, $select_sub, $refresh_sub, $reset_sub);
 2727 	my ($apply_sub, $find_match_sub, $properties_sub);
 2728 
 2729 	# use the user selection to set the user_event fields
 2730 	$select_sub = sub {
 2731 		my $arg=shift;
 2732 		my $eventref=$eventref{$arg};
 2733 		$user_event{$_}=$eventref->{$_}           foreach (@match_tags);
 2734 		$user_event{val}{$_}=$eventref->{val}{$_} foreach (@properties);
 2735 	};
 2736 		
 2737 	# reset the user's entry
 2738 	$reset_sub = sub {
 2739 		undef $user_event{$_}                     foreach (@match_tags);
 2740 		undef $user_event{val}{$_}                foreach (@properties);
 2741 	};
 2742 
 2743 	# find if the matches for a given entry are already in another entry
 2744 	$find_match_sub = sub {
 2745 		EVENT: foreach my $eventref (@event_config) {
 2746 			foreach my $tag (keys %$eventref) {
 2747 				next if $tag eq 'val';
 2748 				next EVENT if &empty($user_event{$tag});
 2749 				next EVENT if $eventref->{$tag} ne $user_event{$tag};
 2750 			}
 2751 			foreach my $tag (keys %user_event) {
 2752 				next if $tag eq 'val';
 2753 				next if &empty($user_event{$tag});
 2754 				next EVENT if !exists $eventref->{$tag};
 2755 				# don't need to check for equality 'cuz we already did
 2756 			}
 2757 			return $eventref; # we have a winner!
 2758 		}
 2759 		return undef;
 2760 	};
 2761 
 2762 	# add the user's entry to the entry table
 2763 	$add_sub=sub{
 2764 		return &gui_mode_user_error("Nothing to match")
 2765 			if !grep(!&empty($user_event{$_}), @match_tags);
 2766 		return &gui_mode_user_error("No properties")
 2767 			if !grep(!&empty($user_event{val}{$_}), @properties);
 2768 		return &gui_mode_user_error("This event is already configured")
 2769 			if &$find_match_sub;
 2770 
 2771 		my %new_event;
 2772 		foreach (@match_tags) {
 2773 			$new_event{$_}=$user_event{$_} 
 2774 				if !&empty($user_event{$_});
 2775 		}
 2776 		foreach (@properties, "is_local") {
 2777 			$new_event{val}{$_}=$user_event{val}{$_} 
 2778 				if !&empty($user_event{val}{$_});
 2779 		}
 2780 		push @event_config, \%new_event;
 2781 		&$apply_sub;
 2782 	};
 2783 
 2784 	# call this when @event_config changes
 2785 	$apply_sub = sub {
 2786 		&gui_mode_event_config_apply;
 2787 		&$refresh_sub;
 2788 	};
 2789 
 2790 	$delete_sub = sub {
 2791 		my $entry=shift;
 2792 
 2793 		$hlist->delete("entry", $entry);
 2794 
 2795 		my $eventref=$eventref{$entry};
 2796 
 2797 		for (my $i=0; $i<@event_config; $i++) {
 2798 			if ($event_config[$i]==$eventref) {
 2799 				splice(@event_config, $i, 1);
 2800 				&$apply_sub;
 2801 				return;
 2802 			}
 2803 		}
 2804 		&gui_mode_user_error("Event has already been deleted");
 2805 		&$refresh_sub;
 2806 	};
 2807 
 2808 	# refresh the event selection widget from the event_config array
 2809 	$refresh_sub = sub {
 2810 
 2811 		$hlist->delete("all");
 2812 		undef %eventref;
 2813 
 2814 		foreach my $eventref (@event_config) {
 2815 			my $entry=$hlist->addchild("");
 2816 
 2817 			$eventref{$entry}=$eventref;
 2818 
 2819 			# "Delete" button
 2820 			my $button=$hlist->Button(-text=>"Delete", 
 2821 				-command=>[$delete_sub, $entry]);
 2822 			$hlist->itemCreate($entry, 0, -itemtype=>"window", 
 2823 				-widget=>$button);
 2824 
 2825 			# "Properties" button
 2826 			$button=$hlist->Button(-text=>"Properties...",
 2827 				-command=>[$properties_sub, $eventref]);
 2828 			$hlist->itemCreate($entry, 1, -itemtype=>"window", 
 2829 				-widget=>$button);
 2830 
 2831 			# local? checkbox
 2832 			$button=$hlist->Checkbutton(-width=>$gui_mode_field_width{"local"},
 2833 				-variable=>\$eventref->{val}{is_local});
 2834 			$hlist->itemCreate($entry, 2, -itemtype=>"window", 
 2835 				-widget=>$button);
 2836 
 2837 			# matches
 2838 			for (my $i=0; $i<@matches; $i++) {
 2839 				my $tag=$match_tags[$i];
 2840 				next if !exists $eventref->{$tag};
 2841 
 2842 				my $val=$eventref->{$tag};
 2843 				$hlist->itemCreate($entry, $i+$col_matches, 
 2844 					-itemtype=>"text", -text=>$val);
 2845 			}
 2846 		}
 2847 	};
 2848 
 2849 	$properties_sub = sub {
 2850 		my $eventref=shift;
 2851 
 2852 		$eventref->{val}={} if !defined $eventref->{val};
 2853 		&gui_mode_properties($hlist, $eventref->{val});
 2854 		&$apply_sub;
 2855 	};
 2856 
 2857 	# build the event selection widget
 2858 	$hlist=$window->Scrolled('HList', -header=>1,
 2859 		-columns => $col_matches + @matches, -padx=>1, -pady=>1,
 2860 		-scrollbars => "se", -selectmode=>"single",
 2861 		-width=>150, -height=>10, -command=>$select_sub);
 2862 	$hlist->pack(@Ptef);
 2863 
 2864 	# put headers on the columns
 2865 	my $col=0;
 2866 	foreach my $name ("delete", "properties", "local?", @matches) {
 2867 		my $header=$hlist->resizeButton(-text=>$name, -column=>$col, 
 2868 			-widget=>\$hlist);
 2869 		$hlist->header("create", $col, -itemtype=>"window", -widget=>$header);
 2870 		$hlist->columnWidth($col, -char=>$gui_mode_field_width{$name});
 2871 		$col++;
 2872 	}
 2873 
 2874 	# populate the event table
 2875 	&$refresh_sub;
 2876 
 2877 	# build the user input area
 2878 	my $frame=$window->Frame(@frameargs); $frame->pack(@Ptef);
 2879 
 2880 	$frame->Button(-text=>"Add", -command=>$add_sub, 
 2881 		-width=>$gui_mode_field_width{"delete"},
 2882 		-padx=>0, -pady=>0,
 2883 		)->pack(@Plef);
 2884 	$frame->Button(-text=>"Properties...", 
 2885 		-command=>[$properties_sub, \%user_event],
 2886 		)->pack(@Plef);
 2887 	$frame->Checkbutton(-width=>$gui_mode_field_width{"local"},
 2888 		-variable=>\$user_event{val}{is_local})->pack(@Plef);
 2889 
 2890 	$user_event{val}{is_local}=1;
 2891 
 2892 	foreach my $name (@matches) {
 2893 		my $tag=$name2tag{$name};
 2894 		$frame->Entry(-width=>$gui_mode_field_width{$name},
 2895 			-textvariable=>\$user_event{$tag})
 2896 			->pack(@Plef);
 2897 	}
 2898 
 2899 	# If we've been passed a new event, put it in the add dialog to be added
 2900 	# If we've been passed a configured event, reconfigure it
 2901 	if (@events) {
 2902 		my $event=shift @events;
 2903 		my $hashref=$gui_mode_hashref{$event};
 2904 		foreach my $name (@matches) {
 2905 			my $tag=$name2tag{$name};
 2906 			$user_event{$tag}=$hashref->{$tag};
 2907 		}
 2908 		my $eventref=&$find_match_sub;
 2909 		if ($eventref) {
 2910 			&$properties_sub($eventref);
 2911 		}
 2912 	}
 2913 
 2914 	# actions
 2915 	&gui_mode_widget_actions($window, Done => sub { $window->destroy }, 
 2916 		Apply=>$apply_sub,
 2917 		"Refresh Table" => $refresh_sub, "Reset add area" => $reset_sub,
 2918 		)->pack(-side=>"bottom");
 2919 }
 2920 
 2921 
 2922 sub gui_mode_variable_config {
 2923 	my ($widget, $varhashref, $variable)=@_;
 2924 
 2925 	my $frame=$widget->Frame->pack(@Ptef, -fill=>"x");
 2926 
 2927 	my $first_word=$variable; $first_word=~s{_.*}{};
 2928 	my $last_word =$variable; $last_word =~s{.*_}{};
 2929 
 2930 	# find the type of value
 2931 	my $type="string"; # reasonable default
 2932 	if ($last_word eq "format") {
 2933 		$type="format";
 2934 	} elsif ($last_word eq "file") {
 2935 		$type="file";
 2936 	} elsif ($last_word eq "interval") {
 2937 		$type="int";
 2938 	} elsif ($last_word eq "disabled") {
 2939 		$type="boolean";
 2940 	} elsif ($first_word eq "suppress") {
 2941 		$type="boolean";
 2942 	} elsif ($var2type{$variable}) {
 2943 		$type=$var2type{$variable};
 2944 	}
 2945 
 2946 	my $name=$variable; $name=~s{^[^\_]+_mode_}{}; $name=~s{_}{ }g;
 2947 	$name=$var2name{$variable} if exists $var2name{$variable};
 2948 	$name.='?' if $type eq "boolean";
 2949 	$name =~ s{^(.)}{\U$1};
 2950 
 2951 	my $real_varref;
 2952 	eval qq(\$real_varref=\\\$$variable); die "$@" if $@;
 2953 
 2954 	my $temp_var=$$real_varref;
 2955 	my $temp_varref=\$temp_var;
 2956 
 2957 	my $real_is_localref=\$is_local{var}{$variable};
 2958 	my $temp_is_local=$is_local{var}{$variable}; # init
 2959 	my $temp_is_localref=\$temp_is_local;
 2960 
 2961 	my $changesub_entry=sub{
 2962 		my $proposed=shift;
 2963 		$temp_is_local=1 if $proposed ne $$real_varref; return 1;
 2964 	};
 2965 
 2966 	my $changesub_checkbutton=sub{
 2967 		$temp_is_local=1 if $$real_varref xor $$temp_varref;
 2968 	};
 2969 
 2970 	# actual widgets
 2971 	if ($type eq "boolean") {
 2972 		$frame->Checkbutton(-text=>"$name", -variable=>$temp_varref, 
 2973 			-command=>$changesub_checkbutton,
 2974 			)->pack(@Plnf);
 2975 	} elsif ($type eq "file") {
 2976 		$frame->Label(-text=>$name)->pack(@Plnf);
 2977 		$frame->Entry(-textvariable=>$temp_varref, -validate=>"key",
 2978 			-validatecommand=>$changesub_entry,
 2979 			)->pack(@Plxf);
 2980 		$frame->Button(-text=>"Browse...", -command => sub {
 2981 				my $file=$gui_mode_main->getSaveFile(-initialdir=>dirname($$temp_varref));
 2982 				$$temp_varref=$file if defined $file && $file ne "";
 2983 			})->pack(@Plnf);
 2984 	} else {
 2985 		$frame->Label(-text=>$name)->pack(@Plnf);
 2986 		$frame->Entry(-textvariable=>$temp_varref, -validate=>"key",
 2987 			-validatecommand=>$changesub_entry,
 2988 			)->pack(@Plef, -fill=>"x");
 2989 	}
 2990 
 2991 	$frame->Checkbutton(-text=>"local?", -variable=>$temp_is_localref,
 2992 		)->pack(@Prnf);
 2993 
 2994 	$varhashref->{$variable}{type}=$type;
 2995 	$varhashref->{$variable}{real_varref}=$real_varref;
 2996 	$varhashref->{$variable}{temp_varref}=$temp_varref;
 2997 	$varhashref->{$variable}{real_is_localref}=$real_is_localref;
 2998 	$varhashref->{$variable}{temp_is_localref}=$temp_is_localref;
 2999 
 3000 	return $temp_varref;
 3001 }
 3002 
 3003 
 3004 sub gui_mode_variable_done {
 3005 	my $varhashref=shift;
 3006 
 3007 	my $is_dirty=0;
 3008 
 3009 	foreach my $variable (keys %$varhashref) {
 3010 		my $real_varref=$varhashref->{$variable}{real_varref};
 3011 		my $real_is_localref=$varhashref->{$variable}{real_is_localref};
 3012 		my $temp_varref=$varhashref->{$variable}{temp_varref};
 3013 		my $temp_is_localref=$varhashref->{$variable}{temp_is_localref};
 3014 
 3015 		if (&val_or_empty($$real_varref) ne &val_or_empty($$temp_varref)) {
 3016 			$is_dirty=1;
 3017 			$$real_varref  = $$temp_varref;
 3018 		}
 3019 
 3020 		if ($$real_is_localref xor $$temp_is_localref) {
 3021 			$is_dirty=1;
 3022 			$$real_is_localref   = $$temp_is_localref;
 3023 		}
 3024 	}
 3025 	&gui_mode_config_dirty if $is_dirty;
 3026 }
 3027 
 3028 
 3029 sub gui_mode_misc_configurables {
 3030 	my $window=&gui_mode_window("misc configurables", $gui_mode_main);
 3031 
 3032 	my $NoteBook=$window->NoteBook; $NoteBook->pack(@Ptef);
 3033 
 3034 	my $varhashref={};
 3035 
 3036 	my (%frame, %is_mode);
 3037 	foreach my $mode_name (qw{Core Report Real GUI Daemon}) {
 3038 		my $mode=lc $mode_name;
 3039 		$is_mode{$mode}=1 if $mode ne "core";
 3040 		my $page=$NoteBook->add($mode, -label => 
 3041 			$mode eq "core"? $mode_name : "$mode_name Mode");
 3042 		$frame{$mode}=$page->Frame->pack(@Ptef);
 3043 	}
 3044 
 3045 	my %var2mode;
 3046 	$var2mode{$_}="report" foreach (qw(other_host_message 
 3047 		output_message_one_day output_message_all_days 
 3048 		output_message_all_days_in_range mail_address mail_command 
 3049 		output_file output_file_and_stdout default_sort default_filter));
 3050 	$var2mode{$_}="gui" foreach (qw(default_login_action
 3051 		print_format print_command save_format window_command));
 3052 	foreach my $scalar (@required_import_scalars, @optional_import_scalars) {
 3053 		my $first_word=$scalar; $first_word=~s{_.*}{};
 3054 
 3055 		# find the mode this belongs in
 3056 		my $mode="core"; # reasonable default
 3057 		if ($is_mode{$first_word}) {
 3058 			$mode=$first_word;
 3059 			next if $scalar eq $mode."_mode";
 3060 		} elsif ($var2mode{$scalar}) {
 3061 			$mode=$var2mode{$scalar};
 3062 		}
 3063 
 3064 		my $window=$frame{$mode};
 3065 		&gui_mode_variable_config($window, $varhashref, $scalar);
 3066 	}
 3067 	&gui_mode_widget_actions($window, "OK" => sub {
 3068 		&gui_mode_variable_done($varhashref); 
 3069 		$window->destroy;
 3070 		})->pack(-side=>"bottom");
 3071 }
 3072 
 3073 
 3074 sub gui_mode_find_dialog {
 3075 	my $window=&gui_mode_window("find", $gui_mode_main);
 3076 
 3077 	my ($outerframe, $innerframe);
 3078 	my %find_what=%gui_mode_find_what; # init
 3079 
 3080 	foreach my $what (qw(c d h)) {
 3081 		$outerframe=$window->Frame(@frameargs);
 3082 		$outerframe->pack(@Ptxf);
 3083 
 3084 		$innerframe=$outerframe->Frame; # being used for layout. no args
 3085 		$innerframe->pack(@Ptxf);
 3086 		$innerframe->Label(-text=>$tag2name{$what})->pack(@Plnf);
 3087 		$innerframe->Entry(-textvariable=>\$find_what{$what}{val},
 3088 			)->pack(@Plef);
 3089 
 3090 		$innerframe=$outerframe->Frame; # being used for layout. no args
 3091 		$innerframe->pack(@Ptxf);
 3092 		$innerframe->Checkbutton(-text=>"case sensitive?", 
 3093 			-variable=>\$find_what{$what}{iscase})->pack(@Plnf);
 3094 
 3095 		$innerframe=$outerframe->Frame; # being used for layout. no args
 3096 		$innerframe->pack(@Ptxf);
 3097 		$find_what{$what}{regex_type}=0 
 3098 			if &empty($find_what{$what}{regex_type});
 3099 		$innerframe->Radiobutton(-text=>"literal", -value=>0,
 3100 			-variable=>\$find_what{$what}{regex_type})->pack(@Plnf);
 3101 		$innerframe->Radiobutton(-text=>"glob", -value=>1,
 3102 			-variable=>\$find_what{$what}{regex_type})->pack(@Plnf);
 3103 		$innerframe->Radiobutton(-text=>"regex", -value=>2,
 3104 			-variable=>\$find_what{$what}{regex_type})->pack(@Plnf);
 3105 	}
 3106 
 3107 	$outerframe=$window->Frame(@frameargs);
 3108 	$outerframe->pack(@Ptxf);
 3109 
 3110 	$find_what{unknowns_type}=0 if &empty($find_what{unknowns_type});
 3111 	$outerframe->Radiobutton(-text=>"unknowns only", -value=>2,
 3112 		-variable=>\$find_what{unknowns_type})->pack(@Plnf);
 3113 	$outerframe->Radiobutton(-text=>"knowns only", -value=>1,
 3114 		-variable=>\$find_what{unknowns_type})->pack(@Plnf);
 3115 	$outerframe->Radiobutton(-text=>"unknowns and knowns", -value=>0,
 3116 		-variable=>\$find_what{unknowns_type})->pack(@Plnf);
 3117 	&gui_mode_widget_actions($window, 
 3118 		Done =>[\&gui_mode_find_done, $window, \%find_what],
 3119 		Apply=>[\&gui_mode_find_apply, \%find_what],
 3120 		Clear=>[\&gui_mode_find_clear, \%find_what],
 3121 		)->pack(-side=>"bottom");
 3122 }
 3123 
 3124 
 3125 sub gui_mode_find_apply {
 3126 	my $hashref=shift;
 3127 
 3128 	%gui_mode_find_what=%$hashref;
 3129 	my @all_entries=$gui_mode_hlist->info("children");
 3130 	foreach my $entry (@all_entries) {
 3131 		&gui_mode_find_check($entry);
 3132 	}
 3133 	
 3134 }
 3135 
 3136 sub gui_mode_find_check {
 3137 	my @entries=(@_);
 3138 	my $unknowns_type=$gui_mode_find_what{unknowns_type};
 3139 
 3140 	return unless %gui_mode_find_what;
 3141 
 3142 	ENTRY:
 3143 	foreach my $entry (@entries) {
 3144 		my $match=1; # init
 3145 		my $hashref=$gui_mode_hashref{$entry};
 3146 		CHECKS: { # bare loop so we can exit early
 3147 			if ($unknowns_type == 1 && $hashref->{_u}) {
 3148 				# users wants knowns and this is unknown
 3149 				$match=0; next CHECKS;
 3150 			} elsif ($unknowns_type == 2 && !$hashref->{_u}) {
 3151 				# user wants unknowns and this is known
 3152 				$match=0; next CHECKS;
 3153 			}
 3154 
 3155 			WHAT:
 3156 			foreach my $what (qw(c d h)) {
 3157 				my $testval=$gui_mode_find_what{$what}{val};
 3158 				next WHAT if &empty($testval);
 3159 
 3160 				my $iscase=$gui_mode_find_what{$what}{iscase};
 3161 				my $regex_type=$gui_mode_find_what{$what}{regex_type};
 3162 
 3163 				# modify the testval for the regex type
 3164 				if (!$regex_type) { # 0=literal
 3165 					$testval=quotemeta $testval;
 3166 				} elsif ($regex_type == 1) { # 1=glob
 3167 					$testval=~s{([^\w\?\*])}{\$1}g;
 3168 					$testval=~s{\?}{.}g;
 3169 					$testval=~s{\*}{.*}g;
 3170 				} elsif ($regex_type == 2) { # 2=regex
 3171 					# nothing special needs to be done
 3172 				} else {
 3173 					die "$prog: impossible value of regex_type: $regex_type";
 3174 				}
 3175 
 3176 				# determine the regex case flag from $iscase
 3177 				my $caseflag=$iscase ? "" : "i";
 3178 				my $val=$hashref->{$what};
 3179 
 3180 				if ($val!~m{(?$caseflag:$testval)}) {
 3181 					$match=0; next CHECKS;
 3182 				}
 3183 			}
 3184 
 3185 		} # done CHECKS
 3186 
 3187 		if ($match) {
 3188 			next if !$gui_mode_hlist->info("hidden", $entry); # already unhidden
 3189 			$gui_mode_hlist->show("entry", $entry);
 3190 			$gui_mode_hidden--;
 3191 			$gui_mode_visible++;
 3192 		} else {
 3193 			next if $gui_mode_hlist->info("hidden", $entry); # already hidden
 3194 			$gui_mode_hlist->selectionClear($entry, $entry);
 3195 			$gui_mode_hlist->hide("entry", $entry);
 3196 			$gui_mode_hidden++;
 3197 			$gui_mode_visible--;
 3198 		}
 3199 	}
 3200 }
 3201 
 3202 sub gui_mode_find_done {
 3203 	my ($window, $hashref)=@_;
 3204 
 3205 	&gui_mode_find_apply($hashref);
 3206 	$window->destroy;
 3207 }
 3208 
 3209 
 3210 sub gui_mode_find_clear {
 3211 	my $find_what_ref=shift;
 3212 
 3213 	undef %gui_mode_find_what;
 3214 	&gui_mode_unhide_all;
 3215 
 3216 	return if !defined $find_what_ref;
 3217 
 3218 	$find_what_ref->{unknowns_type}=0;
 3219 
 3220 	foreach my $what (qw(c d h)) {
 3221 		$find_what_ref->{$what}{val}="";
 3222 		$find_what_ref->{$what}{iscase}=0;
 3223 		$find_what_ref->{$what}{regex_type}=0;
 3224 	}
 3225 }
 3226 
 3227 
 3228 sub gui_mode_save_event {
 3229 	my @events=@_;
 3230 
 3231 	my $what="Save";
 3232 	my $varhashref={};
 3233 
 3234 	my $window=&gui_mode_window("save", $gui_mode_main);
 3235 
 3236 	my $frame=$window->Frame(@frameargs);
 3237 	$frame->pack(@Ptef, -fill=>"x");
 3238 
 3239 	&gui_mode_variable_config($window, $varhashref, "gui_mode_save_events_file");
 3240 
 3241 	# save all/selected
 3242 	&gui_mode_widget_selected($window, $what, $varhashref, "gui_mode_save_all")
 3243 		->pack(@Ptef, -fill=>"x");
 3244 
 3245 	# save format
 3246 	&gui_mode_widget_format($window, $what, $varhashref, "save_format")
 3247 		->pack(@Ptef, -fill=>"x");
 3248 
 3249 	# actions
 3250 	&gui_mode_widget_actions($window, $what, sub {
 3251 		&gui_mode_variable_done($varhashref);
 3252 		&gui_mode_save_event_helper($window, @events);
 3253 		})->pack(-side=>"bottom");
 3254 }
 3255 
 3256 
 3257 sub gui_mode_save_event_helper {
 3258 	my $window=shift;
 3259 	my @entries=@_; # the rest
 3260 
 3261 	# we do this here instead of in the original menu call because the 
 3262 	# selection may have changed since the original menu was made
 3263 	@entries=$gui_mode_hlist->selectionGet if !@entries;
 3264 
 3265 	if (!defined $gui_mode_save_events_file or $gui_mode_save_events_file eq "") {
 3266 		&gui_mode_user_error("No save file specified");
 3267 		return;
 3268 	} elsif (!$gui_mode_save_all && !@entries) {
 3269 		&gui_mode_user_error("You said to save selected ".
 3270 			"events, but none were selected");
 3271 		return;
 3272 	}
 3273 
 3274 	if (-f $gui_mode_save_events_file) {
 3275 		my $dialog=$gui_mode_main->Dialog(-bitmap=>"question",
 3276 			-text=>"File $gui_mode_save_events_file exists.  Overwrite?",
 3277 			-default_button=>"Yes", -buttons=>["Yes", "No"]);
 3278 		my $result=$dialog->Show;
 3279 		return if $result ne "Yes";
 3280 	}
 3281 
 3282 	my $savefd=new FileHandle("> $gui_mode_save_events_file");
 3283 	if (!$savefd) {
 3284 		&gui_mode_user_error("$prog: open $gui_mode_save_events_file for write: $!\n");
 3285 		return;
 3286 	}
 3287 
 3288 	@entries=$gui_mode_hlist->info("children") if $gui_mode_save_all;
 3289 
 3290 	foreach my $entry (@entries) {
 3291 		my $hashref=$gui_mode_hashref{$entry};
 3292 		if (! defined $hashref) {
 3293 			warn "No hashref for entry $entry, class ".$entry->Class."\n";
 3294 			next;
 3295 		}
 3296 		print $savefd &process_tags($save_format, $hashref);
 3297 	}
 3298 	close $savefd;
 3299 
 3300 	$window->destroy;
 3301 	&gui_mode_config_dirty;
 3302 }
 3303 
 3304 
 3305 sub gui_mode_config_dirty {
 3306 
 3307 	$config_is_dirty=1;
 3308 	&config_save if $gui_mode_config_autosave;
 3309 }
 3310 
 3311 
 3312 sub gui_mode_save_config {
 3313 	my $should_wait=shift;
 3314 
 3315 	return if $gui_mode_configure_disabled;
 3316 
 3317 	my $window=&gui_mode_window("save config", $gui_mode_main);
 3318 	
 3319 	my $varhashref={};
 3320 
 3321 	my $frame=$window->Frame(@frameargs);
 3322 	$frame->pack(@Ptef);
 3323 
 3324 	&gui_mode_variable_config($frame, $varhashref, "gui_mode_config_autosave");
 3325 	&gui_mode_variable_config($frame, $varhashref, "gui_mode_config_savelocal");
 3326 	&gui_mode_variable_config($frame, $varhashref, "gui_mode_config_save_does_rcs");
 3327 
 3328 	$frame=$window->Frame(@frameargs);
 3329 	$frame->pack(@Ptef);
 3330 
 3331 	&gui_mode_variable_config($frame, $varhashref, "gui_mode_config_file");
 3332 
 3333 	&gui_mode_widget_actions($window, "Save", sub {
 3334 		&gui_mode_variable_done($varhashref);
 3335 		&config_save; $window->destroy;
 3336 		})->pack(-side=>"bottom");
 3337 
 3338 	if ($should_wait) {
 3339 		$window->grab;
 3340 		$window->waitWindow;
 3341 	}
 3342 }
 3343 
 3344 
 3345 sub do_rcs {
 3346 	my $file=shift;
 3347 
 3348 	my $command=&process_tags($rcs_command, 
 3349 			{%tags, f => $file});
 3350 
 3351 	system($command)==0 or &gui_mode_user_warn("RCS command failed on $file");
 3352 }
 3353 
 3354 
 3355 sub config_save {
 3356 
 3357 	# mode variables should be suppressed to avoid problems with a 
 3358 	# config being used for multiple modes
 3359 	my %mode_vars=(gui_mode=>1, real_mode=>1);
 3360 
 3361 	if ($gui_mode_config_save_does_rcs && -e $gui_mode_config_file) {
 3362 		&do_rcs($gui_mode_config_file);
 3363 	}
 3364 
 3365 	my $savefd=new FileHandle($gui_mode_config_file, "w")
 3366 		|| die "$prog: open $gui_mode_config_file for write: $!\n";
 3367 	
 3368 	my $oldfd=select $savefd;
 3369 
 3370 	print "# this file automatically generated by $version_string\n";
 3371 	print "config_version $current_version\n\n";
 3372 
 3373 	foreach my $varname (@required_import_scalars, @optional_import_scalars) {
 3374 		my $value;
 3375 		my $is_local=$is_local{var}{$varname};
 3376 		my $local="";
 3377 		$local="local " if $is_local;
 3378 		eval "\$value=\$$varname";
 3379 		next if $gui_mode_config_savelocal && !$is_local;
 3380 		if (exists $mode_vars{$varname}) {
 3381 			print "# mode variables are not saved\n";
 3382 			print "#${local}set var $varname=\n\n";
 3383 		} elsif (defined $value) {
 3384 			print "${local}set var $varname=$value\n\n";
 3385 		} else {
 3386 			print "#${local}set var $varname=\n\n";
 3387 		}
 3388 	}
 3389 
 3390 	foreach my $varname (@required_import_arrays, @optional_import_arrays) {
 3391 		my @value;
 3392 		my $is_local=$is_local{arr}{$varname};
 3393 		my $local="";
 3394 		$local="local " if $is_local;
 3395 		eval "\@value=\@$varname";
 3396 		next if $gui_mode_config_savelocal && !$is_local;
 3397 		if (@value) {
 3398 			print "${local}set arr $varname=\n",
 3399 				map ("\t$_\n",@value),"\n\n";
 3400 		} else {
 3401 			print "#${local}set arr $varname=\n\n";
 3402 		}
 3403 	}
 3404 
 3405 	foreach my $varname (@arrays_to_become_hashes) {
 3406 		my %value;
 3407 		my $is_local=$is_local{arr}{$varname};
 3408 		my $local="";
 3409 		$local="local " if $is_local;
 3410 		eval "\%value=\%$varname";
 3411 		next if $gui_mode_config_savelocal && !$is_local;
 3412 		if (%value) {
 3413 			print "${local}set arr $varname=\n",
 3414 				map ("\t$_, $value{$_}\n", sort keys %value), "\n\n";
 3415 		} else {
 3416 			print "#${local}set arr $varname=\n\n";
 3417 		}
 3418 	}
 3419 
 3420 	print "\n# actions:\n\n";
 3421 
 3422 	foreach my $action (sort keys %actions) {
 3423 		my $is_local=$is_local{action}{$action};
 3424 		my $local="";
 3425 		$local="local " if $is_local;
 3426 		next if $gui_mode_config_savelocal && !$is_local;
 3427 		print "${local}action: $action\n";
 3428 		foreach my $field (sort keys %{$actions{$action}}) {
 3429 			print "\t$field:\t$actions{$action}{$field}\n";
 3430 		}
 3431 		print "\n";
 3432 	}
 3433 
 3434 	print "\n# categories:\n\n";
 3435 
 3436 	foreach my $category (sort keys %categories) {
 3437 		my $is_local=$is_local{category}{$category};
 3438 		my $local="";
 3439 		$local="local " if $is_local;
 3440 		next if $gui_mode_config_savelocal && !$is_local;
 3441 		print "${local}category: $category\n";
 3442 		foreach my $field (sort keys %{$categories{$category}}) {
 3443 			print "\t$field:\t$categories{$category}{$field}\n";
 3444 		}
 3445 		print "\n";
 3446 	}
 3447 
 3448 	print "\n# event config:\n\n";
 3449 
 3450 	&config_print_event_tree(\%event_tree, "event:\n");
 3451 
 3452 	print "\n# patterns:\n\n";
 3453 
 3454 	foreach my $logtype (sort keys %patterns) {
 3455 
 3456 		foreach my $varname (@per_log_required_scalar_exts, 
 3457 					@per_log_optional_scalar_exts) {
 3458 			my $fullvarname=$logtype."_".$varname;
 3459 			my $is_local=$is_local{var}{$fullvarname};
 3460 			my $local="";
 3461 			$local="local " if $is_local;
 3462 			next if $gui_mode_config_savelocal && !$is_local;
 3463 			if (exists $log_scalar{$logtype}{$varname}) {
 3464 				my $value=$log_scalar{$logtype}{$varname};
 3465 				print "${local}set var $fullvarname=$value\n\n";
 3466 			} else {
 3467 				print "#${local}set var $fullvarname=\n\n";
 3468 			}
 3469 		}
 3470 
 3471 		foreach my $varname (@per_log_required_array_exts, 
 3472 					@per_log_optional_array_exts) {
 3473 			my $fullvarname=$logtype."_".$varname;
 3474 			my $is_local=$is_local{arr}{$fullvarname};
 3475 			my $local="";
 3476 			$local="local " if $is_local;
 3477 			next if $gui_mode_config_savelocal && !$is_local;
 3478 			if (exists $log_array{$logtype}{$varname}) {
 3479 				my @value=@{$log_array{$logtype}{$varname}};
 3480 				print "${local}set arr $fullvarname=\n", map ("$_\n", @value), "\n\n";
 3481 			} else {
 3482 				print "#${local}set arr $fullvarname=\n\n";
 3483 			}
 3484 		}
 3485 
 3486 		print "logtype:\t$logtype\n";
 3487 
 3488 		foreach my $pattern (@{$patterns{$logtype}}) {
 3489 
 3490 			my $is_local=$is_local{pattern}{$logtype}{$pattern};
 3491 			my $local="";
 3492 			$local="local " if $is_local;
 3493 			next if $gui_mode_config_savelocal && !$is_local;
 3494 			print "\t${local}pattern:\t$pattern\n";
 3495 
 3496 			foreach my $destref (@{$dests{$logtype}{$pattern}}) {
 3497 				print "\n";
 3498 				# weird functions here to make sure dest is printed last
 3499 				foreach my $field (reverse unique("dest", keys %$destref)) {
 3500 					print "\t\t$field:\t$destref->{$field}\n";
 3501 				}
 3502 				print "\n";
 3503 			}
 3504 
 3505 			print "\n";
 3506 		}
 3507 		print "\n";
 3508 	}
 3509 
 3510 	close $savefd;
 3511 
 3512 	if ($gui_mode_config_save_does_rcs && -e $gui_mode_config_file) {
 3513 		&do_rcs($gui_mode_config_file);
 3514 	}
 3515 
 3516 	select $oldfd;
 3517 	$config_is_dirty=0;
 3518 }
 3519 
 3520 
 3521 sub config_print_event_tree {
 3522 	my $posref=shift;
 3523 	my $string=shift;
 3524 
 3525 	return if !defined $posref;
 3526 	if (exists $posref->{val}) {
 3527 		my $is_local=$posref->{val}{is_local};
 3528 		my $local="";
 3529 		$local="local " if $is_local;
 3530 		unless ($gui_mode_config_savelocal && !$is_local) {
 3531 			print "${local}$string";
 3532 			foreach my $key (sort keys %{$posref->{val}}) {
 3533 				next if $key eq "is_local";
 3534 				print "\t$key: \t$posref->{val}{$key}\n";
 3535 			}
 3536 			print "\n";
 3537 		}
 3538 	}
 3539 
 3540 	foreach my $tag (sort keys %{$posref}) {
 3541 		next if $tag eq 'val';
 3542 		foreach my $val (sort keys %{$posref->{$tag}}) {
 3543 			my $tag_name=$tag2name{$tag};
 3544 			&config_print_event_tree($posref->{$tag}{$val}, 
 3545 				$string."\tmatch $tag_name: \t$val\n");
 3546 		}
 3547 	}
 3548 }
 3549 
 3550 
 3551 sub gui_mode_backlogs {
 3552 
 3553 	my $window=&gui_mode_window("backlogs", $gui_mode_main);
 3554 
 3555 	my $my_real_mode_backlogs=$real_mode_backlogs;
 3556 
 3557 	my $scale_val=$relday_start;
 3558 
 3559 	my $major_label;
 3560 	my $scale;
 3561 
 3562 	my $active_sub = sub {
 3563 		my $state=$my_real_mode_backlogs? "normal" : "disable";
 3564 		$scale->configure(-state=>$state);
 3565 	};
 3566 
 3567 	$window->Radiobutton(-text=>"Show no backlogs, just new things",
 3568 		-variable=>\$my_real_mode_backlogs, -value=>0,
 3569 		-command=>$active_sub,
 3570 		)->pack(-anchor=>"nw");
 3571 
 3572 	$window->Radiobutton(-text=>"Show backlogs",
 3573 		-variable=>\$my_real_mode_backlogs, -value=>1,
 3574 		-command=>$active_sub,
 3575 		)->pack(-anchor=>"nw");
 3576 
 3577 	$major_label=$window->Label(
 3578 		-text=>"How many days back of backlogs do you want?");
 3579 	$major_label->pack;
 3580 
 3581 	my $label=$window->Label(-text =>strftime($date_format, localtime(time-$scale_val*86400)));
 3582 	$label->pack;
 3583 
 3584 	$scale=$window->Scale(qw/-orient horizontal -length 6i/, -command=>sub {
 3585 		$scale_val=$scale->get;
 3586 		$label->configure(-text =>strftime($date_format, localtime(time-$scale_val*86400)));
 3587 		});
 3588 	$scale->set($scale_val);
 3589 	$scale->pack;
 3590 
 3591 	&$active_sub;
 3592 
 3593 	&gui_mode_widget_actions($window, "Restart with above setting", sub { 
 3594 		&gui_mode_backlogs_helper($scale_val, $my_real_mode_backlogs) })
 3595 		->pack(qw/-expand 1 -fill x/);
 3596 }
 3597 
 3598 
 3599 sub gui_mode_backlogs_helper {
 3600 	$days_ago=shift;
 3601 	$real_mode_backlogs=shift;
 3602 
 3603 	&gui_mode_exit_hook or return; 
 3604 
 3605 	$opt{d}=$days_ago;
 3606 
 3607 	if ($real_mode_backlogs) {
 3608 		$opt{b}=1;
 3609 	} else {
 3610 		delete $opt{b};
 3611 		delete $opt{d};
 3612 	}
 3613 
 3614 	my @new_ARGV;
 3615 	my $my_optstring=$optstring;
 3616 	while ($my_optstring =~ s{^([^:])(:?)}{}) {
 3617 		my $opt=$1;
 3618 		my $takes_arg=$2;
 3619 
 3620 		next if ! exists $opt{$opt};
 3621 		if ($takes_arg) {
 3622 			push @new_ARGV, "-$opt", $opt{$opt};
 3623 		} else {
 3624 			push @new_ARGV, "-$opt";
 3625 		}
 3626 	}
 3627 	push @new_ARGV, @ARGV; # arguments that remained after opt processing
 3628 	exec ($0, @new_ARGV);
 3629 }
 3630 
 3631 
 3632 sub gui_mode_resize_fields {
 3633 	my $window=&gui_mode_window("resize fields", $gui_mode_main);
 3634 
 3635 	for (my $i=1; $i<@gui_mode_headers; $i++) {
 3636 		my $which=$i; # this is necessary to make the closure work
 3637 		my $frame=$window->Frame(@frameargs);
 3638 		my $header=$gui_mode_headers[$which];
 3639 		my $label=$frame->Label(-text=>$header, -width=>10);
 3640 		my $scale;
 3641 		$scale=$frame->Scale(-orient=>"horizontal", -length=>"6i", -to=>200,
 3642 			-command=>sub{
 3643 				my $val=$scale->get;
 3644 				$gui_mode_field_width{$header}=$val;
 3645 				$gui_mode_hlist->columnWidth($which, -char=>$val);
 3646 				}
 3647 			);
 3648 		$scale->set($gui_mode_field_width{$header});
 3649 		my @pl=qw/-side left -anchor w/;
 3650 		$frame->pack(-side=>"top"); $label->pack(@pl); $scale->pack(@pl);
 3651 	}
 3652 	my $frame=$window->Frame(@frameargs); 
 3653 	$frame->pack(-expand=>1, -fill=>"x");
 3654 	$frame->Button(-text=>"OK",     -command=>sub{$window->destroy})->pack(-side=>"left");
 3655 	$frame->Button(-text=>"Cancel", -command=>sub{$window->destroy})->pack(-side=>"right");
 3656 }
 3657 
 3658 sub gui_mode_restart {
 3659 	&gui_mode_exit_hook or return;
 3660 	exec ($0, @original_ARGV);
 3661 }
 3662 
 3663 sub gui_mode_exit_hook {
 3664 	if ($config_is_dirty && !$gui_mode_configure_disabled) {
 3665 		my $ok;
 3666 		my $window=&gui_mode_window("exit", $gui_mode_main);
 3667 		$window->Label(-text=>"The config has not been saved!")->pack(@Ptef);
 3668 		&gui_mode_widget_actions($window, 
 3669 			"Exit anyway" => sub {$window->destroy; $ok=1},
 3670 			"Save config" => sub {&gui_mode_save_config(1); 
 3671 				if (!$config_is_dirty) {$window->destroy; $ok=1} },
 3672 		)->pack(@Ptef);
 3673 		$window->grab;
 3674 		$window->waitWindow;
 3675 		return 0 if !$ok;
 3676 	}
 3677 	#mta $gui_mode_main->destroy; # should we bother?  It can take a long time
 3678 	return 1;
 3679 }
 3680 
 3681 
 3682 sub gui_mode_exit {
 3683 	&my_exit(0);
 3684 }
 3685 
 3686 
 3687 sub gui_mode_toggle_pause {
 3688 	$gui_mode_paused=!$gui_mode_paused;
 3689 	&gui_mode_status_paused;
 3690 }
 3691 
 3692 
 3693 sub my_exit {
 3694 	my $exit_code=shift;
 3695 
 3696 	&gui_mode_exit_hook or return if $gui_mode;
 3697 	exit $exit_code if defined $exit_code;
 3698 	exit 0;
 3699 }
 3700 
 3701 
 3702 sub gui_mode_unknowns {
 3703 	my ($logtyperef, $patternref, $show_non_matches)=@_;
 3704 	my ($requested_logtype, $pattern);
 3705 	$requested_logtype=$$logtyperef if defined $logtyperef;
 3706 	$pattern=$$patternref if defined $patternref;
 3707 
 3708 	my $title="Unknowns";
 3709 	$title="Unknown matches" if !&empty($pattern);
 3710 	$title="Unknown matches and non-matches" 
 3711 		if !&empty($pattern) && $show_non_matches;
 3712 	$title.=" for type $requested_logtype" if !&empty($requested_logtype); 
 3713 
 3714 	my $window=&gui_mode_window($title, $gui_mode_main);
 3715 
 3716 	my $text=$window->Scrolled(qw/ROText -wrap none/);
 3717 	$text->pack(@Ptef);
 3718 
 3719 	$window->Button(-text=>"OK", -command=>sub{$window->destroy})
 3720 		->pack(-side=>"bottom");
 3721 
 3722 	# get the unknowns
 3723 	my %unknowns;
 3724 	my @entries=$gui_mode_hlist->info("children");
 3725 	foreach my $entry (@entries) {
 3726 		my $hashref=$gui_mode_hashref{$entry};
 3727 		next if not $hashref->{_u};
 3728 		my $logtype=$hashref->{_t};
 3729 		my $line   =$hashref->{_l};
 3730 		push @{$unknowns{$logtype}}, $line
 3731 			if &empty($requested_logtype) or $requested_logtype eq $logtype;
 3732 	}
 3733 
 3734 	# display the unknowns
 3735 	my $gui_mode_match_seen;
 3736 	foreach my $logtype (sort keys %unknowns) {
 3737 		$text->insert("end", "$title (logtype $logtype):\n");
 3738 		foreach my $line (sort @{$unknowns{$logtype}}) {
 3739 			if (not defined $pattern) { # it's easy, just add it
 3740 				$text->insert("end", "\t$line\n");
 3741 			} else {
 3742 				my $match_test=&pattern_test($pattern, $line);
 3743 				if (!$show_non_matches) {
 3744 					if ($match_test==0) {
 3745 						$text->insert("end", "\t$line\n");
 3746 					}
 3747 				} else {
 3748 					if ($match_test==0) {
 3749 						# on the first match
 3750 						if (!$gui_mode_match_seen) {
 3751 							$text->see("end");
 3752 							$gui_mode_match_seen++;
 3753 						}
 3754 
 3755 						$text->tagConfigure("match", -foreground=>"white",
 3756 							-background=>"black");
 3757 						$text->insert("end", "\t$line\n", "match");
 3758 					} else {
 3759 						$text->insert("end", "\t$line\n");
 3760 					}
 3761 				}
 3762 			}
 3763 		}
 3764 		$text->insert("end", "\n");
 3765 	}
 3766 }
 3767 
 3768 
 3769 # returns 0 for match, 1 for no match, -1 for error
 3770 sub pattern_test {
 3771 	my ($pattern, $line)=@_;
 3772 	my $ret=1;
 3773 	eval "\$ret=0 if \$line=~m{^$pattern".'\s*$}; '.
 3774 		'@match_start=@-; @match_end=@+';
 3775 	$ret=-1 if $@;
 3776 	return $ret;
 3777 }
 3778 
 3779 
 3780 sub dests_test {
 3781 	my ($line, $logtyperef, $patternref, $gui_destsref)=@_;
 3782 	my ($logtype, $pattern)=($$logtyperef, $$patternref);
 3783 
 3784 	my $dests_validate=&dests_validate($gui_destsref);
 3785 	return $dests_validate if $dests_validate;
 3786 
 3787 	# if there is no logtype or no pattern, we have bigger problems
 3788 	return "No logtype" if &empty($logtype);
 3789 	return "No pattern" if &empty($pattern);
 3790 
 3791 	# no other tests work unless we have a line.  :(
 3792 	return "" if &empty($line);
 3793 
 3794 	my $pattern_test=&pattern_test($pattern, $line);
 3795 	return "Pattern does not match" if $pattern_test == 1;
 3796 	return "Pattern is bad" if $pattern_test == -1;
 3797 
 3798 	my $real_destsref=&gui_dests_to_real_dests($gui_destsref);
 3799 	return "Unspecified destination error" if !defined $real_destsref;
 3800 
 3801 	my $eval_string=&build_pattern_string($logtype, $pattern, 
 3802 		$real_destsref, 1, 1);
 3803 	debug "eval_string: \n$eval_string\n";
 3804 
 3805 	my $raw_line="__UNKNOWN";
 3806 	my $host    ="__UNKNOWN";
 3807 	$multiplier{__INTERNAL}=1;
 3808 	local $_    =$line;
 3809 
 3810 	eval "$eval_string";
 3811 	debug "error: $@" if $@;
 3812 	return "got error from destinations" if $@;
 3813 
 3814 	return undef; # if we get this far, all appears well
 3815 }
 3816 
 3817 
 3818 # correct dests, ie. rejoin dest_type and dest_category, strip out
 3819 # empties, strip out stuff that isn't applicable
 3820 sub gui_dests_to_real_dests {
 3821 	my $gui_destsref=shift;
 3822 	my $real_destsref;
 3823 
 3824 	foreach my $gui_destref (@$gui_destsref) {
 3825 		my $real_destref={};
 3826 		my $dest_type=$gui_destref->{dest_type};
 3827 		return undef if !defined $dest_type;
 3828 		return undef if !exists $dests_deactivate{$dest_type};
 3829 
 3830 		# cleanup keys that aren't applicable or are empty
 3831 		foreach my $key (keys %$gui_destref) {
 3832 			next if &set_is_member($key, $dests_deactivate{$dest_type});
 3833 			next if &empty($gui_destref->{$key});
 3834 			$real_destref->{$key}=$gui_destref->{$key};
 3835 		}
 3836 
 3837 		# special handling for use_sprintf and delete_if_exists
 3838 		$real_destref->{use_sprintf}="" 
 3839 			if $real_destref->{use_sprintf};
 3840 		$real_destref->{delete_if_unique}="" 
 3841 			if $real_destref->{delete_if_unique};
 3842 
 3843 		# join dest_category and dest_type and save as "dest".
 3844 		my $dest_category=$gui_destref->{dest_category};
 3845 		delete $real_destref->{dest_type};
 3846 		delete $real_destref->{dest_category};
 3847 		my $dest=$dest_type;
 3848 		$dest.=" $dest_category" 
 3849 			if defined $dest_category && length $dest_category;
 3850 		$real_destref->{dest}=$dest;
 3851 		push @$real_destsref, $real_destref;
 3852 	}
 3853 
 3854 	return $real_destsref;
 3855 }
 3856 
 3857 
 3858 sub dests_validate {
 3859 	my ($destsref)=@_;
 3860 	my $num_dests=@$destsref;
 3861 	my $unique_seen=0;
 3862 	return "No dests present" if !$num_dests;
 3863 	for (my $i=0; $i<$num_dests; $i++) {
 3864 		my $d="Destination ".($i+1);
 3865 		my $destref=$destsref->[$i];
 3866 		my $dest_type=$destref->{dest_type};
 3867 		return "$d: missing Destination Type" 
 3868 			if &empty($dest_type);
 3869 		return "$d: unknown dest_type $dest_type" 
 3870 			if !exists $dests_minimum{$dest_type};
 3871 		return "$d: $dest_type must be the only destination if present"
 3872 			if $num_dests>1 && ($dest_type eq "SKIP" or $dest_type eq "LAST");
 3873 		$unique_seen=1 if $dest_type eq "UNIQUE";
 3874 		return "$d: any CATEGORY dest must be before any UNIQUE dest"
 3875 			if $dest_type eq "CATEGORY" && $unique_seen;
 3876 		foreach my $key (@{$dests_minimum{$dest_type}}) {
 3877 			return "$d: missing required parameter $key"
 3878 				if !defined $destref->{$key} or not length $destref->{$key};
 3879 		}
 3880 	}
 3881 	return undef; # if we get this far, all appears well
 3882 }
 3883 
 3884 
 3885 sub gui_mode_description {
 3886 
 3887 	my $window=&gui_mode_window("description of events", $gui_mode_main);
 3888 
 3889 	my $text=$window->Scrolled(qw/ROText -wrap word/);
 3890 	$text->pack(@Ptef);
 3891 
 3892 	foreach my $entry (@_) {
 3893 		my $hashref=$gui_mode_hashref{$entry};
 3894 
 3895 		my $event_string=&process_tags("$real_mode_output_format", $hashref);
 3896 
 3897 		$text->insert("end", "Event:\n");
 3898 		$text->insert("end", "$event_string\n\n");
 3899 
 3900 		my $description = &config_check($hashref, "description");
 3901 		$description = "no description for this event in the config" if
 3902 			!defined $description;
 3903 
 3904 		$text->insert("end", "Event description:\n");
 3905 		$text->insert("end", "$description\n\n\n\n");
 3906 	}
 3907 
 3908 	$window->Button(-text=>"OK", -command=>sub{$window->destroy})
 3909 		->pack(-side=>"bottom");
 3910 }
 3911 
 3912 
 3913 sub gui_mode_login {
 3914 	foreach my $entry (@_) {
 3915 		my $host=$gui_mode_hashref{$entry}{h};
 3916 		
 3917 		return &gui_mode_user_error("Cannot log in because no login method is ",
 3918 			"defined in the config.  Either set default_login_action or add a line ",
 3919 			"for this host to login_action.") 
 3920 				if !$default_login_action && !exists $login_action{$host};
 3921 		my $action=$default_login_action;
 3922 		$action=$login_action{$host} if exists $login_action{$host};
 3923 
 3924 		&gui_mode_do_action($action, $entry);
 3925 	}
 3926 }
 3927 
 3928 
 3929 sub gui_mode_user_error {
 3930 	my $message="@_";
 3931 	$gui_mode_main->Dialog(-text=>$message, -bitmap=>"error",
 3932 		-title=>"Error", -buttons=>["Acknowledge"])->Show;
 3933 }
 3934 
 3935 
 3936 sub gui_mode_user_warn {
 3937 	my $message="@_";
 3938 	$gui_mode_main->Dialog(-text=>$message, -bitmap=>"info",
 3939 		-title=>"Warning", -buttons=>["Acknowledge"])->Show;
 3940 }
 3941 
 3942 
 3943 sub gui_mode_configure_state {
 3944 	return (-state=> "disabled") if $gui_mode_configure_disabled;
 3945 	return (-state=> "normal");
 3946 }
 3947 
 3948 
 3949 sub gui_mode_init {
 3950 
 3951 	$gui_mode_main=Tk::MainWindow->new;
 3952 	$gui_mode_main->title("$appname / main");
 3953 
 3954 	my $menuframe=$gui_mode_main->Frame(@frameargs);
 3955 	$menuframe->pack(qw/-expand 0 -side top -fill x/);
 3956 
 3957 	my ($m, $c); # menu, cascade
 3958 	my @m_pack=qw/-side left -expand 0 -fill x/;
 3959 
 3960 	$m=$menuframe->Menubutton(qw/-text File -tearoff 0 -underline 0/);
 3961 	$m->command(-label => '~Save config...', -command => 
 3962 		\&gui_mode_save_config, &gui_mode_configure_state);
 3963 	$m->checkbutton(-label => '~Autosave config', -variable =>
 3964 		\$gui_mode_config_autosave, -command => sub { 
 3965 			$is_local{var}{gui_mode_config_autosave}=1;
 3966 			&gui_mode_config_dirty; 
 3967 		}, &gui_mode_configure_state);
 3968 	$m->checkbutton(-label => "~Pause", -variable=>\$gui_mode_paused, 
 3969 		-command=>\&gui_mode_status_paused, 
 3970 		-accelerator=>"$gui_mode_modifier+Space");
 3971 	$gui_mode_main->bind("<$gui_mode_modifier-space>"=>\&gui_mode_toggle_pause);
 3972 	$m->command(-label => '~Backlogs...', -command => \&gui_mode_backlogs);
 3973 	$m->separator;
 3974 	$m->command(-label => '~Restart', -command => \&gui_mode_restart,
 3975 		-accelerator=>"$gui_mode_modifier+R");
 3976 	$gui_mode_main->bind("<$gui_mode_modifier-r>" => \&gui_mode_restart);
 3977 	$m->command(-label => '~Close', -command => \&gui_mode_exit, 
 3978 		-accelerator=>"$gui_mode_modifier+W");
 3979 	$gui_mode_main->bind("<$gui_mode_modifier-w>" => \&gui_mode_exit);
 3980 	$m->command(-label => '~Quit', -command => \&gui_mode_exit, 
 3981 		-accelerator=>"$gui_mode_modifier+Q");
 3982 	$gui_mode_main->bind("<$gui_mode_modifier-q>" => \&gui_mode_exit);
 3983 	$m->pack(@m_pack);
 3984 
 3985 	$m=$menuframe->Menubutton(qw/-text Edit -tearoff 0 -underline 1/);
 3986 	$m->command(-label => 'Select ~All', -command=>\&gui_mode_select_all,
 3987 		-accelerator=>"$gui_mode_modifier+A");
 3988 	$gui_mode_main->bind("<$gui_mode_modifier-a>" => \&gui_mode_select_all);
 3989 	$m->command(-label => 'Select Unknowns', 
 3990 		-command=>\&gui_mode_select_unknowns);
 3991 	$m->command(-label => 'Select Knowns', 
 3992 		-command=>\&gui_mode_select_knowns);
 3993 	$m->command(-label => '~Unselect All', -command=>\&gui_mode_unselect_all);
 3994 	$m->separator;
 3995 	$m->command(-label => '~Find', -command=>\&gui_mode_find_dialog,
 3996 		-accelerator=>"<F3>");
 3997 	$gui_mode_main->bind("<F3>" => \&gui_mode_find_dialog);
 3998 	$m->command(-label => '~Clear Find', -command=>\&gui_mode_find_clear);
 3999 	$m->pack(@m_pack);
 4000 
 4001 	$m=$menuframe->Menubutton(qw/-text Go -tearoff 0 -underline 0/);
 4002 	$m->pack(@m_pack);
 4003 	$m->command(-label => 'Go to ~Top', 
 4004 		-command => \&gui_mode_go_top,
 4005 		-accelerator=>"<Home>");
 4006 	$gui_mode_main->bind("<Home>" => \&gui_mode_go_top);
 4007 	$m->command(-label => 'Go to ~Bottom', 
 4008 		-command => \&gui_mode_go_bottom,
 4009 		-accelerator=>"<End>");
 4010 	$gui_mode_main->bind("<End>" => \&gui_mode_go_bottom);
 4011 	$m->checkbutton(-label => "~Follow new", -variable=>\$gui_mode_follow_new, 
 4012 		-command=>\&gui_mode_go_bottom);
 4013 
 4014 	$m=$menuframe->Menubutton(qw/-text View -tearoff 0 -underline 0/);
 4015 	$m->pack(@m_pack);
 4016 	$m->command(-label => '~Hide Selected', 
 4017 		-command => \&gui_mode_hide_events);
 4018 	$m->command(-label => 'Hide Unselected', 
 4019 		-command => [\&gui_mode_hide_events, 1]);
 4020 	$m->command(-label => '~Unhide All', 
 4021 		-command => \&gui_mode_unhide_all);
 4022 	$m->separator;
 4023 	$m->command(-label => '~Resize Fields...', 
 4024 		-command => \&gui_mode_resize_fields);
 4025 	$m->command(-label => "~Patterns...", 
 4026 		-command => \&gui_mode_view_all_patterns,
 4027 		&gui_mode_configure_state);
 4028 	$m->command(-label => "~Event Config...", 
 4029 		-command => \&gui_mode_event_config,
 4030 		&gui_mode_configure_state);
 4031 	$m->command(-label => "~Misc configurables...",
 4032 		-command => \&gui_mode_misc_configurables,
 4033 		&gui_mode_configure_state);
 4034 
 4035 	$m=$menuframe->Menubutton(qw/-text Event -tearoff 0 -underline 0/);
 4036 	$m->pack(@m_pack);
 4037 
 4038 	my $sub= sub { &gui_mode_entry_clear($gui_mode_hlist->selectionGet) };
 4039 	$m->command(
 4040 		-label => "~Clear counts and remove items", 
 4041 		-command => $sub,
 4042 		-accelerator=>"$gui_mode_modifier+C",
 4043 		);
 4044 	$gui_mode_main->bind("<$gui_mode_modifier-c>" => $sub);
 4045 	$m->command(-label => "~Description of events", -command =>
 4046 		sub { &gui_mode_description($gui_mode_hlist->selectionGet); });
 4047 
 4048 	$m->command(-label => "~Save Events", -command => 
 4049 		sub { &gui_mode_save_event });
 4050 
 4051 	$sub = sub { &gui_mode_print };
 4052 	$m->command(-label => "~Print Events", -command => $sub ,
 4053 		-accelerator=>"$gui_mode_modifier+P",);
 4054 	$gui_mode_main->bind("<$gui_mode_modifier-p>" => $sub);
 4055 
 4056 	$m->command(-label => "Show first raw log entry per event",
 4057 		-command => sub { &gui_mode_show("%R\n", 
 4058 			$gui_mode_hlist->selectionGet)});
 4059 
 4060 	$m->command(-label => "Show all raw log entries per event",
 4061 		-command => sub { &gui_mode_show("%A", $gui_mode_hlist->selectionGet)},
 4062 		-state=>$keep_all_raw_logs? "normal" : "disabled" );
 4063 
 4064 	$m->command(-label => "Configure pattern",
 4065 		-command => sub { &gui_mode_configure_event_pattern(
 4066 			$gui_mode_hlist->selectionGet)},
 4067 		&gui_mode_configure_state);
 4068 
 4069 	$m->command(-label => "Configure event",
 4070 		-command => sub { &gui_mode_event_config(
 4071 			$gui_mode_hlist->selectionGet)},
 4072 		&gui_mode_configure_state);
 4073 
 4074 	my $cascade=$m->cascade(-label => "~Select Events with selected. . .",
 4075 		-tearoff=>0, &gui_mode_configure_state);
 4076 
 4077 	$cascade->command(-label => "category", 
 4078 		-command => sub { &gui_mode_select_like(["c"], 
 4079 		$gui_mode_hlist->selectionGet)});
 4080 
 4081 	$cascade->command(-label => "category and data", 
 4082 		-command => sub { &gui_mode_select_like(["c", "d"], 
 4083 		$gui_mode_hlist->selectionGet)});
 4084 
 4085 	$cascade->command(-label => "host", 
 4086 		-command => sub { &gui_mode_select_like(["h"], 
 4087 		$gui_mode_hlist->selectionGet)});
 4088 
 4089 	$cascade->command(-label => "category and host", 
 4090 		-command => sub { &gui_mode_select_like(["c", "h"], 
 4091 		$gui_mode_hlist->selectionGet)});
 4092 
 4093 	$cascade->command(-label => "category, data, and host", 
 4094 		-command => sub { &gui_mode_select_like(["c", "d", "h"], 
 4095 		$gui_mode_hlist->selectionGet)});
 4096 
 4097 	$cascade=$m->cascade(-label => "~Ignore Events with selected. . .",
 4098 		-tearoff=>0, &gui_mode_configure_state);
 4099 
 4100 	$cascade->command(-label => "category", 
 4101 		-command => sub { &gui_mode_ignore(["c"], 
 4102 		$gui_mode_hlist->selectionGet)});
 4103 
 4104 	$cascade->command(-label => "category and data", 
 4105 		-command => sub { &gui_mode_ignore(["c", "d"], 
 4106 		$gui_mode_hlist->selectionGet)});
 4107 
 4108 	$cascade->command(-label => "host", 
 4109 		-command => sub { &gui_mode_ignore(["h"], 
 4110 		$gui_mode_hlist->selectionGet)});
 4111 
 4112 	$cascade->command(-label => "category and host", 
 4113 		-command => sub { &gui_mode_ignore(["c", "h"], 
 4114 		$gui_mode_hlist->selectionGet)});
 4115 
 4116 	$cascade->command(-label => "category, data, and host", 
 4117 		-command => sub { &gui_mode_ignore(["c", "d", "h"], 
 4118 		$gui_mode_hlist->selectionGet)});
 4119 
 4120 	$m->command(-label => "~Login to hosts", -command => 
 4121 		sub { &gui_mode_login($gui_mode_hlist->selectionGet); });
 4122 	$c=$m->cascade(-label => "~Do action on events", -tearoff=>0);
 4123 	foreach my $action (sort keys %actions) {
 4124 		$c->command(-label=>$action, -command => 
 4125 				sub { &gui_mode_do_action(
 4126 					$action,
 4127 					$gui_mode_hlist->selectionGet); }
 4128 			);
 4129 	}
 4130 
 4131 	$m=$menuframe->Menubutton(qw/-text Help -tearoff 0 -underline 0/);
 4132 
 4133 	$m->pack(@m_pack, -side => "right");
 4134 
 4135 	$m->command(-label => "About", -command => sub { &gui_mode_about });
 4136 
 4137 	$gui_mode_hlist=$gui_mode_main->Scrolled(
 4138 		'HList', 
 4139 		-scrollbars	=> 'se',
 4140 		-columns	=> scalar @gui_mode_headers,
 4141 		-header		=> 1,
 4142 		-width		=> 150,
 4143 		-height		=> 10,
 4144 		-padx		=> 0,
 4145 		-pady		=> 0,
 4146 		-selectmode => "extended",
 4147 		-command	=> \&gui_mode_popup,
 4148 		-browsecmd  => \&gui_mode_select_update,
 4149 		);
 4150 
 4151 	$gui_mode_hlist->pack(@Ptef);
 4152 	
 4153 	for (my $i=0; $i<@gui_mode_headers; $i++) {
 4154 		my $name=$gui_mode_headers[$i];
 4155 		my $header=$gui_mode_hlist->resizeButton(-text=>$name, -column=>$i,
 4156 			-widget=>\$gui_mode_hlist);
 4157 		$gui_mode_hlist->header("create", $i, -itemtype=>"window", 
 4158 			-widget=>$header);
 4159 		$gui_mode_hlist->columnWidth($i, -char => $gui_mode_field_width{$name});
 4160 	}
 4161 
 4162 	$gui_mode_hlist->columnWidth(0, "");
 4163 	$gui_mode_hlist->header("create", 0, qw/-itemtype imagetext -bitmap error/);
 4164 
 4165 	my $statusbar=$gui_mode_main->Frame(@frameargs);
 4166 	$statusbar->pack(qw/-expand 0 -side top -fill x/);
 4167 
 4168 	my $pausebutton=$statusbar->Checkbutton(-text=>"Pause", 
 4169 		-variable=>\$gui_mode_paused, -command=>\&gui_mode_status_paused,
 4170 		-relief=>"raised", -borderwidth=>2);
 4171 	$pausebutton->pack(-side=>"left", -expand=>0, -fill=>"x");
 4172 
 4173 	my $raw_frame=$statusbar->Frame(@frameargs)->pack(@Plnf);
 4174 	$raw_frame->Label(-text=>"raw:")->pack(@Plnf);
 4175 	$raw_frame->Label(-textvariable=>\$gui_mode_raw)->pack(@Plnf);
 4176 
 4177 	my $eps_frame=$statusbar->Frame(@frameargs)->pack(@Plnf);
 4178 	$eps_frame->Label(-text=>"e/s:")->pack(@Plnf);
 4179 	$eps_frame->Label(-textvariable=>\$gui_mode_events_per_second)->pack(@Plnf);
 4180 
 4181 	my $total_frame=$statusbar->Frame(@frameargs)->pack(@Plnf);
 4182 	$total_frame->Label(-text=>"total:")->pack(@Plnf);
 4183 	$total_frame->Label(-textvariable=>\$gui_mode_total)->pack(@Plnf);
 4184 
 4185 	my $known_frame=$statusbar->Frame(@frameargs)->pack(@Plnf);
 4186 	$known_frame->Label(-text=>"knowns:")->pack(@Plnf);
 4187 	$known_frame->Label(-textvariable=>\$gui_mode_knowns)->pack(@Plnf);
 4188 
 4189 	my $unknown_frame=$statusbar->Frame(@frameargs)->pack(@Plnf);
 4190 	$unknown_frame->Label(-text=>"unknowns:")->pack(@Plnf);
 4191 	$unknown_frame->Label(-textvariable=>\$gui_mode_unknowns)->pack(@Plnf);
 4192 
 4193 	my $selected_frame=$statusbar->Frame(@frameargs)->pack(@Plnf);
 4194 	$selected_frame->Label(-text=>"selected:")->pack(@Plnf);
 4195 	$selected_frame->Label(-textvariable=>\$gui_mode_selected)->pack(@Plnf);
 4196 
 4197 	my $visible_count_frame=$statusbar->Frame(@frameargs)->pack(@Plnf);
 4198 	$visible_count_frame->Label(-text=>"visible:")->pack(@Plnf);
 4199 	$visible_count_frame->Label(-textvariable=>\$gui_mode_visible)->pack(@Plnf);
 4200 
 4201 	my $hidden_count_frame=$statusbar->Frame(@frameargs)->pack(@Plnf);
 4202 	$hidden_count_frame->Label(-text=>"hidden:")->pack(@Plnf);
 4203 	$hidden_count_frame->Label(-textvariable=>\$gui_mode_hidden)->pack(@Plnf);
 4204 
 4205 	my $statuslabel=$statusbar->Label(-textvariable=>\$gui_mode_status,
 4206 		-justify=>"left");
 4207 	$statuslabel->pack(@Plnf);
 4208 }
 4209 
 4210 
 4211 # update gui_mode_selected when selection changes
 4212 sub gui_mode_select_update {
 4213 	my @selected_entries=$gui_mode_hlist->selectionGet;
 4214 	$gui_mode_selected=scalar @selected_entries;
 4215 }
 4216 
 4217 
 4218 sub gui_mode_about {
 4219 	my $window=&gui_mode_window("about", $gui_mode_main);
 4220 	$window->Label(-text=>"$version_string")->pack(@Ptef);
 4221 
 4222 	my $frame=$window->Frame(@frameargs); $frame->pack(@Ptef);
 4223 	my $URLcopy=$URL;
 4224 	$frame->Label(-text=>"URL:")->pack(@Plnf);
 4225 	$frame->Entry(-textvariable=>\$URLcopy)->pack(@Plef);
 4226 
 4227 	my $text=$window->Scrolled(qw/ROText -wrap none/);
 4228 	$text->pack(@Ptef);
 4229 
 4230 	$text->insert("end", "Authors:\n\n");
 4231 
 4232 	my $fh=new FileHandle;
 4233 	$fh->open("<$authorsfile") or die "$prog: open $authorsfile: $!\n";
 4234 	while (defined($_=<$fh>)) {
 4235 		$text->insert("end", "  ".$_);
 4236 	}
 4237 	close $fh;
 4238 
 4239 	$window->Button(-text=>"OK", -command=>sub{$window->destroy})
 4240 		->pack(-side=>"bottom");
 4241 }
 4242 
 4243 
 4244 sub gui_mode_select_all {
 4245 	my @entries=$gui_mode_hlist->info("children");
 4246 	return if !@entries;
 4247 
 4248 	my $from = shift @entries;
 4249 	my $to   = $from;
 4250 
 4251 	$to=pop @entries if @entries;
 4252 
 4253 	$gui_mode_hlist->selectionSet($from, $to);
 4254 
 4255 	&gui_mode_select_update;
 4256 
 4257 	return;
 4258 }
 4259 
 4260 
 4261 sub gui_mode_unselect_all {
 4262 	my @entries=$gui_mode_hlist->info("children");
 4263 	return if !@entries;
 4264 
 4265 	my $from = shift @entries;
 4266 	my $to   = $from;
 4267 
 4268 	$to=pop @entries if @entries;
 4269 
 4270 	$gui_mode_hlist->selectionClear($from, $to);
 4271 
 4272 	&gui_mode_select_update;
 4273 
 4274 	return;
 4275 }
 4276 
 4277 
 4278 sub gui_mode_select_unknowns {
 4279 	my @entries=$gui_mode_hlist->info("children");
 4280 	return if !@entries;
 4281 
 4282 	foreach my $entry (@entries) {
 4283 		my $hashref=$gui_mode_hashref{$entry};
 4284 		if ($$hashref{_u}) {
 4285 			$gui_mode_hlist->selectionSet($entry, $entry);
 4286 		}
 4287 	}
 4288 
 4289 	&gui_mode_select_update;
 4290 
 4291 	return;
 4292 }
 4293 
 4294 
 4295 sub gui_mode_select_knowns {
 4296 	my @entries=$gui_mode_hlist->info("children");
 4297 	return if !@entries;
 4298 
 4299 	foreach my $entry (@entries) {
 4300 		my $hashref=$gui_mode_hashref{$entry};
 4301 		if (!$$hashref{_u}) {
 4302 			$gui_mode_hlist->selectionSet($entry, $entry);
 4303 		}
 4304 	}
 4305 
 4306 	&gui_mode_select_update;
 4307 
 4308 	return;
 4309 }
 4310 
 4311 
 4312 sub gui_mode_go_top {
 4313 	my @all_entries=$gui_mode_hlist->info("children"); 
 4314 
 4315 	return if !@all_entries;
 4316 	my $top=shift @all_entries;
 4317 	debug "top: $top\n";
 4318 
 4319 	$gui_mode_hlist->see($top);
 4320 }
 4321 
 4322 
 4323 sub gui_mode_go_bottom {
 4324 	my @all_entries=$gui_mode_hlist->info("children"); 
 4325 
 4326 	return if !@all_entries;
 4327 	my $bottom=pop @all_entries;
 4328 	debug "bottom: $bottom\n";
 4329 
 4330 	$gui_mode_hlist->see($bottom);
 4331 }
 4332 
 4333 
 4334 # by default, hide all selected events.  If you set hide_unselected, hides all
 4335 # unselected events
 4336 sub gui_mode_hide_events {
 4337 	my $hide_unselected=shift;
 4338 
 4339 	my @selected_entries=$gui_mode_hlist->selectionGet;
 4340 	my @hide_entries;
 4341 
 4342 	if (!$hide_unselected) {
 4343 		@hide_entries=@selected_entries;
 4344 	} else {
 4345 		my @all_entries=$gui_mode_hlist->info("children"); 
 4346 		@hide_entries=&set_difference(\@all_entries, \@selected_entries);
 4347 	}
 4348 
 4349 	foreach my $entry (@hide_entries) {
 4350 		next if $gui_mode_hlist->info("hidden", $entry); # already hidden
 4351 		$gui_mode_hlist->selectionClear($entry, $entry);
 4352 		$gui_mode_hlist->hide("entry", $entry);
 4353 		$gui_mode_hidden++;
 4354 		$gui_mode_visible--;
 4355 	}
 4356 
 4357 	&gui_mode_select_update;
 4358 
 4359 }
 4360 
 4361 
 4362 sub gui_mode_unhide_all {
 4363 	my @all_entries=$gui_mode_hlist->info("children");
 4364 	foreach my $entry (@all_entries) {
 4365 		$gui_mode_hlist->show("entry", $entry);
 4366 	}
 4367 	$gui_mode_hidden=0;
 4368 	$gui_mode_visible=$gui_mode_total;
 4369 }
 4370 
 4371 
 4372 sub gui_mode_status {
 4373 	my $state=shift;
 4374 	if (defined $state) {
 4375 		$gui_mode_status=$state;
 4376 		if (!$gui_mode_busy) {
 4377 			$gui_mode_main->Busy;
 4378 			$gui_mode_busy=1;
 4379 		}
 4380 	} else {
 4381 		if (!$gui_mode_paused) {
 4382 			$gui_mode_status="Ready";
 4383 		} else {
 4384 			$gui_mode_status="Paused";
 4385 		}
 4386 		if ($gui_mode_busy) {
 4387 			$gui_mode_main->Unbusy;
 4388 			$gui_mode_busy=0;
 4389 		}
 4390 	}
 4391 
 4392 	# update events per second display if applicable
 4393 	my $now=time;
 4394 	if ($now-$gui_mode_raw_last_time>0) {
 4395 		$gui_mode_events_per_second=int(($gui_mode_raw-$gui_mode_raw_last_count)/
 4396 			($now-$gui_mode_raw_last_time));
 4397 		$gui_mode_raw_last_count=$gui_mode_raw;
 4398 		$gui_mode_raw_last_time=$now;
 4399 	}
 4400 
 4401 	$gui_mode_main->update;
 4402 }
 4403 
 4404 
 4405 sub gui_mode_status_scanning {
 4406 	my $now=time;
 4407 	if ($now - $gui_mode_status_updated >= 1) {
 4408 		&gui_mode_status("Scanning files" . " ." x (1 + $now % 3));
 4409 		$gui_mode_status_updated=$now;
 4410 	}
 4411 }
 4412 
 4413 
 4414 # call when there's been a change in pause status
 4415 sub gui_mode_status_paused {
 4416 		if ($gui_mode_paused) {
 4417 			$gui_mode_status="Paused";
 4418 			$gui_mode_main->waitVariable(\$gui_mode_paused);
 4419 		}
 4420 		$gui_mode_status="Resumed";
 4421 }
 4422 
 4423 
 4424 sub do_action {
 4425 	my $action=shift;
 4426 	my $entry_tags_ref=shift;
 4427 	my $force=shift;
 4428 
 4429 	if (! exists $actions{$action}) {
 4430 		warn "$prog: no such action: $action\n";
 4431 		return;
 4432 	}
 4433 
 4434 	my $action_format = $default_action_format;
 4435 	$action_format    = $actions{$action}{action_format}
 4436 		if exists $actions{$action}{action_format};
 4437 
 4438 	my $throttle;
 4439 	$throttle=$actions{$action}{throttle} 
 4440 		if exists $actions{$action}{throttle};
 4441 
 4442 	my $throttle_format = $default_throttle_format;
 4443 	$throttle_format    = $actions{$action}{throttle_format}
 4444 		if exists $actions{$action}{throttle_format};
 4445 
 4446 	my $message = &process_tags($action_format, $entry_tags_ref);
 4447 
 4448 	my $throttle_key = &process_tags($throttle_format, $entry_tags_ref);
 4449 
 4450 	return if !$force && defined $throttle && 
 4451 		&throttle($throttle, $throttle_key);
 4452 
 4453 	my $command=&process_tags($actions{$action}{command}, $entry_tags_ref);
 4454 	if (exists $actions{$action}{window}) {
 4455 		my $title=&process_tags($actions{$action}{window}, $entry_tags_ref);
 4456 		return warn "$prog: action $action needs a window, but window_command not defined\n"
 4457 			if !$window_command;
 4458 		my $big_command=&process_tags($window_command, 
 4459 			{ %$entry_tags_ref, C=>$command, t=> $title }
 4460 		);
 4461 		$command=$big_command;
 4462 	}
 4463 	
 4464 
 4465 	my $use_pipe=$actions{$action}{use_pipe};
 4466 	my $keep_open=$actions{$action}{keep_open};
 4467 
 4468 	if (! defined $use_pipe) {
 4469 		system("$command&");
 4470 		return;
 4471 	}
 4472 
 4473 	my $commandfd;
 4474 
 4475 	# deal with an existing keep_open if we have keep_open set
 4476 	if (!defined $keep_open || !defined $real_mode_keep_open{action}) {
 4477 		# nothing to do
 4478 	} elsif ($real_mode_keep_open{action} ne $action) {
 4479 		# if we already have a different keep_open action, close it
 4480 		close $real_mode_keep_open{handle};
 4481 		delete $real_mode_keep_open{action};
 4482 		delete $real_mode_keep_open{handle};
 4483 	} elsif ($real_mode_keep_open{action} eq $action) {
 4484 		$commandfd=$real_mode_keep_open{handle};
 4485 		die "Internal error: no commandfd" unless $commandfd;
 4486 	}
 4487 
 4488 	# if no commandfd yet (ie. no keep_open for this action) then start
 4489 	# one
 4490 	if (!$commandfd) {
 4491 		$commandfd=new FileHandle("|$command");
 4492 
 4493 		if (!$commandfd) {
 4494 			warn "$prog: Unable to run $command\n";
 4495 			return;
 4496 		}
 4497 		
 4498 		if (defined $keep_open) {
 4499 			$real_mode_keep_open{action}=$action;
 4500 			$real_mode_keep_open{handle}=$commandfd;
 4501 		}
 4502 	}
 4503 
 4504 	print $commandfd $message;
 4505 	$commandfd->flush;
 4506 
 4507 	close $commandfd if !defined $keep_open;
 4508 }
 4509 
 4510 
 4511 sub throttle {
 4512 	my $timespec=shift;
 4513 	my $key=shift;
 4514 
 4515 	my $seconds;
 4516 	my $now=time;
 4517 
 4518 	if ($timespec =~ m{^(?:(?:(\d+)\:)?(\d+)\:)?(\d+)$}) {
 4519 		$seconds=(defined $1?$1:0) * 3600 + (defined $2?$2:0) * 60 + $3;
 4520 	} else {
 4521 		warn "$prog: timespec in 'throttle $key' is illegal\n";
 4522 		return 0;
 4523 	}
 4524 
 4525 	if (exists $throttle_state{$key} &&
 4526 		$throttle_state{$key}+$seconds > $now) {
 4527 			return 1;
 4528 	}
 4529 
 4530 	$throttle_state{$key}=$now;
 4531 	return 0;
 4532 }
 4533 
 4534 
 4535 sub read_paragraph {
 4536 	my $lines_arr_ref=shift || die "Internal error";
 4537 	my $line_ref=shift || die "Internal error";
 4538 
 4539 	my @return;
 4540 	while (defined ($_=$$lines_arr_ref[++$$line_ref]) && !m{^\s*$}) {
 4541 		chomp;
 4542 		s{^\s+}{};
 4543 		next if m{^\#};
 4544 		push @return, $_;
 4545 	}
 4546 	return @return;
 4547 }
 4548 
 4549 sub process_tags {
 4550 	my $string=shift;
 4551 	my $tags_ref=shift || die "Internal error: missing arg";
 4552 	my $tag_char=shift;
 4553 
 4554 	$tag_char='%' unless defined $tag_char; # a nice default
 4555 
 4556 	my $pre_string=$string;
 4557 
 4558 	$string =~ s{\\(.)}{
 4559 		defined $backslash_tags{$1} ? $backslash_tags{$1} :
 4560 		die "undefined backslash tag in '$pre_string': \\$1\n"}eg;
 4561 	$string =~ s{\Q$tag_char\E(\-?\d*)([^\d\-])}{
 4562 		defined $$tags_ref{$2} ?  sprintf("%${1}s", $$tags_ref{$2}) :
 4563 		die "undefined tag in '$pre_string': \%$1$2\n"}eg;
 4564 	return $string;
 4565 }
 4566 
 4567 # some (evil) globals to get around scope issues
 4568 my ($global_file, $global_line, %config_state);
 4569 
 4570 sub config_parse {
 4571 	my $fh=shift || die "$prog: Internal error: undefined arg";
 4572 	my $filename=shift || die "$prog: Internal error: undefined arg";
 4573 	my $config_scalar=shift || die "$prog: Internal error: undefined arg";
 4574 	my $config_array=shift || die "$prog: Internal error: undefined arg";
 4575 	my $depth=shift;
 4576 
 4577 	die "$prog: Internal error: undefined arg" unless defined $depth;
 4578 
 4579 	$global_file=$filename;
 4580 	$global_line=0;
 4581 
 4582 	&config_die("you've exceeded $include_depth_limit levels of includes")
 4583 		if $depth>=$include_depth_limit;
 4584 
 4585 	my ($keyword, $vartype, $varname, @arrvalue, $varvalue);
 4586 	my $config_version=undef;
 4587 	my $file_version=undef;
 4588 	my @lines;
 4589 
 4590 	# use a state table for the config
 4591 	my $state='toplevel';
 4592 
 4593 	&config_state_populate if !%config_state;
 4594 
 4595 	# variables designed to deal with fancy pattern configs
 4596 	my ($logtype, $pattern, $format, $count, $dest, $eventref);
 4597 	my (%my_patterns);
 4598 	my $destref;
 4599 
 4600 	# variables designed to deal with fancy category configs
 4601 	my ($category);
 4602 
 4603 	# variables for fancy action config
 4604 	my ($action);
 4605 
 4606 	while(<$fh>) {
 4607 		chomp;
 4608 		push @lines, $_;
 4609 		last if m{^\s*\@\@end\s*$};
 4610 	}
 4611 
 4612 	&config_preprocessor(\@lines, $filename);
 4613 
 4614 	for (my $line=0; $line<@lines; $line++) {
 4615 		$_=$lines[$line];
 4616 
 4617 		# update for each line, or recursive include will play havoc
 4618 		$global_file=$filename;
 4619 		$global_line=$line;
 4620 
 4621 		next if m{^\s*\#};
 4622 		next if m{^\s*$};
 4623 		s{^\s*}{};
 4624 
 4625 		my $is_local=0;
 4626 		my $nowarn=0;
 4627 		
 4628 		while (s{^(local|nowarn)\s+}{}i) {
 4629 			$is_local=1 if lc $1 eq "local";
 4630 			$nowarn=1 if lc $1 eq "nowarn";
 4631 		}
 4632 			
 4633 		if (s{^(\S+)\s*}{}) {
 4634 			my $keyword=lc $1;
 4635 			&config_check_state($state, $keyword);
 4636 			if ($keyword =~ m{^(set|add|remove|prepend)$}) {
 4637 				$state='toplevel';
 4638 				if (! s{^(\S+)\s*}{} || 
 4639 					($vartype=$1, $vartype!~m{^(arr|var)$})) {
 4640 					&config_die("$keyword should be followed by arr or var");
 4641 				}
 4642 				if (! s{^\s*([^\s\=]+)\s*}{}) {
 4643 					&config_die("$vartype should be followed by name");
 4644 				}
 4645 				$varname=$1;
 4646 				&config_die("'$varname' is not a legal variable name")
 4647 					if $varname !~ m{([A-Za-z]\w+)};
 4648 				&config_die("keyword 'remove' not allowed with vartype 'var'")
 4649 					if $vartype eq 'var' && $keyword eq 'remove';
 4650 				&config_die("can't find '=' in the right place")
 4651 					unless s{^\s*\=\s*}{};
 4652 				if ($vartype eq 'var') {
 4653 					$varvalue=$_;
 4654 					if ($keyword eq 'set') {
 4655 						$config_scalar->{$varname}=$varvalue;
 4656 						$is_local{var}{$varname}=1 if $is_local;
 4657 					} elsif ($keyword eq 'add') {
 4658 						$config_scalar->{$varname}.=$varvalue;
 4659 						$is_local{var}{$varname}=1 if $is_local;
 4660 					} elsif ($keyword eq 'prepend') {
 4661 						$config_scalar->{$varname}=$varvalue.
 4662 							$config_scalar->{$varname};
 4663 						$is_local{var}{$varname}=1 if $is_local;
 4664 					} else {
 4665 						&config_die("Internal error: unknown keyword ".
 4666 							"$keyword for vartype $vartype");
 4667 					}
 4668 					&import_scalar($varname, $config_scalar->{$varname}) 
 4669 						if exists $import_scalars{$varname};
 4670 				} elsif ($vartype eq 'arr') {
 4671 					@arrvalue=&read_paragraph(\@lines, \$line);
 4672 					if ($keyword eq 'set') {
 4673 						$config_array->{$varname} = [ @arrvalue ];
 4674 						$is_local{arr}{$varname}=1 if $is_local;
 4675 					} elsif ($keyword eq 'add') {
 4676 						push @{$config_array->{$varname}}, @arrvalue;
 4677 						$is_local{arr}{$varname}=1 if $is_local;
 4678 					} elsif ($keyword eq 'prepend') {
 4679 						unshift @{$config_array->{$varname}}, @arrvalue;
 4680 						$is_local{arr}{$varname}=1 if $is_local;
 4681 					} elsif ($keyword eq 'remove') {
 4682 						my @items_to_remove=&unique(@arrvalue);
 4683 						s{([^\w])}{\\$1}g foreach @items_to_remove;
 4684 						my $remove_pattern=join('|', @items_to_remove);
 4685 						$remove_pattern=qr{^(?:$remove_pattern)$};
 4686 						my $size_before=@{$config_array->{$varname}};
 4687 						@{$config_array->{$varname}}=
 4688 							grep(!m{$remove_pattern}, 
 4689 								@{$config_array->{$varname}});
 4690 						warn "wasn't able to remove all requested elements ".
 4691 								"from $varname" 
 4692 							if $size_before-@{$config_array->{$varname}}<
 4693 								@items_to_remove;
 4694 					} else {
 4695 						die "Internal error: unknown keyword $keyword ".
 4696 							"for vartype $vartype";
 4697 					}
 4698 					&import_array($varname, @{$config_array->{$varname}})
 4699 						if exists $import_arrays{$varname};
 4700 				} else {
 4701 					die "Internal error: unknown vartype $vartype";
 4702 				}
 4703 			} elsif ($keyword eq 'logtype:') {
 4704 				$state='seen logtype';
 4705 				$logtype=&config_arguments($_, $keyword, 1);
 4706 			} elsif ($keyword eq 'pattern:') {
 4707 				$state='seen pattern';
 4708 				$pattern=&config_arguments($_, $keyword, 1);
 4709 				$format=$count=$dest=undef;
 4710 				push @{$my_patterns{$logtype}}, $pattern;
 4711 				$is_local{pattern}{$logtype}{$pattern}=1 if $is_local;
 4712 			} elsif ($keyword =~ m{^delete_if_unique:?$}) {
 4713 				$state='expecting dest';
 4714 				&config_arguments($_, $keyword, 0, 0);
 4715 				$destref->{delete_if_unique}="";
 4716 			} elsif ($keyword =~ m{^use_sprintf:?$}) {
 4717 				$state='expecting dest';
 4718 				&config_arguments($_, $keyword, 0, 0);
 4719 				$destref->{use_sprintf}="";
 4720 			} elsif ($keyword eq 'format:') {
 4721 				$state='expecting dest';
 4722 				$format=&config_arguments($_, $keyword, 1);
 4723 				$destref->{format}=$format;
 4724 			} elsif ($keyword eq 'count:') {
 4725 				$state='expecting dest';
 4726 				$count=&config_arguments($_, $keyword, 1);
 4727 				$destref->{count}=$count;
 4728 			} elsif ($keyword eq 'dest:') {
 4729 				$state='seen dest';
 4730 				$dest=&config_arguments($_, $keyword, 1);
 4731 				# special destinations: SKIP, LAST, UNIQUE whatever
 4732 				&config_die("dest $dest may not contain backslash\n")
 4733 					if $dest =~ m{\\};
 4734 				push @categories, $dest if $dest ne 'LAST' && $dest ne 'SKIP';
 4735 				$categories[$#categories]=~s{^(UNIQUE|CATEGORY)\s+}{};
 4736 				&config_die("don't have a format for dest: $dest")
 4737 					if !defined $format && $dest ne 'LAST' && $dest ne 'SKIP';
 4738 				$destref->{dest}=$dest;
 4739 				push @{$dests{$logtype}{$pattern}}, $destref;
 4740 				undef $destref;
 4741 			} elsif ($keyword eq 'dest_delete:') {
 4742 				$dest=&config_arguments($_, $keyword, 1);
 4743 				my $found=0;
 4744 				for (my $i=0; $i<@{$dests{$logtype}{$pattern}}; $i++) {
 4745 					my $destref=$dests{$logtype}{$pattern}[$i];
 4746 					if ($dest eq $destref->{dest}) {
 4747 						splice(@{$dests{$logtype}{$pattern}}, $i, 1);
 4748 						$found=1;
 4749 						last;
 4750 					}
 4751 				}
 4752 				&config_warn("can't remove dest $dest") if !$found && !$nowarn;
 4753 			} elsif ($keyword eq 'category:') {
 4754 				$state='seen category';
 4755 				$category=&config_arguments($_, $keyword, 1);
 4756 				push @categories, $category;
 4757 				$is_local{category}{$category}=1 if $is_local;
 4758 			} elsif ($keyword =~ m{^(filter|sort|derive|color|description|do_action|priority):$}) {
 4759 				$keyword=$1;
 4760 				my $arg=&config_arguments($_, $keyword, 1);
 4761 				my %func=(
 4762 					filter => \&filter,
 4763 					sort   => \&sort_keys,
 4764 					derive => \&derive,
 4765 					color  => \&color,
 4766 					do_action =>	\&do_nothing,
 4767 					throttle  =>	\&do_throttle,
 4768 					description =>	\&do_nothing,
 4769 					priority =>	\&priority,
 4770 					);
 4771 				die "Internal error: no handler for $keyword" 
 4772 					if !exists $func{$keyword};
 4773                 eval { &{$func{$keyword}}($arg); };
 4774                 &config_die("Unknown $keyword syntax in $arg: $@\n") if $@;
 4775 				if ($state eq "seen category") {
 4776 					&config_warn("category $category already has a $keyword ".
 4777                     	"($categories{$category}{$keyword}) ".
 4778                     	"but has been defined as new $keyword $arg")
 4779                     	if exists $categories{$category}{$keyword} &&
 4780 							$categories{$category}{$keyword} ne $arg && 
 4781 							!$nowarn;
 4782 					$categories{$category}{$keyword}=$arg;
 4783 				} elsif ($state eq "seen event") {
 4784 					$eventref->{val}{$keyword}=$arg;
 4785 				} elsif ($state =~ m{^((seen|expecting) dest|seen pattern)$}) {
 4786 					$state="expecting dest";
 4787 					$destref->{$keyword}=$arg;
 4788 				} else {
 4789 					die("internal error: state $state");
 4790 				}
 4791 			} elsif ($keyword eq 'action:') {
 4792 				$state='seen action';
 4793 				$action=&config_arguments($_, $keyword, 1);
 4794 				$is_local{action}{$action}=1 if $is_local;
 4795 			} elsif ($keyword =~ m{^(command|window|throttle):$}) {
 4796 				$keyword=$1;
 4797 				my $arg=&config_arguments($_, $keyword, 1);
 4798 				$actions{$action}{$keyword}=$arg;
 4799 			} elsif ($keyword =~ m{^(use_pipe|keep_open):?$}) {
 4800 				$keyword=$1;
 4801 				&config_arguments($_, $keyword, 0, 0);
 4802 				$actions{$action}{$keyword}="";
 4803 			} elsif ($keyword eq 'event:') {
 4804 				$state='seen event';
 4805 				&config_arguments($_, $keyword, 0, 0);
 4806 				$eventref={};
 4807 				$eventref->{val}{is_local}=1 if $is_local;
 4808 				push @event_config, $eventref;
 4809 			} elsif ($keyword eq "match") {
 4810 				&config_die("no tag name specified, ie. one of: ".
 4811 					join(" ", keys %name2tag)) if !s{^(\S+)\s*\:\s*}{};
 4812 				my $tag_name=$1;
 4813 				&config_die("unknown tag name '$tag_name'.  Known are: ".
 4814 					join(" ", keys %name2tag)) if !exists $name2tag{$tag_name};
 4815 				my $val=&config_arguments($_, $keyword, 1);
 4816 				&config_warn("tag name $tag_name already defined")
 4817 					if exists $eventref->{$tag_name} && !$nowarn;
 4818 				$eventref->{$name2tag{$tag_name}}=$val;
 4819 			} elsif ($keyword eq 'block_comment') {
 4820 				$state='toplevel';
 4821 				&config_arguments($_, $keyword, 0, 0);
 4822 				&read_paragraph(\@lines, \$line);
 4823 			} elsif ($keyword eq 'include_if_exists' || $keyword eq 'include'){
 4824 				$state='toplevel';
 4825 				my $filename=&config_arguments($_, $keyword, 1, 1);
 4826 				&config_flush(\%my_patterns); # see comment before the sub
 4827 				&include_file($filename, $keyword eq 'include'?1:0, $depth);
 4828 			} elsif ($keyword eq 'include_dir_if_exists' || 
 4829 					$keyword eq 'include_dir' ) {
 4830 				$state='toplevel';
 4831 				my $dirname=&config_arguments($_, $keyword, 1, 1);
 4832 				&config_flush(\%my_patterns); # see comment before the sub
 4833 				&include_dir($dirname, $keyword eq 'include_dir'?1:0, $depth);
 4834 			} elsif ($keyword eq 'config_version') {
 4835 				$state='toplevel';
 4836 				warn "already saw config_version before"
 4837 					if defined $config_version;
 4838 				$config_version=&config_arguments($_, $keyword, 1, 1);
 4839 				&config_die("config_version should be a version string")
 4840 					unless $config_version =~ m{^([\d+\.]+)$};
 4841 				&config_die("config version '$config_version' is too old")
 4842 					if &funky_cmp($config_version, $minimum_version)<0;
 4843 				&config_die("config version '$config_version' is newer than I")
 4844 					if &funky_cmp($config_version, $current_version)>0;
 4845 			} elsif ($keyword eq 'file_version') {
 4846 				$state='toplevel';
 4847 				warn "already saw file_version before" if defined $file_version;
 4848 				$file_version=&config_arguments($_, $keyword, 1);
 4849 			} elsif ($keyword eq 'end') {
 4850 				$state='toplevel';
 4851 				&config_warn('"end" should be replaced with "@@end"') 
 4852 					if !$nowarn;
 4853 				&config_arguments($_, $keyword, 0, 0);
 4854 				last;
 4855 			} else {
 4856 				&config_die("unknown keyword in config: $keyword");
 4857 			}
 4858 		}
 4859 	}
 4860 
 4861 	$global_line='EOF';
 4862 
 4863 	&config_check_state($state, "end");
 4864 
 4865 	&config_die("'config_version' should be set (ie. to $current_version)")
 4866 		if (! defined $config_version);
 4867 
 4868 	push @config_versions, sprintf("%-50s %-8s %s\n", $filename, 
 4869 		(defined $config_version? $config_version: ""),
 4870 		(defined $file_version? $file_version: "")
 4871 		);
 4872 
 4873 	&config_flush(\%my_patterns); # see comment before the sub
 4874 }
 4875 
 4876 
 4877 # The upcoming code is obscene.  The reason it's necessary is
 4878 # that most of the config has a natural tendency to be overridden
 4879 # by later configs, while the pattern stuff has a natural
 4880 # tendency to be overridden by earlier configs.  I like
 4881 # consistency, so I chose overriding with later configs.  This
 4882 # code implements that by prepending the local patterns to the
 4883 # global pattern list.
 4884 # THIS MUST BE CALLED BEFORE DOING ANY INCLUDES.
 4885 
 4886 sub config_flush {
 4887 	my $patterns_ref=shift;
 4888 
 4889 	foreach my $logtype (keys %$patterns_ref) {
 4890 		unshift @{$patterns{$logtype}}, @{$$patterns_ref{$logtype}};
 4891 	}
 4892 
 4893 	undef %$patterns_ref;
 4894 }
 4895 
 4896 
 4897 # enforce that the config is in a state we allow
 4898 sub config_check_state {
 4899 	my $state=shift;
 4900 	my $keyword=shift;
 4901 
 4902 	$keyword=~s{\:$}{};
 4903 
 4904 	die "Internal error: no config_state for state $state"
 4905 		if !defined $config_state{$state};
 4906 	
 4907 	my %allowed_state;
 4908 	@allowed_state{split(/\s+/, $config_state{$state})}=undef;
 4909 
 4910 	&config_die("keyword $keyword when in state '$state' and expecting ".
 4911 			"one of: $config_state{$state}")
 4912 		if ! exists $allowed_state{$keyword};
 4913 }
 4914 
 4915 
 4916 # this function sets up %config_state, the variable that describes allowed
 4917 # config state transitions
 4918 sub config_state_populate {
 4919 	%config_state=(
 4920 		"toplevel" => 
 4921 			"set add remove prepend logtype action event ".
 4922 			"config_version file_version block_comment ".
 4923 			"include include_if_exists include_dir include_dir_if_exists ".
 4924 			"category end ",
 4925 		"seen logtype"   => "pattern ",
 4926 		"expecting dest" => "dest use_sprintf delete_if_unique format count ",
 4927 		"seen action"    => "command window throttle use_pipe keep_open ",
 4928 		"seen category"  => "filter sort derive ",
 4929 		"seen event"     => "match priority ",
 4930 		);
 4931 
 4932 	# The following commands are good in several states:
 4933 	foreach my $state ("expecting dest", "seen category", "seen event") {
 4934 		$config_state{$state}.="color description do_action priority ";
 4935 	}
 4936 	
 4937 	$config_state{"seen pattern"} = $config_state{"expecting dest"};
 4938 	$config_state{"seen dest"} = $config_state{"expecting dest"}.
 4939 		$config_state{"seen logtype"};
 4940 	
 4941 	# "dest_delete" can only be used after a dest or after a pattern
 4942 	$config_state{"seen pattern"} .= "dest_delete ";
 4943 	$config_state{"seen dest"}    .= "dest_delete ";
 4944 
 4945 	# the following states can transition back to toplevel:
 4946 	foreach my $state ("seen logtype", "seen dest", "seen action", 
 4947 			"seen category", "seen event") {
 4948 		$config_state{$state}.=$config_state{toplevel};
 4949 	}
 4950 }
 4951 
 4952 
 4953 sub config_arguments {
 4954 	my $string=shift;
 4955 	my $keyword=shift;
 4956 	my $min=shift;
 4957 	my $max=shift;
 4958 
 4959 	die "Internal error: min>max" if defined $max && $min>$max;
 4960 	$string=~s{^\s+}{};
 4961 
 4962 	if (defined $max && $max==0) {
 4963 		&config_die("keyword '$keyword' takes no args") if $string=~m{\S};
 4964 	} elsif ($min==1 && !defined $max) {
 4965 		&config_die("keyword '$keyword' needs an arg") if $string !~ m{\S};
 4966 		return $string;
 4967 	} elsif (defined $max && $max==1) {
 4968 		my @split = split(/\s+/, $string);
 4969 		&config_die("keyword '$keyword' may have no more than one arg")
 4970 			if @split>1;
 4971 		return $string;
 4972 	} else {
 4973 		die "Internal error";
 4974 	}
 4975 }
 4976 
 4977 
 4978 # this preprocessor should have identical features to the aide preprocessor
 4979 sub config_preprocessor {
 4980 	my $config_ref=shift || die "Internal error";
 4981 	my $filename=shift || die "Internal error";
 4982 
 4983 	my @if_else_stack; # did we last see an if or an else?
 4984 	my @active_stack=(1); # should we use lines we see, or are we in a false
 4985 		# if?  I couldn't think of a better name for this. . .
 4986 
 4987 	for (my $line=0; $line<@$config_ref; $line++) {
 4988 
 4989 		# perform variable substitutions
 4990 		$config_ref->[$line]=~
 4991 			s(\@\@\{(\w+)\})(exists $VAR{$1}? $VAR{$1}: "\@\@\{$1\}")eg;
 4992 
 4993 		$_=$config_ref->[$line];
 4994 		my $raw_line=$_;
 4995 
 4996 		# process directives if present
 4997 		if (s{^\s*(\@\@\S+)\s*}{}) {
 4998 			my $directive=$1;
 4999 
 5000 			$global_file=$filename;
 5001 			$global_line=$line;
 5002 
 5003 			if (0) {
 5004 			} elsif ($directive =~ m{^\@\@define$}) {
 5005 				if (!m{^(\S+)\s+(\S+)$}) {
 5006 					&config_die("directive $directive takes two arguments");
 5007 				}
 5008 				if ($active_stack[$#active_stack]) {
 5009 					$VAR{$1}=$2;
 5010 				}
 5011 			} elsif ($directive =~ m{^\@\@undef$}) {
 5012 				if (!m{^(\S+)\s*$}) {
 5013 					&config_die("directive $directive takes one argument");
 5014 				}
 5015 				if ($active_stack[$#active_stack]) {
 5016 					delete $VAR{$1};
 5017 				}
 5018 			} elsif ($directive =~ m{^\@\@ifn?def$}) {
 5019 				my $invert=($directive eq '@@ifdef'? 0 : 1);
 5020 				if (!m{^(\S+)\s*$}) {
 5021 					&config_die("directive $directive takes one argument");
 5022 				}
 5023 				push @active_stack, ($active_stack[$#active_stack] && 
 5024 					($invert xor exists($VAR{$1})));
 5025 				push @if_else_stack, "if";
 5026 			} elsif ($directive =~ m{^\@\@ifn?host$}) {
 5027 				my $invert=($directive eq '@@ifhost'? 0 : 1);
 5028 				if (!m{^(\S+)\s*$}) {
 5029 					&config_die("directive $directive takes one argument");
 5030 				}
 5031 				push @active_stack, ($active_stack[$#active_stack] && 
 5032 					($invert xor ($nodename eq $1)));
 5033 				push @if_else_stack, "if";
 5034 			} elsif ($directive =~ m{^\@\@ifn?os$}) {
 5035 				my $invert=($directive eq '@@ifos'? 0 : 1);
 5036 				if (!m{^(\S+)\s*$}) {
 5037 					&config_die("directive $directive takes one argument");
 5038 				}
 5039 				push @active_stack, ($active_stack[$#active_stack] && 
 5040 					($invert xor ($osname eq $1)));
 5041 				push @if_else_stack, "if";
 5042 			} elsif ($directive =~ m{^\@\@else$}) {
 5043 				if (!m{^\s*$}) {
 5044 					&config_die("directive $directive takes no arguments");
 5045 				}
 5046 				&config_die('@@else without @@if') if !@if_else_stack;
 5047 				&config_die('@@else when already in else')
 5048 					if $if_else_stack[$#if_else_stack] eq 'else';
 5049 				$if_else_stack[$#if_else_stack]='else';
 5050 				$active_stack[$#active_stack]=$active_stack[$#active_stack-1]
 5051 					&& !$active_stack[$#active_stack];
 5052 			} elsif ($directive eq '@@endif') {
 5053 				if (!m{^\s*$}) {
 5054 					&config_die("directive $directive takes no arguments");
 5055 				}
 5056 				&config_die("endif without if") if !@if_else_stack;
 5057 				pop @active_stack;
 5058 				pop @if_else_stack;
 5059 			} elsif ($directive eq '@@end') {
 5060 				if (!m{^\s*$}) {
 5061 					&config_die("directive $directive takes no arguments");
 5062 				}
 5063 				# don't need to do anything, handled by the config file reader
 5064 			} elsif ($directive eq '@@output') {
 5065 				if ($active_stack[$#active_stack]) {
 5066 					$raw_line.="\n" if $raw_line !~ m{\n$};
 5067 					print $raw_line;
 5068 				}
 5069 			} elsif ($directive eq '@@warn') {
 5070 				if ($active_stack[$#active_stack]) {
 5071 					$raw_line.="\n" if $raw_line !~ m{\n$};
 5072 					warn $raw_line;
 5073 				}
 5074 			} elsif ($directive eq '@@error') {
 5075 				if ($active_stack[$#active_stack]) {
 5076 					$raw_line.="\n" if $raw_line !~ m{\n$};
 5077 					&config_die($raw_line);
 5078 				}
 5079 			} else {
 5080 				&config_die("no such preprocessor directive: $directive");
 5081 			}
 5082 
 5083 			# Null the line to avoid confusing the config processor
 5084 			$config_ref->[$line]='#';
 5085 		} else { # not in a preprocessor directive
 5086 			if (!$active_stack[$#active_stack]) { # then we don't want the line
 5087 				$config_ref->[$line]='#';
 5088 			}
 5089 		}
 5090 	}
 5091 	$global_line='EOF';
 5092 
 5093 	&config_die("unterminated if") if @if_else_stack;
 5094 	die "Internal error" if @active_stack != 1;
 5095 }
 5096 
 5097 
 5098 sub config_die {
 5099 	defined(my $error=shift) || die "Internal error";
 5100 
 5101 	my $line=$global_line;
 5102 	$line++ unless $line eq "EOF";
 5103 
 5104 	die "$prog: config $global_file line $line: error: $error\n";
 5105 }
 5106 
 5107 sub config_warn {
 5108 	my $error=shift || die "Internal error";
 5109 
 5110 	my $line=$global_line;
 5111 	$line++ unless $line eq "EOF";
 5112 
 5113 	warn "$prog: config $global_file line $line: $error\n";
 5114 }
 5115 
 5116 sub include_file {
 5117 	defined(my $filename=shift) || die "$prog: internal err: missing arg";
 5118 	defined(my $must_exist=shift) || die "$prog: internal err: missing arg";
 5119 	defined(my $depth=shift) || die "$prog: internal err: missing arg";
 5120 
 5121 	$filename=&process_tags($filename, \%tags);
 5122 
 5123 	if (! -r $filename) {
 5124 		&config_die("included filename $filename is not readable\n")
 5125 			if $must_exist;
 5126 	} else {
 5127 		my $configfh=new FileHandle("<$filename") 
 5128 			|| die "$prog: open $filename: $!\n";
 5129 		config_parse($configfh, $filename, $config_scalar, $config_array, 
 5130 			$depth+1);
 5131 		push @config_files, $filename;
 5132 	}
 5133 }
 5134 
 5135 
 5136 sub include_dir {
 5137 	defined(my $dirname=shift) || die "$prog: internal err: missing arg";
 5138 	defined(my $must_exist=shift) || die "$prog: internal err: missing arg";
 5139 	defined(my $depth=shift) || die "$prog: internal err: missing arg";
 5140 
 5141 	if (!-r $dirname || !-x $dirname) {
 5142 		&config_die("included dir $dirname is not readable") if $must_exist;
 5143 		return;
 5144 	}
 5145 	local *DIR;
 5146 	opendir (DIR, $dirname) || &config_die("$prog: opendir $dirname: $!");
 5147 	! -d $_ && !&should_ignore_file($_) && &include_file($_, 1, $depth) 
 5148 		foreach (map {"$dirname/$_"} readdir DIR);
 5149 	closedir DIR;
 5150 }
 5151 
 5152 
 5153 sub make_pattern {
 5154 	return "^(?:".join("|", @_).")" if @_;
 5155 	return "^\777";
 5156 }
 5157 
 5158 
 5159 sub string_nocase_sort_helper {
 5160 	return lc $a cmp lc $b;
 5161 }
 5162 
 5163 
 5164 sub string_sort_helper {
 5165 	return $a cmp $b;
 5166 }
 5167 
 5168 
 5169 sub numeric_sort_helper {
 5170 	return $a <=> $b;
 5171 }
 5172 
 5173 
 5174 sub funky_sort_helper {
 5175 	my $a1=$a; my $b1=$b;
 5176 	$a1=~s{\s+(\d)}{ $1}g;
 5177 	$b1=~s{\s+(\d)}{ $1}g;
 5178 	while (length $a1 && length $b1) {
 5179 		if ($a1=~m{^(\d+)} && (my $a2=$1, $b1=~m{^(\d+)})) {
 5180 			my $ret = $a2<=>$1;
 5181 			return $ret if $ret;
 5182 			$a1=~s{^\d+}{};
 5183 			$b1=~s{^\d+}{};
 5184 		} elsif ($a1=~m{^([^\d]+)} && (my $a3=$1, $b1=~m{^([^\d]+)})) {
 5185 			my $ret = $a3 cmp $1;
 5186 			return $ret if $ret;
 5187 			$a1=~s{^[^\d]+}{};
 5188 			$b1=~s{^[^\d]+}{};
 5189 		} else {
 5190 			return $a1 cmp $b1;
 5191 		}
 5192 	}
 5193 	return $a1 cmp $b1;
 5194 }
 5195 
 5196 
 5197 # this sort function takes a hash ref and returns the keys sorted by their
 5198 # value in the hash.  Yes, this is weird.
 5199 sub sort_by_value {
 5200 	my $hash_ref=shift;
 5201 	my @arr=@_;
 5202 	return sort {$hash_ref->{$a} <=> $hash_ref->{$b}} @arr;
 5203 }
 5204 
 5205 # see comment for sort_by_value.
 5206 sub reverse_sort_by_value {
 5207 	my $hash_ref=shift;
 5208 	my @arr=@_;
 5209 	return sort {$hash_ref->{$b} <=> $hash_ref->{$a}} @arr;
 5210 }
 5211 
 5212 # funky_cmp sets up a call to funky_sort_helper.  Usually you'd expect things
 5213 # to be the other way around, but funky_sort_helper is more performance 
 5214 # critical, so I dropped some function overhead.
 5215 sub funky_cmp {
 5216 	local($a, $b)=(shift, shift);
 5217 	return &funky_sort_helper;
 5218 }
 5219 
 5220 
 5221 sub import_scalar {
 5222 	defined(my $name=shift) || die "Internal error";
 5223 	defined(my $value=shift) || die "Internal error";
 5224 
 5225 	eval "\$$name".'=$value;';
 5226 	die "$@" if $@;
 5227 	$in_config{$name}=1;
 5228 
 5229 	# these variables should take immediate effect
 5230 	$ENV{PATH}=$PATH;
 5231 	$tags{n}=$nodename;
 5232 	$tags{s}=$osname;
 5233 	$tags{r}=$osrelease;
 5234 	umask oct $umask;
 5235 }
 5236 
 5237 
 5238 sub import_array {
 5239 	defined(my $name=shift) || die "Internal error";
 5240 	my @values=@_;
 5241 
 5242 	if (exists $arrays_to_become_hashes{$name}) {
 5243 		my %hash;
 5244 		foreach my $entry (@values) {
 5245 			die "name has an entry that isn't in key, value format:\n$entry\n"
 5246 				if $entry !~ m{^([^,]+),\s+(.*)$};
 5247 			$hash{$1}=$2;
 5248 		}
 5249 		eval "\%$name".'=%hash';
 5250 		die "$@" if $@;
 5251 	} else {
 5252 		eval "\@$name".'=@values';
 5253 		die "$@" if $@;
 5254 	}
 5255 	$in_config{$name}=1;
 5256 }
 5257 
 5258 
 5259 sub import_config_vars {
 5260 	my $config_scalar=shift || die "$prog: Internal error: expecting arg";
 5261 	#mta my $config_array=shift || die "$prog: Internal error: expecting arg";
 5262 	my $PATH;
 5263 
 5264 	my ($i);
 5265 	my (%big_eval, %filename_pats);
 5266 
 5267 	# verify that all required arrays are defined
 5268 	foreach (@required_import_arrays) {
 5269 		die "config missing required array '$_'\n"
 5270 			unless exists $config_array->{$_};
 5271 	}
 5272 
 5273 	# verify that all required scalars are defined
 5274 	foreach (@required_import_scalars) {
 5275 		die "config missing scalar $_\n"
 5276 			unless exists $config_scalar->{$_};
 5277 	}
 5278 
 5279 	# standard arrays should already be imported, so remove them from
 5280 	# the namespace
 5281 	foreach (@required_import_arrays, @optional_import_arrays,
 5282 				@arrays_to_become_hashes) {
 5283 		next unless exists $config_array->{$_};
 5284 		delete $config_array->{$_};
 5285 	}
 5286 
 5287 	# standard scalars should already be imported, so remove them from
 5288 	# the namespace
 5289 	foreach (@required_import_scalars, @optional_import_scalars) {
 5290 		next unless exists $config_scalar->{$_};
 5291 		delete $config_scalar->{$_};
 5292 	}
 5293 
 5294 	# make sure certain arrays contain only unique elements
 5295 	@log_type_list      = &unique(@log_type_list);
 5296 	@optional_log_files = &unique(@optional_log_files);
 5297 
 5298 	foreach my $log_type (@log_type_list) {
 5299 		foreach my $ext (@per_log_required_scalar_exts) {
 5300 			my $i="${log_type}_$ext";
 5301 			die "config missing scalar $i required by logtype $log_type\n"
 5302 				unless exists $config_scalar->{$i};
 5303 		}
 5304 		foreach my $ext (@per_log_required_scalar_exts,
 5305 					@per_log_optional_scalar_exts) {
 5306 			my $i="${log_type}_$ext";
 5307 			next unless exists $config_scalar->{$i};
 5308 			$log_scalar{$log_type}{$ext}=$config_scalar->{$i};
 5309 			delete $config_scalar->{$i};
 5310 		}
 5311 		foreach my $ext (@per_log_required_array_exts) {
 5312 			my $i="${log_type}_$ext";
 5313 			die "config missing array $i required by logtype $log_type\n"
 5314 				unless exists $config_array->{$i};
 5315 		}
 5316 		foreach my $ext (@per_log_required_array_exts,
 5317 					@per_log_optional_array_exts) {
 5318 			my $i="${log_type}_$ext";
 5319 			next unless $config_array->{$i};
 5320 			$log_array{$log_type}{$ext}=$config_array->{$i};
 5321 			delete $config_array->{$i};
 5322 		}
 5323 	}
 5324 
 5325 	foreach my $key (%$config_array) {
 5326 		die "unknown array defined in config: $key\n";
 5327 	}
 5328 
 5329 	foreach my $key (%$config_scalar) {
 5330 		die "unknown scalar defined in config: $key\n";
 5331 	}
 5332 
 5333 	foreach my $var (@legacy_pats) {
 5334 		eval "\$${var}_pat=\$pat{$var} if ! defined ".
 5335 			"\$${var}_pat and exists \$pat{$var}; ";
 5336 		die "$@" if $@;
 5337 	}
 5338 
 5339 	#special rule for $PATH
 5340 	$ENV{PATH}=$PATH if $PATH;
 5341 
 5342 	#
 5343 	# special stuff for permission checking
 5344 	#
 5345 
 5346 	# if either "rw" array exists, only users in it are allowed
 5347 	if (@gui_mode_configure_allow_users || @gui_mode_configure_allow_groups) {
 5348 		$gui_mode_configure_disabled=1 if
 5349 			!&set_is_member($user, \@gui_mode_configure_allow_users) &&
 5350 			!&set_intersection(\@groups, \@gui_mode_configure_allow_groups);
 5351 	}
 5352 
 5353 	$gui_mode_configure_disabled=1 if
 5354 		&set_is_member($user, \@gui_mode_configure_deny_users) ||
 5355 		&set_intersection(\@groups, \@gui_mode_configure_deny_groups);
 5356 }
 5357 
 5358 
 5359 sub run_evals {
 5360 	for my $type (@log_type_list) {
 5361 		my $eval = $evals->{$type};
 5362 		eval $eval;
 5363 		die "$prog: error in eval for type $type (use -I evals to list): $@\n" 
 5364 			if $@;
 5365 	}
 5366 }
 5367 
 5368 sub build_log_stuff {
 5369 
 5370 	my ($i, %big_eval, %filename_pats);
 5371 
 5372 	# brass tacks time
 5373 	foreach my $type (@log_type_list) {
 5374 		for (my $relday=$relday_start; $relday >= $relday_end; $relday--) {
 5375 			$when{$type}{lc strftime($log_scalar{$type}{date_format}, 
 5376 					&relday2time($relday))}=$relday;
 5377 		}
 5378 
 5379 		# backwards compatibility for skip_list
 5380 		if (exists $log_array{$type}{skip_list}) {
 5381 			my $skip_pattern=&make_pattern(
 5382 				map( m{\$$} ? $_ : "$_.*", @{$log_array{$type}{skip_list}}));
 5383 			push @{$patterns{$type}}, $skip_pattern;
 5384 			push @{$dests{$type}{$skip_pattern}}, {dest=>"SKIP"};
 5385 			delete $log_array{$type}{skip_list};
 5386 		}
 5387 
 5388 		# backwards compatibility for raw_rules
 5389 		foreach my $raw_rule (@{$log_array{$type}{raw_rules}}) {
 5390 			my ($category, $pattern, $format, $code_hook)
 5391 				=split(m{, }, $raw_rule);
 5392 			eval "'foo' =~ m{$pattern}"; # check pattern for validity
 5393 			die "problem with pattern '$pattern':\n\t$@\n" if $@;
 5394 			die "1st field missing in rule '$raw_rule'" if ! defined $category;
 5395 			die "2nd field missing in rule '$raw_rule'" if ! defined $pattern;
 5396 			die "3rd field missing in rule '$raw_rule'" if ! defined $format;
 5397 			die "4th field removed in version 0.35, sorry" 
 5398 				if defined $code_hook;
 5399 			$pattern.=".*" unless $pattern=~m{\$$};
 5400 			my $destref={ dest=>$category, format=>$format };
 5401 			push @{$patterns{$type}}, $pattern;
 5402 			push @{$dests{$type}{$pattern}}, $destref;
 5403 			push @categories, $category;
 5404 			delete $log_array{$type}{raw_rules};
 5405 		}
 5406 
 5407 		my $unknowns="Unknowns for type $type";
 5408 		push @unknown_categories, $unknowns;
 5409 		push @categories, $unknowns;
 5410 
 5411 		push @categories, $other_host_message;
 5412 
 5413 		# now let's build the big eval
 5414 		my $big_eval="\$do_type{$type} = sub {\n";
 5415 		$big_eval.="\tdefined(my \$file=shift) || die qq($prog: missing arg);\n";
 5416 		$big_eval.="\tdefined(my \$fh=shift) || die qq($prog: missing arg);\n";
 5417 		$big_eval.="\n";
 5418 		$big_eval.="\tmy \$relday;\n";
 5419 		if (!$real_mode) {
 5420 			# only for real-mode do we need to maintain global per-file last
 5421 			# and multiplier state, so when not in real mode, let's have 
 5422 			# local variables to make this a bit faster
 5423 			$big_eval.="\tmy (\%last, \%multiplier);\n";
 5424 			$big_eval.="\t\$multiplier{\$file}=1;\n";
 5425 		}
 5426 		$big_eval.="\tmy \$host=\$nodename;\n";
 5427 		$big_eval.="\tmy \$raw_line;\n";
 5428 		$big_eval.="\tmy \$pos=tell \$fh;\n"
 5429 			if $real_mode;
 5430 
 5431 		$big_eval.="\twhile(<\$fh>) {\n";
 5432 
 5433 		$big_eval.="\t\tnext if ! defined;\n";
 5434 		$big_eval.="\t\t\$raw_line=\$_;\n";
 5435 		$big_eval.="\t\tmy \$entry_tags_ref;\n";
 5436 		$big_eval.="\t\tmy \%deletes_for_unique;\n";
 5437 		$big_eval.="\t\tif (!m{\\n\$}) {\n";
 5438 		$big_eval.="\t\t\t\$incomplete{\$file}.=\$_; last; \n";
 5439 		$big_eval.="\t\t} elsif (length \$incomplete{\$file}) {\n";
 5440 		$big_eval.="\t\t\t\$_=\$incomplete{\$file}.\$_;\n";
 5441 		$big_eval.="\t\t\t\$incomplete{\$file}='';\n\t\t}\n\n";
 5442 
 5443 		$big_eval.="\t\t\$gui_mode_raw++;\n" if $gui_mode;
 5444 
 5445 		$big_eval.="\t\tchomp;\n";
 5446 		$big_eval.=join("", map("\t\t$_\n", @{$log_array{$type}{pre_date_hook}}))
 5447 			if $log_array{$type}{pre_date_hook};
 5448 		$big_eval.="\n";
 5449 
 5450 		$big_eval.="\t\t# deal with the date (if applicable)\n";
 5451 		$big_eval.="\t\tif(s{$log_scalar{$type}{date_pattern}}{}) {\n";
 5452 		if (!$show_all && !$real_mode) { # ie. simple report mode
 5453 			$big_eval.="\t\t\t\$relday=\$when{\$type}{lc \$1};\n";
 5454 			$big_eval.="\t\t\tif (!defined \$relday) {\n";
 5455 			$big_eval.="\t\t\t\tnext; \n"."\t\t\t}\n";
 5456 		} elsif ($show_all && $is_multiday) {
 5457 			$big_eval.="\t\t\tif (!defined \$when{\$type}{lc \$1}) {\n";
 5458 			$big_eval.="\t\t\t\tnext; \n"."\t\t\t}\n";
 5459 			$big_eval.="\t\t\t\$relday=\$relday_end;\n";
 5460 		} elsif ($show_all) {
 5461 			$big_eval.="\t\t\t\$relday=\$relday_end;\n";
 5462 		} elsif ($real_mode) {
 5463 			$big_eval.="\t\t\tif (!defined \$when{\$type}{lc \$1} && ";
 5464 			$big_eval.="\$real_mode_before_now) {\n";
 5465 			$big_eval.="\t\t\t\tnext; \n"."\t\t\t}\n";
 5466 			$big_eval.="\t\t\t\$relday=\$relday_end;\n";
 5467 		} else {
 5468 			die "$prog: internal error: we should never reach this";
 5469 		}
 5470 
 5471 		$big_eval.="\t\t} else {\n";
 5472 		$big_eval.="\t\t\twarn qq(can't find $type date_pattern in '\$_');\n";
 5473 		$big_eval.="\t\t\tnext;\n";
 5474 		$big_eval.="\t\t}\n\n";
 5475 
 5476 		$big_eval.="\t\t# deal with the hostname (if applicable)\n";
 5477 		if ($log_scalar{$type}{nodename_pattern}) {
 5478 			$big_eval.="\t\tif(s{$log_scalar{$type}{nodename_pattern}}{}) {\n";
 5479 			$big_eval.="\t\t\t\$host=\$1;\n";
 5480 			if (defined $domain && !$leave_FQDNs_alone) {
 5481 				$big_eval.="\t\t\t\$host=~s{\.(${domain}|localdomain)\$}{};\n";
 5482 			}
 5483 			if ($process_all_nodenames) {
 5484 			} elsif (@allow_nodenames) {
 5485 				$big_eval.="\t\t\tif (! exists \$nodename_allowed{\$host}) {\n";
 5486 				$big_eval.=&build_out_string("\t\t\t\t", '$relday', 
 5487 					'$nodename', '$other_host_message', '$host', 
 5488 					'$multiplier{$file}', '$raw_line', '$_',0,quotemeta($type));
 5489 				$big_eval.= "\t\t\t\tnext;\n\t\t\t}\n";
 5490 			} else {
 5491 				$big_eval.="\t\t\tif (\$host ne '$nodename') { \n";
 5492 				$big_eval.=&build_out_string("\t\t\t\t", '$relday', 
 5493 					'$nodename', '$other_host_message', '$host', 
 5494 					'$multiplier{$file}', '$raw_line', '$_',0,quotemeta($type));
 5495 				$big_eval.="\t\t\t\tnext;\n\t\t\t}\n";
 5496 			}
 5497 			$big_eval.="\t\t} else {\n";
 5498 			$big_eval.="\t\t\tdie qq(Can't find $type nodename_pattern in '\$_');\n";
 5499 			$big_eval.="\t\t}\n";
 5500 		}
 5501 
 5502 		if ($log_array{$type}{pre_skip_list_hook}) {
 5503 			$big_eval.=join("", map("\t\t$_\n", @{$log_array{$type}{pre_skip_list_hook}}));
 5504 		}
 5505 
 5506 		$big_eval.="\t\t# if (0) up front, so we can use elsif everywhere\n";
 5507 		$big_eval.="\t\tif (0) {\n";
 5508 		$big_eval.="\t\t} ";
 5509 
 5510 		&patterns_deduplicate;
 5511 
 5512 		$i=0;
 5513 		foreach my $pattern (@{$patterns{$type}}) {
 5514 			my $destsref=$dests{$type}{$pattern};
 5515 			$big_eval.=&build_pattern_string($type, $pattern, $destsref);
 5516 		}
 5517 
 5518 		$big_eval .= qq( else {\n);
 5519 		$big_eval.=&build_out_string("\t\t\t", '$relday', '$host',
 5520 			qq("$unknowns"), '$_', '$multiplier{$file}', '$raw_line', '$_',1,
 5521 			quotemeta($type));
 5522 
 5523 
 5524 		if ($unknowns_only) {
 5525 			$big_eval .= qq(\t\tprint "\$_\\n" if !\$unknowns{$type}{\$_}++;\n);
 5526 			$big_eval .= qq(\t\t\$unknowns_raw{$type}{\$raw_line}++;\n);
 5527 		}
 5528 		$big_eval .= qq(\t\t\tnext; \n). qq(\t\t}\n).qq(\t} continue {\n);
 5529 		$big_eval .= qq(\t\t\$last{\$file}=\$raw_line;\n);
 5530 		$big_eval .= qq(\t\t\$multiplier{\$file}=1;\n);
 5531 		$big_eval .= qq(\t\t\$pos=tell \$fh;\n)
 5532 			if $real_mode;
 5533 		if ($gui_mode) {
 5534 			$big_eval .= qq(\t\t\&gui_mode_status_scanning;\n);
 5535 			$big_eval .= qq(\t\tif (\$gui_mode_types_redone) {\n);
 5536 			$big_eval .= qq(\t\t\t\$gui_mode_types_redone=0;\n);
 5537 			$big_eval .= qq(\t\t\tlast;\n);
 5538 			$big_eval .= qq(\t\t}\n);
 5539 		}
 5540 		$big_eval .= qq(\t}\n);
 5541 		$big_eval .= qq(}\n);
 5542 		$big_eval{$type}=$big_eval;
 5543 		$filename_pats{$type}=&make_pattern(@{$log_array{$type}{filenames}});
 5544 	}
 5545 	@nodename_allowed{@allow_nodenames}=1 x @allow_nodenames;
 5546 	$nodename_allowed{$nodename}=1;
 5547 	@categories=&unique(@categories);
 5548 	return \%big_eval, \%filename_pats;
 5549 }
 5550 
 5551 
 5552 # build the pattern string.  Can either be called from build_log_stuff as one
 5553 # of many patterns, in which case set is_standalone=0, or can be called
 5554 # for dest testing, in which case set is_standalone=1
 5555 sub build_pattern_string {
 5556 	my ($type, $pattern, $destsref, $is_standalone, $is_test)=@_;
 5557 
 5558 	my $ret="";
 5559 
 5560 	my $pattern_normal=$pattern;
 5561 	$pattern_normal.='\s*$' unless $pattern=~m{\$$};
 5562 	$pattern_normal="^$pattern_normal" unless $pattern=~m{^\^};
 5563 
 5564 	if ($is_standalone) {
 5565 		$ret.="{\n";
 5566 		$ret.="\tmy \$file='__INTERNAL';\n";
 5567 		$ret.="\tmy \$relday=\$relday_end;\n";
 5568 		$ret.="\tmy \$entry_tags_ref;\n";
 5569 		$ret.="\tmy \%deletes_for_unique;\n";
 5570 		$ret.="\tlocal \$SIG{__WARN__}=sub { die \@_ };\n"; # upgrade warnings
 5571 		$ret.="\t\tif (0) {\n";
 5572 		$ret.="\t\t}";
 5573 	}
 5574 
 5575 	$ret.=" elsif (m{$pattern_normal}o) {\n";
 5576 	$ret.="\t\t\tmy \%unique;\n" if $is_test;
 5577 	my $num_dests=0;
 5578 	$num_dests=@$destsref if defined $destsref;
 5579 	warn "$prog: no dests for pattern $pattern\n" if !$num_dests;
 5580 	my $which_dest=0;
 5581 
 5582 	foreach my $destref (@$destsref) {
 5583 
 5584 		my $format=$destref->{format};
 5585 		my $dest=$destref->{dest};
 5586 		my $count=1;
 5587 		my $delete_if_unique=undef;
 5588 		$count=$destref->{count} if exists $destref->{count};
 5589 
 5590 		if ($dest eq 'LAST') { # special dest
 5591 			die "LAST dest can only be used as the only dest"
 5592 				unless $num_dests == 1;
 5593 			$ret.=qq(\t\t\t\$multiplier{\$file}=$count;\n);
 5594 			$ret.=qq(\t\t\t\$_=\$last{\$file};\n);
 5595 			$ret.=qq(\t\t\tundef \$last{\$file};\n);
 5596 			$ret.=qq(\t\t\tredo if defined \$_;\n);
 5597 			$ret.=qq(\t\t\tnext;\n);
 5598 		} elsif ($dest eq 'SKIP') { # another special dest
 5599 			die "SKIP dest can only be used as the only dest for ".
 5600 				"pattern $pattern logtype $type"
 5601 				unless $num_dests == 1;
 5602 			$ret.=qq(\t\t\tundef \$last{\$file};\n);
 5603 		} elsif ($dest =~ m{^UNIQUE\s+(\S.*)}) {
 5604 			my $cat=$1;
 5605 			my ($part1, $part2);
 5606 			if ($format=~m{^(.+)\,([^,]+)$}) {
 5607 				$part1=$1; $part2=$2;
 5608 			} else {
 5609 				die "format $format needs 2 comma delimited values ".
 5610 					"UNIQUE dests\n";
 5611 			}
 5612 			$ret.=qq(\t\t\t\$unique{\$relday}{\$host}{"$cat"}{$part1}{$part2}={}\n).
 5613 				qq(\t\t\t\tif !defined \$unique{\$relday}{\$host}{"$cat"}{$part1}{$part2};\n);
 5614 			$ret.=qq(\t\t\tforeach my \$l1 (keys \%deletes_for_unique) {\n);
 5615 			$ret.=qq(\t\t\t\tforeach my \$l2 (keys \%{\$deletes_for_unique{\$l1}}) {\n);
 5616 			$ret.=qq(\t\t\t\t\t\$unique{\$relday}{\$host}{"$cat"}).
 5617 				qq({$part1}{$part2}{\$l1}{\$l2}=undef;\n);
 5618 			$ret.=qq(\t\t\t\t}\n);
 5619 			$ret.=qq(\t\t\t}\n);
 5620 
 5621 		} else {               # normal dest
 5622 
 5623 			$dest=~s{^CATEGORY\s+}{};
 5624 
 5625 			if (exists $destref->{use_sprintf}) {
 5626 				$format="sprintf($format)";
 5627 			} else {
 5628 				$format="\"$format\"";
 5629 			}
 5630 
 5631 			if (exists $destref->{delete_if_unique}) {
 5632 				$delete_if_unique=1;
 5633 			}
 5634 
 5635 			$ret.=&build_out_string("\t\t\t", '$relday', '$host', 
 5636 				qq("$dest"), $format, "$count*\$multiplier{\$file}", 
 5637 				'$raw_line', '$_', 0,
 5638 				quotemeta($type), quotemeta($pattern),
 5639 				$which_dest, $delete_if_unique, $is_test);
 5640 		}
 5641 		$which_dest++;
 5642 	}
 5643 	$ret.=qq(\t\t\tnext;\n\t\t});
 5644 	$ret.="\n}\n" if $is_standalone;
 5645 	return $ret;
 5646 }
 5647 
 5648 
 5649 sub build_out_string {
 5650 	my ($tab, $relday, $host, $category, $data, $count, $raw_line, $line,
 5651 		$is_unknown, $type, $pattern, $which_dest, $delete_if_unique,
 5652 		$is_test)=@_;
 5653 
 5654 	my $ret="";
 5655 
 5656 	$ret.=$tab."\$entry_tags_ref={\%tags, '%'=>'%', h=>$host, _t=>'$type', \n".
 5657 		$tab."\tc=>$category, d=>$data, '#'=>$count, R=>$raw_line, A=>'', \n".
 5658 		$tab."\t_l=>$line, _u=>$is_unknown";
 5659 	$ret.=qq(, _p=>"$pattern", _w=>$which_dest) 
 5660 		if defined $pattern;
 5661 	$ret.="};\n";
 5662 	$ret.=$tab."chomp \$entry_tags_ref->{R};\n";
 5663 
 5664 	if ($real_mode) {
 5665 		$ret.= $tab."\&real_mode_out($relday, \$entry_tags_ref)\n".
 5666 			$tab."\tunless \&is_ignored(\$entry_tags_ref);\n"
 5667 			if !$is_test;
 5668 	} else {
 5669 		$ret.= $tab."\tunless (\&is_ignored(\$entry_tags_ref)) {\n";
 5670 		$ret.= $tab."\t\$count{$relday}{$host}{$category}{$data}+=$count;\n"
 5671 			if !$is_test;
 5672 		$ret.= $tab."\t\$deletes_for_unique{$category}{$data}=undef;\n"
 5673 			if $delete_if_unique && !$is_test;
 5674 		$ret.= $tab."}\n";
 5675 	}
 5676 	return $ret;
 5677 }
 5678 
 5679 
 5680 sub build_event_tree {
 5681 
 5682 	undef %event_tree;
 5683 	$event_change=time;
 5684 
 5685 	foreach my $eventref (@event_config) {
 5686 		my $posref=\%event_tree;
 5687 		next if ! exists $eventref->{val};
 5688 
 5689 		foreach my $key (sort keys %{$eventref}) {
 5690 			next if $key eq 'val';
 5691 			my $val=$eventref->{$key};
 5692 			$posref->{$key}{$val}={} 
 5693 				if !exists $posref->{$key} or !exists $posref->{$key}{$val};
 5694 			$posref=$posref->{$key}{$val};
 5695 		}
 5696 
 5697 		$posref->{val}=$eventref->{val};
 5698 	}
 5699 }
 5700 
 5701 
 5702 sub patterns_deduplicate {
 5703 
 5704 	my %oldpatterns=%patterns;
 5705 	undef %patterns;
 5706 	my %pattern_seen;
 5707 
 5708 	foreach my $type (keys %oldpatterns) {
 5709 		foreach my $pattern (@{$oldpatterns{$type}}) {
 5710 			if (!$pattern_seen{$type}{$pattern}++) {
 5711 				push @{$patterns{$type}}, $pattern;
 5712 			}
 5713 		}
 5714 	}
 5715 
 5716 	my %olddests=%dests;
 5717 	undef %dests;
 5718 
 5719 	# this algorithm looks horribly inefficient, but in practice, the number
 5720 	# of dests per pattern is likely to be 1 or a very small number
 5721 	foreach my $type (keys %olddests) {
 5722 		foreach my $pattern (keys %{$olddests{$type}}) {
 5723 			OLDDESTREF:
 5724 			foreach my $olddestref (@{$olddests{$type}{$pattern}}) {
 5725 				foreach my $destref (@{$dests{$type}{$pattern}}) {
 5726 					next OLDDESTREF if &hash_cmp($olddestref, $destref)==0;
 5727 				}
 5728 				push @{$dests{$type}{$pattern}}, $olddestref;
 5729 			}
 5730 		}
 5731 	}
 5732 }
 5733 
 5734 
 5735 sub hash_cmp {
 5736 	my $hash_ref_1=shift;
 5737 	my $hash_ref_2=shift;
 5738 
 5739 	foreach my $key (keys %$hash_ref_1) {
 5740 		return 1 if ! exists $hash_ref_2->{$key};
 5741 		my $ret=$hash_ref_1->{$key} cmp $hash_ref_2->{$key};
 5742 		return $ret if $ret;
 5743 	}
 5744 
 5745 	foreach my $key (keys %$hash_ref_2) {
 5746 		return -1 if ! exists $hash_ref_1->{$key};
 5747 	}
 5748 	return 0;
 5749 }
 5750 
 5751 
 5752 # convert a date into the number of days before today
 5753 sub normalize2relday {
 5754 	my $date=shift;
 5755 	die "$prog: normalize2relday: need at least one arg" unless defined $date;
 5756 
 5757 	if ($date eq "today") {
 5758 		return 0;
 5759 	}
 5760 
 5761 	if ($date eq "yesterday") {
 5762 		return 1;
 5763 	}
 5764 
 5765 	if ($date =~ m{^\d+$}) {
 5766 		return $date;
 5767 	}
 5768 
 5769 	if ($date =~ m{^(\d{4,})_(\d{1,2})_(\d{1,2})$}) {
 5770 		my $abs_day=&absdate2absday($1, $2, $3);
 5771 		my $abs_today=&absdate2absday(split(/\s+/, 
 5772 			strftime("%Y %m %d", localtime($time_start))));
 5773 		my $check=strftime("%Y_%m_%d", relday2time($abs_today-$abs_day));
 5774 		die "$prog: BUG: normalize2relday check returned $check for $date\n"
 5775 			unless $check eq $date;
 5776 		return $abs_today-$abs_day;
 5777 	}
 5778 
 5779 	die "Unknown date format: $date\n";
 5780 }
 5781 
 5782 
 5783 # convert a relative date (ie. the number of days before today) into
 5784 # a localtime
 5785 sub relday2time {
 5786 	my $days_ago=shift;
 5787 	die "$prog: relday2time: need at least one arg" unless defined $days_ago;
 5788 
 5789 	# move away from the edges of the day to avoid a problem involving
 5790 	# time zones.  Yes, this is ugly.
 5791 	my $time_start_normalized=$time_start;
 5792 	my $hour=strftime("%H", localtime $time_start_normalized);
 5793 	$time_start_normalized-=7200 if $hour >20;
 5794 	$time_start_normalized+=7200 if $hour <4;
 5795 
 5796 	return localtime($time_start_normalized-$days_ago*86400);
 5797 }
 5798 
 5799 
 5800 # convert an absolute year, month, day into days since Gregorian 0
 5801 sub absdate2absday {
 5802 	defined(my $year=shift) || die;
 5803 	defined(my $month=shift) || die;
 5804 	defined(my $day=shift) || die;
 5805 
 5806 	my @month_acc=(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365);
 5807 
 5808 	return
 5809 		($year-1)*365+
 5810 		int(($year-1)/4  )*(+1)+
 5811 		int(($year-1)/100)*(-1)+
 5812 		int(($year-1)/400)*(+1)+
 5813 		$month_acc[$month-1]+
 5814 		$day+
 5815 		($month>2 && (!($year%400) || (!($year%4) && ($year%100)))? 1 : 0);
 5816 }
 5817 
 5818 sub filter {
 5819 	my $filter=shift;
 5820 	my %values=@_;
 5821 
 5822 	if (0) {
 5823 	} elsif ($filter =~ m{^\s*(\S.*?) (and|or) (\S.*)\s*$}) {
 5824 		my $keyword = $2;
 5825 		my $filter1 = $1;
 5826 		my $filter2 = $3;
 5827 		my @return1 = &filter($filter1, %values);
 5828 		my @return2 = &filter($filter2, %values);
 5829 		if ($keyword eq "and") {
 5830 			my %in_return2;
 5831 			@in_return2{@return2}=undef;
 5832 			return grep {exists $in_return2{$_}} @return1;
 5833 		} elsif ($keyword eq "or") {
 5834 			return &unique(@return1, @return2);
 5835 		} else {
 5836 			die "Internal error";
 5837 		}
 5838 	} elsif ($filter =~ m{^(>=|<=|<|>|=|==|!=|<>|><)\s+(\d+)\s*$}) {
 5839 
 5840 		my $keyword=$1;
 5841 		my $value=$2;
 5842 		my $is_percent=$3;
 5843 
 5844 		if (0) { # I like ifs to line up. . .
 5845 
 5846 		} elsif ($keyword eq '>=') {
 5847 			return grep {$values{$_}>=$value} keys %values;
 5848 		} elsif ($keyword eq '<=') {
 5849 			return grep {$values{$_}<=$value} keys %values;
 5850 		} elsif ($keyword eq '<') {
 5851 			return grep {$values{$_}< $value} keys %values;
 5852 		} elsif ($keyword eq '>') {
 5853 			return grep {$values{$_}> $value} keys %values;
 5854 		} elsif ($keyword =~ m{^(=|==)$}) {
 5855 			return grep {$values{$_}==$value} keys %values;
 5856 		} elsif ($keyword =~ m{^(!=|<>|><)$}) {
 5857 			return grep {$values{$_}!=$value} keys %values;
 5858 		}
 5859 
 5860 	} elsif ($filter =~ m{^((?:top|bottom)(?:_strict)?)\s+(\d+)(\%?)\s*$}) {
 5861 
 5862 		my $keyword=$1;
 5863 		my $value=$2;
 5864 		my $is_percent=$3;
 5865 
 5866 		if (0) {
 5867 		} elsif ($keyword =~ m{^(top|bottom)(_strict)?$}) {
 5868 			return if $value == 0;
 5869 			my $how_many=$value;
 5870 			my @keys=&sort_by_value(\%values, keys %values);
 5871 			@keys=reverse @keys if $1 eq "top";
 5872 			my $is_strict=$2;
 5873 
 5874 			if ($is_percent) { # switch to percentage
 5875 				die "percentage must be between 0 and 100" 
 5876 					if $value<0 || $value>100;
 5877 				$how_many=&ceiling(@keys*$value/100);
 5878 			}
 5879 
 5880 			return @keys if @keys <= $how_many; # no need to do more work
 5881 
 5882 			my @return=splice(@keys, 0, $how_many);
 5883 
 5884 			# what if we we have a bunch of items with equal value and the 
 5885 			# top-whatever cuts off in the middle?  Unless we strictly want
 5886 			# just the top-whatever, we should include those, too.
 5887 			my $last_val=$values{$return[$#return]};
 5888 			while (!$is_strict && @keys && $values{$keys[0]}==$last_val) {
 5889 				push @return, shift @keys;
 5890 			}
 5891 
 5892 			return @return; # done!
 5893 		}
 5894 	} elsif ($filter =~ m{^\s*none\s*$}) {
 5895 		return keys %values;
 5896 	} else {
 5897 		die "unknown filter format in $filter\n";
 5898 	}
 5899 
 5900 	return 1;
 5901 }
 5902 
 5903 
 5904 sub sort_keys {
 5905 	my $sort=lc shift;
 5906 	my $hash_ref=shift;
 5907 	my @keys=@_;
 5908 
 5909 	$sort=~s{\s+}{ }g;
 5910 	die "No sort specified" if $sort =~ m{^\s*$};
 5911 	my @sorts=reverse split(/\s/, $sort);
 5912 	foreach my $sort (@sorts) {
 5913 		if ($sort eq 'reverse') {
 5914 			@keys=reverse @keys;
 5915 		} elsif ($sort =~ m{^(funky|numeric|string)$}) {
 5916 			my $sort_helper="${sort}_sort_helper";
 5917 			@keys=sort $sort_helper @keys;
 5918 		} elsif ($sort =~ m{^value$}) {
 5919 			@keys=&sort_by_value($hash_ref, @keys);
 5920 		} elsif ($sort eq "none") {
 5921 		} else {
 5922 			die "unknown sort: $sort\n";
 5923 		}
 5924 	}
 5925 	return @keys;
 5926 }
 5927 
 5928 
 5929 sub derive {
 5930 	defined(my $derivation=lc shift) || die "Internal error";
 5931 	my $relday=shift;
 5932 	my $host=shift;
 5933 
 5934 	my ($keyword, $cat1, $cat2, $arg);
 5935 
 5936 	my $quote_pat='\"([^\"]+)\"';
 5937 
 5938 	if ($derivation =~ 
 5939 		m{^\s*$quote_pat\s+(add|subtract|remove)\s+$quote_pat\s*$}) {
 5940 		$keyword=$2;
 5941 		$cat1=$1;
 5942 		$cat2=$3;
 5943 	} elsif ($derivation =~ m{^\s*(=)\s+$quote_pat\s*$}) {
 5944 		$keyword=$1;
 5945 		$cat1=$2;
 5946 	} else {
 5947 		die "Derivation $derivation in illegal format\n";
 5948 	}
 5949 
 5950 	my %return=%{$count{$relday}{$host}{$cat1}} 
 5951 		if defined $cat1 && defined $relday && defined $host && 
 5952 			exists $count{$relday}{$host}{$cat1};
 5953 
 5954 	my %category2=%{$count{$relday}{$host}{$cat2}} 
 5955 		if defined $cat2 && defined $relday && defined $host &&
 5956 			exists $count{$relday}{$host}{$cat2};
 5957 
 5958 	if (0) {
 5959 	} elsif ($keyword eq "add") {
 5960 		foreach my $key (keys %category2) {
 5961 			$return{$key}+=$category2{$key};
 5962 		}
 5963 	} elsif ($keyword eq "subtract") {
 5964 		foreach my $key (keys %category2) {
 5965 			$return{$key}-=$category2{$key};
 5966 		}
 5967 	} elsif ($keyword eq "remove") {
 5968 		foreach my $key (keys %category2) {
 5969 			delete $return{$key};
 5970 		}
 5971 	} else {
 5972 		die "Unknown keyword: $keyword\n";
 5973 	}
 5974 
 5975 	return %return;
 5976 }
 5977 
 5978 
 5979 sub priority {
 5980 	my $priority=shift;
 5981 
 5982 	die "$prog: unknown value for priority: $priority\n"
 5983 		unless exists $priority_name{uc $priority};
 5984 }
 5985 
 5986 
 5987 sub color {
 5988 	my $color=shift;
 5989 	my $do_color=shift;
 5990 
 5991 	return if !defined $color;
 5992 	$color=lc $color;
 5993 
 5994 	my @colors=split(/\s+/, $color);
 5995 
 5996 	foreach my $i (@colors) {
 5997 		if (!exists $colors{$i}) {
 5998 			die "No such color: $i\n";
 5999 		} else {
 6000 			next unless $do_color;
 6001 			print &process_tags($colors{$i}, {e=>"\033", a=>"\007"});
 6002 		}
 6003 	}
 6004 }
 6005 
 6006 
 6007 sub ceiling {
 6008 	my $val=shift;
 6009 	return $val if int $val == $val;
 6010 	return int ($val + 1);
 6011 }
 6012 
 6013 
 6014 sub should_ignore_file {
 6015 	my $filename=basename shift;
 6016 
 6017 	return 0 if !@filename_ignore_patterns;
 6018 
 6019 	my $pattern=&make_pattern(@filename_ignore_patterns);
 6020 
 6021 	return 1 if ($filename =~ m{${pattern}$});
 6022 	return 0;
 6023 }
 6024 
 6025 
 6026 # mostly, you want perl to whine if it encounters undef when you ask it to
 6027 # work with a value.  But sometimes you don't.  empty is like !length, except
 6028 # without an error for undef.
 6029 sub empty($) {
 6030 	my $val=shift;
 6031 
 6032 	return 1 if !defined $val || !length $val;
 6033 	return 0;
 6034 }
 6035 
 6036 # like above, sometimes perl's insistence on whining about undefined values
 6037 # is undesirable.  val_or_empty transparently converts an undef to an empty
 6038 # string
 6039 sub val_or_empty($) {
 6040 	my $val=shift;
 6041 	return "" if !defined $val;
 6042 	return $val;
 6043 }
 6044 
 6045 
 6046 sub do_nothing {
 6047 }
 6048 
 6049 
 6050 sub daemon_mode_syslog_on {
 6051 
 6052 	setlogsock "unix";
 6053 	openlog($prog, 'pid', 'daemon');
 6054 	$SIG{__WARN__}=sub { my $m="@_"; $m=~s{^$prog: }{}; 
 6055 		syslog("warning", "%s", $m); warn @_};
 6056 	$SIG{__DIE__}=sub { my $m="@_"; $m=~s{^$prog: }{}; 
 6057 		syslog("err", "%s", $m); die @_};
 6058 }
 6059 
 6060 
 6061 # used by daemon mode to daemonize.  Some blocks of the below code are
 6062 # paraphrased from Proc::Daemon, as noted below.
 6063 sub daemon_mode_daemonize {
 6064 
 6065 	&daemon_mode_syslog_on; # in case it wasn't called already
 6066 
 6067 	my $pid=$$;
 6068 
 6069 	if (-f $daemon_mode_pid_file) {
 6070 		my $pidfd=new FileHandle ("< $daemon_mode_pid_file") or
 6071 			die "$prog: open $daemon_mode_pid_file: $!\n";
 6072 		$pid=<$pidfd>; chomp $pid;
 6073 		die "$prog: already running in daemon mode\n"
 6074 			if $$ ne $pid && qx(ps -p $pid) =~ m{$prog};
 6075 	}
 6076 
 6077 	if (!$daemon_mode_foreground) {
 6078 		$pid=fork; # like Proc::Daemon
 6079 		die "$prog: fork: $!\n" if $pid<0;
 6080 		exit if $pid>0;
 6081 	}
 6082 
 6083 	POSIX::setsid || die "$prog: setsid: $!\n"; # like Proc::Daemon
 6084 
 6085 	if (!$daemon_mode_foreground) {
 6086 		$pid=fork; # like Proc::Daemon
 6087 		die "$prog: fork: $!\n" if $pid<0;
 6088 		exit if $pid>0;
 6089 	}
 6090 
 6091 	# after we chdir /, a relative PID file won't work.  Better make it
 6092 	# absolute.
 6093 	my $pid_file=$daemon_mode_pid_file;
 6094 	$pid_file = $cwd."/".$pid_file if $pid_file !~ m{^/};
 6095 
 6096 	my $pidfd=new FileHandle ("> $pid_file") 
 6097 		|| die "$prog: open $pid_file: $!\n";
 6098 	print $pidfd "$$\n";
 6099 	$pidfd->close;
 6100 	&rm_on_exit($pid_file);
 6101 
 6102 	$SIG{HUP}='IGNORE'; # like Proc::Daemon
 6103 
 6104 	$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{ABRT} = sub {
 6105 		my ($sig)=@_; die "$prog: exiting on signal $sig\n";
 6106 	};
 6107 
 6108 	chdir "/" || die "$prog: chdir /: $!\n"; # like Proc::Daemon
 6109 
 6110 	umask 077; # like Proc::Daemon
 6111 
 6112 	STDIN->close; # like Proc::Daemon
 6113 	STDIN->open("+>/dev/null") || die "$prog: open /dev/null: $!\n";
 6114 
 6115 	STDOUT->close; # like Proc::Daemon
 6116 	STDOUT->open("+>&STDIN")   || die "$prog: dup stdin: $!\n";
 6117 
 6118 	STDERR->close; # like Proc::Daemon
 6119 	STDERR->open("+>&STDIN")   || die "$prog: dup stdin: $!\n";
 6120 
 6121 	warn "$prog: starting up\n";
 6122 }
 6123 
 6124 
 6125 # handling for temporary files.  It's a kinda magic.
 6126 my @rm_on_exit;
 6127 sub rm_on_exit { push @rm_on_exit, @_; }
 6128 END { # this installs an atexit handler
 6129 	foreach my $file (@rm_on_exit) { 
 6130 		unlink $file || warn "$prog: unable to unlink $file: $!\n";
 6131 	}
 6132 }
 6133 
 6134 
 6135 __END__
 6136 
 6137 # internal config file for log_analysis
 6138 
 6139 # what version config are we compatible with?  Every config file should have 
 6140 # one of these.
 6141 config_version @VERSION@
 6142 
 6143 # what version is this file?  If you like doing configuration management, set
 6144 # this.
 6145 file_version $Revision: 1.310 $
 6146 
 6147 # sulog type
 6148 
 6149 # add our name to the log_type_list
 6150 add arr log_type_list=
 6151 sulog
 6152 
 6153 # set the basename(s) of the file(s) we'll be looking at.  For sulog, that's 
 6154 # just "sulog", but for others, there are more than one (ie. syslog has 
 6155 # syslog, maillog, authlog, etc.)  This is mandatory.
 6156 set arr sulog_filenames=
 6157 sulog
 6158 
 6159 # Some files (ie. wtmp, wtmpx) are in a binary format, so they need a 
 6160 # command to be run as an interpreter to be analyzed.  This is optional.
 6161 # It doesn't apply to the sulog format; see wtmp (later) for an example.
 6162 #set var sulog_open_command=
 6163 
 6164 # If open_command and decompression_rules apply to the same file, then 
 6165 # two commands need to be run.  How do we get output from one to the other?
 6166 # A pipe won't always work, so we default to using temp files.  This variable
 6167 # lets you use a pipe instead.  This is optional.  It is ignored unless 
 6168 # open_command is set.  None of the default log types use this, but I know
 6169 # someone who wants it for his private ruleset.
 6170 #set var sulog_pipe_decompress_to_open=
 6171 
 6172 # Arbitrary perl code to be run for each line, before doing anything else.
 6173 # This is optional.  sulog doesn't need it; see wtmp (later) for an
 6174 # example.
 6175 #set var sulog_pre_date_hook=
 6176 
 6177 # pattern that describes the date in each log line.  The pattern will be 
 6178 # stripped off before proceeding.  $1 should contain the date after the
 6179 # pattern is run.  This is mandatory.
 6180 set var sulog_date_pattern=^SU\s+(\d+\/\d+)\s+\S+\s+
 6181 
 6182 # date_format follow the rules for strftime(3).  It should describe
 6183 # the date as extracted to $1 in the last step.
 6184 set var sulog_date_format=%m/%d
 6185 
 6186 # pattern that describes the nodename in each log line, after the date has
 6187 # been stripped.  It will be stripped off before proceeding.  $1 should 
 6188 # contain the nodename.  This is optional, and doesn't apply to sulog; 
 6189 # see syslog for an example.
 6190 #set var sulog_nodename_pattern=
 6191 
 6192 # some lines of arbitrary perl code that get called after the nodename
 6193 # has been stripped, before any further processing is done.  sulog doesn't
 6194 # use this; see syslog for a real example of this.  This is optional.
 6195 #set arr sulog_pre_skip_list_hook=
 6196 
 6197 # raw_rules and skip_list have been obsoleted by the new config format, so 
 6198 # they are deprecated, and can be ignored
 6199 # set arr sulog_skip_list=
 6200 
 6201 # set arr sulog_raw_rules=
 6202 
 6203 @@ifndef __USE_MINIMAL_CONFIG
 6204 
 6205 logtype: sulog
 6206 
 6207 	pattern:	\-\s+\S+\s+($pat{user})\-($pat{user})
 6208 
 6209 					format:	$1 => $2
 6210 					dest:	su: failed for
 6211 
 6212 	pattern:	\+\s+\S+\s+($pat{user})\-($pat{user})
 6213 
 6214 					format:	$1 => $2
 6215 					dest:	su: succeeded for
 6216 
 6217 @@endif
 6218 
 6219 # and that's it for sulog.
 6220 
 6221 # wtmp type
 6222 
 6223 add arr log_type_list=
 6224 wtmp
 6225 
 6226 # file basenames that this log type applies to
 6227 set arr wtmp_filenames=
 6228 wtmp
 6229 wtmpx
 6230 
 6231 # wtmp files are in a binary format, and are intended to be interpreted
 6232 # by the last command.  Rather than try to read them ourselves, we call
 6233 # last.  Subject to usual tags, plus the %f tag stands for the filename.
 6234 set var wtmp_open_command=last -f %f
 6235 
 6236 # don't pipe decompress to the open command, or last will whine about seeking
 6237 #set var pipe_decompress_to_open=
 6238 
 6239 # This is a hook to run arbitrary perl code for each log line before
 6240 # doing anything else.
 6241 set arr wtmp_pre_date_hook=
 6242 @@ifndef __USE_MINIMAL_CONFIG
 6243 	# the second-to-last line of output is always empty.  This would cause
 6244 	# it to fail the date_pattern check, so let's skip it in advance.
 6245 	next if m{^$};
 6246 @@endif
 6247 
 6248 set var wtmp_date_pattern= (?:Sun|Mon|Tue|Wed|Thu|Fri|Sat) ((?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d+).*
 6249 
 6250 set var wtmp_date_format=%b %e
 6251 
 6252 # set arr wtmp_skip_list=
 6253 
 6254 # set arr wtmp_raw_rules=
 6255 
 6256 @@ifndef __USE_MINIMAL_CONFIG
 6257 
 6258 logtype: wtmp
 6259 
 6260 
 6261 	pattern:	($pat{file}) begins
 6262 
 6263 					dest: 	SKIP
 6264 
 6265 	pattern:	(?:reboot \s+system boot|reboot\s+~)
 6266 
 6267 					format:	reboot
 6268 					dest:	major events
 6269 
 6270 	pattern:	ftp\s+ftp\s+($pat{host})
 6271 
 6272 					format:	$1
 6273 					dest:	FTP: successful FTP from (partial nodename)
 6274 
 6275 	pattern:	($pat{user})\s+(pts/\d+|tty\w+)\s+($pat{host})
 6276 
 6277 					format:	$1 from $3
 6278 					dest:	login: successful login for user from (partial nodename)
 6279 
 6280 	pattern:	($pat{user})\s+(pts/\d+|tty\w+)
 6281 
 6282 					format:	$1
 6283 					dest:	login: successful local login
 6284 
 6285 	#mta this next guy should probably take advtantage of the X11 info
 6286      pattern:  ($pat{user})\s+(pts/\d+|tty\w+)\s+(\:\d+(?:\.\d+)?)
 6287 
 6288 					format:   $1
 6289 					dest:     login: successful local login
 6290 
 6291 @@endif
 6292 
 6293 # syslog
 6294 # This one is kinda scary.
 6295 
 6296 add arr log_type_list=
 6297 syslog
 6298 
 6299 
 6300 # file basenames that this log type applies to
 6301 set arr syslog_filenames=
 6302 authlog
 6303 daemon
 6304 local1
 6305 messages
 6306 maillog
 6307 secure
 6308 syslog
 6309 
 6310 set var syslog_date_pattern=^((?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)+\s+\d{1,2})\s+\d+\:\d+\:\d+\s+
 6311 
 6312 set var syslog_date_format=%b %e
 6313 
 6314 set var syslog_nodename_pattern=^(\S+)\s*
 6315 
 6316 set arr syslog_pre_skip_list_hook=
 6317 @@ifndef __USE_MINIMAL_CONFIG
 6318 	# get rid of msgid on Solaris hosts
 6319 	s{^(\S+: )\[ID \d+ \w+\.\w+\] }{$1};
 6320 	#
 6321 	# get rid of PID field, if present
 6322 	s{^([^\s\[]+)\[\d+\]}{$1};
 6323 	#
 6324 	# for sendmail, get rid of queue ID
 6325 	if (m{^$pat{sendmail_tag}: }) {
 6326 		s{^($pat{sendmail_tag}): $pat{sendmail_queue_id}: }{$1: };
 6327 		s{^($pat{sendmail_tag}): SYSERR\($pat{user}\): }{$1: SYSERR: };
 6328 	}
 6329     #
 6330     # get rid of kernel timestamp on recent Linux kernels
 6331     s{^kernel: \[ *\d+\.\d+\] }{kernel: };
 6332 @@endif
 6333 
 6334 # skip_list and raw_rules are obsolete and deprecated, but will continue 
 6335 # to work.
 6336 
 6337 # set arr syslog_skip_list=
 6338 
 6339 # set arr syslog_raw_rules=
 6340 
 6341 
 6342 # time for the new config format.  Hopefully, this is both more clear and 
 6343 # more extensible.
 6344 
 6345 @@ifndef __USE_MINIMAL_CONFIG
 6346 
 6347 logtype: syslog
 6348 
 6349 # first, a bunch of patterns that we want to skip, AKA discard.
 6350 
 6351 	pattern:	PAM_pwdb: \($pat{word}\) session closed for user $pat{user}
 6352 
 6353 					dest:	SKIP
 6354 
 6355 	pattern:	-- MARK --
 6356 
 6357 					dest:	SKIP
 6358 
 6359 # This one drops all the cron jobs info
 6360 	pattern:	/USR/SBIN/CRON: .*
 6361 
 6362 					dest:	SKIP
 6363 
 6364 	pattern:	/usr/sbi/cron: .*
 6365 
 6366 					dest:	SKIP
 6367 # let's try to recognize kernel device info messages and throw them out:
 6368 
 6369 	pattern:	/bsd: \w+\d at\ 
 6370 
 6371 					dest:	SKIP
 6372 
 6373 	pattern:	/bsd: \w+\d:\ 
 6374 
 6375 					dest:	SKIP
 6376 
 6377 	pattern:	crosspost: seconds \d+ links \d+ \d+ symlinks \d+ \d+ mkdirs \d+ \d+ missing \d+ toolong \d+ other \d+
 6378 
 6379 					dest:	SKIP
 6380 
 6381 	pattern:	ftpd: FTP session closed
 6382 
 6383 					dest:	SKIP
 6384 
 6385 	pattern:	ftpd: (?:LIST|CWD|NLST) .*
 6386 
 6387 					dest:	SKIP
 6388 
 6389 	pattern:	ftpd: (?:NOOP|NLST)
 6390 
 6391 					dest:	SKIP
 6392 
 6393 	pattern:	ftpd: PASS password
 6394 
 6395 					dest:	SKIP
 6396 
 6397 	pattern:	ftpd: PORT
 6398 
 6399 					dest:	SKIP
 6400 
 6401 	pattern:	ftpd: PWD
 6402 
 6403 					dest:	SKIP
 6404 
 6405 	pattern:	ftpd: QUIT
 6406 
 6407 					dest:	SKIP
 6408 
 6409 	pattern:	ftpd: REST
 6410 
 6411 					dest:	SKIP
 6412 
 6413 	pattern:	ftpd: SYST
 6414 
 6415 					dest:	SKIP
 6416 
 6417 	pattern:	ftpd: TYPE ASCII
 6418 
 6419 					dest:	SKIP
 6420 
 6421 	pattern:	ftpd: TYPE Image
 6422 
 6423 					dest:	SKIP
 6424 
 6425      pattern:  ftpd: USER \(none\)
 6426 
 6427                          dest:     SKIP
 6428 
 6429 	pattern:	ftpd: USER ($pat{user})
 6430 
 6431 					dest:	SKIP
 6432 
 6433 	pattern:	ftpd: User ($pat{user}) timed out after (\d+) seconds at .*
 6434 
 6435 					dest:	SKIP
 6436 
 6437 	pattern:	ftpd: cmd failure
 6438 
 6439 					dest:	SKIP
 6440 
 6441 	pattern:	identd: from: ($pat{ip}) \( ($pat{host}) \) for: (\d+), (\d+)
 6442 
 6443 					dest:	SKIP
 6444 
 6445 	pattern:	identd: from: ($pat{ip}) \(($pat{host})\) EMPTY REQUEST
 6446 	#mta this probably should be flagged, but isn't for now
 6447 
 6448 					dest:	SKIP
 6449 
 6450 	pattern:	identd: from: ($pat{ip}) \(($pat{host})\) for invalid-port\(s\): (\d+) , (\d+)
 6451 	#mta this probably should be flagged, but isn't for now
 6452 
 6453 					dest:	SKIP
 6454 
 6455 	pattern:	identd: Successful lookup: (\d+) , (\d+) : ($pat{user})\.($pat{user})
 6456 
 6457 					dest:	SKIP
 6458 
 6459 	pattern:	identd: Returned: (\d+) , (\d+) : NO-USER
 6460 
 6461 					dest:	SKIP
 6462 
 6463 	pattern:	$pat{sendmail_tag}: ((?:$pat{host} )?\[($pat{ip})\]) did not issue MAIL/EXPN/VRFY/ETRN during connection to MTA
 6464 
 6465 					format: $1
 6466 					dest: host did not issue MAIL/EXPN/VRFY/ETRN during connection
 6467 
 6468 	pattern:	imapd: Authenticated user=($pat{user}) host=($pat{host} \[$pat{ip}\])
 6469 					format: $1 on $2
 6470 					dest: imapd: user authenticated
 6471 
 6472 	pattern:	imapd: AUTHENTICATE PLAIN failure host=($pat{host} \[$pat{ip}\])
 6473 					format: $1
 6474 					dest: imapd: authentication failed
 6475 
 6476 	pattern:	imapd: Login disabled user=($pat{user}) auth=($pat{user}) host=($pat{host} \[($ip_pat)\])
 6477 					format: $1 from $3
 6478 					dest: imapd: login disabled
 6479 
 6480 	pattern:	imapd: Login failed user=($pat{user}) auth=($pat{user}) host=($pat{host} \[($ip_pat)\])
 6481 					format: $1 from $3
 6482 					dest: imapd: login failed
 6483 
 6484 	pattern:	imapd: Login user=($pat{user}) host=($pat{host} \[($ip_pat)\])
 6485 					format: $1 from $2
 6486 					dest: imapd: login
 6487 
 6488 	pattern:	imapd: Logout user=($pat{user}) host=($pat{host} \[($ip_pat)\])
 6489 					format: $1 from $2
 6490 					dest: imapd: logout
 6491 
 6492 	pattern:	imapd: Killed \(lost mailbox lock\) user=($pat{user}) host=($pat{host} \[$pat{ip}\])
 6493 					format: $1 on $2
 6494 					dest: imapd: killed (lost mailbox lock)
 6495 
 6496 	pattern:	imapd: Command stream end of file, while reading line user=($pat{user}) host=($pat{host} \[$pat{ip}\])
 6497 					format: $1 on $2
 6498 					dest: imapd: command stream end of file
 6499 
 6500 	pattern:	imapd: Autologout user=($pat{user}) host=($pat{host} \[$pat{ip}\])
 6501 					format: $1 on $2
 6502 					dest: imapd: autologout
 6503 
 6504 	pattern:	innd: E
 6505 
 6506 					dest:	SKIP
 6507 
 6508 	pattern:	innd: L:$pat{file}
 6509 
 6510 					dest:	SKIP
 6511 
 6512 	pattern:	innd: ME HISstats \d+ hitpos \d+ hitneg \d+ missed \d+ dne
 6513 
 6514 					dest:	SKIP
 6515 
 6516 	pattern:	kernel: (\s+\w{8}){8}
 6517 
 6518 					dest:	SKIP
 6519 
 6520 	pattern:	kernel: \w+: CDROM not ready\.  Make sure there is a disc in the drive\.
 6521 
 6522 					dest:	SKIP
 6523 
 6524 	pattern:	kernel: \w+: Setting promiscuous mode\.
 6525 
 6526 					dest:	SKIP
 6527 
 6528 	pattern:	kernel: Adding Swap: (\d+)k swap-space \(priority (\-?\d+)\)
 6529 
 6530 					dest:	SKIP
 6531 
 6532 	pattern:	kernel: ATAPI device \w+:
 6533 
 6534 					dest:	SKIP
 6535 
 6536 	pattern:	kernel: cdrom: open failed.
 6537 
 6538 					dest:	SKIP
 6539 
 6540 	pattern:	kernel: Detected (\d+(?:\.\d+)?) (?:M|k)?Hz processor\.
 6541 
 6542 					dest:	SKIP
 6543 
 6544 	pattern:	kernel: Detected PS\/2 Mouse Port\.
 6545 
 6546 					dest:	SKIP
 6547 
 6548 	pattern:	kernel: \s+\"(?:\w\w\s){12}\"
 6549 
 6550 					dest:	SKIP
 6551 
 6552 	pattern:	kernel: Device not ready\.  Make sure there is a disc in the drive\.
 6553 
 6554 					dest:	SKIP
 6555 
 6556 	pattern:	kernel: Disc change detected
 6557 
 6558 					dest:	SKIP
 6559 
 6560 	pattern:	kernel: EFLAGS: .*
 6561 
 6562 					dest:	SKIP
 6563 
 6564 	pattern:	kernel: EIP: .*
 6565 
 6566 					dest:	SKIP
 6567 
 6568 	pattern:	kernel: Linux version .*
 6569 
 6570 					dest:	SKIP
 6571 
 6572 	pattern:	kernel: Memory: .*
 6573 
 6574 					dest:	SKIP
 6575 
 6576 	pattern:	kernel: Process .*
 6577 
 6578 					dest:	SKIP
 6579 
 6580 	pattern:	kernel: sr0: disc change detected
 6581 
 6582 					dest:	SKIP
 6583 
 6584 	pattern:	kernel: UDF-fs DEBUG .*
 6585 
 6586 					dest:	SKIP
 6587 
 6588 	pattern:	kernel: UDF-fs INFO .*
 6589 
 6590 					dest:	SKIP
 6591 
 6592 	pattern:	kernel: EXT3 .* internal journal
 6593 
 6594 					dest:	SKIP
 6595 
 6596 
 6597 	pattern:	named-xfer: send AXFR query 0 to ($pat{ip})
 6598 
 6599 					dest:	SKIP
 6600 
 6601 	pattern:	named: .*(?:Lame server|XSTATS|NSTATS|USAGE|ns_forw|ns_resp).*
 6602 
 6603 					dest:	SKIP
 6604 
 6605 	pattern:	named: .*(?:Cleaned cache|bad referral|points to a CNAME).*
 6606 
 6607 					dest:	SKIP
 6608 
 6609 	pattern:	named: .*(?:all possible.*lame|NS points to CNAME|wrong ans\. name).*
 6610 
 6611 					dest:	SKIP
 6612 
 6613 	pattern:	named: .*(?:send AXFR query| zone .* loaded|sysquery|invalid RR type).*
 6614 
 6615 					dest:	SKIP
 6616 
 6617 	pattern:	named: .*(?:name .* is invalid .* proceeding anyway)
 6618 
 6619 					dest:	SKIP
 6620 
 6621 	pattern:	named: Forwarding source address is .*
 6622 
 6623 					dest:	SKIP
 6624 
 6625 	pattern:	named: invalid RR type .* in authority section
 6626 
 6627 					dest:	SKIP
 6628 
 6629 	pattern:	named: listening on .*
 6630 
 6631 					dest:	SKIP
 6632 
 6633 	pattern:	named: Received NOTIFY answer from .*
 6634 
 6635 					dest:	SKIP
 6636 
 6637 	pattern:	named: Sent NOTIFY for .*
 6638 
 6639 					dest:	SKIP
 6640 
 6641 	pattern:	named: unrelated additional info \'($pat{host})\' type A from \[($pat{ip})\]\.(\d+)
 6642 
 6643 					dest:	SKIP
 6644 
 6645 	pattern:	named: zone transfer .* of .*
 6646 
 6647 					dest:	SKIP
 6648 
 6649 	pattern:	newsyslog: logfile turned over
 6650 
 6651 					dest:	SKIP
 6652 
 6653 	pattern:	ntpdate: step time server
 6654 
 6655 					dest:	SKIP
 6656 
 6657 	pattern:	ofpap: \d+ done
 6658 
 6659 					dest:	SKIP
 6660 
 6661 	pattern:	ofpap: PostScript
 6662 
 6663 					dest:	SKIP
 6664 
 6665 	pattern:	ofpap: done
 6666 
 6667 					dest:	SKIP
 6668 
 6669 	pattern:	ofpap: sending to pap\[\d+\]
 6670 
 6671 					dest:	SKIP
 6672 
 6673 	pattern:	ofpap: starting for \?
 6674 
 6675 					dest:	SKIP
 6676 
 6677 	pattern:	ofpap: straight text
 6678 
 6679 					dest:	SKIP
 6680 
 6681 	pattern:	q?popper: \(v[\d\.]+\) Unable to get canonical name of client,\ err = \d+
 6682 
 6683 					dest:	SKIP
 6684 
 6685 	pattern:	q?popper: Unable to obtain socket and address of client,\ err = \d+
 6686 
 6687 					dest:	SKIP
 6688 
 6689 	pattern:	q?popper: warning: can't verify hostname: gethostbyname\($pat{host}\) failed
 6690 
 6691 					dest:	SKIP
 6692 
 6693 	pattern:	q?popper: (?:$pat{mail_user})?\@\[?$pat{host}\]?: -ERR POP EOF received
 6694 
 6695 					dest:	SKIP
 6696 
 6697 	pattern:	q?popper: (?:$pat{mail_user})?\@\[?$pat{host}\]?: -ERR POP hangup
 6698 
 6699 					dest:	SKIP
 6700 
 6701 	pattern:	q?popper: (?:$pat{mail_user})?\@\[?$pat{host}\]?: -ERR POP timeout
 6702 
 6703 					dest:	SKIP
 6704 
 6705 	pattern:	q?popper: (?:$pat{mail_user})?\@\[?$pat{host}\]?: -ERR SIGHUP or SIGPIPE flagged
 6706 
 6707 					dest:	SKIP
 6708 
 6709 	pattern:	savecore: no core dump
 6710 
 6711 					dest:	SKIP
 6712 
 6713 	pattern:	$pat{sendmail_tag}: $pat{file}: \d+ aliases, longest \d+ bytes, \d+ bytes total
 6714 
 6715 					dest:	SKIP
 6716 
 6717 	pattern:	$pat{sendmail_tag}: Authentication-Warning: $pat{host}: $pat{mail_user} set sender to
 6718 
 6719 					dest:	SKIP
 6720 
 6721 	pattern:	$pat{sendmail_tag}: Authentication-Warning: $pat{host}: $pat{mail_user}\@$pat{host} didn't use HELO protocol
 6722 
 6723 					dest:	SKIP
 6724 
 6725 	pattern:	$pat{sendmail_tag}: alias database $pat{file} (?:auto|)rebuilt by $pat{mail_user}
 6726 
 6727 					dest:	SKIP
 6728 
 6729 	pattern:	$pat{sendmail_tag}: clone ($pat{sendmail_queue_id}), owner\=($pat{mail_user}(?:\@$pat{host})?)
 6730 
 6731 					dest:	SKIP
 6732 
 6733 	pattern:	$pat{sendmail_tag}: $pat{sendmail_queue_id}: clone: owner=$pat{mail_user}
 6734 
 6735 					dest:	SKIP
 6736 
 6737 	pattern:	$pat{sendmail_tag}: collect: premature EOM: Error \d+
 6738 
 6739 					dest:	SKIP
 6740 
 6741 	pattern:	$pat{sendmail_tag}: gethostbyaddr\($pat{ip}\) failed: .*
 6742 
 6743 					dest:	SKIP
 6744 
 6745 	pattern:	$pat{sendmail_tag}: gethostbyaddr: $pat{host} != $pat{ip}
 6746 
 6747 					dest:	SKIP
 6748 
 6749 	pattern:	$pat{sendmail_tag}: to=.*stat=(?:Sent|queued).*
 6750 
 6751 					dest:	SKIP
 6752 
 6753 	pattern:	$pat{sendmail_tag}: from=.*
 6754 
 6755 					dest:	SKIP
 6756 
 6757 	pattern:	$pat{sendmail_tag}: \w+: DSN: .*
 6758 
 6759 					dest:	SKIP
 6760 
 6761 	pattern:	$pat{sendmail_tag}: \w+: return to sender: .*
 6762 
 6763 					dest:	SKIP
 6764 
 6765 	pattern:	sshd: Connection closed by $pat{ip}
 6766 
 6767 					dest:	SKIP
 6768 
 6769 	pattern:	sshd: Generating 768 bit RSA key.
 6770 
 6771 					dest:	SKIP
 6772 
 6773 	pattern:	sshd: Generating new 768 bit RSA key.
 6774 
 6775 					dest:	SKIP
 6776 
 6777 	pattern:	sshd: RSA key generation complete.
 6778 
 6779 					dest:	SKIP
 6780 
 6781 	pattern:	sshd: fatal: Connection closed by remote host\.
 6782 
 6783 					dest:	SKIP
 6784 
 6785 	pattern:	sshd: fatal: Could not write ident string\.
 6786 
 6787 					dest:	SKIP
 6788 
 6789 	pattern:	sshd: fatal: Did not receive ident string\.
 6790 
 6791 					dest:	SKIP
 6792 
 6793 	pattern:	sshd: fatal: Local: Command terminated on signal \d+\.
 6794 
 6795 					dest:	SKIP
 6796 
 6797 	pattern:	sshd: fatal: Read error from remote host: Connection timed out
 6798 
 6799 					dest:	SKIP
 6800 
 6801 	pattern:	sshd: fatal: Read error from remote host: Connection reset (?:by peer)?
 6802 
 6803 					dest:	SKIP
 6804 
 6805 	pattern:	sshd: fatal: Read error from remote host: No route to host
 6806 
 6807 					dest:	SKIP
 6808 
 6809 	pattern:	sshd: fatal: Read from socket failed: Connection reset by peer
 6810 
 6811 					dest:	SKIP
 6812 
 6813 	pattern:	sshd: fatal: Read from socket failed: No route to host
 6814 
 6815 					dest:	SKIP
 6816 
 6817 	pattern:	sshd: fatal: Session canceled by user
 6818 
 6819 					dest:	SKIP
 6820 
 6821 	pattern:	sshd: fatal: Write failed: Broken pipe
 6822 
 6823 					dest:	SKIP
 6824 
 6825 	pattern:	sshd: fatal: Timeout before authentication\.
 6826 
 6827 					dest:	SKIP
 6828 
 6829 	pattern:	sshd: fatal: Timeout before authentication for ($pat{ip})\.
 6830 
 6831 					dest:	SKIP
 6832 
 6833 	pattern:	sshd: log: Closing connection to ($pat{ip})
 6834 
 6835 					dest:	SKIP
 6836 
 6837 	pattern:	sshd: log: fwd X11 connect from
 6838 
 6839 					dest:	SKIP
 6840 
 6841 	pattern:	sshd: log: Generating \d+ bit RSA key.
 6842 
 6843 					dest:	SKIP
 6844 
 6845 	pattern:	sshd: log: Generating new (\d+) bit RSA key\.
 6846 
 6847 					dest:	SKIP
 6848 
 6849 	pattern:	sshd: log: RhostsRsa authentication not available for connections from unprivileged port\.
 6850 
 6851 					dest:	SKIP
 6852 
 6853 	pattern:	sshd: log: Rsa authentication refused for $pat{user}: no $pat{file}/\.ssh\s
 6854 
 6855 					dest:	SKIP
 6856 
 6857 	pattern:	sshd: log: RSA key generation complete\.
 6858 
 6859 					dest:	SKIP
 6860 
 6861 	pattern:	sshd: log: Server listening on port 22.
 6862 
 6863 					dest:	SKIP
 6864 
 6865 	pattern:	sshd: log: Setting tty modes failed
 6866 
 6867 					dest:	SKIP
 6868 
 6869 	pattern:	sshd: log: Wrong response to RSA authentication challenge.
 6870 
 6871 					dest:	SKIP
 6872 
 6873 	pattern:	sshd: log: executing remote command as user ($pat{user})
 6874 
 6875 					dest:	SKIP
 6876 
 6877 	pattern:	snmpd\w.*: local pdu process error
 6878 
 6879 					dest:	SKIP
 6880 
 6881 	pattern:	snmpd\w.*: session_send_loopback_request\(\) failed
 6882 
 6883 					dest:	SKIP
 6884 
 6885 	pattern:	snmpd\w*: session_open\(\) failed for a pdu received from
 6886 
 6887 					dest:	SKIP
 6888 
 6889 	pattern:	su: Authentication failed for $pat{user}
 6890 
 6891 					dest:	SKIP
 6892 
 6893 	pattern:	sudo:\s+ $pat{user} : \(command continued\) .*
 6894 
 6895 					dest:	SKIP
 6896 
 6897 	pattern:	traceroute: gethostbyaddr: .*
 6898 
 6899 					dest:	SKIP
 6900 
 6901 	pattern:	unix:
 6902 
 6903 					dest:	SKIP
 6904 
 6905      pattern:  unix: :
 6906 
 6907                          dest:     SKIP
 6908 
 6909 	pattern:	unix: \t\<SUN[\d\.]+[G]? cyl \d+ alt \d+ hd \d+ sec \d+\>
 6910 
 6911 					dest:	SKIP
 6912 
 6913 	pattern:	unix: Copyright \(c\) 1983-1997\, Sun Microsystems\, Inc\.
 6914 
 6915 					dest:	SKIP
 6916 
 6917 	pattern:	unix: Ethernet address \= ((?:\w+\:){5}\w+)
 6918 
 6919 					dest:	SKIP
 6920 
 6921 	pattern:	unix: \w+ is .*
 6922 
 6923 					dest:	SKIP
 6924 
 6925 	pattern:	unix: SUNW\,\w+ is .*
 6926 
 6927 					dest:	SKIP
 6928 
 6929 	pattern:	unix: \w+ at .*
 6930 
 6931 					dest:	SKIP
 6932 
 6933 	pattern:	unix: SUNW\,\w+ at .*
 6934 
 6935 					dest:	SKIP
 6936 
 6937 	pattern:	unix: \w+: screen \w+x\w+, (?:single|double) buffered, \w+ mappable, rev \w+
 6938 
 6939 					dest:	SKIP
 6940 
 6941 	pattern:	unix: MMCODEC: Manufacturer id \w+, Revision \w+
 6942 
 6943 					dest:	SKIP
 6944 
 6945 	pattern:	unix: No contiguous memory requested for SX
 6946 
 6947 					dest:	SKIP
 6948 
 6949 	pattern:	unix: SBus level \d+ 
 6950 
 6951 					dest:	SKIP
 6952 
 6953 	pattern:	unix: SBus slot \w+ 0x\w+
 6954 
 6955 					dest:	SKIP
 6956 
 6957 	pattern:	unix: SunOS Release ([\d\.]+) Version .*
 6958 
 6959 					dest:	SKIP
 6960 
 6961 	pattern:	unix: avail mem = \w+
 6962 
 6963 					dest:	SKIP
 6964 
 6965 	pattern:	unix: cpu \d+ initialization complete - online
 6966 
 6967 					dest:	SKIP
 6968 
 6969 	pattern:	unix: cpu\w+: \w+,\w+ \(mid \w+ impl 0x\w+ ver 0x\w+ clock \w+ MHz\)
 6970 
 6971 					dest:	SKIP
 6972 
 6973 	pattern:	unix: dump on /dev/dsk/\w+ size \w+
 6974 
 6975 					dest:	SKIP
 6976 
 6977 	pattern:	unix: esp\w+:\s+esp-options=0x\w+
 6978 
 6979 					dest:	SKIP
 6980 
 6981 	pattern:	unix: mem = \w+ \(0x\w+\)
 6982 
 6983 					dest:	SKIP
 6984 
 6985 	pattern:	unix: pac: enabled - SuperSPARC/SuperCache
 6986 
 6987 					dest:	SKIP
 6988 
 6989 	pattern:	unix: pseudo-device: pm\w+
 6990 
 6991 					dest:	SKIP
 6992 
 6993 	pattern:	unix: pseudo-device: vol\w+
 6994 
 6995 					dest:	SKIP
 6996 
 6997 	pattern:	unix: root nexus = SUNW,SPARCstation-\d+
 6998 
 6999 					dest:	SKIP
 7000 
 7001 	pattern:	unix: root on /iommu@\w+,\w+/sbus@\w+,\w+/espdma@\w+,\w+/esp@\w+,\w+/sd@\w+,\w+:a fstype ufs
 7002 
 7003 					dest:	SKIP
 7004 
 7005 	pattern:	unix: sparc ipl \d+
 7006 
 7007 					dest:	SKIP
 7008 
 7009 	pattern:	unix: syncing file systems... done
 7010 
 7011 					dest:	SKIP
 7012 
 7013 	pattern:	unix: syncing file systems...SunOS Release \d+\.\d+
 7014 
 7015 					dest:	SKIP
 7016 
 7017 	pattern:	unix: vac: enabled
 7018 
 7019 					dest:	SKIP
 7020 
 7021 	pattern:	x?ntpd: tickadj \= (\d+), tick = (\d+), tvu_maxslew = (\d+)
 7022 
 7023 					dest:	SKIP
 7024 
 7025 	pattern:	x?ntpd: time reset .*
 7026 
 7027 					dest:	SKIP
 7028 
 7029 	pattern:	x?ntpd: x?ntpd [\d\-\.]+
 7030 
 7031 					dest:	SKIP
 7032 
 7033 	pattern:	x?ntpd: precision = \d+ usec
 7034 
 7035 					dest:	SKIP
 7036 
 7037 	pattern:	x?ntpd: synchronisation lost
 7038 
 7039 					dest:	SKIP
 7040 
 7041 	pattern:	x?ntpd: synchronized to $pat{ip}, stratum=\d+
 7042 
 7043 					dest:	SKIP
 7044 
 7045 	pattern:	x?ntpd: synchronized to LOCAL\(0\), stratum=\d+
 7046 
 7047 					dest:	SKIP
 7048 
 7049 	pattern:	/usr/dt/bin/ttsession: child \(\d+\) exited due to signal \d+
 7050 
 7051 					dest:	SKIP
 7052 
 7053 	pattern:	/usr/dt/bin/ttsession: exiting
 7054 
 7055 					dest:	SKIP
 7056 
 7057 
 7058 # OK, now let's have a bunch of useful rules.
 7059 
 7060 	pattern:	(kernel: device \w+ (?:entered|left) promiscuous mode|lpd: lpd shutdown succeeded|named: Ready to answer queries|named: deleting interface \[($pat{ip})\]\.\d+|named: named shutting down|named: reloading nameserver|named: starting|reboot: rebooted by \w+|rpcbind: rpcbind terminating on signal|$pat{sendmail_tag}: (?:sendmail )?(?:startup|shutdown|restarting|rejecting).*|$pat{sendmail_tag}: starting daemon|sshd: error: Bind to port \d+ on $pat{ip} failed: Address already in use.|sshd: Received signal 15; terminating.|sshd: fatal: Cannot bind any address.|sshd: log: Received signal \d+; terminating|shutdown: reboot by .*|sshd: Received SIGHUP; restarting\.|sshd: Server listening on $pat{ip} port \d+\.|syslogd(?: [\d\.\-\#]+)?: restart|syslogd: configuration restart|syslogd: (?:going down|exiting) on signal \d+|unix: BAD TRAP|x?ntpd: x?ntpd exiting on signal \d+).*
 7061 
 7062 					format:	$1
 7063 					dest:	major events
 7064 
 7065 	pattern:	(inetd: /usr/openwin/bin/Xaserver: Hangup|named: $pat{file}:\d+:.*|$pat{sendmail_tag}: alias database $pat{file} out of date|kernel: EXT2\-fs error \(device [\w\:]+\):|$pat{sendmail_tag}: SYSERR: Cannot create database for alias file .*|$pat{sendmail_tag}: SYSERR: dbm map .*|$pat{sendmail_tag}: SYSERR: MX list for .* points back to .*|$pat{sendmail_tag}: unable to write ${pat{file}}|$pat{sendmail_tag}: forward $pat{file}\+?: Group writable directory|sshd: error: bind: Address already in use|sshd: fatal: Bind to port \d+ failed: Address already in use.|x?ntpd: can't open $pat{file}:.*)
 7066 
 7067 					format:	$1
 7068 					dest:	stuff that might need fixing
 7069 
 7070 	pattern:  (kernel: end_request: I/O error,\ dev [^\,]+),\ sector \d+
 7071 
 7072 					format:	$1
 7073 					dest:	stuff that might need fixing
 7074 
 7075 	pattern:	kernel: Out of Memory: Killed process (\d+) \(($pat{file})\)\.
 7076 
 7077 					format: kernel: out of memory, killed process $2
 7078 					dest:	stuff that might need fixing
 7079 
 7080 	pattern:	$pat{sendmail_tag}: .*: (cannot open $pat{file}: Group writable directory)
 7081 
 7082 					format:	$1
 7083 					dest:	stuff that might need fixing
 7084 
 7085 	pattern:	ofpap: \d+ died with (\d+)
 7086 
 7087 					format:	ofpap: died with $1
 7088 					dest:	stuff that might need fixing
 7089 
 7090 	pattern:	(?:in\.)?ftpd: refused connect from ($pat{host})
 7091 
 7092 					format:	$1
 7093 					dest:	FTP: refused connection from
 7094 
 7095 	pattern:	(?:in\.)?ftpd: connect(?:ion)? from ($pat{host})(?: \[$pat{ip}\])?
 7096 
 7097 					format:	$1
 7098 					dest:	FTP: connection from
 7099 
 7100 	pattern:	ftpd: ANONYMOUS FTP LOGIN FROM ($pat{host}) \[($pat{ip})\],\ ($pat{user}(?:\@(?:$pat{host}|)|))
 7101 
 7102 					format:	$3 from $1 ($2)
 7103 					dest:	FTP: anonymous login
 7104 
 7105 	pattern:	ftpd: ANONYMOUS FTP LOGIN FROM ($pat{host}) \[($pat{ip})\],?
 7106 
 7107 					format:	unknown from $1 ($2)
 7108 					dest:	FTP: anonymous login
 7109 
 7110 	pattern:	mountd: refused mount request from ($pat{host})
 7111 
 7112 					format:	$1
 7113 					dest:	NFS: refused with
 7114 
 7115 	pattern:	mountd: authenticated mount request from ($pat{host}):\d+
 7116 
 7117 					format:	$1
 7118 					dest:	NFS: authorized NFS with
 7119 
 7120 	pattern:	mountd: authenticated unmount request from ($pat{host}):\d+
 7121 
 7122 					format:	$1
 7123 					dest:	NFS: authorized NFS with
 7124 
 7125 	pattern:	PAM_pwdb: \(login\) session opened for user ($pat{user})\s+
 7126 
 7127 					format:	$1
 7128 					dest:	login: successful login
 7129 
 7130 	pattern: ipmon: \d+:\d+:\d+\.\d+(?:\s*\d+x)? +(\w+) \@\d+:\d+ b ($host_pat)\,([\w\-]+) -> ($host_pat)\,([\w\-]+) PR (\w+) len \d+ \d+.*$
 7131 		format: $2 => $4 $6 $5
 7132 		dest: ipmon: blocked packet
 7133 
 7134 	pattern: ipmon: \d+:\d+:\d+\.\d+(?:\s*\d+x)? +(\w+) \@\d+:\d+ b ($host_pat) -> ($host_pat) PR (icmp) len \d+ \d+ icmp (\w+\/\w+) .*$
 7135 		format: $2 => $3 $4 $5
 7136 		dest: ipmon: blocked packet
 7137 
 7138 	pattern: ipmon: \d+:\d+:\d+\.\d+(?:\s*\d+x)? +(\w+) \@\d+:\d+ b ($host_pat) -> ($host_pat) PR (\d+) len \d+ \(\d+\) IN.*$
 7139 		format: $2 => $3 proto-$4
 7140 		dest: ipmon: blocked packet
 7141 
 7142 	pattern:	kernel: IP fw-in deny \w+ (\w+) ($pat{ip}):(\d+) ($pat{ip}):(\d+)
 7143 
 7144 					format:	$1 $2 => $4:$5
 7145 					dest:	CATEGORY kernel: firewall deny
 7146 
 7147 					format: $2, $4
 7148 					dest:   UNIQUE scans
 7149 
 7150 	pattern:	kernel: Packet log: inp(?:ut)? DENY \w+ PROTO=17 ($pat{ip}):(\d+) ($pat{ip}):(\d+) .*
 7151 
 7152 					use_sprintf
 7153 					format:	"%-15s => %-15s UDP   %-5s", $1, $3, $4
 7154 					dest:	CATEGORY kernel: firewall deny
 7155 
 7156 					format: $1, $3
 7157 					dest:   UNIQUE scans
 7158 
 7159 	pattern:	kernel: Packet log: inp(?:ut)? DENY \w+ PROTO=1 ($pat{ip}):(\d+) ($pat{ip}):(\d+) .*
 7160 
 7161 					use_sprintf
 7162 					format:	"%-15s => %-15s ICMP  %-5s", $1, $3, $2
 7163 					dest:	CATEGORY kernel: firewall deny
 7164 
 7165 					format: $1, $3
 7166 					dest:   UNIQUE scans
 7167 
 7168 	pattern:	kernel: Packet log: inp(?:ut)? DENY \w+ PROTO=6 ($pat{ip}):(\d+) ($pat{ip}):(\d+) .*
 7169 
 7170 					use_sprintf
 7171 					format:	"%-15s => %-15s TCP   %-5s", $1, $3, $4
 7172 					dest:	CATEGORY kernel: firewall deny
 7173 
 7174 					format: $1, $3
 7175 					dest:   UNIQUE scans
 7176 
 7177 	pattern:	kernel: Packet log: inp(?:ut)? DENY \w+ PROTO=2 ($pat{ip}):(\d+) ($pat{ip}):(\d+) .*
 7178 
 7179 					use_sprintf
 7180 					format:	"%-15s => %-15s IGMP  %-5s", $1, $3, $4
 7181 					dest:	CATEGORY kernel: firewall deny
 7182 
 7183 					format: $1, $3
 7184 					dest:   UNIQUE scans
 7185 
 7186 	pattern:	kernel: Packet log: inp(?:ut)? REJECT \w+ PROTO=17 ($pat{ip}):(\d+) ($pat{ip}):(\d+) .*
 7187 
 7188 					use_sprintf
 7189 					format:	"%-15s => %-15s UDP   %-5s", $1, $3, $4
 7190 					dest:	CATEGORY kernel: firewall reject
 7191 
 7192 					format: $1, $3
 7193 					dest:   UNIQUE scans
 7194 
 7195 	pattern:	kernel: Packet log: inp(?:ut)? REJECT \w+ PROTO=1 ($pat{ip}):(\d+) ($pat{ip}):(\d+) .*
 7196 
 7197 					use_sprintf
 7198 					format:	"%-15s => %-15s ICMP  %-5s", $1, $3, $2
 7199 					dest:	CATEGORY kernel: firewall reject
 7200 
 7201 					format: $1, $3
 7202 					dest:   UNIQUE scans
 7203 
 7204 	pattern:	kernel: Packet log: inp(?:ut)? REJECT \w+ PROTO=6 ($pat{ip}):(\d+) ($pat{ip}):(\d+) .*
 7205 
 7206 					use_sprintf
 7207 					format:	"%-15s => %-15s TCP   %-5s", $1, $3, $4
 7208 					dest:	CATEGORY kernel: firewall reject
 7209 
 7210 					format: $1, $3
 7211 					dest:   UNIQUE scans
 7212 
 7213 	pattern:	kernel: Packet log: inp(?:ut)? REJECT \w+ PROTO=2 ($pat{ip}):(\d+) ($pat{ip}):(\d+) .*
 7214 
 7215 					use_sprintf
 7216 					format:	"%-15s => %-15s IGMP  %-5s", $1, $3, $4
 7217 					dest:	CATEGORY kernel: firewall reject
 7218 
 7219 					format: $1, $3
 7220 					dest:   UNIQUE scans
 7221 
 7222 	# this next one contributed by Tim Meushaw and modified
 7223 	pattern:	pattern: kernel: Denied Packet:.* SRC=($pat{ip}) DST=($pat{ip}).* PROTO=([A-Z]+) SPT=(\d+) DPT=(\d+) .*
 7224 
 7225 					use_sprintf
 7226 					format: "%-15s => %-15s %-5s %-5s", $1, $2, $3, $5
 7227 					dest:   CATEGORY kernel: firewall deny
 7228 
 7229 					format: $1, $2
 7230 					dest:   UNIQUE scans
 7231 
 7232 	pattern: kernel: IN=($pat{word})? OUT=($pat{word})? MAC=($pat{mac}):($pat{mac}):([[:xdigit:]]{2}:[[:xdigit:]]{2}) SRC=($pat{ip}) DST=($pat{ip}) LEN=(\d+) TOS=0x([[:xdigit:]]{2}) PREC=0x([[:xdigit:]]{2}) TTL=(\d+) ID=(\d+) (DF )?PROTO=(\w+) SPT=(\d+) DPT=(\d+).*
 7233 
 7234 					use_sprintf:
 7235 					format: "%-15s => %-15s %-5s %-5s", $6, $7, $14, $16
 7236 					dest: kernel: firewall deny
 7237 
 7238 					format: $6, $7
 7239 					dest: UNIQUE scans
 7240 
 7241 
 7242 	pattern: kernel: IN=($pat{word})? OUT=($pat{word})? MAC=($pat{mac}):($pat{mac}):([[:xdigit:]]{2}:[[:xdigit:]]{2}) SRC=($pat{ip}) DST=($pat{ip}) LEN=(\d+) TOS=0x([[:xdigit:]]{2}) PREC=0x([[:xdigit:]]{2}) TTL=(\d+) ID=(\d+) (DF )?PROTO=(\w+) TYPE=(\d+) CODE=(\d+).*
 7243 
 7244 					use_sprintf:
 7245 					format: "%-15s => %-15s %-5s %-5s", $6, $7, $14, $15
 7246 					dest: kernel: firewall deny
 7247 
 7248 					format: $6, $7
 7249 					dest: UNIQUE scans
 7250 
 7251 	pattern:	login: invalid password for \`($pat{user})\' on \`($pat{file})\'
 7252 
 7253 					format:	$1
 7254 					dest:	login: authentication failure for
 7255 
 7256 	pattern:	login: invalid password for \`($pat{user})\' on \`($pat{file})\' from \`($pat{host})\'
 7257 
 7258 					format:	$1
 7259 					dest:	login: authentication failure for
 7260 
 7261 	pattern:	login: LOGIN ON ($pat{file}) BY ($pat{user})
 7262 
 7263 					format:	$2
 7264 					dest:	login: successful local login
 7265 
 7266 	pattern:	login: FAILED LOGIN \d+ FROM \($pat{host}\) FOR ($pat{user}),\ Authentication failure
 7267 
 7268 					format:	$1
 7269 					dest:	login: authentication failure for
 7270 
 7271 	pattern:	named: zone $pat{zone}/IN: loaded serial $pat{int}
 7272 
 7273 					dest: SKIP
 7274 
 7275 	pattern:	named.*Zone "($pat{zone})".* No default TTL set using SOA minimum instead
 7276 
 7277 					format:	$1
 7278 					dest:	named: no default TTL (bind 8.2)
 7279 
 7280 	pattern:	named.*Err/TO getting serial# for "($pat{zone})"
 7281 
 7282 					format:	$1
 7283 					dest:	named: serial number errors for zone
 7284 
 7285 	pattern:	named.*Zone "($pat{zone})" \(IN\) SOA serial\# \(\d+\) rcvd from \[($pat{ip})\] is \< ours
 7286 
 7287 					format:	$2 for $1
 7288 					dest:	named: master has serial number too low for zone
 7289 
 7290 	pattern:	named-xfer: serial from \[($pat{ip})\],\ zone ($pat{zone})\: \d+ lower than current\: \d+
 7291 
 7292 					format:	$1 for $2
 7293 					dest:	named: master has serial number too low for zone
 7294 
 7295 	pattern:	named-xfer: \[($pat{ip})\] not authoritative for ($pat{zone})\,
 7296 
 7297 					format:	$1 for $2
 7298 					dest:	named: master server is not authoritative for zone
 7299 
 7300 	pattern:	named-xfer: connect\(($pat{ip})\) for zone ($pat{zone}) failed: (.*)
 7301 
 7302 					format:	$1 for $2
 7303 					dest:	named: connect to master server for zone failed
 7304 
 7305 	pattern:	named-xfer\: \[\[($pat{ip})\].\d+\] transfer refused from \[($pat{ip})\]\,\ zone ($pat{zone})
 7306 
 7307 					format:	$3 from $2
 7308 					dest:	named: we were refused transfer
 7309 
 7310      #mta take advantage of zone info
 7311 	pattern:	(?:$pat{file})?named: unapproved AXFR from \[($pat{ip})\]\.(\d+) for \"($pat{zone})\" \(acl\)
 7312 
 7313 					format:	$1
 7314 					dest:	named: unapproved zone transfer requested by
 7315 
 7316 	pattern:	(?:$pat{file})?named: denied AXFR from \[($pat{ip})\]\.(\d+) for \"($pat{zone})\" \(acl\)
 7317 
 7318 					format:	$1 for $3
 7319 					dest:	named: unapproved zone transfer requested by
 7320 
 7321 	pattern:    (?:$pat{file})?named: client ($pat{ip})\#(\d+): zone transfer denied
 7322 
 7323 					format:	$1
 7324 					dest:	named: unapproved zone transfer requested by
 7325 
 7326 	pattern:	(?:$pat{file})?named: unapproved update from \[($pat{ip})\]
 7327 
 7328 					format:	$1
 7329 					dest:	named: unapproved update from
 7330 
 7331      #mta: this next guy should probably display the zone
 7332 	pattern:	(?:$pat{file})?named: approved AXFR from \[($pat{ip})\]\.(\d+) for \"($pat{zone})\"
 7333 
 7334 					format:	$1
 7335 					dest:	named: approved zone transfer requested by
 7336 
 7337 	pattern:	(?:$pat{file})?named: client ($ip_pat)\#($pat{port}): transfer of \'($pat{zone})\': AXFR started
 7338 
 7339 					format: $1
 7340 					dest: named: zone transfer started to
 7341 
 7342 	pattern:	(?:$pat{file})?named: zone ($pat{zone}): refresh: failure trying master ($pat{ip})\#($pat{port}): timed out
 7343 
 7344 					format: $2 for $1
 7345 					dest: named: AXFR timed out
 7346 
 7347 	pattern:	(?:$pat{file})?named: zone ($pat{zone}): refresh: retry limit for master ($ip_pat)\#($pat{port}) exceeded
 7348 
 7349 					format: $2 for $1
 7350 					dest: named: retry limit for zone exceeded
 7351 
 7352 	pattern:	(?:$pat{file})?named: Response from unexpected source \(\[($pat{ip})\]\.(\d+)\)
 7353 
 7354 					format:	$1
 7355 					dest:	named: responses from unexpected sources
 7356 
 7357 	pattern:	(?:$pat{file})?named: Malformed response from \[($pat{ip})\]\.(\d+) \((.*)\)
 7358 
 7359 					format:	$1
 7360 					dest:	named: malformed response
 7361 
 7362 	pattern:	(?:$pat{file})?named: unapproved query from \[($pat{ip})\]\.\d+ for \"($pat{host})\"
 7363 
 7364 					format:	$1 for $2
 7365 					dest:	named: unapproved query
 7366 
 7367 	pattern:	(?:$pat{file})?named: creating IPv4 interface ([\w\:]+) failed; interface ignored
 7368 
 7369 					format: $1
 7370 					dest: named: creating IPv4 interface failed; interface ignored
 7371 
 7372 	pattern:	(?:$pat{file})?named: client ($ip_pat)\#($pat{port}): update forwarding denied
 7373 
 7374 					format: $1
 7375 					dest: named: client update forwarding denied
 7376 
 7377 	pattern:	(?:$pat{file})?named: client ($ip_pat)\#($pat{port}): update '($pat{zone})' denied
 7378 
 7379 					format: $1
 7380 					dest: named: client update denied
 7381 
 7382 	pattern: (?:$pat{file})?named: zone ($pat{zone}): transferred serial (\d+)
 7383 		format: $1
 7384 		dest: named: successfully transferred zone
 7385 	pattern: (?:$pat{file})?named: client ($ip_pat)\#($pat{port}): transfer of '($pat{zone})': send: socket is not connected
 7386 		format: $1 for $3
 7387 		dest: named: transfer failed: socket is not connected
 7388 	pattern: (?:$pat{file})?named: transfer of '($pat{zone})' from ($pat{ip})\#($pat{port}): end of transfer
 7389 		format: $2 for $1
 7390 		dest: named: zone transfer completed
 7391 	pattern: (?:$pat{file})?named: zone ($pat{zone}): refresh: failure trying master ($pat{ip})#($pat{port}): operation canceled
 7392 		format: $2 for $1
 7393 		dest: named: refresh failure trying master: operation cancelled
 7394 	pattern: (?:$pat{file})?named: loading configuration from '($pat{file})'
 7395 		dest: SKIP
 7396 	pattern: (?:$pat{file})?named: zone ($pat{zone}): refresh: non-authoritative answer from master ($pat{ip})\#($pat{port})
 7397 		format: $2 for $1
 7398 		dest: named: non-authoritative answer from zone master
 7399 
 7400 	pattern: sshd: Accepted hostbased for ($user_pat) from ($ip_pat) port (\d+) ssh2
 7401 		format: $1 from $2
 7402 		dest:	sshd: accepted hostbased
 7403 
 7404 	pattern: $pat{named_tag}: client ($pat{ip})\#($pat{port}): transfer of '($pat{zone})': AXFR-style IXFR started
 7405 		format: $1 for $3
 7406 		dest: named: AXFR-style IXFR started
 7407 
 7408 	pattern: named: client ($pat{ip})\#($pat{port}): updating zone '($pat{zone})': update failed: 'RRset exists \(value dependent\)' prerequisite not satisfied \(NXRRSET\)
 7409 		format: $1 for $3
 7410 		dest: named: update failed; RRset does not even exist
 7411 
 7412 	pattern:	nscd: gethostbyaddr: ($pat{host}) \!\= ($pat{ip})
 7413 
 7414 					format:	$1 != $2
 7415 					dest:	nscd: host/IP mismatch
 7416 
 7417 	pattern:	pam_tally: user ($pat{user}) \((\d+)\) tally (\d+),\ deny (\d+)
 7418 
 7419 					format:	$1 ($2) limit $4
 7420 					dest:	pam_tally: user attempting login after exceeding login failure limit
 7421 
 7422 	pattern:	q?popper: ($pat{mail_user})\@\[?($pat{host})\]?: -ERR (?:authentication failure|Password supplied for "$pat{mail_user}" is incorrect\.?|not authorized)
 7423 
 7424 					format:	$1\@$2
 7425 					dest:	popper: authentication failure
 7426 
 7427 	pattern:	q?popper: Failed attempted login to ($pat{user}) from host ($pat{host})
 7428 
 7429 					format:	$1\@$2
 7430 					dest:	popper: authentication failure
 7431 
 7432 	pattern:	q?popper: ($pat{user})\@$pat{host}: -ERR $pat{file} lock busy\!  Is another session active\? \(11\)
 7433 
 7434 					format:	$1
 7435 					dest:	popper: POP lock for
 7436 
 7437 	pattern:	q?popper: Stats: ($pat{mail_user}) (\d+) (\d+) (\d+) (\d+)
 7438 
 7439 					format:	$1
 7440 					dest:	popper: users checked mail
 7441 
 7442 	pattern:	q?popper: Stats: ($pat{mail_user}) (\d+) (\d+) (\d+) (\d+) ($pat{host}) ($pat{ip})
 7443 
 7444 					format:	$1
 7445 					dest:	popper: users checked mail
 7446 
 7447 	pattern:	q?popper: apop \"($pat{mail_user})\"\s*
 7448 
 7449 					format:	$1
 7450 					dest:	popper: user is using apop
 7451 
 7452 	pattern:	q?popper: ($pat{mail_user})\@\[?($pat{host})\]?: -ERR You must use APOP to connect to this server
 7453 
 7454 					format:	$1\@$2
 7455 					dest:	popper: should have used APOP
 7456 
 7457 	pattern:	q?popper: @($pat{host}): -ERR Too few arguments for the auth command.
 7458 
 7459 					format:	$1
 7460 					dest:	popper: too few arguments for the auth command
 7461 
 7462 	pattern:	q?popper: ((?:$pat{user})?\@$pat{host}): -ERR Unknown command: "(\w+)".
 7463 
 7464 					format:	$2 from $1
 7465 					dest:	popper: unknown command from
 7466 
 7467 	pattern:	unix: NOTICE: quota_ufs: (?:Warning: over disk|over disk and time|over hard disk) limit \(pid \d+,\ uid (\d+), inum (\d+), fs ($pat{file})\)(?:\^M)?
 7468 
 7469 					format:	$1
 7470 					dest:	quota exceeded (user's UID)
 7471 
 7472 	pattern:	$pat{sendmail_tag}: to=([^\,]+)\,.*stat=(unknown mailer error)
 7473 
 7474 					format:	'$1' got '$2'
 7475 					dest:	sendmail: delivery failed
 7476 
 7477 	pattern:	$pat{sendmail_tag}: to=([^\,]+)\,.*stat=Deferred[^\,]*
 7478 
 7479 					format:	'$1'
 7480 					dest:	sendmail: delivery deferred
 7481 
 7482 	pattern:	$pat{sendmail_tag}: to=([^\,]+)\,.*stat=(.*)
 7483 
 7484 					format:	'$1' got '$2'
 7485 					dest:	sendmail: delivery failed
 7486 
 7487 	pattern:	$pat{sendmail_tag}: \<([^\>]+)\>\.\.\.\s*(.*\S)
 7488 
 7489 					format:	'$1' got '$2'
 7490 					dest:	sendmail: delivery failed
 7491 
 7492 	pattern:	$pat{sendmail_tag}: ruleset=check_mail,\ arg1=\<?$pat{mail_user}\@($pat{host})\>?,\ relay=([^\,]+),\ reject\=.*(Sender domain.*|DENY)
 7493 
 7494 					format:	from user '$1' from server '$2' because '$3'
 7495 					dest:	sendmail: we rejected incoming mail
 7496 
 7497 	pattern:	$pat{sendmail_tag}: ruleset=check_mail,\ arg1=([^\,]+),\ relay=([^\,]+),\ reject\=.*(Domain name required).*
 7498 
 7499 					format:	from user '$1' from server '$2' because '$3'
 7500 					dest:	sendmail: we rejected incoming mail
 7501 
 7502 	pattern:	$pat{sendmail_tag}: ruleset=check_rcpt,\ arg1=([^\,]+),\ relay=([^\,]+),\ reject=.*\.\.\.\s*(.*\S)
 7503 
 7504 					format:	from user '$1' from server '$2' because '$3'
 7505 					dest:	sendmail: we rejected incoming mail
 7506 
 7507 	pattern:	$pat{sendmail_tag}: timeout waiting for input from ($pat{host}) .*
 7508 
 7509 					format:	$1
 7510 					dest:	sendmail: communications problems with
 7511 
 7512 	pattern:	$pat{sendmail_tag}: lost input channel from (.*)
 7513 
 7514 					format:	'$1'
 7515 					dest:	sendmail: communications problems with
 7516 
 7517 	pattern:	$pat{sendmail_tag}: (?:SYSERR: )?collect: (?:I\/O error on connection from|premature EOM: Connection reset by|unexpected close on connection from) ($pat{host}|\[$pat{ip}\]).*
 7518 
 7519 					format:	'$1'
 7520 					dest:	sendmail: communications problems with
 7521 
 7522 	pattern:	$pat{sendmail_tag}: Null connection from (.*)
 7523 
 7524 					format:	'$1'
 7525 					dest:	sendmail: Null connect from
 7526 
 7527 	pattern:	$pat{sendmail_tag}: ((?:IDENT:)?[^\:\,]+): expn ($pat{mail_user}) \[rejected\]
 7528 
 7529 					format:	'$1' expnd '$2'
 7530 					dest:	sendmail: expn rejected
 7531 
 7532 	pattern:	$pat{sendmail_tag}: ((?:IDENT:)?[^\:\,]+): expn ($pat{mail_user})
 7533 
 7534 					format:	'$1' expnd '$2'
 7535 					dest:	sendmail: expn allowed
 7536 
 7537 	pattern:	$pat{sendmail_tag}: ((?:IDENT:)?[^\:\,]+): vrfy ($pat{mail_user}) \[rejected\]
 7538 
 7539 					format:	'$1' vrfyd '$2'
 7540 					dest:	sendmail: vrfy rejected
 7541 
 7542 	pattern:	$pat{sendmail_tag}: ((?:IDENT:)?[^\:\,]+): vrfy ($pat{mail_user})
 7543 
 7544 					format:	'$1' vrfyd '$2'
 7545 					dest:	sendmail: vrfy allowed
 7546 
 7547 	pattern:	$pat{sendmail_tag}: Authentication-Warning: ($pat{host}): ($pat{mail_user}) set sender to ($pat{mail_user})\@($pat{host}) using -f
 7548 
 7549 					dest: SKIP
 7550 
 7551 	pattern:	sshd: log: Unknown group id (\d+)
 7552 
 7553 					format:	$1
 7554 					dest:	sshd: login to unknown group (check /etc/passwd)
 7555 
 7556 	pattern:	sshd: Could not reverse map address ($pat{ip})\.
 7557 
 7558 					format:	$1
 7559 					dest:	sshd: could not reverse map address
 7560 
 7561 	pattern:	sshd: log: Connection for ($pat{user}) not allowed from ($pat{host})
 7562 
 7563 					format:	$1 from $2
 7564 					dest:	sshd: denied access
 7565 
 7566 	pattern:	sshd: Did not receive ident string from ($pat{ip})\.
 7567 
 7568 					format:	$1
 7569 					dest:	sshd: did not receive ident string from host
 7570 
 7571 	pattern:	sshd: log: Connection from ($pat{ip}) port (\d+)
 7572 
 7573 					format:	$1
 7574 					dest:	sshd: connection from
 7575 
 7576 	pattern:	sshd: log: (?:RSA|Password) authentication for ($pat{user}) accepted\.?
 7577 
 7578 					format:	$1
 7579 					dest:	sshd: authentications for
 7580 
 7581 	pattern:	sshd: log: Rhosts with RSA host authentication accepted for ($pat{user}),\ ($pat{user}) on ($pat{host})\.
 7582 
 7583 					format:	$1 from $2\@$3
 7584 					dest:	sshd: authentications for
 7585 
 7586 	pattern:	sshd: log: Could not reverse map address ($pat{ip})\.
 7587 
 7588 					format:	$1
 7589 					dest:	sshd: could not reverse map
 7590 
 7591 	pattern:	sshd: Failed keyboard-interactive for ($pat{user}) from ($pat{ip}) port \d+
 7592 
 7593 					format:	$1 from $2
 7594 					dest:	sshd: failed keyboard-interactive
 7595 
 7596 	pattern:	sshd: Failed password for ($pat{user}) from ($pat{ip}) port \d+
 7597 
 7598 					format:	$1 from $2
 7599 					dest:	sshd: failed password
 7600 
 7601 	pattern:	sshd: Accepted password for ($pat{user}) from ($pat{ip}) port \d+(?:\s+ssh2)?
 7602 
 7603 					format:	$1 from $2
 7604 					dest:	sshd: accepted password
 7605 
 7606 	pattern:	sshd: Accepted publickey for ($pat{user}) from ($pat{ip}) port \d+(?:\s+ssh2)?
 7607 
 7608 					format:	$1 from $2
 7609 					dest:	sshd: accepted publickey
 7610 
 7611 	pattern:	sshd: Accepted rsa for ($pat{user}) from ($pat{ip}) port \d+
 7612 
 7613 					format:	$1 from $2
 7614 					dest:	sshd: accepted rsa
 7615 
 7616 	pattern:	sshd: Faking authloop for illegal user ($pat{user}) from ($pat{ip}) port (\d+)
 7617 
 7618 					format:	$1 from $2
 7619 					dest:	sshd: illegal user
 7620 
 7621 	pattern:	su\: \'su ($pat{user})\' failed for ($pat{user}) on ($pat{file})
 7622 
 7623 					format:	$2 => $1
 7624 					dest:	su: failed for
 7625 
 7626 	pattern:	su\: \'su ($pat{user})\' succeeded for ($pat{user}) on ($pat{file})
 7627 
 7628 					format:	$2 => $1
 7629 					dest:	su: succeeded for
 7630 
 7631 	pattern:	su: \- $pat{file} ($pat{user})-($pat{user})
 7632 
 7633 					format:	$1 => $2
 7634 					dest:	su: failed for
 7635 
 7636 	pattern:	su: \+ $pat{file} ($pat{user})-($pat{user})
 7637 
 7638 					format:	$1 => $2
 7639 					dest:	su: succeeded for
 7640 
 7641 	pattern:	PAM_pwdb: \(su\) session opened for user ($pat{user}) by ($pat{user})
 7642 
 7643 					format:	$2 => $1
 7644 					dest:	su: succeeded for
 7645 
 7646 	pattern:	PAM_pwdb: \d+ authentication failure\;\ ($pat{user})\(uid=\d+\) \-\> ($pat{user}) for su service
 7647 
 7648 					format:	$1 => $2
 7649 					dest:	su: failed for
 7650 
 7651 	pattern:	su: ($pat{user}) to ($pat{user}) on $pat{file}
 7652 
 7653 					format:	$1 => $2
 7654 					dest:	su: succeeded for
 7655 
 7656 	pattern:	sudo:\s*($pat{user}) : user NOT in sudoers ; TTY=$pat{file} ; PWD=$pat{file} ; USER=$pat{user} ; COMMAND=($pat{file}).*
 7657 
 7658 					format:	$1 ran $2
 7659 					dest:	sudo: unauthorized user ran command
 7660 
 7661 	pattern:	sudo:\s+($pat{user}) : TTY=$pat{file} ; PWD=$pat{file} ; USER=$pat{user} ; COMMAND=($pat{file}).*
 7662 
 7663 					format:	$1 ran $2
 7664 					dest:	sudo: authorized user ran command
 7665 
 7666 	pattern:	sudo:\s+($pat{user}) : (?:3 incorrect passwords|password incorrect) ; TTY=$pat{file} ; PWD=$pat{file} ; USER=$pat{user} ; COMMAND=($pat{file}).*
 7667 
 7668 					format:	$1 ran $2
 7669 					dest:	sudo: incorrect password
 7670 
 7671 	pattern:	snmpdx?: agent_process\(\) : bad community from ($pat{ip})
 7672 
 7673 					format:	$1
 7674 					dest:	snmpd: bad community from
 7675 
 7676 	pattern:	identd: Connection from ($pat{host})
 7677 
 7678 					format:	$1
 7679 					dest:	identd: connection from
 7680 
 7681 	pattern:	([^\:\s]+): refused connect from (.*\S)
 7682 
 7683 					format:	$1 from $2
 7684 					dest:	service refused connection
 7685 
 7686 	pattern:	([^\:\s]+): connect from (.*\S)
 7687 
 7688 					format:	$1 from $2
 7689 					dest:	service allowed connection
 7690 
 7691 	pattern:	tftpd: tftpd: trying to get file: ($pat{file})
 7692 
 7693 					format:	$1
 7694 					dest:	tftpd: trying to get file
 7695 
 7696 	# bind says a "user@host:/somedir/named" when it starts
 7697 	pattern:	$pat{user}\@$pat{host}:$pat{file}/named
 7698 
 7699 					format:	named started
 7700 					dest:	major events
 7701 
 7702 	# OpenBSD says a "user@host:/somedir/GENERIC" when it starts
 7703 	pattern:	/bsd:\s*$pat{user}\@$pat{host}:$pat{file}/GENERIC
 7704 
 7705 					format:	booted
 7706 					dest:	major events
 7707 
 7708 
 7709 	pattern: spamd: info: setuid to ($user_pat) succeeded
 7710 		dest: SKIP
 7711 
 7712 	pattern: spamd: connection from ($host_pat) \[($ip_pat)\] at port (\d+)
 7713 		dest: SKIP
 7714 
 7715 	pattern: clamav-milter: ($pat{sendmail_queue_id}): clean message from <($mail_user_pat\@$host_pat)>
 7716 		dest: SKIP
 7717 
 7718 	pattern: clamav-milter: ($pat{sendmail_queue_id}): Intercepted virus from <($pat{mail_address})> to <($pat{mail_address})>
 7719 		format: from $2 to $3
 7720 		dest: clamav-milter: intercepted virus
 7721 
 7722 	pattern: clamav-milter: ($pat{sendmail_queue_id}): stream: (.*) FOUND Intercepted virus from <($pat{mail_address})> to <($pat{mail_address})>
 7723 		format: from $4 to $3
 7724 		dest: clamav-milter: intercepted virus
 7725 
 7726 	pattern: clamav-milter: stream: ($file_pat) FOUND
 7727 		format: $1
 7728 		dest: clamav-milter found virus
 7729 
 7730 	pattern: clamd: stream: ($file_pat) FOUND
 7731 		format: $1
 7732 		dest: clamd found virus
 7733 
 7734 	pattern: ($pat{sendmail_tag}): Milter (?:add|change|delete): .*
 7735 		dest: SKIP
 7736 
 7737 	pattern: ($pat{sendmail_tag}): ($pat{user})\@?($host_pat) \[($pat{ip})\]\: ETRN ($pat{zone})
 7738 		format: $5 from $4
 7739 		dest: sendmail: received ETRN from IP
 7740 
 7741 	# this looks like some sort of continuation thing.
 7742 	pattern: ($pat{sendmail_tag}): ($pat{sendmail_queue_id})\[([2-9]|\d\d+)\]:.*
 7743 		dest: SKIP
 7744 
 7745 	pattern: spamd: clean message \((\d+\.\d+)/(\d+\.\d+)\) for ($user_pat):(\d+) in (\d+\.\d+) seconds, (\d+) bytes.
 7746 		dest: SKIP
 7747 
 7748 	pattern: spamd: identified spam \((\d+\.\d+)/(\d+\.\d+)\) for ($user_pat):(\d+) in (\d+\.\d+) seconds, (\d+) bytes.
 7749 		format: $3
 7750 		dest: spamd: identified spam sent for user
 7751 
 7752 	pattern: spamd: processing message (.*) for ($user_pat):(\d+)\.
 7753 		dest: SKIP
 7754 
 7755 	pattern: spamd: processing message \(unknown\) for ($user_pat):(\d+)\.
 7756 		dest: SKIP
 7757 
 7758 	pattern: clamd: Database correctly reloaded \((\d+) viruses\)
 7759 		dest: SKIP
 7760 
 7761 	pattern: clamd: Reading databases from ($file_pat)
 7762 		dest: SKIP
 7763 
 7764 	pattern: clamd: SelfCheck: Database modification detected. Forcing reload.
 7765 		dest: SKIP
 7766 
 7767 	pattern: clamd: SelfCheck: Database status OK.
 7768 		dest: SKIP
 7769 
 7770 	pattern: named: transfer of \'($zone_pat)/IN\' from ($pat{ip})\#($pat{port}): failed while receiving responses: (.*)
 7771 		format: $1 from $2 error $4
 7772 		dest: named: transfer failed
 7773 
 7774 	pattern: (spamd: Still running as ($pat{user}): user not specified with -u, not found, or set to ($pat{user}).  Fall back to ($pat{user}).)
 7775 		format: $1
 7776 		dest: stuff that might need fixing
 7777 
 7778 	pattern: (sshd: rexec line ($pat{int}): Deprecated option .*)
 7779 		format: $1
 7780 		dest: stuff that might need fixing
 7781 
 7782 
 7783 	pattern: sshd: input_userauth_request: (?:illegal|invalid) user ($pat{user})
 7784 		format: $1
 7785 		#dest: sshd: input_userauth_request: illegal user
 7786 		# personally, I think this should be a SKIP
 7787 		dest: SKIP
 7788 
 7789 	pattern: sshd: Failed password for (?:illegal|invalid) user ($pat{user}) from ($pat{ip}) port ($pat{port}) ($pat{ssh_id})
 7790 		format: $1 from $2
 7791 		delete_if_unique
 7792 		dest: sshd: failed password for invalid user
 7793 
 7794 		format: "$2 ssh user", $1
 7795 		dest: UNIQUE scans
 7796 
 7797 	pattern: sshd: (?:Illegal|Invalid) user ($pat{user}) from ($pat{ip})
 7798 		format: $1 from $2
 7799 		delete_if_unique
 7800 		dest: sshd: invalid user
 7801 
 7802 		format: "$2 ssh user", $1
 7803 		dest: UNIQUE scans
 7804 
 7805 	pattern: sshd: Failed password for ($pat{user}) from ($pat{ip}) port ($pat{port}) ($pat{ssh_id})
 7806 		format: $1 from $2
 7807 		delete_if_unique
 7808 		dest: sshd: user failed password
 7809 
 7810 		format: "$2 ssh user", $1
 7811 		dest: UNIQUE scans
 7812 
 7813 
 7814 	pattern:	kernel: (($pat{word}): link up)\, ($pat{anything})
 7815 
 7816 		dest:	SKIP
 7817 
 7818 
 7819 	pattern:	sshd: fatal: Timeout before authentication for ($pat{ip})
 7820 
 7821 		format:	$1
 7822 		dest:	CATEGORY sshd: timeout before authentication
 7823 
 7824 
 7825 	pattern:	postfix\/trivial\-rewrite: table ($pat{anything}) has changed \-\- restarting
 7826 
 7827 		dest:	SKIP
 7828 
 7829 
 7830 	pattern:	postfix\/qmgr: 5886610385E: from\=\<($pat{mail_address})\>\, status\=expired\, returned to sender
 7831 
 7832 		format:	$1
 7833 		dest:	CATEGORY email: bounced
 7834 
 7835 
 7836 	pattern:	postfix\/scache: statistics: address lookup hits\=($pat{int}) miss\=($pat{int}) success\=($pat{int})\%
 7837 
 7838 		dest:	SKIP
 7839 
 7840 
 7841 	pattern:	postfix\/scache: statistics: max simultaneous domains\=($pat{int}) addresses\=($pat{int}) connection\=($pat{int})
 7842 
 7843 		dest:	SKIP
 7844 
 7845 
 7846 	pattern:	kernel: (($pat{word}): link down)
 7847 
 7848 		format:	$1
 7849 		dest:	CATEGORY stuff that might need fixing
 7850 
 7851 
 7852 	pattern:	postfix\/smtpd: NOQUEUE: reject: RCPT from ($pat{host})\[($pat{ip})\]: 550 \<($pat{mail_address})\>: Recipient address rejected: User unknown in local recipient table\; from\=\<($pat{mail_address})\> to\=\<($pat{mail_address})\> proto\=ESMTP helo\=\<($pat{host})\>
 7853 
 7854 		format:	$3
 7855 		dest:	CATEGORY email: unknown user
 7856 
 7857 
 7858 	pattern:	sshd: User ($pat{user}) not allowed because shell ($pat{anything}) does not exist
 7859 
 7860 		format:	$1
 7861 		dest:	CATEGORY sshd: user not allowed because shell does not exist
 7862 
 7863 
 7864 	pattern:	sshd: User ($pat{user}) not allowed because shell ($pat{file}) is not executable
 7865 
 7866 		format:	$1
 7867 		dest:	CATEGORY sshd: user not allowed because shell is not executable
 7868 
 7869 
 7870 	pattern:	postfix\/scache: statistics: domain lookup hits\=($pat{int}) miss\=($pat{int}) success\=($pat{int})\%
 7871 
 7872 		dest:	SKIP
 7873 
 7874 
 7875 	pattern:	postfix\/scache: statistics: start interval ($pat{anything})
 7876 
 7877 		dest:	SKIP
 7878 
 7879 
 7880 	pattern:	sshd: User ($pat{user}) not allowed because account is locked
 7881 
 7882 		format:	$1
 7883 		dest:	CATEGORY sshd: user not allowed because account is locked
 7884 
 7885 
 7886 	pattern:	sshd: User ($pat{user}) not allowed because not listed in AllowUsers
 7887 
 7888 		format:	$1
 7889 		dest:	CATEGORY sshd: denied access by policy
 7890 
 7891 
 7892 	pattern:	postfix\/local: ($pat{word}): to\=\<($pat{mail_address}|$pat{mail_user})\>\, (?:orig_to=<($pat{mail_address}|$pat{mail_user})?>, )?relay\=($pat{word})\, delay\=($pat{int})\, status\=sent \(($pat{anything})\)
 7893 
 7894 		format:	email: email delivered locally
 7895 		dest:	CATEGORY statistics
 7896 
 7897 
 7898 	pattern:	postfix\/local: ($pat{word}): to\=\<($pat{mail_address}|$pat{mail_user})\>\, (?:orig_to=<($pat{mail_address}|$pat{mail_user})?>, )?relay\=($pat{word})\, delay\=($pat{int})\, status\=bounced \(($pat{anything})\)
 7899 
 7900 		format:	$2
 7901 		dest:	email: email bounced
 7902 
 7903 
 7904 	pattern:	dhcpd: if ($pat{host}) ($pat{word}) ($pat{word}) rrset doesn\'t exist ($pat{word}) ($pat{host}) ($pat{int}) ($pat{word}) ($pat{word}) ($pat{ip}): timed out\.
 7905 
 7906 		format:	$1
 7907 		dest:	CATEGORY dhcpd: rrset doesn't exist
 7908 
 7909 
 7910 	pattern:	sshd: Did not receive identification string from ($pat{ip})
 7911 
 7912 		format:	$1
 7913 		dest:	CATEGORY sshd: did not receive ident string from host
 7914 
 7915 
 7916 	pattern:	named: zone ($pat{zone})\/default: refresh: retry limit for master ($pat{ip})\#($pat{port}) exceeded \(source ($pat{ip})\#($pat{port})\)
 7917 
 7918 		format:	$1 from $2
 7919 		dest:	CATEGORY named: retry limit for zone exceeded
 7920 
 7921 
 7922 	pattern:	named: transfer of \'($pat{zone})\' from ($pat{ip})\#($pat{port}): failed to connect: timed out
 7923 
 7924 		format:	$1 from $2
 7925 		dest:	CATEGORY named: transfer failed
 7926 
 7927 
 7928 	pattern:	sshd: error: Could not get shadow information for NOUSER
 7929 
 7930 		dest:	SKIP
 7931 
 7932 
 7933 	pattern:	(postfix\/postfix\-script: fatal: usage: postfix start \(or stop\, reload\, abort\, flush\, check\, set\-permissions\, upgrade\-configuration\))
 7934 
 7935 		format:	$1
 7936 		dest:	CATEGORY stuff that might need fixing
 7937 
 7938 
 7939 	pattern:	postfix\/postfix\-script: refreshing the Postfix mail system
 7940 
 7941 		dest:	SKIP
 7942 
 7943 
 7944 	pattern:	postfix\/master: reload configuration ($pat{file})
 7945 
 7946 		dest:	SKIP
 7947 
 7948 
 7949 	pattern:	postfix\/postfix\-script: starting the Postfix mail system
 7950 
 7951 		dest:	SKIP
 7952 
 7953 
 7954 	pattern:	postfix\/master: daemon started \-\- version ([\d\.]+)\, configuration ($pat{file})
 7955 
 7956 		dest:	SKIP
 7957 
 7958 
 7959 	pattern:	postfix\/postfix\-script: stopping the Postfix mail system
 7960 
 7961 		dest:	SKIP
 7962 
 7963 
 7964 	pattern:	postfix\/master: terminating on signal 15
 7965 
 7966 		dest:	SKIP
 7967 
 7968 
 7969 	pattern:	dhcpd: DHCPACK on ($pat{ip}) to ($pat{mac}) \(($pat{word})\) via ($pat{word})
 7970 
 7971 		dest:	SKIP
 7972 
 7973 
 7974 	pattern:	dhcpd: DHCPREQUEST for ($pat{ip}) from ($pat{mac}) \(($pat{word})\) via ($pat{word})
 7975 
 7976 		dest:	SKIP
 7977 
 7978 
 7979 	pattern:	(postfix)\/($pat{word}): warning: (dict_nis_init: NIS domain name not set \- NIS lookups disabled)
 7980 
 7981 		format:	$1: $3
 7982 		dest:	CATEGORY stuff that might need fixing
 7983 
 7984 
 7985 	pattern:	postfix\/($pat{word}): warning: (database ($pat{file}) is older than source file ($pat{file}))
 7986 
 7987 		format:	postfix: $2
 7988 		dest:	CATEGORY stuff that might need fixing
 7989 
 7990 
 7991 	pattern:	named: client ($pat{ip})\#($pat{port}): view ($pat{word}): query \(cache\) \'($pat{file})\' denied
 7992 
 7993 		format:	$1
 7994 		dest:	CATEGORY named: unapproved query
 7995 
 7996 
 7997 	pattern:	named: zone ($pat{zone})\/default: Transfer started\.
 7998 
 7999 		dest:	SKIP
 8000 
 8001 
 8002 	pattern:	named: transfer of \'($pat{zone})\' from ($pat{ip})\#($pat{port}): connected using ($pat{ip})\#($pat{port})
 8003 
 8004 		dest:	SKIP
 8005 
 8006 
 8007 	pattern:	named: client ($pat{ip})\#($pat{port}): view default: update \'($pat{zone})\' denied
 8008 
 8009 		format:	$1 for $3
 8010 		dest:	CATEGORY named: unapproved update from
 8011 
 8012 
 8013 	pattern:	dhcpd: Wrote ($pat{int}) leases to leases file\.
 8014 
 8015 		dest:	SKIP
 8016 
 8017 
 8018 	pattern:	dhcpd: Wrote ($pat{int}) new dynamic host decls to leases file\.
 8019 
 8020 		dest:	SKIP
 8021 
 8022 
 8023 	pattern:	dhcpd: Wrote ($pat{int}) deleted host decls to leases file\.
 8024 
 8025 		dest:	SKIP
 8026 
 8027 
 8028 	pattern:	postfix\/smtpd: ($pat{word}): client\=($pat{host})\[($pat{ip})\]
 8029 
 8030 		dest:	SKIP
 8031 
 8032 
 8033 	pattern:	postfix\/qmgr: ($pat{word}): from\=\<($pat{mail_address})?\>\, size\=($pat{int})\, nrcpt\=($pat{int}) \(([^\)]+)\)
 8034 
 8035 		dest:	SKIP
 8036 
 8037 
 8038 	pattern:	postfix\/smtp: ($pat{word}): host ($pat{host})\[($pat{ip})\] said: ($pat{anything})
 8039 
 8040 		format:	$2 said $4
 8041 		dest:	CATEGORY email: host reported problem
 8042 
 8043 
 8044 	pattern:	postfix\/smtp: ($pat{word}): to\=\<($pat{mail_address})?\>\, (?:orig_to=<($pat{mail_address}|($pat{mail_user}))?>, )?relay\=($pat{host})\[($pat{ip})\]\, delay\=($pat{int})\, status\=sent \(($pat{anything})\)
 8045 
 8046 		format:	email sent via smtp
 8047 		dest:	CATEGORY statistics
 8048 
 8049 
 8050 	pattern:	postfix\/smtp: ($pat{word}): to\=\<($pat{mail_address})?\>\, (?:orig_to=<($pat{mail_address}|($pat{mail_user}))?>, )?relay\=($pat{host})\[($pat{ip})\]\, delay\=($pat{int})\, status\=bounced \(($pat{anything})\)
 8051 
 8052 		format:	$2
 8053 		dest:	email: email bounced
 8054 
 8055 
 8056 	pattern:	postfix\/smtp: ($pat{word}): to\=\<($pat{mail_address})?\>\, (?:orig_to=<($pat{mail_address}|($pat{mail_user}))?>, )?relay\=($pat{host})\[($pat{ip})\]\, delay\=($pat{int})\, status\=deferred \(($pat{anything})\)
 8057 
 8058 		format:	email deferred via smtp
 8059 		dest:	CATEGORY statistics
 8060 
 8061 
 8062 		format:	$2
 8063 		dest:	CATEGORY email address temporarily unreachable
 8064 
 8065 
 8066 	pattern:	postfix\/smtpd: disconnect from ($pat{host})\[($pat{ip})\]
 8067 
 8068 		dest:	SKIP
 8069 
 8070 
 8071 	pattern:	named: client ($pat{ip})\#($pat{port}): view default: transfer of \'($pat{zone})\/($pat{word})\': AXFR (started|ended)
 8072 
 8073 		dest:	SKIP
 8074 
 8075 
 8076 	pattern:	postfix\/cleanup: ($pat{word}): message\-id\=\<([^\<\>]+)\>
 8077 
 8078 		dest:	SKIP
 8079 
 8080 
 8081 	pattern:	postfix\/pickup: ($pat{word}): uid\=($pat{int}) from\=\<($pat{mail_user}|$pat{mail_address})\>
 8082 
 8083 		dest:	SKIP
 8084 
 8085 
 8086 	pattern:	postfix\/qmgr: ($pat{word}): removed
 8087 
 8088 		dest:	SKIP
 8089 
 8090 
 8091 	pattern:	postfix\/anvil: statistics: max cache size ($pat{int}) at ($pat{anything})
 8092 
 8093 		dest:	SKIP
 8094 
 8095 
 8096 	pattern:	postfix\/anvil: statistics: max connection count ($pat{int}) for \(($pat{word}):($pat{ip})\) at ($pat{anything})
 8097 
 8098 		dest:	SKIP
 8099 
 8100 
 8101 	pattern:	postfix\/anvil: statistics: max connection rate ($pat{int})\/($pat{int})s for \((smtp(?:\-local)?):($pat{ip})\) at ($pat{anything})
 8102 
 8103 		dest:	SKIP
 8104 
 8105 
 8106 	# last message repeated.  This one is pretty much unique.
 8107 	pattern: last message repeated (\d+) times?
 8108 
 8109 					count:  $1
 8110 					dest:   LAST
 8111 
 8112 category: scans
 8113 	filter: >= 5
 8114 
 8115 category: wormsign
 8116 	filter: >= 10
 8117 
 8118 @@endif
 8119 
 8120 
 8121 # plain: log type for plain text files
 8122 add arr log_type_list=
 8123 plain
 8124 
 8125 add arr plain_filenames=
 8126 
 8127 set var plain_date_pattern=()
 8128 
 8129 set var plain_date_format=
 8130 
 8131 # null: special type for "processing" /dev/null or for throwing out files
 8132 
 8133 add arr log_type_list=
 8134 null
 8135 
 8136 add arr null_filenames=
 8137 null
 8138 
 8139 add arr null_pre_date_hook=
 8140 @@ifndef __USE_MINIMAL_CONFIG
 8141 	next;
 8142 @@endif
 8143 
 8144 set var null_date_pattern=
 8145 
 8146 set var null_date_format=
 8147 
 8148 @@ifndef __USE_MINIMAL_CONFIG
 8149 
 8150 logtype: null
 8151 
 8152 	pattern: .*
 8153 				dest: SKIP
 8154 
 8155 @@endif
 8156 
 8157 # done describing log types.
 8158 
 8159 @@ifndef __USE_MINIMAL_CONFIG
 8160 
 8161 # some default actions.
 8162 
 8163 action: ssh
 8164 	command: ssh %h
 8165 	window:  %h
 8166 
 8167 action: telnet
 8168 	command: telnet %h
 8169 	window:  %h
 8170 
 8171 set var window_command=xterm -n "%t" -name "%t" -title "%t" -e %C
 8172 
 8173 # some login config
 8174 set var default_login_action=ssh
 8175 
 8176 add arr login_action=
 8177 
 8178 @@endif
 8179 
 8180 # global variables
 8181 
 8182 set var other_host_message=Other hosts syslogging to us
 8183 
 8184 # pretty format for dates, in strftime(3) format
 8185 set var date_format=%Y_%m_%d
 8186 
 8187 # output message for one-day mode (default), subject to usual tags, plus %d
 8188 # stands for date
 8189 set var output_message_one_day =  Logs for %n on %d
 8190 
 8191 # same concept as above except for -a mode with no date range
 8192 set var output_message_all_days=  All logs for %n as of %d
 8193 
 8194 # same concept as the last two, except for -a mode with a date range.  %s
 8195 # for start date, %e for end date
 8196 set var output_message_all_days_in_range=  All logs for %n for %s through %e
 8197 
 8198 
 8199 # command used to send mail.  Subject to usual tag substitutions, plus
 8200 # %m stands for mail_address and %o stands for the relevant output message.
 8201 set var mail_command = Mail -s '%o' %m
 8202 
 8203 # command used to get the process' memory size
 8204 @@ifos Linux
 8205 set var memory_size_command=ps -p %p -o vsz | tail -n +2
 8206 @@endif
 8207 
 8208 @@ifos SunOS
 8209 set var memory_size_command=ps -p %p -o vsz | tail +2
 8210 @@endif
 8211 
 8212 # Set PATH environment variable.
 8213 set var PATH=/usr/local/bin:/bin:/usr/bin:/usr/ucb:/usr/X11R6/bin:/usr/openwin/bin
 8214 
 8215 # these variables are usually set by uname(2), but you can override them 
 8216 # if you really want to.
 8217 #set var nodename=ook
 8218 #set var osname=Linux
 8219 #set var osrelease=2.2
 8220 
 8221 # assorted variables that default to not being defined, but you can set
 8222 # them if you want to.  This group corresponds to command-line options.
 8223 #set var show_all=
 8224 #set var real_mode=
 8225 #set var days_ago=
 8226 #set var output_file=
 8227 #set var mail_address=
 8228 #set var process_all_nodenames=
 8229 
 8230 # patterns that can be used in other patterns.  NB: this is obsolete.
 8231 #set var file_pat=[\w\_\-\/\.]+
 8232 #set var host_pat=[\w\.\-]+
 8233 #set var ip_pat=[\d\.]+
 8234 #set var user_pat=[\w\_\-]+
 8235 #set var mail_user_pat=[\w\_\-\.\*\+\=]+
 8236 #set var word_pat=[\w\_\-]+
 8237 #set var zone_pat=[\w\.\-]+
 8238 
 8239 # The new way to specify patterns that can be used in other patterns
 8240 set arr pat=
 8241 	anything,          (?:.*)
 8242 	date_iso,          (?:\d{4}\-?\d{2}\-?\d{2})
 8243 	datetime_iso,      (?:\d{4}\-?\d{2}\-?\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?)
 8244 	file,              (?:[\w\_\-\/\.]+)
 8245 	host,              (?:[\w\.\-]+)
 8246 	int,               (?:\d+)
 8247 	interface,         (?:[\w\_\-\/\.]+)
 8248 	ip,                (?:[\d\.]+)
 8249 	user,              (?:[\w\_\-]+)
 8250 	mail_user,         (?:[\w\_\-\.\*\+\=]+)
 8251 	mail_address,      (?:[\w\_\-\.\*\+\=]+\@[\w\.\-]+)
 8252 	named_tag,         (?:(?:$pat{file})?named)
 8253 	port,              (?:\d{1,5})
 8254 	real,              (?:\-?\d+\.?\d*)
 8255 	word,              (?:[\w\_\-]+)
 8256 	zone,              (?:[\w\.\-]+(?:/IN)?)
 8257 	sendmail_tag,      (?:sendmail|sm-mta|sm-msp-queue)
 8258 	#
 8259 	# note: sendmail_queue_id includes the first continuation line
 8260 	sendmail_queue_id, (?:(?:NOQUEUE|[A-Za-z]{3}\d{5}|[0-9A-Za-z]{12}|[0-9A-Za-z]{8}\d{6})(?:\[1\])?)
 8261 	#
 8262 	# ssh_id currently is only ssh2, because I don't have anything else
 8263 	ssh_id,            (?:ssh2)
 8264 	mac,               (?:(?:[0-9a-fA-F]{1,2}:){5}[0-9a-fA-F]{1,2})
 8265 	hex,               (?:[[:xdigit:]]+)
 8266 	version,           (?:\d+(?:\.\d+)*)
 8267 	whitespace,        (?:\s+)
 8268 	non_whitespace,    (?:\S+)
 8269 	oid,               (?:\d+(?:\.\d+)*)
 8270 
 8271 logtype: syslog
 8272 
 8273 	pattern: kernel: IN=($pat{word})? OUT=($pat{word})? MAC=($pat{mac}):($pat{mac}):([[:xdigit:]]{2}:[[:xdigit:]]{2}) SRC=($pat{ip}) DST=($pat{ip}) LEN=(\d+) TOS=0x([[:xdigit:]]{2}) PREC=0x([[:xdigit:]]{2}) TTL=(\d+) ID=(\d+) (DF )?PROTO=(\w+) SPT=(\d+) DPT=(\d+).*
 8274 		use_sprintf:
 8275 		delete_if_unique
 8276 		format: "%-15s => %-15s %-5s %-5s", $6, $7, $14, $16
 8277 		dest: kernel: firewall deny
 8278 
 8279 		format: $6, $7
 8280 		dest: UNIQUE scans
 8281 
 8282 		format: "$14 $16", $6
 8283 		dest: UNIQUE wormsign
 8284 
 8285 	pattern: kernel: IN=($pat{word})? OUT=($pat{word})? MAC=($pat{mac}):($pat{mac}):([[:xdigit:]]{2}:[[:xdigit:]]{2}) SRC=($pat{ip}) DST=($pat{ip}) LEN=(\d+) TOS=0x([[:xdigit:]]{2}) PREC=0x([[:xdigit:]]{2}) TTL=(\d+) ID=(\d+) (DF )?PROTO=(\w+) TYPE=(\d+) CODE=(\d+).*
 8286 		use_sprintf:
 8287 		format: "%-15s => %-15s %-5s %-5s", $6, $7, $14, $15
 8288 		dest: kernel: firewall deny
 8289 
 8290 		format: $6, $7
 8291 		dest: UNIQUE scans
 8292 
 8293 
 8294 set arr commands_to_run=
 8295 @@ifndef __USE_MINIMAL_CONFIG
 8296 w
 8297 df -k
 8298 [ -f /etc/dumpdates ]  && cat /etc/dumpdates
 8299 @@endif
 8300 
 8301 # log files that, if not present or not openable, will cause an error
 8302 # 2000-08-27: morty: required_log_files plays havoc with some internal
 8303 # stuff.  I'm discontinuing support for it in the config file.
 8304 
 8305 # log files that we process if present.  Note that these are globbed.
 8306 add arr optional_log_files=
 8307 @@ifndef __USE_MINIMAL_CONFIG
 8308 /var/log/authlog*
 8309 /var/log/daemon*
 8310 /var/log/maillog*
 8311 /var/adm/messages*
 8312 /var/log/messages*
 8313 /var/log/secure*
 8314 /var/log/syslog*
 8315 /var/log/wtmp*
 8316 /var/adm/wtmpx*
 8317 /var/adm/sulog*
 8318 @@endif
 8319 
 8320 # rules for decompressing compressed files.
 8321 set arr decompression_rules=
 8322 gz, gzip -dc %f
 8323 bz2, bzip2 -dc %f
 8324 Z, compress -c %f
 8325 
 8326 # filename patterns to ignore when including directories
 8327 set arr filename_ignore_patterns=
 8328 .*\~
 8329 \..*\.swp
 8330 \#.*\#
 8331 .*,v
 8332 
 8333 # rules for PGPing stuff
 8334 set arr pgp_rules=
 8335 	2, pgp -afe %m 2>/dev/null
 8336 	5, pgpe -afr %m 2>&1
 8337 	g, gpg -aer %m 2>&1
 8338 
 8339 # colors used for real mode
 8340 add arr colors=
 8341 bell,		%a
 8342 #
 8343 normal,		%e[0m
 8344 bold,		%e[1m
 8345 intense,	%e[2m
 8346 italics,	%e[3m
 8347 underscore,	%e[4m
 8348 blink,		%e[5m
 8349 inverse,	%e[7m
 8350 #
 8351 black,		%e[30m
 8352 red,		%e[31m
 8353 green,		%e[32m
 8354 yellow,		%e[33m
 8355 blue,		%e[34m
 8356 magenta,	%e[35m
 8357 cyan,		%e[36m
 8358 white,		%e[37m
 8359 #
 8360 black_bg,	%e[40m
 8361 red_bg,		%e[41m
 8362 green_bg,	%e[42m
 8363 yellow_bg,	%e[43m
 8364 blue_bg,	%e[44m
 8365 magenta_bg,	%e[45m
 8366 cyan_bg,	%e[46m
 8367 white_bg,	%e[47m
 8368 #
 8369 
 8370 
 8371 # umask, the usual meaning.
 8372 set var umask=077
 8373 
 8374 # priority, ie. "niceness".  man nice for more info.
 8375 #   If you set it to 0, the program will not attempt to set priority.
 8376 #   Please don't set this to a negative number unless you *really* know 
 8377 # what you're doing.
 8378 set var priority=0
 8379 
 8380 # any categories listed in here won't appear in the output.  Defaults to none,
 8381 # but feel free to add to it in the local config file if you get too much
 8382 # stuff you're not interested in.
 8383 set arr ignore_categories=
 8384 
 8385 # the format string used for "real mode".  %n is the nodename of the message,
 8386 # %c is the category of the message, %# is the count, %d is the data, \\ is
 8387 # backslash, \n is newline, \t is tab, %R is the raw log line minus newline
 8388 set var real_mode_output_format=%c: (loghost %n, from host %h)\n%-10# %d\n\n
 8389 
 8390 # in "real mode", how many seconds should we sleep after we're done looking
 8391 # at the log files, before we look for more input
 8392 set var real_mode_sleep_interval=1
 8393 
 8394 # in "real mode", every now and then we want to check if the log files have
 8395 # rolled over or new log files have appeared.  This is how often we do that.
 8396 set var real_mode_check_interval=300
 8397 
 8398 # the default format for actions to use when piping data.  You can override
 8399 # on a per-action basis with action_format.
 8400 set var default_action_format=%c\n%#\t%d\n
 8401 
 8402 # throttles use a key to decide if they should throttle.  Make the key
 8403 # include the information that you want to suppress if it appears again.
 8404 # You can override on a per-action basis with throttle_format.
 8405 set var default_throttle_format=%c\n%d\n
 8406 
 8407 # the format to use for printing
 8408 set var print_format=%c\n%#\t%d\n
 8409 
 8410 # the format to use for saveing
 8411 set var save_format=%c\n%#\t%d\n
 8412 
 8413 # command to use for printing
 8414 set var print_command=fmt -s|lpr
 8415 
 8416 # should GUI mode print all?
 8417 set var gui_mode_print_all=0
 8418 
 8419 # should GUI mode save all?
 8420 set var gui_mode_save_all=0
 8421 
 8422 # GUI mode file for saving events
 8423 set var gui_mode_save_events_file=
 8424 
 8425 # should the GUI config be autosaved?
 8426 set var gui_mode_config_autosave=0
 8427 
 8428 # should the GUI config save everything, or only local changes
 8429 set var gui_mode_config_savelocal=0
 8430 
 8431 # should the GUI config do RCS before and after saving?
 8432 set var gui_mode_config_save_does_rcs=1
 8433 
 8434 # what is the config file for GUI mode named?  Don't bother defining here,
 8435 # because not checked until afterwards
 8436 # set var gui_mode_config_file=
 8437 
 8438 # How do we run RCS?
 8439 set var rcs_command=ci -q -l -t-%f -m'automatic check-in' %f
 8440 
 8441 # how should we sort?  You can set this to "string" for a simple string
 8442 # sort, "funky" for a sort that understands IP address and other strings
 8443 # with embedded integer values, or "numeric" for a simple numeric sort 
 8444 # (don't use unless you are really dealing with only numeric data)
 8445 set var default_sort=funky
 8446 
 8447 # categories that will appear before anything else in the output.
 8448 set arr priority_categories=
 8449 @@ifndef __USE_MINIMAL_CONFIG
 8450 major events
 8451 stuff that might need fixing
 8452 @@endif
 8453 
 8454 
 8455 # includes
 8456 
 8457 # assorted standard includes.  include_if_exists continues if the file
 8458 # doesn't exist; include dies nastily if the file doesn't exist.
 8459 # include_dir includes all the files in the directory, dying if the
 8460 # directory doesn't exist or if a file can't be opened; include_dir_if_exists
 8461 # includes all the files in the directory, not dying if the directory
 8462 # doesn't exist, but dying if a file in the directory can't be opened.
 8463 
 8464 # This clump is put here last so that local configs will override the default.
 8465 
 8466 @@ifndef __NO_STD_INCLUDES
 8467 
 8468 include_dir_if_exists @prefix@/etc/log_analysis.d
 8469 include_if_exists @prefix@/etc/log_analysis.conf
 8470 include_if_exists @prefix@/etc/log_analysis.conf-%s
 8471 include_if_exists @prefix@/etc/log_analysis.conf-%s-%r
 8472 include_if_exists @prefix@/etc/log_analysis.conf-%n
 8473 include_dir_if_exists /etc/log_analysis.d
 8474 include_if_exists /etc/log_analysis.conf
 8475 include_if_exists /etc/log_analysis.conf-%s
 8476 include_if_exists /etc/log_analysis.conf-%s-%r
 8477 include_if_exists /etc/log_analysis.conf-%n
 8478 
 8479 @@endif
 8480 
 8481 
 8482 # The internal implementation for reading the ~/.log_analysis.conf config
 8483 # is to set _CONFIG_FILE1 to the file.
 8484 # the internal implementation of the -f option is to set _CONFIG_FILE2 to
 8485 # -f's argument.
 8486 
 8487 @@ifdef _CONFIG_FILE1
 8488 include @@{_CONFIG_FILE1}
 8489 @@endif
 8490 
 8491 @@ifdef _CONFIG_FILE2
 8492 include @@{_CONFIG_FILE2}
 8493 @@endif
 8494 
 8495 
 8496 
 8497 # the @@end preprocessor directive can be used to stop processing the 
 8498 # config file.  But you don't need it.
 8499 @@end
 8500 
 8501 =head1 NAME
 8502 
 8503 log_analysis - Analyze various system logs
 8504 
 8505 =head1 SYNOPSIS
 8506 
 8507 B<log_analysis> [B<-h>] [B<-r>] [B<-g>] [B<-f> config_file] [B<-o> file] [B<-O>] [B<-n> nodename] [B<-U>] [B<-u> unknownsdir] [B<-D> var1,var2=value,...] [B<-d> days_ago] [B<-a>] [B<-F>] [B<-i>] [B<-m> mail_address] [B<-M> mail_prog] [B<-s>] [B<-S>] [B<-t> forced_type] [required_files. . .]
 8508 B<log_analysis> B<-I info_type>
 8509 
 8510 =head1 DESCRIPTION
 8511 
 8512 I<log_analysis> analyzes and summarizes system logs files.  
 8513 It also runs some other commands (ie. I<w>, I<df -k>) to show the system
 8514 state.  It's intended to be run on a daily basis out of cron.
 8515 
 8516 I<log_analysis> supports several major modes.  The default mode is 
 8517 report mode, which scans through your logs, produces a text report, 
 8518 and exits.  There is also real mode, which lets you monitor your logs
 8519 continuously; gui mode, which is a gui sitting on top of real mode; and
 8520 daemon mode, which is a daemonized variant of real mode.
 8521 
 8522 =head1 OPTIONS
 8523 
 8524 =over 4
 8525 
 8526 =item B<-a> all
 8527 
 8528 Show all logs, not just the ones from yesterday.
 8529 
 8530 =item B<-A> daemon mode
 8531 
 8532 Start in daemon mode.  Daemon mode is like real mode, except that
 8533 the process daemonizes, and
 8534 there is no regular output, just actions.  daemon mode is 
 8535 useful if you want to start I<log_analysis> at system boot time to 
 8536 run actions.  It's also useful if you have actions configured, and you 
 8537 have multiple copies of log_analysis running in real/gui mode, and you 
 8538 only want the actions to happen once.
 8539 
 8540 See I<-r> for more info on real mode.  In general, anything that applies 
 8541 to real mode applies to daemon mode unless it explicitly says otherwise.
 8542 
 8543 The variables specific to daemon mode are
 8544 I<daemon_mode> and I<daemon_mode_pid_file>.  One variable that is not
 8545 specific to daemon mode but is really useful with daemon mode is
 8546 I<real_mode_no_actions_unless_is_daemon>.
 8547 
 8548 =item B<-b> real mode backlogs
 8549 
 8550 By default, real mode and gui mode ignore all existing log messages
 8551 and only show new logs.  With this option, real mode shows logs as
 8552 indicated by I<days_ago>.  See I<-r> for more info.
 8553 
 8554 =item B<-d days_ago>
 8555 
 8556 Show logs from I<days_ago> days ago.  Defaults to 1 (ie. show
 8557 yesterday's logs.)  In I<-a> mode, this option only affects the
 8558 heading, and it defaults to 0.
 8559 
 8560 You can also provide an absolute date in the form YYYY_MM_DD,
 8561 ie. 2001_03_02.  And you can provide the symbolic names I<today>
 8562 (equivalent to 0) and I<yesterday> (equivalent to 1).
 8563 
 8564 And you can even provide a date range in the form YYYY_MM_DD-YYYY_MM_DD
 8565 or ago1-ago2
 8566 to get output for a range of days.  Each day is output individually, so
 8567 if you use the I<-o> option, you get a separate file for each day, and 
 8568 if you use the I<-m> option, you get a separate mail for each day.
 8569 
 8570 You can also set this in the config with the I<days_ago> variable.
 8571 
 8572 See I<-r> for how days_ago is handled under real mode and gui mode.
 8573 
 8574 =item B<-D var1,var2=value,var3,...>
 8575 
 8576 This option lets you define preprocessor constants.  Its argument is a 
 8577 comma-separated list of constants to define.  To set a constant to a 
 8578 particular value, say "constant=value".
 8579 
 8580 =item B<-f config_file>
 8581 
 8582 Read I<config_file> in addition to the internal config and the internal
 8583 config files.  See L<"CONFIG FILE"> for details.
 8584 
 8585 =item B<-F>
 8586 
 8587 Instead of loading the whole internal config, just use a minimal subset.
 8588 
 8589 =item B<-g>
 8590 
 8591 "gui mode", ie. monitor log files continuously.  Currently conflicts with
 8592 many other modes and options.  Yes, has built-in support for log file
 8593 rollover.  This is basically real mode (see I<-r>) with a GUI; variables
 8594 that apply to real mode also apply to gui mode, but not vice versa.
 8595 
 8596 See variables 
 8597 I<gui_mode>, I<gui_mode_modifier>, and I<window_command>
 8598 for gui mode specifics.  See B<-r> for many things that also apply to
 8599 gui mode.
 8600 
 8601 =item B<-h> help
 8602 
 8603 Show command summary and exit.
 8604 
 8605 =item B<-i> includes suppress
 8606 
 8607 Don't include the standard include files, ie. /etc/log_analysis.conf,
 8608 @prefix@/etc/log_analysis.conf, and the others listed in L<"FILES">.  
 8609 Note that this option does not stop the inclusion of
 8610 $HOME/.log_analysis.conf in gui mode.
 8611 
 8612 =item B<-I info>
 8613 
 8614 This option is used for obtaining internal information about I<log_analysis>.
 8615 I<log_analysis> exits immediately after outputting the information.
 8616 
 8617 If I<info> is I<help>, I<log_analysis> outputs the list of things you can use
 8618 for I<info>.
 8619 
 8620 If I<info> is I<categories>, all categories (those mentioned in the various
 8621 configs and implicit categories) will be listed.
 8622 
 8623 If I<info> is I<colors>, all colors that work for real_mode and
 8624 gui_mode will be listed.
 8625 
 8626 If I<info> is I<config_versions>, all config files will be listed with their
 8627 config_version and file_version (if defined).
 8628 
 8629 If I<info> is I<evals>, the evals built from the config (internal and local)
 8630 are output.
 8631 
 8632 If I<info> is I<internal_config>, the internal config is output.
 8633 
 8634 If I<info> is I<log_files>, the log files that would have been read are 
 8635 output.
 8636 
 8637 If I<info> is I<log_types>, the known log types are output.
 8638 
 8639 If I<info> is I<nothing>, I<log_analysis> just exits.  Useful for testing
 8640 configs.
 8641 
 8642 If I<info> is I<pats>, the known subpatterns will be listed.
 8643 
 8644 If I<info> is I<patterns>, the various patterns defined for the log types
 8645 are output.
 8646 
 8647 =item B<-m mail_address>
 8648 
 8649 Mail output to I<mail_address>.  This can also be specified in the config;
 8650 see B<mail_address> in L<"VARIABLES">.
 8651 
 8652 =item B<-M mail_command>
 8653 
 8654 Use I<mail_command> to send the mail.  This
 8655 can also be specified in the config; see B<mail_command> in L<"VARIABLES">
 8656 for more info, including the default.
 8657 
 8658 =item B<-n nodename>
 8659 
 8660 Use I<nodename> as the nodename (AKA hostname) instead of the default.
 8661 This is more than just cosmetic: entries in syslogged files
 8662 will be processed differently if they didn't come from this nodename.  This
 8663 can also be specified in the config file; see B<nodename> in L<"VARIABLES">.
 8664 
 8665 =item B<-N> process all nodenames
 8666 
 8667 If the logs contain entries for nodes other than I<nodename>, (ie. if the
 8668 host is a syslog server), analyze them anyway.
 8669 
 8670 =item B<-o file>
 8671 
 8672 Output to I<file> instead of to standard output.  Works with I<-m>, 
 8673 so you can save to a file and send mail with one command.
 8674 
 8675 =item B<-O>
 8676 
 8677 With I<-o file>, causes the output to go both to the file and to standard 
 8678 output.  NB: this does not currently work with I<-m>, so you can't output
 8679 to a file, standard output, and to email.
 8680 
 8681 =item B<-p pgp_type>
 8682 
 8683 Encrypts the mail output.  Uses pgp_type to determine the encryption command.
 8684 For use with B<-m> or B<mail_address>.  See B<pgp_type> in the list of global
 8685 variables for info on encryption types.
 8686 
 8687 =item B<-r>
 8688 
 8689 "Real mode", ie. monitor log files continuously.  Currently conflicts with
 8690 many other modes and options.  Yes, has built-in support for log file
 8691 rollover.  See I<-g> for a GUI that can sit on top of this mode, and I<-A>
 8692 to run real mode as a daemon.
 8693 
 8694 See variables 
 8695 I<real_mode>, I<real_mode_output_format>,
 8696 I<real_mode_sleep_interval>, 
 8697 I<real_mode_check_interval>,
 8698 I<real_mode_backlogs> (or the I<-b> option), 
 8699 and
 8700 I<keep_all_raw_logs>
 8701 in the list of global variables for more 
 8702 configurables.
 8703 
 8704 I<WARNING:> in real mode and gui mode, only the most recent file per glob 
 8705 in I<optional_log_files> is monitored.  This means that you should set it 
 8706 to something like I</var/log/messages*> and I</var/log/syslog*> rather 
 8707 than I</var/log/*>.
 8708 
 8709 I<WARNING:> in real mode and in gui mode, I<log_analysis> treats
 8710 I<days_ago> differently; if it's a simple number, it is treated as the
 8711 number of days ago to start looking at logs.  So, if days_ago is 7,
 8712 I<log_analysis> looks through the past 7 days' worth of logs.
 8713 HOWEVER, even if B<-d> is set, I<log_analysis> doesn't actually show
 8714 these logs unless I<-b> is specified or the corresponding variable
 8715 real_mode_backlogs is set.
 8716 
 8717 I<NOTE:> The primary feature of I<log_analysis> is its reporting
 8718 capability.  Using it for continuous monitoring makes sense if you
 8719 want a single config for reporting and for continuous monitoring.  If
 8720 you just want continuous monitoring then you may be better off with
 8721 some of the other software out there, such as L<swatch(1)>.
 8722 
 8723 =item B<-s> suppress other commands
 8724 
 8725 Usually, I<log_analysis> runs assorted commands that show system state
 8726 (ie. I<w>, I<df -k>).  This option doesn't run those commands.
 8727 See B<commands_to_run> in L<"VARIABLES"> for the list of extra commands.
 8728 The B<suppress_commands> variable does the same thing as this option.
 8729 
 8730 =item B<-S> suppress output footer
 8731 
 8732 Usually, I<log_analysis> will include its version number, the time it
 8733 spent running, and its arguments at the end of the output.  This option
 8734 suppresses that output.  The B<suppress_footer> variable does the same
 8735 thing as this option.
 8736 
 8737 =item B<-t forced_type>
 8738 
 8739 I<log_analysis> usually determines the type of logfiles by looking at the
 8740 per-type I<log_filenames> extension.  This option and the I<type_force>
 8741 variable let you bypass that check.
 8742 
 8743 =item B<-U> unknowns-only
 8744 
 8745 Output logfile unknowns to stdout and exit.  If I<unknownsdir> exists,
 8746 also wipe I<unknownsdir> if it exists and then write out raw unknown 
 8747 lines to files in I<unknownsdir>.  This exists to make writing 
 8748 custom rules easier.
 8749 
 8750 =item B<-u unknownsdir>
 8751 
 8752 Use I<unknownsdir> as the unknownsdir.  If I<unknownsdir> already exists,
 8753 and contains files, its files will be used as the input for I<log_analysis> 
 8754 regardless of any other command line options.  If I<-U> is also specified,
 8755 after all processing I<unknownsdir> will be wiped out and its files
 8756 rewritten with the current unknowns.  This is useful for writing your own
 8757 configs.
 8758 
 8759 =item B<-v> version
 8760 
 8761 Output version and exit.
 8762 
 8763 =item B<required-files>
 8764 
 8765 If files are specified on the command line, log_analysis ignores its
 8766 built-in list of optional and required log files, and process the 
 8767 files on the command line.  If one of the files doesn't exist, it's
 8768 a fatal error.
 8769 
 8770 =back
 8771 
 8772 =head1 CONFIG FILE
 8773 
 8774 The script has an embedded config file.  It will also read various
 8775 external config files if they exist; see L<"FILES"> for a list.  Later
 8776 directives (from later in the file or from a file read later) override
 8777 earlier directives.
 8778 
 8779 You can make comments with '#' at the beginning of a line.  If you want
 8780 a '#' or '=' at the beginning of a line, you usually need to quote it with
 8781 backslash.
 8782 
 8783 Some directives take a "block" as argument.  A block is a collection of lines
 8784 that ends with a line that is empty or only contains whitespace.  '#' at the
 8785 beginning of a line still comments out the line.  Leading whitespace on a 
 8786 line is ignored.
 8787 
 8788 Before the config is parsed, it is passed through a preprocessor inspired
 8789 by the L<aide(1)> preprocessor.
 8790 
 8791 =over 4
 8792 
 8793 =head2 Pattern directives
 8794 
 8795 These directives describe your logs, and are the main point of this
 8796 program.  The basic idea here is that you first declare what logtype
 8797 you are working with, and then you specify a bunch of perl patterns
 8798 that describe different kinds of log messages, and that save parts of
 8799 the message.  For each perl pattern, you specify one or more
 8800 destinations that describe what you want done with it.
 8801 
 8802 =item B<logtype:> I<type>
 8803 
 8804 Future patterns should be applied to this logtype (ie. sulog, syslog,
 8805 wtmp.)  Example: 
 8806 
 8807 I<logtype: syslog>
 8808 
 8809 =item B<pattern:> I<pattern>
 8810 
 8811 I<pattern> is a perl regex (see L<perlre(1)>) that implictly starts
 8812 with ^ (beginning of the line) and implicitly ends with \s*$ (optional
 8813 whitespace and the end of the line.)  This should only be issued after
 8814 a B<logtype:> has been issued in the same config file.  Wildcard parts
 8815 of the pattern should be surrounded with parentheses, to save these
 8816 parts for later use in the B<format:>.  Note that there are some tokens 
 8817 with special meanings that can be used here in the format $pat{something}, 
 8818 ie.  $pat{ip}, $pat{file}, etc. (see L<"pat"> for details, and 
 8819 run I<log_analysis -I pats> for the current list).  Examples:
 8820 
 8821 I<pattern: popper: Stats: ($pat{mail_user}) (\d+) (\d+) (\d+) (\d+)>
 8822 
 8823 I<pattern: login: LOGIN ON ($pat{file}) BY ($pat{user})>
 8824 
 8825 The order of precedence for patterns is undefined, except that user-defined 
 8826 patterns always have precedence over the patterns of the internal config.  
 8827 
 8828 =item B<format:> I<format>
 8829 
 8830 I<format> is treated as a string that contains the useful information
 8831 from a pattern.  Note that it should not actually be quoted.  A format
 8832 is mandatory for category destinations, but should not be used with
 8833 SKIP or LAST destinations.
 8834 
 8835 For example, if we had a pattern that was I<login: LOGIN ON
 8836 ($pat{file}) BY ($pat{user})>, we would probably just want $2, so we
 8837 might say:
 8838 
 8839 I<format: $2>
 8840 
 8841 Similarly, if we had a patterns that was I<kernel: deny (\d+) packets 
 8842 from ($pat{ip}) to ($pat{ip})>, we might want to say:
 8843 
 8844 I<format: $2 =E<gt> $3>
 8845 
 8846 =item B<use_sprintf>
 8847 
 8848 I<use_sprintf> is optional.  If this directive is present for a given
 8849 format, than instead of the format being treated as a string, it is
 8850 treated as the arguments for L<sprintf(3)>.  For example, if you have
 8851 a source IP address in $2 and a destination IP address in $3, you
 8852 could just have dest as I<$2 =E<gt> $3>, but you would have things
 8853 lining up better if you did this:
 8854 
 8855 I<format: "%-15s =E<gt> $3", $2>
 8856 
 8857 I<use_sprintf>
 8858 
 8859 =item B<delete_if_unique>
 8860 
 8861 I<delete_if_unique> is optional.
 8862 This feature can be used when you have multiple I<dest>s for one 
 8863 pattern, one of which is a regular category and one of which is a 
 8864 I<UNIQUE> with a filter.  You want the one that is a regular
 8865 category to be deleted if the I<UNIQUE> category meets its filter, ie.
 8866 because it's a scan.  See L<"UNIQUE DESTINATION"> for more info.
 8867 
 8868 =item B<count:> I<count>
 8869 
 8870 I<count> is optional.  The default is that a log line that matches a
 8871 pattern causes the category to increment by 1.  But sometimes, a
 8872 single log line corresponds to multiple events, ie. if you have a log
 8873 message of the form "5 packets denied by firewall" or "last message
 8874 repeated 3 times", you can extract the event count to I<count>.  For
 8875 example, if you're using the pattern I<kernel: deny (\d+) packets from
 8876 ($pat{ip}) to ($pat{ip})>, you might say:
 8877 
 8878 I<count: $1>
 8879 
 8880 =item B<color:> I<colors>
 8881 
 8882 space-separated list of colors to display this message in when in
 8883 real-mode or gui-mode.  For a list of colors that will work in both
 8884 modes, run I<log_analysis -I colors>.  Note that "bell" is among the
 8885 available colors, because it didn't fit anywhere else.  See the
 8886 I<colors> entry for more info.
 8887 
 8888 NOTE: if multiple dest configs with conflicting color settings result 
 8889 in delivery to the same line in gui mode, the result is currently 
 8890 undefined.  There is only one line to be displayed, after all.
 8891 
 8892 =item B<description:> I<description_text>
 8893 
 8894 This is a simple text description of the event, to explain the problem
 8895 to your operators.  It can be accessed via gui mode.  The note above by
 8896 color applies.
 8897 
 8898 =item B<do_action:> I<action>
 8899 
 8900 Run "action" (described elsewhere in the config with the "action:" 
 8901 keyword) if this event is seen in real mode or gui mode.
 8902 
 8903 =item B<priority:> I<priority>
 8904 
 8905 Assign priority I<priority> to action.  Currently, the only priority that 
 8906 does anything is "IGNORE".  It can be used to ignore events.
 8907 
 8908 =item B<dest:> I<dest>
 8909 
 8910 This describes what you want done with the data in a pattern.  If
 8911 I<dest> is the special token I<SKIP> the data is discarded.  If
 8912 I<dest> is the special token I<LAST>, the data is assumed to be of the
 8913 form "last message repeated N times", and we pretend as though the
 8914 last message we saw occurred, using I<count> as a multiplier.  If I<dest>
 8915 starts with the special token I<UNIQUE>, we do special "unique" handling, 
 8916 which is covered in L<"UNIQUE DESTINATION">.  If I<dest> starts with the 
 8917 special token I<CATEGORY> or is any other string, it is treated as a 
 8918 category that the
 8919 pattern data should be saved to.  Ie. if I<pattern> was I<login: LOGIN
 8920 ON ($pat{file}) BY ($pat{user})>, and I<format> was I<$2>, then one
 8921 might set I<dest> to I<login: successful local login>.  You must have
 8922 a format defined before the I<dest>.
 8923 
 8924 You can have multiple I<dest> directives for a single I<pattern>, if
 8925 all of the I<dest>s are category destinations.  Each one needs its own
 8926 I<format>.  Similarly, if you set I<count> or I<use_sprintf>, they are
 8927 tied to the particular I<dest> you set them with.
 8928 
 8929 Note that I<dest> "closes" the description of a destination, so you
 8930 need to have any other related directives (ie. I<format>, I<count>,
 8931 I<use_sprintf>, I<delete_if_unique>) 
 8932 before the I<dest> directive.  This ordering is
 8933 necessary to avoid ambiguity in the multiple-destination case.
 8934 
 8935 =back
 8936 
 8937 =head2 Event directives
 8938 
 8939 You can configure what happens for incoming events based on certain
 8940 criteria.  Currently, those criteria are a simple string match of one
 8941 or more of the category, data, or hostname.  So, for example, you can
 8942 ignore all messages from "roguehost", or color "user logged in" 
 8943 messages for a certain user in bright red.  Here are the useful 
 8944 directives:
 8945 
 8946 =over 4
 8947 
 8948 =item B<event:>
 8949 
 8950 Starts a new event config.
 8951 
 8952 =item B<match> I<category:> I<value>
 8953 
 8954 =item B<match> I<data:>     I<value>
 8955 
 8956 =item B<match> I<hostname:> I<value>
 8957 
 8958 This event config applies when the "category" is "value", or the
 8959 "data" is value, or the "hostname" is "value".  If multiple match lines
 8960 are supplied, they are ANDed together.
 8961 
 8962 =item B<color:> I<color>
 8963 
 8964 =item B<description:> I<description_text>
 8965 
 8966 =item B<do_action:> I<action>
 8967 
 8968 =item B<priority:> I<priority>
 8969 
 8970 color, description, do_action, and priority work the same way as they do in
 8971 a "dest" config or in an "event" config.  
 8972 
 8973 If "event", "dest", and "category" configs all apply to a given event than 
 8974 "event" has highest precedence, followed by "dest", followed by "category".
 8975 
 8976 =back
 8977 
 8978 =head2 Category directives
 8979 
 8980 Several patterns can lead to the same category, so category-specific
 8981 directives are associated with the category, not with a pattern.  Here
 8982 are the category directives:
 8983 
 8984 =over 4
 8985 
 8986 =item B<category:> I<category>
 8987 
 8988 Specifies which category subsequent directives will define.
 8989 
 8990 =item B<filter:> I<filter commands>
 8991 
 8992 By default, I<log_analysis> will output all the data it finds in a category.
 8993 Filters let you specify, say, that only the top 10 items should be output,
 8994 or that only the items that occurred fewer than 5 times should be output.
 8995 If a category has data, but none of the data meet the filter rules, then 
 8996 the category will be completely skipped.  See L<"FILTERS"> for more info.
 8997 
 8998 =item B<sort:> I<sorting keywords>
 8999 
 9000 Specifies how this category should be sorted in the output.  Examples are
 9001 "funky", "string", "value", "reverse value", etc.  The default is "funky".
 9002 See L<"SORTING"> for more info.
 9003 
 9004 =item B<derive:> I<derive commands>
 9005 
 9006 The usual way to populate categories is via the pattern config.  But 
 9007 sometimes, you want to combine two or more elemental categories to make
 9008 a new category.  Any categories derived in this manner may not be a
 9009 destination for simple patterns.
 9010 
 9011 There are currently three subcommands for this (the quotes are literal):
 9012 
 9013 =over 8
 9014 
 9015 =item I<"category1"> B<add> I<"category2">
 9016 
 9017 =item I<"category1"> B<subtract> I<"category2">
 9018 
 9019 These do what you expect: take the values for the items in category2 and
 9020 add or subtract them from the values for the items in category1.  Any
 9021 item defined in either category will be in the new category.  Subtract
 9022 can cause the values in the new category to be negative or 0.
 9023 
 9024 =item I<"category1"> B<remove> I<"category2">
 9025 
 9026 The new category will contain items in category1 that are not in category2.
 9027 This is very different from subtract.
 9028 
 9029 Example: if category1 contains A with a value of 2 and B with a value of 2,
 9030 while category2 contains A with a value of 1 and C with a value of 1,
 9031 '"category1" subtract "category2"' will contain A with a value of 1, B with
 9032 a value of 2, and C with a value of -1, while '"category1" remove "category2"'
 9033 will only contain B with a value of 2.
 9034 
 9035 =back
 9036 
 9037 =item B<color:> I<color>
 9038 
 9039 =item B<description:> I<description_text>
 9040 
 9041 =item B<do_action:> I<action>
 9042 
 9043 =item B<priority:> I<priority>
 9044 
 9045 color, description, do_action, and priority work the same way as they do in
 9046 a "dest" config or in an "event" config.  
 9047 
 9048 If "event", "dest", and "category" configs all apply to a given event than 
 9049 "event" has highest precedence, followed by "dest", followed by "category".
 9050 
 9051 =back
 9052 
 9053 =head2 Action directives
 9054 
 9055 In real mode and in gui mode, sometimes you want an "action" (like
 9056 paging someone) to automatically happen when a particular message is
 9057 seen.  And in gui mode, you might want to run a command on a message
 9058 interactively (ie. to telnet or ssh into the host it came from.)  The
 9059 directives to do that (inspired by L<swatch(1)>) are:
 9060 
 9061 =over 4
 9062 
 9063 =item B<action:> I<action_name>
 9064 
 9065 Starts defining a new action named action_name.
 9066 
 9067 =item B<command:> I<command>
 9068 
 9069 The command to run for the current action.  I<command> uses the same
 9070 tags as I<real_mode_output_format>.
 9071 
 9072 WARNING: you can potentially shoot yourself in the foot by passing
 9073 data that has not been sanitized to a command on your system.  Be
 9074 careful!
 9075 
 9076 =item B<window:> I<title>
 9077 
 9078 Performing the action will require creating a window using I<title> as
 9079 the title.  The title will be passed to I<window_command> as the "%t"
 9080 tag.  I<title> itself uses the same tags as
 9081 I<real_mode_output_format>.  This only makes sense for gui mode.
 9082 
 9083 WARNING: you can potentially shoot yourself in the foot by passing
 9084 data that has not been sanitized to a command on your system.  Be
 9085 careful!
 9086 
 9087 =item B<use_pipe:>
 9088 
 9089 The data in the event will be sent to the command via standard input.
 9090 The format used will be that specified by the I<default_action_format>
 9091 variable, unless overridden locally by the I<action_format:> directive.
 9092 These formats allow the same tags as I<real_mode_output_format>.
 9093 
 9094 =item B<action_format:> I<format>
 9095 
 9096 See I<use_pipe> above.
 9097 
 9098 =item B<throttle:> I<throttle_time>
 9099 
 9100 Automatically-triggered actions can potentially result in a slew of 
 9101 events.  The "throttle" option lets you specify a minimum amount of
 9102 time before the action should recur with this event.  The time can 
 9103 be specified as seconds, as minutes:seconds, or as 
 9104 hours:minutes:seconds.
 9105 
 9106 Throttles do not apply to actions and logins that are 
 9107 explicitly invoked via the GUI.
 9108 
 9109 By default, the throttle is triggered on unique category and data.  That
 9110 is, if the event was category "user logged in" and the data was "morty", 
 9111 then the throttle will keep "user logged in", "morty" events from causing
 9112 the action to run again, but won't stop "user logged in", "esther" or
 9113 "no such user", "morty" events from triggering the action.  This default 
 9114 is set with the I<default_throttle_format> variable, which defaults to
 9115 "%c\n%d".  It can be overriden on a per-action basis with the 
 9116 I<throttle_format:> directive, which takes the same tags as 
 9117 I<real_mode_output_format>.  If you want the throttle to be global to the
 9118 action (say, a pager action), set throttle_format to a simple scalar value
 9119 (like 1).
 9120 
 9121 =item B<throttle_format:> I<format>
 9122 
 9123 See I<throttle:> above.
 9124 
 9125 =back
 9126 
 9127 =head2 Other directives
 9128 
 9129 =over 4
 9130 
 9131 =item B<config_version> I<version-number>
 9132 
 9133 Declare that the config is compatible with version I<version-number>.
 9134 This is for version-control purposes.  Every config file should have one
 9135 of these.  You can scan your config files' config versions with 
 9136 I<-I config_versions>.
 9137 
 9138 =item B<file_version> I<revision-information>
 9139 
 9140 Your own version control information.  I<revision-information> can be 
 9141 arbitrary text.  You can scan your config files' config versions with 
 9142 I<-I config_versions>.
 9143 
 9144 =item B<include> I<file>
 9145 
 9146 Read in configuration from I<file>.  Dies if I<file> doesn't exist.  
 9147 I<file> is subject to usual tag substitutions; see L<"TAG SUBSTITUTION">.
 9148 
 9149 =item B<include_if_exists> I<file>
 9150 
 9151 Just like B<include>, but doesn't die if the file doesn't exist.
 9152 
 9153 =item B<include_dir> I<dir>
 9154 
 9155 Read in all files in I<dir>, and include them.  Die if the directory
 9156 doesn't exist, or if a file in the directory isn't readable.  I<dir>
 9157 is subject to the usual tag substitutions; see L<"TAG SUBSTITUTION">.
 9158 Any filenames that match a pattern in I<filename_ignore_patterns> will
 9159 be skipped.
 9160 
 9161 =item B<include_dir_if_exists> I<dir>
 9162 
 9163 Just like B<include_dir>, but doesn't die if the directory doesn't exist.
 9164 I<Does> still die if any of the files in I<dir> isn't readable.
 9165 
 9166 =item B<block_comment>
 9167 
 9168 Throws out the block immediately after it.
 9169 
 9170 =item B<set var> I<varname> =I<value>
 9171 
 9172 Set scalar variable I<varname> to value I<value>.  
 9173 If the variable already exists, this will overwrite it.
 9174 
 9175 See L<"VARIABLES"> for the list of variables you can play with.
 9176 
 9177 =item B<add var> I<varname> =I<value>
 9178 
 9179 If scalar variable B<varname> already exists, append I<value> to the end
 9180 of its current value.  If it doesn't yet exist, create it and set it to
 9181 I<value>.
 9182 
 9183 See L<"VARIABLES"> for the list of variables you can play with.
 9184 
 9185 =item B<prepend var> I<varname> =I<value>
 9186 
 9187 If scalar variable B<varname> already exists, prepend I<value> to the 
 9188 current value.  If it doesn't yet exist, create it and set it to
 9189 I<value>.
 9190 
 9191 See L<"VARIABLES"> for the list of variables you can play with.
 9192 
 9193 =item B<set arr> I<arrname> =
 9194 
 9195 Read in the block that follows this declaration, make the lines into an
 9196 array, and set the array variable I<arrname> to that array.
 9197 
 9198 See L<"VARIABLES"> for the list of variables you can play with.
 9199 
 9200 =item B<add arr> I<arrname> =
 9201 
 9202 Read in the block that follows this declaration, make the lines into an
 9203 array, and append that array to the array named I<arrname>.
 9204 
 9205 See L<"VARIABLES"> for the list of variables you can play with.
 9206 
 9207 =item B<prepend arr> I<arrname> =
 9208 
 9209 Read in the block that follows this declaration, make the lines into an
 9210 array, and prepend that array to the array named I<arrname>.
 9211 
 9212 See L<"VARIABLES"> for the list of variables you can play with.
 9213 
 9214 =item B<remove arr> I<arrname> =
 9215 
 9216 Read in the block that follows this declaration, and for each line, look for
 9217 and delete that line from array I<arrname>.  If one of these lines cannot
 9218 be found, the result is a warning, not death.
 9219 
 9220 See L<"VARIABLES"> for the list of variables you can play with.
 9221 
 9222 =item B<local> OTHER DIRECTIVE
 9223 
 9224 Putting "local" in front of another directive means that this directive 
 9225 should be saved when gui_mode_config_savelocal is in effect.
 9226 
 9227 =item B<nowarn> OTHER DIRECTIVE
 9228 
 9229 Putting "nowarn" in front of another directive means that this directive 
 9230 should not generate a config warning, i.e. for redefining a category 
 9231 filter.
 9232 
 9233 =back
 9234 
 9235 =head1 VARIABLES
 9236 
 9237 Some variables are scalar, which means they are strings or numbers.  Some 
 9238 variables are arrays, which are lists of scalars.
 9239 
 9240 Some variables are mandatory, which means they must be defined somewhere in
 9241 one of the config files, while some variables are optional.
 9242 
 9243 Some variables are global, while some are per-log-type extensions.  Some
 9244 example of per-log-type extensions are date_pattern and filenames.  
 9245 Extensions should actually appear in the format "TYPE_EXTENSION",
 9246 ie. date_pattern would actually appear as I<syslog_date_pattern> for the
 9247 syslog log-type and I<sulog_date_pattern> for sulog.
 9248 
 9249 To see examples of many of the possibilities, as well as the default values,
 9250 run I<log_analysis -I internal_config>.
 9251 
 9252 =head2 PER-LOG-TYPE VARIABLE EXTENSIONS
 9253 
 9254 =over 4
 9255 
 9256 =item B<filenames>
 9257 
 9258 This mandatory extension is an array of file basenames that apply to the log
 9259 type.  For example, if you wanted I</var/adm/messages.1> to be processed by
 9260 the syslog rules, you might add I<messages> to I<syslog_filenames>.
 9261 
 9262 =item B<open_command>
 9263 
 9264 Some log files (ie. wtmp log types) are in a binary format that needs to be 
 9265 interpreted by external commands.  This optional scalar extension specifies a
 9266 command to be run to interpret the file.  The command is subject to the usual
 9267 tag substitutions (see L<"TAG SUBSTITUTIONS">), plus the %f tag maps to 
 9268 the file.  For example, the wtmp log type defines I<wtmp_open_command>
 9269 as "I<last -f %f>".  If both I<decompression_rules> and I<open_command> apply
 9270 to a given file, the intermediate data will be stored in a temp file unless
 9271 I<pipe_decompress_to_open> is used.  See L<"pipe_decompress_to_open"> for more
 9272 info.
 9273 
 9274 =item B<pipe_decompress_to_open>
 9275 
 9276 If both I<decompression_rules> and I<open_command> apply to a given file,
 9277 the intermediate data will be stored in a temporary file by default to
 9278 avoid problems with some commands that can't handle input from a pipe.
 9279 If this optional scalar extension is set to I<1> (or any "true") value, then
 9280 instead, the output of the decompression rule will be piped to the 
 9281 open command, and the open command's %f tag will be mapped to "-".
 9282 
 9283 =item B<open_command_is_continuous>
 9284 
 9285 If an I<open_command> has been specified and the command is the sort that
 9286 never exits (ie. tcpdump or the like) you should set this to let log_analysis
 9287 know what to expext.  Such commands should only ever be used in real mode
 9288 or gui mode.
 9289 
 9290 =item B<pre_date_hook>
 9291 
 9292 This optional extension is an array of arbitrary perl commands that are run
 9293 for each log line, before the date processing (or any other processing) is 
 9294 done.
 9295 
 9296 =item B<date_pattern>
 9297 
 9298 This mandatory extension is a scalar that contains a pattern with at least one
 9299 parenthesized subpattern.  Before any rules are 
 9300 applied to a log line, the engine strips off the date pattern.  If the engine is
 9301 only looking at one day (ie. the default), it takes the part of the string 
 9302 that matched the parenthesized subpattern, and if it isn't equal to the right
 9303 date, it skips the line.  The B<date_format> extension (next) describes what
 9304 the date should look like.
 9305 
 9306 =item B<date_format>
 9307 
 9308 This mandatory extension is a scalar that describes the date using the
 9309 same format as B<strftime(3)>.  For example, syslog_date_format is "%b %e".
 9310 
 9311 =item B<nodename_pattern>
 9312 
 9313 This optional extension is a pattern with at least one parenthesized
 9314 subpattern.  If it exists, then after the I<date_pattern> is stripped
 9315 from the line, this pattern is stripped, and the part that matched the
 9316 subpattern is compared to the nodename.  If they're not equal, then
 9317 the relevant counter for the category named by the
 9318 I<other_host_message> variable is incremented.  Note that all nodenames
 9319 are subject to having the local domain stripped from them; see I<domain>
 9320 and I<leave_FQDNs_alone> for details.
 9321 
 9322 =item B<pre_skip_list_hook>
 9323 
 9324 This optional extension is an array of perl commands to be run after the
 9325 nodename check, just before the skip_list check.
 9326 
 9327 =item B<skip_list>
 9328 
 9329 This optional extension is obsolete and deprecated, but still works for 
 9330 backwards compatibility.
 9331 
 9332 =item B<raw_rules>
 9333 
 9334 This optional extension is obsolete and deprecated, but still works for 
 9335 backwards compatibility.
 9336 
 9337 =back
 9338 
 9339 =head2 GLOBAL VARIABLES
 9340 
 9341 These variables are all globals.
 9342 
 9343 =over 4
 9344 
 9345 =item B<log_type_list>
 9346 
 9347 This variable is a mandatory global array that contains the list of all
 9348 known log-types, ie. I<syslog>, I<sulog>, I<wtmpx>, etc.
 9349 
 9350 =item B<pat>
 9351 
 9352 This variable is a madatory global array that contains a list of subpattern
 9353 names followed by a comma, optional whitespace, and a perl regex that 
 9354 represents that subpattern.  Some of the predefined patterns include "ip", 
 9355 "zone", "user", "mail_user", etc.  Run I<log_analysis -I pats> for a list.
 9356 
 9357 =item B<host_pat>
 9358 
 9359 =item B<file_pat>
 9360 
 9361 =item B<ip_pat>
 9362 
 9363 =item B<mail_user_pat>
 9364 
 9365 =item B<user_pat>
 9366 
 9367 =item B<word_pat>
 9368 
 9369 =item B<zone_pat>
 9370 
 9371 Legacy variables.  Please don't use them.
 9372 
 9373 =item B<other_host_message>
 9374 
 9375 =item B<output_message_one_day>
 9376 
 9377 =item B<output_message_all_days>
 9378 
 9379 =item B<output_message_all_days_in_range>
 9380 
 9381 Assorted mandatory scalars that are used for human-readable output.
 9382 B<other_host_message> defaults to "Other hosts syslogging to us",
 9383 B<output_message_one_day> defaults to "Logs for %n on %d",
 9384 B<output_message_all_days> defaults to "All logs for %n as of %d".
 9385 B<output_message_all_days_in_range> defaults to "All logs for %n for %s
 9386 through %e".
 9387 
 9388 =item B<date_format>
 9389 
 9390 This variable is a mandatory global scalar that describes how you want
 9391 the date printed in the output.  Uses the format of B<strftime(3)>.
 9392 Note that you probably shouldn't use characters that you wouldn't want
 9393 in a filename (ie. whitespace or '/') if you want to use the %d tag for 
 9394 I<output_file>.
 9395 
 9396 =item B<output_file>
 9397 
 9398 Equivalent to I<-o file>.
 9399 This variable is an optional global scalar that lists a filename that will
 9400 be output to instead of to standard output.  Works with I<mail_address> (if 
 9401 specified.)  Note that this variable is subject to the usual tag
 9402 substitutions (see L<"TAG SUBSTITUTIONS">, plus you can use the %d tag
 9403 for the date, so you can set it to something like 
 9404 "/var/log_analysis/archive/%n-%d".  See I<output_file_and_stdout>.
 9405 
 9406 =item B<output_file_and_stdout>
 9407 
 9408 Equivalent to I<-O>.
 9409 This variable is an optional global scalar that changes the behavior of
 9410 I<-o> or I<output_file>.  By default, I<-o> or I<output_file> 
 9411 causes output to only to only go to the named file.  With this variable,
 9412 output also goes to standard output.  Note: this does not currently
 9413 work with I<-m>.
 9414 
 9415 =item B<nodename>
 9416 
 9417 This variable is an optional global scalar that is used in a bunch of 
 9418 places: in checking to see whether a message from syslog (or other log 
 9419 type that defines I<nodename_pattern>) originated on this host; in 
 9420 reading in various default config files; etc.  
 9421 If left unset in the config, its value is set from the output
 9422 uname(2).  Its value is used to set the I<n> tag.  Note that 
 9423 unless I<leave_FQDNs_alone> is set, I<log_analysis> will try to strip 
 9424 the local domain name from I<nodename>.
 9425 
 9426 =item B<osname>
 9427 
 9428 =item B<osrelease>
 9429 
 9430 These two optional global scalars default to the equivalent of I<uname -s> and
 9431 I<uname -r>, respectively.  They are only used for reading in default config
 9432 files.  Their values set the I<s> and I<r> tags, respectively.
 9433 
 9434 =item B<domain>
 9435 
 9436 This variable is an optional global scalar.  If you don't set it, 
 9437 I<log_analysis> will try to set it by looking for a I<domain> line in 
 9438 I</etc/resolv.conf>.  If I<log_analysis> has I<domain> set, it will 
 9439 attempt to strip away the local domain name from all nodenames it 
 9440 encounters, unless I<leave_FQDNs_alone> is set.  See I<leave_FQDNs_alone> 
 9441 for details.
 9442 
 9443 =item B<leave_FQDNs_alone>
 9444 
 9445 This variable is an optional global scalar.  By default, if I<log_analysis>
 9446 has I<domain> set (either explicitly or implicitly), it will attempt to
 9447 strip away the domain name in I<domain>, or "localdomain",
 9448 from all nodenames it encounters.  
 9449 If you set this to I<1>, or to some other true value, I<log_analysis> will 
 9450 not attempt to strip the domain name in I<domain>.
 9451 
 9452 =item B<PATH>
 9453 
 9454 This variable is an optional global scalar that sets the I<PATH>
 9455 environment variable.  This doesn't help the initial setting of 
 9456 I<nodename>, I<osname>, or I<osrelease>, which
 9457 are set from uname(2).
 9458 
 9459 =item B<umask>
 9460 
 9461 This variable is an optional global scalar that sets the umask.
 9462 See L<umask(2)>.
 9463 
 9464 =item B<priority>
 9465 
 9466 This variable is an optional global scalar that sets the priority,
 9467 or 
 9468 "niceness."
 9469 See L<nice(1)>.
 9470 Setting this to zero means run unchanged from the current niceness.
 9471 Setting this negative is a bad idea unless you really know what you're 
 9472 doing, and is forbdidden to non-root users.
 9473 
 9474 =item B<decompression_rules>
 9475 
 9476 This variable is an optional global array of rules to decompress 
 9477 compressed files, in the format: compression-extension, comma, space,
 9478 command to decompress to stdout.  The command is subject to the usual
 9479 tag substitutions (see L<"TAG SUBSTITUTIONS">, plus %f stands for the 
 9480 filename.  For example, the rule for gzipped files is:
 9481 
 9482 C<gz, gzip -dc %f>
 9483 
 9484 The default rules support: .gz .Z .bz2
 9485 
 9486 If both I<decompression_rules> and I<open_command> apply to a given file,
 9487 the default is to use a temp file for the intermediate results unless
 9488 I<pipe_decompress_to_open> is used.  See L<"pipe_decompress_to_open"> for
 9489 more info.
 9490 
 9491 =item B<pgp_rules>
 9492 
 9493 This variable is an optional global array of rules for PGP encrypting
 9494 messages, in the format: PGP type (user defined), comma, space, 
 9495 command to PGP encrypt stdin to stdout.
 9496 The command is subject to the usual tag substitutions, plus %m stands for
 9497 the email address.  For use with the "B<-p>" and "B<-m>" options.  For 
 9498 example, the rule for gnupg is:
 9499 
 9500 C<g, gpg -aer %m 2E<gt>&1>
 9501 
 9502 Internally defined rules are "g" for "gnupg", "2" for PGP 2.x, and "5" 
 9503 for PGP 5.x.
 9504 
 9505 B<WARNING>: The user who runs log_analysis must have already imported
 9506 the mail destination's key for this to work.  Make sure to test this
 9507 before you put it in a cronjob.
 9508 
 9509 =item B<filename_ignore_patterns>
 9510 
 9511 This variable is an optional global array of patterns that describe filenames
 9512 to be skipped in an include_dir/include_dir_if_exists context, such as
 9513 emacs backup file (".*~") or vim backup files ("\..*\.swp").  Only the 
 9514 file component of the path is examined, not the directory component.
 9515 Patterns implicitly begin with ^ and implicitly end with $.
 9516 
 9517 =item B<mail_address>
 9518 
 9519 This variable is an optional global scalar that can consist of an email
 9520 address.  If set, the output of the script will be mailed to the
 9521 address it is set to.  The B<-m> option does the same thing, and overrides
 9522 this.
 9523 
 9524 =item B<mail_command>
 9525 
 9526 This variable is an optional global scalar that is the command used to send
 9527 mail if B<-m> is user or B<mail_address> is set.  The B<-M> option does
 9528 the same thing, and overrides this.  This variable is subject to the 
 9529 usual tag substitutions, plus %m stands for mail_address and %o stands for
 9530 the relevant output message.  The default is:
 9531 
 9532 C<Mail -s '%o' %m>
 9533 
 9534 =item B<memory_size_command>
 9535 
 9536 This variable is an optional global scalar that is the command used to 
 9537 determine the process' memory size.  Subject to the usual tag substitutions,
 9538 plus %p stands for the PID (process ID) in question.  If set, the command
 9539 is run at the end of the report, and the output is included in the footer.
 9540 
 9541 The default value for Linux is:
 9542 
 9543 C<ps -p %p -o vsz | tail -n +2>
 9544 
 9545 The default value for Solaris/SunOS is:
 9546 
 9547 C<ps -p %p -o vsz | tail -n +2>
 9548 
 9549 =item B<optional_log_files>
 9550 
 9551 This variable is an optional array of file globs that are to be processed.
 9552 Note that, unlike I<required_log_files>, these are globs rather than literal
 9553 filenames, although literal filenames will also work.  [Globs are filenames
 9554 with wildcards, ie. I</var/adm/messages*>.]
 9555 
 9556 See I<-r> for an issue specific to real mode and gui mode.
 9557 
 9558 =item B<commands_to_run>
 9559 
 9560 This variable is an optional array of commands that are also supposed to be
 9561 run to give a snapshot of the system state.  These are currently:
 9562 I<w>, I<df -k>, and I<cat /etc/dumpdates>.
 9563 
 9564 =item B<rcs_command>
 9565 
 9566 This variable is an optional global scalar that is the command used to do
 9567 RCS check-in on files (i.e. when gui_mode_config_save_does_rcs is set).
 9568 This variable is subject to the 
 9569 usual tag substitutions, plus %f stands for the file in question.  
 9570 The default is intended for RCS, although SCCS, CVS, SVN, or other systems
 9571 could be substituted.  The default is:
 9572 
 9573 C<ci -q -l -t-%f -m'automatic check-in' %f>
 9574 
 9575 =item B<suppress_commands>
 9576 
 9577 If set, the commands in B<commands_to_run> are NOT run during report mode.
 9578 This is equivalent to the B<-s> option.
 9579 
 9580 =item B<suppress_footer>
 9581 
 9582 If set, the various report mode footers are not displayed.
 9583 This is equivalent to the B<-S> option.
 9584 
 9585 =item B<ignore_categories>
 9586 
 9587 This variable is an optional array of categories that you don't want to see.
 9588 Rather than try to remove all the rules for these categories, you can just
 9589 list them here.
 9590 
 9591 =item B<priority_categories>
 9592 
 9593 This variable is an optional array of categories that will be listed first 
 9594 in the output.
 9595 
 9596 =item B<days_ago>
 9597 
 9598 This optional scalar variable is the config equivalent of the B<-d> option.
 9599 
 9600 =item B<process_all_nodenames>
 9601 
 9602 This optional scalar variable is the config equivalent of the B<-N> option.
 9603 
 9604 =item B<type_force>
 9605 
 9606 This optional scalar is the config equivalent of the B<-t> option.
 9607 
 9608 =item B<allow_nodenames>
 9609 
 9610 This variable is an optional array of nodenames that can log to this
 9611 host.  Usually, logs labelled as being from another host will not be 
 9612 anaylzed, and each such line will be listed in a special category; if
 9613 you chose to allow some nodenames (or if you choose to process all 
 9614 nodenames by setting B<-N> or setting I<process_all_nodenames>) then
 9615 these log messages will also be processed.
 9616 
 9617 =item B<real_mode>
 9618 
 9619 This variable is the config equivalent of the B<-r> option; see the 
 9620 B<-r> option for more details.
 9621 
 9622 =item B<real_mode_output_format>
 9623 
 9624 This is a required global scalar.  It describes the per-output format
 9625 for real mode and gui mode.  
 9626 It is subject to normal tag substitution
 9627 (see L<"TAG SUBSTITUTION">); in addition to the normal tags, "%c" is
 9628 replaced with the category, "%#" is replaced with the count, "%d" is
 9629 replaced with the formatted data, "%h" is replaced with the nodename
 9630 of the message, and "%R" is the raw, original log line without the
 9631 trailing newline.  If I<keep_all_log_lines> is set, you also get "%A"
 9632 for all the raw logs line.
 9633 I<WARNING:> you usually want "%h" (nodename of the
 9634 message), not "%n" (nodename of the host you're running on, which is
 9635 one of the default tags substitutions.)  Defaults to "%c: (loghost %n,
 9636 from host %h)\n%-10# %d\n\n".
 9637 
 9638 =item B<real_mode_sleep_interval>
 9639 
 9640 This optional global scalar is for use with real mode and gui mode.  In
 9641 these modes, I<log_analysis> reads log files for more data, sleeps for
 9642 a little while, and then reads again.  The sleep interval controls how
 9643 long I<log_analysis> sleeps (in seconds).  It defaults to 1.
 9644 
 9645 =item B<real_mode_check_interval>
 9646 
 9647 This optional global scalar is for use with real mode and gui mode.
 9648 In these modes, I<log_analysis> sits in a loop reading from the logs
 9649 files.  Periodically, it wants to check if the log files have rolled
 9650 over or if newer log files have appeared.  If at least this long (in
 9651 seconds) goes by since the last time we've checked, we check again.
 9652 
 9653 =item B<keep_all_raw_logs>
 9654 
 9655 This optional global scalar is a boolean for use with real mode and 
 9656 gui mode.  It enables a %A tag that contains all the raw logs for a 
 9657 given entry.  That is, if you have multiple log lines that contain 
 9658 essentially the same data, only the first line shows up in %R, and 
 9659 the rest are thrown out.  This variable lets you keep them all.  It 
 9660 can eat up a lot of memory, so it's disabled by default.
 9661 
 9662 =item B<real_mode_backlogs>
 9663 
 9664 This optional global scalar is equivalent to I<-b>.
 9665 
 9666 =item B<colors>
 9667 
 9668 This variable is an optional global array for use with real mode and
 9669 gui mode.  It defines the colors available on console, using "name,
 9670 string" pairs.  The usual tag substitution rules apply to the string,
 9671 plus the special tag %a stands for octal character 007 (ASCII BEL) and
 9672 %e stands for octal character 033 (ASCII ESC).  Some of the colors are
 9673 actually mode changes (ie. "normal", "inverse", "reverse", "blink",
 9674 etc.)  If you define any colors, you should also define a "normal"
 9675 color.  Note that "bell" is among the colors; it didn't belong
 9676 anywhere else.  You can list colors with I<log_analysis -I colors>.
 9677 
 9678 =item B<gui_mode>
 9679 
 9680 This variable is the config equivalent of the B<-g> option; see the 
 9681 B<-g> option for more details.  It is an optional scalar.
 9682 
 9683 =item B<gui_mode_modifier>
 9684 
 9685 In gui mode, the default modifier to do things with the keyboard is
 9686 "alt", ie. "alt-q" to exit.  This lets you change it.  It is an
 9687 optional scalar.
 9688 
 9689 =item B<gui_mode_unknowns_pause_at>
 9690 
 9691 In gui mode, if you get this many unknowns, the GUI will automatically 
 9692 pause.  Useful when building configurations.  It is an optional scalar.
 9693 Defaults unset.
 9694 
 9695 =item B<report_mode_output_node_per_category>
 9696 
 9697 =item B<report_mode_combine_nodes>
 9698 
 9699 =item B<report_mode_combine_shows_nodes>
 9700 
 9701 =item B<report_mode_combine_is_partway>
 9702 
 9703 These are assorted options for dealing with output for multiple node
 9704 situations (ie. logservers.)  They are all optional scalars.
 9705 See L<"LOGSERVER CONSIDERATIONS"> for details.
 9706 
 9707 =item B<window_command>
 9708 
 9709 In gui mode, if we need a window to run a command, say an action, this
 9710 will be the command that is used.  The tags are the same as
 9711 I<real_mode_output_format>, plus we have "%t" as the title and "%C" as
 9712 the command.  It is an optional scalar.
 9713 
 9714 =item B<login_action>
 9715 
 9716 This optional array lets you specify what action should be used to
 9717 login to a given host in gui mode, overriding I<default_login_action>.
 9718 Lines are in the format I<host, login_action>.
 9719 
 9720 =item B<default_login_action>
 9721 
 9722 This optional scalar specifies which login action should be used to
 9723 login in hosts by default in gui mode.
 9724 
 9725 =item B<default_throttle_format>
 9726 
 9727 See the I<throttle:> directive in the I<action> group.
 9728 
 9729 =item B<default_action_format>
 9730 
 9731 See the I<use_pipe> directive in the I<action> group.
 9732 
 9733 =item B<print_command>
 9734 
 9735 =item B<print_format>
 9736 
 9737 =item B<save_format>
 9738 
 9739 =item B<gui_mode_config_autosave>
 9740 
 9741 =item B<gui_mode_config_savelocal>
 9742 
 9743 =item B<gui_mode_config_save_does_rcs>
 9744 
 9745 =item B<gui_mode_config_file>
 9746 
 9747 =item B<gui_mode_print_all>
 9748 
 9749 =item B<gui_mode_save_all>
 9750 
 9751 =item B<gui_mode_save_events_file>
 9752 
 9753 These are for GUI use.
 9754 
 9755 =item B<default_sort>
 9756 
 9757 This variable is an optional global scalar that describes how certain
 9758 things will be sorted.  See L<"SORTING"> for info on what this can be set
 9759 to.  Defaults to I<funky>.
 9760 
 9761 =item B<default_filter>
 9762 
 9763 This variable is an optional global scalar that describes the default
 9764 category filter.  See L<"FILTERS"> for info on what this can be set
 9765 to.
 9766 
 9767 =back
 9768 
 9769 =head1 PREPROCESSOR DIRECTIVES
 9770 
 9771 NB: these get completely processed before all other directives, so they 
 9772 don't care about other syntax elements.
 9773 Except as noted, these should appear at the beginning of the
 9774 line after optional whitespace.
 9775 
 9776 =over 4
 9777 
 9778 =item B<@@end>
 9779 
 9780 End of config file.
 9781 
 9782 =item B<@@define> I<var> I<val>
 9783 
 9784 Define I<var> as value I<val>.  I<var> should contain only alphanumerics and
 9785 underscores, and start with an alphanumeric.  I<val> may contain no 
 9786 whitespace.
 9787 
 9788 =item B<@@undef> I<var>
 9789 
 9790 Undo any previous definition of I<var>.
 9791 
 9792 =item B<@@ifdef> I<var>
 9793 
 9794 =item B<@@ifndef> I<var>
 9795 
 9796 =item B<@@else>
 9797 
 9798 =item B<@@endif>
 9799 
 9800 If variable I<var> is defined, even defined as a false value,
 9801 the lines after the @@ifdef are used, otherwise the lines
 9802 are effectively commented out.  @@ifndef is the logical reverse.
 9803 @@ifdef and @@ifndef must be terminated by an @@endif.   They may contain an 
 9804 @@else section that works in the usual way.
 9805 
 9806 =item B<@@ifhost> I<name>
 9807 
 9808 =item B<@@ifnhost> I<name>
 9809 
 9810 These are just like @@ifdef and @@ifndef above, except that they test if 
 9811 the variable I<nodename> is equal to the value supplied for I<name>.
 9812 
 9813 =item B<@@ifos> I<name>
 9814 
 9815 =item B<@@ifnos> I<name>
 9816 
 9817 These are just like @@ifdef and @@ifndef above, except that they test if 
 9818 the variable I<osname> is equal to the value supplied for I<name>.
 9819 
 9820 =item B<@@{var}>
 9821 
 9822 If this string appears anywhere on any line, then if I<var> is a defined
 9823 variable, its value is substituted.  If I<var> is not a defined variable,
 9824 the string is left literally.  Note that this behaviour is different from
 9825 that of L<aide(1)>.
 9826 
 9827 =item B<@@warn> I<message>
 9828 
 9829 Print out I<message> as soon as the config is read.
 9830 
 9831 =item B<@@error> I<message>
 9832 
 9833 Print out I<message> and exit as soon as the config is read.
 9834 
 9835 =back
 9836 
 9837 =head1 SORTING
 9838 
 9839 You can sort category items using several different criteria.  You can 
 9840 set the I<default_sort>, and then on a per-category basis, you can use the 
 9841 I<sort:> keyword to control things even closer.  If you don't override it,
 9842 I<default_sort> defaults to I<funky>.  Sorts stack, so you can use 
 9843 "reverse string" or "reverse value".  In theory, you
 9844 can stack all of them, ie. "reverse value reverse funky", but there is no
 9845 guarantee that sorts are stable.
 9846 
 9847 The available sorts are:
 9848 
 9849 =over 4
 9850 
 9851 =item B<string> 
 9852 
 9853 Simple string "lexicographical" sort.  Does not handle numbers well.
 9854 
 9855 =item B<numeric> 
 9856 
 9857 Sorts numbers, including decimal numbers, correctly, but cannot handle 
 9858 non-numeric characters, and cannot handle IPs correctly.
 9859 
 9860 =item B<funky> 
 9861 
 9862 Tries to do the right thing with mixed integers and strings.
 9863 Handles IP addresses correctly.  It does not handle decimal numbers correctly.
 9864 
 9865 =item B<reverse> 
 9866 
 9867 Reverses the current order.  Can be used in conjunction with another sort, 
 9868 ie. "reverse string".
 9869 
 9870 =item B<value>
 9871 
 9872 Sorts by count (ascending) instead of by item.
 9873 
 9874 =item B<none> 
 9875 
 9876 Does no additional sorting.
 9877 
 9878 =back
 9879 
 9880 =head1 FILTERS
 9881 
 9882 Sometimes, you don't want to see all the information in a category, just
 9883 the top few items, or whatever.  Filters let you do this.  You can set a 
 9884 default filter using I<default_filter> (defaults to "none") or you can set 
 9885 filters on a per-category basis using the I<filter:> keyword.
 9886 
 9887 Some commands you can use:
 9888 
 9889 =over 4
 9890 
 9891 =item B<E<gt>= >N
 9892 
 9893 Only show items whose count is greater than or equal to N.
 9894 
 9895 =item B<E<lt>= >N
 9896 
 9897 =item B<E<gt> >N
 9898 
 9899 =item B<E<lt> >N
 9900 
 9901 =item B<= >N, B<== >N
 9902 
 9903 =item B<!= >N, B<E<lt>E<gt> >N, B<E<gt>E<lt> >N
 9904 
 9905 These are analagous to E<gt>=.
 9906 
 9907 =item B<top >N
 9908 
 9909 =item B<top >NB<%>
 9910 
 9911 =item B<top_strict >N
 9912 
 9913 =item B<top_strict >NB<%>
 9914 
 9915 Only show those items who count is in the top N or top N%.  The difference 
 9916 between I<top> and I<top_strict> is what happens when there's a tie to
 9917 be in the top N.  I<top> will include all the items that tie, even if this
 9918 means there will be more than N.  I<top_strict> always cuts off after N.
 9919 
 9920 =item B<bottom >N
 9921 
 9922 =item B<bottom >NB<%>
 9923 
 9924 =item B<bottom_strict >N
 9925 
 9926 =item B<bottom_strict >NB<%>
 9927 
 9928 Analagous to top.
 9929 
 9930 =item subfilter B<and> subfilter
 9931 
 9932 =item subfilter B<or> subfilter
 9933 
 9934 Lets you "and" or "or" two or more subfilters togther (ie. "top 10 and 
 9935 E<gt>= 4").
 9936 
 9937 =back
 9938 
 9939 =head1 UNIQUE DESTINATION
 9940 
 9941 I<log_analysis> has a relatively simple counting mechanism that is usually
 9942 effective.  One exception is when you want to track how often one value
 9943 occurs in your log uniquely with another value.  For example, suppose you're
 9944 watching firewall logs, $1 is the source IP, $2 is the destination IP, and 
 9945 you want to know if you're being scanned.  Tracking counts of "$1 $2" 
 9946 requires you to manually count how many times $1 occurs.  Tracking just "$1"
 9947 doesn't really tell you what you want, because you don't know if the source
 9948 IP is really scanning a bunch of different hosts, or just has a renegade
 9949 process that's banging away at a single destination.  What you want to track
 9950 is how many times $1 occurs with a unique $2.
 9951 
 9952 To do this sort of thing in a pattern config, set I<format:> to 
 9953 I<value1, value2> and set I<dest:> to "I<UNIQUE> category-name".  In our
 9954 example, we might say:
 9955 
 9956   format: $1, $2
 9957   dest:   UNIQUE scans
 9958 
 9959 The fields in format are not evaluated in a string context, and only the 
 9960 last comma acts as a separator.  So, if $3 contains the protocol information,
 9961 you might say this:
 9962 
 9963   format: sprintf("%-15s %s", $1, $3), $2
 9964   dest:   UNIQUE scans
 9965 
 9966 When detecting scans in particular, it makes sense to specify an event 
 9967 filter, ie.:
 9968 
 9969   category: scans
 9970     filter: >= 5
 9971 
 9972 Note that it's often useful to specify multiple dests with firewall pattern,
 9973 ie. one regular category dest, one UNIQUE dest with a filter threshold to 
 9974 detect a scan.  If so, you might want to add I<delete_if_unique> to the 
 9975 regular dest, so if it turns out you have a scan, you don't have to wade
 9976 through lots of garbage.  Ie.:
 9977 
 9978   pattern: kernel: block from ($pat{ip}):($pat{port}) to ($pat{ip}):($pat{port})
 9979 
 9980     format: $1 => $3:$4
 9981     delete_if_unique
 9982     dest: kernel block
 9983 
 9984     format: $1, $3
 9985     dest: UNIQUE scans
 9986 
 9987   category: scans
 9988     filter: >=5
 9989 
 9990 =head1 TAG SUBSTITUTIONS
 9991 
 9992 A few items are subject to "tag substitutions".  These are kind of like
 9993 printf's "%" sequences: a sequence like "%n" gets replaced with the nodename.
 9994 You can optionally specify field widths, which default to right-justified 
 9995 (ie.  "%10n") or can be preceeded with a "-" to make them left-justified 
 9996 (ie. "%-10n").  Also, a few of the basic C-style backslash sequences are
 9997 understood (ie. \n for newline, \t for tab, \\ for backslash).
 9998 Anything subject to tag substitutions will be listed as such.
 9999 
10000 Here are the standard tag sequences:
10001 
10002 =over 4
10003 
10004 =item B<%%> literal %
10005 
10006 =item B<%n> nodename (ie. the output of I<uname -n>.)
10007 
10008 =item B<%r> OS release (ie. the output of I<uname -r>.)
10009 
10010 =item B<%s> OS name (ie. the output of I<uname -s>.)
10011 
10012 =back
10013 
10014 There are also other tag sequences that apply in special situations.  They 
10015 are listed where they apply.
10016 
10017 If you try to use an undefined sequence (ie. "%Z" or something else), 
10018 you'll get an error.
10019 
10020 =head1 LOGSERVER CONSIDERATIONS
10021 
10022 I<log_analysis> defaults to single host operation.
10023 If you have a logserver that allows logs from multiple hosts 
10024 (ie. centralized logging) then you potentially have two concerns: 
10025 configuring what hostnames to allow, and how to display 
10026 multi-node logs in report mode.
10027 
10028 By default, log_analysis will only allow logs from the nodename of
10029 the logserver, so if you want to allow other nodes, you need to tell 
10030 I<log_analysis> which hostnames it should allow logs from.  Either set 
10031 I<allow_nodenames> to a list of nodenames to allow logs from, or set
10032 I<process_all_nodenames> (AKA option I<-N>) to accept everything.
10033 Another useful variable here is I<leave_FQDNs_alone>.
10034 
10035 Once you've accepted multiple nodes, there are a number of ways 
10036 I<log_analysis> can display them.  Let's say I received two 
10037 "Accepted publickey for morty from 192.168.1.1 port 50000 ssh2"
10038 events from "red-sonja" and three from "conan".  In the default 
10039 mode, that would look like this:
10040 
10041   Logs found for other hosts.  For host conan:
10042 
10043   ...
10044   sshd: accepted publickey:
10045   3          morty from 192.168.1.1
10046   ...
10047 
10048   Logs found for other hosts.  For host red-sonja:
10049 
10050   ...
10051   sshd: accepted publickey:
10052   2          morty from 192.168.1.1
10053   ...
10054 
10055 You can get the categories listed together more compactly by
10056 setting I<report_mode_output_node_per_category>.  Ie:
10057 
10058   ...
10059   sshd: accepted publickey: (host conan)
10060   3          morty from 192.168.1.1
10061 
10062   sshd: accepted publickey: (host red-sonja)
10063   2          morty from 192.168.1.1
10064   ...
10065 
10066 
10067 If you set I<report_mode_combine_nodes>, the category will be 
10068 combined into a single category.  Ie.:
10069 
10070   ...
10071   sshd: accepted publickey:
10072   5          morty from 192.168.1.1
10073   ...
10074 
10075 If you set both I<report_mode_combine_nodes> and 
10076 I<report_mode_combine_shows_nodes>, you get the combined messages along
10077 with a list of applicable hostnames.  Ie.:
10078 
10079   ...
10080   sshd: accepted publickey:
10081   5          morty from 192.168.1.1 (conan red-sonja)
10082   ...
10083 
10084 If you set both I<report_mode_combine_nodes> and
10085 I<report_mode_combine_is_partway>, the messages are listed like so:
10086 
10087   ...
10088   sshd: accepted publickey:
10089   3          morty from 192.168.1.1 (conan)
10090   2          morty from 192.168.1.1 (red-sonja)
10091   ...
10092 
10093 Other combinations of the variables I<report_mode_output_node_per_category>,
10094 I<report_mode_combine_nodes>, I<report_mode_combine_shows_nodes>, and
10095 I<report_mode_combine_is_partway> produce undefined results.
10096 
10097 =head1 EXAMPLES
10098 
10099 B<log_analysis -m root@whatever>
10100 
10101 Analyze yesterday's logs and mail the results to root@whatever.  You might 
10102 want to put this in a cronjob.
10103 
10104 B<log_analysis -p5 -m root@whatever>
10105 
10106 Same as the last one, but PGP encrypt the logs using PGP 5 before mailing.
10107 
10108 B<log_analysis -a>
10109 
10110 Look at all the logs, not just yesterday's.
10111 
10112 B<log_analysis -sa /var/adm/sulog>
10113 
10114 Analyze all the contents of sulog, don't bother with local state.
10115 
10116 B<log_analysis -san otherhost syslog-file>
10117 
10118 Analyze all the contents of syslog-file, which was created on "otherhost".
10119 Don't run the local state commands.
10120 
10121 B<log_analysis -sd1 -f foo.conf -U>
10122 
10123 This style of command is useful while developing local configs to handle
10124 log messages unknown to the internal config.  
10125 
10126 Use I<foo.conf> as a config file in addition to the internal config.  
10127 Output only the unknowns.
10128 
10129 =head1 COMPATIBILITY
10130 
10131 Written for Solaris and Linux.  May work for other OSs.
10132 
10133 Written for perl 5.00503.  May work with some earlier perl versions.
10134 
10135 =head1 NOTES
10136 
10137 You often need to be root to read interesting log files.
10138 
10139 It is customary to regularly "rollover" log files.  Many log file 
10140 formats don't include year infomation; among other benefits, rollover
10141 makes the dates in such logfiles unambiguous.  B<log_analysis> by
10142 default looks for log lines that match a particular day of the year,
10143 but does not even try to guess the year.  If the OS you're using
10144 doesn't rollover some logfiles by default (ie. Solaris doesn't
10145 rollover /var/adm/wtmpx, /var/adm/wtmp, or /var/adm/sulog), you will
10146 need to rollover these files yourself to get valid output from this
10147 program.
10148 
10149 On some OSes, '%' (ie. the percent symbol) has a special meaning in
10150 crontabs, and needs to be commented.  See L<crontab(1)>.
10151 
10152 When there are a lot of unknowns, B<log_analysis> can take a lot
10153 longer to run.  This is particularly a problem when you're first
10154 running it, before you customize for your site.  To get around this
10155 problem, if you send B<log_analysis> a SIGINT (ie. if you hit
10156 control-C), it will stop going through your logs and immediately
10157 output the results.
10158 
10159 =head1 FILES
10160 
10161 =over 4
10162 
10163 =item B</etc/log_analysis.conf>
10164 
10165 =item B</etc/log_analysis.conf-%n>
10166 
10167 =item B</etc/log_analysis.conf-%s-%r>
10168 
10169 =item B</etc/log_analysis.conf-%s>
10170 
10171 =item B<@prefix@/etc/log_analysis.conf>
10172 
10173 =item B<@prefix@/etc/log_analysis.conf-%n>
10174 
10175 =item B<@prefix@/etc/log_analysis.conf-%s-%r>
10176 
10177 =item B<@prefix@/etc/log_analysis.conf-%s>
10178 
10179 Config files, in order of precedence.  "%n", "%s", and "%r" have the
10180 usual tag substitution meanings; see L<"TAG SUBSTITUTIONS">.
10181 
10182 =item B</etc/log_analysis.d>
10183 
10184 =item B<@prefix@/etc/log_analysis.d>
10185 
10186 Plug-in directories.  All files in these directories will be treated as 
10187 config files and include'd.
10188 
10189 =item B<$HOME/.log_analysis.conf>
10190 
10191 If you start log_analysis with the "-g" option, this file will be loaded
10192 as a config file after all other config files, except those specified
10193 by I<-f>.  This is also the default file for the "save config" menu option.
10194 
10195 =back
10196 
10197 =head1 AUTHOR
10198 
10199 Mordechai T. Abzug <morty@frakir.org>
10200 
10201 =head1 See Also
10202 
10203 L<syslogd(8)>, L<last(1)>, L<perlre(1)>
10204 
10205 =cut