You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by jm...@apache.org on 2007/07/20 14:51:30 UTC

svn commit: r557985 - in /spamassassin/branches/jm_bug_3852_two_level_configs: ./ lib/Mail/ lib/Mail/SpamAssassin/ lib/Mail/SpamAssassin/Conf/ lib/Mail/SpamAssassin/Plugin/ lib/Mail/SpamAssassin/Util/ t/

Author: jm
Date: Fri Jul 20 05:51:28 2007
New Revision: 557985

URL: http://svn.apache.org/viewvc?view=rev&rev=557985
Log:
add a new double-level hash type, which transparently presents a hash API for an underlying two-tier pair of hashes; continue converting {conf}->{foo} => {conf}->cf_foo for scalars

Added:
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util/TwoTierArray.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util/TwoTierHash.pm
Modified:
    spamassassin/branches/jm_bug_3852_two_level_configs/MANIFEST
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Conf.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Conf/Parser.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Dns.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/PerMsgStatus.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/AutoLearnThreshold.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/Check.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/HTMLEval.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/HeaderEval.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/MIMEEval.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util.pm
    spamassassin/branches/jm_bug_3852_two_level_configs/t/priorities.t

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/MANIFEST
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/MANIFEST?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/MANIFEST (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/MANIFEST Fri Jul 20 05:51:28 2007
@@ -504,3 +504,5 @@
 t/root_spamd_x_u.t
 t/spamc_x_E_R.t
 t/spamc_x_e.t
+lib/Mail/SpamAssassin/Util/TwoTierArray.pm
+lib/Mail/SpamAssassin/Util/TwoTierHash.pm

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin.pm?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin.pm (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin.pm Fri Jul 20 05:51:28 2007
@@ -349,7 +349,7 @@
     $self->{username} = (Mail::SpamAssassin::Util::portable_getpwuid ($>))[0];
   }
 
-  $self->create_locker();
+  $self->create_locker(1);
 
   $self->{resolver} = Mail::SpamAssassin::DnsResolver->new($self);
 
@@ -357,16 +357,19 @@
 }
 
 sub create_locker {
-  my ($self) = @_;
+  my ($self, $withoutconf) = @_;
 
   my $class;
-  my $m = $self->{conf}->{lock_method};
+  my $m;
+  if (!$withoutconf) {
+    $m = $self->{conf}->cf_lock_method;
+  }
 
   # let people choose what they want -- even if they may not work on their
   # OS.  (they could be using cygwin!)
-  if ($m eq 'win32') { $class = 'Win32'; }
-  elsif ($m eq 'flock') { $class = 'Flock'; }
-  elsif ($m eq 'nfssafe') { $class = 'UnixNFSSafe'; }
+  if ($m && $m eq 'win32') { $class = 'Win32'; }
+  elsif ($m && $m eq 'flock') { $class = 'Flock'; }
+  elsif ($m && $m eq 'nfssafe') { $class = 'UnixNFSSafe'; }
   else {
     # OS-specific defaults
     if (Mail::SpamAssassin::Util::am_running_on_windows()) {

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Conf.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Conf.pm?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Conf.pm (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Conf.pm Fri Jul 20 05:51:28 2007
@@ -82,6 +82,8 @@
 use Mail::SpamAssassin::Conf::Parser;
 use Mail::SpamAssassin::Logger;
 use Mail::SpamAssassin::Util::TieOneStringHash;
+use Mail::SpamAssassin::Util::TwoTierHash;
+use Mail::SpamAssassin::Util::TwoTierArray;
 use File::Spec;
 
 use strict;
@@ -242,7 +244,7 @@
 	}
       }
 
-      if ($relative && !exists $self->{scoreset}->[0]->{$rule}) {
+      if ($relative && !defined($self->get_score_for_rule($rule))) {
         info("config: score: relative score without previous setting in " .
 	  "configuration");
         return $INVALID_VALUE;
@@ -256,11 +258,16 @@
 
         # Set the actual scoreset values appropriately
         for my $index (0..3) {
-          my $score = $relative ?
-            $self->{scoreset}->[$index]->{$rule} + $scores[$index] :
-            $scores[$index];
-
-          $self->{scoreset}->[$index]->{$rule} = $score + 0.0;
+          my $score;
+          if ($relative) {
+            my $base = (defined $self->{tiers}->[1]->{scoreset}->[$index]->{$rule})
+                          ? $self->{tiers}->[1]->{scoreset}->[$index]->{$rule}
+                          : $self->{tiers}->[0]->{scoreset}->[$index]->{$rule};
+            $score = $base + $scores[$index] + 0.0;
+          } else {
+            $score = $scores[$index] + 0.0;
+          }
+          $self->{activetier}->{scoreset}->[$index]->{$rule} = $score;
         }
       }
     }
@@ -661,14 +668,16 @@
       # We only deal with From, Subject, and To ...
       elsif ($hdr =~ /^(?:From|Subject|To)$/) {
 	unless (defined $string && $string =~ /\S/) {
-	  delete $self->{rewrite_header}->{$hdr};
+          # undef => "deleted". we cannot really delete since that
+          # will kill lower tiers
+          $self->{activetier}->{rewrite_header}->{$hdr} = undef;
 	  return;
 	}
 
 	if ($hdr ne 'Subject') {
           $string =~ tr/()/[]/;
 	}
-        $self->{rewrite_header}->{$hdr} = $string;
+        $self->{activetier}->{rewrite_header}->{$hdr} = $string;
         return;
       }
       else {
@@ -676,7 +685,8 @@
 	info("config: rewrite_header: ignoring $hdr, not From, Subject, or To");
 	return $INVALID_VALUE;
       }
-    }
+    },
+    type => $CONF_TYPE_HASH_KEY_VALUE
   });
 
 =item add_header { spam | ham | all } header_name string
@@ -735,10 +745,10 @@
       $hline = join("\\", @line);
       chop($hline);  # remove dummy newline again
       if (($type eq "ham") || ($type eq "all")) {
-        $self->{headers_ham}->{$name} = $hline;
+        $self->{activetier}->{headers_ham}->{$name} = $hline;
       }
       if (($type eq "spam") || ($type eq "all")) {
-        $self->{headers_spam}->{$name} = $hline;
+        $self->{activetier}->{headers_spam}->{$name} = $hline;
       }
     }
   });
@@ -770,10 +780,10 @@
       return if ( $name eq "Checker-Version" );
 
       if (($type eq "ham") || ($type eq "all")) {
-        delete $self->{headers_ham}->{$name};
+        $self->{activetier}->{headers_ham}->{$name} = undef;
       }
       if (($type eq "spam") || ($type eq "all")) {
-        delete $self->{headers_spam}->{$name};
+        $self->{activetier}->{headers_spam}->{$name} = undef;
       }
     }
   });
@@ -795,11 +805,13 @@
     setting => 'clear_headers',
     code => sub {
       my ($self, $key, $value, $line) = @_;
-      for my $name (keys %{ $self->{headers_ham} }) {
-        delete $self->{headers_ham}->{$name} if $name ne "Checker-Version";
-      }
-      for my $name (keys %{ $self->{headers_spam} }) {
-        delete $self->{headers_spam}->{$name} if $name ne "Checker-Version";
+
+      foreach my $type (qw(headers_ham headers_spam)) {
+        for my $name ($self->get_all_tier_keys($type)) {
+          next if ($name eq "Checker-Version");
+          # override/replace all others with undef
+          $self->{activetier}->{$type}->{$name} = undef;
+        }
       }
     }
   });
@@ -842,11 +854,12 @@
         return $INVALID_VALUE;
       }
 
-      $self->{report_safe} = $value+0;
-      if (! $self->{report_safe}) {
-        $self->{headers_spam}->{"Report"} = "_REPORT_";
+      $self->{activetier}->{report_safe} = $value+0;
+      if (! $self->{activetier}->{report_safe}) {
+        $self->{activetier}->{headers_spam}->{"Report"} = "_REPORT_";
       }
-    }
+    },
+    type => $CONF_TYPE_BOOL
   });
 
 =back
@@ -942,8 +955,9 @@
 	    return $INVALID_VALUE;
 	}
 
-	$self->{normalize_charset} = 1;
-    }
+	$self->{activetier}->{normalize_charset} = 1;
+    },
+    type => $CONF_TYPE_BOOL
   });
 
 
@@ -1023,9 +1037,9 @@
 	return $MISSING_REQUIRED_VALUE;
       }
       foreach my $net (split (/\s+/, $value)) {
-        $self->{trusted_networks}->add_cidr ($net);
+        $self->{activetier}->{trusted_networks}->add_cidr ($net);
       }
-      $self->{trusted_networks_configured} = 1;
+      $self->{activetier}->{trusted_networks_configured} = 1;
     }
   });
 
@@ -1039,8 +1053,8 @@
     setting => 'clear_trusted_networks',
     code => sub {
       my ($self, $key, $value, $line) = @_;
-      $self->{trusted_networks} = $self->new_netset();
-      $self->{trusted_networks_configured} = 0;
+      $self->{activetier}->{trusted_networks} = $self->new_netset();
+      $self->{activetier}->{trusted_networks_configured} = 0;
     }
   });
 
@@ -1080,9 +1094,9 @@
 	return $MISSING_REQUIRED_VALUE;
       }
       foreach my $net (split (/\s+/, $value)) {
-        $self->{internal_networks}->add_cidr ($net);
+        $self->{activetier}->{internal_networks}->add_cidr ($net);
       }
-      $self->{internal_networks_configured} = 1;
+      $self->{activetier}->{internal_networks_configured} = 1;
     }
   });
 
@@ -1096,8 +1110,8 @@
     setting => 'clear_internal_networks',
     code => sub {
       my ($self, $key, $value, $line) = @_;
-      $self->{internal_networks} = $self->new_netset();
-      $self->{internal_networks_configured} = 0;
+      $self->{activetier}->{internal_networks} = $self->new_netset();
+      $self->{activetier}->{internal_networks_configured} = 0;
     }
   });
 
@@ -1138,9 +1152,9 @@
 	return $MISSING_REQUIRED_VALUE;
       }
       foreach my $net (split (/\s+/, $value)) {
-        $self->{msa_networks}->add_cidr ($net);
+        $self->{activetier}->{msa_networks}->add_cidr ($net);
       }
-      $self->{msa_networks_configured} = 1;
+      $self->{activetier}->{msa_networks_configured} = 1;
     }
   });
 
@@ -1154,8 +1168,8 @@
     setting => 'clear_msa_networks',
     code => sub {
       my ($self, $key, $value, $line) = @_;
-      $self->{msa_networks} = Mail::SpamAssassin::NetSet->new(); # not new_netset
-      $self->{msa_networks_configured} = 0;
+      $self->{activetier}->{msa_networks} = Mail::SpamAssassin::NetSet->new(); # not new_netset
+      $self->{activetier}->{msa_networks_configured} = 0;
     }
   });
 
@@ -1212,18 +1226,19 @@
     code => sub {
       my ($self, $key, $value, $line) = @_;
       if ($value =~ /^test(?::\s+.+)?$/) {
-        $self->{dns_available} = $value;
+        $self->{activetier}->{dns_available} = $value;
       }
       elsif ($value =~ /^(?:yes|1)$/) {
-        $self->{dns_available} = 'yes';
+        $self->{activetier}->{dns_available} = 'yes';
       }
       elsif ($value =~ /^(?:no|0)$/) {
-        $self->{dns_available} = 'no';
+        $self->{activetier}->{dns_available} = 'no';
       }
       else {
         return $INVALID_VALUE;
       }
-    }
+    },
+    type => $CONF_TYPE_STRING
   });
 
 =item dns_test_interval n   (default: 600 seconds)
@@ -1239,8 +1254,9 @@
     code => sub {
       my ($self, $key, $value, $line) = @_;
       if ($value !~ /^\d+$/) { return $INVALID_VALUE; }
-      $self->{dns_test_interval} = $value;
-    }
+      $self->{activetier}->{dns_test_interval} = $value;
+    },
+    type => $CONF_TYPE_NUMERIC
   });
 
 =back
@@ -1316,7 +1332,7 @@
       if ($value eq '') {
         return $MISSING_REQUIRED_VALUE;
       }
-      push (@{$self->{bayes_ignore_headers}}, split(/\s+/, $value));
+      push (@{$self->{activetier}->{bayes_ignore_headers}}, split(/\s+/, $value));
     }
   });
 
@@ -1533,10 +1549,11 @@
         return $INVALID_VALUE;
       }
       
-      $self->{lock_method} = $value;
+      $self->{activetier}->{lock_method} = $value;
       # recreate the locker
       $self->{main}->create_locker();
-    }
+    },
+    type => $CONF_TYPE_STRING
   });
 
 =item fold_headers ( 0 | 1 )        (default: 1)
@@ -1574,7 +1591,7 @@
       if ($value eq '') {
         return $MISSING_REQUIRED_VALUE;
       }
-      push(@{$self->{report_safe_copy_headers}}, split(/\s+/, $value));
+      push(@{$self->{activetier}->{report_safe_copy_headers}}, split(/\s+/, $value));
     }
   });
 
@@ -1799,7 +1816,8 @@
 
       $self->{allow_user_rules} = $value+0;
       dbg("config: " . ($self->{allow_user_rules} ? "allowing":"not allowing") . " user rules!");
-    }
+    },
+    type => $CONF_TYPE_BOOL
   });
 
 =item redirector_pattern	/pattern/modifiers
@@ -1834,7 +1852,7 @@
       $pattern = "(?".$3.")".$pattern if $3;
       $pattern = qr/$pattern/;
 
-      push @{$self->{main}->{conf}->{redirector_patterns}}, $pattern;
+      push @{$self->{activetier}->{redirector_patterns}}, $pattern;
       # dbg("config: adding redirector regex: " . $value);
     }
   });
@@ -2070,7 +2088,7 @@
       }
       elsif ($value =~ /^(\S+)\s+exists:(.*)$/) {
         $self->{parser}->add_test ($1, "$2 =~ /./", $TYPE_HEAD_TESTS);
-        $self->{descriptions}->{$1} = "Found a $2 header";
+        $self->{activetier}->{descriptions}->{$1} = "Found a $2 header";
       }
       else {
 	my @values = split(/\s+/, $value, 2);
@@ -3081,97 +3099,184 @@
   my $self = {
     main => shift,
     registered_commands => [],
+    plugins_loaded => { },
+    eval_plugins => { },
+    errors => 0,
   }; bless ($self, $class);
 
   $self->{parser} = Mail::SpamAssassin::Conf::Parser->new($self);
+
+  # create a new config tier for the basic, system-wide config
+  $self->{tiers}->[0] = $self->new_tier();
+  $self->{activetier} = $self->{tiers}->[0];
+  $self->create_lookup_doublehashes();
+
+  # and populate some defaults:
   $self->{parser}->register_commands($self->set_default_commands());
 
-  $self->{errors} = 0;
-  $self->{plugins_loaded} = { };
+  # Make sure we add in X-Spam-Checker-Version
+  $self->{tiers}->[0]->{headers_spam}->{"Checker-Version"} =
+                "SpamAssassin _VERSION_ (_SUBVERSION_) on _HOSTNAME_";
+  $self->{tiers}->[0]->{headers_ham}->{"Checker-Version"} =
+                $self->{tiers}->[0]->{headers_spam}->{"Checker-Version"};
 
-  $self->{tests} = { };
-  $self->{test_types} = { };
-  $self->{scoreset} = [ {}, {}, {}, {} ];
-  $self->{scoreset_current} = 0;
-  $self->set_score_set (0);
-  $self->{tflags} = { };
-  $self->{source_file} = { };
+  # these should potentially be settable by end-users
+  # perhaps via plugin?
+  $self->{num_check_received} = 9;
+  $self->{bayes_expiry_pct} = 0.75;
+  $self->{bayes_expiry_period} = 43200;
+  $self->{bayes_expiry_max_exponent} = 9;
+
+  $self->{encapsulated_content_description} = 'original message before SpamAssassin';
+
+  # testing stuff
+  $self->{regression_tests} = { };
+
+  $self;
+}
+
+sub new_tier {
+  my ($self) = @_;
+
+  my $tier = { };
+
+  # the guideline is, if a config setting is something that can be set
+  # in the user config file, and should be discarded for scanning as
+  # another user -- it should be stored in a tier rather than on $self.
+
+  $tier->{tests} = { };
+  $tier->{test_types} = { };
+  $tier->{scoreset} = [ {}, {}, {}, {} ];
+  $tier->{tflags} = { };
+  $tier->{source_file} = { };
+  $tier->{meta_dependencies} = { };
 
   # keep descriptions in a slow but space-efficient single-string
   # data structure
-  tie %{$self->{descriptions}}, 'Mail::SpamAssassin::Util::TieOneStringHash'
+  tie %{$tier->{descriptions}}, 'Mail::SpamAssassin::Util::TieOneStringHash'
     or warn "tie failed";
 
   # after parsing, tests are refiled into these hashes for each test type.
   # this allows e.g. a full-text test to be rewritten as a body test in
   # the user's user_prefs file.
-  $self->{body_tests} = { };
-  $self->{uri_tests}  = { };
-  $self->{uri_evals}  = { }; # not used/implemented yet
-  $self->{head_tests} = { };
-  $self->{head_evals} = { };
-  $self->{body_evals} = { };
-  $self->{full_tests} = { };
-  $self->{full_evals} = { };
-  $self->{rawbody_tests} = { };
-  $self->{rawbody_evals} = { };
-  $self->{meta_tests} = { };
-  $self->{eval_plugins} = { };
-  $self->{duplicate_rules} = { };
+  $tier->{body_tests} = { };
+  $tier->{uri_tests}  = { };
+  $tier->{uri_evals}  = { }; # not used/implemented yet
+  $tier->{head_tests} = { };
+  $tier->{head_evals} = { };
+  $tier->{body_evals} = { };
+  $tier->{full_tests} = { };
+  $tier->{full_evals} = { };
+  $tier->{rawbody_tests} = { };
+  $tier->{rawbody_evals} = { };
+  $tier->{meta_tests} = { };
+  $tier->{duplicate_rules} = { };
+
+  $tier->{rewrite_header} = { };
+  $tier->{user_rules_to_compile} = { };
+  $tier->{user_defined_rules} = { };
+  $tier->{headers_spam} = { };
+  $tier->{headers_ham} = { };
+  $tier->{priorities} = { };
+
+  $tier->{bayes_ignore_headers} = [ ];
+  $tier->{bayes_ignore_from} = { };
+  $tier->{bayes_ignore_to} = { };
+
+  $tier->{whitelist_auth} = { };
+  $tier->{whitelist_from} = { };
+  $tier->{whitelist_allows_relays} = { };
+  $tier->{blacklist_from} = { };
+
+  $tier->{blacklist_to} = { };
+  $tier->{whitelist_to} = { };
+  $tier->{more_spam_to} = { };
+  $tier->{all_spam_to} = { };
+
+  $tier->{trusted_networks} = $self->new_netset();
+  $tier->{internal_networks} = $self->new_netset();
+  $tier->{msa_networks} = Mail::SpamAssassin::NetSet->new(); # not new_netset
+  $tier->{trusted_networks_configured} = 0;
+  $tier->{internal_networks_configured} = 0;
 
-  # testing stuff
-  $self->{regression_tests} = { };
+  $tier->{scoreset_current} = 0;
+  $self->set_score_set (0);
+
+  return $tier;
+}
 
-  $self->{rewrite_header} = { };
-  $self->{user_rules_to_compile} = { };
-  $self->{user_defined_rules} = { };
-  $self->{headers_spam} = { };
-  $self->{headers_ham} = { };
-
-  $self->{bayes_ignore_headers} = [ ];
-  $self->{bayes_ignore_from} = { };
-  $self->{bayes_ignore_to} = { };
-
-  $self->{whitelist_auth} = { };
-  $self->{whitelist_from} = { };
-  $self->{whitelist_allows_relays} = { };
-  $self->{blacklist_from} = { };
-
-  $self->{blacklist_to} = { };
-  $self->{whitelist_to} = { };
-  $self->{more_spam_to} = { };
-  $self->{all_spam_to} = { };
-
-  $self->{trusted_networks} = $self->new_netset();
-  $self->{internal_networks} = $self->new_netset();
-  $self->{msa_networks} = Mail::SpamAssassin::NetSet->new(); # not new_netset
-  $self->{trusted_networks_configured} = 0;
-  $self->{internal_networks_configured} = 0;
+# create a new config tier for user configuration
+sub push_tier {
+  my ($self) = @_;
+  $self->{tiers}->[1] = $self->new_tier();
+  $self->{activetier} = $self->{tiers}->[1];
+  $self->create_lookup_doublehashes();
+}
 
-  # Make sure we add in X-Spam-Checker-Version
-  $self->{headers_spam}->{"Checker-Version"} =
-                "SpamAssassin _VERSION_ (_SUBVERSION_) on _HOSTNAME_";
-  $self->{headers_ham}->{"Checker-Version"} =
-                $self->{headers_spam}->{"Checker-Version"};
+# and delete it once the user is no longer active
+sub pop_tier {
+  my ($self) = @_;
+  delete $self->{tiers}->[1];
+  $self->{activetier} = $self->{tiers}->[0];
+}
 
-  # these should potentially be settable by end-users
-  # perhaps via plugin?
-  $self->{num_check_received} = 9;
-  $self->{bayes_expiry_pct} = 0.75;
-  $self->{bayes_expiry_period} = 43200;
-  $self->{bayes_expiry_max_exponent} = 9;
+# create "double-hash" lookup structures that point into the 2 tiers of
+# config data, providing a transparent UI so that calling code doesn't have
+# to know the gritty details in many cases
+sub create_lookup_doublehashes {
+  my ($self) = @_;
+  foreach my $hash (qw(
 
-  $self->{encapsulated_content_description} = 'original message before SpamAssassin';
+      tflags meta_dependencies source_file priorities rewrite_header
+      headers_spam headers_ham duplicate_rules rules_to_replace
+      test_types priority
 
-  $self;
+    ))
+  {
+    $self->new_two_tier_hash($hash);
+  }
+  foreach my $hash (qw(
+
+      report_safe_copy_headers redirector_patterns
+
+    ))
+  {
+    $self->new_two_tier_array($hash);
+  }
+}
+
+sub new_two_tier_hash {
+  my ($self, $name, $t0, $t1) = @_;
+  if (!defined $t0) {
+    $t0 = $self->{tiers}->[0]->{$name} = { };
+  }
+  if (!defined $t1) {
+    $t1 = $self->{tiers}->[1]->{$name} = { };
+  }
+  delete $self->{$name};
+  tie %{$self->{$name}}, 'Mail::SpamAssassin::Util::TwoTierHash', $t0, $t1
+      or warn "tie failed";
+}
+
+sub new_two_tier_array {
+  my ($self, $name, $t0, $t1) = @_;
+  if (!defined $t0) {
+    $t0 = $self->{tiers}->[0]->{$name} = [ ];
+  }
+  if (!defined $t1) {
+    $t1 = $self->{tiers}->[1]->{$name} = [ ];
+  }
+  delete $self->{$name};
+  tie @{$self->{$name}}, 'Mail::SpamAssassin::Util::TwoTierArray', $t0, $t1
+      or warn "tie failed";
 }
 
 sub mtime {
   my $self = shift;
   if (@_) {
-    $self->{mtime} = shift;
+    $self->{activetier}->{mtime} = shift;
   }
-  return $self->{mtime};
+  return $self->{activetier}->{mtime};
 }
 
 ###########################################################################
@@ -3190,77 +3295,47 @@
 
 sub set_score_set {
   my ($self, $set) = @_;
-  $self->{scores} = $self->{scoreset}->[$set];
-  $self->{scoreset_current} = $set;
+  $self->{activetier}->{scoreset_current} = $set;
+
+  # use TwoTierHash objects to represent certain hashes; lookups
+  # in these "hashes" will fall through correctly to the other tiers.
+  $self->new_two_tier_hash('scores',
+            $self->{tiers}->[0]->{scoreset}->[$set],
+            $self->{tiers}->[1]->{scoreset}->[$set]);
+
   dbg("config: score set $set chosen.");
 }
 
 sub get_score_set {
   my($self) = @_;
-  return $self->{scoreset_current};
-}
-
-sub get_rule_types {
-  my ($self) = @_;
-  return @rule_types;
+  return $self->{activetier}->{scoreset_current};
 }
 
-sub get_rule_keys {
-  my ($self, $test_type, $priority) = @_;
+sub delete_rule {
+  my ($self, $type, $rulename, $priority) = @_;
 
   # special case rbl_evals since they do not have a priority
-  if ($test_type eq 'rbl_evals') {
-    return keys(%{$self->{$test_type}});
-  }
-
-  if (defined($priority)) {
-    return keys(%{$self->{$test_type}->{$priority}});
-  }
-  else {
-    my @rules;
-    foreach my $pri (keys(%{$self->{priorities}})) {
-      push(@rules, keys(%{$self->{$test_type}->{$pri}}));
+  if ($type eq 'rbl_evals') {
+    if ($self->{tiers}->[1]->{$type}->{$rulename}) {
+      return delete $self->{tiers}->[1]->{$type}->{$rulename};
+    } else {
+      return delete $self->{tiers}->[0]->{$type}->{$rulename};
     }
-    return @rules;
-  }
-}
-
-sub get_rule_value {
-  my ($self, $test_type, $rulename, $priority) = @_;
-
-  # special case rbl_evals since they do not have a priority
-  if ($test_type eq 'rbl_evals') {
-    return keys(%{$self->{$test_type}->{$rulename}});
   }
 
   if (defined($priority)) {
-    return $self->{$test_type}->{$priority}->{$rulename};
-  }
-  else {
-    foreach my $pri (keys(%{$self->{priorities}})) {
-      if (exists($self->{$test_type}->{$pri}->{$rulename})) {
-        return $self->{$test_type}->{$pri}->{$rulename};
-      }
+    if ($self->{tiers}->[1]->{$type}->{$priority}->{$rulename}) {
+      return delete $self->{tiers}->[1]->{$type}->{$priority}->{$rulename};
+    } else {
+      return delete $self->{tiers}->[0]->{$type}->{$priority}->{$rulename};
     }
-    return undef; # if we get here we didn't find the rule
-  }
-}
-
-sub delete_rule {
-  my ($self, $test_type, $rulename, $priority) = @_;
-
-  # special case rbl_evals since they do not have a priority
-  if ($test_type eq 'rbl_evals') {
-    return delete($self->{$test_type}->{$rulename});
-  }
-
-  if (defined($priority)) {
-    return delete($self->{$test_type}->{$priority}->{$rulename});
   }
   else {
-    foreach my $pri (keys(%{$self->{priorities}})) {
-      if (exists($self->{$test_type}->{$pri}->{$rulename})) {
-        return delete($self->{$test_type}->{$pri}->{$rulename});
+    foreach my $pri ($self->get_all_tier_keys("priorities")) {
+      if ($self->{tiers}->[1]->{$type}->{$pri}->{$rulename}) {
+        return delete $self->{tiers}->[1]->{$type}->{$pri}->{$rulename};
+      } else {
+        return delete $self->{tiers}->[0]->{$type}->{$pri}->{$rulename};
       }
     }
     return undef; # if we get here we didn't find the rule
@@ -3330,33 +3405,9 @@
 } # add_meta_depends()
 
 sub is_rule_active {
-  my ($self, $test_type, $rulename, $priority) = @_;
-
-  # special case rbl_evals since they do not have a priority
-  if ($test_type eq 'rbl_evals') {
-    return 0 unless ($self->{$test_type}->{$rulename});
-    return ($self->{scores}->{$rulename});
-  }
-
-  # first determine if the rule is defined
-  if (defined($priority)) {
-    # we have a specific priority
-    return 0 unless ($self->{$test_type}->{$priority}->{$rulename});
-  }
-  else {
-    # no specific priority so we must loop over all currently defined
-    # priorities to see if the rule is defined
-    my $found_p = 0;
-    foreach my $pri (keys %{$self->{priorities}}) {
-      if ($self->{$test_type}->{$pri}->{$rulename}) {
-        $found_p = 1;
-        last;
-      }
-    }
-    return 0 unless ($found_p);
-  }
-
-  return ($self->{scores}->{$rulename});
+  my ($self, $type, $rulename, $priority) = @_;
+  return 0 unless ($self->get_rule_value($type, $rulename, $priority));
+  return $self->get_score_for_rule($rulename);
 }
 
 ###########################################################################
@@ -3398,10 +3449,135 @@
 }
 
 ###########################################################################
+# Tiering-aware accessor methods
+
+sub get_all_tier_keys {
+  my ($self, $hashname) = @_;
+  if (defined $self->{tiers}->[1]->{$hashname}) {
+    return Mail::SpamAssassin::Util::uniq_list(
+            keys %{ $self->{tiers}->[0]->{$hashname} },
+            keys %{ $self->{tiers}->[1]->{$hashname} }
+          );
+  } else {
+    return keys %{ $self->{tiers}->[0]->{$hashname} };
+  }
+}
+
+sub get_all_tier_lists {
+  my ($self, $listname) = @_;
+  my $l0 = $self->{tiers}->[0]->{$listname};
+  my $l1 = $self->{tiers}->[1]->{$listname};
+  if ($l1) {
+    if ($l0) {
+      return Mail::SpamAssassin::Util::uniq_list(@{$l0}, @{$l1});
+    } else {
+      return @{ $l1 };
+    }
+  } else {
+    if ($l0) {
+      return @{ $l0 };
+    } else {
+      return ();
+    }
+  }
+}
+
+sub tier_lookup {
+  my ($self, $hashname, $rule) = @_;
+  # note: exists(), not defined(), since this way we can "delete" the
+  # data from a lower tier by overriding it with undef
+  if (exists $self->{tiers}->[1]->{$hashname}->{$rule}) {
+    return $self->{tiers}->[1]->{$hashname}->{$rule};
+  } else {
+    return $self->{tiers}->[0]->{$hashname}->{$rule};
+  }
+}
 
 sub get_description_for_rule {
   my ($self, $rule) = @_;
-  return $self->{descriptions}->{$rule};
+  return $self->tier_lookup("descriptions", $rule);
+}
+
+sub get_tflags_for_rule {
+  my ($self, $rule) = @_;
+  return $self->{tflags}->{$rule} || '';    # always provide a string default
+}
+
+sub get_score_for_rule {
+  my ($self, $rule) = @_;
+  return $self->{scores}->{$rule};
+}
+
+sub get_score_in_scoreset {
+  my ($self, $rule, $set) = @_;
+  if ($self->{tiers}->[1]->{scoreset}->[$set]->{$rule}) {
+    return $self->{tiers}->[1]->{scoreset}->[$set]->{$rule};
+  } else {
+    return $self->{tiers}->[0]->{scoreset}->[$set]->{$rule};
+  }
+}
+
+sub get_rule_types {
+  my ($self) = @_;
+  return @rule_types;
+}
+
+sub get_rule_keys {
+  my ($self, $type, $priority) = @_;
+
+  my @rules;
+  if ($type eq 'rbl_evals') {
+    # special case rbl_evals since they do not have a priority
+    push @rules, keys(%{$self->{tiers}->[0]->{$type}});
+    push @rules, keys(%{$self->{tiers}->[1]->{$type}});
+  }
+  elsif (defined($priority)) {
+    push @rules, keys(%{$self->{tiers}->[0]->{$type}->{$priority}});
+    push @rules, keys(%{$self->{tiers}->[1]->{$type}->{$priority}});
+  }
+  else {
+    foreach my $pri ($self->get_all_tier_keys("priorities")) {
+      push @rules, keys(%{$self->{tiers}->[0]->{$type}->{$pri}});
+      push @rules, keys(%{$self->{tiers}->[1]->{$type}->{$pri}});
+    }
+  }
+  return @rules;
+}
+
+sub get_rule_value {
+  my ($self, $type, $rulename, $priority) = @_;
+
+  # special case rbl_evals since they do not have a priority
+  if ($type eq 'rbl_evals') {
+    if ($self->{tiers}->[1]->{$type}->{$rulename}) {
+      return keys %{$self->{tiers}->[1]->{$type}->{$rulename}};
+    } else {
+      return keys %{$self->{tiers}->[0]->{$type}->{$rulename}};
+    }
+  }
+
+  if (defined($priority)) {
+    if ($self->{tiers}->[1]->{$type}->{$priority}->{$rulename}) {
+      return $self->{tiers}->[1]->{$type}->{$priority}->{$rulename};
+    } else {
+      return $self->{tiers}->[0]->{$type}->{$priority}->{$rulename};
+    }
+  }
+  else {
+    foreach my $pri ($self->get_all_tier_keys("priorities")) {
+      if ($self->{tiers}->[1]->{$type}->{$pri}->{$rulename}) {
+        return $self->{tiers}->[1]->{$type}->{$pri}->{$rulename};
+      } else {
+        return $self->{tiers}->[0]->{$type}->{$pri}->{$rulename};
+      }
+    }
+    return undef; # if we get here we didn't find the rule
+  }
+}
+
+sub get_all_priorities {
+  my ($self) = @_;
+  return $self->get_all_tier_keys('priorities');
 }
 
 ###########################################################################
@@ -3415,7 +3591,7 @@
     return 1;
 
   } elsif ($type == $TYPE_META_TESTS) {
-    my $tflags = $self->{tflags}->{$rulename}; $tflags ||= '';
+    my $tflags = $self->get_tflags_for_rule($rulename);
     if ($tflags =~ m/\bnet\b/i) {
       return 0;
     } else {
@@ -3438,7 +3614,7 @@
     return 1;
 
   } elsif ($type == $TYPE_META_TESTS) {
-    my $tflags = $self->{tflags}->{$rulename}; $tflags ||= '';
+    my $tflags = $self->get_tflags_for_rule($rulename);
     if ($tflags =~ m/\bnet\b/i) {
       return 0;
     } else {
@@ -3554,9 +3730,9 @@
   if (!$self->{main}->{keep_config_parsing_metadata} &&
         !$self->{allow_user_rules})
   {
-    delete $self->{if_stack};
-    delete $self->{source_file};
-    delete $self->{meta_dependencies};
+    delete $self->{activetier}->{if_stack};
+    delete $self->{activetier}->{source_file};
+    delete $self->{activetier}->{meta_dependencies};
   }
 }
 
@@ -3571,7 +3747,8 @@
 
 sub finish {
   my ($self) = @_;
-  untie %{$self->{descriptions}};
+  if ($self->{tiers}->[0]->{descriptions}) { untie %{$self->{tiers}->[0]->{descriptions}}; }
+  if ($self->{tiers}->[1]->{descriptions}) { untie %{$self->{tiers}->[1]->{descriptions}}; }
   %{$self} = ();
 }
 

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Conf/Parser.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Conf/Parser.pm?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Conf/Parser.pm (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Conf/Parser.pm Fri Jul 20 05:51:28 2007
@@ -64,11 +64,16 @@
            - $CONF_TYPE_HASH_KEY_VALUE: hash key/value pair,
              like "describe" or tflags
 
-If this is set, a 'code' block is assigned based on the type.
+If this is set, a 'code' block is assigned based on the type, if
+none already exists, and an accessor of the right type will be used
+for the generated $conf->cf_setting() method.
 
 Note that C<$CONF_TYPE_HASH_KEY_VALUE>-type settings require that the
 value be non-empty, otherwise they'll produce a warning message.
 
+If a type is omitted, a default accessor will be generated using the
+$CONF_TYPE_STRING type.
+
 =item code
 
 A subroutine to deal with the setting.  Only used if B<type> is not set.  ONE OF
@@ -166,7 +171,7 @@
 # Implementations for default accessors.
 # access a scalar configuration setting. e.g.:
 #   my $val = $conf->cf_foo_bar();
-my $cf_accessor_scalar_value = '
+our $cf_accessor_scalar_value = '
 
   sub cf_${SETTING} {
     if (defined $_[0]->{tiers}->[1]->{${SETTING}}) {
@@ -180,10 +185,13 @@
 
 # access a hash key=>value configuration setting. e.g.:
 #   my $val = $conf->cf_foo_bar("baz");
-my $cf_accessor_hash_key_value = '
+# note use of "exists" instead of "defined"; this is deliberate so that
+# "deletion" of a sys-level setting can take place at user-level,
+# by setting val to undef
+our $cf_accessor_hash_key_value = '
 
   sub cf_${SETTING} {
-    if (defined $_[0]->{tiers}->[1]->{${SETTING}}->{$_[1]}) {
+    if (exists $_[0]->{tiers}->[1]->{${SETTING}}->{$_[1]}) {
       return $_[0]->{tiers}->[1]->{${SETTING}}->{$_[1]};
     } else {
       return $_[0]->{tiers}->[0]->{${SETTING}}->{$_[1]};
@@ -199,7 +207,7 @@
 #   }
 # TODO: it would be more efficient to fix calling code to iterate
 # through the tiers instead of using this API.
-my $cf_accessor_addrlist_value = '
+our $cf_accessor_addrlist_value = '
 
   sub cf_${SETTING} {
     if (defined $_[0]->{tiers}->[1]->{${SETTING}}) {
@@ -236,7 +244,7 @@
     # note! exists, not defined -- we want to be able to set
     # "undef" default values.
     if (exists($cmd->{default})) {
-      $conf->{$cmd->{setting}} = $cmd->{default};
+      $conf->{activetier}->{$cmd->{setting}} = $cmd->{default};
     }
 
     $self->setup_accessor($cmd);
@@ -590,14 +598,18 @@
 
   if ($conf->{lint_rules}) {
     # Check for description and score issues in lint fashion
-    while ( my $k = each %{$conf->{descriptions}} ) {
-      if (!exists $conf->{tests}->{$k}) {
+    foreach my $k ($conf->get_all_tier_keys('descriptions')) {
+      if (!exists $conf->{tiers}->[0]->{tests}->{$k}
+        && !exists $conf->{tiers}->[1]->{tests}->{$k})
+      {
         $self->lint_warn("config: warning: description exists for non-existent rule $k\n", $k);
       }
     }
 
-    while ( my($sk) = each %{$conf->{scores}} ) {
-      if (!exists $conf->{tests}->{$sk}) {
+    foreach my $sk ($conf->get_all_tier_keys('scores')) {
+      if (!exists $conf->{tiers}->[0]->{tests}->{$sk}
+        && !exists $conf->{tiers}->[1]->{tests}->{$sk})
+      {
         $self->lint_warn("config: warning: score set for non-existent rule $sk\n", $sk);
       }
     }
@@ -613,14 +625,17 @@
   my ($self) = @_;
   my $conf = $self->{conf};
 
-  while ( my $k = each %{$conf->{tests}} ) {
-    if ( ! exists $conf->{scores}->{$k} ) {
+  my $ss = $conf->{activetier}->{scoreset};
+  while ( my $k = each %{$conf->{activetier}->{tests}} ) {
+    if ( ! exists $ss->[0]->{$k} ) {
       # T_ rules (in a testing probationary period) get low, low scores
       my $set_score = ($k =~/^T_/) ? 0.01 : 1.0;
 
-      $set_score = -$set_score if ( ($conf->{tflags}->{$k}||'') =~ /\bnice\b/ );
+      if ($conf->get_tflags_for_rule($k) =~ /\bnice\b/) {
+        $set_score = -$set_score;
+      }
       for my $index (0..3) {
-        $conf->{scoreset}->[$index]->{$k} = $set_score;
+        $ss->[$index]->{$k} = $set_score;
       }
     }
   }
@@ -632,9 +647,10 @@
   my ($self, $cmd) = @_;
 
   # find a default accessor method, if possible
-  if (!defined $cmd->{accessor} && $cmd->{type}) {
-    my $type = $cmd->{type};
-    if ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_STRING) {
+  if (!defined $cmd->{accessor}) {
+    my $type = $cmd->{accessortype} || $cmd->{type};
+
+    if (!$type || $type == $Mail::SpamAssassin::Conf::CONF_TYPE_STRING) {
       $cmd->{accessor} = $cf_accessor_scalar_value;
     }
     elsif ($type == $Mail::SpamAssassin::Conf::CONF_TYPE_BOOL) {
@@ -716,7 +732,7 @@
     return $Mail::SpamAssassin::Conf::INVALID_VALUE;
   }
 
-  $conf->{$key} = $value+0.0;
+  $conf->{activetier}->{$key} = $value+0.0;
 }
 
 sub set_bool_value {
@@ -739,7 +755,7 @@
     return $Mail::SpamAssassin::Conf::INVALID_VALUE;
   }
 
-  $conf->{$key} = $value+0;
+  $conf->{activetier}->{$key} = $value+0;
 }
 
 sub set_string_value {
@@ -749,7 +765,7 @@
     return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
   }
 
-  $conf->{$key} = $value;
+  $conf->{activetier}->{$key} = $value;
 }
 
 sub set_hash_key_value {
@@ -760,7 +776,7 @@
     return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
   }
 
-  $conf->{$key}->{$k} = $v;
+  $conf->{activetier}->{$key}->{$k} = $v;
 }
 
 sub set_addrlist_value {
@@ -784,12 +800,12 @@
 sub set_template_append {
   my ($conf, $key, $value, $line) = @_;
   if ( $value =~ /^"(.*?)"$/ ) { $value = $1; }
-  $conf->{$key} .= $value."\n";
+  $conf->{activetier}->{$key} .= $value."\n";
 }
 
 sub set_template_clear {
   my ($conf, $key, $value, $line) = @_;
-  $conf->{$key} = '';
+  $conf->{activetier}->{$key} = '';
 }
 
 ###########################################################################
@@ -812,10 +828,10 @@
 
   dbg("conf: finish parsing");
 
-  while (my ($name, $text) = each %{$conf->{tests}}) {
-    my $type = $conf->{test_types}->{$name};
+  while (my ($name, $text) = each %{$conf->{activetier}->{tests}}) {
+    my $type = $conf->{activetier}->{test_types}->{$name};
     my $priority = $conf->{priority}->{$name} || 0;
-    $conf->{priorities}->{$priority}++;
+    $conf->{activetier}->{priorities}->{$priority}++;
 
     # eval type handling
     if (($type & 1) == 1) {
@@ -827,24 +843,24 @@
           # we've already warned about this
         }
         elsif ($type == $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS) {
-          $conf->{body_evals}->{$priority}->{$name} = $packed;
+          $conf->{activetier}->{body_evals}->{$priority}->{$name} = $packed;
         }
         elsif ($type == $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS) {
-          $conf->{head_evals}->{$priority}->{$name} = $packed;
+          $conf->{activetier}->{head_evals}->{$priority}->{$name} = $packed;
         }
         elsif ($type == $Mail::SpamAssassin::Conf::TYPE_RBL_EVALS) {
           # We don't do priorities for $Mail::SpamAssassin::Conf::TYPE_RBL_EVALS
           # we also use the arrayref instead of the packed string
-          $conf->{rbl_evals}->{$name} = [ $function, @$argsref ];
+          $conf->{activetier}->{rbl_evals}->{$name} = [ $function, @$argsref ];
         }
         elsif ($type == $Mail::SpamAssassin::Conf::TYPE_RAWBODY_EVALS) {
-          $conf->{rawbody_evals}->{$priority}->{$name} = $packed;
+          $conf->{activetier}->{rawbody_evals}->{$priority}->{$name} = $packed;
         }
         elsif ($type == $Mail::SpamAssassin::Conf::TYPE_FULL_EVALS) {
-          $conf->{full_evals}->{$priority}->{$name} = $packed;
+          $conf->{activetier}->{full_evals}->{$priority}->{$name} = $packed;
         }
         #elsif ($type == $Mail::SpamAssassin::Conf::TYPE_URI_EVALS) {
-        #  $conf->{uri_evals}->{$priority}->{$name} = $packed;
+        #  $conf->{activetier}->{uri_evals}->{$priority}->{$name} = $packed;
         #}
         else {
           $self->lint_warn("unknown type $type for $name: $text", $name);
@@ -857,22 +873,22 @@
     # non-eval tests
     else {
       if ($type == $Mail::SpamAssassin::Conf::TYPE_BODY_TESTS) {
-        $conf->{body_tests}->{$priority}->{$name} = $text;
+        $conf->{activetier}->{body_tests}->{$priority}->{$name} = $text;
       }
       elsif ($type == $Mail::SpamAssassin::Conf::TYPE_HEAD_TESTS) {
-        $conf->{head_tests}->{$priority}->{$name} = $text;
+        $conf->{activetier}->{head_tests}->{$priority}->{$name} = $text;
       }
       elsif ($type == $Mail::SpamAssassin::Conf::TYPE_META_TESTS) {
-        $conf->{meta_tests}->{$priority}->{$name} = $text;
+        $conf->{activetier}->{meta_tests}->{$priority}->{$name} = $text;
       }
       elsif ($type == $Mail::SpamAssassin::Conf::TYPE_URI_TESTS) {
-        $conf->{uri_tests}->{$priority}->{$name} = $text;
+        $conf->{activetier}->{uri_tests}->{$priority}->{$name} = $text;
       }
       elsif ($type == $Mail::SpamAssassin::Conf::TYPE_RAWBODY_TESTS) {
-        $conf->{rawbody_tests}->{$priority}->{$name} = $text;
+        $conf->{activetier}->{rawbody_tests}->{$priority}->{$name} = $text;
       }
       elsif ($type == $Mail::SpamAssassin::Conf::TYPE_FULL_TESTS) {
-        $conf->{full_tests}->{$priority}->{$name} = $text;
+        $conf->{activetier}->{full_tests}->{$priority}->{$name} = $text;
       }
       else {
         $self->lint_warn("unknown type $type for $name: $text", $name);
@@ -891,25 +907,25 @@
 
   if (!$conf->{allow_user_rules}) {
     # free up stuff we no longer need
-    delete $conf->{tests};
-    delete $conf->{priority};
-    delete $conf->{test_types};
+    delete $conf->{activetier}->{tests};
+    delete $conf->{activetier}->{priority};
+    delete $conf->{activetier}->{test_types};
   }
 }
 
 sub trace_meta_dependencies {
   my ($self) = @_;
   my $conf = $self->{conf};
-  $conf->{meta_dependencies} = { };
+  %{$conf->{activetier}->{meta_dependencies}} = ( ); 
 
-  foreach my $name (keys %{$conf->{tests}}) {
-    next unless ($conf->{test_types}->{$name}
+  foreach my $name (keys %{$conf->{activetier}->{tests}}) {
+    next unless ($conf->{activetier}->{test_types}->{$name}
                     == $Mail::SpamAssassin::Conf::TYPE_META_TESTS);
 
     my $deps = [ ];
     my $alreadydone = { };
     $self->_meta_deps_recurse($conf, $name, $name, $deps, $alreadydone);
-    $conf->{meta_dependencies}->{$name} = join (' ', @{$deps});
+    $conf->{activetier}->{meta_dependencies}->{$name} = join (' ', @{$deps});
   }
 }
 
@@ -921,7 +937,7 @@
   $alreadydone->{$name} = 1;
 
   # Obviously, don't trace empty or nonexistent rules
-  my $rule = $conf->{tests}->{$name};
+  my $rule = $conf->{activetier}->{tests}->{$name};
   return unless $rule;
 
   # Lex the rule into tokens using a rather simple RE method ...
@@ -933,7 +949,7 @@
     # has to be an alpha+numeric token
     next if ($token =~ /^(?:\W+|[+-]?\d+(?:\.\d+)?)$/);
     # and has to be a rule name
-    next unless exists $conf->{tests}->{$token};
+    next unless exists $conf->{activetier}->{tests}->{$token};
 
     # add and recurse
     push @{$deps}, $token;
@@ -945,8 +961,8 @@
   my ($self) = @_;
   my $conf = $self->{conf};
 
-  die unless $conf->{meta_dependencies};    # order requirement
-  my $pri = $conf->{priority};
+  die unless $conf->{activetier}->{meta_dependencies};    # order requirement
+  my $pri = $conf->{activetier}->{priority};
 
   # sort into priority order, lowest first -- this way we ensure that if we
   # rearrange the pri of a rule early on, we cannot accidentally increase its
@@ -957,7 +973,7 @@
   {
     # we only need to worry about meta rules -- they are the
     # only type of rules which depend on other rules
-    my $deps = $conf->{meta_dependencies}->{$rule};
+    my $deps = $conf->{activetier}->{meta_dependencies}->{$rule};
     next unless (defined $deps);
 
     my $basepri = $pri->{$rule};
@@ -977,9 +993,9 @@
 
   my %names_for_text = ();
   my %dups = ();
-  while (my ($name, $text) = each %{$conf->{tests}}) {
-    my $type = $conf->{test_types}->{$name};
-    my $tf = ($conf->{tflags}->{$name}||''); $tf =~ s/\s+/ /gs;
+  while (my ($name, $text) = each %{$conf->{activetier}->{tests}}) {
+    my $type = $conf->{activetier}->{test_types}->{$name};
+    my $tf = $conf->get_tflags_for_rule($name); $tf =~ s/\s+/ /gs;
     # ensure similar, but differently-typed, rules are not marked as dups;
     # take tflags into account too due to "tflags multiple"
     $text = "$type\t$text\t$tf";
@@ -997,7 +1013,7 @@
     my $first_pri;
     my @names = sort {$a cmp $b} split(' ', $names_for_text{$text});
     foreach my $name (@names) {
-      my $priority = $conf->{priority}->{$name} || 0;
+      my $priority = $conf->{activetier}->{priority}->{$name} || 0;
 
       if (!defined $first || $priority < $first_pri) {
         $first_pri = $priority;
@@ -1010,11 +1026,11 @@
     foreach my $name (@names) {
       next if $name eq $first;
       push @dups, $name;
-      delete $conf->{tests}->{$name};
+      delete $conf->{activetier}->{tests}->{$name};
     }
 
     dbg("rules: $first merged duplicates: ".join(' ', @dups));
-    $conf->{duplicate_rules}->{$first} = \@dups;
+    $conf->{activetier}->{duplicate_rules}->{$first} = \@dups;
   }
 }
 
@@ -1058,17 +1074,28 @@
   my ($self) = @_;
   my $conf = $self->{conf};
 
+  # if we're in tier 1 (user configs) copy the booleans from the lower
+  # tier
+  if ($conf->{activetier} == $conf->{tiers}->[1]) {
+    $conf->{tiers}->[1]->{trusted_networks_configured} ||=
+                        $conf->{tiers}->[0]->{trusted_networks_configured};
+    $conf->{tiers}->[1]->{internal_networks_configured} ||=
+                        $conf->{tiers}->[0]->{internal_networks_configured};
+  }
+
   # validate trusted_networks and internal_networks, bug 4760.
   # check that all internal_networks are listed in trusted_networks
   # too.  do the same for msa_networks, but check msa_networks against
   # internal_networks if trusted_networks aren't defined
 
-  my ($nt, $matching_against);
-  if ($conf->{trusted_networks_configured}) {
-    $nt = $conf->{trusted_networks};
+  my ($nt0, $nt1, $matching_against);
+  if ($conf->{activetier}->{trusted_networks_configured}) {
+    $nt0 = $conf->{tiers}->[0]->{trusted_networks};
+    $nt1 = $conf->{tiers}->[1]->{trusted_networks};
     $matching_against = 'trusted_networks';
-  } elsif ($conf->{internal_networks_configured}) {
-    $nt = $conf->{internal_networks};
+  } elsif ($conf->{activetier}->{internal_networks_configured}) {
+    $nt0 = $conf->{tiers}->[0]->{internal_networks};
+    $nt1 = $conf->{tiers}->[1]->{internal_networks};
     $matching_against = 'internal_networks';
   } else {
     return;
@@ -1084,7 +1111,11 @@
 
     foreach my $net (@{$net_list->{nets}}) {
       # don't check to see if an excluded network is included - that's senseless
-      if (!$net->{exclude} && !$nt->contains_net($net)) {
+      if (!$net->{exclude}
+                && !($nt0 && $nt0->contains_net($net))
+                && !($nt1 && $nt1->contains_net($net))
+        )
+      {
         my $msg = "$matching_against doesn't contain $net_type entry '".
                   ($net->{as_string})."'";
 
@@ -1153,24 +1184,24 @@
     return unless $self->is_meta_valid($name, $text);
   }
 
-  $conf->{tests}->{$name} = $text;
-  $conf->{test_types}->{$name} = $type;
+  $conf->{activetier}->{tests}->{$name} = $text;
+  $conf->{activetier}->{test_types}->{$name} = $type;
   if ($type == $Mail::SpamAssassin::Conf::TYPE_META_TESTS) {
-    $conf->{priority}->{$name} ||= 500;
+    $conf->{activetier}->{priority}->{$name} ||= 500;
   }
   else {
-    $conf->{priority}->{$name} ||= 0;
+    $conf->{activetier}->{priority}->{$name} ||= 0;
   }
-  $conf->{priority}->{$name} ||= 0;
-  $conf->{source_file}->{$name} = $self->{currentfile};
+  $conf->{activetier}->{priority}->{$name} ||= 0;
+  $conf->{activetier}->{source_file}->{$name} = $self->{currentfile};
 
   if ($self->{main}->{keep_config_parsing_metadata}) {
-    $conf->{if_stack}->{$name} = $self->get_if_stack_as_string();
+    $conf->{activetier}->{if_stack}->{$name} = $self->get_if_stack_as_string();
   }
 
   if ($self->{scoresonly}) {
-    $conf->{user_rules_to_compile}->{$type} = 1;
-    $conf->{user_defined_rules}->{$name} = 1;
+    $conf->{activetier}->{user_rules_to_compile}->{$type} = 1;
+    $conf->{activetier}->{user_defined_rules}->{$name} = 1;
   }
 }
 
@@ -1294,7 +1325,7 @@
     $re =~ s/([^\*\?_a-zA-Z0-9])/\\$1/g;	# escape any possible metachars
     $re =~ tr/?/./;				# "?" -> "."
     $re =~ s/\*+/\.\*/g;			# "*" -> "any string"
-    $conf->{$singlelist}->{$addr} = "^${re}\$";
+    $conf->{activetier}->{$singlelist}->{$addr} = "^${re}\$";
   }
 }
 
@@ -1303,17 +1334,25 @@
   my $conf = $self->{conf};
 
   $addr = lc $addr;
-  if ($conf->{$listname}->{$addr}) {
-    push @{$conf->{$listname}->{$addr}{domain}}, $domain;
+  if ($conf->{activetier}->{$listname}->{$addr}) {
+    push @{$conf->{activetier}->{$listname}->{$addr}{domain}}, $domain;
+  }
+  elsif ($conf->{tiers}->[0]->{$listname}->{$addr}) {
+    # copy entry from a lower tier so we can extend it
+    $conf->{activetier}->{$listname}->{$addr}{re} =
+            $conf->{tiers}->[0]->{$listname}->{$addr}{re};
+    @{$conf->{activetier}->{$listname}->{$addr}{domain}} =
+            @{$conf->{tiers}->[0]->{$listname}->{$addr}{domain}};
   }
   else {
+    # create a new entry
     my $re = $addr;
     $re =~ s/[\000\\\(]/_/gs;			# paranoia
     $re =~ s/([^\*\?_a-zA-Z0-9])/\\$1/g;	# escape any possible metachars
     $re =~ tr/?/./;				# "?" -> "."
     $re =~ s/\*+/\.\*/g;			# "*" -> "any string"
-    $conf->{$listname}->{$addr}{re} = "^${re}\$";
-    $conf->{$listname}->{$addr}{domain} = [ $domain ];
+    $conf->{activetier}->{$listname}->{$addr}{re} = "^${re}\$";
+    $conf->{activetier}->{$listname}->{$addr}{domain} = [ $domain ];
   }
 }
 
@@ -1322,7 +1361,7 @@
   my $conf = $self->{conf};
 
   foreach my $addr (@addrs) {
-    delete($conf->{$singlelist}->{$addr});
+    delete $conf->{activetier}->{$singlelist}->{$addr};
   }
 }
 
@@ -1331,7 +1370,7 @@
   my $conf = $self->{conf};
 
   foreach my $addr (@addrs) {
-    delete($conf->{$listname}->{$addr});
+    delete $conf->{activetier}->{$listname}->{$addr};
   }
 }
 

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Dns.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Dns.pm?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Dns.pm (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Dns.pm Fri Jul 20 05:51:28 2007
@@ -330,7 +330,7 @@
 
   return if !defined $self->{async}->get_last_start_lookup_time();
 
-  my $deadline = $self->{conf}->{rbl_timeout} + $self->{async}->get_last_start_lookup_time();
+  my $deadline = $self->{conf}->cf_rbl_timeout + $self->{async}->get_last_start_lookup_time();
   my $now = time;
 
   # should not give up before at least attempting to collect some responses
@@ -355,11 +355,11 @@
     @left = $self->{async}->get_pending_lookups();
 
     # complete_lookups could cause a change in get_last_start_lookup_time
-    $deadline = $self->{conf}->{rbl_timeout} +
+    $deadline = $self->{conf}->cf_rbl_timeout +
                 $self->{async}->get_last_start_lookup_time();
 
     # dynamic timeout
-    my $dynamic = (int($self->{conf}->{rbl_timeout}
+    my $dynamic = (int($self->{conf}->cf_rbl_timeout
                       * (1 - 0.7*(($total - @left) / $total) ** 2) + 1)
                   + $self->{async}->get_last_start_lookup_time());
     $deadline = $dynamic if ($dynamic < $deadline);
@@ -372,7 +372,7 @@
 
   return if !defined $self->{async}->get_last_start_lookup_time();
 
-  my $deadline = $self->{conf}->{rbl_timeout} + $self->{async}->get_last_start_lookup_time();
+  my $deadline = $self->{conf}->cf_rbl_timeout + $self->{async}->get_last_start_lookup_time();
   my $now = time;
 
   # should not give up before at least attempting to collect some responses
@@ -396,11 +396,11 @@
     # complete_lookups() may have called completed_callback, which may call
     # start_lookup() again (like in URIDNSBL), so get_last_start_lookup_time
     # may have changed and deadline needs to be recomputed
-    $deadline = $self->{conf}->{rbl_timeout} +
+    $deadline = $self->{conf}->cf_rbl_timeout +
                 $self->{async}->get_last_start_lookup_time();
 
     # dynamic timeout
-    my $dynamic = (int($self->{conf}->{rbl_timeout}
+    my $dynamic = (int($self->{conf}->cf_rbl_timeout
                       * (1 - 0.7*(($total - @left) / $total) ** 2) + 1)
                   + $self->{async}->get_last_start_lookup_time());
     $deadline = $dynamic if ($dynamic < $deadline);
@@ -628,8 +628,8 @@
 
 sub is_dns_available {
   my ($self) = @_;
-  my $dnsopt = $self->{conf}->{dns_available};
-  my $dnsint = $self->{conf}->{dns_test_interval} || 600;
+  my $dnsopt = $self->{conf}->cf_dns_available;
+  my $dnsint = $self->{conf}->cf_dns_test_interval || 600;
   my @domains;
 
   $LAST_DNS_CHECK ||= 0;

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/PerMsgStatus.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/PerMsgStatus.pm?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/PerMsgStatus.pm (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/PerMsgStatus.pm Fri Jul 20 05:51:28 2007
@@ -178,7 +178,7 @@
   $self->{score} = (sprintf "%0.3f", $self->{score}) + 0;
   
   dbg("check: is spam? score=".$self->{score}.
-                        " required=".$self->{conf}->{required_score});
+                        " required=".$self->{conf}->cf_required_score);
   dbg("check: tests=".$self->get_names_of_tests_hit());
   dbg("check: subtests=".$self->get_names_of_subtests_hit());
   $self->{is_spam} = $self->is_spam();
@@ -204,8 +204,8 @@
 sub learn {
   my ($self) = @_;
 
-  if (!$self->{conf}->{bayes_auto_learn} ||
-      !$self->{conf}->{use_bayes} ||
+  if (!$self->{conf}->cf_bayes_auto_learn ||
+      !$self->{conf}->cf_use_bayes ||
       $self->{disable_auto_learning})
   {
     $self->{auto_learn_status} = "disabled";
@@ -323,9 +323,8 @@
 
   # This function needs to use use sum($score[scoreset % 2]) not just {score}.
   # otherwise we shift what we autolearn on and it gets really wierd.  - tvd
-  my $orig_scoreset = $self->{conf}->get_score_set();
+  my $orig_scoreset = $self->{conf}->get_score_set;
   my $new_scoreset = $orig_scoreset;
-  my $scores = $self->{conf}->{scores};
 
   if (($orig_scoreset & 2) == 0) { # we don't need to recompute
     dbg("learn: auto-learn: currently using scoreset $orig_scoreset");
@@ -333,10 +332,8 @@
   else {
     $new_scoreset = $orig_scoreset & ~2;
     dbg("learn: auto-learn: currently using scoreset $orig_scoreset, recomputing score based on scoreset $new_scoreset");
-    $scores = $self->{conf}->{scoreset}->[$new_scoreset];
   }
 
-  my $tflags = $self->{conf}->{tflags};
   my $points = 0;
 
   # Just in case this function is called multiple times, clear out the
@@ -346,33 +343,36 @@
   $self->{head_only_points} = 0;
 
   foreach my $test (@{$self->{test_names_hit}}) {
+    my $score = $self->{conf}->get_score_in_scoreset($test, $new_scoreset);
+    my $tf = $self->{conf}->get_tflags_for_rule($test);
+
     # According to the documentation, noautolearn, userconf, and learn
     # rules are ignored for autolearning.
-    if (exists $tflags->{$test}) {
-      next if $tflags->{$test} =~ /\bnoautolearn\b/;
-      next if $tflags->{$test} =~ /\buserconf\b/;
+    if ($tf) {
+      next if $tf =~ /\b(?:noautolearn|userconf)\b/;
 
       # Keep track of the learn points for an additional autolearn check.
       # Use the original scoreset since it'll be 0 in sets 0 and 1.
-      if ($tflags->{$test} =~ /\blearn\b/) {
+      if ($tf =~ /\blearn\b/) {
 	# we're guaranteed that the score will be defined
-        $self->{learned_points} += $self->{conf}->{scoreset}->[$orig_scoreset]->{$test};
+        $self->{learned_points} +=
+            $self->{conf}->get_score_in_scoreset($test, $orig_scoreset);
 	next;
       }
     }
 
     # ignore tests with 0 score in this scoreset
-    next if ($scores->{$test} == 0);
+    next if ($score == 0);
 
     # Go ahead and add points to the proper locations
     if (!$self->{conf}->maybe_header_only ($test)) {
-      $self->{body_only_points} += $scores->{$test};
+      $self->{body_only_points} += $score;
     }
     if (!$self->{conf}->maybe_body_only ($test)) {
-      $self->{head_only_points} += $scores->{$test};
+      $self->{head_only_points} += $score;
     }
 
-    $points += $scores->{$test};
+    $points += $score;
   }
 
   # Figure out the final value we'll use for autolearning
@@ -395,7 +395,7 @@
 sub is_spam {
   my ($self) = @_;
   # changed to test this so sub-tests can ask "is_spam" during a run
-  return ($self->{score} >= $self->{conf}->{required_score});
+  return ($self->{score} >= $self->{conf}->cf_required_score);
 }
 
 ###########################################################################
@@ -463,14 +463,11 @@
 
 sub get_required_score {
   my ($self) = @_;
-  return $self->{conf}->{required_score};
+  return $self->{conf}->cf_required_score;
 }
 
 # left as backward compatibility
-sub get_required_hits {
-  my ($self) = @_;
-  return $self->{conf}->{required_score};
-}
+sub get_required_hits { return $_[0]->get_required_score(); }
 
 ###########################################################################
 
@@ -504,7 +501,7 @@
 
   if (!exists $self->{'report'}) {
     my $report;
-    $report = $self->{conf}->{report_template};
+    $report = $self->{conf}->cf_report_template;
     $report ||= '(no report template found)';
 
     $report = $self->_replace_tags($report);
@@ -619,7 +616,7 @@
 
   my $msg = $self->{msg}->get_mbox_separator() || '';
 
-  if ($self->{is_spam} && $self->{conf}->{report_safe}) {
+  if ($self->{is_spam} && $self->{conf}->cf_report_safe) {
     $msg .= $self->rewrite_report_safe();
   }
   else {
@@ -656,8 +653,8 @@
 
   # the report charset
   my $report_charset = "; charset=iso-8859-1";
-  if ($self->{conf}->{report_charset}) {
-    $report_charset = "; charset=" . $self->{conf}->{report_charset};
+  if ($self->{conf}->cf_report_charset) {
+    $report_charset = "; charset=" . $self->{conf}->cf_report_charset;
   }
 
   # the SpamAssassin report
@@ -764,7 +761,7 @@
   my $ct = $self->{msg}->get_header("Content-Type");
   if (defined $ct && $ct ne '' && $ct !~ m{text/plain}i) {
     $disposition = "attachment";
-    $report .= $self->_replace_tags($self->{conf}->{unsafe_report_template});
+    $report .= $self->_replace_tags($self->{conf}->cf_unsafe_report_template);
     # if we wanted to defang the attachment, this would be the place
   }
   else {
@@ -772,7 +769,7 @@
   }
 
   my $type = "message/rfc822";
-  $type = "text/plain" if $self->{conf}->{report_safe} > 1;
+  $type = "text/plain" if $self->{conf}->cf_report_safe > 1;
 
   my $description = $self->{conf}->{'encapsulated_content_description'};
 
@@ -842,7 +839,7 @@
       my $created_subject = 0;
       my $subject = $self->{msg}->get_pristine_header('Subject');
       if (!defined($subject) && $self->{is_spam}
-            && exists $self->{conf}->{rewrite_header}->{'Subject'})
+            && defined $self->{conf}->{rewrite_header}->{'Subject'})
       {
         push(@pristine_headers, "Subject: \n");
         $created_subject = 1;
@@ -940,7 +937,7 @@
   $hdr_data = $self->_replace_tags($hdr_data);
   $hdr_data =~ s/(?:\r?\n)+$//; # make sure there are no trailing newlines ...
 
-  if ($self->{conf}->{fold_headers}) {
+  if ($self->{conf}->cf_fold_headers) {
     if ($hdr_data =~ /\n/) {
       $hdr_data =~ s/\s*\n\s*/\n\t/g;
       return $hdr_data;
@@ -1144,7 +1141,7 @@
 
 sub _get_tag_value_for_required_score {
   my $self  = shift;
-  return sprintf("%2.1f", $self->{conf}->{required_score});
+  return sprintf("%2.1f", $self->{conf}->cf_required_score);
 }
 
 sub _get_tag {
@@ -1170,8 +1167,8 @@
             SUBVERSION => sub { $Mail::SpamAssassin::SUB_VERSION },
 
             HOSTNAME => sub {
-	      $self->{conf}->{report_hostname} ||
-	      Mail::SpamAssassin::Util::fq_hostname();
+	      $self->{conf}->cf_report_hostname ||
+	            Mail::SpamAssassin::Util::fq_hostname();
 	    },
 
 	    REMOTEHOSTNAME => sub {
@@ -1198,7 +1195,7 @@
               return $lasthop ? $lasthop->{helo} : '';
             },
 
-            CONTACTADDRESS => sub { $self->{conf}->{report_contact}; },
+            CONTACTADDRESS => sub { $self->{conf}->cf_report_contact; },
 
             BAYES => sub {
               defined($self->{bayes_score}) ?
@@ -1259,11 +1256,10 @@
               my $arg = (shift || ",");
               my $line = '';
               foreach my $test (sort @{$self->{test_names_hit}}) {
-                if (!$line) {
-                  $line .= $test . "=" . $self->{conf}->{scores}->{$test};
-                } else {
-                  $line .= $arg . $test . "=" . $self->{conf}->{scores}->{$test};
+                if ($line) {
+                  $line .= $arg;
                 }
+                $line .= $test . "=" . $self->{conf}->get_score_for_rule($test);
               }
               return $line ? $line : 'none';
             },
@@ -2298,17 +2294,18 @@
   # Rely on the 'envelope-sender-header' header if the user has configured one.
   # Assume that because they have configured it, their MTA will always add it.
   # This will prevent us falling through and picking up inappropriate headers.
-  if (defined $self->{conf}->{envelope_sender_header}) {
+  if (defined $self->{conf}->cf_envelope_sender_header) {
+    my $hdr = $self->{conf}->cf_envelope_sender_header;
     # make sure we get the most recent copy - there can be only one EnvelopeSender.
-    $envf = $self->get($self->{conf}->{envelope_sender_header}.":addr");
+    $envf = $self->get($hdr.":addr");
     # ok if it contains an "@" sign, or is "" (ie. "<>" without the < and >)
     goto ok if defined $envf && ($envf =~ /\@/ || $envf =~ /^$/);
     # Warn them if it's configured, but not there or not usable.
     if (defined $envf) {
       chomp $envf;
-      dbg("message: envelope_sender_header '$self->{conf}->{envelope_sender_header}: $envf' is not an FQDN, ignoring");
+      dbg("message: envelope_sender_header '$hdr: $envf' is not an FQDN, ignoring");
     } else {
-      dbg("message: envelope_sender_header '".$self->{conf}->{envelope_sender_header}."' not found in message");
+      dbg("message: envelope_sender_header '$hdr' not found in message");
     }
     # Couldn't get envelope-sender using the configured header.
     return;

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/AutoLearnThreshold.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/AutoLearnThreshold.pm?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/AutoLearnThreshold.pm (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/AutoLearnThreshold.pm Fri Jul 20 05:51:28 2007
@@ -124,8 +124,8 @@
 
   # Figure out min/max for autolearning.
   # Default to specified auto_learn_threshold settings
-  my $min = $conf->{bayes_auto_learn_threshold_nonspam};
-  my $max = $conf->{bayes_auto_learn_threshold_spam};
+  my $min = $conf->cf_bayes_auto_learn_threshold_nonspam;
+  my $max = $conf->cf_bayes_auto_learn_threshold_spam;
 
   # Find out what score we should consider this message to have ...
   my $score = $scan->get_autolearn_points();

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/Check.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/Check.pm?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/Check.pm (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/Check.pm Fri Jul 20 05:51:28 2007
@@ -159,6 +159,7 @@
 sub run_rbl_eval_tests {
   my ($self, $pms) = @_;
   my ($rulename, $pat, @args);
+  my $conf = $pms->{conf};
 
   # XXX - possible speed up, moving this check out of the subroutine into Check->new()
   if ($self->{main}->{local_tests_only}) {
@@ -166,8 +167,9 @@
     return 0;
   }
 
-  while (my ($rulename, $test) = each %{$pms->{conf}->{rbl_evals}}) {
-    my $score = $pms->{conf}->{scores}->{$rulename};
+  foreach my $rulename ($conf->get_rule_keys('rbl_evals')) {
+    my $test = $conf->get_rule_value('rbl_evals', $rulename);
+    my $score = $conf->get_score_for_rule($rulename);
     next unless $score;
 
     $pms->{test_log_msgs} = ();        # clear test state
@@ -233,7 +235,8 @@
   if (defined $opts{pre_loop_body}) {
     $opts{pre_loop_body}->($self, $pms, $conf, %nopts);
   }
-  while (my($rulename, $test) = each %{$opts{testhash}->{$priority}}) {
+  foreach my $rulename ($conf->get_rule_keys($opts{confhashname}, $priority)) {
+    my $test = $conf->get_rule_value($opts{confhashname}, $rulename, $priority);
     $opts{loop_body}->($self, $pms, $conf, $rulename, $test, %nopts);
   }
   if (defined $opts{post_loop_body}) {
@@ -302,7 +305,7 @@
   $self->run_generic_tests ($pms, $priority,
     consttype => $Mail::SpamAssassin::Conf::TYPE_META_TESTS,
     type => 'meta',
-    testhash => $pms->{conf}->{meta_tests},
+    confhashname => 'meta_tests',
     args => [ ],
     loop_body => sub
   {
@@ -331,7 +334,7 @@
         # warnings; this is better than adding a 0 to a hash for every
         # rule referred to in a meta...
         $meta{$rulename} .= "(\$h->{'$token'} || 0) ";
-      
+ 
         if (!exists $conf->{scores}->{$token}) {
           dbg("rules: meta test $rulename has undefined dependency '$token'");
         }
@@ -388,14 +391,16 @@
         if (!defined $conf->{meta_dependencies}->{ $metas[$i] }) {
           warn "no meta_dependencies defined for $metas[$i]";
         }
-        my $alldeps = join ' ', grep {
-                ($tflags->{$_}||'') =~ /\bnet\b/
-              } split (' ', $conf->{meta_dependencies}->{ $metas[$i] } );
-
-        if ($alldeps ne '') {
-          $self->add_evalstr ('
-            $self->ensure_rules_are_complete(q{'.$metas[$i].'}, qw{'.$alldeps.'});
-          ');
+        else {
+          my $alldeps = join ' ', grep {
+                  ($tflags->{$_}||'') =~ /\bnet\b/
+                } split (' ', $conf->{meta_dependencies}->{ $metas[$i] } );
+
+          if ($alldeps ne '') {
+            $self->add_evalstr ('
+              $self->ensure_rules_are_complete(q{'.$metas[$i].'}, qw{'.$alldeps.'});
+            ');
+          }
         }
 
         # Add this meta rule to the eval line
@@ -437,7 +442,7 @@
   $self->run_generic_tests ($pms, $priority,
     consttype => $Mail::SpamAssassin::Conf::TYPE_HEAD_TESTS,
     type => 'head',
-    testhash => $pms->{conf}->{head_tests},
+    confhashname => 'head_tests',
     args => [ ],
     loop_body => sub
   {
@@ -547,7 +552,7 @@
   $self->run_generic_tests ($pms, $priority,
     consttype => $Mail::SpamAssassin::Conf::TYPE_BODY_TESTS,
     type => 'body',
-    testhash => $pms->{conf}->{body_tests},
+    confhashname => 'body_tests',
     args => [ @$textary ],
     loop_body => sub
   {
@@ -619,7 +624,7 @@
   $self->run_generic_tests ($pms, $priority,
     consttype => $Mail::SpamAssassin::Conf::TYPE_URI_TESTS,
     type => 'uri',
-    testhash => $pms->{conf}->{uri_tests},
+    confhashname => 'uri_tests',
     args => [ @uris ],
     loop_body => sub
   {
@@ -686,7 +691,7 @@
   $self->run_generic_tests ($pms, $priority,
     consttype => $Mail::SpamAssassin::Conf::TYPE_RAWBODY_TESTS,
     type => 'rawbody',
-    testhash => $pms->{conf}->{rawbody_tests},
+    confhashname => 'rawbody_tests',
     args => [ @$textary ],
     loop_body => sub
   {
@@ -756,7 +761,7 @@
   $self->run_generic_tests ($pms, $priority,
     consttype => $Mail::SpamAssassin::Conf::TYPE_FULL_TESTS,
     type => 'full',
-    testhash => $pms->{conf}->{full_tests},
+    confhashname => 'full_tests',
     args => [ $fullmsgref ],
     pre_loop_body => sub
   {
@@ -789,35 +794,32 @@
   my ($self, $pms, $priority) = @_;
   return unless (defined($pms->{conf}->{head_evals}->{$priority}));
   $self->run_eval_tests ($pms, $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS,
-			 $pms->{conf}->{head_evals}->{$priority}, '', $priority);
+            'head_evals', '', $priority);
 }
 
 sub do_body_eval_tests {
   my ($self, $pms, $priority, $bodystring) = @_;
   return unless (defined($pms->{conf}->{body_evals}->{$priority}));
   $self->run_eval_tests ($pms, $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS,
-			 $pms->{conf}->{body_evals}->{$priority}, 'BODY: ',
-			 $priority, $bodystring);
+            'body_evals', 'BODY: ', $priority, $bodystring);
 }
 
 sub do_rawbody_eval_tests {
   my ($self, $pms, $priority, $bodystring) = @_;
   return unless (defined($pms->{conf}->{rawbody_evals}->{$priority}));
   $self->run_eval_tests ($pms, $Mail::SpamAssassin::Conf::TYPE_RAWBODY_EVALS,
-			 $pms->{conf}->{rawbody_evals}->{$priority}, 'RAW: ',
-			 $priority, $bodystring);
+            'rawbody_evals', 'RAW: ', $priority, $bodystring);
 }
 
 sub do_full_eval_tests {
   my ($self, $pms, $priority, $fullmsgref) = @_;
   return unless (defined($pms->{conf}->{full_evals}->{$priority}));
   $self->run_eval_tests($pms, $Mail::SpamAssassin::Conf::TYPE_FULL_EVALS,
-			$pms->{conf}->{full_evals}->{$priority}, '',
-			$priority, $fullmsgref);
+            'full_evals', '', $priority, $fullmsgref);
 }
 
 sub run_eval_tests {
-  my ($self, $pms, $testtype, $evalhash, $prepend2desc, $priority, @extraevalargs) = @_;
+  my ($self, $pms, $testtype, $confhashname, $prepend2desc, $priority, @extraevalargs) = @_;
  
   return if $self->{main}->call_plugins("have_shortcircuited",
                                         { permsgstatus => $pms });
@@ -867,7 +869,7 @@
     };
   }
 
-  while (my ($rulename, $test) = each %{$evalhash})  { 
+  foreach my $rulename ($conf->get_rule_keys($confhashname, $priority)) {
     if ($tflagsref->{$rulename}) {
       # If the rule is a net rule, and we are in a non-net scoreset, skip it.
       if ($tflagsref->{$rulename} =~ /\bnet\b/) {
@@ -879,6 +881,7 @@
       }
     }
  
+    my $test = $conf->get_rule_value($confhashname, $rulename, $priority);
     my ($function, $argstr) = ($test,'');
     if ($test =~ s/^([^,]+)(,.*)$//gs) {
       ($function, $argstr) = ($1,$2);

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/HTMLEval.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/HTMLEval.pm?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/HTMLEval.pm (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/HTMLEval.pm Fri Jul 20 05:51:28 2007
@@ -89,7 +89,7 @@
 
   return 0 unless exists $pms->{html}{charsets};
 
-  my @locales = Mail::SpamAssassin::Util::get_my_locales($pms->{conf}->{ok_locales});
+  my @locales = Mail::SpamAssassin::Util::get_my_locales($pms->{conf}->cf_ok_locales);
   return 0 if grep { $_ eq "all" } @locales;
 
   my $okay = 0;

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/HeaderEval.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/HeaderEval.pm?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/HeaderEval.pm (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/HeaderEval.pm Fri Jul 20 05:51:28 2007
@@ -116,7 +116,7 @@
   my ($self, $pms) = @_;
   my $hdr;
 
-  my @locales = Mail::SpamAssassin::Util::get_my_locales($self->{main}->{conf}->{ok_locales});
+  my @locales = Mail::SpamAssassin::Util::get_my_locales($self->{main}->{conf}->cf_ok_locales);
 
   return 0 if grep { $_ eq "all" } @locales;
 

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/MIMEEval.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/MIMEEval.pm?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/MIMEEval.pm (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Plugin/MIMEEval.pm Fri Jul 20 05:51:28 2007
@@ -68,7 +68,7 @@
 
   my $type = $pms->get('Content-Type');
 
-  my @locales = Mail::SpamAssassin::Util::get_my_locales($self->{main}->{conf}->{ok_locales});
+  my @locales = Mail::SpamAssassin::Util::get_my_locales($self->{main}->{conf}->cf_ok_locales);
 
   return 0 if grep { $_ eq "all" } @locales;
 
@@ -196,7 +196,7 @@
     }
 
     if (! $pms->{mime_faraway_charset}) {
-      my @l = Mail::SpamAssassin::Util::get_my_locales($self->{main}->{conf}->{ok_locales});
+      my @l = Mail::SpamAssassin::Util::get_my_locales($self->{main}->{conf}->cf_ok_locales);
 
       if (!(grep { $_ eq "all" } @l) &&
 	  !Mail::SpamAssassin::Locales::is_charset_ok_for_locales($charset, @l))

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util.pm?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util.pm (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util.pm Fri Jul 20 05:51:28 2007
@@ -1550,6 +1550,21 @@
 
 ###########################################################################
 
+sub uniq_list {
+  my %u=();
+  return grep {
+          defined
+        } map {
+          if (exists $u{$_}) {
+            undef; 
+          } else {
+            $u{$_} = undef; $_; 
+          }
+        } @_;
+}
+
+###########################################################################
+
 1;
 
 =back

Added: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util/TwoTierArray.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util/TwoTierArray.pm?view=auto&rev=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util/TwoTierArray.pm (added)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util/TwoTierArray.pm Fri Jul 20 05:51:28 2007
@@ -0,0 +1,106 @@
+# A tied object presenting an array API to a two-tiered pair of arrays
+
+# <@LICENSE>
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at:
+# 
+#     http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# </...@LICENSE>
+
+package Mail::SpamAssassin::Util::TwoTierArray;
+
+use strict;
+use warnings;
+use Carp qw(croak);
+
+use Tie::Array;
+
+our @ISA = qw(Tie::Array);
+
+# structure: 2 arrays, "tier 0" and "tier 1".  all writes go to tier 1,
+# and all reads from tier 1, and if not found there, tier 0.  In
+# effect tier 1 overrides tier 0.  Note that writes will NEVER affect
+# tier 0; create a new object to modify the contents of that tier.
+
+###########################################################################
+
+sub TIEARRAY {
+  my $class = shift;
+  my $a0 = shift;
+  my $a1 = shift;
+  my $self = {
+    a0 => $a0 || [],
+    a1 => $a1 || [],
+  };
+  return bless $self, $class;
+}
+
+sub STORE {
+  my ($self, $i, $v) = @_;
+  my $a0size = scalar @{$self->{a0}};
+  if ($i > $a0size) {
+    $self->{a1}->[$i - $a0size] = $v;
+  } else {
+    # a write to the a0 area! we cannot do this!
+    croak "cannot write to immutable tier 0 part of array: $i / $a0size";
+  }
+}
+
+sub FETCH {
+  my ($self, $i) = @_;
+  my $a0size = scalar @{$self->{a0}};
+  if ($i > $a0size) {
+    return $self->{a1}->[$i - $a0size];
+  } else {
+    return $self->{a0}->[$i];
+  }
+}
+
+sub FETCHSIZE {
+  my ($self) = @_;
+  return scalar(@{$self->{a0}}) + scalar(@{$self->{a1}});
+}
+
+sub STORESIZE {
+  my ($self, $count) = @_;
+  my $a0size = scalar @{$self->{a0}};
+  if ($count > $a0size) {
+    @{$self->{a1}} = $count - $a0size;
+  } else {
+    # a write to the a0 area! we cannot do this!
+    croak "cannot resize immutable tier 0 part of array: $count / $a0size";
+  }
+}
+
+sub EXISTS {
+  my ($self, $i) = @_;
+  my $a0size = scalar @{$self->{a0}};
+  if ($i > $a0size) {
+    return exists $self->{a1}->[$i - $a0size];
+  } else {
+    return exists $self->{a0}->[$i];
+  }
+}
+
+sub DELETE {
+  my ($self, $i) = @_;
+  my $a0size = scalar @{$self->{a0}};
+  if ($i > $a0size) {
+    delete $self->{a1}->[$i - $a0size];
+  } else {
+    # a write to the a0 area! we cannot do this!
+    croak "cannot write to immutable tier 0 part of array: $i / $a0size";
+  }
+}
+
+1;

Added: spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util/TwoTierHash.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util/TwoTierHash.pm?view=auto&rev=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util/TwoTierHash.pm (added)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/lib/Mail/SpamAssassin/Util/TwoTierHash.pm Fri Jul 20 05:51:28 2007
@@ -0,0 +1,103 @@
+# A tied object presenting a hash API to a two-tiered pair of hashes
+
+# <@LICENSE>
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at:
+# 
+#     http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# </...@LICENSE>
+
+package Mail::SpamAssassin::Util::TwoTierHash;
+
+use strict;
+use warnings;
+use Carp qw(croak);
+
+our @ISA = qw();
+
+# structure: 2 hashes, "tier 0" and "tier 1".  all writes go to tier 1,
+# and all reads from tier 1, and if not found there, tier 0.  In
+# effect tier 1 overrides tier 0.  Note that writes will NEVER affect
+# tier 0; create a new object to modify the contents of that tier.
+
+###########################################################################
+
+sub TIEHASH {
+  my $class = shift;
+  my $h0 = shift;
+  my $h1 = shift;
+  my $self = { h0 => $h0, h1 => $h1 };
+  return bless $self, $class;
+}
+
+sub STORE {
+  my ($self, $k, $v) = @_;
+  $self->{h1}->{$k} = $v;
+  1;
+}
+
+sub FETCH {
+  my ($self, $k) = @_;
+  if (exists $self->{h1}->{$k}) {
+    return $self->{h1}->{$k};
+  } else {
+    return $self->{h0}->{$k};
+  }
+}
+
+sub EXISTS {
+  my ($self, $k) = @_;
+  if (exists $self->{h1}->{$k}) {
+    return 1;
+  } elsif (exists $self->{h0}->{$k}) {
+    return 1;
+  } else {
+    return;
+  }
+}
+
+sub DELETE {
+  my ($self, $k) = @_;
+  return delete $self->{h1}->{$k};
+}
+
+sub FIRSTKEY {
+  my ($self) = @_;
+  $self->{_keys} = make_keys_list($self->{h0}, $self->{h1});
+  return each %{$self->{_keys}};
+}
+
+sub make_keys_list {
+  my ($h0, $h1) = @_;
+  my %keys = ();
+  foreach my $k (keys %{$h0}) { $keys{$k} = 1; }
+  foreach my $k (keys %{$h1}) { $keys{$k} = 1; }
+  return \%keys;
+}
+
+sub NEXTKEY {
+  my ($self, $lastk) = @_;
+  return each %{$self->{_keys}};
+}
+
+sub CLEAR {
+  my ($self) = @_;
+  $self->{h1} = { };
+}
+
+sub SCALAR {
+  my ($self) = @_;
+  return scalar $self->{h1};
+}
+
+1;

Modified: spamassassin/branches/jm_bug_3852_two_level_configs/t/priorities.t
URL: http://svn.apache.org/viewvc/spamassassin/branches/jm_bug_3852_two_level_configs/t/priorities.t?view=diff&rev=557985&r1=557984&r2=557985
==============================================================================
--- spamassassin/branches/jm_bug_3852_two_level_configs/t/priorities.t (original)
+++ spamassassin/branches/jm_bug_3852_two_level_configs/t/priorities.t Fri Jul 20 05:51:28 2007
@@ -99,7 +99,7 @@
 sub assert_rule_pri {
   my ($r, $pri) = @_;
 
-  if (defined $conf->{rbl_evals}->{$r}) {
+  if (defined $conf->{activetier}->{rbl_evals}->{$r}) {
     # ignore rbl_evals; they do not use the priority system at all
     return 1;
   }
@@ -109,11 +109,11 @@
     full_evals rawbody_evals head_evals body_evals
   ))
   {
-    if (defined $conf->{$ruletype}->{$pri}->{$r}) {
+    if (defined $conf->{activetier}->{$ruletype}->{$pri}->{$r}) {
       return 1;
     }
-    foreach my $foundpri (keys %{$conf->{priorities}}) {
-      next unless (defined $conf->{$ruletype}->{$foundpri}->{$r});
+    foreach my $foundpri (keys %{$conf->{activetier}->{priorities}}) {
+      next unless (defined $conf->{activetier}->{$ruletype}->{$foundpri}->{$r});
       warn "FAIL: rule '$r' not found at priority $pri; found at $foundpri\n";
       return 0;
     }