2002-07-03 15:10:44 +02:00
|
|
|
package Qpsmtpd;
|
|
|
|
use strict;
|
2004-03-05 13:46:24 +01:00
|
|
|
use vars qw($VERSION $LogLevel);
|
2002-07-03 15:10:44 +02:00
|
|
|
|
2002-09-24 12:56:35 +02:00
|
|
|
use Sys::Hostname;
|
|
|
|
use Qpsmtpd::Constants;
|
2002-07-03 15:10:44 +02:00
|
|
|
|
2004-06-05 12:09:30 +02:00
|
|
|
$VERSION = "0.28";
|
2004-03-05 13:46:24 +01:00
|
|
|
sub TRACE_LEVEL { $LogLevel }
|
|
|
|
|
|
|
|
sub version { $VERSION };
|
2002-07-03 15:10:44 +02:00
|
|
|
|
2004-03-05 13:46:24 +01:00
|
|
|
sub init_logger {
|
|
|
|
my $self = shift;
|
|
|
|
# Get the loglevel - we localise loglevel to zero while we do this
|
|
|
|
my $loglevel = do {
|
|
|
|
local $LogLevel = 0;
|
|
|
|
$self->config("loglevel");
|
|
|
|
};
|
|
|
|
if (defined($loglevel) and $loglevel =~ /^\d+$/) {
|
|
|
|
$LogLevel = $loglevel;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$LogLevel = LOGWARN; # Default if no loglevel file found.
|
|
|
|
}
|
|
|
|
return $LogLevel;
|
|
|
|
}
|
|
|
|
|
2002-09-24 12:56:35 +02:00
|
|
|
sub log {
|
|
|
|
my ($self, $trace, @log) = @_;
|
2004-03-05 13:46:24 +01:00
|
|
|
my $level = TRACE_LEVEL();
|
|
|
|
$level = $self->init_logger unless defined $level;
|
2002-09-24 12:56:35 +02:00
|
|
|
warn join(" ", $$, @log), "\n"
|
2004-03-05 13:46:24 +01:00
|
|
|
if $trace <= $level;
|
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
|
|
|
}
|
|
|
|
|
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];
|
|
|
|
}
|
2003-08-30 17:14:56 +02:00
|
|
|
my $configdir = ($ENV{QMAIL} || '/var/qmail') . '/control';
|
2002-07-03 15:10:44 +02:00
|
|
|
my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
|
|
|
|
$configdir = "$name/config" if (-e "$name/config/$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 {
|
|
|
|
my ($self, $configfile, $config) = @_;
|
2003-06-10 12:06:31 +02:00
|
|
|
return unless -e $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;
|
2003-09-15 12:50:27 +02:00
|
|
|
#$self->log(10, "returning get_config for $config ",Data::Dumper->Dump([\@config], [qw(config)]));
|
2002-07-03 15:10:44 +02:00
|
|
|
$self->{_config_cache}->{$config} = \@config;
|
|
|
|
return wantarray ? @config : $config[0];
|
|
|
|
}
|
|
|
|
|
2004-09-05 18:28:08 +02:00
|
|
|
sub _compile {
|
|
|
|
my ($plugin, $package, $file) = @_;
|
|
|
|
|
|
|
|
my $sub;
|
|
|
|
open F, $file or die "could not open $file: $!";
|
|
|
|
{
|
|
|
|
local $/ = undef;
|
|
|
|
$sub = <F>;
|
|
|
|
}
|
|
|
|
close F;
|
|
|
|
|
|
|
|
my $line = "\n#line 1 $file\n";
|
|
|
|
|
|
|
|
my $eval = join(
|
|
|
|
"\n",
|
|
|
|
"package $package;",
|
|
|
|
'use Qpsmtpd::Constants;',
|
|
|
|
"require Qpsmtpd::Plugin;",
|
|
|
|
'use vars qw(@ISA);',
|
|
|
|
'@ISA = qw(Qpsmtpd::Plugin);',
|
|
|
|
"sub plugin_name { qq[$plugin] }",
|
|
|
|
$line,
|
|
|
|
$sub,
|
|
|
|
"\n", # last line comment without newline?
|
|
|
|
);
|
|
|
|
|
|
|
|
#warn "eval: $eval";
|
|
|
|
|
|
|
|
$eval =~ m/(.*)/s;
|
|
|
|
$eval = $1;
|
|
|
|
|
|
|
|
eval $eval;
|
|
|
|
die "eval $@" if $@;
|
|
|
|
}
|
2002-07-03 15:10:44 +02:00
|
|
|
|
2002-07-06 09:16:23 +02:00
|
|
|
sub load_plugins {
|
|
|
|
my $self = shift;
|
2004-08-31 03:58:57 +02:00
|
|
|
|
|
|
|
$self->{hooks} ||= {};
|
|
|
|
|
2002-07-06 09:16:23 +02:00
|
|
|
my @plugins = $self->config('plugins');
|
|
|
|
|
|
|
|
my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
|
|
|
|
my $dir = "$name/plugins";
|
2004-03-05 13:46:24 +01:00
|
|
|
$self->log(LOGNOTICE, "loading plugins from $dir");
|
|
|
|
|
|
|
|
$self->_load_plugins($dir, @plugins);
|
|
|
|
}
|
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) = @_;
|
|
|
|
|
2002-07-06 09:16:23 +02:00
|
|
|
for my $plugin (@plugins) {
|
2004-03-05 13:46:24 +01:00
|
|
|
$self->log(LOGINFO, "Loading $plugin");
|
2003-04-15 19:50:45 +02:00
|
|
|
($plugin, my @args) = split /\s+/, $plugin;
|
2003-11-02 12:13:08 +01:00
|
|
|
|
2004-03-05 13:46:24 +01:00
|
|
|
if (lc($plugin) eq '$include') {
|
|
|
|
my $inc = shift @args;
|
|
|
|
my $config_dir = ($ENV{QMAIL} || '/var/qmail') . '/control';
|
|
|
|
my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
|
|
|
|
$config_dir = "$name/config" if (-e "$name/config/$inc");
|
|
|
|
if (-d "$config_dir/$inc") {
|
|
|
|
$self->log(LOGDEBUG, "Loading include dir: $config_dir/$inc");
|
|
|
|
opendir(DIR, "$config_dir/$inc") || die "opendir($config_dir/$inc): $!";
|
|
|
|
my @plugconf = sort grep { -f $_ } map { "$config_dir/$inc/$_" } grep { !/^\./ } readdir(DIR);
|
|
|
|
closedir(DIR);
|
|
|
|
foreach my $f (@plugconf) {
|
|
|
|
$self->_load_plugins($dir, $self->_config_from_file($f, "plugins"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
elsif (-f "$config_dir/$inc") {
|
|
|
|
$self->log(LOGDEBUG, "Loading include file: $config_dir/$inc");
|
|
|
|
$self->_load_plugins($dir, $self->_config_from_file("$config_dir/$inc", "plugins"));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->log(LOGCRIT, "CRITICAL PLUGIN CONFIG ERROR: Include $config_dir/$inc not found");
|
|
|
|
}
|
|
|
|
next;
|
|
|
|
}
|
2004-09-07 07:35:16 +02: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
|
2004-09-05 18:28:08 +02:00
|
|
|
_compile($plugin_name, $package, "$dir/$plugin") unless
|
|
|
|
defined &{"${package}::register"};
|
2003-11-02 12:13:08 +01:00
|
|
|
|
2004-08-31 03:58:57 +02:00
|
|
|
my $plug = $package->new();
|
|
|
|
$plug->_register($self, @args);
|
2002-07-06 09:16:23 +02:00
|
|
|
|
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 {
|
|
|
|
return {}; # base class implements empty transaction
|
|
|
|
}
|
|
|
|
|
2002-07-08 04:30:11 +02:00
|
|
|
sub run_hooks {
|
|
|
|
my ($self, $hook) = (shift, shift);
|
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;
|
2004-08-31 03:58:57 +02:00
|
|
|
for my $code (@{$hooks->{$hook}}) {
|
2004-03-05 13:46:24 +01:00
|
|
|
$self->log(LOGINFO, "running plugin ", $code->{name});
|
2004-08-31 03:58:57 +02:00
|
|
|
eval { (@r) = $code->{code}->($self, $self->transaction, @_); };
|
2004-03-05 13:46:24 +01:00
|
|
|
$@ and $self->log(LOGCRIT, "FATAL PLUGIN ERROR: ", $@) and next;
|
2004-09-04 02:51:02 +02:00
|
|
|
|
|
|
|
!defined $r[0]
|
|
|
|
and $self->log(LOGERROR, "plugin ".$code->{name}
|
|
|
|
."running the $hook hook returned undef!")
|
|
|
|
and next;
|
2003-01-20 12:02:20 +01:00
|
|
|
|
2004-09-04 05:16:10 +02:00
|
|
|
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]
|
2004-09-06 19:36:57 +02:00
|
|
|
if (!defined $cnotes || ref $cnotes eq "HASH");
|
2004-09-04 05:16:10 +02:00
|
|
|
}
|
|
|
|
|
2004-09-04 02:57:16 +02:00
|
|
|
# should we have a hook for "OK" too?
|
2003-01-20 12:02:20 +01:00
|
|
|
if ($r[0] == DENY or $r[0] == DENYSOFT) {
|
2004-09-04 02:57:16 +02:00
|
|
|
$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");
|
2003-01-20 12:02:20 +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, '');
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2002-07-03 15:10:44 +02:00
|
|
|
1;
|