283 lines
6.7 KiB
Bash
Executable File
283 lines
6.7 KiB
Bash
Executable File
#!/bin/bash
|
|
### Copyright 1999-2025. WebPros International GmbH. All rights reserved.
|
|
|
|
export LC_ALL="C"
|
|
unset GREP_OPTIONS
|
|
|
|
PRODUCT_ROOT_D="/opt/psa"
|
|
|
|
learn_bin="/usr/bin/sa-learn"
|
|
|
|
maildir="/var/qmail/mailnames"
|
|
maildir_subdir="Maildir"
|
|
|
|
train_subdir=".spamtrain"
|
|
train_confdir="$maildir/$train_subdir"
|
|
|
|
stamp_file="$train_confdir/last_update"
|
|
|
|
spam_dirs=''
|
|
spam_subdirs="*.Spam*"
|
|
spamassassin_subdir=".spamassassin"
|
|
|
|
ham_dirs=''
|
|
skip_subdirs=".Drafts .Sent .Trash .spamassassin @attachments courierimap* .sent-mail"
|
|
|
|
# do not train spamfilter if messages size greater than mas_size_max
|
|
msg_size_max="256000"
|
|
|
|
# remove messages from .Spam folder after period(in days)
|
|
delete_interval="30"
|
|
|
|
owner="popuser"
|
|
group="popuser"
|
|
system="$(uname -s)"
|
|
|
|
if [ "$system" = "Linux" ]; then
|
|
su_opts="-s /bin/sh"
|
|
fi
|
|
|
|
accounts=''
|
|
domains=''
|
|
enabled_mail_addresses="UNDEFINED"
|
|
|
|
cur_time="$(date +'%s')"
|
|
expired_time="$(expr $cur_time - $delete_interval \* 3600 \* 24)"
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
get_domains_list()
|
|
{
|
|
if [ -z "$domains" ]; then
|
|
domains="`find $maildir/ -maxdepth 1 -mindepth 1 -type d ! -name $train_subdir -exec basename {} \;`"
|
|
fi
|
|
}
|
|
|
|
get_accounts_list()
|
|
{
|
|
local domain="$1"
|
|
|
|
if [ -z "$domain" ]; then
|
|
print_err "Domain not defined for searching mail accounts"
|
|
fi
|
|
|
|
accounts="`find $maildir/$domain -maxdepth 1 -mindepth 1 -type d -exec basename {} \;`"
|
|
}
|
|
|
|
get_enabled_mail_addresses_list()
|
|
{
|
|
[ "$enabled_mail_addresses" = "UNDEFINED" ] || return 0
|
|
|
|
enabled_mail_addresses="`set -o pipefail; $PRODUCT_ROOT_D/admin/sbin/mail_handlers_control --list --json |
|
|
"/usr/bin/python3" -c 'import sys, json; \
|
|
handlers = json.load(sys.stdin)["handlers"]; \
|
|
print( "\n".join( \
|
|
h["address"] for h in handlers if h["name"] == "spam" and h["enabled"] \
|
|
) );'`" ||
|
|
print_err "Cannot get list of mailboxes with enabled spam filter"
|
|
}
|
|
|
|
get_spamdirs_list()
|
|
{
|
|
local spam_find
|
|
local domain="$1"
|
|
local account="$2"
|
|
|
|
if [ -z "$domain" -o -z "$account" ]; then
|
|
print_err "Domain or account not defined for searching spam dirs"
|
|
fi
|
|
|
|
# generate find command for spam directories
|
|
for dir in $spam_subdirs; do
|
|
if [ -z "$spam_find" ]; then
|
|
spam_find="find $maildir/$domain/$account/$maildir_subdir \
|
|
-maxdepth 1 -mindepth 1 -type d -name $dir"
|
|
continue
|
|
fi
|
|
spam_find="$spam_find -or -name $dir"
|
|
done
|
|
|
|
spam_dirs="`$spam_find ! -name 'tmp' ! -name 'new' ! -name 'cur' -exec echo {}\% \; | xargs echo -n`"
|
|
}
|
|
|
|
get_hamdirs_list()
|
|
{
|
|
local ham_find
|
|
local domain="$1"
|
|
local account="$2"
|
|
|
|
if [ -z "$domain" -o -z "$account" ]; then
|
|
print_err "Domain or account not defined for searching ham dirs"
|
|
fi
|
|
|
|
for dir in $spam_subdirs $skip_subdirs; do
|
|
if [ -z "$ham_find" ]; then
|
|
ham_find="find $maildir/$domain/$account/$maildir_subdir \
|
|
-maxdepth 1 -mindepth 1 -type d ! -name $dir"
|
|
continue
|
|
fi
|
|
ham_find="$ham_find -and ! -name $dir"
|
|
done
|
|
|
|
ham_dirs=".%`$ham_find ! -name 'tmp' ! -name 'new' ! -name 'cur' -exec echo {}\% \; | xargs echo -n`"
|
|
}
|
|
|
|
spam_learn()
|
|
{
|
|
local type="$1"
|
|
local path="$2"
|
|
local folders="$3"
|
|
local subdirs="cur"
|
|
local stamp
|
|
local learn_messages="$train_confdir/learn_msgs_$cur_time"
|
|
local spam_messages="$train_confdir/spam_msgs_$cur_time"
|
|
|
|
[ "$type" = "ham" -o "$type" = "spam" ] || print_err "Wrong type of content spam/ham: $type"
|
|
|
|
if [ "$type" = "spam" ]; then
|
|
subdirs="$subdirs%new%tmp"
|
|
fi
|
|
|
|
if [ -f "$stamp_file" ]; then
|
|
stamp="`cat $stamp_file`"
|
|
fi
|
|
|
|
IFS_OLD="$IFS"
|
|
IFS="%
|
|
"
|
|
touch $learn_messages
|
|
touch $spam_messages
|
|
for folder in $folders; do
|
|
folder="`basename $folder`"
|
|
for dir in $subdirs; do
|
|
for msg in `find $path/$maildir_subdir/$folder/$dir -type f`; do
|
|
local msg_file="${msg##*/}"
|
|
local msg_date="${msg_file%%.*}"
|
|
|
|
# Spam messages to remove from mailbox after training
|
|
if [ "$type" = "spam" -a "$msg_date" -lt "$expired_time" ]; then
|
|
echo "$msg" >> $spam_messages
|
|
fi
|
|
|
|
# skip messages greater than size limit..
|
|
local msg_size="`/usr/bin/stat -c '%s' \"$msg\"`"
|
|
if [ "$msg_size" -gt "$msg_size_max" ]; then
|
|
continue
|
|
fi
|
|
|
|
if [ -n "$stamp" ]; then
|
|
if [ "$msg_date" -gt "$stamp" ]; then
|
|
echo "$msg" >> $learn_messages
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
echo "$msg" >> $learn_messages
|
|
done
|
|
done
|
|
done
|
|
IFS="$IFS_OLD"
|
|
|
|
su - $owner $su_opts -c "$learn_bin --$type --no-sync -L --dbpath $path/$spamassassin_subdir -f $learn_messages" < /dev/null
|
|
|
|
cat $spam_messages | xargs rm -f
|
|
|
|
su - $owner $su_opts -c "$learn_bin --sync --dbpath $path/$spamassassin_subdir" < /dev/null
|
|
|
|
rm $learn_messages
|
|
rm $spam_messages
|
|
}
|
|
|
|
spam_train()
|
|
{
|
|
local cur_domain
|
|
local cur_account
|
|
|
|
local found
|
|
local domain_file="$train_confdir/domain"
|
|
local account_file="$train_confdir/account"
|
|
|
|
if [ -f "$domain_file" -a -f "$account_file" ]; then
|
|
cur_domain="`cat $domain_file`"
|
|
cur_account="`cat $account_file`"
|
|
fi
|
|
|
|
get_enabled_mail_addresses_list
|
|
get_domains_list
|
|
|
|
for domain in $domains; do
|
|
# begin from previous point if the training was stopped or killed before..
|
|
if [ -n "$cur_domain" -a -n "$cur_account" ]; then
|
|
if [ "$domain" != "$cur_domain" -a "$account" != "$cur_account" ]; then
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
found=1
|
|
|
|
get_accounts_list "$domain"
|
|
|
|
echo "$domain" > $domain_file
|
|
|
|
for account in $accounts; do
|
|
echo "$enabled_mail_addresses" | grep -qxF "${account}@${domain}" 2>/dev/null || continue
|
|
|
|
get_spamdirs_list "$domain" "$account"
|
|
get_hamdirs_list "$domain" "$account"
|
|
|
|
if [ ! -d "$maildir/$domain/$account/$spamassassin_subdir" ]; then
|
|
mkdir $maildir/$domain/$account/$spamassassin_subdir || \
|
|
print_err "Unable to create spamassassin subdir for ${account}@${domain}"
|
|
|
|
chmod 700 $maildir/$domain/$account/$spamassassin_subdir
|
|
chown $owner:$group $maildir/$domain/$account/$spamassassin_subdir
|
|
fi
|
|
|
|
if [ -n "$spam_dirs" ]; then
|
|
spam_learn "spam" "$maildir/$domain/$account" "$spam_dirs"
|
|
spam_learn "ham" "$maildir/$domain/$account" "$ham_dirs"
|
|
fi
|
|
|
|
echo "$account" > $account_file
|
|
done
|
|
done
|
|
|
|
# remove reference on domain/account
|
|
# if checking was not killed/stopped by timeout etc..
|
|
rm -f $domain_file $account_file
|
|
|
|
# Looking for all domains/accounts if reference domain/account
|
|
# was removed after last spamfilter learning
|
|
if [ -z "$found" -a -n "$domains" ]; then
|
|
spam_train
|
|
fi
|
|
|
|
echo "$cur_time" >$stamp_file
|
|
}
|
|
|
|
print_err()
|
|
{
|
|
echo "ERROR: $*"
|
|
exit 1
|
|
}
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
# The some checks for avoid a stupid errors
|
|
if [ -z "$maildir" -o -z "$learn_bin" ]; then
|
|
print_err "Some constants are not defined."
|
|
fi
|
|
|
|
if [ ! -d "$train_confdir" ]; then
|
|
mkdir $train_confdir || print_err "Unable to create config dir: $train_confdir"
|
|
fi
|
|
|
|
if [ ! -x "$learn_bin" ]; then
|
|
echo "Spamassassin not found"
|
|
exit 0
|
|
fi
|
|
|
|
spam_train
|
|
|
|
|