mirror of
https://git.proxmox.com/git/proxmox-spamassassin
synced 2025-04-28 16:01:29 +00:00
263 lines
9.5 KiB
Perl
Executable File
263 lines
9.5 KiB
Perl
Executable File
#!/usr/bin/perl -T
|
|
|
|
use lib '.'; use lib 't';
|
|
use SATest; sa_t_init("cross_user_config_leak");
|
|
use Test::More tests => 6;
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# bug 6003
|
|
|
|
# TODO: we could also do this by having a boolean attribute on the command
|
|
# structure itself to indicate that this test is superfluous. But that's
|
|
# exposing a test-only feature through production code, so right now in my opinion
|
|
# this is cleaner.
|
|
#
|
|
my @ignored_commands = qw(
|
|
|
|
score header body uri rawbody full meta test loadplugin tryplugin
|
|
version_tag uri_detail uridnssub uridnsbl urirhsbl urirhssub urinsrhsbl
|
|
urinsrhssub urifullnsrhsbl urifullnsrhssub add_header remove_header
|
|
redirector_pattern reuse mimeheader rbl_timeout uridnsbl_timeout
|
|
util_rb_tld util_rb_2tld util_rb_3tld shortcircuit asn_lookup asn_lookup_ipv6
|
|
|
|
);
|
|
|
|
use strict;
|
|
use warnings;
|
|
require Mail::SpamAssassin;
|
|
|
|
my $sa = create_saobj({
|
|
rules_filename => $localrules,
|
|
site_rules_filename => $siterules,
|
|
userprefs_filename => $userrules,
|
|
require_rules => 1,
|
|
local_tests_only => 1,
|
|
dont_copy_prefs => 1,
|
|
#debug=>1,
|
|
});
|
|
|
|
$sa->compile_now(0,1);
|
|
ok($sa);
|
|
|
|
print "Copying config to backup\n";
|
|
my %conf_backup;
|
|
$sa->copy_config(undef, \%conf_backup) or die "copy_config failed";
|
|
ok(scalar keys %conf_backup > 2);
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# these need to be pretty improbable so they won't crop up in the defaults
|
|
my $EXPECTED_VAL_STRING = '__test_expected_str';
|
|
my $EXPECTED_VAL_BOOL = 1;
|
|
my $EXPECTED_VAL_BOOL_FALSE = 0;
|
|
my $EXPECTED_VAL_NUMERIC = 9438234;
|
|
my $EXPECTED_VAL_TEMPLATE = '__test_expected_tmpl';
|
|
my $EXPECTED_VAL_HK_KEY = '__test_expected_hk_key';
|
|
my $EXPECTED_VAL_HK_VALUE = '__test_expected_hk_val';
|
|
my $EXPECTED_VAL_ADDRLIST = '__test_expected_foo@bar.com';
|
|
my $EXPECTED_VAL_NOARGS = '__test_expected_noargs';
|
|
my $EXPECTED_VAL_STRINGLIST = [qw(__test_expected_s1 __test_expected_s2)];
|
|
my $EXPECTED_VAL_IPADDRLIST = '__test_expected_';
|
|
my $EXPECTED_VAL_DURATION = 9438234;
|
|
|
|
my %expected_val;
|
|
my %ignored_command;
|
|
foreach my $k (@ignored_commands) { $ignored_command{$k}++; }
|
|
|
|
print "Reading $workdir/user_prefs1\n";
|
|
$sa->read_scoreonly_config("$workdir/user_prefs1");
|
|
set_all_confs($sa->{conf});
|
|
|
|
$sa->signal_user_changed( { username => "user1", user_dir => "$workdir/user1" });
|
|
ok validate_all_confs($sa->{conf}, 1, 'after first user config read');
|
|
|
|
print "Restoring config from backup\n";
|
|
$sa->copy_config(\%conf_backup, undef) or die "copy_config failed";
|
|
ok validate_all_confs($sa->{conf}, 0, 'after restoring from backup');
|
|
|
|
|
|
print "Reading $workdir/user_prefs2\n";
|
|
$sa->read_scoreonly_config("$workdir/user_prefs2");
|
|
$sa->signal_user_changed( { username => "user2", user_dir => "$workdir/user2" });
|
|
ok validate_all_confs($sa->{conf}, 0, 'after second user config read');
|
|
|
|
print "Restoring config from backup, second time\n";
|
|
$sa->copy_config(\%conf_backup, undef) or die "copy_config failed";
|
|
ok validate_all_confs($sa->{conf}, 0, 'after second restore from backup');
|
|
exit;
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
sub set_all_confs {
|
|
my ($conf) = @_;
|
|
foreach my $cmd (@{$conf->{registered_commands}}) {
|
|
my $k = $cmd->{setting};
|
|
|
|
if (!defined $cmd->{type}) {
|
|
next if $ignored_command{$k};
|
|
next if ($cmd->{command} && $ignored_command{$cmd->{command}});
|
|
|
|
# administrative commands by definition cannot change between users
|
|
next if ($cmd->{is_admin});
|
|
|
|
# attempt to infer types from the default value; if it's a scalar,
|
|
# we can consider the type to be similarly scalar
|
|
my $def = $cmd->{default};
|
|
if (defined $def && ref $def =~ /SCALAR/) {
|
|
if ("".$def =~ /[^\.\-\d]/) {
|
|
$cmd->{type} = $Mail::SpamAssassin::Conf::CONF_TYPE_STRING;
|
|
} else {
|
|
$cmd->{type} = $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC;
|
|
# we don't actually have to differentiate booleans and numeric,
|
|
# they're stored the same anyway
|
|
}
|
|
}
|
|
|
|
# ignore commands defined using custom code; we don't know how/what they
|
|
# store. Off for now; there's a lot of risk that we'll miss a bug if we
|
|
# don't pay attention to them anyway. They can be dealt with on a
|
|
# case-by-case basis using @ignored_commands instead.
|
|
#
|
|
##next if defined $cmd->{code};
|
|
}
|
|
|
|
if (!defined $cmd->{type}) {
|
|
warn "undef config type for $k".
|
|
($cmd->{command} ? " (command=$cmd->{command})" : "");
|
|
next;
|
|
}
|
|
|
|
if ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_NOARGS) {
|
|
$conf->{$k} = $EXPECTED_VAL_NOARGS;
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_STRING) {
|
|
$conf->{$k} = $EXPECTED_VAL_STRING;
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL) {
|
|
if ($cmd->{default} != $EXPECTED_VAL_BOOL) {
|
|
$conf->{$k} = $EXPECTED_VAL_BOOL;
|
|
} else {
|
|
# we can't use the same value as the default, otherwise we'll
|
|
# be unable to tell cases where the config has been leaked
|
|
# from cases where the default is in use
|
|
$conf->{$k} = $EXPECTED_VAL_BOOL_FALSE;
|
|
}
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC) {
|
|
$conf->{$k} = $EXPECTED_VAL_NUMERIC;
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_DURATION) {
|
|
$conf->{$k} = $EXPECTED_VAL_NUMERIC;
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_TEMPLATE) {
|
|
$conf->{$k} = $EXPECTED_VAL_TEMPLATE;
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_STRINGLIST) {
|
|
$conf->{$k} = [@$EXPECTED_VAL_STRINGLIST];
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_IPADDRLIST) {
|
|
$conf->{$k} = $EXPECTED_VAL_IPADDRLIST;
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE) {
|
|
$conf->{$k}->{$EXPECTED_VAL_HK_KEY} = $EXPECTED_VAL_HK_VALUE;
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_ADDRLIST) {
|
|
$conf->add_to_addrlist($k, $EXPECTED_VAL_ADDRLIST);
|
|
}
|
|
|
|
if (ref $conf->{$k} eq 'ARRAY') {
|
|
@{$expected_val{$k}} = @{$conf->{$k}}; # ensure this copies!
|
|
} elsif (ref $conf->{$k} eq 'HASH') {
|
|
%{$expected_val{$k}} = %{$conf->{$k}};
|
|
} else {
|
|
$expected_val{$k} = $conf->{$k};
|
|
}
|
|
}
|
|
}
|
|
|
|
my $setting_details;
|
|
my $validation_passed;
|
|
my $settings_should_exist;
|
|
|
|
sub validate_all_confs {
|
|
my ($conf, $exist, $stage) = @_;
|
|
|
|
$setting_details = '';
|
|
$validation_passed = 1;
|
|
$settings_should_exist = $exist;
|
|
|
|
foreach my $cmd (@{$conf->{registered_commands}}) {
|
|
my $k = $cmd->{setting};
|
|
|
|
# if the default value is undef, it's a permitted value, obvs
|
|
next if ($settings_should_exist && !defined $cmd->{default});
|
|
|
|
# ignore use_dcc etc changed default from data/01_test_rules.cf
|
|
next if $k =~ /^use_(?:dcc|razor2|pyzor)$/;
|
|
|
|
$setting_details = "key='$k' when=$stage";
|
|
if (!defined $cmd->{type}) {
|
|
# warn "undef config type for $k"; # already done this
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_NOARGS) {
|
|
assert_validation($conf->{$k}, $expected_val{$k});
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_STRING) {
|
|
assert_validation($conf->{$k}, $expected_val{$k});
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL) {
|
|
assert_validation($conf->{$k}, $expected_val{$k});
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC) {
|
|
assert_validation($conf->{$k}, $expected_val{$k});
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_DURATION) {
|
|
assert_validation($conf->{$k}, $expected_val{$k});
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_TEMPLATE) {
|
|
assert_validation($conf->{$k}, $expected_val{$k});
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_STRINGLIST) {
|
|
# flatten for comparison
|
|
my $val = $conf->{$k} ? join(" ", @{$conf->{$k}}) : undef;
|
|
my $exp_val = $expected_val{$k} ? join(" ", @{$expected_val{$k}}) : undef;
|
|
assert_validation($val, $exp_val);
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_IPADDRLIST) {
|
|
assert_validation($conf->{$k}, $expected_val{$k});
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_HASH_KEY_VALUE) {
|
|
my $val = $conf->{$k}->{$EXPECTED_VAL_HK_KEY};
|
|
assert_validation($val, $EXPECTED_VAL_HK_VALUE);
|
|
}
|
|
elsif ($cmd->{type} == $Mail::SpamAssassin::Conf::CONF_TYPE_ADDRLIST) {
|
|
my $val = $conf->{$k}->{$EXPECTED_VAL_ADDRLIST};
|
|
if (($settings_should_exist && !defined $val)
|
|
|| (!$settings_should_exist && $val))
|
|
{
|
|
assert_validation($k, $val, 0); # this will fail, which is what we want
|
|
}
|
|
} else {
|
|
warn "unknown config type: $cmd->{type} for $k";
|
|
}
|
|
}
|
|
return $validation_passed;
|
|
}
|
|
|
|
sub assert_validation {
|
|
my ($val, $expected_val) = @_;
|
|
if ($settings_should_exist && (!defined $val || $val ne $expected_val)) {
|
|
warn "found=".(defined $val ? "'$val'" : "(none)").
|
|
" wanted=".(defined $expected_val ? "'$expected_val'" : "(none)").
|
|
" $setting_details";
|
|
$validation_passed = 0;
|
|
$keep_workdir = 1;
|
|
}
|
|
if (!$settings_should_exist && defined($val) && "".$val eq "".$expected_val) {
|
|
warn "found=".(defined $val ? "'$val'" : "(none)")." wanted=(none)".
|
|
" $setting_details";
|
|
$validation_passed = 0;
|
|
$keep_workdir = 1;
|
|
}
|
|
}
|