<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/perl -w -d

# Copyright (C) 2000 Daniel J. Urist
# Contact: Daniel J. Urist &lt;durist@world.std.com&gt;
# Portions of this code are Copyright (C) Jim Trocki
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


# FIXME 
# - all options supported from latest mon release?
# - hostgroup crashes (still!); right now we preload the debugger as a kluge
# - path validations should test for regular file vs. directory


# RECONSIDER
# - current validation scheme for globals warns but sets them anyway?
#

# TODO:
# - Edit help doco from mon to make it more relevant to the GUI
# - More validation
# - Watches that don't have any periods should be grayed out
# - More use should be made of the Watches HList; it should come up
#   with just a list of hostgroups; double-clicking on a hostgroup should
#   bring up a list of watches. Ideally, we should be able to drag and 
#   drop watches between hostgroups.
# - Add dialog for config file reopen

# Tell the debugger to go nonstop
BEGIN {
  $ENV{PERLDB_OPTS} = "N";
}

my $Version = '1.0';

require 5.00503;

use strict;
use Time::Period; # Needed for read_cf
use Tk;
use Tk::Dialog;
use Tk::Scale;
use Tk::Balloon;
use Tk::HList;
use Tk::BrowseEntry;
use Tk::Pane;

# Global variables for config file 
my $monconfigfile; # Config file
my $savefile;      # Save file
my %CF;
my %groups;
my %watch;
my %globals;       # We have to get this ourselves; read_cf doesn't do it completely


# Globals for windows we only want one of
my $Help;
my $Balloon;       # For any and all balloon help
my $Edit_globals;
my $Edit_global_submenu;
my $Edit_hostgroups;
my $Edit_hostgroups_add;
my $Edit_watches;
my $Edit_watches_edit;
my $Edit_watches_edit_period;
my $Edit_watches_edit_period_add_alert;

#
# Tk Entry function used a lot in the hash below
#
sub GLOBALS_entry {
  my($parent, $value, $g, $msg, $widgets) = @_;
  my $id = $parent-&gt;Entry();
  $id-&gt;insert(0, $value);
  $Balloon-&gt;attach($id, -balloonmsg =&gt; $msg);
  return $id;
}

#
# Path validation function used a lot in the hash 
#
sub GLOBALS_path_validate {
  my($parent, $val) = @_;
  $parent-&gt;Dialog(-title =&gt; 'Warning', 
		  -text =&gt; "The path \"$val\" Does Not Exist")-&gt;Show
		    unless -e $val;
} 
  
  
#
# All possible global vars in the mon config file and the widget we use to display and set them
#
# The "widget" subroutine gets passed the following args:
#   parent widget ID
#   current value of variable
#   ref to duped globals hash
#   balloon help message
#   ref to hash of widget ids in parent window
#
# This is an odd assortment of stuff, but it's convenient. It would be really nice if Perl provided
# some way to do simple in-line objects without having to go through the hassle of creating packages.
#
# The "validate" subroutine gets passed the parent id and the value
#
my %GLOBALS = ( 
	       'ORDER' =&gt; [ 'Paths', 'Authentication', 'Tuning', 'History', 'Ports', 'Downtime logging', 'Dependency behavior' ],
	       'Paths' =&gt; {
			   'ORDER' =&gt; ['basedir', 'mondir', 'alertdir', 'logdir', 'statedir', 'pidfile'],
			   'basedir' =&gt; {
					 'bmsg' =&gt; 'The full path for the state, script, and alert directory (optional)',
					 'widget' =&gt; \&amp;GLOBALS_entry,
					 'validate' =&gt; sub {
					    my($parent, $val) = @_;
					    &amp;GLOBALS_path_validate($parent, $val) if $val;
					 },
					}, 

			   'alertdir' =&gt; {
					  'bmsg' =&gt; 'The full path to the alert scripts',
					  'widget' =&gt; \&amp;GLOBALS_entry,
					  'validate' =&gt; \&amp;GLOBALS_path_validate,
					 }, 

			   'mondir' =&gt; {
					'bmsg' =&gt; 'The full path to the monitor scripts',
					'widget' =&gt; \&amp;GLOBALS_entry,
					'validate' =&gt; \&amp;GLOBALS_path_validate,
				       }, 

			   'statedir' =&gt; {
					  'bmsg' =&gt; 'The full path to the state directory',
					  'widget' =&gt; \&amp;GLOBALS_entry,
					  'validate' =&gt; \&amp;GLOBALS_path_validate,
					 }, 

			   'logdir' =&gt; {
					'bmsg' =&gt; 'The full path to the log directory',
					'widget' =&gt; \&amp;GLOBALS_entry,
					'validate' =&gt; \&amp;GLOBALS_path_validate,
				       },

			   'pidfile' =&gt; { 
					 'bmsg' =&gt; 'The file the sever will store its pid in',
					 'widget' =&gt; \&amp;GLOBALS_entry,
					 'validate' =&gt; \&amp;GLOBALS_path_validate,
					},
			  },
	       
	       'Authentication' =&gt; {
				    'ORDER' =&gt; [ 'authfile', 'authtype', 'userfile'],
				    'authfile' =&gt; {
						   'bmsg' =&gt; 'The full path to the authentication file',
						   'widget' =&gt; \&amp;GLOBALS_entry,
						   'validate' =&gt; \&amp;GLOBALS_path_validate,
						  },

				    'authtype' =&gt; {
						   'bmsg' =&gt; 'The type of authentication to use',
						   'widget' =&gt; sub {
						     my($parent, $value, $g, $msg, $widgets) = @_;
						     my $type;
						     my $F = $parent-&gt;Frame;
						     foreach $type ( 'getpwnam', 'userfile' ){
						       $_ = $F-&gt;Radiobutton(-text =&gt; $type, value =&gt; $type, 
									    -variable =&gt; \$globals{authtype},
									    # Toggle the state of userfile
									    -command =&gt; sub {
									      if($g-&gt;{authtype} eq "userfile"){
										$widgets-&gt;{userfile}-&gt;configure(-state=&gt;'normal');
									      }
									      else{
										$widgets-&gt;{userfile}-&gt;configure(-state=&gt;'disabled');
									      }
									    }
									   )-&gt;pack(-side =&gt; 'left');
														   
						     }
						     $Balloon-&gt;attach($F, -balloonmsg =&gt; $msg);
						     return $F;
						   },
						   'validate' =&gt; sub {},
						  }, 

				    'userfile' =&gt; { 
						   'bmsg' =&gt; 'This file is used when authtype is set to userfile',
						   'widget' =&gt; sub{
						     my($parent, $value, $g, $msg) = @_;
						     my $id;
						     if( $g-&gt;{authtype} eq "userfile" ){
						       $id = $parent-&gt;Entry();
						       $Balloon-&gt;attach($id, -balloonmsg =&gt; $msg);
						     }
						     else {
						       $id = $parent-&gt;Entry(-state =&gt; 'disabled');
						     }
						     $id-&gt;insert(0, $value);
						     return $id;
						   },
						   'validate' =&gt; sub {
						     &amp;GLOBALS_path_validate(@_) if $globals{authtype} eq "userfile";
						   },
						  },
				   },
	       
	       'Tuning' =&gt; {
			    'ORDER' =&gt; ['maxprocs', 'cltimeout', 'randstart'],
			    'maxprocs' =&gt; { 
					   'bmsg' =&gt; 'Limit on the number of concurrently forked processes',
					   'widget' =&gt; \&amp;GLOBALS_entry,
					   'validate' =&gt; sub {
					     my($parent, $val) = @_;
					     $parent-&gt;Dialog(-title =&gt; 'Warning', -text =&gt; "You must enter a number!")-&gt;Show 
					       unless $val =~ /^\d+$/
					   },
					  },

			    'cltimeout' =&gt; { 
					    'bmsg' =&gt; 'Client inactivity timeout in seconds',
					    'widget' =&gt; \&amp;GLOBALS_entry,
					    'validate' =&gt; sub {
					      my($parent, $val) = @_;
					      $parent-&gt;Dialog(-title =&gt; 'Warning', -text =&gt; "You must enter a number!")-&gt;Show 
						unless $val =~ /^\d+$/
					      },
					   },
			    'randstart' =&gt; {
					    'bmsg' =&gt; 'Randomize runtime of all services within this window (seconds)',
					    'widget' =&gt; \&amp;GLOBALS_entry,
					    'validate' =&gt; sub {
					      my($parent, $val) = @_;
					      $parent-&gt;Dialog(-title =&gt; 'Warning', -text =&gt; "You must enter a number!")-&gt;Show 
						unless $val =~ /^\d+$/
					      },
					   },
			   },
	       
#	       # FIXME need to somehow get the "use snmp" option in here
#	       'SNMP support' =&gt; {
#				  'snmpport' =&gt; { 'value' =&gt; "", 'bdoc' =&gt; "", },
#				 },
	       
	       'Ports' =&gt; {
			   'ORDER' =&gt; [ 'serverport',  'serverbind', 'trapport', 'trapbind'],
			   'serverport' =&gt; { 
					    'bmsg' =&gt; 'The TCP port number that the server should bind to',
					    'widget' =&gt; sub {
					      my($parent, $value, $g, $msg) = @_;
					      my $id = $parent-&gt;Scale('-from' =&gt; 1, '-to' =&gt; '65535', 
								      -orient =&gt; 'horizontal',
								      -tickinterval =&gt; 20000, -length =&gt; 200);
					      $id-&gt;set($value);
					      $Balloon-&gt;attach($id, -balloonmsg =&gt; $msg);
					      return $id;
					    },
					    'validate' =&gt; sub {}, #FIXME check to see if this port is in use
					   },

			   'serverbind' =&gt; {
					    'bmsg' =&gt; 'Address to bind the server port to',
					    'widget' =&gt; \&amp;GLOBALS_entry,
					    'validate' =&gt; sub {}, #FIXME make sure this is valid
					   },

			   'trapport' =&gt; { 
					    'bmsg' =&gt; 'The UDP port number that the trap server should bind to',
					    'widget' =&gt; sub {
					      my($parent, $value, $g, $msg) = @_;
					      my $id = $parent-&gt;Scale('-from' =&gt; 1, '-to' =&gt; '65535', 
								      -orient =&gt; 'horizontal',
								      -tickinterval =&gt; 20000, -length =&gt; 200);
					      $id-&gt;set($value);
					      $Balloon-&gt;attach($id, -balloonmsg =&gt; $msg);
					      return $id;
					    },
					    'validate' =&gt; sub {}, #FIXME check to see if this port is in use
					   },

			   'trapbind' =&gt; {
					    'bmsg' =&gt; 'Address to bind the trap port to',
					    'widget' =&gt; \&amp;GLOBALS_entry,
					    'validate' =&gt; sub {}, #FIXME make sure this is valid
					   },

			  },
	       

	       'Downtime logging' =&gt; {
				      'ORDER' =&gt; [ 'dtlogging', 'dtlogfile' ],
				      'dtlogging' =&gt; { 
						      'bmsg' =&gt; 'Turns downtime logging on or off',
						      'widget' =&gt; sub {
							my($parent, $value, $g, $msg, $widgets) = @_;
							my $F = $parent-&gt;Frame;
							foreach $_ ('yes', 'no'){
							  $F-&gt;Radiobutton(-text =&gt; $_, value =&gt; $_,
									  -variable =&gt; \$g-&gt;{dtlogging},
									  -command =&gt; sub {
									    if($g-&gt;{dtlogging} eq "yes"){
									      $widgets-&gt;{dtlogfile}-&gt;configure(-state=&gt;'normal');
									    }
									    else{
									      $widgets-&gt;{dtlogfile}-&gt;configure(-state=&gt;'disabled');
									    }
									  }									  
									 )-&gt;pack(-side =&gt; 'left');
							}
							return $F;
						      },
						      'validate' =&gt; sub {},
						     },
				      'dtlogfile' =&gt; { 
						      'bmsg' =&gt; 'File which will be used to record the downtime log',
						      'widget' =&gt; sub{
							my($parent, $value, $g, $msg) = @_;
							my $id;
							if( $g-&gt;{dtlogging} eq "yes" ){
							  $id = $parent-&gt;Entry();
							  $Balloon-&gt;attach($id, -balloonmsg =&gt; $msg);
							}
							else {
							  $id = $parent-&gt;Entry(-state =&gt; 'disabled');
							}
							$id-&gt;insert(0, $value);
							return $id;
						      },
						      'validate' =&gt; sub {
							&amp;GLOBALS_path_validate(@_) if $globals{dtlogging} eq "yes";
						      },						      
						     },
				     },
	       
	       'History' =&gt; {
			     'ORDER' =&gt; [ 'historicfile', 'histlength', 'historictime'],
			     'histlength' =&gt; { 
					      'bmsg' =&gt; 'The maximum number of events to be retained in history list',
					      'widget' =&gt; sub {
						my($parent, $value, $g, $msg) = @_;
						my $id = $parent-&gt;Scale('-from' =&gt; 0, '-to' =&gt; 1000, 
									-orient =&gt; 'horizontal', 
									-tickinterval =&gt; 300, -length =&gt; 200);
						$id-&gt;set($value);
						$Balloon-&gt;attach($id, -balloonmsg =&gt; $msg);
						return $id;
					      },
					      'validate' =&gt; sub {},
					     },
			     'historicfile' =&gt; { 
						'bmsg' =&gt; 'File to store alert history in',
						'widget' =&gt; \&amp;GLOBALS_entry,						
						'validate' =&gt; \&amp;GLOBALS_path_validate,
						},	  
			     'historictime' =&gt; { 
						'bmsg' =&gt; 'The amount of the history file to read upon startup (s, m, h)',
						'widget' =&gt; \&amp;GLOBALS_entry,							
						'validate' =&gt; sub {}, #FIXME make this real
					       },	  

			    },
	       
	       'Dependency behavior' =&gt; {
					 'ORDER' =&gt; [ 'dep_recur_limit', 'dep_behavior' ],
					 'dep_recur_limit' =&gt; { 
							       'bmsg' =&gt; 'Limit dependency recursion level to depth',
							       'widget' =&gt; sub {
								 my($parent, $value, $g, $msg) = @_;
								 my $id = $parent-&gt;Scale(-from =&gt; 0, -to =&gt; 100, 
											 -orient =&gt; 'horizontal', 
											 -tickinterval =&gt; 20, -length =&gt; 200);
								 $id-&gt;set($value);
								 $Balloon-&gt;attach($id, -balloonmsg =&gt; $msg);
								 return $id;
							       },
							       'validate' =&gt; sub {},
							      },		       

					 'dep_behavior' =&gt; { 
							    'bmsg' =&gt; 'Controls whether the dependency expression suppresses alerts or monitors',
							    'widget' =&gt; sub {
							      my($parent, $value, $g, $msg) = @_;
							      my $F = $parent-&gt;Frame;
							      $F-&gt;Radiobutton(-text =&gt; 'monitor', value =&gt; 'm',
									      -variable =&gt; \$g-&gt;{dep_behavior},)-&gt;pack(-side =&gt; 'left');
							      $F-&gt;Radiobutton(-text =&gt; 'alert', value =&gt; 'a',
									      -variable =&gt; \$g-&gt;{dep_behavior},)-&gt;pack(-side =&gt; 'right');
							      return $F;
							    },
							    'validate' =&gt; sub {},
							   },
					},
	      );


my %Doc = (
	   'Global Configs' =&gt;
q{
Global Variables

The following variables may be set to override compiled-in
defaults. Command-line options will have a higher precedence than
these definitions.

alertdir = dir 
       dir is the full path to the alert scripts. This is the value
       set by the -a command-line parameter.

       Multiple alert paths may be specified by separating them with a
       colon. All paths must be absolute.

       When the configuration file is read, all alerts referenced from
       the configuration will be looked up in each of these paths, and
       the full path to the first instance of the alert found is
       stored in a hash. This hash is only generated upon startup or
       after a "reset" command, so newly added alert scripts will not
       be recognized until a "reset" is performed.

mondir = dir 
       dir is the full path to the monitor scripts. This value may
       also be set by the -s command-line parameter.

       Multiple alert paths may be specified by separating them with a
       colon. All paths must be absolute.

       When the configuration file is read, all monitors referenced
       from the configuration will be looked up in each of these
       paths, and the full path to the first instance of the monitor
       found is stored in a hash. This hash is only generated upon
       startup or after a "reset" command, so newly added monitor
       scripts will not be recognized until a "reset" is performed.

statedir = dir 
       dir is the full path to the state directory. mon uses this
       directory to save various state information.

logdir = dir 
       dir is the full path to the log directory. mon uses this
       directory to save various logs, including the downtime log.

basedir = dir 
       dir is the full path for the state, script, and alert
       directory.

cfbasedir = dir 
       dir is the full path where all the config files can be found
       (monusers.cf, auth.cf, etc.).

authfile = file 
       file is the full path to the authentication file. 

authtype = type 
       type is the type of authentication to use. If type is getpwnam,
       then the standard Unix passwd file authentication method will
       be used (calls getpwnam(3) on the user and compares the
       crypt(3)ed version of the password with what it gets from
       getpwnam). This will not work if shadow passwords are enabled
       on the system.

       If type is userfile, then usernames and hashed passwords are
       read from userfile, which is defined via the userfile
       configuration variable.

       If type is shadow, then shadow password may be used (NOT
       IMPLEMENTED).

userfile = file 
       This file is used when authtype is set to userfile. It consists
       of a sequence of lines of the format 'username :
       password'. password is stored as the hash returned by the
       standard Unix crypt(3) function. NOTE: the format of this file
       is compatible with the Apache file based username/password file
       format. It is possible to use the htpasswd program supplied
       with Apache to manage the mon userfile.

       Blank lines and lines beginning with # are ignored.

snmpport = portnum 
       Set the SNMP port that the server binds to. 

serverbind = addr 

trapbind = addr 

       serverbind and trapbind specify which address to bind the
       server and trap ports to, respectively. If these are not
       defined, the default address is INADDR_ANY, which allows
       connections on all interfaces. For security reasons, it could
       be a good idea to bind only to the loopback interface.

snmp ={yes|no} 
       Turn on/off SNMP support (currently unimplemented). 

dtlogfile = file 
       file is a file which will be used to record the downtime
       log. Whenever a service fails for some amount of time and then
       stop failing, this even is written to the log. If this
       parameter is not set, no logging is done. The format of the
       file is as follows (# is a comment and may be ignored):

       timenoticed group service firstfail downtime interval summary.

       timenoticed is the time(2) the service came back up.

       group service is the group and service which failed.

       firstfail is the time(2) when the service began to fail.

       downtime is the number of seconds the service failed.

       interval is the frequency (in seconds) that the service is
       polled.

       summary is the summary line from when the service was failing.

dtlogging = yes/no 

       Turns downtime logging on or off. The default is off. 

histlength = num 
       num is the the maximum number of events to be retained in
       history list. The default is 100. This value may also be set by
       the -k command-line parameter.

historicfile = file 
       If this variable is set, then alerts are logged to file, and
       upon startup, some (or all) of the past history is read into
       memory.

historictime = timeval 
       num is the amount of the history file to read upon
       startup. "Now" - timeval is read. See the explanation of
       interval in the "Service Definitions" section for a description
       of timeval.

serverport = port 
       port is the TCP port number that the server should bind
       to. This value may also be set by the -p command-line
       parameter. Normally this port is looked up via
       getservbyname(3), and it defaults to 2583.

trapport = port 
       port is the UDP port number that the trap server should bind
       to. Normally this port is looked up via getservbyname(3), and
       it defaults to 2583.

pidfile = path 
       path is the file the sever will store its pid in. This value
       may also be set by the -P command-line parameter.

maxprocs = num 
       Throttles the number of concurrently forked processes to
       num. The intent is to provide a safety net for the unlikely
       situation when the server tries to take on too many tasks at
       once. Note that this situation has only been reported to happen
       when trying to use a garbled configuration file! You don't want
       to use a garbled configuration file now, do you?

cltimeout = secs 
       Sets the client inactivity timeout to secs. This is meant to
       help thwart denial of service attacks or recover from crashed
       clients. secs is interpreted as a "1h/1m/1s" string, where "1m"
       = 60 seconds.

randstart = interval 
       When the server starts, normally all services will not be
       scheduled until the interval defined in the respective service
       section. This can cause long delays before the first check of a
       service, and possibly a high load on the server if multiple
       things are scheduled at the same intervals. This option is used
       to randomize the scheduling of the first test for all services
       during the startup period, and immediately after the reset
       command. If randstart is defined, the scheduled run time of all
       services of all watch groups will be a random number between
       zero and randstart seconds.

dep_recur_limit = depth 
       Limit dependency recursion level to depth. If dependency
       recursion (dependencies which depend on other dependencies)
       tries to go beyond depth, then the recursion is aborted and a
       messages is logged to syslog. The default limit is 10.

dep_behavior = {a|m} 
       dep_behavior controls whether the dependency expression
       suppresses either the running of alerts or monitors when a node
       in the dependency graph fails. Read more about the behavior in
       the "Service Definitions" section below.

       This is a global setting which controls the default settings
       for the service-specified variable.

syslog_facility = facility 
       Specifies the syslog facility used for logging. daemon is the
       default.

startupalerts_on_reset = {yes|no} 

       If set to "yes", startupalerts will be invoked when the reset
       client command is executed. The default is "no".
},

	   'Host Groups' =&gt;
q{
Hostgroup entries begin with the keyword hostgroup, and are followed
by a hostgroup tag and one or more hostnames or IP addresses,
separated by whitespace. The hostgroup tag must be composed of
alphanumeric characters, a dash ("-"), a period ("."), or an
underscore ("_"). Non-blank lines following the first hostgroup line
are interpreted as more hostnames. The hostgroup definition ends with
a blank line. For example:

       hostgroup servers nameserver smtpserver nntpserver
               nfsserver httpserver smbserver

       hostgroup router_group cisco7000 agsplus
},

	   'Watches' =&gt; 
q{
Watch Group Entries

Watch entries begin with a line that starts with the keyword watch,
followed by whitespace and a single word which normally refers to a
pre-defined hostgroup. If the second word is not recognized as a
hostgroup tag, a new hostgroup is created whose tag is that word, and
that word is its only member.

Watch entries consist of one or more service definitions. 

There is a special watch group entry called "default". If a default
watch group is defined with a "default" service entry, then this
definition will be used in handling unknown mon traps.

Service Definitions

service servicename 
       A service definition begins with they keyword service followed
       by a word which is the tag for this service.

       The components of a service are an interval, monitor, and one
       or more time period definitions, as defined below.

       If a service name of "default" is defined within a watch group
       called "dafault" (see above), then the default/default
       definition will be used for handling unknown mon traps.

interval timeval 
       The keyword interval followed by a time value specifies the
       frequency that a monitor script will be triggered. Time values
       are defined as "30s", "5m", "1h", or "1d", meaning 30 seconds,
       5 minutes, 1 hour, or 1 day. The numeric portion may be a
       fraction, such as "1.5h" or an hour and a half. This format of
       a time specification will be referred to as timeval.

traptimeout timeval 
       This keyword takes the same time specification argument as
       interval, and makes the service expect a trap from an external
       source at least that often, else a failure will be
       registered. This is used for a heartbeat-style service.

trapduration timeval 
       If a trap is received, the status of the service the trap was
       delivered to will normally remain constant. If trapduration is
       specified, the status of the service will remain in a failure
       state for the duration specified by timeval, and then it will
       be reset to "success".

randskew timeval 
       Rather than schedule the monitor script to run at the start of
       each interval, randomly adjust the interval specified by the
       interval parameter by plus-or-minus randskew. The skew value is
       specified as the interval parameter: "30s", "5m", etc... For
       example if interval is 1m, and randskew is "5s", then mon will
       schedule the monitor script some time between every 55 seconds
       and 65 seconds. The intent is to help distribute the load on
       the server when many services are scheduled at the same
       intervals.

monitor monitor-name [arg...] 
       The keyword monitor followed by a script name and arguments
       specifies the monitor to run when the timer expires.
       Shell-like quoting conventions are followed when specifying the
       arguments to send to the monitor script. The script is invoked
       from the directory given with the -s argument, and all
       following words are supplied as arguments to the monitor
       program, followed by the list of hosts in the group referred to
       by the current watch group. If the monitor line ends with ";;"
       as a separate word, the host groups are not appended to the
       argument list when the program is invoked.

allow_empty_group 
       The allow_empty_group option will allow a monitor to be invoked
       even when the hostgroup for that watch is empty because of
       disabled hosts. The default behavior is not to invoke the
       monitor when all hosts in a hostgroup have been disabled.

description descriptiontext 
       The text following description is queried by client programs,
       passed to alerts and monitors via an environment variable. It
       should contain a brief description of the service, suitable for
       inclusion in an email or on a web page.

exclude_hosts host [host...] 
       Any hosts listed after exclude_hosts will be excluded from the
       service check.

exclude_period periodspec 
       Do not run a scheduled monitor during the time identified by
       periodspec.

depend dependexpression 
       The depend keyword is used to specify a dependency expression,
       which evaluates to either true of false, in the boolean
       sense. Dependencies are actual Perl expressions, and must obey
       all syntactical rules. The expressions are evaluated in their
       own package space so as to not accidentally have some unwanted
       side-effect. If a syntax error is found when evaluating the
       expression, it is logged via syslog.

       Before evaluation, the following substitutions on the
       expression occur: phrases which look like "group:service" are
       substituted with the value of the current operational status of
       that specified service. These opstatus substitutions are
       computed recursively, so if service A depends upon service B,
       and service B depends upon service C, then service A depends
       upon service C. Successful operational statuses (which evaluate
       to "1") are "STAT_OK", "STAT_COLDSTART", "STAT_WARMSTART", and
       "STAT_UNKNOWN". The word "SELF" (in all caps) can be used for
       the group (e.g. "SELF:service"), and is an abbreviation for the
       current watch group.

       This feature can be used to control alerts for services which
       are dependent on other services, e.g. an SMTP test which is
       dependent upon the machine being ping-reachable.

dep_behavior {a|m} 
       The evaluation of dependency graphs can control the suppression
       of either alert or monitor invocations.

       Alert suppression. If this option is set to "a", then the
       dependency expression will be evaluated after the monitor for
       the service exits or after a trap is received. An alert will
       only be sent if the evaluation succeeds, meaning that none of
       the nodes in the dependency graph indicate failure.

       Monitor suppression. If it is set to "m", then the dependency
       expression will be evaulated before the monitor for the service
       is about to run. If the evaulation succeeds, then the monitor
       will be run. Otherwise, the monitor will not be run and the
       status of the service will remain the same.

Period Definitions

Periods are used to define the conditions which should allow alerts to
be delivered.

period [label:] periodspec 
       A period groups one or more alarms and variables which control
       how often an alert happens when there is a failure.  The period
       keyword has two forms. The first takes an argument which is a
       period specification from Patrick Ryan's Time::Period Perl 5
       module. Refer to "perldoc Time::Period" for more information.

       The second form requires a label followed by a period
       specification, as defined above. The label is a tag consisting
       of an alphabetic character or underscore followed by zero or
       more alphanumerics or underscores and ending with a colon. This
       form allows multiple periods with the same period
       definition. One use is to have a period definition which has no
       alertafter or alertevery parameters for a particular time
       period, and another for the same time period with a different
       set of alerts that does contain those parameters.

alertevery timeval 
       The alertevery keyword (within a period definition) takes the
       same type of argument as the interval variable, and limits the
       number of times an alert is sent when the service continues to
       fail. For example, if the interval is "1h", then only the
       alerts in the period section will only be triggered once every
       hour. If the alertevery keyword is omitted in a period entry,
       an alert will be sent out every time a failure is detected. By
       default, if the output of two successive failures changes, then
       the alertevery interval is overridden. If the word "summary" is
       the last argument, then only the summary output lines will be
       considered when comparing the output of successive failures.

alertafter num 

alertafter num timeval 
       The alertafter keyword (within a period section) has two forms:
       only with the "num" argument, or with the "num timeval"
       arguments. In the first form, an alert will only be invoked
       after "num" consecutive failures.

       In the second form, the arguments are a positive integer
       followed by an interval, as described by the interval variable
       above. If these parameters are specified, then the alerts for
       that period will only be called after that many failures happen
       within that interval. For example, if alertafter is given the
       arguments "3 30m", then the alert will be called if 3 failures
       happen within 30 minutes.

numalerts num 

       This variable tells the server to call no more than num alerts
       during a failure. The alert counter is kept on a per-period
       basis, and is reset upon each success.

comp_alerts 

       If this option is specified, then upalerts will only be called
       if a corresponding "down" alert has been called.

alert alert [arg...] 
       A period may contain multiple alerts, which are triggered upon
       failure of the service. An alert is specified with the alert
       keyword, followed by an optional exit parmeter, and arguments
       which are interpreted the same as the monitor definition, but
       without the ";;" exception. The exit parameter takes the form
       of exit=x or exit=x-y and has the effect that the alert is only
       called if the exit status of the monitor script falls within
       the range of the exit parameter. If, for example, the alert
       line is alert exit=10-20 mail.alert mis then mail-alert will
       only be invoked with mis as its arguments if the monitor
       program's exit value is between 10 and 20. This feature allows
       you to trigger different alerts at different severity levels
       (like when free disk space goes from 8% to 3%).

       See the ALERT PROGRAMS section above for a list of the
       pramaeters mon will pass automatically to alert programs.

upalert alert [arg...] 
       An upalert is the compliment of an alert. An upalert is called
       when a services makes the state transition from failure to
       success. The upalert script is called supplying the same
       parameters as the alert script, with the addition of the -u
       parameter which is simply used to let an alert script know that
       it is being called as an upalert. Multiple upalerts may be
       specified for each period definition. Please note that the
       default behavior is that an upalert will be sent regardless if
       there were any prior "down" alerts sent, since upalerts are
       triggered on a state transition. Set the per-period comp_alerts
       option to pair upalerts with "down" alerts.

startupalert alert [arg...] 
       A startupalert is only called when the mon server starts
       execution.

upalertafter timeval 
       The upalertafter parameter is specified as a string that
       follows the syntax of the interval parameter ("30s", "1m",
       etc.), and controls the triggering of an upalert. If a service
       comes back up after being down for a time greater than or equal
       to the value of this option, an upalert will be called. Use
       this option to prevent upalerts to be called because of "blips"
       (brief outages).
},
);

#' Keep emacs perl-mode happy...

my $main = new MainWindow;
$Balloon = $main-&gt;Balloon; 

my $menubar = $main-&gt;Frame;
$menubar-&gt;pack(-fill =&gt; 'x');
my $file = $menubar-&gt;Menubutton(qw/-text File -underline 0 -menuitems/ =&gt;
    [
     [Button    =&gt; '~Open', -command =&gt; [\&amp;open_config_file]],
     [Button    =&gt; '~Save', -command =&gt; [\&amp;save_config_file]],
     [Button    =&gt; 'Save ~As...', -command =&gt; [\&amp;save_config_file_as]],
     [Separator =&gt; ''],
     [Button    =&gt; '~Quit', -command =&gt; [\&amp;exit]],
    ])-&gt;pack(-side =&gt; 'left');
my $help = $menubar-&gt;Menubutton(qw/-text Help -underline 0 -menuitems/ =&gt;
    [
     [Button    =&gt; '~About', -command =&gt; sub {
	my $A = $main-&gt;Toplevel(-title =&gt; 'About');
	$A-&gt;Label(
		  -text =&gt; "mongui.pl Version $Version\n\nCopyright (C) 2000 Daniel J. Urist\nContact: Daniel J. Urist &lt;durist\@world.std.com&gt;\nPortions of this code are Copyright (C) Jim Trocki\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version\n2 of the License, or (at your option) any later version."
		 )-&gt;pack;
	$A-&gt;Button(-text =&gt; 'Dismiss', -command =&gt; sub {$A-&gt;destroy;})-&gt;pack;
      }],
     [Button    =&gt; '~Global Configs', -command =&gt; [\&amp;Help, 'Global Configs']],
     [Button    =&gt; '~Host Groups', -command =&gt; [\&amp;Help, 'Host Groups']],
     [Button    =&gt; '~Watches', -command =&gt; [\&amp;Help, 'Watches']],
    ])-&gt;pack(-side =&gt; 'right');


my $buttons = $main-&gt;Frame;
$buttons-&gt;pack;

$main-&gt;Button(-text =&gt; 'Global Configs',
		 -command =&gt; sub { 
		   unless($monconfigfile){
		     &amp;no_config_file_selected;
		     return;
		   }
		   &amp;Edit_globals;
		 }
		)-&gt;pack(-side =&gt; 'left');

$main-&gt;Button(-text =&gt; 'Host Groups',
	      -command =&gt; sub {
		unless($monconfigfile){
		  &amp;no_config_file_selected;
		  return;
		}
		&amp;Edit_hostgroups;
	      }
	     )-&gt;pack(-side =&gt; 'left');

$main-&gt;Button(-text =&gt; 'Watches',
	      -command =&gt; sub {
		unless($monconfigfile){
		  &amp;no_config_file_selected;
		  return;
		}		
		&amp;Edit_watches;
	      }
	     )-&gt;pack(-side =&gt; 'left');

MainLoop;

sub Help {
  my ($subject) = @_;
  $Help-&gt;destroy if Exists($Help);
  $Help = $main-&gt;Toplevel(-takefocus =&gt; 1, -title =&gt; $subject);
  my $R = $Help-&gt;Scrolled('ROText', -scrollbars =&gt;'e')-&gt;pack;
  $R-&gt;insert('end', $Doc{$subject});
  $Help-&gt;Button(-text =&gt; 'Dismiss', -command =&gt; sub {$Help-&gt;destroy;})-&gt;pack;
}

sub open_config_file {
  $monconfigfile = $main-&gt;getOpenFile;
  $savefile = $monconfigfile;

  if( -e $monconfigfile ){
    eval{ &amp;read_cf($monconfigfile) };
    if($@){
      $main-&gt;Dialog(-title =&gt; 'Error', 
		    -text =&gt; "Parse error in config file $monconfigfile: $@")-&gt;Show;
      undef $monconfigfile;
      undef $savefile;
      return;
    }
    &amp;read_globals($monconfigfile);
  }
  else{
    $main-&gt;Dialog(-title =&gt; 'Warning', 
		  -text =&gt; "Config File $monconfigfile Does Not Exist")-&gt;Show;
  }
}

sub save_config_file {
  unless($monconfigfile){
    &amp;no_config_file_selected;
    return;
  }
  eval{ &amp;write_cf($savefile) };
  $main-&gt;Dialog(-title =&gt; 'Error', 
		-text =&gt; "Could not write config file $savefile: $@")-&gt;Show if $@;
}

sub save_config_file_as {
  unless($monconfigfile){
    &amp;no_config_file_selected;
    return;
  }
  $savefile = $main-&gt;getSaveFile;
  &amp;save_config_file($savefile);
}

sub no_config_file_selected {
    my $errmsg = $main-&gt;Dialog(-title =&gt; 'Error', 
			       -text =&gt; "No config file selected!")-&gt;Show;
}

sub implement_me {
  my $squawk = $main-&gt;Dialog(-text =&gt; "Implement me, Dan!");
  $squawk-&gt;Show;
}


############################################################
# Globals
############################################################

#
# Read all the globals defined in mon.cf into %globals
#
sub read_globals {
  # Set some default values
  $globals{dtlogging} = "no";
  $globals{serverport} = 2583;
  $globals{trapport} = 2583;
  $globals{serverbind} = "IN_ADDR_ANY"; # FIXME should we require POSIX or does mon want these as strings?
  $globals{trapbind} = "IN_ADDR_ANY";   #
  $globals{dep_recur_limit} = 10;
  $globals{dep_behavior} = "m";         # FIXME what is the real default for this?
  
  my($CF) = @_;
  open(CF, $CF) or die "Could not open $CF for reading\n";
  my @vars = grep(!/^\s|^#|^hostgroup|^watch/, &lt;CF&gt;); 
  close CF;
  chomp(@vars);
  map { $_ =~ /^(\S+)\s*=\s*(\S+)/; $globals{$1} = $2; } @vars;

  # Postprocessing for consistency
  $globals{randstart} =~ s/s$//;
}


#
# Create the "Edit Globals" window
#
sub Edit_globals {
  $Edit_globals-&gt;destroy if Exists($Edit_globals);
  $Edit_globals = $main-&gt;Toplevel(-takefocus =&gt; 1, -title =&gt; 'Global Configs');
  my $F = $Edit_globals-&gt;Frame-&gt;pack;
  my %g = %globals;
  my $category;
  foreach $category (@{$GLOBALS{ORDER}}) {
    $F-&gt;Button( -text =&gt; $category, -command =&gt; [\&amp;Edit_global_submenu, \%g, $category] )-&gt;pack(-fill =&gt; 'x');
  }

  my $B = $Edit_globals-&gt;Frame-&gt;pack;
  $B-&gt;Button(-text =&gt; "Okay", -command =&gt; sub {
	       %globals = %g;
	       $Edit_globals-&gt;destroy;
	     })-&gt;pack(-side =&gt; "left");
  $B-&gt;Button(-text =&gt; "Cancel", -command =&gt; sub {$Edit_globals-&gt;destroy;})-&gt;pack(-side =&gt; "right");
}

# 
# Create a submenu for global variables
#
sub Edit_global_submenu {
  my($g, $category) = @_;
  $Edit_global_submenu-&gt;destroy if Exists($Edit_global_submenu);
  $Edit_global_submenu = $Edit_globals-&gt;Toplevel(-takefocus =&gt; 1, -title =&gt; "Edit $category Configs");
  my $S = $Edit_global_submenu-&gt;Frame;
  $S-&gt;pack;

  my %widgets = ();
  my $var;
  foreach $var ( @{$GLOBALS{$category}-&gt;{ORDER}} ){
    $S-&gt;Label(-text =&gt; $var)-&gt;pack;
    $_ = &amp;{$GLOBALS{$category}-&gt;{$var}-&gt;{widget}}($S, $g-&gt;{$var}, $g, $GLOBALS{$category}-&gt;{$var}-&gt;{bmsg}, \%widgets);
    $_-&gt;pack;
    $widgets{$var} = $_;
  }

  my $A = $Edit_global_submenu-&gt;Frame(-borderwidth =&gt; 10)-&gt;pack;;
  $A-&gt;Button(-text =&gt; "Okay", 
	     -command =&gt; sub {
	       # This is a kludge to deal with inconsistencies in widgets;
	       # some widgets support the "get" method, some are just bound
	       # to variables (we are only dealing with single-values here).
	       # eval traps the errors if the get() method doesn't exist
	       foreach $_ ( keys %widgets ){
		 eval { $g-&gt;{$_} = $widgets{$_}-&gt;get; };
		 # validate 
		 &amp;{$GLOBALS{$category}-&gt;{$_}-&gt;{validate}}($Edit_global_submenu, $g-&gt;{$_});
	       }
	       $Edit_global_submenu-&gt;destroy;
	     },
	    )-&gt;pack(-side =&gt; 'left');

  $A-&gt;Button(-text =&gt; "Cancel", -command =&gt; [$Edit_global_submenu =&gt; 'destroy'] )-&gt;pack(-side =&gt; 'right');
}

############################################################
# HOSTGROUPS
############################################################

sub Edit_hostgroups {
  $Edit_hostgroups-&gt;destroy if Exists($Edit_hostgroups);
  $Edit_hostgroups = $main-&gt;Toplevel(-takefocus =&gt; 1, -title =&gt; 'Edit Host Groups');

  my $F = $Edit_hostgroups-&gt;Frame-&gt;pack;

  my $H = $F-&gt;Scrolled('Pane', -scrollbars =&gt; 'e', -gridded =&gt; 'y')-&gt;pack; 
  my $i = 0;                                                               
  foreach ( sort keys %groups ){
    my $hg_name = $H-&gt;Entry(-width =&gt; 15,)-&gt;grid(-row =&gt; $i, -column =&gt; 0);
    $hg_name-&gt;insert(0, $_);
    $Balloon-&gt;attach($hg_name, -balloonmsg =&gt; 'Unique hostgroup name');


    my $hg_list = $H-&gt;Entry(-width =&gt; 50,)-&gt;grid(-row =&gt; $i, -column =&gt; 1);
    $hg_list-&gt;insert(0, join(" ", @{$groups{$_}}));
    $Balloon-&gt;attach($hg_list, -balloonmsg =&gt; 'List of hosts');
    $i++;
  }

  my $F2 = $F-&gt;Frame-&gt;pack;
  $F2-&gt;Button(-text =&gt; 'Add Hostgroup', -command =&gt; sub { &amp;Edit_hostgroups_add($H); })-&gt;pack(-side =&gt; 'left');
  $F2-&gt;Button(-text =&gt; 'Delete Hostgroup', -command =&gt; sub {
		my $widget = $Edit_hostgroups-&gt;focusCurrent;
		return unless Exists($widget);
		my $parentpath = $widget-&gt;parent-&gt;PathName;
		my $H_subwidgetpath = $H-&gt;Subwidget('scrolled')-&gt;PathName; # There seems to be an additional frame
		if( $parentpath =~ /^$H_subwidgetpath/ ){
		  %_ = $widget-&gt;gridInfo;
		  my($hg_list, $hg_name)= $H-&gt;Subwidget('scrolled')-&gt;gridSlaves('-row' =&gt; $_{-row});
		  $hg_name-&gt;gridForget;
		  $hg_name-&gt;destroy;
		  $hg_list-&gt;gridForget;
		  $hg_list-&gt;destroy;
		}
	      })-&gt;pack(-side =&gt; 'right');

  my $B = $Edit_hostgroups-&gt;Frame-&gt;pack;
  $B-&gt;Button(-text =&gt; "Okay", -command =&gt; sub {
	       my($hg_list, $hg_name);
	       %groups = ();
	       @_ = $H-&gt;Subwidget('scrolled')-&gt;gridSlaves;
	       while($hg_list = shift and $hg_name = shift){
		 next unless $hg_list-&gt;Exists and $hg_name-&gt;Exists;
		 @{$groups{$hg_name-&gt;get}} = split(' ', $hg_list-&gt;get);
	       }
	       $Edit_hostgroups-&gt;destroy;
	     })-&gt;pack(-side =&gt; "left");
  $B-&gt;Button(-text =&gt; "Cancel", -command =&gt; sub {$Edit_hostgroups-&gt;destroy;})-&gt;pack(-side =&gt; "right");
}

sub Edit_hostgroups_add {
  $Edit_hostgroups_add-&gt;destroy if Exists($Edit_hostgroups_add);
  $Edit_hostgroups_add = $Edit_hostgroups-&gt;Toplevel(-takefocus =&gt; 1, -title =&gt; 'Add Host Group');
  my($H) = @_;

  $Edit_hostgroups_add-&gt;Label(-text =&gt; "Host Group Name")-&gt;pack;
  my $name = $Edit_hostgroups_add-&gt;Entry(-width =&gt; 15)-&gt;pack;
  $Balloon-&gt;attach($name, -balloonmsg =&gt; 'Unique hostgroup name');

  $Edit_hostgroups_add-&gt;Label(-text =&gt; "Hosts")-&gt;pack;
  my $hosts = $Edit_hostgroups_add-&gt;Entry(-width =&gt; 50)-&gt;pack;;
  $Balloon-&gt;attach($hosts, -balloonmsg =&gt; 'List of hosts');

  my $B = $Edit_hostgroups_add-&gt;Frame-&gt;pack;
  $B-&gt;Button(-text =&gt; "Okay", -command =&gt; sub {
	       my @hgs = map{$_-&gt;get;} $H-&gt;Subwidget('scrolled')-&gt;gridSlaves(-column =&gt; 0);
	       my $hg_name = $name-&gt;get;
	       if ($hg_name !~ /\S/){
		 $Edit_hostgroups_add-&gt;Dialog(-title =&gt; 'Warning', 
					      -text =&gt; "Invalid Host Group Name!")-&gt;Show; 
		 return;
	       }

	       if( grep(/^$hg_name$/, @hgs) ) {
		 $Edit_hostgroups_add-&gt;Dialog(-title =&gt; 'Warning', 
					      -text =&gt; "Duplicate Host Group Name!")-&gt;Show; 
		 return;
	       }

	       # FIXME we check here, but we don't check when we edit in place
	       my @bogushosts = grep {
		 $_ !~ /\d+\.\d+\.\d+\.\d+/ and !defined gethostbyname($_);
	       } split(' ', $hosts-&gt;get);
	       
	       $Edit_hostgroups_add-&gt;Dialog(-title =&gt; 'Warning', 
					    -text =&gt; "Can not resolve hosts " . join("; ", @bogushosts))-&gt;Show
					      if scalar @bogushosts;

	       my $hg_name_entry = $H-&gt;Entry(-width =&gt; 15,)-&gt;grid(-column =&gt; 0, -row =&gt; scalar(@hgs) + 1);
	       $hg_name_entry-&gt;insert(0,$hg_name);

	       my $hg_list = $H-&gt;Entry(-width =&gt; 50,)-&gt;grid(-column =&gt; 1, -row =&gt; scalar(@hgs) + 1);
	       $hg_list-&gt;insert(0, join(" ", $hosts-&gt;get));
	       
	       $Edit_hostgroups_add-&gt;destroy;
	     })-&gt;pack(-side =&gt; "left");
  $B-&gt;Button(-text =&gt; "Cancel", -command =&gt; sub {$Edit_hostgroups_add-&gt;destroy;})-&gt;pack(-side =&gt; "right");

}

############################################################
# Watches
############################################################
sub Edit_watches {
  $Edit_watches-&gt;destroy if Exists($Edit_watches);
  $Edit_watches = $main-&gt;Toplevel(-takefocus =&gt; 1, -title =&gt; 'Edit Watches');
  my %w = %watch; # Dup the watches hash so we can cancel
  my $H_holder;
  my $H = $Edit_watches-&gt;Scrolled (
				   'HList',
				   -scrollbars =&gt; 'se',
				   -itemtype   =&gt; 'text',
				   -separator  =&gt; '.',
				   -selectmode =&gt; 'single',
				   -height =&gt; 25,
				   -command =&gt; sub {
				     $_ = $$H_holder-&gt;info('anchor');
				     if($_ =~ /([^.]+)\.([^.]+)/){
				       &amp;Edit_watches_edit($$H_holder, \%w, $1, $2 );
				     }
				   },
				  );
  $Balloon-&gt;attach($H, -balloonmsg =&gt; 'Double-click to edit watch');
  $H_holder = \$H;
  $H-&gt;pack;

  my $w;
  foreach $w (sort keys %w ) {
    $H-&gt;add($w, -text =&gt; $w);
    map { $H-&gt;add($w . "." . $_, -text =&gt; $_); } sort(keys %{$w{$w}});
  }
  
  my $WB = $Edit_watches-&gt;Frame-&gt;pack;
  $WB-&gt;Button(-text =&gt; "Add\nService/Watch", -command =&gt; sub {
		my $hg = "";
		my $s = "";
		&amp;Edit_watches_edit($H, \%w, $hg, $s);
	       })-&gt;pack(-side =&gt; "left");
  $WB-&gt;Button(-text =&gt; "Delete\nService/Watch", -command =&gt; sub {
		$_ = $H-&gt;info('anchor');
		$H-&gt;delete('entry', $_);
		if($_ =~ /([^.]+)\.([^.]+)/){
		  delete $w{$1}-&gt;{$2};
		}
		else{
		  delete $w{$_};
		}
	      })-&gt;pack(-side =&gt; "left");

  my $B = $Edit_watches-&gt;Frame-&gt;pack;
  $B-&gt;Button(-text =&gt; "Okay", -command =&gt; sub {
	       %watch = %w;
	       $Edit_watches-&gt;destroy;
	     })-&gt;pack(-side =&gt; "left");
  $B-&gt;Button(-text =&gt; "Cancel", -command =&gt; sub {$Edit_watches-&gt;destroy;})-&gt;pack(-side =&gt; "right");
}

sub Edit_watches_edit {
  my ($Parent_H, $w, $hg, $s) = @_;
  
  my $w_hash = $w-&gt;{$hg}-&gt;{$s};
  $Edit_watches_edit-&gt;destroy if Exists($Edit_watches_edit);
  $Edit_watches_edit = $Edit_watches-&gt;Toplevel(-takefocus =&gt; 1, -title =&gt; 'Edit Watch');

  my $F = $Edit_watches_edit-&gt;Frame-&gt;pack;

  # host group
  $F-&gt;Label(-text =&gt; "Host Group")-&gt;pack; 
  my $Hostgroup = $F-&gt;BrowseEntry(
				  -width =&gt; 20,
				  -state =&gt; 'readonly',
				  -variable =&gt; \$hg,
				 )-&gt;pack;
  map { $Hostgroup-&gt;insert("end", $_); } sort(keys %groups);

  # service
  $F-&gt;Label(-text =&gt; "Service")-&gt;pack; 
  my $Service = $F-&gt;Entry(-width =&gt; 20, -textvariable =&gt; \$s)-&gt;pack;

  # description
  $F-&gt;Label(-text =&gt; "Description")-&gt;pack; 
  my $Description = $F-&gt;Entry(-width =&gt; 50)-&gt;pack; 
  $Description-&gt;insert( 0, $w_hash-&gt;{description} ) if $hg;

  # interval
  $F-&gt;Label(-text =&gt; "Interval")-&gt;pack;
  my $Interval= $F-&gt;Scale(-from =&gt; 0, -to =&gt; 60000, 
			  -orient =&gt; 'horizontal', 
			  -tickinterval =&gt; 20000, -length =&gt; 200,
			  -variable =&gt; \$w_hash-&gt;{interval}
			 )-&gt;pack;
  $Balloon-&gt;attach($Interval, -balloonmsg =&gt; 'Time in seconds between monitor runs');

  # monitor
  opendir(MOND, $globals{mondir}) 
    or $Edit_watches-&gt;Dialog(-title =&gt; 'Warning', -text =&gt; "Can not open mon directory " . $globals{mondir})-&gt;Show;
							   
  my @choices = sort( grep(/\.monitor$/, readdir(MOND) ) );
  closedir MOND;
  $F-&gt;Label(-text =&gt; "Monitor")-&gt;pack;
  my $Monitor = $F-&gt;BrowseEntry(
				-width =&gt; 50,
				-choices =&gt; \@choices, 
				-variable =&gt; \$w_hash-&gt;{monitor}
			       )-&gt;pack;
  $Balloon-&gt;attach($Monitor, -balloonmsg =&gt; 'Monitor script with arguments');

  # depend
  $F-&gt;Label(-text =&gt; "Dependencies")-&gt;pack;
  $F-&gt;Scrolled('Entry', -width =&gt; 50, -textvariable =&gt; \$w_hash-&gt;{depend})-&gt;pack;
  my $Depend_H = $F-&gt;Scrolled('HList', 
			      -scrollbars =&gt; "e",
			      -selectmode =&gt; 'single',
			      -width =&gt; 30,
			      -command =&gt; sub {
				$w_hash-&gt;{depend} = join(" ",  $w_hash-&gt;{depend}, $_[0])
				  unless $w_hash-&gt;{depend} =~ /\b$_[0]\b/;
			      },
			     )-&gt;pack;
  $Balloon-&gt;attach($Depend_H, -balloonmsg =&gt; 'Double-click to add dependencies');
  my ($x, $y);
  foreach $x ( sort keys %watch ){
    foreach $y ( sort keys %{$watch{$x}} ){
      $_ = $x . ":" . $y;
      $Depend_H-&gt;add($_, -text =&gt; $_);
    }
  }

  # periods
  $F-&gt;Label(-text =&gt; "Periods")-&gt;pack;
  my $Periods_H;
  $Periods_H = $F-&gt;Scrolled('HList', 
			    -scrollbars =&gt; "e",
			    -selectmode =&gt; 'single',
			    -width =&gt; 30,
			    -command =&gt; [\&amp;Edit_watches_edit_period, \$Periods_H, $hg, $s],
			   )-&gt;pack;  
  $Balloon-&gt;attach($Periods_H, -balloonmsg =&gt; 'Double-click to edit period');

  foreach ( sort keys %{$w_hash-&gt;{periods}} ){
    $Periods_H-&gt;add($_, -text =&gt; $_);
  }

  my $PB = $F-&gt;Frame-&gt;pack;
  $PB-&gt;Button(-text =&gt; "Add Period", -command =&gt; [\&amp;Edit_watches_edit_period, \$Periods_H, $hg, $s])-&gt;pack(-side =&gt; 'left');
  $PB-&gt;Button(-text =&gt; "Delete Period", -command =&gt; sub {
		if( $_ = $Periods_H-&gt;info('anchor') ){
		  delete $w_hash-&gt;{periods}-&gt;{$_};
		  $Periods_H-&gt;delete('entry',$_);
		}
	      })-&gt;pack(-side =&gt; 'right');
  
  my $B = $F-&gt;Frame-&gt;pack;
  $B-&gt;Button(-text =&gt; "Okay", -command =&gt; sub {
	       $w-&gt;{$hg}-&gt;{$s} = $w_hash;
	       $Parent_H-&gt;add("$hg", -text =&gt; $hg) unless $Parent_H-&gt;info('exists', "$hg");
	       $Parent_H-&gt;add("$hg.$s", -text =&gt; $s) unless $Parent_H-&gt;info('exists', "$hg.$s");
	       $Edit_watches_edit-&gt;destroy;
	     })-&gt;pack(-side =&gt; 'left');
  $B-&gt;Button(-text =&gt; "Cancel", -command =&gt; sub {$Edit_watches_edit-&gt;destroy;} )-&gt;pack(-side =&gt; 'right');

}

# If we change a hg above or a period here, we treat that as an implicit add 
# and don't delete the old one
sub Edit_watches_edit_period {
  my($Periods_H, $hg, $s, $p) = @_;

  $Edit_watches_edit_period-&gt;destroy if Exists($Edit_watches_edit_period);
  $Edit_watches_edit_period = $Edit_watches-&gt;Toplevel(-takefocus =&gt; 1, 
						      -title =&gt; "Edit Period for Hostgroup $hg, Service $s");
  my $F = $Edit_watches_edit_period-&gt;Frame-&gt;pack;

  # period
  $p = "" unless $p;
  my $p_orig = $p;
  $F-&gt;Label(-text =&gt; "Period")-&gt;pack; 
  my $Period = $F-&gt;Entry(-width =&gt; 50, -textvariable =&gt; \$p)-&gt;pack;
  $Balloon-&gt;attach($Period, -balloonmsg =&gt; 'Time period for alerts in perl Time::Period format');  

  my $F2 = $F-&gt;Frame-&gt;pack;

  # We need to dupe the subhash here so we can do a "cancel" or "okay"
  my %period_hash = $p ? %{$watch{$hg}-&gt;{$s}-&gt;{periods}-&gt;{$p}} : ();
  my $ph = \%period_hash;

  # numalerts
  # FIXME should be scale?
  $ph-&gt;{numalerts} = 0 unless $ph-&gt;{numalerts}; # FIXME is this a good default:
  $F2-&gt;Label(-text =&gt; "Num Alerts")-&gt;pack(-side =&gt; 'left'); 
  my $numalerts = $F2-&gt;Entry(-width =&gt; 3, -textvariable =&gt; \$ph-&gt;{numalerts})-&gt;pack(-side =&gt; 'left'); 
  $Balloon-&gt;attach($numalerts, -balloonmsg =&gt; 'Call no more than this number of alerts during a failure');  

  # compalerts
  $ph-&gt;{comp_alerts} = 0 unless $ph-&gt;{comp_alerts};
  $F2-&gt;Label(-text =&gt; "Comp Alerts")-&gt;pack(-side =&gt; 'left'); 
  my $compalerts = $F2-&gt;Checkbutton(-variable =&gt; \$ph-&gt;{compalerts})-&gt;pack(-side =&gt; 'left');
  $Balloon-&gt;attach($compalerts, -balloonmsg =&gt; 'Upalerts will only be called if a corresponding alert has been called');  

  # alertevery
  $ph-&gt;{alertevery} = "" unless $ph-&gt;{alertevery};
  $F-&gt;Label(-text =&gt; "Alertevery")-&gt;pack;
  my $alertevery = $F-&gt;Entry(-width =&gt; 10, -textvariable =&gt; \$ph-&gt;{alertevery})-&gt;pack;
  $Balloon-&gt;attach($alertevery, -balloonmsg =&gt; 'Alert once per this time period during a failure');  

  # alertafter
  $ph-&gt;{alertafter} = "" unless $ph-&gt;{alertafter};
  $F-&gt;Label(-text =&gt; "Alertafter")-&gt;pack;
  my $alertafter = $F-&gt;Entry(-width =&gt; 10, -textvariable =&gt; \$ph-&gt;{alertafter})-&gt;pack;
  $Balloon-&gt;attach($alertafter, -balloonmsg =&gt; 'Alert after this many failures or this many failures per time period');  

  # alerts
  $F-&gt;Label(-text =&gt; "Alerts")-&gt;pack;
  my $A = $F-&gt;Scrolled('Pane', -scrollbars =&gt; 'e', -gridded =&gt; 'y')-&gt;pack; # It would be nice to have this sunken,
  $Balloon-&gt;attach($A, -balloonmsg =&gt; 'Alert scripts with arguments');  
  my $i = 0;                                                               # but (relief =&gt; sunken) doesn't seem to work
  foreach ( @{$ph-&gt;{alerts}}){
    next unless defined $_;
    $A-&gt;Entry(-textvariable =&gt; \$_, -width =&gt; 50,)-&gt;grid(-row =&gt; $i);
    $i++;
  }

  my $F3 = $F-&gt;Frame-&gt;pack;
  $F3-&gt;Button(-text =&gt; 'Add Alert', -command =&gt; [\&amp;Edit_watches_edit_period_add_alert, $ph, $A, 'alerts'])-&gt;pack(-side =&gt; 'left');
  $F3-&gt;Button(-text =&gt; 'Delete Alert', -command =&gt; sub {
		my $alert_w = $Edit_watches_edit_period-&gt;focusCurrent;
		return unless Exists($alert_w);
		my $parentpath = $alert_w-&gt;parent-&gt;PathName;
		my $A_subwidgetpath = $A-&gt;Subwidget('scrolled')-&gt;PathName; # There seems to be an additional frame
		if( $parentpath =~ /^$A_subwidgetpath/ ){
		  $_ = $alert_w-&gt;cget('-textvariable');
		  undef $$_; 		  # Undef the variable; note that this does NOT delete it from the array
		  $alert_w-&gt;destroy;
		}

	     })-&gt;pack(-side =&gt; 'right');

  # upalerts
  $F-&gt;Label(-text =&gt; "Upalerts")-&gt;pack;
  my $U = $F-&gt;Scrolled('Pane', -scrollbars =&gt; 'e', -gridded =&gt; 'y')-&gt;pack;
  $Balloon-&gt;attach($U, -balloonmsg =&gt; 'Alert scripts with arguments');  
  $i = 0;
  foreach ( @{$ph-&gt;{upalerts}}){
    next unless defined $_;
    $U-&gt;Entry(-textvariable =&gt; \$_, -width =&gt; 50)-&gt;grid(-row =&gt; $i);
    $i++;
  }
  my $F4 = $F-&gt;Frame-&gt;pack;
  $F4-&gt;Button(-text =&gt; 'Add Upalert', -command =&gt; [\&amp;Edit_watches_edit_period_add_alert, $ph, $U, 'upalerts'])-&gt;pack(-side =&gt; 'left');
  $F4-&gt;Button(-text =&gt; 'Delete Upalert', -command =&gt; sub {
		my $upalert_w = $Edit_watches_edit_period-&gt;focusCurrent;
		return unless Exists($upalert_w);
		my $parentpath = $upalert_w-&gt;parent-&gt;PathName;
		my $U_subwidgetpath = $U-&gt;Subwidget('scrolled')-&gt;PathName; # There seems to be an additional frame
		if( $parentpath =~ /^$U_subwidgetpath/ ){
		  $_ = $upalert_w-&gt;cget('-textvariable');
		  undef $$_; 		  # Undef the variable; note that this does NOT delete it from the array
		  $upalert_w-&gt;destroy;
		}

	     })-&gt;pack(-side =&gt; 'right');

  # okay and cancel buttons
  my $B = $Edit_watches_edit_period-&gt;Frame-&gt;pack;
  $B-&gt;Button(-text =&gt; 'Okay', 
	     -command =&gt; sub{
	       delete $watch{$hg}-&gt;{$s}-&gt;{periods}-&gt;{$p};
	       $watch{$hg}-&gt;{$s}-&gt;{periods}-&gt;{$p} = $ph;
	       $$Periods_H-&gt;delete('entry', $p_orig) 
		 if $$Periods_H-&gt;info('exists', $p_orig);      # FIXME this inserts it at the end, and we want the same position! 
	       $$Periods_H-&gt;add($p, -text =&gt; $p);              # BUT there seems to be no way to get that from HList :(
	       $Edit_watches_edit_period-&gt;destroy;
	     }, 
	    )-&gt;pack(-side =&gt; 'left');
  $B-&gt;Button(-text =&gt; 'Cancel', -command =&gt; sub{$Edit_watches_edit_period-&gt;destroy;} )-&gt;pack(-side =&gt; 'right');
  
}

sub Edit_watches_edit_period_add_alert {
  my($ph, $pane, $type) = @_;

  $Edit_watches_edit_period_add_alert-&gt;destroy if Exists($Edit_watches_edit_period_add_alert);
  $Edit_watches_edit_period_add_alert = 
    $Edit_watches_edit_period-&gt;Toplevel(-takefocus =&gt; 1, 
					-title =&gt; "Add Alert");
  
  # Get the list of alert scripts for use in alerts/upalerts
  opendir(ALERTD, $globals{alertdir})
    or $Edit_watches_edit_period_add_alert-&gt;Dialog(-title =&gt; 'Warning', 
						   -text =&gt; "Could not open directory " . $globals{alertdir})-&gt;Show;
  my @alertscripts = sort( grep(/\.alert$/, readdir(ALERTD)) );
  closedir ALERTD;

  my $var = "";
  $Edit_watches_edit_period_add_alert-&gt;BrowseEntry(-variable =&gt; \$var, -width =&gt; 50,
						   -choices =&gt; \@alertscripts)-&gt;pack;
  $Edit_watches_edit_period_add_alert-&gt;Button(-text =&gt; 'Cancel', 
					      -command =&gt; sub {$Edit_watches_edit_period_add_alert-&gt;destroy;})-&gt;pack(-side =&gt; 'left');

  $Edit_watches_edit_period_add_alert-&gt;Button(
					      -text =&gt; 'Okay', 
					      -command =&gt; sub {
						# Add the alert/upalert to our hash 
						$pane-&gt;Entry(-textvariable =&gt;\$var, -width =&gt; 50)-&gt;grid(-row =&gt; scalar @{$ph-&gt;{$type}});
						push @{$ph-&gt;{$type}}, $var;

						$Edit_watches_edit_period_add_alert-&gt;destroy;
					      }
					     )-&gt;pack(-side =&gt; 'left');
}



# ========================================================
# ========================================================
# Write the config file
sub write_cf {
  my ($CF) = @_;

  my %globals = %globals; # Make a local copy so we can do some kludgery
  $globals{randstart} .= "s";


  # FIXME should flock this
  open(CF, "&gt;$CF") or die "Could not open $CF for writing";

  # Write out the global vars
  print CF "#####################################\n";
  print CF "# Global Options\n#\n";
  my $k;
  foreach $k ( sort keys %globals ){
    printf CF ("%-10s %s %s\n", $k, "=", $globals{$k});
  }

  print CF "\n\n";

  # Write out the hostgroups
  print CF "#####################################\n";
  print CF "# Host Groups\n#\n";
  foreach $k ( sort keys %groups ){
    print CF join(" ", "hostgroup", $k, @{$groups{$k}}), "\n";
  }
 
  # Write out the watch definitions
  # This gets a little more complicated
  my($w, $s, $s_var, $p, $p_var, @a_types, $a_t, $a);
  foreach $w ( sort keys %watch ){
    print CF "\n#####################################\n";
    print CF "watch $w\n";
    foreach $s ( sort keys %{$watch{$w}} ){
      print CF "\t", "service ", $s, "\n";
      foreach $s_var ( sort keys %{$watch{$w}{$s}} ){
	next if $s_var =~ /^_/
	  or ref $watch{$w}{$s}{$s_var}
	    or $s_var eq "service";

	# KLUDGEY -- fix inconsistent interval variables
	next if $s_var eq "randskew" and $watch{$w}{$s}{$s_var} eq "0";
	$watch{$w}{$s}{$s_var} .= "s" if $s_var =~ /^interval|randskew$/; 

	print CF "\t\t", $s_var, " ", $watch{$w}{$s}{$s_var}, "\n";
      }
      # Now we do the periods
      foreach $p (sort keys %{$watch{$w}{$s}{periods}}){
	@a_types = ();
	print CF "\t\t", "period", " ", $p, "\n";
	foreach $p_var ( sort keys %{$watch{$w}{$s}{periods}{$p}} ){

	  next if $p_var =~ /^_/ or $p_var eq "period";

	  # KLUDGEY -- fix inconsistent period variables
	  next if $p_var eq "alertevery" and $watch{$w}{$s}{periods}{$p}{$p_var} eq "0";

	  if( ref $watch{$w}{$s}{periods}{$p}{$p_var} ){
	    push @a_types, $p_var;
	    next;
	  }
   
	  print CF "\t\t\t", $p_var, " ", $watch{$w}{$s}{periods}{$p}{$p_var}, "\n";
	}

	foreach $a_t (sort @a_types){
	  foreach $a ( @{$watch{$w}{$s}{periods}{$p}{$a_t}} ){
	    $a_t =~ s/s$//; # strip plural
	    print CF "\t\t\t", $a_t, " ", $a, "\n";
	  }
	}
      }
    }
  }


  close CF;
}


# ========================================================
# ========================================================
# Ripped from mon itself

#
# parse configuration file
#
# build the following data structures:
#
# %group
#       each element of %group is an array of hostnames
#       group records are terminated by a blank line in the
#       configuration file
# %watch{"group"}-&gt;{"service"}-&gt;{"variable"} = value
#
sub read_cf {
  # KLUDGE: make "strict" shut up
  my(%opt, %alias, $PWD, $STAT_UNTESTED);

    my ($CF) = @_;
    my ($var, $watchgroup, $ingroup, $curgroup, $inwatch,
	$servnum, $args, $hosts, %disabled, $h, $i,
	$aliasReading, $aliasGroup);
    my ($sref, $pref);
    my ($service, $period);

    #
    # parse configuration file
    #
    if ($opt{"M"} || $CF =~ /\.m4$/) {
	open (CFG, "m4 $CF |") ||
	    die "could not open m4 pipe of cf file: $CF: $!\n";
    } else {
	open (CFG, $CF) ||
	    die "could not open cf file: $CF: $!\n";
    }

    $servnum = 0;
    %alias = ();

    my $DEP_BEHAVIOR = "a";

    my $incomplete_line = 0;
    my $linepart = "";
    my $l = "";
    my $acc_line = "";

    for (;;) {
    	last if (!defined ($linepart = &lt;CFG&gt;));
	next if $linepart =~ /^\s*#/;

	#
	# accumulate multi-line lines (ones which are \-escaped)
	#
	if ($incomplete_line) { $linepart =~ s/^\s*//; }

	if ($linepart =~ /^(.*)\\\s*$/)
	{
	    $incomplete_line = 1;
	    $acc_line .= $1;
	    chomp $acc_line;
	    next;
	}

	else
	{
	    $acc_line .= $linepart;
	}

	$l = $acc_line;
	$acc_line = "";

	chomp $l;
	$l =~ s/^\s*//;
	$l =~ s/\s*$//;

	$incomplete_line = 0;
	$linepart = "";

	#
	# variables than can be overriden by the command line
	#
	if ($l =~ /^(\w+) \s* = \s* (.*) \s*$/ix) {
	    if ($1 eq "alertdir") {
		$CF{"ALERTDIR"} = $2;
		next;
	    } elsif ($1 eq "basedir") {
		$CF{"BASEDIR"} = $2;
		$CF{"BASEDIR"} = "$PWD/$CF{BASEDIR}" if ($CF{"BASEDIR"} !~ m{^/});
		$CF{"BASEDIR"} =~ s{/$}{};
		next;
	    } elsif ($1 eq "cfbasedir") {
		$CF{"CFBASEDIR"} = $2;
		$CF{"CFBASEDIR"} = "$PWD/$CF{CFBASEDIR}" if ($CF{"CFBASEDIR"} !~ m{^/});
		$CF{"CFBASEDIR"} =~ s{/$}{};
		next;
	    } elsif ($1 eq "mondir") {
		$CF{"SCRIPTDIR"} = $2;
		next;
	    } elsif ($1 eq "logdir") {
		$CF{"LOGDIR"} = $2;
		next;
	    } elsif ($1 eq "histlength") {
		$CF{"MAX_KEEP"} = $2;
		next;
	    } elsif ($1 eq "serverport") {
		$CF{"SERVPORT"} = $2;
		next;
	    } elsif ($1 eq "trapport") {
		$CF{"TRAPPORT"} = $2;
		next;
	    } elsif ($1 eq "serverbind") {
	    	$CF{"SERVERBIND"} = $2;
		next;
	    } elsif ($1 eq "trapbind") {
	    	$CF{"TRAPBIND"} = $2;
		next;
	    } elsif ($1 eq "pidfile") {
		$CF{"PIDFILE"} = $2;
		next;
	    } elsif ($1 eq "randstart") {
		$CF{"RANDSTART"} = dhmstos($2);
		die "cf error: bad syntax, line $.\n"
		    if (!defined ($CF{"RANDSTART"}));
		next;
	    } elsif ($1 eq "maxprocs") {
		$CF{"MAXPROCS"} = $2;
		next;
	    } elsif ($1 eq "statedir") {
		$CF{"STATEDIR"} = $2;
		next;
	    } elsif ($1 eq "authfile") {
		$CF{"AUTHFILE"} = $2;
		next;
	    } elsif ($1 eq "authtype") {
		$CF{"AUTHTYPE"} = $2;
		next;
	    } elsif ($1 eq "userfile") {
		$CF{"USERFILE"} = $2;
		next;
	    } elsif ($1 eq "ocfile") {
		$CF{"OCFILE"} = $2;
		next;
	    } elsif ($1 eq "historicfile") {
	    	$CF{"HISTORICFILE"} = $2;
		next;
	    } elsif ($1 eq "historictime") {
	    	$CF{"HISTORICTIME"} = dhmstos($2);
		die "cf error: bad syntax, line $.\n"
		    if (!defined $CF{"HISTORICTIME"});
		next;
	    } elsif ($1 eq "cltimeout") {
		$CF{"CLIENT_TIMEOUT"} = dhmstos($2);
		die "cf error: bad syntax, line $.\n"
		    if (!defined ($CF{"CLIENT_TIMEOUT"}));
		next;
	    } elsif ($1 eq "use snmp") {
		$CF{"SNMP"} = 1;
		eval "use SNMP";
		die "could not use SNMP: $@\n" if ($@ ne "");
		next;
	    } elsif ($1 eq "dtlogfile") {
		$CF{"DTLOGFILE"} = $2;
		next;
	    } elsif ($1 eq "dtlogging") {
		$CF{"DTLOGGING"} = 0;
		if ($2 == 1 || $2 eq "yes" || $2 eq "true") {
		    $CF{"DTLOGGING"} = 1;
		}
		next;
	    } elsif ($1 eq "snmpport") {
		$CF{"SNMPPORT"} = $2;
		next;
	    } elsif ($1 eq "dtlogfile") {
		$CF{"DTLOGFILE"} = $2;
		next;
	    } elsif ($1 eq "dep_recur_limit") {
	    	$CF{"DEP_RECUR_LIMIT"} = $2;
		next;
	    } elsif ($1 eq "dep_behavior") {
		if ($2 ne "m" &amp;&amp; $2 ne "a") {
		    die "cf error: unknown dependency behavior, line $.\n";
		}
		$DEP_BEHAVIOR = $2;
		next;
	    } else {
		die "cf error: unknown variable, line $.\n";
	    }
	}

	#
	# end of record
	#
	if ($l eq "")
	{
	    $ingroup    = 0;
	    $curgroup   = "";
	    $inwatch    = 0;
	    $watchgroup = "";
	    $servnum = 0;
	    $period = 0;
	    undef $aliasReading;
	    next;
	}

	#
	# group record
	#
	if ($l =~ /^hostgroup\s+([a-zA-Z0-9_.-]+)\s*(.*)/)
	{
	    $curgroup = $1;
	    $hosts = $2;
	    %disabled = ();
	    foreach $h (grep (/^\*/, @{$groups{$curgroup}}))
	    {
		$h =~ s/^\*//;
		$disabled{$h} = 1;
	    }
	    @{$groups{$curgroup}} = split(/\s+/, $hosts);
	    #
	    # keep hosts which were previously disabled
	    #
	    for ($i=0;$i&lt;@{$groups{$curgroup}};$i++)
	    {
		$groups{$curgroup}[$i] = "*$groups{$curgroup}[$i]"
		    if ($disabled{$groups{$curgroup}[$i]});
	    }
	    $ingroup = 1;
	    next;
	}

	elsif ($ingroup)
	{
	    push (@{$groups{$curgroup}}, split(/\s+/, $l));
	    for ($i=0;$i&lt;@{$groups{$curgroup}};$i++)
	    {
		$groups{$curgroup}[$i] = "*$groups{$curgroup}[$i]"
		    if ($disabled{$groups{$curgroup}[$i]});
	    }
	    next;
	}

	#
	# alias record
	#
	if ($l =~ /^alias\s+([a-zA-Z0-9_.-]+)\s*$/)
	{
	    $aliasReading = 1;
	    $aliasGroup = $1;
	    next;
	}
	
	elsif ($aliasReading)
	{
	    if ($l =~ /\A(.*)\Z/)
	    {
		push (@{$alias{$aliasGroup}}, $1);
		next;
	    }
	}

	#
	# watch record
	#
	if ($l =~ /^watch\s+([a-zA-Z0-9_.-]+)\s*/)
	{
	    $watchgroup = $1;
	    if (!defined ($groups{$watchgroup}))
	    {
	    	@{$groups{$watchgroup}} = ($watchgroup);
	    }
	    die "cf error: watch already defined, line $.\n"
	    	if ($watch{$watchgroup});
	    $ingroup    = 0;
	    $curgroup   = "";
	    $service = "";
	    $period = 0;
	    $inwatch = 1;
	    next;
	}
	
	#
	# from here on we are in a watch record
	#
	next if (!$inwatch);

	#
	# env variables
	#
	if ($l =~ /^([A-Z_][A-Z0-9_]*)=(.*)/)
	{
	    die "cf error: environment variable defined without a service, line $.\n"
		if ($service eq "");
	    $watch{$watchgroup}-&gt;{$service}-&gt;{"ENV"}-&gt;{$1} = $2;
	    next;
	}

	#
	# non-env variables
	#
	else
	{
	    $l =~ /^(\w+)\s*(.*)$/;
	    $var = $1;
	    $args = $2;
	}

	#
	# service entry
	#
	if ($var eq "service")
	{
	    $service = $args;
	    die "cf error: invalid service tag, line $.\n"
		if ($service !~ /^[a-zA-Z0-9_.-]+$/);
	    $period = 0;
	    $sref = \%{$watch{$watchgroup}-&gt;{$service}};
	    $sref-&gt;{"service"} = $args;
	    $sref-&gt;{"interval"} = undef;
	    $sref-&gt;{"randskew"} = 0;
	    $sref-&gt;{"dep_behavior"} = $DEP_BEHAVIOR;
	    $sref-&gt;{"_op_status"} = $STAT_UNTESTED;
	    $sref-&gt;{"_last_op_status"} = $STAT_UNTESTED;
	    $sref-&gt;{"_ack"} = 0;
	    $sref-&gt;{"_ack_comment"} = '';
	    $sref-&gt;{"_consec_failures"} = 0;
	    $sref-&gt;{"_failure_count"} = 0 if (!defined($sref-&gt;{"_failure_count"}));
	    $sref-&gt;{"_start_of_monitor"} = time if (!defined($sref-&gt;{"_start_of_monitor"}));
	    $sref-&gt;{"_alert_count"} = 0 if (!defined($sref-&gt;{"_alert_count"}));
	    $sref-&gt;{"_last_failure"} = 0 if (!defined($sref-&gt;{"_last_failure"}));
	    $sref-&gt;{"_last_success"} = 0 if (!defined($sref-&gt;{"_last_success"}));
	    $sref-&gt;{"_last_trap"} = 0 if (!defined($sref-&gt;{"_last_trap"}));
	    $sref-&gt;{"_exitval"} = "undef" if (!defined($sref-&gt;{"_exitval"}));
	    $sref-&gt;{"_last_check"} = undef;
	    $sref-&gt;{"_depend_status"} = undef;
	    next;
	}

	if ($service eq "")
	{
	    die "cf error: need to specify service in watch record, line $.\n";
	}


	#
	# period definition
	#
	if ($var eq "period")
	{
	    $period = 1;

	    my $periodstr;

	    if ($args =~ /^([a-z_]\w*) \s* : \s* (.*)$/ix)
	    {
		$periodstr = $1;
		$args = $2;
	    }
	    
	    else
	    {
		$periodstr = $args;
	    }

	    $pref = \%{$sref-&gt;{"periods"}-&gt;{$periodstr}};

	    if (inPeriod (time, $args) == -1)
	    {
		die "cf error: malformed period, line $.\n";
	    }

	    $pref-&gt;{"period"} = $args;
	    $pref-&gt;{"alertevery"} = 0;
	    $pref-&gt;{"numalerts"} = 0;
	    $pref-&gt;{"_alert_sent"} = 0;
	    @{$pref-&gt;{"alerts"}} = ();
	    @{$pref-&gt;{"upalerts"}} = ();
	    @{$pref-&gt;{"startupalerts"}} = ();
	    next;
	}

	#
	# alert
	#
	if ($var eq "alert" &amp;&amp; !$period)
	{
	    die "cf error: need to specify a period for alert, line $.\n";
	}

	elsif ($var eq "upalert" &amp;&amp; !$period)
	{
	    die "cf error: need to specify a period for upalert, line $.\n";
	}
	
	elsif ($var eq "alertevery" &amp;&amp; !$period)
	{
	    die "cf error: need to specify a period for alertevery, line $.\n";
	}

	elsif ($var eq "alertafter" &amp;&amp; !$period)
	{
	    die "cf error: need to specify a period for alertafter, line $.\n";
	}

	#
	# for each service there can be one or more alert periods
	# this is stored as an array of hashes named
	#     %{$watch{$watchgroup}-&gt;{$service}-&gt;{"periods"}}
	# each index for this hash is something like "wd {Mon-Fri} hr {7am-11pm}"
	# the value of the hash is an array containing the list of alert commands
	# and arguments
	#
	if ($var eq "alert")
	{
	    push @{$pref-&gt;{"alerts"}}, $args;
	}

	elsif ($var eq "upalert")
	{
	    $sref-&gt;{"_upalert"} = 1;
	    push @{$pref-&gt;{"upalerts"}}, $args;
	}
	
	elsif ($var eq "startupalert")
	{
	    push @{$pref-&gt;{"startupalerts"}}, $args;
	}

	#
	# non-alert variables
	#
	else
	{
	    if ($var eq "interval")
	    {
		$args = dhmstos ($args) ||
		    die "cf error: invalid time interval, line $.\n";
	    }

	    elsif ($var eq "traptimeout")
	    {
		$args = dhmstos ($args) ||
		    die "cf error: invalid waitfortrap interval, line $.\n";
		$sref-&gt;{"_trap_timer"} = $args;
	    }

	    elsif ($var eq "trapduration")
	    {
		$args = dhmstos ($args) ||
		    die "cf error: invalid trapduration interval, line $.\n";
	    }
	    
	    elsif ($var eq "randskew")
	    {
		$args = dhmstos ($args) ||
		    die "cf error: invalid random skew time, line $.\n";
	    }
	    
	    elsif ($var eq "alertevery")
	    {
		my $summary_flag;
		if ($args =~ /(\S+)(\s+)summary(\s*)$/i)
		{
		    $summary_flag = 1;
		    $args = $1;
		}
		
		else
		{
		    $summary_flag = 0;
		}

		$args = dhmstos ($args) ||
		    die "cf error: invalid time interval, line $.\n";
		$pref-&gt;{"alertevery"} = $args;
		$pref-&gt;{"_alertsum"} = $summary_flag;
		next;

	    }
	    
	    elsif ($var eq "alertafter")
	    {
		my ($p1, $p2);

		if ($args =~ /^(\d+)$/)
		{
		    $p1 = $1;
		    $pref-&gt;{"alertafter_consec"} = $p1;
		}
		
		elsif ($args =~ /(\d+)\s+(\d+[hms])$/)
		{
		    ($p1, $p2) = ($1, $2);
		    if (($p1 - 1) * $sref-&gt;{"interval"} &gt;= dhmstos($p2))
		    {
			die "cf error:  interval &amp; alertafter not sensible.\n" .
			    "No alerts can be generated with those parameters, line $.\n";
		    }
		    $pref-&gt;{"alertafter"} = $p1;
		    $pref-&gt;{"alertafterival"} = dhmstos ($p2);

		    $pref-&gt;{"_1stfailtime"} = 0;
		    $pref-&gt;{"_failcount"} = 0;
		}

		else
		{
		    die "cf error: invalid interval specification, line $.\n";
		}
	    }
	    
	    elsif ($var eq "upalertafter")
	    {
		$args = dhmstos ($args) ||
		    die "cf error: invalid upalertafter specification, line $.\n";
	    }
	    
	    elsif ($var eq "numalerts")
	    {
		die "cf error: non-numeric arg, line $.\n" if ($args !~ /^\d+$/);
	    	$pref-&gt;{"numalerts"} = $args;
		next;
	    }

	    elsif ($var eq "comp_alerts")
	    {
	    	$pref-&gt;{"comp_alerts"} = 1;
		next;
	    }
	    
	    elsif ($var eq "dep_behavior")
	    {
		if ($args ne "m" &amp;&amp; $args ne "a")
		{
		    die "cf error: unknown dependency behavior, line $.\n";
		}
	    }

	    $sref-&gt;{$var} = $args;
	}

	next;
    }

    close (CFG);

    1;
}


#
# convert a string like "20m" into seconds
#
sub dhmstos {
    my ($str) = @_;
    my ($s);

    if ($str =~ /^\s*(\d+(?:\.\d+)?)([dhms])\s*$/i) {
	if ($2 eq "m") {
	    $s = $1 * 60;
	} elsif ($2 eq "h") {
	    $s = $1 * 60 * 60;
	} elsif ($2 eq "d") {
	    $s = $1 * 60 * 60 * 24;
	} else {
	    $s = $1;
	}
    } else {
    	return undef;
    }
    $s;
}


</pre></body></html>