• If you are still using CentOS 7.9, it's time to convert to Alma 8 with the free centos2alma tool by Plesk or Plesk Migrator. Please let us know your experiences or concerns in this thread:
    CentOS2Alma discussion

Train Spamassassin in IMAP

I

inc595

Guest
I re-wrote my bash version into perl. It creates junk, ham_learn and spam_learn directories in all mailboxes. You can then use move spam messages to spam_learn directory to train spamassassin just as you would in the Plesk control panel. Then do the same to non-spam or "ham" mail by moving some mail to the ham_learn.

The first time the script is run it will check to see if the directories exist. If they do not then it will create them. If they do exist it will then learn from the mail in them. Ham mail is moved back to the inbox and spam mail is deleted. Any mail already tagged as spam is moved into the junk dir and then removed after 15 days.


Simply ceate the script /usr/local/sbin/psa-sa_learn.pl and set permissions
Code:
vi /usr/local/sbin/psa-sa_learn.pl
chmod +x /usr/local/sbin/psa-sa_learn.pl
Then paste in the following
Code:
#!/usr/bin/perl

use Switch;
use File::Copy;

$version="Plesk Spamassassin Learn Version 0.3 --writen by inc595";
$maildir="/var/qmail/mailnames";
$mode=$ARGV[0];

if ( "$ARGV[1]" != "" ) {
   $domainlist=$ARGV[1];
   print $domainlist;
}else{
   @dmnlist=</var/qmail/mailnames/*>;
   foreach $dmn (@dmnlist) {
      split(/\//, $dmn);
      push (@domainlist, $_[4]); 
   }
}

switch ($mode) {
   case /-n|-t|-j/ {
      learn();
   }
   case /-v|-V/ {
        print $version;
   }
   case "--help" {
      print "Description: 
This script creates ham_learn and spam_learn directories in all mailboxes.
If users are using webmail or IMAP they can train spamassassin by moving
mail to ham_learn to for good mail and spam_learn for spam mail.\n
Usage: psa-sa_learn.sh [OPTION] | All domains
\tpsa-sa_learn.sh [OPTION] example.com | Single domain
-n\t\tNormal mode\tRun live and silent.
-t \t\tTest mode\tRun without making change.
\t\t\t\tand display what would happen.
-v \t\t\tDisplay Version information.\n";
   }
   else { print "psa-sa_learn.pl: missing file argument\nTry \'psa-sa_learn.pl --help\' for more information."; }
}

#subs
sub learn(){
   foreach $domain (@domainlist) {
      foreach $mailname (`ls $maildir/$domain | grep -v .spamassassin | grep -v .qmail-default | grep -v user_prefs | grep -v auto-whitelist | grep -v bayes* | grep -v "@mbox.quota" | grep -v "*qmail*"`){
         chomp($maildir);
         chomp($mailname);
         $path="$maildir/$domain/$mailname";
         $jpath="$path/Maildir/.junk";
         if (-d $jpath ) {
            switch ($mode) {
               case "-t" { print "Junk directory exists.\n$jpath\n"; }
               case "-j" {
                  create_qmail();
                  create_procmail();
               }
            }
         }else{
            switch ($mode) {
               case "-t" { print "Junk directory does not exist.\nCreating $jpath\n"; }
               case "-n" {
                  mkdir("$jpath", 0700) || print $!;
                  mkdir("$jpath/cur", 0700) || print $!;
                  mkdir("$jpath/new", 0700) || print $!;
                  mkdir("$jpath/tmp", 0700) || print $!;
                  open(MDF, ">>$jpath/maildirfolder");
                  chmod(0600, "$jpath/maildirfolder");
                  open(CIA, "$jpath/courierimapacl");
                     print CIA "owner aceilrstwx";
                  close (CIA);
                  chmod(0644, "$jpath/courierimapacl");
                  `chown -R popuser:popuser $jpath`;
                  create_qmail($path);
                  create_procmail($path);
               }
            }
         }
         foreach $type ( 'spam', 'ham' ) {
            $mpath="$path/Maildir/.$type\_learn";
            if ( -d $mpath ) {
               switch ($mode) {
                  case "-t" {
                     print "$type directory exists.\n$mpath\n";
                     if ( `ls -l $mpath/cur/ | wc -l` != 1 ) {
                        print "Not empty; learning $type.\n";
                        print "/usr/local/psa/admin/sbin/spammng --bayes --mailname=$mailname@$domain --$type=$mpath/cur/*\n";
                        if ( $type eq 'spam' ) {
                           print "rm $mpath/cur/*\n";
                           print "$mpath/cur/* for $mailname@$domain has been deleted\n";
                        }elsif ( $type eq 'ham' ) {
                           print "mv $mpath/cur/*  $maildir/$domain/$mailname/Maildir/cur\n";
                           print "$mpath/cur/* for $mailname@$domain has been moved to the inbox\n";
                        }else{
                           print "You should not see me\n";
                        }
                     }else{
                        print "Directory empty; skipping $type.";
                     }
                  }
                  case "-n" {
                     if ( `ls -l $mpath/cur/ | wc -l` != 1 ) {
                        `/usr/local/psa/admin/sbin/spammng --bayes --mailname=$mailname@$domain --$type=$mpath/cur/*`;
                        if ( $type eq 'spam' ) {
                           unlink ("$mpath/cur/*");
                        }elsif ( $type eq 'ham' ) {
                           $oldlocation="$mpath/cur/*";
                           $newlocation="$maildir/$domain/$mailname/Maildir/cur";
                           `mv $oldlocation $newlocation`;
                        }else{
                           print "You should not see me";
                        }
                     }
                  }
               }   
            }else{
               switch ($mode) {
                  case "-t" { print "Directory does not exist;\nCreating $mpath\n"; }
                  case "-n" {
                     mkdir("$mpath", 0700) || print $!;
                     mkdir("$mpath/cur", 0700) || print $!;
                     mkdir("$mpath/new", 0700) || print $!;
                     mkdir("$mpath/tmp", 0700) || print $!;
                     open(MDF, ">>$mpath/maildirfolder");
                     chmod(0600, "$mpath/maildirfolder");
                     open(CIA, "$mpath/courierimapacl");
                        print CIA "owner aceilrstwx";
                     close (CIA);
                     chmod(0644, "$mpath/courierimapacl");
                  }
               }
            }
         }
      }
   }
}

sub create_qmail() {
$file="$path/.qmail";
$forward=`grep '&' $file`;
   open(QM, ">$file");
   print QM "| /usr/local/psa/bin/psa-spamc accept
|preline /usr/bin/procmail -m -o .procmailrc
|find ./Maildir/.junk/cur -type f -mtime +15 -exec rm {} \\;
$forward";
   close(QM);
   `chown popuser:popuser $file`;
}

sub create_procmail($pth) {
   $file="$path/.procmailrc";
   print "$file\n";
   open(PROC, ">$file");
   print PROC "
MAILDIR=$maildir/$domain/$mailname/Maildir/
DEFAULT=\${MAILDIR}/
SPAMDIR=\${MAILDIR}/.junk/
# All mail tagged as spam (eg. with a score higher than the set threshold)
# is moved to the designated spam folder
:0
* ^X-Spam-Status: Yes.*
\${SPAMDIR}
";
   close(PROC);
   chmod(0600, "$file");
   `chown popuser:popuser $file`;
}


Then run every hour by editing your crontab.

Code:
crontab -e

And add this.
Code:
0 */1 * * * /usr/local/sbin/psa-sa_learn.pl -n >/dev/null 2>&1
 
Horde Spam Handling Options

To turn on Spam reporting options in Horde and tailor them to work with my script I modified the following files. What it does is turn on an "Empty Spam" icon. This will empty the junk folder. Then it creates a "Report as Spam" link which moves mail to the spam_learn directory. It also creates a 'Report as Innocent" link that moves mail to the ham_learn directory. This helps users in Horde to not have to understand what is going on as much.

Code:
/usr/share/psa-horde/imp/config/conf.php
/usr/share/psa-horde/imp/config/prefs.php
/usr/share/psa-horde/imp/mailbox.php
/usr/share/psa-horde/imp/message.php


Make sure to back them up before making any changes.
Code:
cp /usr/share/psa-horde/imp/config/conf.php /usr/share/psa-horde/imp/config/conf.php.original
cp /usr/share/psa-horde/imp/config/prefs.php /usr/share/psa-horde/imp/config/prefs.php.original
cp /usr/share/psa-horde/imp/mailbox.php /usr/share/psa-horde/imp/mailbox.php.original
cp /usr/share/psa-horde/imp/message.php /usr/share/psa-horde/imp/message.php.original

These diffs show the line number and what was modified or added to each respective file. Since we modified the default prefs the user doesn't need to set them for these to work. The user can still overide these settings in Horde, but that's on them.

#/usr/share/psa-horde/imp/config/conf.php
Code:
*** conf.php	2008-11-07 09:05:34.000000000 -0500
--- conf.php.original	2008-11-07 09:05:55.000000000 -0500
***************
*** 35,44 ****
  $conf['fetchmail']['size_limit'] = '4000000';
  $conf['msgsettings']['filtering']['words'] = './config/filter.txt';
  $conf['msgsettings']['filtering']['replacement'] = '****';
! $conf['spam']['spamfolder'] = true;
! $conf['spam']['notspamfolder'] = true;
! $conf['spam']['reporting'] = true;
! $conf['notspam']['reporting'] = true;
  $conf['msg']['prepend_header'] = false;
  $conf['msg']['append_trailer'] = false;
  $conf['compose']['use_vfs'] = false;
--- 35,42 ----
  $conf['fetchmail']['size_limit'] = '4000000';
  $conf['msgsettings']['filtering']['words'] = './config/filter.txt';
  $conf['msgsettings']['filtering']['replacement'] = '****';
! $conf['spam']['reporting'] = false;
! $conf['notspam']['reporting'] = false;
  $conf['msg']['prepend_header'] = false;
  $conf['msg']['append_trailer'] = false;
  $conf['compose']['use_vfs'] = false;

#/usr/share/psa-horde/imp/config/prefs.php
Code:
*** prefs.php	2008-11-07 10:12:18.000000000 -0500
--- prefs.php.original	2008-11-07 09:05:50.000000000 -0500
***************
*** 295,301 ****
  
  // spam folder
  $_prefs['spam_folder'] = array(
!     'value' => 'junk',
      'locked' => false,
      'shared' => false,
      'type' => 'implicit');
--- 295,301 ----
  
  // spam folder
  $_prefs['spam_folder'] = array(
!     'value' => 'Spam',
      'locked' => false,
      'shared' => false,
      'type' => 'implicit');
***************
*** 600,613 ****
  
  // What should we do with spam messages after reporting them?
  $_prefs['delete_spam_after_report'] = array(
!     'value' => 3,
      'locked' => false,
      'shared' => false,
      'type' => 'enum',
      'enum' => array(0 => _("Nothing"),
                      1 => _("Delete spam messages"),
!                     2 => _("Move spam messages to spam folder and innocent messages to INBOX"),
!                     3 => _("Move spam messages to spam_learn folder and innocent messages to ham_learn")),
      'desc' => _("What should we do with spam messages after they have been reported as spam or innocent?"),
      'help' => 'prefs-delete_spam_after_report'
  );
--- 600,612 ----
  
  // What should we do with spam messages after reporting them?
  $_prefs['delete_spam_after_report'] = array(
!     'value' => 0,
      'locked' => false,
      'shared' => false,
      'type' => 'enum',
      'enum' => array(0 => _("Nothing"),
                      1 => _("Delete spam messages"),
!                     2 => _("Move spam messages to spam folder and innocent messages to INBOX")),
      'desc' => _("What should we do with spam messages after they have been reported as spam or innocent?"),
      'help' => 'prefs-delete_spam_after_report'
  );
***************
*** 759,765 ****
  
  // display the 'Empty Spam' link in the menubar?
  $_prefs['empty_spam_menu'] = array(
!     'value' => 1,
      'locked' => false,
      'shared' => false,
      'type' => 'checkbox',
--- 758,764 ----
  
  // display the 'Empty Spam' link in the menubar?
  $_prefs['empty_spam_menu'] = array(
!     'value' => 0,
      'locked' => false,
      'shared' => false,
      'type' => 'checkbox',

#/usr/share/psa-horde/imp/mailbox.php
Code:
*** mailbox.php	2008-11-07 10:05:04.000000000 -0500
--- mailbox.php.original	2008-11-07 10:07:00.000000000 -0500
***************
*** 171,183 ****
          require_once IMP_BASE . '/lib/Message.php';
          $imp_message = &IMP_Message::singleton();
          $imp_message->copy($targetMbox, IMP_MESSAGE_MOVE, $indices_mbox, true);
-     } elseif ($delete_spam == 3) {
-         $targetMbox = ($action == 'spam') ? IMP::folderPref('spam_learn', true) : 'INBOX.ham_learn';
-         require_once IMP_BASE . '/lib/Message.php';
-         $imp_message = &IMP_Message::singleton();
-         $imp_message->copy($targetMbox, IMP_MESSAGE_MOVE, $indices_mbox, true);
      }
- 
      break;
  
  case 'message_missing':
--- 171,177 ----

#/usr/share/psa-horde/imp/message.php
Code:
*** message.php	2008-11-07 10:04:49.000000000 -0500
--- message.php.original	2008-11-07 10:07:14.000000000 -0500
***************
*** 162,179 ****
                  require IMP_BASE . '/mailbox.php';
                  exit;
              }
-     } elseif ($delete_spam == 3) {
-         $targetMbox = ($action == 'spam') ? IMP::folderPref('spam_learn', true) : 'INBOX.ham_learn';
-         if ($targetMbox) {
-             require_once IMP_BASE . '/lib/Message.php';
-             $imp_message = &IMP_Message::singleton();
-             $imp_message->copy($targetMbox, IMP_MESSAGE_MOVE, $imp_mailbox, true);
-             if ($prefs->getValue('mailbox_return')) {
-                 _returnToMailbox($imp_mailbox->getMessageIndex());
-                 require IMP_BASE . '/mailbox.php';
-                 exit;
-             }
- 
          } else {
              $notification->push(_("Could not move message to spam mailbox - no spam mailbox defined in preferences."), 'horde.error');
          }
--- 162,167 ----
 
Back
Top