diff --git a/Changes b/Changes index 1d9a95d..51a50a8 100644 --- a/Changes +++ b/Changes @@ -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!) diff --git a/plugins/logging/file b/plugins/logging/file new file mode 100644 index 0000000..1dcdf28 --- /dev/null +++ b/plugins/logging/file @@ -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] [reopen] [nosplit] I + +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 for any +integer(s) I, 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 below) to facilitate SMTP service alarms with Nagios or a similar +monitoring agent. + +=item loglevel I + +The internal log level below which messages will be logged. The I +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 + +=item B + +=item B + +=item B + +=item B + +=item B + +=item B + +=item B + +=back + +=back + + +The chosen I 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 , with contributions by Peter J. +Holzer . + +=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: diff --git a/plugins/logging/syslog b/plugins/logging/syslog new file mode 100644 index 0000000..1fb3899 --- /dev/null +++ b/plugins/logging/syslog @@ -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 + +The internal log level below which messages will be logged. Priorities count +downward as follows: + +=over + +=item B + +=item B + +=item B + +=item B + +=item B + +=item B + +=item B + +=item B + +=back + + +=item B + +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 +setting is used, all messages will be logged at that priority regardless of +what the original priority might have been. + +=item B + +The ident string that will be attached to messages logged via this plugin. +The default is 'qpsmtpd'. + +=item B + +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 + +=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