#!/usr/bin/perl -w
#############################################################################
# syslog.pl
# Written by: Brandon Zehm <caspian @ dotconf.net>
# 
# This script is licensed under the GPL.
# 
# Purpose:
#   To make a small, no modules required, perl script that can log messages
#   to a syslogd daemon.
#
# Technical Description:
#   This script connects to the /dev/log socket and sends a user supplied
#   message to it in the syslogd format.
#
#############################################################################
use strict;
use IO::Socket;



## Global Variable(s)
my %conf = (
    "programName"          => $0,
    "version"              => "1.21",
    "authorName"           => 'Brandon Zehm',                           ## Information about the author or owner of this script.
    "authorEmail"          => 'caspian@dotconf.net',
    "debug"                => 0,
    
    "syslogSocket"         => '/dev/log',
    
    "priority"             => 6,
    "facility"             => 1,
    "message"              => '',
);
$conf{"programName"} =~ s/(.)*[\/,\\]//;                         ## Remove path from programName



my %syslogFacility = (
    "KERN"                 =>  0,     ##  kernel messages
    "USER"                 =>  1,     ##  random user-level messages
    "MAIL"                 =>  2,     ##  mail system
    "DAEMON"               =>  3,     ##  system daemons
    "AUTH"                 =>  4,     ##  security/authorization messages
    "SYSLOG"               =>  5,     ##  messages generated internally by syslogd
    "LPR"                  =>  6,     ##  line printer subsystem
    "NEWS"                 =>  7,     ##  network news subsystem
    "UUCP"                 =>  8,     ##  UUCP subsystem
    "CRON"                 =>  9,     ##  clock daemon
    "AUTHPRIV"             =>  10,    ##  security/authorization messages (private)
    "FTP"                  =>  11,    ##  ftp daemon
);


my %syslogPriority = (
    "EMERG"                =>  0,     ##  system is unusable
    "ALERT"                =>  1,     ##  action must be taken immediately
    "CRIT"                 =>  2,     ##  critical conditions
    "ERR"                  =>  3,     ##  error conditions
    "WARNING"              =>  4,     ##  warning conditions
    "NOTICE"               =>  5,     ##  normal but significant condition
    "INFO"                 =>  6,     ##  informational
    "DEBUG"                =>  7,     ##  debug-level messages
);





#############################
##
##      MAIN PROGRAM
##
#############################

## Initialize
initialize();

## Process Command Line
processCommandLine();

## Call the syslog function:
syslog($conf{'syslogSocket'}, $conf{'priority'}, $conf{'facility'}, $conf{'message'});

## Quit
quit("",0);
































######################################################################
## Function:    help ()
##
## Description: For all those newbies ;) 
##              Prints a help message and exits the program.
## 
######################################################################
sub help {
print <<EOM;

$conf{'programName'}-$conf{'version'} by $conf{'authorName'} <$conf{'authorEmail'}>

Usage:  $conf{'programName'} [options] <message>

  Message will be sent to syslogd with optionally specified priority
  and log facility.
  
  --priority=[0-7]          Syslog priority (6/INFO is used by default)
  --facility=[0-11]         Syslog facility (1/USER is used by default)
  --syslogSocket=<file>     Socket filename (/dev/log is used by defult)

EOM
quit("", 1);
}










######################################################################
##  Function: initialize ()
##  
##  Does all the script startup jibberish.
##  
######################################################################
sub initialize {

  ## Set STDOUT to flush immediatly after each print  
  $| = 1;

  ## Intercept signals
  $SIG{'QUIT'}  = sub { quit("$$ - EXITING: Received SIG$_[0]", 1); };
  $SIG{'INT'}   = sub { quit("$$ - EXITING: Received SIG$_[0]", 1); };
  $SIG{'KILL'}  = sub { quit("$$ - EXITING: Received SIG$_[0]", 1); };
  $SIG{'TERM'}  = sub { quit("$$ - EXITING: Received SIG$_[0]", 1); };
  
  ## ALARM and HUP signals are not supported in Win32
  unless ($^O =~ /win/i) {
    $SIG{'HUP'}   = sub { quit("$$ - EXITING: Received SIG$_[0]", 1); };
    $SIG{'ALRM'}  = sub { quit("$$ - EXITING: Received SIG$_[0]", 1); };
  }
  
  return(1);
}









######################################################################
##  Function: processCommandLine ()
##  
##  Processes command line storing important data in global var %conf
##  
######################################################################
sub processCommandLine {
  
  
  ############################
  ##  Process command line  ##
  ############################
  
  my $numargv = @ARGV;
  my $counter = 0;
  for ($counter = 0; $counter < $numargv; $counter++) {

    if ($ARGV[$counter] =~ s/^--priority=//i) {          ## Priority ##
      $conf{'priority'} = $';
    }

    elsif ($ARGV[$counter] =~ s/^--facility=//i) {       ## Facility ##
      $conf{'facility'} = $';
    }

    elsif ($ARGV[$counter] =~ s/^--syslogSocket=//i) {   ## Syslog Socket ##
      $conf{'syslogSocket'} = $';
    }

    elsif ($ARGV[$counter] =~ /^-h$|^--help$/i) {        ## Help ##
      help();
    }

    else {                                               ## The Message ##
      $conf{'message'} .= $ARGV[$counter];
    }

  }
  
  
  
  ###################################################
  ##  Verify required variables are set correctly  ##
  ###################################################
  if (!$conf{'message'}) {
    help();
  }
  
  ## Convert names to numbers
  if ($conf{'facility'} =~ /\D/) {
    $conf{'facility'} = uc($conf{'facility'});
    $conf{'facility'} = $syslogFacility{$conf{'facility'}};
  } 
  if ($conf{'facility'} =~ /\D/) {
    quit("$$ - ERROR - [$conf{'facility'}] is not a known facility!\n", 1);
  }
  
  ## Convert names to numbers
  if ($conf{'priority'} =~ /\D/) {
    $conf{'priority'} = uc($conf{'priority'});
    $conf{'priority'} = $syslogPriority{$conf{'priority'}};
  }
  if ($conf{'priority'} =~ /\D/) {
    quit("$$ - ERROR - [$conf{'priority'}] is not a known priority!\n", 1);
  }
  
  return(1);
}















###############################################################################################
## FUNCTION:    
##   syslog (string $socketName, int $priority, int $facility, string $message)
## 
## 
## DESCRIPTION: 
##   Connects to the socket $socketName, and sends it a syslog formatted message.  
##   i.e. it syslog's $message with the priority and facility specified.
##   Returns 0 on success, non-zero on error.  If an error occurs the error message
##   is stored in the global variable $conf{'error'}.
##
## Example: 
##   syslog("/dev/log", 6, 1, "Test syslog message");
##
##
## Priorities (on a Linux system)
##   LOG_EMERG       0       system is unusable
##   LOG_ALERT       1       action must be taken immediately
##   LOG_CRIT        2       critical conditions
##   LOG_ERR         3       error conditions
##   LOG_WARNING     4       warning conditions
##   LOG_NOTICE      5       normal but significant condition
##   LOG_INFO        6       informational
##   LOG_DEBUG       7       debug-level messages
##
##
## Facilities (on a Linux system)
##   LOG_KERN        0       kernel messages
##   LOG_USER        1       random user-level messages
##   LOG_MAIL        2       mail system
##   LOG_DAEMON      3       system daemons
##   LOG_AUTH        4       security/authorization messages
##   LOG_SYSLOG      5       messages generated internally by syslogd
##   LOG_LPR         6       line printer subsystem
##   LOG_NEWS        7       network news subsystem
##   LOG_UUCP        8       UUCP subsystem
##   LOG_CRON        9       clock daemon
##   LOG_AUTHPRIV    10      security/authorization messages (private)
##   LOG_FTP         11      ftp daemon
##   LOG_LOCAL0      16      reserved for local use
##   LOG_LOCAL1      17      reserved for local use
##   LOG_LOCAL2      18      reserved for local use
##   LOG_LOCAL3      19      reserved for local use
##   LOG_LOCAL4      20      reserved for local use
##   LOG_LOCAL5      21      reserved for local use
##   LOG_LOCAL6      22      reserved for local use
##   LOG_LOCAL7      23      reserved for local use
##
## v1.0 - 12/04/2002
###############################################################################################
sub syslog {
    
    ## Get incoming variables
    my %incoming = ();
    (
      $incoming{'socketName'},
      $incoming{'priority'},
      $incoming{'facility'},
      $incoming{'message'},
    ) = @_;
    
    ## Set defaults if some variables are not defined
    $incoming{'socketName'}  = "/dev/log"         if (!defined($incoming{'socketName'}));
    $incoming{'priority'}    = 6                  if (!defined($incoming{'priority'}));
    $incoming{'facility'}    = 1                  if (!defined($incoming{'facility'}));
    $incoming{'message'}     = "Default Message"  if (!defined($incoming{'message'}));
    
    ## Bit-shift the facility (black magic)
    $incoming{'facility'} = ($incoming{'facility'}<<3);
    
    ## Make sure values are sane (more black magic)
    $incoming{'priority'} = $incoming{'priority'} & 0x07;
    $incoming{'facility'} = $incoming{'facility'} & 0x3f8;
    
    ## Generate syslog value for the priority and facility
    $incoming{'value'} = ($incoming{'priority'} + $incoming{'facility'});
    
    
    ## Debuging
    printmsg("OS-DEBUG - Pri: $incoming{'priority'}  Fac: $incoming{'facility'}  Sum: $incoming{'value'}", 3);
    printmsg("syslog(): Syslogging the message [$incoming{'message'}]", 1);
    
    if (! -S $incoming{'socketName'}) {
        $conf{'error'} = "OS-ERROR - The specified syslog socket [$incoming{'socketName'}] either does not exist or is not a socket.";
        return(1);
    }
    
    ## Open a UNIX socket in dgram mode with udp protocol. 
    socket(SOCKET, PF_UNIX, SOCK_DGRAM, 0) or do { 
        $conf{'error'} = "syslog() - OS-ERROR - $!";
        return(2);
    };
    
    ## Connect our socket to SOCKET
    connect(SOCKET, sockaddr_un ($incoming{'socketName'})) or do {
        $conf{'error'} = "syslog() - OS-ERROR - $!";
        return(3);
    };
    
    ## Sending the message to the syslog socket
    print SOCKET "<$incoming{'value'}>$incoming{'message'}" or do {
        $conf{'error'} = "syslog() - OS-ERROR - $!";
        return(4);
    };
    
    ## Close the socket
    close SOCKET;
    
    
    ## Return success
    return(0);
}



















###############################################################################################
##  Function:    printmsg (string $message, int $level)
##
##  Description: Handles all messages - Only processes the message if the messages level
##               is less than or equal to the current debug level ($conf{'debug'}).
##               
##
##  Input:       $message          A message to be printed, logged, etc.
##               $level            The debug level of the message. If
##                                 not defined 0 will be assumed.  0 is
##                                 considered a normal message, 1 and 
##                                 higher is considered a debug message.
##  
##  Output:      Prints to STDOUT, to LOGFILE, both, or none depending 
##               on the state of the program.
##  
##  Example:     printmsg("WARNING: We believe in generic error messages... NOT!", 1);
###############################################################################################
sub printmsg {
    my %incoming = ();
    (
      $incoming{'message'},
      $incoming{'level'}
    ) = @_;
    $incoming{'level'} = 0 if (!defined($incoming{'level'}));
    $incoming{'message'} =~ s/\r|\n/ /sg;
    $incoming{'message'} =~ s/\s*$//sg;
    
    ## Continue on if the debug level is >= the incoming message level
    if ($conf{'debug'} >= $incoming{'level'}) {
        
        ## Print to STDOUT always
        print localtime() . " - $$ - $conf{'programName'} - $incoming{'message'}\n";
        
    }
    
    ## Return 0 errors
    return(0);
}












######################################################################
##  Function:    quit (string $message, int $errorLevel)
##  
##  Description: Exits the program, optionally printing $message.  It 
##               returns an exit error level of $errorLevel to the 
##               system  (0 means no errors, and is assumed if empty.)
##
##  Example:     quit("Exiting program normally", 0);
######################################################################
sub quit {
  my %incoming = ();
  (
    $incoming{'message'},
    $incoming{'errorLevel'}
  ) = @_;
  $incoming{'errorLevel'} = 0 if (!defined($incoming{'errorLevel'}));
  
  
  ## Print exit message
  if ($incoming{'message'}) { 
    printmsg($incoming{'message'}, 0);
  }
  
  ## Exit
  exit($incoming{'errorLevel'});
}






