2014-09-16 08:41:31 +02:00
|
|
|
package Qpsmtpd::Config;
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
use Sys::Hostname;
|
|
|
|
|
|
|
|
use lib 'lib';
|
|
|
|
use parent 'Qpsmtpd::Base';
|
|
|
|
use Qpsmtpd::Constants;
|
|
|
|
|
2014-09-16 09:54:47 +02:00
|
|
|
our %config_cache = ();
|
2014-09-16 08:41:31 +02:00
|
|
|
our %dir_memo;
|
|
|
|
our %defaults = (
|
2014-09-16 18:52:05 +02:00
|
|
|
me => hostname,
|
|
|
|
timeout => 1200,
|
|
|
|
);
|
2014-09-16 08:41:31 +02:00
|
|
|
|
|
|
|
sub log {
|
|
|
|
my ($self, $trace, @log) = @_;
|
2014-09-16 18:52:05 +02:00
|
|
|
|
2014-09-16 08:41:31 +02:00
|
|
|
# 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";
|
|
|
|
}
|
|
|
|
|
2014-09-16 18:26:55 +02:00
|
|
|
sub config {
|
|
|
|
my ($self, $qp, $c, $type) = @_;
|
|
|
|
|
|
|
|
$qp->log(LOGDEBUG, "in config($c)");
|
|
|
|
|
|
|
|
# first run the user_config hooks
|
|
|
|
my ($rc, @config);
|
|
|
|
if (ref $type && $type->can('address')) {
|
|
|
|
($rc, @config) = $qp->run_hooks_no_respond('user_config', $type, $c);
|
|
|
|
if (defined $rc && $rc == OK) {
|
|
|
|
return wantarray ? @config : $config[0];
|
2014-09-16 18:52:05 +02:00
|
|
|
}
|
|
|
|
}
|
2014-09-16 18:26:55 +02:00
|
|
|
|
|
|
|
# then run the config hooks
|
|
|
|
($rc, @config) = $qp->run_hooks_no_respond('config', $c);
|
|
|
|
$qp->log(LOGDEBUG,
|
2014-09-16 18:52:05 +02:00
|
|
|
"config($c): hook returned ("
|
|
|
|
. join(',', map { defined $_ ? $_ : 'undef' } ($rc, @config))
|
|
|
|
. ")"
|
|
|
|
);
|
2014-09-16 18:26:55 +02:00
|
|
|
if (defined $rc && $rc == OK) {
|
|
|
|
return wantarray ? @config : $config[0];
|
2014-09-16 18:52:05 +02:00
|
|
|
}
|
2014-09-16 18:26:55 +02:00
|
|
|
|
|
|
|
# then qmail
|
|
|
|
@config = $self->get_qmail($c, $type);
|
|
|
|
return wantarray ? @config : $config[0] if @config;
|
|
|
|
|
|
|
|
# then the default, which may be undefined
|
|
|
|
return $self->default($c);
|
|
|
|
}
|
|
|
|
|
2014-09-16 08:41:31 +02:00
|
|
|
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 {
|
2014-09-16 09:54:47 +02:00
|
|
|
%config_cache = ();
|
2014-09-16 18:52:05 +02:00
|
|
|
%dir_memo = ();
|
2014-09-16 08:41:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub default {
|
|
|
|
my ($self, $def) = @_;
|
2014-09-16 18:52:05 +02:00
|
|
|
return if !exists $defaults{$def};
|
2014-09-16 08:41:31 +02:00
|
|
|
return wantarray ? ($defaults{$def}) : $defaults{$def};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_qmail {
|
|
|
|
my ($self, $config, $type) = @_;
|
|
|
|
$self->log(LOGDEBUG, "trying to get config for $config");
|
|
|
|
|
|
|
|
# CDB config support really should be moved to a plugin
|
|
|
|
if ($type and $type eq "map") {
|
2014-09-17 18:11:47 +02:00
|
|
|
return $self->get_qmail_map($config);
|
2014-09-16 08:41:31 +02:00
|
|
|
}
|
|
|
|
|
2014-09-17 18:11:47 +02:00
|
|
|
return $self->from_file($config);
|
2014-09-16 08:41:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub get_qmail_map {
|
2014-09-17 18:11:47 +02:00
|
|
|
my ($self, $config, $file) = @_;
|
|
|
|
|
|
|
|
$file ||= $self->config_dir($config) . "/$config.cdb";
|
2014-09-16 08:41:31 +02:00
|
|
|
|
2014-09-17 18:11:47 +02:00
|
|
|
if (!-e $file) {
|
|
|
|
$self->log(LOGDEBUG, "File $file does not exist");
|
2014-09-16 09:54:47 +02:00
|
|
|
$config_cache{$config} ||= [];
|
2014-09-16 08:41:31 +02:00
|
|
|
return +{};
|
|
|
|
}
|
|
|
|
eval { require CDB_File };
|
|
|
|
|
|
|
|
if ($@) {
|
2014-09-17 18:11:47 +02:00
|
|
|
$self->log(LOGERROR, "No CDB Support! Did NOT read $file, could not load CDB_File: $@");
|
2014-09-16 08:41:31 +02:00
|
|
|
return +{};
|
|
|
|
}
|
|
|
|
|
|
|
|
my %h;
|
2014-09-17 18:11:47 +02:00
|
|
|
unless (tie(%h, 'CDB_File', $file)) {
|
|
|
|
$self->log(LOGERROR, "tie of $file failed: $!");
|
2014-09-16 08:41:31 +02:00
|
|
|
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 {
|
2014-09-17 18:11:47 +02:00
|
|
|
my ($self, $config, $file, $visited) = @_;
|
|
|
|
$file ||= $self->config_dir($config) . "/$config";
|
|
|
|
|
|
|
|
if (!-e $file) {
|
2014-09-16 09:54:47 +02:00
|
|
|
$config_cache{$config} ||= [];
|
2014-09-16 08:41:31 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$visited ||= [];
|
2014-09-17 18:11:47 +02:00
|
|
|
push @$visited, $file;
|
2014-09-16 08:41:31 +02:00
|
|
|
|
2014-09-17 18:11:47 +02:00
|
|
|
open my $CF, '<', $file or do {
|
|
|
|
warn "$$ could not open configfile $file: $!";
|
2014-09-16 08:41:31 +02:00
|
|
|
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;
|
|
|
|
|
2014-09-17 18:11:47 +02:00
|
|
|
for my $inc ($self->expand_inclusion($inclusion, $file)) {
|
|
|
|
my @insertion = $self->from_file($config, $inc, $visited);
|
2014-09-16 08:41:31 +02:00
|
|
|
splice @config, $pos, 0, @insertion; # insert the inclusion
|
|
|
|
$pos += @insertion;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$pos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-16 09:54:47 +02:00
|
|
|
$config_cache{$config} = \@config;
|
2014-09-16 08:41:31 +02:00
|
|
|
|
|
|
|
return wantarray ? @config : $config[0];
|
|
|
|
}
|
|
|
|
|
2014-09-16 09:54:47 +02:00
|
|
|
sub expand_inclusion {
|
2014-09-16 08:41:31 +02:00
|
|
|
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;
|