extracted config*() from Qpsmtpd.pm -> Config.pm
* includes full test converage for Qpsmtpd::Config * folded t/config.t into t/qpsmtpd-config.t * includes additional tests for Qpsmtpd * folded t/tempstuff into t/qpsmtpd.t * PBP adjustments here and there * other tweaks to handle test warnings
This commit is contained in:
parent
79d2b99211
commit
ebdb25a4bd
6
MANIFEST
6
MANIFEST
@ -41,6 +41,7 @@ lib/Qpsmtpd/Address.pm
|
|||||||
lib/Qpsmtpd/Auth.pm
|
lib/Qpsmtpd/Auth.pm
|
||||||
lib/Qpsmtpd/Base.pm
|
lib/Qpsmtpd/Base.pm
|
||||||
lib/Qpsmtpd/Command.pm
|
lib/Qpsmtpd/Command.pm
|
||||||
|
lib/Qpsmtpd/Config.pm
|
||||||
lib/Qpsmtpd/ConfigServer.pm
|
lib/Qpsmtpd/ConfigServer.pm
|
||||||
lib/Qpsmtpd/Connection.pm
|
lib/Qpsmtpd/Connection.pm
|
||||||
lib/Qpsmtpd/Constants.pm
|
lib/Qpsmtpd/Constants.pm
|
||||||
@ -166,7 +167,6 @@ run.tcpserver
|
|||||||
STATUS
|
STATUS
|
||||||
t/addresses.t
|
t/addresses.t
|
||||||
t/auth.t
|
t/auth.t
|
||||||
t/config.t
|
|
||||||
t/config/badhelo
|
t/config/badhelo
|
||||||
t/config/badrcptto
|
t/config/badrcptto
|
||||||
t/config/dnsbl_allow
|
t/config/dnsbl_allow
|
||||||
@ -178,6 +178,8 @@ t/config/plugins
|
|||||||
t/config/public_suffix_list
|
t/config/public_suffix_list
|
||||||
t/config/rcpthosts
|
t/config/rcpthosts
|
||||||
t/config/relayclients
|
t/config/relayclients
|
||||||
|
t/config/size_threshold
|
||||||
|
t/config/test_config_file
|
||||||
t/helo.t
|
t/helo.t
|
||||||
t/misc.t
|
t/misc.t
|
||||||
t/plugin_tests.t
|
t/plugin_tests.t
|
||||||
@ -210,10 +212,10 @@ t/plugin_tests/user_config
|
|||||||
t/plugin_tests/virus/clamdscan
|
t/plugin_tests/virus/clamdscan
|
||||||
t/qpsmtpd-address.t
|
t/qpsmtpd-address.t
|
||||||
t/qpsmtpd-base.t
|
t/qpsmtpd-base.t
|
||||||
|
t/qpsmtpd-config.t
|
||||||
t/qpsmtpd-smtp.t
|
t/qpsmtpd-smtp.t
|
||||||
t/qpsmtpd.t
|
t/qpsmtpd.t
|
||||||
t/rset.t
|
t/rset.t
|
||||||
t/tempstuff.t
|
|
||||||
t/Test/Qpsmtpd.pm
|
t/Test/Qpsmtpd.pm
|
||||||
t/Test/Qpsmtpd/Plugin.pm
|
t/Test/Qpsmtpd/Plugin.pm
|
||||||
UPGRADING.pod
|
UPGRADING.pod
|
||||||
|
209
lib/Qpsmtpd.pm
209
lib/Qpsmtpd.pm
@ -5,11 +5,10 @@ use strict;
|
|||||||
our $VERSION = "0.95";
|
our $VERSION = "0.95";
|
||||||
use vars qw($TraceLevel $Spool_dir $Size_threshold);
|
use vars qw($TraceLevel $Spool_dir $Size_threshold);
|
||||||
|
|
||||||
use Sys::Hostname;
|
|
||||||
|
|
||||||
use lib 'lib';
|
use lib 'lib';
|
||||||
use base 'Qpsmtpd::Base';
|
use base 'Qpsmtpd::Base';
|
||||||
use Qpsmtpd::Address;
|
use Qpsmtpd::Address;
|
||||||
|
use Qpsmtpd::Config;
|
||||||
use Qpsmtpd::Constants;
|
use Qpsmtpd::Constants;
|
||||||
|
|
||||||
my $git;
|
my $git;
|
||||||
@ -21,12 +20,6 @@ if (-e ".git") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
our $hooks = {};
|
our $hooks = {};
|
||||||
my %defaults = (
|
|
||||||
me => hostname,
|
|
||||||
timeout => 1200,
|
|
||||||
);
|
|
||||||
my $_config_cache = {};
|
|
||||||
our %config_dir_memo;
|
|
||||||
|
|
||||||
our $LOGGING_LOADED = 0;
|
our $LOGGING_LOADED = 0;
|
||||||
|
|
||||||
@ -36,10 +29,9 @@ sub _restart {
|
|||||||
if ($args{restart}) {
|
if ($args{restart}) {
|
||||||
|
|
||||||
# reset all global vars to defaults
|
# reset all global vars to defaults
|
||||||
$self->clear_config_cache;
|
$self->conf->clear_cache();
|
||||||
$hooks = {};
|
$hooks = {};
|
||||||
$LOGGING_LOADED = 0;
|
$LOGGING_LOADED = 0;
|
||||||
%config_dir_memo = ();
|
|
||||||
$TraceLevel = LOGWARN;
|
$TraceLevel = LOGWARN;
|
||||||
$Spool_dir = undef;
|
$Spool_dir = undef;
|
||||||
$Size_threshold = undef;
|
$Size_threshold = undef;
|
||||||
@ -60,11 +52,11 @@ sub load_logging {
|
|||||||
|
|
||||||
my $configdir = $self->config_dir("logging");
|
my $configdir = $self->config_dir("logging");
|
||||||
my $configfile = "$configdir/logging";
|
my $configfile = "$configdir/logging";
|
||||||
my @loggers = $self->_config_from_file($configfile, 'logging');
|
my @loggers = $self->conf->from_file($configfile, 'logging');
|
||||||
|
|
||||||
$configdir = $self->config_dir('plugin_dirs');
|
$configdir = $self->config_dir('plugin_dirs');
|
||||||
$configfile = "$configdir/plugin_dirs";
|
$configfile = "$configdir/plugin_dirs";
|
||||||
my @plugin_dirs = $self->_config_from_file($configfile, 'plugin_dirs');
|
my @plugin_dirs = $self->conf->from_file($configfile, 'plugin_dirs');
|
||||||
unless (@plugin_dirs) {
|
unless (@plugin_dirs) {
|
||||||
my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
|
my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
|
||||||
@plugin_dirs = ("$name/plugins");
|
@plugin_dirs = ("$name/plugins");
|
||||||
@ -81,7 +73,7 @@ sub load_logging {
|
|||||||
|
|
||||||
$configdir = $self->config_dir("loglevel");
|
$configdir = $self->config_dir("loglevel");
|
||||||
$configfile = "$configdir/loglevel";
|
$configfile = "$configdir/loglevel";
|
||||||
$TraceLevel = $self->_config_from_file($configfile, 'loglevel');
|
$TraceLevel = $self->conf->from_file($configfile, 'loglevel');
|
||||||
|
|
||||||
unless (defined($TraceLevel) and $TraceLevel =~ /^\d+$/) {
|
unless (defined($TraceLevel) and $TraceLevel =~ /^\d+$/) {
|
||||||
$TraceLevel = LOGWARN; # Default if no loglevel file found.
|
$TraceLevel = LOGWARN; # Default if no loglevel file found.
|
||||||
@ -94,7 +86,7 @@ sub load_logging {
|
|||||||
|
|
||||||
sub trace_level { return $TraceLevel; }
|
sub trace_level { return $TraceLevel; }
|
||||||
|
|
||||||
sub init_logger { # needed for compatibility purposes
|
sub init_logger { # needed for compatibility
|
||||||
shift->trace_level();
|
shift->trace_level();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,17 +98,11 @@ sub log {
|
|||||||
sub varlog {
|
sub varlog {
|
||||||
my ($self, $trace) = (shift, shift);
|
my ($self, $trace) = (shift, shift);
|
||||||
my ($hook, $plugin, @log);
|
my ($hook, $plugin, @log);
|
||||||
if ($#_ == 0) { # log itself
|
if ($#_ == 0) { (@log) = @_; } # log itself
|
||||||
(@log) = @_;
|
elsif ($#_ == 1) { ($hook, @log) = @_; } # plus the hook
|
||||||
}
|
else { ($hook, $plugin, @log) = @_; } # from a plugin
|
||||||
elsif ($#_ == 1) { # plus the hook
|
|
||||||
($hook, @log) = @_;
|
|
||||||
}
|
|
||||||
else { # called from plugin
|
|
||||||
($hook, $plugin, @log) = @_;
|
|
||||||
}
|
|
||||||
|
|
||||||
$self->load_logging; # in case we don't have this loaded yet
|
$self->load_logging;
|
||||||
|
|
||||||
my ($rc) =
|
my ($rc) =
|
||||||
$self->run_hooks_no_respond("logging", $trace, $hook, $plugin, @log)
|
$self->run_hooks_no_respond("logging", $trace, $hook, $plugin, @log)
|
||||||
@ -135,15 +121,14 @@ sub varlog {
|
|||||||
warn join(' ', $$ . $prefix, @log), "\n";
|
warn join(' ', $$ . $prefix, @log), "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
sub clear_config_cache {
|
sub conf {
|
||||||
$_config_cache = {};
|
my $self = shift;
|
||||||
|
if (!$self->{_config}) {
|
||||||
|
$self->{_config} = Qpsmtpd::Config->new();
|
||||||
|
};
|
||||||
|
return $self->{_config};
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
|
||||||
# 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 {
|
sub config {
|
||||||
my ($self, $c, $type) = @_;
|
my ($self, $c, $type) = @_;
|
||||||
|
|
||||||
@ -169,30 +154,17 @@ sub config {
|
|||||||
return wantarray ? @config : $config[0];
|
return wantarray ? @config : $config[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
# then get_qmail_config
|
# then qmail
|
||||||
@config = $self->get_qmail_config($c, $type);
|
@config = $self->conf->get_qmail($c, $type);
|
||||||
return wantarray ? @config : $config[0] if @config;
|
return wantarray ? @config : $config[0] if @config;
|
||||||
|
|
||||||
# then the default, if any
|
# then the default, which may be undefined
|
||||||
if (exists $defaults{$c}) {
|
return $self->conf->default($c);
|
||||||
return wantarray ? ($defaults{$c}) : $defaults{$c};
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub config_dir {
|
sub config_dir {
|
||||||
my ($self, $config) = @_;
|
my $self = shift;
|
||||||
if (exists $config_dir_memo{$config}) {
|
return $self->conf->config_dir(@_);
|
||||||
return $config_dir_memo{$config};
|
|
||||||
}
|
|
||||||
my $configdir = ($ENV{QMAIL} || '/var/qmail') . '/control';
|
|
||||||
my ($path) = ($ENV{PROCESS} ? $ENV{PROCESS} : $0) =~ m!(.*?)/([^/]+)$!;
|
|
||||||
$configdir = "$path/config" if -e "$path/config/$config";
|
|
||||||
if (exists $ENV{QPSMTPD_CONFIG}) {
|
|
||||||
$ENV{QPSMTPD_CONFIG} =~ /^(.*)$/; # detaint
|
|
||||||
$configdir = $1 if -e "$1/$config";
|
|
||||||
}
|
|
||||||
return $config_dir_memo{$config} = $configdir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub plugin_dirs {
|
sub plugin_dirs {
|
||||||
@ -206,143 +178,6 @@ sub plugin_dirs {
|
|||||||
return @plugin_dirs;
|
return @plugin_dirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub get_qmail_config {
|
|
||||||
my ($self, $config, $type) = @_;
|
|
||||||
$self->log(LOGDEBUG, "trying to get config for $config");
|
|
||||||
my $configdir = $self->config_dir($config);
|
|
||||||
|
|
||||||
my $configfile = "$configdir/$config";
|
|
||||||
|
|
||||||
# CDB config support really should be moved to a plugin
|
|
||||||
if ($type and $type eq "map") {
|
|
||||||
return $self->get_qmail_config_map($config, $configfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $self->_config_from_file($configfile, $config);
|
|
||||||
}
|
|
||||||
|
|
||||||
sub get_qmail_config_map {
|
|
||||||
my ($self, $config, $configfile) = @_;
|
|
||||||
|
|
||||||
unless (-e $configfile . ".cdb") {
|
|
||||||
$_config_cache->{$config} ||= [];
|
|
||||||
return +{};
|
|
||||||
}
|
|
||||||
eval { require CDB_File };
|
|
||||||
|
|
||||||
if ($@) {
|
|
||||||
$self->log(LOGERROR,
|
|
||||||
"No CDB Support! Did NOT read $configfile.cdb, could not load CDB_File module: $@"
|
|
||||||
);
|
|
||||||
return +{};
|
|
||||||
}
|
|
||||||
|
|
||||||
my %h;
|
|
||||||
unless (tie(%h, 'CDB_File', "$configfile.cdb")) {
|
|
||||||
$self->log(LOGERROR, "tie of $configfile.cdb failed: $!");
|
|
||||||
return +{};
|
|
||||||
}
|
|
||||||
|
|
||||||
# We explicitly don't cache cdb entries. The assumption is that
|
|
||||||
# the data is in a CDB file in the first place because there's
|
|
||||||
# lots of data and the cache hit ratio would be low.
|
|
||||||
return \%h;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub _config_from_file {
|
|
||||||
my ($self, $configfile, $config, $visited) = @_;
|
|
||||||
unless (-e $configfile) {
|
|
||||||
$_config_cache->{$config} ||= [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$visited ||= [];
|
|
||||||
push @$visited, $configfile;
|
|
||||||
|
|
||||||
open my $CF, '<', $configfile or do {
|
|
||||||
warn "$$ could not open configfile $configfile: $!";
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
my @config = <$CF>;
|
|
||||||
close $CF;
|
|
||||||
|
|
||||||
chomp @config;
|
|
||||||
@config = grep { length($_) and $_ !~ m/^\s*#/ and $_ =~ m/\S/ } @config;
|
|
||||||
for (@config) { s/^\s+//; s/\s+$//; } # trim leading/trailing whitespace
|
|
||||||
|
|
||||||
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++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$_config_cache->{$config} = \@config;
|
|
||||||
|
|
||||||
return wantarray ? @config : $config[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
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 !/^\./ } sort 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub load_plugins {
|
sub load_plugins {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
|
@ -3,6 +3,10 @@ use strict;
|
|||||||
|
|
||||||
use Net::IP;
|
use Net::IP;
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
return bless {}, shift;
|
||||||
|
};
|
||||||
|
|
||||||
sub tildeexp {
|
sub tildeexp {
|
||||||
my ($self, $path) = @_;
|
my ($self, $path) = @_;
|
||||||
$path =~ s{^~([^/]*)} {
|
$path =~ s{^~([^/]*)} {
|
||||||
|
192
lib/Qpsmtpd/Config.pm
Normal file
192
lib/Qpsmtpd/Config.pm
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package Qpsmtpd::Config;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Sys::Hostname;
|
||||||
|
|
||||||
|
use lib 'lib';
|
||||||
|
use parent 'Qpsmtpd::Base';
|
||||||
|
use Qpsmtpd::Constants;
|
||||||
|
|
||||||
|
our $_config_cache = {};
|
||||||
|
our %dir_memo;
|
||||||
|
our %defaults = (
|
||||||
|
me => hostname,
|
||||||
|
timeout => 1200,
|
||||||
|
);
|
||||||
|
|
||||||
|
sub log {
|
||||||
|
my ($self, $trace, @log) = @_;
|
||||||
|
# logging methods attempt to read config files, this log() prevents that
|
||||||
|
# until after logging has fully loaded
|
||||||
|
return if $trace > LOGWARN;
|
||||||
|
no warnings 'once';
|
||||||
|
if ($Qpsmtpd::LOGGING_LOADED) {
|
||||||
|
return Qpsmtpd->log($trace, @log);
|
||||||
|
}
|
||||||
|
warn join(' ', $$, @log) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub config_dir {
|
||||||
|
my ($self, $config) = @_;
|
||||||
|
if (exists $dir_memo{$config}) {
|
||||||
|
return $dir_memo{$config};
|
||||||
|
}
|
||||||
|
my $configdir = ($ENV{QMAIL} || '/var/qmail') . '/control';
|
||||||
|
my ($path) = ($ENV{PROCESS} ? $ENV{PROCESS} : $0) =~ m!(.*?)/([^/]+)$!;
|
||||||
|
$configdir = "$path/config" if -e "$path/config/$config";
|
||||||
|
if (exists $ENV{QPSMTPD_CONFIG}) {
|
||||||
|
$ENV{QPSMTPD_CONFIG} =~ /^(.*)$/; # detaint
|
||||||
|
$configdir = $1 if -e "$1/$config";
|
||||||
|
}
|
||||||
|
return $dir_memo{$config} = $configdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub clear_cache {
|
||||||
|
$_config_cache = {};
|
||||||
|
%dir_memo = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
sub default {
|
||||||
|
my ($self, $def) = @_;
|
||||||
|
return if ! $defaults{$def};
|
||||||
|
return wantarray ? ($defaults{$def}) : $defaults{$def};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_qmail {
|
||||||
|
my ($self, $config, $type) = @_;
|
||||||
|
$self->log(LOGDEBUG, "trying to get config for $config");
|
||||||
|
my $configdir = $self->config_dir($config);
|
||||||
|
|
||||||
|
my $configfile = "$configdir/$config";
|
||||||
|
|
||||||
|
# CDB config support really should be moved to a plugin
|
||||||
|
if ($type and $type eq "map") {
|
||||||
|
return $self->get_qmail_map($config, $configfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $self->from_file($configfile, $config);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_qmail_map {
|
||||||
|
my ($self, $config, $configfile) = @_;
|
||||||
|
|
||||||
|
if (!-e $configfile . ".cdb") {
|
||||||
|
$_config_cache->{$config} ||= [];
|
||||||
|
return +{};
|
||||||
|
}
|
||||||
|
eval { require CDB_File };
|
||||||
|
|
||||||
|
if ($@) {
|
||||||
|
$self->log(LOGERROR,
|
||||||
|
"No CDB Support! Did NOT read $configfile.cdb, could not load CDB_File module: $@"
|
||||||
|
);
|
||||||
|
return +{};
|
||||||
|
}
|
||||||
|
|
||||||
|
my %h;
|
||||||
|
unless (tie(%h, 'CDB_File', "$configfile.cdb")) {
|
||||||
|
$self->log(LOGERROR, "tie of $configfile.cdb failed: $!");
|
||||||
|
return +{};
|
||||||
|
}
|
||||||
|
|
||||||
|
# We explicitly don't cache cdb entries. The assumption is that
|
||||||
|
# the data is in a CDB file in the first place because there's
|
||||||
|
# lots of data and the cache hit ratio would be low.
|
||||||
|
return \%h;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub from_file {
|
||||||
|
my ($self, $configfile, $config, $visited) = @_;
|
||||||
|
if (!-e $configfile) {
|
||||||
|
$_config_cache->{$config} ||= [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$visited ||= [];
|
||||||
|
push @$visited, $configfile;
|
||||||
|
|
||||||
|
open my $CF, '<', $configfile or do {
|
||||||
|
warn "$$ could not open configfile $configfile: $!";
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
my @config = <$CF>;
|
||||||
|
close $CF;
|
||||||
|
|
||||||
|
chomp @config;
|
||||||
|
@config = grep { length($_) and $_ !~ m/^\s*#/ and $_ =~ m/\S/ } @config;
|
||||||
|
for (@config) { s/^\s+//; s/\s+$//; } # trim leading/trailing whitespace
|
||||||
|
|
||||||
|
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->from_file($inc, $config, $visited);
|
||||||
|
splice @config, $pos, 0, @insertion; # insert the inclusion
|
||||||
|
$pos += @insertion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$_config_cache->{$config} = \@config;
|
||||||
|
|
||||||
|
return wantarray ? @config : $config[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
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 !/^\./ } sort 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
@ -142,9 +142,9 @@ sub reset_transaction {
|
|||||||
|
|
||||||
sub connection {
|
sub connection {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
@_ and $self->{_connection} = shift;
|
if (@_) { $self->{_connection} = shift; }
|
||||||
return $self->{_connection}
|
return $self->{_connection} if $self->{_connection};
|
||||||
|| ($self->{_connection} = Qpsmtpd::Connection->new());
|
return $self->{_connection} = Qpsmtpd::Connection->new();
|
||||||
}
|
}
|
||||||
|
|
||||||
sub helo {
|
sub helo {
|
||||||
|
@ -30,8 +30,9 @@ sub hook_mail {
|
|||||||
my $from = lc($sender->user) . '@' . $host;
|
my $from = lc($sender->user) . '@' . $host;
|
||||||
|
|
||||||
for my $bad (@badmailfromto) {
|
for my $bad (@badmailfromto) {
|
||||||
|
next if !$bad;
|
||||||
$bad =~ s/^\s*(\S+).*/$1/;
|
$bad =~ s/^\s*(\S+).*/$1/;
|
||||||
next unless $bad;
|
next if !$bad;
|
||||||
$bad = lc $bad;
|
$bad = lc $bad;
|
||||||
if ($bad !~ m/\@/) {
|
if ($bad !~ m/\@/) {
|
||||||
$self->log(LOGWARN, 'bad config, no @ sign in ' . $bad);
|
$self->log(LOGWARN, 'bad config, no @ sign in ' . $bad);
|
||||||
|
@ -157,24 +157,26 @@ Adjust the quantity of logging for this plugin. See docs/logging.pod
|
|||||||
|
|
||||||
Written by Gavin Carr <gavin@openfusion.com.au>.
|
Written by Gavin Carr <gavin@openfusion.com.au>.
|
||||||
|
|
||||||
nfslock feature by JT Moree <jtmoree@kahalacorp.com> - 2007-01-22
|
2007-01-22 - nfslock feature by JT Moree <jtmoree@kahalacorp.com>
|
||||||
|
|
||||||
p0f feature by Matt Simerson <msimerson@cpan.org> - 2010-05-03
|
2010-05-03 - p0f feature by Matt Simerson <msimerson@cpan.org>
|
||||||
|
|
||||||
geoip, loglevel, reject added. Refactored into subs - Matt Simerson - 2012-05
|
2012-05 - geoip, loglevel, reject added. Refactored into subs by Matt Simerson
|
||||||
|
|
||||||
=cut
|
=cut
|
||||||
|
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
|
use Net::IP;
|
||||||
|
|
||||||
use Qpsmtpd::Constants;
|
use Qpsmtpd::Constants;
|
||||||
|
|
||||||
my $VERSION = '0.11';
|
my $VERSION = '0.12';
|
||||||
|
|
||||||
BEGIN { @AnyDBM_File::ISA = qw(DB_File GDBM_File NDBM_File) }
|
BEGIN { @AnyDBM_File::ISA = qw(DB_File GDBM_File NDBM_File) }
|
||||||
use AnyDBM_File;
|
use AnyDBM_File;
|
||||||
use Fcntl qw(:DEFAULT :flock LOCK_EX LOCK_NB);
|
use Fcntl qw(:DEFAULT :flock LOCK_EX LOCK_NB);
|
||||||
use Net::IP;
|
|
||||||
|
|
||||||
my $DENYMSG = "This mail is temporarily denied";
|
my $DENYMSG = "This mail is temporarily denied";
|
||||||
my ($QPHOME) = ($0 =~ m!(.*?)/([^/]+)$!);
|
my ($QPHOME) = ($0 =~ m!(.*?)/([^/]+)$!);
|
||||||
@ -197,9 +199,10 @@ my %DEFAULTS = (
|
|||||||
|
|
||||||
sub register {
|
sub register {
|
||||||
my ($self, $qp, %arg) = @_;
|
my ($self, $qp, %arg) = @_;
|
||||||
|
my $c = $self->qp->config('denysoft_greylist');
|
||||||
my $config = {
|
my $config = {
|
||||||
%DEFAULTS,
|
%DEFAULTS,
|
||||||
map { split /\s+/, $_, 2 } $self->qp->config('denysoft_greylist'),
|
($c ? map { split /\s+/, $_, 2 } $c : ()),
|
||||||
%arg
|
%arg
|
||||||
};
|
};
|
||||||
if (my @bad = grep { !exists $PERMITTED_ARGS{$_} } sort keys %$config) {
|
if (my @bad = grep { !exists $PERMITTED_ARGS{$_} } sort keys %$config) {
|
||||||
@ -265,9 +268,8 @@ sub hook_data {
|
|||||||
return DECLINED unless $transaction->notes('greylist');
|
return DECLINED unless $transaction->notes('greylist');
|
||||||
|
|
||||||
# Decline if ALL recipients are whitelisted
|
# Decline if ALL recipients are whitelisted
|
||||||
if (($transaction->notes('whitelistrcpt') || 0) ==
|
my $recips = scalar $transaction->recipients || 0;
|
||||||
scalar($transaction->recipients))
|
if (($transaction->notes('whitelistrcpt') || 0) == $recips) {
|
||||||
{
|
|
||||||
$self->log(LOGWARN, "skip: all recipients whitelisted");
|
$self->log(LOGWARN, "skip: all recipients whitelisted");
|
||||||
return DECLINED;
|
return DECLINED;
|
||||||
}
|
}
|
||||||
@ -370,7 +372,7 @@ sub get_db_key {
|
|||||||
sub get_db_tie {
|
sub get_db_tie {
|
||||||
my ($self, $db, $lock) = @_;
|
my ($self, $db, $lock) = @_;
|
||||||
|
|
||||||
tie(my %db, 'AnyDBM_File', $db, O_CREAT | O_RDWR, 0600) or do {
|
tie(my %db, 'AnyDBM_File', $db, O_CREAT | O_RDWR, oct('0600')) or do {
|
||||||
$self->log(LOGCRIT, "tie to database $db failed: $!");
|
$self->log(LOGCRIT, "tie to database $db failed: $!");
|
||||||
close $lock;
|
close $lock;
|
||||||
return;
|
return;
|
||||||
@ -419,7 +421,7 @@ sub get_db_lock {
|
|||||||
return $self->get_db_lock_nfs($db) if $self->{_args}{nfslock};
|
return $self->get_db_lock_nfs($db) if $self->{_args}{nfslock};
|
||||||
|
|
||||||
# Check denysoft db
|
# Check denysoft db
|
||||||
open(my $lock, ">$db.lock") or do {
|
open(my $lock, '>', "$db.lock") or do {
|
||||||
$self->log(LOGCRIT, "opening lockfile failed: $!");
|
$self->log(LOGCRIT, "opening lockfile failed: $!");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -450,7 +452,7 @@ sub get_db_lock_nfs {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
open(my $lock, "+<$db.lock") or do {
|
open(my $lock, '+<', "$db.lock") or do {
|
||||||
$self->log(LOGCRIT, "opening nfs lockfile failed: $!");
|
$self->log(LOGCRIT, "opening nfs lockfile failed: $!");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -129,17 +129,16 @@ sub register {
|
|||||||
$self->log(LOGERROR, "Bad parameters for the clamdscan plugin") if @_ % 2;
|
$self->log(LOGERROR, "Bad parameters for the clamdscan plugin") if @_ % 2;
|
||||||
$self->{'_args'} = {@_};
|
$self->{'_args'} = {@_};
|
||||||
|
|
||||||
eval 'use ClamAV::Client';
|
eval 'use ClamAV::Client'; ## no critic (Stringy)
|
||||||
if ($@) {
|
if ($@) {
|
||||||
warn "unable to load ClamAV::Client\n";
|
|
||||||
$self->log(LOGERROR, "unable to load ClamAV::Client");
|
$self->log(LOGERROR, "unable to load ClamAV::Client");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set some sensible defaults
|
# Set sensible defaults
|
||||||
$self->{'_args'}{'deny_viruses'} ||= 'yes';
|
$self->{_args}{deny_viruses} ||= 'yes';
|
||||||
$self->{'_args'}{'max_size'} ||= 1024;
|
$self->{_args}{max_size} ||= 1024;
|
||||||
$self->{'_args'}{'scan_all'} ||= 1;
|
$self->{_args}{scan_all} ||= 1;
|
||||||
for my $setting ('deny_viruses', 'defer_on_error') {
|
for my $setting ('deny_viruses', 'defer_on_error') {
|
||||||
next unless $self->{'_args'}{$setting};
|
next unless $self->{'_args'}{$setting};
|
||||||
if (lc $self->{'_args'}{$setting} eq 'no') {
|
if (lc $self->{'_args'}{$setting} eq 'no') {
|
||||||
@ -241,9 +240,11 @@ sub get_filename {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!-f $filename) {
|
if (!-f $filename) {
|
||||||
|
if ($transaction->data_size) {
|
||||||
$self->log(LOGERROR, "spool file missing! Attempting to respool");
|
$self->log(LOGERROR, "spool file missing! Attempting to respool");
|
||||||
$transaction->body_spool;
|
$transaction->body_spool;
|
||||||
$filename = $transaction->body_filename;
|
$filename = $transaction->body_filename;
|
||||||
|
};
|
||||||
if (!-f $filename) {
|
if (!-f $filename) {
|
||||||
$self->log(LOGERROR, "skip: failed spool to $filename! Giving up");
|
$self->log(LOGERROR, "skip: failed spool to $filename! Giving up");
|
||||||
return;
|
return;
|
||||||
@ -264,14 +265,14 @@ sub set_permission {
|
|||||||
my $dir_mode = (stat($self->spool_dir()))[2];
|
my $dir_mode = (stat($self->spool_dir()))[2];
|
||||||
$self->log(LOGDEBUG, "spool dir mode: $dir_mode");
|
$self->log(LOGDEBUG, "spool dir mode: $dir_mode");
|
||||||
|
|
||||||
if ($dir_mode & 0010 || $dir_mode & 0001) {
|
if ($dir_mode & oct('0010') || $dir_mode & oct('0001')) {
|
||||||
|
|
||||||
# match the spool file mode with the mode of the directory -- add
|
# match the spool file mode with the mode of the directory -- add
|
||||||
# the read bit for group, world, or both, depending on what the
|
# the read bit for group, world, or both, depending on what the
|
||||||
# spool dir had, and strip all other bits, especially the sticky bit
|
# spool dir had, and strip all other bits, especially the sticky bit
|
||||||
my $fmode =
|
my $fmode =
|
||||||
($dir_mode & 0044) | ($dir_mode & 0010 ? 0040 : 0) |
|
($dir_mode & oct('0044')) | ($dir_mode & oct('0010') ? oct('0040') : 0) |
|
||||||
($dir_mode & 0001 ? 0004 : 0);
|
($dir_mode & oct('0001') ? oct('0004') : 0);
|
||||||
|
|
||||||
unless (chmod $fmode, $filename) {
|
unless (chmod $fmode, $filename) {
|
||||||
$self->log(LOGERROR, "chmod: $filename: $!");
|
$self->log(LOGERROR, "chmod: $filename: $!");
|
||||||
|
@ -396,7 +396,7 @@ sub cmd_hup {
|
|||||||
|
|
||||||
# clear cache
|
# clear cache
|
||||||
print "Clearing cache\n";
|
print "Clearing cache\n";
|
||||||
Qpsmtpd::clear_config_cache();
|
Qpsmtpd::Config::clear_cache();
|
||||||
|
|
||||||
# should also reload modules... but can't do that yet.
|
# should also reload modules... but can't do that yet.
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package Test::Qpsmtpd;
|
package Test::Qpsmtpd;
|
||||||
use strict;
|
use strict;
|
||||||
|
|
||||||
|
use Carp qw(croak);
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
use lib 't';
|
use lib 't';
|
||||||
use lib 'lib';
|
use lib 'lib';
|
||||||
use Carp qw(croak);
|
use parent 'Qpsmtpd::SMTP';
|
||||||
use base qw(Qpsmtpd::SMTP);
|
|
||||||
use Test::More;
|
|
||||||
use Qpsmtpd::Constants;
|
use Qpsmtpd::Constants;
|
||||||
use Test::Qpsmtpd::Plugin;
|
use Test::Qpsmtpd::Plugin;
|
||||||
|
|
||||||
@ -78,7 +81,7 @@ sub input {
|
|||||||
|
|
||||||
sub config_dir {
|
sub config_dir {
|
||||||
return './t/config' if $ENV{QPSMTPD_DEVELOPER};
|
return './t/config' if $ENV{QPSMTPD_DEVELOPER};
|
||||||
'./config.sample';
|
return './config.sample';
|
||||||
}
|
}
|
||||||
|
|
||||||
sub plugin_dirs {
|
sub plugin_dirs {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
package Test::Qpsmtpd::Plugin;
|
package Test::Qpsmtpd::Plugin;
|
||||||
|
use strict;
|
||||||
1;
|
1;
|
||||||
|
|
||||||
# Additional plugin methods used during testing
|
# Additional plugin methods used during testing
|
||||||
|
37
t/config.t
37
t/config.t
@ -1,37 +0,0 @@
|
|||||||
#!/usr/bin/perl -w
|
|
||||||
use Test::More qw(no_plan);
|
|
||||||
use File::Path;
|
|
||||||
use strict;
|
|
||||||
use lib 't';
|
|
||||||
use_ok('Test::Qpsmtpd');
|
|
||||||
|
|
||||||
my @mes;
|
|
||||||
|
|
||||||
BEGIN { # need this to happen before anything else
|
|
||||||
my $cwd = `pwd`;
|
|
||||||
chomp($cwd);
|
|
||||||
@mes = qw{ ./config.sample/me ./t/config/me };
|
|
||||||
foreach my $f (@mes) {
|
|
||||||
open my $me_config, '>', $f;
|
|
||||||
print $me_config "some.host.example.org";
|
|
||||||
close $me_config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ok(my ($smtpd, $conn) = Test::Qpsmtpd->new_conn(), "get new connection");
|
|
||||||
|
|
||||||
is($smtpd->config('me'), 'some.host.example.org', 'config("me")');
|
|
||||||
|
|
||||||
# test for ignoring leading/trailing whitespace (relayclients has a
|
|
||||||
# line with both)
|
|
||||||
my $relayclients = join ",", sort $smtpd->config('relayclients');
|
|
||||||
is(
|
|
||||||
$relayclients,
|
|
||||||
'127.0.0.1,192.0.,2001:0DB8,2001:0DB8:0000:0000:0000:0000:0000:0001,2001:DB8::1,2001:DB8::1/32',
|
|
||||||
'config("relayclients") are trimmed'
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach my $f (@mes) {
|
|
||||||
unlink $f if -f $f;
|
|
||||||
}
|
|
||||||
|
|
@ -46,10 +46,10 @@ helo
|
|||||||
sender_permitted_from
|
sender_permitted_from
|
||||||
greylisting p0f genre,windows
|
greylisting p0f genre,windows
|
||||||
|
|
||||||
auth/auth_checkpassword checkpw /usr/local/vpopmail/bin/vchkpw true /usr/bin/true
|
#auth/auth_checkpassword checkpw /usr/local/vpopmail/bin/vchkpw true /usr/bin/true
|
||||||
auth/auth_vpopmail
|
#auth/auth_vpopmail
|
||||||
auth/auth_vpopmaild
|
auth/auth_vpopmaild
|
||||||
auth/auth_vpopmail_sql
|
#auth/auth_vpopmail_sql
|
||||||
auth/auth_flat_file
|
auth/auth_flat_file
|
||||||
auth/authdeny
|
auth/authdeny
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ auth/authdeny
|
|||||||
rcpt_ok
|
rcpt_ok
|
||||||
|
|
||||||
headers days 5 reject_type temp require From,Date
|
headers days 5 reject_type temp require From,Date
|
||||||
domainkeys
|
#domainkeys
|
||||||
dkim
|
dkim
|
||||||
dmarc
|
dmarc
|
||||||
|
|
||||||
|
3
t/config/size_threshold
Normal file
3
t/config/size_threshold
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Messages below the size below will be stored in memory and not spooled.
|
||||||
|
# Without this file, the default is 0 bytes, i.e. all messages will be spooled.
|
||||||
|
10000
|
@ -8,7 +8,7 @@ use Qpsmtpd::Constants;
|
|||||||
sub register_tests {
|
sub register_tests {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
eval 'use ClamAV::Client';
|
eval 'use ClamAV::Client'; ## no critic (Stringy)
|
||||||
if ( ! $@ ) {
|
if ( ! $@ ) {
|
||||||
$self->register_test('test_register', 3);
|
$self->register_test('test_register', 3);
|
||||||
$self->register_test('test_get_clamd', 1);
|
$self->register_test('test_get_clamd', 1);
|
||||||
@ -42,6 +42,9 @@ sub test_err_and_return {
|
|||||||
|
|
||||||
sub test_get_filename {
|
sub test_get_filename {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
my $tran = $self->qp->transaction();
|
||||||
|
$tran->{_body_array} = ['line','two'];
|
||||||
|
|
||||||
my $filename = $self->get_filename();
|
my $filename = $self->get_filename();
|
||||||
ok( $filename, "get_filename ($filename)" );
|
ok( $filename, "get_filename ($filename)" );
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,12 @@ use Test::More;
|
|||||||
|
|
||||||
use lib 'lib'; # test lib/Qpsmtpd/Base (vs site_perl)
|
use lib 'lib'; # test lib/Qpsmtpd/Base (vs site_perl)
|
||||||
|
|
||||||
BEGIN { use_ok('Qpsmtpd::Base'); }
|
BEGIN {
|
||||||
|
use_ok('Qpsmtpd::Base');
|
||||||
|
use_ok('Qpsmtpd::Constants');
|
||||||
|
}
|
||||||
|
|
||||||
my $base = bless {}, 'Qpsmtpd::Base';
|
my $base = Qpsmtpd::Base->new();
|
||||||
|
|
||||||
__tildeexp();
|
__tildeexp();
|
||||||
__is_localhost();
|
__is_localhost();
|
||||||
|
89
t/qpsmtpd-config.t
Normal file
89
t/qpsmtpd-config.t
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Data::Dumper;
|
||||||
|
use File::Path;
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
use lib 'lib'; # test lib/Qpsmtpd (vs site_perl)
|
||||||
|
use lib 't';
|
||||||
|
|
||||||
|
my @mes;
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
use_ok('Qpsmtpd::Config'); # call classes directly
|
||||||
|
use_ok('Qpsmtpd::Constants');
|
||||||
|
|
||||||
|
use_ok('Test::Qpsmtpd'); # call via a connection object
|
||||||
|
|
||||||
|
@mes = qw{ ./config.sample/me ./t/config/me };
|
||||||
|
foreach my $f (@mes) {
|
||||||
|
open my $me_config, '>', $f;
|
||||||
|
print $me_config "host.example.org";
|
||||||
|
close $me_config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
my $config = Qpsmtpd::Config->new();
|
||||||
|
|
||||||
|
isa_ok($config, 'Qpsmtpd::Config');
|
||||||
|
|
||||||
|
__log();
|
||||||
|
__config_dir();
|
||||||
|
__from_file();
|
||||||
|
__get_qmail();
|
||||||
|
__config_via_smtpd();
|
||||||
|
|
||||||
|
foreach my $f (@mes) { unlink $f; }
|
||||||
|
|
||||||
|
done_testing();
|
||||||
|
|
||||||
|
sub __log {
|
||||||
|
my $warned = '';
|
||||||
|
local $SIG{__WARN__} = sub {
|
||||||
|
if ($_[0] eq "$$ test log message\n") {
|
||||||
|
$warned = join ' ', @_;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
warn @_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ok($config->log(LOGWARN, "test log message"), 'log');
|
||||||
|
is($warned, "$$ test log message\n", 'LOGWARN emitted correct warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub __get_qmail {
|
||||||
|
is($config->get_qmail('me'), 'host.example.org', 'get_qmail("me")');
|
||||||
|
ok(!$config->get_qmail('not-me'), 'get_qmail("not-me")');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub __from_file {
|
||||||
|
my $test_file = 't/config/test_config_file';
|
||||||
|
my @r = $config->from_file($test_file, 'test_config_file');
|
||||||
|
ok( @r, "from_file, $test_file");
|
||||||
|
cmp_ok('1st line with content', 'eq', $r[0], "from_file string compare");
|
||||||
|
ok( !$r[1], "from_file");
|
||||||
|
};
|
||||||
|
|
||||||
|
sub __config_dir {
|
||||||
|
my $dir = $config->config_dir('logging');
|
||||||
|
ok($dir, "config_dir, $dir");
|
||||||
|
|
||||||
|
#warn Data::Dumper::Dumper($Qpsmtpd::config_dir_memo{logging});
|
||||||
|
$dir = $Qpsmtpd::Config::dir_memo{logging};
|
||||||
|
ok($dir, "config_dir, $dir (memo)");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub __config_via_smtpd {
|
||||||
|
ok(my ($smtpd, $conn) = Test::Qpsmtpd->new_conn(), "get new connection");
|
||||||
|
|
||||||
|
is($smtpd->config('me'), 'host.example.org', 'config("me")');
|
||||||
|
|
||||||
|
# test for ignoring leading/trailing whitespace (relayclients has a
|
||||||
|
# line with both)
|
||||||
|
my $relayclients = join ',', sort $smtpd->config('relayclients');
|
||||||
|
is($relayclients,
|
||||||
|
'127.0.0.1,192.0.,2001:0DB8,2001:0DB8:0000:0000:0000:0000:0000:0001,2001:DB8::1,2001:DB8::1/32',
|
||||||
|
'config("relayclients") are trimmed'
|
||||||
|
);
|
||||||
|
};
|
69
t/qpsmtpd.t
69
t/qpsmtpd.t
@ -7,20 +7,23 @@ use File::Path;
|
|||||||
use Test::More;
|
use Test::More;
|
||||||
|
|
||||||
use lib 'lib'; # test lib/Qpsmtpd (vs site_perl)
|
use lib 'lib'; # test lib/Qpsmtpd (vs site_perl)
|
||||||
|
use lib 't';
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
use_ok('Qpsmtpd');
|
use_ok('Qpsmtpd');
|
||||||
use_ok('Qpsmtpd::Constants');
|
use_ok('Qpsmtpd::Constants');
|
||||||
}
|
|
||||||
|
|
||||||
use lib 't';
|
|
||||||
use_ok('Test::Qpsmtpd');
|
use_ok('Test::Qpsmtpd');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
my $qp = bless {}, 'Qpsmtpd';
|
my $qp = bless {}, 'Qpsmtpd';
|
||||||
|
|
||||||
ok($qp->version(), "version, " . $qp->version());
|
ok($qp->version(), "version, " . $qp->version());
|
||||||
is_deeply(Qpsmtpd::hooks(), {}, 'hooks, empty');
|
is_deeply(Qpsmtpd::hooks(), {}, 'hooks, empty');
|
||||||
|
|
||||||
|
ok(my ($smtpd, $conn) = Test::Qpsmtpd->new_conn(), "get new connection");
|
||||||
|
ok(Qpsmtpd::hooks(), "hooks, populated");
|
||||||
|
|
||||||
__temp_file();
|
__temp_file();
|
||||||
__temp_dir();
|
__temp_dir();
|
||||||
__size_threshold();
|
__size_threshold();
|
||||||
@ -33,26 +36,10 @@ __log();
|
|||||||
__load_logging();
|
__load_logging();
|
||||||
|
|
||||||
__config_dir();
|
__config_dir();
|
||||||
__config_from_file();
|
|
||||||
__get_qmail_config();
|
|
||||||
__config();
|
__config();
|
||||||
|
|
||||||
done_testing();
|
done_testing();
|
||||||
|
|
||||||
sub __get_qmail_config {
|
|
||||||
ok(!$qp->get_qmail_config('me'), "get_qmail_config, me");
|
|
||||||
|
|
||||||
# TODO: add positive tests.
|
|
||||||
}
|
|
||||||
|
|
||||||
sub __config_from_file {
|
|
||||||
my $test_file = 't/config/test_config_file';
|
|
||||||
my @r = $qp->_config_from_file($test_file);
|
|
||||||
ok( @r, "_config_from_file, $test_file");
|
|
||||||
cmp_ok('1st line with content', 'eq', $r[0], "_config_from_file string compare");
|
|
||||||
ok( !$r[1], "_config_from_file");
|
|
||||||
};
|
|
||||||
|
|
||||||
sub __log {
|
sub __log {
|
||||||
my $warned = '';
|
my $warned = '';
|
||||||
local $SIG{__WARN__} = sub {
|
local $SIG{__WARN__} = sub {
|
||||||
@ -67,15 +54,6 @@ sub __log {
|
|||||||
is($warned, "$$ test log message\n", 'LOGWARN emitted correct warning');
|
is($warned, "$$ test log message\n", 'LOGWARN emitted correct warning');
|
||||||
}
|
}
|
||||||
|
|
||||||
sub __config_dir {
|
|
||||||
my $dir = $qp->config_dir('logging');
|
|
||||||
ok($dir, "config_dir, $dir");
|
|
||||||
|
|
||||||
#warn Data::Dumper::Dumper($Qpsmtpd::config_dir_memo{logging});
|
|
||||||
$dir = $Qpsmtpd::config_dir_memo{logging};
|
|
||||||
ok($dir, "config_dir, $dir (memo)");
|
|
||||||
}
|
|
||||||
|
|
||||||
sub __load_logging {
|
sub __load_logging {
|
||||||
$Qpsmtpd::LOGGING_LOADED = 1;
|
$Qpsmtpd::LOGGING_LOADED = 1;
|
||||||
ok(!$qp->load_logging(), "load_logging, loaded");
|
ok(!$qp->load_logging(), "load_logging, loaded");
|
||||||
@ -90,6 +68,25 @@ sub __load_logging {
|
|||||||
sub __spool_dir {
|
sub __spool_dir {
|
||||||
my $dir = $qp->spool_dir();
|
my $dir = $qp->spool_dir();
|
||||||
ok( $dir, "spool_dir is at $dir");
|
ok( $dir, "spool_dir is at $dir");
|
||||||
|
|
||||||
|
my $cwd = `pwd`;
|
||||||
|
chomp($cwd);
|
||||||
|
open my $spooldir, '>', "./config.sample/spool_dir";
|
||||||
|
print $spooldir "$cwd/t/tmp";
|
||||||
|
close $spooldir;
|
||||||
|
|
||||||
|
my $spool_dir = $smtpd->spool_dir();
|
||||||
|
ok($spool_dir =~ m!t/tmp/$!, "Located the spool directory");
|
||||||
|
|
||||||
|
my $tempfile = $smtpd->temp_file();
|
||||||
|
my $tempdir = $smtpd->temp_dir();
|
||||||
|
|
||||||
|
ok($tempfile =~ /^$spool_dir/, "Temporary filename");
|
||||||
|
ok($tempdir =~ /^$spool_dir/, "Temporary directory");
|
||||||
|
ok(-d $tempdir, "And that directory exists");
|
||||||
|
|
||||||
|
unlink "./config.sample/spool_dir";
|
||||||
|
rmtree($spool_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub __temp_file {
|
sub __temp_file {
|
||||||
@ -112,7 +109,7 @@ sub __temp_dir {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub __size_threshold {
|
sub __size_threshold {
|
||||||
ok( ! $qp->size_threshold(), "size_threshold is undefined")
|
is( $qp->size_threshold(), 10000, "size_threshold from t/config is 1000")
|
||||||
or warn "size_threshold: " . $qp->size_threshold;
|
or warn "size_threshold: " . $qp->size_threshold;
|
||||||
|
|
||||||
$Qpsmtpd::Size_threshold = 5;
|
$Qpsmtpd::Size_threshold = 5;
|
||||||
@ -149,9 +146,19 @@ sub __auth_mechanism {
|
|||||||
$qp->{_auth_mechanism} = undef;
|
$qp->{_auth_mechanism} = undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub __config_dir {
|
||||||
|
my $dir = $qp->config_dir('logging');
|
||||||
|
ok($dir, "config_dir, $dir");
|
||||||
|
|
||||||
|
#warn Data::Dumper::Dumper($Qpsmtpd::config_dir_memo{logging});
|
||||||
|
$dir = $Qpsmtpd::Config::dir_memo{logging};
|
||||||
|
ok($dir, "config_dir, $dir (memo)");
|
||||||
|
}
|
||||||
|
|
||||||
sub __config {
|
sub __config {
|
||||||
my @r = $qp->config('badhelo');
|
my @r = $qp->config('badhelo');
|
||||||
ok($r[0], "config, badhelo, @r");
|
ok($r[0], "config, badhelo, @r");
|
||||||
|
|
||||||
my $a = FakeAddress->new(test => 'test value');
|
my $a = FakeAddress->new(test => 'test value');
|
||||||
ok(my ($qp, $cxn) = Test::Qpsmtpd->new_conn(), "get new connection");
|
ok(my ($qp, $cxn) = Test::Qpsmtpd->new_conn(), "get new connection");
|
||||||
my @test_data = (
|
my @test_data = (
|
||||||
@ -250,6 +257,8 @@ sub __config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
package FakeAddress;
|
package FakeAddress;
|
||||||
|
|
||||||
sub new {
|
sub new {
|
||||||
@ -258,3 +267,5 @@ sub new {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub address { } # pass the can('address') conditional
|
sub address { } # pass the can('address') conditional
|
||||||
|
|
||||||
|
1;
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
#!/usr/bin/perl -w
|
|
||||||
use Test::More qw(no_plan);
|
|
||||||
use File::Path;
|
|
||||||
use strict;
|
|
||||||
use lib 't';
|
|
||||||
use_ok('Test::Qpsmtpd');
|
|
||||||
|
|
||||||
BEGIN { # need this to happen before anything else
|
|
||||||
my $cwd = `pwd`;
|
|
||||||
chomp($cwd);
|
|
||||||
open my $spooldir, '>', "./config.sample/spool_dir";
|
|
||||||
print $spooldir "$cwd/t/tmp";
|
|
||||||
close $spooldir;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok(my ($smtpd, $conn) = Test::Qpsmtpd->new_conn(), "get new connection");
|
|
||||||
|
|
||||||
my ($spool_dir, $tempfile, $tempdir) =
|
|
||||||
($smtpd->spool_dir, $smtpd->temp_file(), $smtpd->temp_dir());
|
|
||||||
|
|
||||||
ok($spool_dir =~ m!t/tmp/$!, "Located the spool directory");
|
|
||||||
ok($tempfile =~ /^$spool_dir/, "Temporary filename");
|
|
||||||
ok($tempdir =~ /^$spool_dir/, "Temporary directory");
|
|
||||||
ok(-d $tempdir, "And that directory exists");
|
|
||||||
|
|
||||||
unlink "./config.sample/spool_dir";
|
|
||||||
rmtree($spool_dir);
|
|
Loading…
Reference in New Issue
Block a user