Started work on SpamAssassin MySQL / LDAP integration
authorJamie Cameron <jcameron@webmin.com>
Sun, 7 Oct 2007 21:17:25 +0000 (21:17 +0000)
committerJamie Cameron <jcameron@webmin.com>
Sun, 7 Oct 2007 21:17:25 +0000 (21:17 +0000)
spam/config
spam/config-debian-linux
spam/config-freebsd
spam/config.info
spam/index.cgi
spam/lang/en
spam/spam-lib.pl

index 451d12f..96bcbcc 100644 (file)
@@ -6,3 +6,5 @@ warn_procmail=1
 call_spam=1
 processes=spamd amavisd
 procmail_cmd=*
+mode=0
+addto=0
index 64bc6fe..5668302 100644 (file)
@@ -7,3 +7,5 @@ call_spam=1
 processes=spamd amavisd
 restart_cmd=/etc/init.d/spamassassin restart
 procmail_cmd=*
+mode=0
+addto=0
index 06844f8..7a30b8c 100644 (file)
@@ -6,3 +6,5 @@ warn_procmail=1
 call_spam=1
 processes=spamd amavisd
 procmail_cmd=*
+mode=0
+addto=0
index a7f807e..6e15cef 100644 (file)
@@ -1,3 +1,4 @@
+line0=Configuration files and programs,11
 local_cf=SpamAssassin configuration file or directory,0
 spamassassin=Full path to SpamAssassin command,0
 sa_learn=Full path to sa-learn command,0
@@ -8,3 +9,13 @@ restart_cmd=Command to restart processes,3,Just send HUP signal
 procmail_cmd=SpamAssassin command for Procmail configuration,10,*-Detect automatically
 before_cmd=Command to run before changing rules,3,None
 after_cmd=Command to run after changing rules,3,None
+line1=LDAP and SQL server options,11
+mode=Store SpamAssassin settings in,1,0-Configuration files,1-MySQL database,2-PostgreSQL database,3-LDAP database
+server=LDAP or SQL server hostname,0
+port=LDAP or SQL server port,3,Default
+user=LDAP or SQL server login,0
+pass=LDAP or SQL server password,0
+db=SQL database name,3,Not needed
+base=LDAP base DN,3,Not needed
+dbglobal=Tag for global settings in SQL database,3,Default (<tt>@GLOBAL</tt>)
+addto=Add new directives to,1,1-SQL or LDAP database,0-Configuration file
index 09b82d6..575e6b1 100755 (executable)
@@ -44,10 +44,14 @@ else {
 
        if (!-r $local_cf && !-d $local_cf && !$module_info{'usermin'}) {
                # Config not found
-               print "<p>",&text('index_econfig',
+               print &text('index_econfig',
                        "<tt>$local_cf</tt>",
-                       "$gconfig{'webprefix'}/config.cgi?$module_name"),
-                       "<p>\n";
+                       "../config.cgi?$module_name"),"<p>\n";
+               }
+       elsif ($dberr = &check_spamassassin_db()) {
+               # Cannot contact the DB
+               print &text('index_edb', $dberr,
+                           "../config.cgi?$module_name"),"<p>\n";
                }
        else {
                # Work out of SpamAssassin is enabled in procmail
index 7677bd6..dcb8fb2 100644 (file)
@@ -2,6 +2,7 @@ index_title=SpamAssassin Mail Filter
 index_ecmd=The SpamAssassin command $1 was not found on your system. Maybe it is not installed, or your <a href='$2'>module configuration</a> is incorrect.
 index_ecmd2=The SpamAssassin command $1 was not found on your system.
 index_econfig=The SpamAssassin local configuration file or directory $1 was not found on your system. Maybe SpamAssassin is not installed, or your <a href='$2'>module configuration</a> is incorrect.
+index_edb=Failed to connect to the SpamAssassin configuration database : $1. Check the <a href='$2'>module configuration</a> to be sure you are using the correct settings.
 index_version=SpamAssassin version $1
 index_warn_usermin=SpamAssassin does not appear to be set up in your Procmail configuration file $2 or the global file $1, so any configuration done using this module will have no effect unless SpamAssassin has been setup globally.
 index_warn_webmin=SpamAssassin does not appear to be set up in the system's Procmail configuration file $1, so any configuration done using this module will have no effect unless users have it set up individually.
@@ -289,3 +290,8 @@ simple_err=Failed to save message tests
 
 before_ecmd=Before-saving command failed : $1
 after_ecmd=After-saving command failed : $1
+
+connect_emysql=Failed to load the database driver $1
+connect_elogin=Failed to login to the database $1 : $2.
+connect_equery=The database $1 does not contain the preferences table $2
+
index 4bd300c..8a58be1 100644 (file)
@@ -19,6 +19,9 @@ if ($module_info{'usermin'}) {
                        mkdir($1, 0700);
                        }
                }
+       $database_userpref_name = $remote_user;
+       $include_config_files = 1;      # XXX
+       $add_to_db = 1;
        }
 else {
        # Running under Webmin, typically editing global config file
@@ -29,6 +32,9 @@ else {
        if ($access{'nocheck'}) {
                $warn_procmail = 0;
                }
+       $database_userpref_name = $config{'dbglobal'} || '@GLOBAL';
+       $include_config_files = 1;
+       $add_to_db = $config{'addto'};
        }
 $add_cf = !-d $local_cf ? $local_cf :
          $module_info{'usermin'} ? "$local_cf/user_prefs" :
@@ -39,41 +45,68 @@ $add_cf = !-d $local_cf ? $local_cf :
 sub get_config
 {
 local @rv;
-local $lnum = 0;
-local $file = $_[0] || $local_cf;
-if (-d $file) {
-       # A directory of files - read them all
-       opendir(DIR, $file);
-       local @files = sort { $a cmp $b } readdir(DIR);
-       closedir(DIR);
-       local $f;
-       foreach $f (@files) {
-               if ($f =~ /\.cf$/) {
-                       local $add = &get_config("$file/$f");
-                       map { $_->{'index'} += scalar(@rv) } @$add;
-                       push(@rv, @$add);
+if ($include_config_files) {
+       # Reading from file(s)
+       local $lnum = 0;
+       local $file = $_[0] || $local_cf;
+       if (-d $file) {
+               # A directory of files - read them all
+               opendir(DIR, $file);
+               local @files = sort { $a cmp $b } readdir(DIR);
+               closedir(DIR);
+               local $f;
+               foreach $f (@files) {
+                       if ($f =~ /\.cf$/) {
+                               local $add = &get_config("$file/$f");
+                               map { $_->{'index'} += scalar(@rv) } @$add;
+                               push(@rv, @$add);
+                               }
                        }
                }
-       }
-else {
-       # A single file that can be read right here
-       open(FILE, $file);
-       while(<FILE>) {
-               s/\r|\n//g;
-               s/^#.*$//;
-               if (/^(\S+)\s*(.*)$/) {
-                       local $dir = { 'name' => $1,
-                                      'value' => $2,
-                                      'index' => scalar(@rv),
-                                      'file' => $file,
-                                      'line' => $lnum };
-                       $dir->{'words'} = [ split(/\s+/, $dir->{'value'}) ];
-                       push(@rv, $dir);
+       else {
+               # A single file that can be read right here
+               open(FILE, $file);
+               while(<FILE>) {
+                       s/\r|\n//g;
+                       s/^#.*$//;
+                       if (/^(\S+)\s*(.*)$/) {
+                               local $dir = { 'name' => $1,
+                                              'value' => $2,
+                                              'index' => scalar(@rv),
+                                              'file' => $file,
+                                              'mode' => 0,
+                                              'line' => $lnum };
+                               $dir->{'words'} =
+                                       [ split(/\s+/, $dir->{'value'}) ];
+                               push(@rv, $dir);
+                               }
+                       $lnum++;
                        }
-               $lnum++;
+               close(FILE);
+               }
+       }
+
+if ($config{'mode'} == 1 || $config{'mode'} == 2) {
+       # Add from SQL database
+       local $dbh = &connect_spamassasin_db();
+       &error($dbh) if (!ref($dbh));
+       local $cmd = $dbh->prepare("select preference,value from userpref where username = ?");
+       $cmd->execute($database_userpref_name);
+       while(my ($name, $value) = $cmd->fetchrow()) {
+               local $dir = { 'name' => $name,
+                              'value' => $value,
+                              'mode' => $config{'mode'} };
+               $dir->{'words'} =
+                       [ split(/\s+/, $dir->{'value'}) ];
+               push(@rv, $dir);
                }
-       close(FILE);
+       $cmd->finish();
        }
+elsif ($config{'mode'} == 3) {
+       # From LDAP
+       # XXX
+       }
+
 return \@rv;
 }
 
@@ -116,27 +149,89 @@ for($i=0; $i<@old || $i<@new; $i++) {
                }
        if ($old[$i] && $new[$i]) {
                # Replacing a directive
-               local $lref = &read_file_lines($old[$i]->{'file'});
-               $lref->[$old[$i]->{'line'}] = $line;
+               if ($old[$i]->{'mode'} == 0) {
+                       # In a file
+                       local $lref = &read_file_lines($old[$i]->{'file'});
+                       $lref->[$old[$i]->{'line'}] = $line;
+                       }
+               elsif ($old[$i]->{'mode'} == 1 || $old[$i]->{'mode'} == 2) {
+                       # In an SQL DB
+                       local $dbh = &connect_spamassasin_db();
+                       &error($dbh) if (!ref($dbh));
+                       local $cmd = $dbh->prepare("update userpref set value = ? where username = ? and preference = ? and value = ?");
+                       $cmd->execute($new[$i]->{'value'},
+                                     $database_userpref_name,
+                                     $old[$i]->{'name'},
+                                     $old[$i]->{'value'});
+                       $cmd->finish();
+                       }
+               elsif ($old[$i]->{'mode'} == 3) {
+                       # In LDAP
+                       # XXX
+                       }
                $_[0]->[$old[$i]->{'index'}] = $new[$i];
                }
        elsif ($old[$i]) {
                # Deleting a directive
-               local $lref = &read_file_lines($old[$i]->{'file'});
-               splice(@$lref, $old[$i]->{'line'}, 1);
+               if ($old[$i]->{'mode'} == 0) {
+                       # From a file
+                       local $lref = &read_file_lines($old[$i]->{'file'});
+                       splice(@$lref, $old[$i]->{'line'}, 1);
+                       foreach $c (@{$_[0]}) {
+                               if ($c->{'line'} > $old[$i]->{'line'} &&
+                                   $c->{'file'} eq $old[$i]->{'file'}) {
+                                       $c->{'line'}--;
+                                       }
+                               }
+                       }
+               elsif ($old[$i]->{'mode'} == 1 || $old[$i]->{'mode'} == 2) {
+                       # From an SQL DB
+                       local $dbh = &connect_spamassasin_db();
+                       &error($dbh) if (!ref($dbh));
+                       local $cmd = $dbh->prepare("delete from userpref where username = ? and preference = ? and value = ?");
+                       $cmd->execute($database_userpref_name,
+                                     $old[$i]->{'name'},
+                                     $old[$i]->{'value'});
+                       $cmd->finish();
+                       }
+               elsif ($old[$i]->{'mode'} == 3) {
+                       # From LDAP
+                       # XXX
+                       }
+
+               # Fix up indexes
                splice(@{$_[0]}, $old[$i]->{'index'}, 1);
                foreach $c (@{$_[0]}) {
-                       $c->{'line'}-- if ($c->{'line'} > $old[$i]->{'line'} &&
-                                          $c->{'file'} eq $old[$i]->{'file'});
-                       $c->{'index'}-- if ($c->{'index'} > $old[$i]->{'index'});
+                       if ($c->{'index'} > $old[$i]->{'index'}) {
+                               $c->{'index'}--;
+                               }
                        }
                }
        elsif ($new[$i]) {
                # Adding a directive
-               local $lref = &read_file_lines($add_cf);
-               $new[$i]->{'line'} = @$lref;
+               local $addmode = scalar(@old) ? $old[0]->{'mode'} :
+                                $add_to_db ? $config{'mode'} : 0;
+               if ($addmode == 0) {
+                       # To a file
+                       local $lref = &read_file_lines($add_cf);
+                       $new[$i]->{'line'} = @$lref;
+                       push(@$lref, $line);
+                       }
+               elsif ($addmode == 1 || $addmode == 2) {
+                       # To an SQL DB
+                       local $dbh = &connect_spamassasin_db();
+                       &error($dbh) if (!ref($dbh));
+                       local $cmd = $dbh->prepare("insert into userpref (username, preference, value) values (?, ?, ?)");
+                       $cmd->execute($database_userpref_name,
+                                     $new[$i]->{'name'},
+                                     $new[$i]->{'value'});
+                       $cmd->finish();
+                       }
+               elsif ($addmode == 3) {
+                       # To LDAP
+                       # XXX
+                       }
                $new[$i]->{'index'} = @{$_[0]};
-               push(@$lref, $line);
                push(@{$_[0]}, $new[$i]);
                }
        }
@@ -614,5 +709,84 @@ if ($config{'after_cmd'}) {
        }
 }
 
+# check_spamassassin_db()
+# Checks if the LDAP or MySQL backend can be contacted, and if not returns
+# an error message.
+sub check_spamassassin_db
+{
+if ($config{'mode'} == 0) {
+       return undef;   # Local files always work
+       }
+elsif ($config{'mode'} == 1 || $config{'mode'} == 2) {
+       # Connect to a database
+       local $dbh = &connect_spamassasin_db();
+       return $dbh if (!ref($dbh));
+       local $testcmd = $dbh->prepare("select * from userpref limit 1");
+       if (!$testcmd || !$testcmd->execute()) {
+               undef($connect_spamassasin_db_cache);
+               $dbh->disconnect();
+               return &text('connect_equery', "<tt>$config{'db'}</tt>",
+                                              "<tt>userpref</tt>");
+               }
+       $testcmd->finish();
+       undef($connect_spamassasin_db_cache);
+       $dbh->disconnect();
+       return undef;
+       }
+elsif ($config{'mode'} == 3) {
+       # Connect to LDAP
+       # XXX
+       }
+else {
+       return "Unknown config mode $config{'mode'} !";
+       }
+}
+
+# connect_spamassasin_db()
+# Attempts to connect to the SpamAssasin MySQL or PostgreSQL database. Returns
+# a driver handle on success, or an error message string on failure.
+sub connect_spamassasin_db
+{
+if (defined($connect_spamassasin_db_cache)) {
+       return $connect_spamassasin_db_cache;
+       }
+local $driver = $config{'mode'} == 1 ? "mysql" : "Pg";
+local $drh;
+eval <<EOF;
+use DBI;
+\$drh = DBI->install_driver(\$driver);
+EOF
+if ($@) {
+       return &text('connect_edriver', "DBD::$driver");
+        }
+local $dbistr = &make_dbistr($driver, $config{'db'}, $config{'host'});
+local $dbh = $drh->connect($dbistr,
+                           $config{'user'}, $config{'pass'}, { });
+$dbh || return &text('connect_elogin', "<tt>$config{'db'}</tt>",$drh->errstr)."\n";
+$connect_spamassasin_db_cache = $dbh;
+return $dbh;
+}
+
+sub make_dbistr
+{
+local ($driver, $db, $host) = @_;
+local $rv;
+if ($driver eq "mysql") {
+       $rv = "database=$db";
+       }
+elsif ($driver eq "Pg") {
+       $rv = "dbname=$db";
+       }
+else {
+       $rv = $db;
+       }
+if ($host) {
+       $rv .= ";host=$host";
+       }
+return $rv;
+}
+
+
+
 1;