# -*-perl-*-

# $Id: Rsyslog.plm,v 1.7 2012/03/16 19:54:24 timc Exp $

# What package to handle /etc/rsyslog.conf

# $Log: Rsyslog.plm,v $
# Revision 1.7  2012/03/16 19:54:24  timc
# add ability to not generate the rsyslog.conf
#
# Revision 1.6  2012/03/02 20:18:41  timc
# default to udp forwarding for the time being
#
# Revision 1.5  2012/03/01 21:40:41  timc
# no forwarding at all
#
# Revision 1.4  2012/03/01 21:34:42  timc
# stop trying to do this until we can make it work properly
#
# Revision 1.3  2012/01/27 17:15:46  timc
# made changes to work better with rsyslog and correct a problem with
# additions to logrotate
#
# Revision 1.2  2012/01/12 21:47:44  timc
# Mostly copied from Syslog.plm, fresh start for Rsyslog
#
# Revision 1.1  2012/01/11 20:55:06  timc
# checkin
#
# Revision 1.2  2006/12/19 16:57:24  thomas
# Rsyslog creates it's own logrotate.d entry
#
# Revision 1.1  2004/03/05 15:16:36  thomas
# Add /Rsyslog subtree
#


=head1 NAME

What::Node::Rsyslog -- manage system logging


=head1 DESCRIPTION

The What::Node::Rsyslog controls the configuration of the
rsyslog daemon

Nodes under the B<Rsyslog> root node correspond to lines in the
rsyslog.conf file.  The node name is a unique key to allow later
modification of the logging instance.  The attributes of each node are
listed below.  Please see the appropriate B<rsyslog.conf> man page for
a full description of the rsyslog.conf file.

=over 4

=item selector

The B<selector> attribute specifies a pattern of facilities and priorities
belonging to the specified action.

=item action

The B<action> attribute specifies a destination for the logged
messages.  This can be a file, a remote host (prepended by an '@'
symbol) or any other destination understood by the underlying rsyslog
daemon.

=item comment

This attribute will by prepended with a '#' symbol and placed on the
line above the rsyslog entry.

=item norotate

If this attribute has a positive value ("1", "yes", "on", etc.), the
B<action> value will not be included in any logrotate script that gets
written out.

=item NoAutoGenerateRsyslogConfFile

If this attribute has a positive value ("1", "yes", "on", etc.), then
no rsyslog.conf file will be written out at all.  This is the first
method by which you can create custom entries in /etc/rsyslog.conf


=back

Any triggers attached to the root B<Rsyslog> node will be run any time
the configuration changes.  A good trigger to place here would be one
that caused the rsyslog daemon to re-examine its configuration file.

An additional facility exists to automate creation of a logrotate
control file from the rsyslog data.  If a B<logrotate> attribute exists
at the top level B</Rsyslog> node, the content of the B</Rsyslog> node
will be used to create the file given as the value of the B<logrotate>
attribute.  In addition, the special word @@FILELIST@@ will be
substituted with a space-separated list of files indicated in the
B<action> attribute of the B</Rsyslog> sub-nodes.  Only actions
beginning with a '/' character will be used, and a specific action can
be skipped from the list by including the B<norotate> attribute with
an affirmative value, as indicated above.

=head1 EXAMPLES

Log auth events at severity B<info> or higher to B</var/log/authlog>.

  <node path="/Rsyslog/auth"
     action="/var/log/authlog"
     selector="auth.info" />

Send all events at seveity B<err> or higer to the log host.

  <node path="/Rsyslog/loghost_error"
     action="@loghost"
     selector="*.err" />

Disable the previously enabled loghost events.

  <delete path="/Rsyslog/loghost_error" />

Run /etc/rc.d/init.d/rsyslog reload (or equivalent) any time
/etc/rsyslog.conf changes.

  <node path="/Rsyslog" >
    <trigger path="Init" init="rsyslog" functions="reload" />
  </node>

=cut

package What::Node::Rsyslog;

use strict;
use FileHandle;
#use Data::Dump;
use Data::Dumper;

use base qw(What::Node);

our %opts = ();

# Set the priority for this module in $::
$::module_priority{'Rsyslog'} = 20;

###############################################################################
# Apply the current configuration

sub apply {
  my $class = shift;
  my $section = shift;
  my $what = shift;
  my $sname = shift;
  my $cname = shift;

  my ($children, $newconfig, $newconfigtrailer, $newfilename, $attrs, $rootattrs);

  $newfilename =
    (defined ($::opts{root}) ? $::opts{root} : '') . '/etc/rsyslog.conf';

  $rootattrs = $class->getattrs ($section);
  defined ($rootattrs->{minsize}) or $rootattrs->{minsize} = 0;

  $newconfig = <<__EOF__
########
# rsyslog.conf configuration created by What::Node::Rsyslog
# Do not edit!  Hand-made changes will be lost!
########
#rsyslog v3 config file

# if you experience problems, check
# http://www.rsyslog.com/troubleshoot for assistance

#### MODULES ####

\$ModLoad imuxsock.so    # provides support for local system logging (e.g. via logger command)
\$ModLoad imklog.so      # provides kernel logging support (previously done by rklogd)

# Use default timestamp format
\$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat

########
# what-generated entries follow
########
__EOF__
  ;

  $newconfigtrailer = <<__Eof__
########
# end of what-generated entries
########

# ### begin forwarding rule ###
# The statement between the begin ... end define a SINGLE forwarding
# rule. They belong together, do NOT split them. If you create multiple
# forwarding rules, duplicate the whole block!
# Remote Logging (we use TCP for reliable delivery)
#
# An on-disk queue is created for this action. If the remote host is
# down, messages are spooled to disk and sent when it is up again.
\$WorkDirectory /var/spool/rsyslog # where to place spool files
\$ActionQueueFileName fwdRule1 # unique name prefix for spool files
\$ActionQueueMaxDiskSpace 1g   # 1gb space limit (use as much as possible)
\$ActionQueueSaveOnShutdown on # save messages to disk on shutdown
\$ActionQueueType LinkedList   # run asynchronously
\$ActionResumeRetryCount -1    # infinite retries if host is down
# remote host is: name/ip:port, e.g. 192.168.0.1:514, port optional
*.* \@loghost:514
#above changed to single at for UDP which appears to work
### end of the forwarding rule ###


__Eof__
  ;

  $children = $class->getchildren ($section);
  my %rotates;
  my $WriteRsyslogConfFile; #by default, yes we write it out
  $WriteRsyslogConfFile = 1;
  #print Dumper(keys %$children);

  foreach (sort keys %$children) {
    #print "nnn: $_\n";

    #I absolutely hate this dollar underbar BS in perl
    if ($_ eq "NoAutoGenerateRsyslogConfFile") {
	$WriteRsyslogConfFile = 0;  #no -- don't write it out
	#print "not going to write out rsyslog.conf\n";
    }


    $attrs = $children->{$_}->getattrs();
    #rotates goes here so it sorts through all -- if a node exists at all rotate it
    #otherwise why the hell say anything about it at all?
    if ($attrs->{action}) {
	$rotates{$attrs->{action}} = 1 if ($attrs->{action} =~ /^\// && ! $class->checkbool ($attrs->{norotate}));
	#print "attrs-action: $attrs->{action}\n";
	#print "attrs-selector: $attrs->{selector}\n";
    }
    defined ($attrs->{selector}) && defined ($attrs->{action})
      or next;
    defined ($attrs->{comment}) and $newconfig .= "# $attrs->{comment}\n";
    $newconfig .= sprintf "%-40s %s\n",
      $attrs->{selector}, $attrs->{action};
    #no, not here
    #$rotates{$attrs->{action}} = 1 if ($attrs->{action} =~ /^\// && ! $class->checkbool ($attrs->{norotate}));
    #print "action: $attrs->{action}\n";
    #print "rotates: $rotates{$attrs->{action}}\n\n";

  }

  #put the forwarding rule trailer on the header and generated rules
  $newconfig .= $newconfigtrailer;


  #if we need a custom rsyslog.conf file -- then don't generate one at all
  #thisWriteRsyslogConfFile is always true unless we specifically say to NOT do it.
  if ($WriteRsyslogConfFile) {
      if ($class->check_and_replace_content ($newfilename,
					     $newconfig,
					     $rootattrs->{minsize},
					     1
	  )) {
	  $class->run_triggers ($section, $what, $cname);
      }
  }

  # Create the logrotate script if requested
  if ($rootattrs->{logrotate}) {
    my $logrotate = $class->gettext ($section);
    if (defined $logrotate) {
      # Do substitutions
      while ($logrotate =~ /\@\@FILELIST\@\@(\S*)/) {
	my $suffix = $1;
	$suffix = '' unless defined $suffix;
	my $filelist = join ' ', (map "$_$suffix", sort keys %rotates);
	$suffix =~ s/([.*])/\\$1/g;
	$logrotate =~ s/\@\@FILELIST\@\@${suffix}/${filelist}/;
      }
      $newfilename = (defined ($::opts{root}) ? $::opts{root} : '') . $rootattrs->{logrotate};

      $class->check_and_replace_content ($newfilename,
                                         $logrotate,
                                         1,
                                         1
                                        )
    }
  }

  return 1;
}


###############################################################################
# Generate a what config from a rsyslog.conf file

sub generate {
  my $class = shift;
  my $section = shift;
  my $what = shift;
  my $filename = shift;

  # Assume $filenames refers to xinetd config files from filedist
  defined ($filename) or $filename =
    (defined ($::opts{root}) ? $::opts{root} : '') . '/etc/rsyslog.conf';

  (-f $filename) or return 0;

  my ($i, $attrs) = (0, {});
  my $fh = new FileHandle $filename, 'r';
  if (!defined $fh) {
    $section->err ("Failed to open $filename!\n");
    return 0;
  }
  while (<$fh>) {
    if (s/^\s*\#\s*//) {
      chomp ($attrs->{comment}=$_);
      next;
    }
    ($attrs->{selector}, $attrs->{action}) = split;
    defined ($attrs->{selector}) && defined ($attrs->{action}) or next;
    $class->setattrs ($section, $attrs, $i++, 1);
    $attrs = {};
  }
  return 1;
}


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

1;
