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/06/11 22:13:46 UTC

svn commit: r546243 - in /spamassassin/branches/3.2: spamd/ t/

Author: jm
Date: Mon Jun 11 13:13:45 2007
New Revision: 546243

URL: http://svn.apache.org/viewvc?view=rev&rev=546243
Log:
bug 5480: fix CVE-2007-2873, a local user symlink-attack DoS
vulnerability in spamd when '--allow-tell' is used.  Full details at
 http://spamassassin.apache.org/advisories/cve-2007-2873.txt

Added:
    spamassassin/branches/3.2/t/root_spamd.t
    spamassassin/branches/3.2/t/root_spamd_tell.t
    spamassassin/branches/3.2/t/root_spamd_tell_paranoid.t
    spamassassin/branches/3.2/t/root_spamd_tell_x.t
    spamassassin/branches/3.2/t/root_spamd_tell_x_paranoid.t
    spamassassin/branches/3.2/t/root_spamd_x.t
    spamassassin/branches/3.2/t/root_spamd_x_paranoid.t
Modified:
    spamassassin/branches/3.2/spamd/spamd.raw
    spamassassin/branches/3.2/t/SATest.pm
    spamassassin/branches/3.2/t/config.dist
    spamassassin/branches/3.2/t/spamd_allow_user_rules.t

Modified: spamassassin/branches/3.2/spamd/spamd.raw
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.2/spamd/spamd.raw?view=diff&rev=546243&r1=546242&r2=546243
==============================================================================
--- spamassassin/branches/3.2/spamd/spamd.raw (original)
+++ spamassassin/branches/3.2/spamd/spamd.raw Mon Jun 11 13:13:45 2007
@@ -489,14 +489,28 @@
 # support setuid() to user unless:
 # run with -u
 # we're not root
-# doing --vpopmail
+# doing --vpopmail or --virtual-config-dir
 # we disable user-config
 my $setuid_to_user = (
 	$opt{'username'} ||
 	$> != 0 ||
 	$opt{'vpopmail'} ||
-	(!$opt{'user-config'} && !($opt{'setuid-with-sql'}||$opt{'setuid-with-ldap'}))
-	) ? 0 : 1;
+	$opt{'virtual-config-dir'}
+      ) ? 0 : 1;
+
+dbg("spamd: will perform setuids? $setuid_to_user");
+
+if ( $opt{'vpopmail'} ) {
+  if ( !$opt{'username'} ) {
+    die "spamd: cannot use --vpopmail without -u\n";
+  }
+}
+
+if ( $opt{'virtual-config-dir'} ) {
+  if ( !$opt{'username'} ) {
+    die "spamd: cannot use --virtual-config-dir without -u\n";
+  }
+}
 
 # always copy the config, later code may disable
 my $copy_config_p = 1;
@@ -1463,18 +1477,8 @@
     $compress_zlib = $hdrs->{compress_zlib};
   }
 
-  handle_setuid_to_user if ($setuid_to_user && $> == 0);
-
-  if ( $opt{'sql-config'} && !defined($current_user) ) {
-    unless ( handle_user_sql('nobody') ) {
-      service_unavailable_error("Error fetching user preferences via SQL");
-      return 0;
-    }
-  }
-
-  if ( $opt{'ldap-config'} && !defined($current_user) ) {
-    handle_user_ldap('nobody');
-  }
+  return 0 unless do_user_handling();
+  if ($> == 0) { die "spamd: still running as root! dying"; }
 
   my $resp = "EX_OK";
 
@@ -1669,6 +1673,9 @@
   my $expected_length = $hdrs->{expected_length};
   my $compress_zlib = $hdrs->{compress_zlib};
 
+  return 0 unless do_user_handling();
+  if ($> == 0) { die "spamd: still running as root! dying"; }
+
   if (!$opt{tell}) {
     service_unavailable_error("TELL commands have not been enabled.");
     return 0;
@@ -1684,8 +1691,6 @@
     return 0;
   }
 
-  &handle_setuid_to_user if ($setuid_to_user && $> == 0);
-
   if ($opt{'sql-config'} && !defined($current_user)) {
     unless (handle_user_sql('nobody')) {
       service_unavailable_error("Error fetching user preferences via SQL");
@@ -1789,6 +1794,26 @@
 
 ###########################################################################
 
+sub do_user_handling {
+  if ($setuid_to_user && $> == 0) {
+    handle_setuid_to_user();
+  }
+
+  if ( $opt{'sql-config'} && !defined($current_user) ) {
+    unless ( handle_user_sql('nobody') ) {
+      service_unavailable_error("Error fetching user preferences via SQL");
+      return 0;
+    }
+  }
+
+  if ( $opt{'ldap-config'} && !defined($current_user) ) {
+    handle_user_ldap('nobody');
+  }
+
+  dbg ("spamd: running as uid $>");
+  return 1;
+}
+
 # generalised header parser.  
 sub parse_headers {
   my ($hdrs, $client) = @_;
@@ -1891,9 +1916,12 @@
       handle_user_setuid_with_ldap($current_user);
       $setuid_to_user = 1;    # as above
     }
+    else {
+      handle_user_setuid_basic($current_user);
+    }
   }
   else {
-    handle_user($current_user);
+    handle_user_setuid_basic($current_user);
     if ( $opt{'sql-config'} ) {
       unless ( handle_user_sql($current_user) ) {
         service_unavailable_error("Error fetching user preferences via SQL");
@@ -2016,7 +2044,7 @@
   return 1;
 }
 
-sub handle_user {
+sub handle_user_setuid_basic {
   my $username = shift;
 
   #
@@ -2055,6 +2083,14 @@
     }
   }
 
+  if ($opt{'user-config'}) {
+    handle_user_set_user_prefs($dir, $username);
+  }
+}
+
+sub handle_user_set_user_prefs {
+  my ($dir, $username) = @_;
+
   #
   # If vpopmail config enabled then set $dir to virtual homedir
   #
@@ -2076,33 +2112,14 @@
   }
   my $cf_file = $dir . "/.spamassassin/user_prefs";
 
-  #
-  # If vpopmail config enabled then pass virtual homedir onto create_default_cf_needed
-  #
-  if ( $opt{'vpopmail'} ) {
-    if ( !$opt{'username'} ) {
-      warn "spamd: cannot use vpopmail without -u\n";
+  create_default_cf_if_needed( $cf_file, $username, $dir );
+  $spamtest->read_scoreonly_config($cf_file);
+  $spamtest->signal_user_changed(
+    {
+      username => $username,
+      user_dir => $dir
     }
-    create_default_cf_if_needed( $cf_file, $username, $dir );
-    $spamtest->read_scoreonly_config($cf_file);
-    $spamtest->signal_user_changed(
-      {
-        username => $username,
-        user_dir => $dir
-      }
-    );
-
-  }
-  else {
-    create_default_cf_if_needed( $cf_file, $username, $dir );
-    $spamtest->read_scoreonly_config($cf_file);
-    $spamtest->signal_user_changed(
-      {
-        username => $username,
-        user_dir => $dir
-      }
-    );
-  }
+  );
 
   return 1;
 }
@@ -2788,6 +2805,8 @@
 The pattern B<must> expand to an absolute directory when spamd is running
 daemonized (B<-d>).
 
+Currently, use of this without B<-u> is not supported. This inhibits setuid.
+
 =item B<-r> I<pidfile>, B<--pidfile>=I<pidfile>
 
 Write the process ID of the spamd parent to the file specified by I<pidfile>.
@@ -2801,7 +2820,7 @@
 maildir.  This option is useful for vpopmail virtual users who do not have an
 entry in the system /etc/passwd file.
 
-Currently, use of this without B<-u> is not supported.
+Currently, use of this without B<-u> is not supported. This inhibits setuid.
 
 =item B<-s> I<facility>, B<--syslog>=I<facility>
 

Modified: spamassassin/branches/3.2/t/SATest.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.2/t/SATest.pm?view=diff&rev=546243&r1=546242&r2=546243
==============================================================================
--- spamassassin/branches/3.2/t/SATest.pm (original)
+++ spamassassin/branches/3.2/t/SATest.pm Mon Jun 11 13:13:45 2007
@@ -146,6 +146,8 @@
 
   $ENV{'TEST_DIR'} = $cwd;
   $testname = $tname;
+
+  $current_user = (getpwuid($>))[0];
 }
 
 # a port number between 32768 and 65535; used to allow multiple test

Modified: spamassassin/branches/3.2/t/config.dist
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.2/t/config.dist?view=diff&rev=546243&r1=546242&r2=546243
==============================================================================
--- spamassassin/branches/3.2/t/config.dist (original)
+++ spamassassin/branches/3.2/t/config.dist Mon Jun 11 13:13:45 2007
@@ -57,3 +57,9 @@
 # depending on changes in the third-party modules we import.
 run_saw_ampersand_test=n
 
+# ---------------------------------------------------------------------------
+
+# The "root_*.t" tests require root privileges, and may create files in
+# the filesystem as part of the test.  Disabled by default.
+run_root_tests=n
+

Added: spamassassin/branches/3.2/t/root_spamd.t
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.2/t/root_spamd.t?view=auto&rev=546243
==============================================================================
--- spamassassin/branches/3.2/t/root_spamd.t (added)
+++ spamassassin/branches/3.2/t/root_spamd.t Mon Jun 11 13:13:45 2007
@@ -0,0 +1,48 @@
+#!/usr/bin/perl
+
+# run with:   sudo prove -v t/root_spamd*
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 14 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+
+q{ Return-Path: sb55sb55@yahoo.com}, 'firstline',
+q{ Subject: There yours for FREE!}, 'subj',
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+q{ X-Spam-Level: **********}, 'stars',
+q{ TEST_ENDSNUMS}, 'endsinnums',
+q{ TEST_NOREALNAME}, 'noreal',
+q{ This must be the very last line}, 'lastline',
+
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+ok(start_spamd("-L"));
+
+ok(spamcrun("< data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+%patterns = (
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+             );
+
+
+ok (spamcrun("< data/spam/018", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());

Added: spamassassin/branches/3.2/t/root_spamd_tell.t
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.2/t/root_spamd_tell.t?view=auto&rev=546243
==============================================================================
--- spamassassin/branches/3.2/t/root_spamd_tell.t (added)
+++ spamassassin/branches/3.2/t/root_spamd_tell.t Mon Jun 11 13:13:45 2007
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_tell");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 6 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+q{ Message successfully } => 'learned',
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+# remove these first
+unlink('log/user_state/bayes_seen');
+unlink('log/user_state/bayes_toks');
+
+# ensure it is writable by all
+use File::Path; mkpath("log/user_state"); chmod 01777, "log/user_state";
+
+ok(start_spamd("-L --allow-tell"));
+
+ok(spamcrun("-lx -L ham < data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());
+
+# ensure these are not owned by root
+ok check_owner('log/user_state/bayes_seen');
+ok check_owner('log/user_state/bayes_toks');
+
+sub check_owner {
+  my $f = shift;
+  my @stat = stat $f;
+
+  print "stat($f) = ".join(', ', @stat)."\n";
+
+  if (!defined $stat[1]) {
+    warn "no stat for $f";
+    return 0;
+  }
+  elsif ($stat[4] == 0) {
+    warn "stat for $f: owner is root";
+    return 0;
+  }
+  else {
+    return 1;
+  }
+}

Added: spamassassin/branches/3.2/t/root_spamd_tell_paranoid.t
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.2/t/root_spamd_tell_paranoid.t?view=auto&rev=546243
==============================================================================
--- spamassassin/branches/3.2/t/root_spamd_tell_paranoid.t (added)
+++ spamassassin/branches/3.2/t/root_spamd_tell_paranoid.t Mon Jun 11 13:13:45 2007
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_tell_paranoid");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 6 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+q{ Message successfully } => 'learned',
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+# remove these first
+unlink('log/user_state/bayes_seen');
+unlink('log/user_state/bayes_toks');
+
+# ensure it is writable by all
+use File::Path; mkpath("log/user_state"); chmod 01777, "log/user_state";
+
+ok(start_spamd("-L --allow-tell --paranoid"));
+
+ok(spamcrun("-lx -L ham < data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());
+
+# ensure these are not owned by root
+ok check_owner('log/user_state/bayes_seen');
+ok check_owner('log/user_state/bayes_toks');
+
+sub check_owner {
+  my $f = shift;
+  my @stat = stat $f;
+
+  print "stat($f) = ".join(', ', @stat)."\n";
+
+  if (!defined $stat[1]) {
+    warn "no stat for $f";
+    return 0;
+  }
+  elsif ($stat[4] == 0) {
+    warn "stat for $f: owner is root";
+    return 0;
+  }
+  else {
+    return 1;
+  }
+}

Added: spamassassin/branches/3.2/t/root_spamd_tell_x.t
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.2/t/root_spamd_tell_x.t?view=auto&rev=546243
==============================================================================
--- spamassassin/branches/3.2/t/root_spamd_tell_x.t (added)
+++ spamassassin/branches/3.2/t/root_spamd_tell_x.t Mon Jun 11 13:13:45 2007
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_tell_x");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 6 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+q{ Message successfully } => 'learned',
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+# remove these first
+unlink('log/user_state/bayes_seen');
+unlink('log/user_state/bayes_toks');
+
+# ensure it is writable by all
+use File::Path; mkpath("log/user_state"); chmod 01777, "log/user_state";
+
+ok(start_spamd("-L --allow-tell --create-prefs -x"));
+
+ok(spamcrun("-lx -L ham < data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());
+
+# ensure these are not owned by root
+ok check_owner('log/user_state/bayes_seen');
+ok check_owner('log/user_state/bayes_toks');
+
+sub check_owner {
+  my $f = shift;
+  my @stat = stat $f;
+
+  print "stat($f) = ".join(', ', @stat)."\n";
+
+  if (!defined $stat[1]) {
+    warn "no stat for $f";
+    return 0;
+  }
+  elsif ($stat[4] == 0) {
+    warn "stat for $f: owner is root";
+    return 0;
+  }
+  else {
+    return 1;
+  }
+}

Added: spamassassin/branches/3.2/t/root_spamd_tell_x_paranoid.t
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.2/t/root_spamd_tell_x_paranoid.t?view=auto&rev=546243
==============================================================================
--- spamassassin/branches/3.2/t/root_spamd_tell_x_paranoid.t (added)
+++ spamassassin/branches/3.2/t/root_spamd_tell_x_paranoid.t Mon Jun 11 13:13:45 2007
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_tell_x_paranoid");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 6 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+q{ Message successfully } => 'learned',
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+# remove these first
+unlink('log/user_state/bayes_seen');
+unlink('log/user_state/bayes_toks');
+
+# ensure it is writable by all
+use File::Path; mkpath("log/user_state"); chmod 01777, "log/user_state";
+
+ok(start_spamd("-L --allow-tell --create-prefs -x --paranoid"));
+
+ok(spamcrun("-lx -L ham < data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());
+
+# ensure these are not owned by root
+ok check_owner('log/user_state/bayes_seen');
+ok check_owner('log/user_state/bayes_toks');
+
+sub check_owner {
+  my $f = shift;
+  my @stat = stat $f;
+
+  print "stat($f) = ".join(', ', @stat)."\n";
+
+  if (!defined $stat[1]) {
+    warn "no stat for $f";
+    return 0;
+  }
+  elsif ($stat[4] == 0) {
+    warn "stat for $f: owner is root";
+    return 0;
+  }
+  else {
+    return 1;
+  }
+}

Added: spamassassin/branches/3.2/t/root_spamd_x.t
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.2/t/root_spamd_x.t?view=auto&rev=546243
==============================================================================
--- spamassassin/branches/3.2/t/root_spamd_x.t (added)
+++ spamassassin/branches/3.2/t/root_spamd_x.t Mon Jun 11 13:13:45 2007
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_x");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 14 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+
+q{ Return-Path: sb55sb55@yahoo.com}, 'firstline',
+q{ Subject: There yours for FREE!}, 'subj',
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+q{ X-Spam-Level: **********}, 'stars',
+q{ TEST_ENDSNUMS}, 'endsinnums',
+q{ TEST_NOREALNAME}, 'noreal',
+q{ This must be the very last line}, 'lastline',
+
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+ok(start_spamd("-L --create-prefs -x"));
+
+ok(spamcrun("< data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+%patterns = (
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+             );
+
+
+ok (spamcrun("< data/spam/018", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());

Added: spamassassin/branches/3.2/t/root_spamd_x_paranoid.t
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.2/t/root_spamd_x_paranoid.t?view=auto&rev=546243
==============================================================================
--- spamassassin/branches/3.2/t/root_spamd_x_paranoid.t (added)
+++ spamassassin/branches/3.2/t/root_spamd_x_paranoid.t Mon Jun 11 13:13:45 2007
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("root_spamd_x_paranoid");
+use Test;
+
+use constant TEST_ENABLED => conf_bool('run_root_tests');
+use constant IS_ROOT => eval { ($> == 0); };
+use constant RUN_TESTS => (TEST_ENABLED && IS_ROOT);
+
+BEGIN { plan tests => (RUN_TESTS ? 14 : 0) };
+exit unless RUN_TESTS;
+
+# ---------------------------------------------------------------------------
+
+%patterns = (
+
+q{ Return-Path: sb55sb55@yahoo.com}, 'firstline',
+q{ Subject: There yours for FREE!}, 'subj',
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+q{ X-Spam-Level: **********}, 'stars',
+q{ TEST_ENDSNUMS}, 'endsinnums',
+q{ TEST_NOREALNAME}, 'noreal',
+q{ This must be the very last line}, 'lastline',
+
+);
+
+# run spamc as unpriv uid
+$spamc = "sudo -u nobody $spamc";
+
+ok(start_spamd("-L --create-prefs -x --paranoid"));
+
+ok(spamcrun("< data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+%patterns = (
+q{ X-Spam-Status: Yes, score=}, 'status',
+q{ X-Spam-Flag: YES}, 'flag',
+             );
+
+
+ok (spamcrun("< data/spam/018", \&patterns_run_cb));
+ok_all_patterns();
+
+ok(stop_spamd());

Modified: spamassassin/branches/3.2/t/spamd_allow_user_rules.t
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.2/t/spamd_allow_user_rules.t?view=diff&rev=546243&r1=546242&r2=546243
==============================================================================
--- spamassassin/branches/3.2/t/spamd_allow_user_rules.t (original)
+++ spamassassin/branches/3.2/t/spamd_allow_user_rules.t Mon Jun 11 13:13:45 2007
@@ -36,7 +36,7 @@
 ";
 close OUT;
 
-ok (start_spamd ("--virtual-config-dir=log/virtualconfig/%u -L"));
+ok (start_spamd ("--virtual-config-dir=log/virtualconfig/%u -L -u $current_user"));
 ok (spamcrun ("-u testuser < data/spam/009", \&patterns_run_cb));
 ok (stop_spamd ());