[svn:qpsmtpd] r609 - in branches/0.3x: . plugins/logging

[email protected] 2 Feb 2006 08:46:50 -0000
Newsgroups perl.cvs.qpsmtpd
Message-ID <[email protected]>
Author: aqua
Date: Thu Feb  2 00:46:49 2006
New Revision: 609

Added:
   branches/0.3x/plugins/logging/file
   branches/0.3x/plugins/logging/syslog
Modified:
   branches/0.3x/Changes
Log:
Import file & syslog log plugins.  File plugins include strftime(3) formatting
and unique session naming support based roughly on patch from pjh.


Modified: branches/0.3x/Changes
==============================================================================
--- branches/0.3x/Changes	(original)
+++ branches/0.3x/Changes	Thu Feb  2 00:46:49 2006
@@ -1,3 +1,11 @@
+0.32 -
+
+  Add logging/file plugin for simple logging to a file (Devin Carraway and
+  Peter J. Holzer).
+
+  Add logging/syslog plugin for logging via the syslog facility (Devin
+  Carrway)
+
 0.31.1 - 2005/11/18
 
   Add missing files to the distribution, oops... (Thanks Budi Ang!)

Added: branches/0.3x/plugins/logging/file
==============================================================================
--- (empty file)
+++ branches/0.3x/plugins/logging/file	Thu Feb  2 00:46:49 2006
@@ -0,0 +1,267 @@
+#!/usr/bin/perl
+# $Id$
+
+=head1 NAME
+
+file - Simple log-to-file logging for qpsmtpd
+
+=head1 DESCRIPTION
+
+The 'file' logging plugin for qpsmtpd records qpsmtpd log messages into a
+file (or a named pipe, if you prefer.)
+
+=head1 CONFIGURATION
+
+To enable the logging plugin, add a line of this form to the qpsmtpd plugins
+configuration file:
+
+=over
+
+logging/file [loglevel I<level>] [reopen] [nosplit] I<path>
+
+For example:
+
+logging/file loglevel LOGINFO /var/log/qpsmtpd.log
+logging/file /var/log/qpsmtpd.log.%Y-%m-%d
+logging/file loglevel LOGCRIT reopen |/usr/local/sbin/page-sysadmin
+
+=back
+
+Multiple instances of the plugin can be configured by appending :I<N> for any
+integer(s) I<N>, to log to multiple files simultaneously, e.g. to log critical
+errors and normally verbose logs elsewhere.
+
+The filename or command given can include strftime conversion specifiers,
+which can be used to substitute time and date information into the logfile.
+The file will be reopened whenever this output changes (for example, with a
+format of qpsmtpd.log.%Y-%m-%d-%h, the log would be reopened once per hour).
+
+The list of supported conversion specifiers depends on the strftime()
+implementation of your C library. See strftime(3) for details.  Additionally,
+%i will be expanded to a (hopefully) unique session-id; if %i is used, a new
+logfile will be started for each SMTP connection.
+
+The following optional configuration setting can be supplied:
+
+=over
+
+=item nosplit
+
+If specified, the output file or pipe will be reopened at once once per
+connection, and only prior to the first log output.  This prevents logs for
+sessions that span log intervals being split across multiple logfiles.
+Without this option, the log will be reopened only when its output filename
+changes; if strftime specifiers are not used, the log will not be reopened
+at all.
+
+=item reopen
+
+Forces the log output to be reopened once per connection, as soon as something
+is available to be logged.  This can be combined with a high log severity (see
+I<loglevel> below) to facilitate SMTP service alarms with Nagios or a similar
+monitoring agent.
+
+=item loglevel I<loglevel>
+
+The internal log level below which messages will be logged.  The I<loglevel>
+given should be chosen from the list below.  Priorities count downward (for
+example, if LOGWARN were selected, LOGERROR, LOGCRIT and LOGEMERG messages
+would be logged as well).
+
+=over
+
+=item B<LOGDEBUG>
+
+=item B<LOGINFO>
+
+=item B<LOGNOTICE>
+
+=item B<LOGWARN>
+
+=item B<LOGERROR>
+
+=item B<LOGCRIT>
+
+=item B<LOGALERT>
+
+=item B<LOGEMERG>
+
+=back
+
+=back
+
+
+The chosen I<path> should be writable by the user running qpsmtpd; it will be
+created it did not already exist, and appended to otherwise.
+
+=head1 AUTHORS
+
+Devin Carraway <[email protected]>, with contributions by Peter J.
+Holzer <[email protected]>.
+
+=head1 LICENSE
+
+Copyright (c) 2005-2006, Devin Carraway
+Copyright (c) 2006, Peter J. Holzer.
+
+This plugin is licensed under the same terms as the qpsmtpd package itself.
+Please see the LICENSE file included with qpsmtpd for details.
+
+=cut
+
+use strict;
+use warnings;
+
+use IO::File;
+use Sys::Hostname;
+use POSIX qw(strftime);
+
+sub register {
+    my ($self, $qp, @args) = @_;
+    my %args;
+
+    $self->{_loglevel} = LOGWARN;
+
+    while (1) {
+    	last if !@args;
+    	if (lc $args[0] eq 'loglevel') {
+            shift @args;
+            my $ll = shift @args;
+            if (!defined $ll) {
+                warn "Malformed arguments to logging/file plugin";
+                return;
+            }
+            if ($ll =~ /^(\d+)$/) {
+                $self->{_loglevel} = $1;
+            }
+            elsif ($ll =~ /^(LOG\w+)$/) {
+                $self->{_loglevel} = log_level($1);
+                defined $self->{_loglevel} or $self->{_loglevel} = LOGWARN;
+            }
+        }
+	elsif (lc $args[0] eq 'nosplit') {
+		shift @args;
+		$self->{_nosplit} = 1;
+	}
+	elsif (lc $args[0] eq 'reopen') {
+		shift @args;
+		$self->{_reopen} = 1;
+	}
+        else { last }
+    }
+
+    unless (@args && $args[0]) {
+        warn "Malformed arguments to syslog plugin";
+        return;
+    }
+
+    my $output = join(' ', @args);
+
+    if ($output =~ /^\s*\|(.*)/) {
+    	$self->{_log_pipe} = 1;
+        $self->{_log_format} = $1;
+    } else {
+        $output =~ /^(.*)/; # detaint
+        $self->{_log_format} = $1;
+    }
+    $self->{_current_output} = '';
+    $self->{_session_counter} = 0;
+    1;
+}
+
+sub log_output {
+    my ($self, $txn) = @_;
+    my $output = $self->{_log_format};
+    $output =~ s/%i/($txn->notes('logging-session-id') || 'parent')/ge;
+    $output = strftime $output, localtime;
+    $output;
+}
+
+sub open_log {
+    my ($self,$output,$qp) = @_;
+
+    if ($self->{_log_pipe}) {
+        unless ($self->{_f} = new IO::File "|$output") {
+            warn "Error opening log output to command $output: $!";
+            return undef;
+        }
+    } else {
+        unless ($self->{_f} = new IO::File ">>$output") {
+            warn "Error opening log output to path $output: $!";
+            return undef;
+        }
+    }
+    $self->{_current_output} = $output;
+    $self->{_f}->autoflush(1);
+    1;
+}
+
+
+# Reopen the output iff the interpolated output filename has changed
+# from the one currently open, or if reopening was selected and we haven't
+# yet done so during this session.
+#
+# Returns true if the file was reopened, zero if not, undef on error.
+sub maybe_reopen {
+    my ($self, $txn) = @_;
+
+    my $new_output = $self->log_output($txn);
+    if (!$self->{_current_output} ||
+        $self->{_current_output} ne $new_output ||
+        ($self->{_reopen} &&
+         !$txn->notes('file-reopened-this-session'))) {
+        unless ($self->open_log($new_output, $txn)) {
+            return undef;
+        }
+        $txn->notes('file-reopened-this-session', 1);
+        return 1;
+    }
+    return 0;
+}
+
+sub hook_connect {
+    my ($self, $txn) = @_;
+
+    $txn->notes('file-logged-this-session', 0);
+    $txn->notes('file-reopened-this-session', 0);
+    $txn->notes('logging-session-id',
+                sprintf("%08d-%04d-%d",
+                        scalar time, $$, ++$self->{_session_counter}));
+    return DECLINED;
+}
+
+sub hook_disconnect {
+    my ($self) = @_;
+
+    if ($self->{reopen_} && $self->{_f}) {
+        $self->{_f} = undef;
+    }
+    return DECLINED;
+}
+
+sub hook_logging {
+    my ($self, $txn, $trace, $hook, $plugin, @log) = @_;
+
+    return DECLINED if !defined $self->{_loglevel} or
+                       $trace > $self->{_loglevel};
+    return DECLINED if defined $plugin and $plugin eq $self->plugin_name;
+
+    # Possibly reopen the log iff:
+    # - It's not already open
+    # - We're allowed to split sessions across logfiles
+    # - We haven't logged anything yet this session
+    if (!$self->{_f} ||
+        !$self->{_nosplit} ||
+        !$txn->notes('file-logged-this-session')) {
+        unless (defined $self->maybe_reopen($txn)) {
+            return DECLINED;
+        }
+        $txn->notes('file-logged-this-session', 1);
+    }
+
+    my $f = $self->{_f};
+    print $f scalar localtime, ' ', hostname(), '[', $$, ']: ', @log, "\n";
+    return DECLINED;
+}
+
+# vi: tabstop=4 shiftwidth=4 expandtab:

Added: branches/0.3x/plugins/logging/syslog
==============================================================================
--- (empty file)
+++ branches/0.3x/plugins/logging/syslog	Thu Feb  2 00:46:49 2006
@@ -0,0 +1,166 @@
+#!/usr/bin/perl
+# $Id$
+
+=head1 NAME
+
+syslog - Syslog logging plugin for qpsmtpd
+
+=head1 DESCRIPTION
+
+The syslog plugin for qpsmtpd passes qpsmtpd log messages into the standard
+UNIX syslog facility, mapping qpsmtpd priorities to syslog priorities.
+
+=head1 CONFIGURATION
+
+To enable the logging plugin, add a line of this form to the qpsmtpd plugins
+configuration file:
+
+=over
+
+logging/syslog [loglevel l] [priority p] [ident str] [facility f]
+
+For example:
+
+logging/syslog loglevel LOGINFO priority LOG_NOTICE
+
+=back
+
+The following optional configuration settings can be supplied:
+
+=over
+
+=item B<loglevel>
+
+The internal log level below which messages will be logged.  Priorities count
+downward as follows:
+
+=over
+
+=item B<LOGDEBUG>
+
+=item B<LOGINFO>
+
+=item B<LOGNOTICE>
+
+=item B<LOGWARN>
+
+=item B<LOGERROR>
+
+=item B<LOGCRIT>
+
+=item B<LOGALERT>
+
+=item B<LOGEMERG>
+
+=back
+
+
+=item B<priority>
+
+Normally, log messages will be mapped from the above log levels into the
+syslog(3) log levels of their corresponding names.  This will cause various
+messages to appear or not in syslog outputs according to your syslogd
+configuration (typically /etc/syslog.conf).  However, if the B<priority>
+setting is used, all messages will be logged at that priority regardless of 
+what the original priority might have been.
+
+=item B<ident>
+
+The ident string that will be attached to messages logged via this plugin.
+The default is 'qpsmtpd'.
+
+=item B<facility>
+
+The syslog facility to which logged mesages will be directed.  See syslog(3)
+for details.  The default is LOG_MAIL.
+
+=back
+
+=head1 AUTHOR
+
+Devin Carraway <[email protected]>
+
+=head1 LICENSE
+
+Copyright (c) 2005, Devin Carraway.
+
+This plugin is licensed under the same terms as the qpsmtpd package itself.
+Please see the LICENSE file included with qpsmtpd for details.
+
+=cut
+
+use strict;
+use warnings;
+
+use Sys::Syslog;
+
+sub register {
+    my ($self, $qp, @args) = @_;
+    my %args;
+
+    if (@args % 2 == 0) {
+        %args = @args;
+    } else {
+        warn "Malformed arguments to syslog plugin";
+        return;
+    }
+
+    my $ident = 'qpsmtpd';
+    my $logopt = 'pid';
+    my $facility = 'LOG_MAIL';
+
+    $self->{_loglevel} = LOGWARN;
+
+    if ($args{loglevel}) {
+        if ($args{loglevel} =~ /^(\d+)$/) {
+            $self->{_loglevel} = $1;
+        }
+        elsif ($args{loglevel} =~ /^(LOG\w+)$/) {
+            $self->{_loglevel} = log_level($1) || LOGWARN;
+        }
+    }
+
+    if ($args{priority}) {
+        if ($args{priority} =~ /^(\d+|LOG\w+)$/) {
+            $self->{_priority} = $1;
+        }
+    }
+    
+    if ($args{ident} && $args{ident} =~ /^([\w\-.]+)$/) {
+        $ident = $1;
+    }
+    if ($args{facility} && $args{facility} =~ /^(\w+)$/) {
+        $facility = $1;
+    }
+
+    unless (openlog $ident, $logopt, $facility) {
+        warn "Error opening syslog output";
+        return;
+    }
+}
+
+my %priorities_ = (
+    0 => 'LOG_EMERG',
+    1 => 'LOG_ALERT',
+    2 => 'LOG_CRIT',
+    3 => 'LOG_ERR',
+    4 => 'LOG_WARNING',
+    5 => 'LOG_NOTICE',
+    6 => 'LOG_INFO',
+    7 => 'LOG_DEBUG',
+);
+
+sub hook_logging {
+    my ($self, $txn, $trace, $hook, $plugin, @log) = @_;
+
+    return DECLINED if $trace > $self->{_loglevel};
+    return DECLINED if defined $plugin and $plugin eq $self->plugin_name;
+
+    my $priority = $self->{_priority} ?
+                   $self->{_priority} : $priorities_{$trace};
+
+    syslog $priority, '%s', join(' ', @log);
+    return DECLINED;
+}
+
+# vi: tabstop=4 shiftwidth=4 expandtab