#!/usr/local/bin/perl

##################################################
#
# n_httpd
#
#      Version 1.4
#      Last revised on August 30, 1998.
#
# Author: Patrick Ryan <pgryan@geocities.com>
#
##################################################

##################################################
#
# Usage:
#
#      n_httpd
#
# Synopsis:
#
#      This is a simple http server that accompanies
#      NodeWatch.  Its purpose is to retrieve the
#      current node status for nodes that are
#      being monitored by NodeWatch and display
#      the selected nodes in a report via a
#      http connection.  Nodes of various states
#      (up, down, or unknown) are selected by
#      URL.
#
#      This code is based on the server sample
#      given in "Programming Perl" by Larry Wall
#      and Randal L. Schwartz, March 1992 edition,
#      ISBN 0-937175-64-1, published by O'Reilly
#      and Associated, Inc., page 344.
#
##################################################

##################################################
# User configurable variables.

  # Access control.  If the user isn't on the network $network, then the
  # connection is terminated.
  $network = "140.107.0.0";

  # What port should the server listen on?
  $port = 80;

  # The location of the lockfile.
  $lockfile = "/home/netops/etc/n_httpd.lockfile";

  # The PS command.  It is used to see if the PID listed in $lockfile
  # corresponds to a n_httpd process.
  $ps = "/usr/bin/ps -A -o pid,args";

  # The location of the NodeWatch lockfile (it contains the process id).
  $nodewatch_pid_file = "/home/netops/etc/nodewatch.lockfile";

  # The n_httpd user under which the process will run
  $n_httpd_user = "netops";

  # The signal to send to the NodeWatch process.
  $dump_sig = "USR1";

  # The location of the node stats dump file.
  $dump_file = "/home/netops/etc/nodewatch.node.stats";

  # The maximum amount of time to wait from when the signal is sent to
  # when $dump_file is actually dumped to.  If that time elapses, then the
  # script terminates with an error.
  $dump_wait_timeout = 30;

  # The maximum amount of time to wait for the dump file to change.  If it
  # hasn't changed in the time between when it started dumping and when
  # the time period elapses, then it is assumed it is done dumping.
  $dump_change_secs = 5;

  # Syslog configuration stuff.

    # Should the process ID be displayed in the syslog?
    $syslog_show_pid = 0;

    # What should the process be called when making entries to the syslog?
    $syslog_p_name = "n_httpd";

    # What classification should the process be given?
    $syslog_p_type = "local1";

    # What classification should messages (except &error) be given?
    $syslog_m_type = "notice";

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

##################################################
# What the functions do.
#
# do_lockfile          = Tries to ensure only one
#                        instance is running.
# initiate_server      = Sets up the server to accept
#                        connections.
# get_status_report    = Retrieves the report.
# fresh_dump           = Ensures a fresh copy of the
#                        node status dump from
#                        NodeWatch.
# get_status_from_dump = Reads the node status dump
#                        file and builds an array
#                        that contains the stats
#                        for down nodes.
# prep_node_data       = Does three things.  1)
#                        Removes nodes from %status
#                        that don't belong in the
#                        report.  2) Adds nodes
#                        to one of three arrays
#                        (@up, @down, or @unknown)
#                        based on their states.
#                        3) Set the value of the
#                        remaining items in %status
#                        to their timestamps only.
# http_usage           = Generate the usage information.
# gen_html_header      = Generates the HTML header.
# gen_report           = Builds an HTML report
#                        of down node status.
# gen_html_footer      = Generates the HTMl footer.
# to_syslog            = Sends messages to the syslog.
# error                = Sends messages to the syslog
#                        with a message type of 'err'.
#
##################################################

##################################################
# The main program.

use Socket;

require "ctime.pl";

$SIG{CHLD} = sub { wait; };

# Note that we are starting up in the syslog.
to_syslog("Starting up.");

initiate_server();

# Become user.
changeid();

# Try to make sure another n_httpd process isn't running.
do_lockfile();

# Okay, we're ready to accept connections and the S socket has been set up.

$net_addr = pack("C4", split(/\./, $network));

# The HTTP 1.0 specification requries the end of line terminator be CRLF.
$eol = pack("cc", 13, 10);

# Loop forever.
while () {
  # The accept function waits for a connection.
  if ( ! ($addr = accept(NS, S)) ) {
    error("Can't accept a connection.  $!.");
    exit(1);
  } else {
    # The network address is stored in bytes 5 - 8.
    $peer_name = gethostbyaddr(substr($addr, 4, 4), AF_INET);
    if ($peer_name eq "") {
      $peer_name = join(".", unpack("C4", substr($addr, 4, 4)));
    }

    # If the client is not on the network $net_addr then dump the connection.
    if (($net_addr & substr($addr, 4, 4)) ne $net_addr) {
      shutdown(NS, 2);
      to_syslog("Unauthorized connection attempt from $peer_name.");
      next;
    }
  }

  # Okay, we made a connection.  Have the process fork so that the
  # original process can continue to listen to the server port while
  # this child process handles the client request.  The fork function
  # will return 0 if it could spawn off as a child process.  So, anything
  # inside of this if statement is being executed by the child process
  # and not by the parent.
  if ( fork() == 0 ) {
     # Foreach line of whatever the client sends us.
     while (<NS>) {
       if (/^GET\s+(.+)\s+\S+/i) {
         # Store the name of the document requested in $document.
         $document = $1;

         # Okay, we got a request for a document.  Make sure the client
         # sends the whole request.
         while (<NS>) {
           if (/^\s+$/) {
             last;
           }
         }

         # Make sure the document requested is valid.
         if ( ($document !~ /up/i) && ($document !~ /down/i) &&
              ($document !~ /unknown/i) ) {
           print NS 'HTTP/1.0 200 Document Follows' . $eol;
           print NS 'Content-type: text/html' . $eol . $eol;
           print NS gen_html_header();
           print NS http_usage();
           print NS gen_html_footer();

           close(NS);
           exit(0);
         }

         # Sends the response and the document to the client.
         print NS 'HTTP/1.0 200 Document Follows' . $eol;
         print NS 'Content-type: text/html' . $eol . $eol;
         print NS gen_html_header();
         print NS get_status_report();
         print NS gen_html_footer();

         # We've done our job, kill the connection.
         close(NS);
       }
     }

     # The child process has fulfilled its purpose, so now we can kill
     # it.  Note: The parent is a seperate process and will continue to
     # run.
     exit(0);
  }

  close(NS);
}

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

##################################################
# Function definitions, nothing else below this point.

sub changeid {
  # Give up root identity and become the $n_httpd_user user
  # Depends on $n_httpd_user and error().

  my(@pw);

  @pw = (getpwnam($n_httpd_user))[2, 3];

  if (@pw != 2) {
    error("Cannot find user $n_httpd_user.\n");
    exit(1);
  }

  # Set real & effective uids and gids.  Note that this only
  # sets the primary group from the password file.
  ($(, $)) = ($pw[1], $pw[1]);
  ($<, $>) = ($pw[0], $pw[0]);
}

sub do_lockfile {
  # Tries to make sure another n_httpd process isn't already running.
  # If a lockfile exists and the pid inside matches the pid of a n_httpd
  # process currently running, then it kills this process with an error
  # message.  If not, it writes its pid to the lock file and returns.
  # Depends on $ps, $lockfile, $port, and error().

  my($pid, @ps, $process);

  # First of all, does $lockfile exist?  If not, dump the PID into it
  # and bail...
  if ( ! (-e $lockfile) ) {
    if ( ! open(FILE, ">$lockfile") ) {
      error("Couldn't open $lockfile.  Aborting.");
      exit(1);
    }
    printf(FILE "%d\n", $$);
    close(FILE);
    return;
  }

  # Okay, $lockfile exists.  Let's try to get a pid out of it.
  if ( ! open(FILE, $lockfile) ) {
    error("The lock file ($lockfile) can't be opened.  Aborting.");
    exit(1);
  }
  $pid = <FILE>;
  close(FILE);

  # Remove the newline from $pid, if there is one.
  chop($pid) if (substr($pid, -1, 1) eq "\n");

  # Okay, we have a pid.  Is there a n_http_d process associated with it?

  @ps = `$ps`;

  foreach $process (@ps) {
    if ($process =~ /^\s+$pid\s+.*n_httpd/) {
      error("Only one instance of n_httpd may run at a time.  Aborting.");
      exit(1);
    }
  }

  # Cool, there isn't a n_httpd process running with the pid listed in the
  # lockfile.  Let's write our pid to the lockfile and be done with this
  # business.

  if ( ! open(FILE, ">$lockfile") ) {
    error("The lock file ($lockfile) can't be opened.  Aborting.");
    exit(1);
  }
  printf(FILE "%d\n", $$);
  close(FILE);
}

sub initiate_server {
  # Sets up the server to accept connections.
  # Depends on $port and error(), AF_INET, and SOCK_STREAM.
  # Sets $peer_name and the socket S.

  my($sockaddr, $proto, $this);

  $sockaddr = 'S n a4 x8';
  $proto = (getprotobyname('tcp'))[2];

  # If the port is specified as a service and not by number, set it to the
  # corresponding number for that service.
  if ($port !~ /^\d+$/) {
    $port = (getservbyport($port, 'tcp'))[2];
  }

  $this = pack($sockaddr, AF_INET, $port, "\0\0\0\0");

  select(NS);
  $| = 1;
  select(STDOUT);

  if ( ! socket(S, AF_INET, SOCK_STREAM, $proto) ) {
    error("The socket couldn't be created.  $!.");
    exit(1);
  }
  if ( ! bind(S, $this) ) {
    error("The socket couldn't be bound to port $port.  $!.");
    exit(1);
  }
  if ( ! listen(S, 5) ) {
    error("Can't listen for incoming connection.  $!.");
    exit(1);
  }

  select(S);
  $| = 1;
  select(STDOUT);
}

sub get_status_report {
  # Retrieves the stats from a fresh node stats dump file and puts them
  # in a hash called %stats.  The non-down nodes are stripped from the hash
  # and the the value of each element of the hash is set to the time the
  # node went down.  Then, an HTML report is prepared for the down nodes
  # and then returned.
  # Depends on fresh_dump(), get_stats_from_dump(), fis_stats_hash(),
  # gen_report(), and to_syslog().

  local(%stats, @up, @down, @unknown, $selection);
  my($html, $error);

  # fresh_dump() will make sure there is a fresh stats file to work with.
  # If it isn't fresh, then fresh_dump() will send the NodeWatch process
  # the dump signal and wait until it finishes dumping a fresh stats dump.
  # The fresh_dump() function will return non-null if there was an error.
  if ( ($error = fresh_dump()) ne "") {
    return($error);
  }

  # Okay, now we're gauranteed a fresh dump file.  Let's open it up
  # get the stats.  get_stats_from_dump() sticks the stats in %status
  # for down nodes.
  if ( ($error = get_stats_from_dump()) ne "" ) {
    return($error);
  }

  # Now let's prepare the node data.  First, remove entries for nodes
  # that don't belong in the report from %status.  Second, add nodes
  # to one of the three state arrays (@up, @down, or @unknown) based
  # on their states.  Third, set the value of the remaining items in
  # %status to just their timestamps only.
  prep_node_data();

  # Generate the report to display the entries of %status in HTML.
  $html = gen_report();

  # Log the fact that we're returning the status report to $peer_name.
  # $selection is set by gen_report().
  to_syslog("Sending report to $peer_name for nodes that are $selection");

  return($html);
}

sub fresh_dump {
  # Makes sure there is a fresh node stats dump.  If it isn't fresh, then
  # it will send NodeWatch the dump signal (probably USR1, it's named in
  # $nodewatch_pid_file).  It will wait up to $dump_wait_timeout seconds
  # for NodeWatch to start the dump and will wait for NodeWatch to finish
  # dumping.  It will return null if sucessful and non-null if not.  The
  # error message will be returned if there was an error.
  # Depends on $nodewatch_pid_file, $dump_file, $dump_sig, $dump_wait_timeout,
  # and $dump_change_secs.

  my($pid, $f_size, $i, $dump_file_time_before);

  # Is it the time the dump file was changed the same as the current time?
  # In other words, is this a fresh dump?  If so, then forget sending the
  # signal, there's no need to update the dump file if it's already
  # up to date.  This test will fail and the subroutine will go on if
  # $dump_file doesn't exit.
  if ((stat($dump_file))[9] == time) {
    return;
  }

  # If the process ID storage file can't be opened, scream.
  if (!open(FILE, $nodewatch_pid_file)) {
    error("Couldn't open $nodewatch_pid_file.");
    return("Couldn't open $nodewatch_pid_file.");
  }

  # Assign the process ID to $pid.
  $pid = <FILE>;

  # Close the process ID storage file.
  close(FILE);

  # If $pid has a trailing newline, get rid of the newline.
  if ( substr($pid, -1, 1) eq "\n" ) {
    chop($pid);
  }

  # Record the last time $dump_file has changed.  $dump_file_time_before
  # will not get defined if $dump_file doesn't exist.
  $dump_file_time_before = (stat($dump_file))[9];

  # Send the stats dump signal $dump_sig to NodeWatch.  Scream if it
  # doesn't happen.
  if (! kill($dump_sig, $pid)) {
    error("The stats dump signal (SIG$dump_sig) couldn't be sent.");
    return("The stats dump signal (SIG$dump_sig) couldn't be sent.");
  }

  # Make sure that NodeWatch has started the dump.  If not, then wait for
  # it to start dumping.  Time out after $dump_wait_timeout seconds.  The
  # assumption here is that the dump file isn't fresh.  That the old
  # time stamp won't equal the time stamp given to the file when it is
  # dumped to.  Even if $dump_file didn't exist before we sent the signal.
  $i = 0;
  while () {
    last if ($dump_file_time_before != (stat($dump_file))[9]);
    sleep(1);
    $i++;
    if ($i == $dump_wait_timeout) {
      error("NodeWatch didn't start dumping the stats in time.");
      return("NodeWatch didn't start dumping the stats in time.");
    }
  }

  # Wait for NodeWatch to completely dump the stats.  Completely is defined as
  # the file not changing size for $dump_change_secs.  The 8th element of
  # the array of return values stat gives is the file size.
  for ($i = 0; $i <= $dump_change_secs; $i++) {
    $f_size = (stat($dump_file))[7];
    sleep(1);
    if ($f_size != (stat($dump_file))[7]) {
      $i = 0;
    }
  }

  # It was successful, return null.
  return;
}

sub get_stats_from_dump {
  # Retrieves the stats from $dump_file and returns a hash, keyed by
  # the node and containing the stats for that node.  Puts the data
  # in %stats.  Returns null if everything went fine and the error message
  # if there was an error.  It only puts down nodes in %status.
  # Depends on $dump_file and %status.

  my($node, $status, $time);

  if ( ! open(DUMP, $dump_file) ) {
    error("Can't open $dump_file.");
    return("Can't open $dump_file.");
  }

  %status = ();

  # The assumed format of the dump file is
  # <node>\t<status>\t<time when node went to status>

  while (<DUMP>) {
    # Skip comments.
    next if (/^\s*#/);

    chop($_);
    ($node, $status, $time) = split(/\t/, $_);
    $status{$node} = "$status\t$time";
  }
  close(DUMP);

  # It was successful, return null.
  return;
}

sub prep_node_data {
  # Erases nodes of types that aren't listed in $document from %status.
  # Also puts nodes into one of three arrays, based on their status.
  # Those arrays are @up, @down, and @unknown.  Also rips the status
  # and tab from the value of each element that remains in %status.
  # Depends on %status, $document, @up, @down, and @unknown.

  my($node, $status, $time);

  # Erase nodes that don't belong on %status.
  foreach $node (keys %status) {
    $status = (split(/\t/, $status{$node}))[0];

    if ( ($document !~ /up/i) && ($status =~ /up/i) ) {
      delete($status{$node});
    }
    if ( ($document !~ /down/i) && ($status =~ /down/i) ) {
      delete($status{$node});
    }
    if ( ($document !~ /unknown/i) && ($status =~ /unknown/i) ) {
      delete($status{$node});
    }
  }

  # Puts the nodes into alphabetical order and then copies their name
  # over to the proper array.  Also erases the status from the value of
  # the element in %status.  That way, $status{$node} will only contain
  # the time the node changed to that status.

  # This isn't necessary, but it makes me feel better for some reason.
  @up = ();
  @down = ();
  @unknown = ();

  foreach $node (sort(keys(%status))) {
    ($status, $time) = split(/\t/, $status{$node});

    if ($status =~ /up/i) {
      push(@up, $node);
      $status{$node} = $time;
    }
    if ($status =~ /down/i) {
      push(@down, $node);
      $status{$node} = $time;
    }
    if ($status =~ /unknown/i) {
      push(@unknown, $node);
      $status{$node} = $time;
    }
  }
}

sub gen_html_header {
  # Generates and returns the HTML header.
  # Depends on ctime() from "ctime.pl" and $eol.

  my($header, $now);

  $now = ctime(time());
  chop($now);

  # Note: each line must have at least two spaces at the beginning of the
  # line, even if it is a blank line.

  $header = <<"  END OF HEADER";
  <html>
  
  <head>
  <title>NodeWatch Node Status - $now</title>
  </head>
  
  <body>
  
  <p>
  <center>
  <a href=\"/\">Usage</a> ||
  <a href=\"/down\">Down Nodes</a> ||
  <a href=\"/up\">Up Nodes</a> ||
  <a href=\"/unknown\">Unknown Nodes</a> ||
  <a href=\"/up_down_unknown\">All Nodes</a>
  </center>
  
  <hr>
  </p>
  
  END OF HEADER

  # Take out the first two characters from $header (space characters).
  $header = substr($header, 2);

  # Take out the leading two spaces from each line and also replace
  # the newline with the proper line terminator sequence.
  $header =~ s/\n  /$eol/g;

  return($header);
}

sub http_usage {
  # Returns the usage statement.
  # Depends on $eol.

  my($usage);

  # Note: each line must have at least two spaces at the beginning of the
  # line, even if it is a blank line.

  $usage = <<"  END OF USAGE";
  <p>
  <h1>Usage</h1>
  </p>
  
  <p>
  You must specify a document that contains one or more of the following
  strings:
  
  <ul>
    <li>up</li>
    <li>down</li>
    <li>unknown</li>
  </ul>
  </p>
  
  <p>
  A report is returned that contains the nodes for each
  state you specified, seperated by status.
  </p>
  
  <p>
  You may use characters in between the strings, such as
  spaces.  This would make the URL more readable.  But the
  words (up, down, or unknown) themselves must be continuous.
  </p>
  
  <p>
  I recommend making bookmarks for your favorite reports.
  </p>
  
  <p>
  A node is up if has returned a certain number of pings.
  A node is down if it has missed a certain number of pings.
  And a node is unknown if NodeWatch is just starting up or
  if the node was just added to the node list, or if the count
  for the number of pings a node must hit or miss to go up or
  down has changed because of changes in the node list.
  </p>
  
  <p>
  <h1>Examples</h1>
  </p>
  
  <p>
  For example, to specify a report that contains all of the
  down nodes, use a URL that looks something like
  
  <ul>
    <li><a href=\"/down\">http://hostname:port/down</a></li>
  </ul>
  </p>
  
  <p>
  You could get a report for all of the nodes that are
  up or down by saying something like
  
  <ul>
    <li><a href=\"/ Up or Down Nodes\">http://hostname:port/ Up or Down Nodes</a></li>
  </ul>
  </p>
  
  <p>
  Or, you could get a report for all of the nodes that are
  down or unknown by saying something like
  
  <ul>
    <li><a href=\"/down_unknown\">http://hostname:port/down_unknown</a></li>
  </ul>
  </p>
  
  <p>
  And for all the nodes in all of the states
  
  <ul>
    <li><a href=\"/up down unknown\">http://hostname:port/up down unknown</a></li>
  </ul>
  </p>
  END OF USAGE

  # Take out the first two characters from $usage (space characters) and
  # the last (newline).
  $usage = substr($usage, 2, length($usage) - 1);

  # Take out the leading two spaces from each line and also replace
  # the newline with the proper line terminator sequence.
  $usage =~ s/\n  /$eol/g;

  return($usage);
}

sub gen_report {
  # Generates a report written in HTML based on the entries of %status.
  # Depends on %status, @up, @down, @unknown, $eol, $document, $selection,
  # and ctime() from the "ctime.pl" library.

  my($report, $node, $status, $time, $now);

  $report = "<p>$eol<h1>Current Status of Monitored Nodes</h1>$eol</p>$eol";

  # Set $selection to the various node states requested.  All we are sure
  # of is that at least one of the states are being reported on.
  if ($document =~ /up/i) {
    if ( ($document =~ /down/i) && ($document =~ /unknown/i) ) {
      $selection = "up, down, or unknown.";
    } elsif ($document =~ /down/i) {
      $selection = "up or down.";
    } elsif ($document =~ /unknown/i) {
      $selection = "up or unknown.";
    } else {
      $selection = "up.";
    }
  } else {
    if ( ($document =~ /down/i) && ($document =~ /unknown/i) ) {
      $selection = "down or unknown.";
    } elsif ($document =~ /down/i) {
      $selection = "down.";
    } else {
      $selection = "unknown.";
    }
  }

  $now = ctime(time());
  chop($now);

  $report .= "$eol<p>$eol<ul>$eol  Report for nodes that are $selection";
  $report .= "<br>$eol  The current time is $now.$eol</ul>$eol</p>$eol";

  if ($document =~ /up/i) {
    $report .= "$eol<p>$eol<h3>Nodes that are Up</h3>$eol</p>$eol$eol";
    if (@up > 0) {
      $report .= "<p>$eol<ul>$eol";
      foreach $node (@up) {
        $report .= "  <li>$node up since $status{$node}</li>$eol";
      }
      $report .= "</ul>$eol</p>$eol";
    } else {
      $report .= "<p>$eol<ul>$eol  ";
      $report .= "There are no nodes that are up.$eol</ul>$eol</p>$eol";
    }
  }
  if ($document =~ /down/i) {
    $report .= "$eol<p>$eol<h3>Nodes that are Down</h3>$eol</p>$eol$eol";
    if (@down > 0) {
      $report .= "<p>$eol<ul>$eol";
      foreach $node (@down) {
        $report .= "  <li>$node down since $status{$node}</li>$eol";
      }
      $report .= "</ul>$eol</p>$eol";
    } else {
      $report .= "<p>$eol<ul>$eol  ";
      $report .= "There are no nodes that are down.$eol</ul>$eol</p>$eol";
    }
  }
  if ($document =~ /unknown/i) {
    $report .= "$eol<p>$eol<h3>Nodes that are Unknown</h3>$eol</p>$eol$eol";
    if (@unknown > 0) {
      $report .= "<p>$eol<ul>$eol";
      foreach $node (@unknown) {
        $report .= "  <li>$node unknown since $status{$node}</li>$eol";
      }
      $report .= "</ul>$eol</p>$eol";
    } else {
      $report .= "<p>$eol<ul>$eol  ";
      $report .= "There are no nodes that are unknown.$eol</ul>$eol</p>";
    }
  }

  return($report);
}

sub gen_html_footer {
  # Generates the HTML footer.
  # Depends on $eol.

  my($footer);

  # Note: each line must have at least two spaces at the beginning of the
  # line, even if it is a blank line.

  $footer = <<"  END OF FOOTER";
  
  <p>
  <hr>
  
  <center>
  <a href=\"/\">Usage</a> ||
  <a href=\"/down\">Down Nodes</a> ||
  <a href=\"/up\">Up Nodes</a> ||
  <a href=\"/unknown\">Unknown Nodes</a> ||
  <a href=\"/up_down_unknown\">All Nodes</a>
  </center>
  </p>
  
  </body>
  
  </html>
  END OF FOOTER

  # Take out the first two characters from $footer (space characters) and
  # the last (newline).
  $footer = substr($footer, 2, length($footer) - 1);

  # Take out the leading two spaces from each line and also replace
  # the newline with the proper line terminator sequence.
  $footer =~ s/\n  /$eol/g;

  return($footer);
}

sub to_syslog {
  # Sends messages to syslog.  Takes two arguments: the message and the
  # type.  The second argument is optional.  If it isn't present, then
  # $syslog_m_type is used.
  # Depends on $syslog_show_pid, $syslog_p_name, $syslog_p_type,
  # $syslog_m_type, openlog(), syslog(), and closelog() (those three
  # from the Sys::Syslog namespace).

  local($msg) = $_[0];
  local($m_type);

  use Sys::Syslog;

  # Set up $m_type.
  if (defined($_[1])) {
    $m_type = $_[1];
  } else {
    $m_type = $syslog_m_type;
  }

  # Display the process id in the syslog message if it is set to do so.
  if ($syslog_show_pid) {
    openlog($syslog_p_name, 'pid', $syslog_p_type);
  } else {
    openlog($syslog_p_name, '', $syslog_p_type);
  }

  syslog($m_type, $msg);

  closelog();
}

sub error {
  # Sends out an error to the syslog.
  # Depends on to_syslog().

  local($msg) = $_[0];

  to_syslog($msg, 'err');
}

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

