2002-07-03 15:10:44 +02:00
|
|
|
package Qpsmtpd;
|
|
|
|
use strict;
|
2005-12-22 22:30:53 +01:00
|
|
|
use vars qw($VERSION $Logger $TraceLevel $Spool_dir $Size_threshold);
|
2002-07-03 15:10:44 +02:00
|
|
|
|
2002-09-24 12:56:35 +02:00
|
|
|
use Sys::Hostname;
|
|
|
|
use Qpsmtpd::Constants;
|
2005-07-29 20:05:08 +02:00
|
|
|
use Qpsmtpd::Transaction;
|
|
|
|
use Qpsmtpd::Connection;
|
2002-07-03 15:10:44 +02:00
|
|
|
|
2005-12-22 22:30:53 +01:00
|
|
|
$VERSION = "0.40-dev";
|
2004-03-05 13:46:24 +01:00
|
|
|
|
|
|
|
sub version { $VERSION };
|
2002-07-03 15:10:44 +02:00
|
|
|
|
2005-05-25 22:07:58 +02:00
|
|
|
sub TRACE_LEVEL { $TraceLevel }; # leave for plugin compatibility
|
2005-03-24 22:16:35 +01:00
|
|
|
|
|
|
|
sub load_logging {
|
|
|
|
# need to do this differently that other plugins so as to
|
|
|
|
# not trigger logging activity
|
|
|
|
my $self = shift;
|
2005-12-02 03:35:14 +01:00
|
|
|
#warn("load_logging: $self->{hooks}{logging} ", caller(8), "\n");
|
2005-03-24 22:16:35 +01:00
|
|
|
return if $self->{hooks}->{"logging"};
|
|
|
|
my $configdir = $self->config_dir("logging");
|
|
|
|
my $configfile = "$configdir/logging";
|
|
|
|
my @loggers = $self->_config_from_file($configfile,'logging');
|
|
|
|
my $dir = $self->plugin_dir;
|
|
|
|
|
|
|
|
$self->_load_plugins($dir, @loggers);
|
|
|
|
|
|
|
|
foreach my $logger (@loggers) {
|
|
|
|
$self->log(LOGINFO, "Loaded $logger");
|
|
|
|
}
|
|
|
|
|
|
|
|
return @loggers;
|
|
|
|
}
|
|
|
|
|
2005-03-29 22:15:53 +02:00
|
|
|
sub trace_level {
|
2005-03-24 22:16:35 +01:00
|
|
|
my $self = shift;
|
2005-03-29 22:15:53 +02:00
|
|
|
return $TraceLevel if $TraceLevel;
|
2005-03-24 22:16:35 +01:00
|
|
|
|
|
|
|
my $configdir = $self->config_dir("loglevel");
|
|
|
|
my $configfile = "$configdir/loglevel";
|
2005-05-25 22:07:58 +02:00
|
|
|
$TraceLevel = $self->_config_from_file($configfile,'loglevel');
|
2005-03-24 22:16:35 +01:00
|
|
|
|
2005-05-25 22:07:58 +02:00
|
|
|
unless (defined($TraceLevel) and $TraceLevel =~ /^\d+$/) {
|
2005-03-29 22:15:53 +02:00
|
|
|
$TraceLevel = LOGWARN; # Default if no loglevel file found.
|
2005-03-24 22:16:35 +01:00
|
|
|
}
|
|
|
|
|
2005-03-29 22:15:53 +02:00
|
|
|
return $TraceLevel;
|
2004-03-05 13:46:24 +01:00
|
|
|
}
|
|
|
|
|
2005-05-25 22:07:58 +02:00
|
|
|
sub init_logger { # needed for compatibility purposes
|
|
|
|
shift->trace_level();
|
|
|
|
}
|
|
|
|
|
2002-09-24 12:56:35 +02:00
|
|
|
sub log {
|
|
|
|
my ($self, $trace, @log) = @_;
|
2005-03-24 22:16:35 +01:00
|
|
|
$self->varlog($trace,join(" ",@log));
|
|
|
|
}
|
|
|
|
|
|
|
|
sub varlog {
|
|
|
|
my ($self, $trace) = (shift,shift);
|
|
|
|
my ($hook, $plugin, @log);
|
|
|
|
if ( $#_ == 0 ) { # log itself
|
|
|
|
(@log) = @_;
|
|
|
|
}
|
|
|
|
elsif ( $#_ == 1 ) { # plus the hook
|
|
|
|
($hook, @log) = @_;
|
|
|
|
}
|
|
|
|
else { # called from plugin
|
|
|
|
($hook, $plugin, @log) = @_;
|
|
|
|
}
|
|
|
|
|
|
|
|
$self->load_logging; # in case we already don't have this loaded yet
|
|
|
|
|
|
|
|
my ($rc) = $self->run_hooks("logging", $trace, $hook, $plugin, @log);
|
|
|
|
|
|
|
|
unless ( $rc and $rc == DECLINED or $rc == OK ) {
|
|
|
|
# no logging plugins registered so fall back to STDERR
|
2005-12-02 03:35:14 +01:00
|
|
|
my $fd = $self->{fd};
|
2005-03-24 22:16:35 +01:00
|
|
|
warn join(" ", $$ .
|
2005-12-02 03:35:14 +01:00
|
|
|
(defined $fd ? " fd:$fd" : "") .
|
2005-03-24 22:16:35 +01:00
|
|
|
(defined $plugin ? " $plugin plugin:" :
|
|
|
|
defined $hook ? " running plugin ($hook):" : ""),
|
|
|
|
@log), "\n"
|
2005-03-29 22:15:53 +02:00
|
|
|
if $trace <= $self->trace_level();
|
2005-03-24 22:16:35 +01:00
|
|
|
}
|
2002-07-03 15:10:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#
|
|
|
|
# method to get the configuration. It just calls get_qmail_config by
|
|
|
|
# default, but it could be overwritten to look configuration up in a
|
|
|
|
# database or whatever.
|
|
|
|
#
|
|
|
|
sub config {
|
2003-03-25 13:50:07 +01:00
|
|
|
my ($self, $c, $type) = @_;
|
2002-07-03 15:10:44 +02:00
|
|
|
|
2002-10-17 09:39:54 +02:00
|
|
|
#warn "SELF->config($c) ", ref $self;
|
2002-09-24 12:56:35 +02:00
|
|
|
|
2002-07-03 15:10:44 +02:00
|
|
|
my %defaults = (
|
|
|
|
me => hostname,
|
|
|
|
timeout => 1200,
|
|
|
|
);
|
|
|
|
|
2002-11-06 07:40:35 +01:00
|
|
|
my ($rc, @config) = $self->run_hooks("config", $c);
|
|
|
|
@config = () unless $rc == OK;
|
|
|
|
|
2002-07-06 04:09:01 +02:00
|
|
|
if (wantarray) {
|
2003-03-25 13:50:07 +01:00
|
|
|
@config = $self->get_qmail_config($c, $type) unless @config;
|
2003-07-24 14:43:46 +02:00
|
|
|
@config = $defaults{$c} if (!@config and $defaults{$c});
|
2002-07-06 04:09:01 +02:00
|
|
|
return @config;
|
|
|
|
}
|
|
|
|
else {
|
2003-03-25 13:50:07 +01:00
|
|
|
return ($config[0] || $self->get_qmail_config($c, $type) || $defaults{$c});
|
2002-07-06 04:09:01 +02:00
|
|
|
}
|
2002-07-03 15:10:44 +02:00
|
|
|
}
|
|
|
|
|
2004-09-08 18:26:33 +02:00
|
|
|
sub config_dir {
|
|
|
|
my ($self, $config) = @_;
|
|
|
|
my $configdir = ($ENV{QMAIL} || '/var/qmail') . '/control';
|
|
|
|
my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
|
|
|
|
$configdir = "$name/config" if (-e "$name/config/$config");
|
2005-07-31 10:48:04 +02:00
|
|
|
if (exists $ENV{QPSMTPD_CONFIG}) {
|
|
|
|
$ENV{QPSMTPD_CONFIG} =~ /^(.*)$/; # detaint
|
|
|
|
$configdir = $1 if -e "$1/$config";
|
|
|
|
}
|
2004-09-08 18:26:33 +02:00
|
|
|
return $configdir;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub plugin_dir {
|
|
|
|
my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
|
|
|
|
my $dir = "$name/plugins";
|
|
|
|
}
|
2002-07-08 04:30:11 +02:00
|
|
|
|
2002-07-03 15:10:44 +02:00
|
|
|
sub get_qmail_config {
|
2003-03-25 13:50:07 +01:00
|
|
|
my ($self, $config, $type) = @_;
|
2004-03-05 13:46:24 +01:00
|
|
|
$self->log(LOGDEBUG, "trying to get config for $config");
|
2002-07-03 15:10:44 +02:00
|
|
|
if ($self->{_config_cache}->{$config}) {
|
|
|
|
return wantarray ? @{$self->{_config_cache}->{$config}} : $self->{_config_cache}->{$config}->[0];
|
|
|
|
}
|
2004-09-08 18:26:33 +02:00
|
|
|
my $configdir = $self->config_dir($config);
|
2003-03-25 13:50:07 +01:00
|
|
|
|
|
|
|
my $configfile = "$configdir/$config";
|
|
|
|
|
|
|
|
if ($type and $type eq "map") {
|
2003-08-30 17:14:56 +02:00
|
|
|
return +{} unless -e $configfile . ".cdb";
|
2003-03-25 13:50:07 +01:00
|
|
|
eval { require CDB_File };
|
|
|
|
|
|
|
|
if ($@) {
|
2004-07-18 13:02:08 +02:00
|
|
|
$self->log(LOGERROR, "No CDB Support! Did NOT read $configfile.cdb, could not load CDB_File module: $@");
|
|
|
|
return +{};
|
2003-03-25 13:50:07 +01:00
|
|
|
}
|
2004-07-18 13:02:08 +02:00
|
|
|
|
2003-03-25 13:50:07 +01:00
|
|
|
my %h;
|
|
|
|
unless (tie(%h, 'CDB_File', "$configfile.cdb")) {
|
2004-03-05 13:46:24 +01:00
|
|
|
$self->log(LOGERROR, "tie of $configfile.cdb failed: $!");
|
|
|
|
return +{};
|
2003-03-25 13:50:07 +01:00
|
|
|
}
|
|
|
|
#warn Data::Dumper->Dump([\%h], [qw(h)]);
|
|
|
|
# should we cache this?
|
|
|
|
return \%h;
|
|
|
|
}
|
|
|
|
|
2004-03-05 13:46:24 +01:00
|
|
|
return $self->_config_from_file($configfile, $config);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _config_from_file {
|
2005-12-02 03:35:14 +01:00
|
|
|
my ($self, $configfile, $config, $visited) = @_;
|
2003-06-10 12:06:31 +02:00
|
|
|
return unless -e $configfile;
|
2005-12-02 03:35:14 +01:00
|
|
|
|
|
|
|
$visited ||= [];
|
|
|
|
push @{$visited}, $configfile;
|
|
|
|
|
2003-04-23 05:36:36 +02:00
|
|
|
open CF, "<$configfile" or warn "$$ could not open configfile $configfile: $!" and return;
|
2002-07-03 15:10:44 +02:00
|
|
|
my @config = <CF>;
|
|
|
|
chomp @config;
|
2004-03-05 13:46:24 +01:00
|
|
|
@config = grep { length($_) and $_ !~ m/^\s*#/ and $_ =~ m/\S/} @config;
|
2002-07-03 15:10:44 +02:00
|
|
|
close CF;
|
2005-12-02 03:35:14 +01:00
|
|
|
|
|
|
|
my $pos = 0;
|
|
|
|
while ($pos < @config) {
|
|
|
|
# recursively pursue an $include reference, if found. An inclusion which
|
|
|
|
# begins with a leading slash is interpreted as a path to a file and will
|
|
|
|
# supercede the usual config path resolution. Otherwise, the normal
|
|
|
|
# config_dir() lookup is employed (the location in which the inclusion
|
|
|
|
# appeared receives no special precedence; possibly it should, but it'd
|
|
|
|
# be complicated beyond justifiability for so simple a config system.
|
|
|
|
if ($config[$pos] =~ /^\s*\$include\s+(\S+)\s*$/) {
|
|
|
|
my ($includedir, $inclusion) = ('', $1);
|
|
|
|
|
|
|
|
splice @config, $pos, 1; # remove the $include line
|
|
|
|
if ($inclusion !~ /^\//) {
|
|
|
|
$includedir = $self->config_dir($inclusion);
|
|
|
|
$inclusion = "$includedir/$inclusion";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (grep($_ eq $inclusion, @{$visited})) {
|
|
|
|
$self->log(LOGERROR, "Circular \$include reference in config $config:");
|
|
|
|
$self->log(LOGERROR, "From $visited->[0]:");
|
|
|
|
$self->log(LOGERROR, " includes $_")
|
|
|
|
for (@{$visited}[1..$#{$visited}], $inclusion);
|
|
|
|
return wantarray ? () : undef;
|
|
|
|
}
|
|
|
|
push @{$visited}, $inclusion;
|
|
|
|
|
|
|
|
for my $inc ($self->expand_inclusion_($inclusion, $configfile)) {
|
|
|
|
my @insertion = $self->_config_from_file($inc, $config, $visited);
|
|
|
|
splice @config, $pos, 0, @insertion; # insert the inclusion
|
|
|
|
$pos += @insertion;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$pos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-07-03 15:10:44 +02:00
|
|
|
$self->{_config_cache}->{$config} = \@config;
|
2005-12-02 03:35:14 +01:00
|
|
|
|
2002-07-03 15:10:44 +02:00
|
|
|
return wantarray ? @config : $config[0];
|
|
|
|
}
|
|
|
|
|
2005-12-02 03:35:14 +01:00
|
|
|
sub expand_inclusion_ {
|
|
|
|
my $self = shift;
|
|
|
|
my $inclusion = shift;
|
|
|
|
my $context = shift;
|
|
|
|
my @includes;
|
|
|
|
|
|
|
|
if (-d $inclusion) {
|
|
|
|
$self->log(LOGDEBUG, "inclusion of directory $inclusion from $context");
|
|
|
|
|
|
|
|
if (opendir(INCD, $inclusion)) {
|
|
|
|
@includes = map { "$inclusion/$_" }
|
|
|
|
(grep { -f "$inclusion/$_" and !/^\./ } readdir INCD);
|
|
|
|
closedir INCD;
|
|
|
|
} else {
|
|
|
|
$self->log(LOGERROR, "Couldn't open directory $inclusion,".
|
|
|
|
" referenced from $context ($!)");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$self->log(LOGDEBUG, "inclusion of file $inclusion from $context");
|
|
|
|
@includes = ( $inclusion );
|
|
|
|
}
|
|
|
|
return @includes;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2002-07-06 09:16:23 +02:00
|
|
|
sub load_plugins {
|
|
|
|
my $self = shift;
|
2005-05-13 00:08:37 +02:00
|
|
|
|
2005-12-02 03:35:14 +01:00
|
|
|
# if ($HOOKS) {
|
|
|
|
# return $self->{hooks} = $HOOKS;
|
|
|
|
# }
|
2005-05-13 00:08:37 +02:00
|
|
|
|
2005-03-29 22:15:53 +02:00
|
|
|
$self->log(LOGWARN, "Plugins already loaded") if $self->{hooks};
|
2004-09-08 18:26:33 +02:00
|
|
|
$self->{hooks} = {};
|
2004-08-31 03:58:57 +02:00
|
|
|
|
2002-07-06 09:16:23 +02:00
|
|
|
my @plugins = $self->config('plugins');
|
|
|
|
|
2004-09-08 18:26:33 +02:00
|
|
|
my $dir = $self->plugin_dir;
|
2004-03-05 13:46:24 +01:00
|
|
|
$self->log(LOGNOTICE, "loading plugins from $dir");
|
|
|
|
|
2004-09-08 18:26:33 +02:00
|
|
|
@plugins = $self->_load_plugins($dir, @plugins);
|
|
|
|
|
2005-12-02 03:35:14 +01:00
|
|
|
# $HOOKS = $self->{hooks};
|
|
|
|
#
|
2004-09-08 18:26:33 +02:00
|
|
|
return @plugins;
|
2004-03-05 13:46:24 +01:00
|
|
|
}
|
2002-07-06 09:16:23 +02:00
|
|
|
|
2004-03-05 13:46:24 +01:00
|
|
|
sub _load_plugins {
|
|
|
|
my $self = shift;
|
|
|
|
my ($dir, @plugins) = @_;
|
2004-09-08 18:26:33 +02:00
|
|
|
|
|
|
|
my @ret;
|
2005-03-24 22:16:35 +01:00
|
|
|
for my $plugin_line (@plugins) {
|
2005-03-25 13:30:37 +01:00
|
|
|
my ($plugin, @args) = split ' ', $plugin_line;
|
2003-11-02 12:13:08 +01:00
|
|
|
|
2002-07-06 09:16:23 +02:00
|
|
|
my $plugin_name = $plugin;
|
2004-09-07 07:35:16 +02:00
|
|
|
$plugin =~ s/:\d+$//; # after this point, only used for filename
|
2002-11-06 07:40:35 +01:00
|
|
|
|
2002-07-06 09:16:23 +02:00
|
|
|
# Escape everything into valid perl identifiers
|
|
|
|
$plugin_name =~ s/([^A-Za-z0-9_\/])/sprintf("_%2x",unpack("C",$1))/eg;
|
|
|
|
|
|
|
|
# second pass cares for slashes and words starting with a digit
|
|
|
|
$plugin_name =~ s{
|
|
|
|
(/+) # directory
|
|
|
|
(\d?) # package's first character
|
|
|
|
}[
|
|
|
|
"::" . (length $2 ? sprintf("_%2x",unpack("C",$2)) : "")
|
|
|
|
]egx;
|
|
|
|
|
2004-03-05 13:46:24 +01:00
|
|
|
my $package = "Qpsmtpd::Plugin::$plugin_name";
|
|
|
|
|
2003-10-23 10:48:56 +02:00
|
|
|
# don't reload plugins if they are already loaded
|
2005-07-14 15:31:07 +02:00
|
|
|
unless ( defined &{"${package}::plugin_name"} ) {
|
2005-03-24 22:16:35 +01:00
|
|
|
Qpsmtpd::Plugin->compile($plugin_name,
|
|
|
|
$package, "$dir/$plugin", $self->{_test_mode});
|
|
|
|
$self->log(LOGDEBUG, "Loading $plugin_line")
|
|
|
|
unless $plugin_line =~ /logging/;
|
|
|
|
}
|
2003-11-02 12:13:08 +01:00
|
|
|
|
2004-08-31 03:58:57 +02:00
|
|
|
my $plug = $package->new();
|
2004-09-08 18:26:33 +02:00
|
|
|
push @ret, $plug;
|
2004-08-31 03:58:57 +02:00
|
|
|
$plug->_register($self, @args);
|
2002-07-06 09:16:23 +02:00
|
|
|
|
2002-07-08 04:30:11 +02:00
|
|
|
}
|
2004-09-08 18:26:33 +02:00
|
|
|
|
|
|
|
return @ret;
|
2002-07-08 04:30:11 +02:00
|
|
|
}
|
2002-07-06 09:16:23 +02:00
|
|
|
|
2004-08-31 03:58:57 +02:00
|
|
|
sub transaction {
|
2005-07-29 20:05:08 +02:00
|
|
|
my $self = shift;
|
|
|
|
return $self->{_transaction} || $self->reset_transaction();
|
|
|
|
}
|
|
|
|
|
|
|
|
sub reset_transaction {
|
|
|
|
my $self = shift;
|
|
|
|
$self->run_hooks("reset_transaction") if $self->{_transaction};
|
|
|
|
return $self->{_transaction} = Qpsmtpd::Transaction->new();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sub connection {
|
|
|
|
my $self = shift;
|
|
|
|
@_ and $self->{_connection} = shift;
|
|
|
|
return $self->{_connection} || ($self->{_connection} = Qpsmtpd::Connection->new());
|
2004-08-31 03:58:57 +02:00
|
|
|
}
|
|
|
|
|
2002-07-08 04:30:11 +02:00
|
|
|
sub run_hooks {
|
|
|
|
my ($self, $hook) = (shift, shift);
|
2005-11-23 00:22:48 +01:00
|
|
|
if ($self->{_continuation} && $hook ne "logging" && $hook ne "config") {
|
2005-06-18 20:22:16 +02:00
|
|
|
die "Continuations in progress from previous hook (this is the $hook hook)";
|
|
|
|
}
|
2004-08-31 03:58:57 +02:00
|
|
|
my $hooks = $self->{hooks};
|
|
|
|
if ($hooks->{$hook}) {
|
2002-07-08 04:30:11 +02:00
|
|
|
my @r;
|
2005-06-18 20:22:16 +02:00
|
|
|
my @local_hooks = @{$hooks->{$hook}};
|
|
|
|
while (@local_hooks) {
|
|
|
|
my $code = shift @local_hooks;
|
|
|
|
@r = $self->run_hook($hook, $code, @_);
|
|
|
|
next unless @r;
|
|
|
|
if ($r[0] == CONTINUATION) {
|
2005-06-22 20:25:16 +02:00
|
|
|
$self->disable_read() if $self->isa('Danga::Client');
|
2005-06-18 20:22:16 +02:00
|
|
|
$self->{_continuation} = [$hook, [@_], @local_hooks];
|
2005-03-24 22:16:35 +01:00
|
|
|
}
|
2004-09-04 02:57:16 +02:00
|
|
|
last unless $r[0] == DECLINED;
|
2002-07-08 04:30:11 +02:00
|
|
|
}
|
2002-11-06 07:40:35 +01:00
|
|
|
$r[0] = DECLINED if not defined $r[0];
|
2002-07-08 04:30:11 +02:00
|
|
|
return @r;
|
2002-07-06 09:16:23 +02:00
|
|
|
}
|
2002-07-08 04:30:11 +02:00
|
|
|
return (0, '');
|
|
|
|
}
|
|
|
|
|
2005-06-18 20:22:16 +02:00
|
|
|
sub finish_continuation {
|
|
|
|
my ($self) = @_;
|
|
|
|
die "No continuation in progress" unless $self->{_continuation};
|
2005-06-22 20:25:16 +02:00
|
|
|
$self->enable_read() if $self->isa('Danga::Client');
|
2005-06-18 20:22:16 +02:00
|
|
|
my $todo = $self->{_continuation};
|
|
|
|
$self->{_continuation} = undef;
|
|
|
|
my $hook = shift @$todo || die "No hook in the continuation";
|
|
|
|
my $args = shift @$todo || die "No hook args in the continuation";
|
|
|
|
my @r;
|
|
|
|
while (@$todo) {
|
|
|
|
my $code = shift @$todo;
|
|
|
|
@r = $self->run_hook($hook, $code, @$args);
|
|
|
|
if ($r[0] == CONTINUATION) {
|
2005-06-22 20:25:16 +02:00
|
|
|
$self->disable_read() if $self->isa('Danga::Client');
|
2005-06-18 20:22:16 +02:00
|
|
|
$self->{_continuation} = [$hook, $args, @$todo];
|
|
|
|
return @r;
|
|
|
|
}
|
|
|
|
last unless $r[0] == DECLINED;
|
|
|
|
}
|
|
|
|
$r[0] = DECLINED if not defined $r[0];
|
|
|
|
my $responder = $hook . "_respond";
|
|
|
|
if (my $meth = $self->can($responder)) {
|
2005-06-22 20:25:16 +02:00
|
|
|
warn("continuation finished on $self\n");
|
2005-06-23 23:05:44 +02:00
|
|
|
return $meth->($self, $r[0], $r[1], @$args);
|
2005-06-18 20:22:16 +02:00
|
|
|
}
|
|
|
|
die "No ${hook}_respond method";
|
|
|
|
}
|
|
|
|
|
|
|
|
sub run_hook {
|
|
|
|
my ($self, $hook, $code, @args) = @_;
|
|
|
|
my @r;
|
2005-07-11 21:10:49 +02:00
|
|
|
if ( $hook eq 'logging' ) { # without calling $self->log()
|
2005-12-14 02:21:20 +01:00
|
|
|
eval { (@r) = $code->{code}->($self, $self->{_transaction}, @args); };
|
2005-07-11 21:10:49 +02:00
|
|
|
$@ and warn("FATAL LOGGING PLUGIN ERROR: ", $@) and next;
|
2005-06-18 20:22:16 +02:00
|
|
|
}
|
2005-07-11 21:10:49 +02:00
|
|
|
else {
|
|
|
|
$self->varlog(LOGINFO, $hook, $code->{name});
|
|
|
|
eval { (@r) = $code->{code}->($self, $self->transaction, @args); };
|
|
|
|
$@ and $self->log(LOGCRIT, "FATAL PLUGIN ERROR: ", $@) and return;
|
|
|
|
|
|
|
|
!defined $r[0]
|
|
|
|
and $self->log(LOGERROR, "plugin ".$code->{name}
|
|
|
|
."running the $hook hook returned undef!")
|
|
|
|
and return;
|
|
|
|
|
|
|
|
if ($self->transaction) {
|
|
|
|
my $tnotes = $self->transaction->notes( $code->{name} );
|
|
|
|
$tnotes->{"hook_$hook"}->{'return'} = $r[0]
|
|
|
|
if (!defined $tnotes || ref $tnotes eq "HASH");
|
|
|
|
} else {
|
|
|
|
my $cnotes = $self->connection->notes( $code->{name} );
|
|
|
|
$cnotes->{"hook_$hook"}->{'return'} = $r[0]
|
|
|
|
if (!defined $cnotes || ref $cnotes eq "HASH");
|
|
|
|
}
|
|
|
|
|
|
|
|
# should we have a hook for "OK" too?
|
|
|
|
if ($r[0] == DENY or $r[0] == DENYSOFT or
|
|
|
|
$r[0] == DENY_DISCONNECT or $r[0] == DENYSOFT_DISCONNECT)
|
|
|
|
{
|
|
|
|
$r[1] = "" if not defined $r[1];
|
|
|
|
$self->log(LOGDEBUG, "Plugin $code->{name}, hook $hook returned $r[0], $r[1]");
|
|
|
|
$self->run_hooks("deny", $code->{name}, $r[0], $r[1]) unless ($hook eq "deny");
|
|
|
|
}
|
2005-06-18 20:22:16 +02:00
|
|
|
|
2005-07-11 21:10:49 +02:00
|
|
|
}
|
2005-06-18 20:22:16 +02:00
|
|
|
return @r;
|
|
|
|
}
|
|
|
|
|
2002-07-08 04:30:11 +02:00
|
|
|
sub _register_hook {
|
|
|
|
my $self = shift;
|
2004-06-11 22:01:17 +02:00
|
|
|
my ($hook, $code, $unshift) = @_;
|
2002-07-06 09:16:23 +02:00
|
|
|
|
2004-08-31 03:58:57 +02:00
|
|
|
my $hooks = $self->{hooks};
|
2004-06-11 22:01:17 +02:00
|
|
|
if ($unshift) {
|
|
|
|
unshift @{$hooks->{$hook}}, $code;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
push @{$hooks->{$hook}}, $code;
|
|
|
|
}
|
2002-07-06 09:16:23 +02:00
|
|
|
}
|
|
|
|
|
2005-02-22 03:47:39 +01:00
|
|
|
sub spool_dir {
|
|
|
|
my $self = shift;
|
|
|
|
|
2005-03-24 22:16:35 +01:00
|
|
|
unless ( $Spool_dir ) { # first time through
|
2005-03-01 15:33:26 +01:00
|
|
|
$self->log(LOGINFO, "Initializing spool_dir");
|
2005-03-24 22:16:35 +01:00
|
|
|
$Spool_dir = $self->config('spool_dir')
|
2005-03-01 15:33:26 +01:00
|
|
|
|| Qpsmtpd::Utils::tildeexp('~/tmp/');
|
2005-02-22 03:47:39 +01:00
|
|
|
|
2005-03-24 22:16:35 +01:00
|
|
|
$Spool_dir .= "/" unless ($Spool_dir =~ m!/$!);
|
2005-03-01 15:33:26 +01:00
|
|
|
|
2005-03-24 22:16:35 +01:00
|
|
|
$Spool_dir =~ /^(.+)$/ or die "spool_dir not configured properly";
|
|
|
|
$Spool_dir = $1; # cleanse the taint
|
2005-02-22 03:47:39 +01:00
|
|
|
|
|
|
|
# Make sure the spool dir has appropriate rights
|
2005-03-24 22:16:35 +01:00
|
|
|
if (-e $Spool_dir) {
|
|
|
|
my $mode = (stat($Spool_dir))[2];
|
2005-03-01 15:33:26 +01:00
|
|
|
$self->log(LOGWARN,
|
2005-03-24 22:16:35 +01:00
|
|
|
"Permissions on spool_dir $Spool_dir are not 0700")
|
2005-03-01 15:33:26 +01:00
|
|
|
if $mode & 07077;
|
2005-02-22 03:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# And finally, create it if it doesn't already exist
|
2005-03-24 22:16:35 +01:00
|
|
|
-d $Spool_dir or mkdir($Spool_dir, 0700)
|
|
|
|
or die "Could not create spool_dir $Spool_dir: $!";
|
|
|
|
}
|
2005-03-01 15:33:26 +01:00
|
|
|
|
2005-03-24 22:16:35 +01:00
|
|
|
return $Spool_dir;
|
2005-02-22 03:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# For unique filenames. We write to a local tmp dir so we don't need
|
|
|
|
# to make them unpredictable.
|
|
|
|
my $transaction_counter = 0;
|
|
|
|
|
|
|
|
sub temp_file {
|
|
|
|
my $self = shift;
|
|
|
|
my $filename = $self->spool_dir()
|
|
|
|
. join(":", time, $$, $transaction_counter++);
|
|
|
|
return $filename;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub temp_dir {
|
|
|
|
my $self = shift;
|
|
|
|
my $mask = shift || 0700;
|
|
|
|
my $dirname = $self->temp_file();
|
|
|
|
-d $dirname or mkdir($dirname, $mask)
|
|
|
|
or die "Could not create temporary directory $dirname: $!";
|
|
|
|
return $dirname;
|
|
|
|
}
|
|
|
|
|
2005-12-22 22:30:53 +01:00
|
|
|
sub size_threshold {
|
|
|
|
my $self = shift;
|
|
|
|
unless ( defined $Size_threshold ) {
|
|
|
|
$Size_threshold = $self->config('size_threshold') || 0;
|
|
|
|
$self->log(LOGNOTICE, "size_threshold set to $Size_threshold");
|
|
|
|
}
|
|
|
|
return $Size_threshold;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub auth_user {
|
|
|
|
my ($self, $user) = @_;
|
|
|
|
$user =~ s/[\r\n].*//s;
|
|
|
|
$self->{_auth_user} = $user if $user;
|
|
|
|
return (defined $self->{_auth_user} ? $self->{_auth_user} : "" );
|
|
|
|
}
|
|
|
|
|
|
|
|
sub auth_mechanism {
|
|
|
|
my ($self, $mechanism) = @_;
|
|
|
|
$mechanism =~ s/[\r\n].*//s;
|
|
|
|
$self->{_auth_mechanism} = $mechanism if $mechanism;
|
|
|
|
return (defined $self->{_auth_mechanism} ? $self->{_auth_mechanism} : "" );
|
|
|
|
}
|
|
|
|
|
2002-07-03 15:10:44 +02:00
|
|
|
1;
|
2005-07-02 04:08:37 +02:00
|
|
|
|
|
|
|
__END__
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
Qpsmtpd
|
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
|
|
|
This is the base class for the qpsmtpd mail server. See
|
|
|
|
L<http://smtpd.develooper.com/> and the I<README> file for more information.
|
|
|
|
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
|
|
|
|
Copyright 2001-2005 Ask Bjoern Hansen, Develooper LLC. See the
|
|
|
|
LICENSE file for more information.
|
|
|
|
|
|
|
|
|
|
|
|
|