spamassassin

This commit is contained in:
Joshua Tauberer 2013-08-23 11:59:28 -04:00
parent 5cef1bb63d
commit 97b2105a1f
7 changed files with 105 additions and 16 deletions

View file

@ -8,6 +8,8 @@ This draws heavily on Sovereign by Alex Payne (https://github.com/al3x/sovereign
Deploying to EC2 from the command line
--------------------------------------
Amazon's EC2 isn't a great place to host a mail server. Do you still need to request permission to send email first? And you don't know if you'll get an IP address with a bad reputation from its previous owner. But it makes deployment easy, so it may at least be useful for testing.
Sign up for Amazon Web Services.
Create an Access Key at https://console.aws.amazon.com/iam/home?#security_credential. Download the key and save the information somewhere secure.
@ -53,6 +55,8 @@ Somehow download these files.
sh scripts/index.sh
...
logout
You'll also want to set reverse DNS (PTR), which is something your hosting provider will probably have a control panel for.
Terminate your instance with:

View file

@ -109,17 +109,22 @@ EOF
sed -i "s/#port = 143/port = 0/" /etc/dovecot/conf.d/10-master.conf
sed -i "s/#port = 110/port = 0/" /etc/dovecot/conf.d/10-master.conf
# Modify the unix socket for LMTP.
sed -i "s/unix_listener lmtp \(.*\)/unix_listener \/var\/spool\/postfix\/private\/dovecot-lmtp \1\n user = postfix\n group = postfix\n/" /etc/dovecot/conf.d/10-master.conf
# Add an additional auth socket for postfix. Check if it already is
# set to make sure this is idempotent.
if grep -q "mailinabox-postfix-private-auth" /etc/dovecot/conf.d/10-master.conf; then
# already done
true;
else
sed -i "s/\(\s*unix_listener auth-userdb\)/ unix_listener \/var\/spool\/postfix\/private\/auth \{ # mailinabox-postfix-private-auth\n mode = 0666\n user = postfix\n group = postfix\n \}\n\1/" /etc/dovecot/conf.d/10-master.conf
fi
# Create a Unix domain socket specific for postgres to connect via LMTP because
# postgres is already configured to use this location, and create a TCP socket
# for spampd to inject mail on (if it's configured later). dovecot's standard
# lmtp unix socket is also listening.
cat > /etc/dovecot/conf.d/99-local.conf << EOF;
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
user = postfix
group = postfix
}
inet_listener lmtp {
address = 127.0.0.1
port = 10026
}
}
EOF
# Drew Crawford sets the auth-worker process to run as the mail user, but we don't care if it runs as root.

48
scripts/spamassassin.sh Normal file
View file

@ -0,0 +1,48 @@
# Spam filtering with spamassassin via spampd.
apt-get -q -y install spampd dovecot-antispam
# Hook into postfix. Replace dovecot with spampd as the mail delivery agent.
tools/editconf.py /etc/postfix/main.cf virtual_transport=lmtp:[127.0.0.1]:10025
# Hook into dovecot. This is actually the default but we don't want to lose track of it.
tools/editconf.py /etc/default/spampd DESTPORT=10026
# Automatically move spam into a folder called Spam. Enable the sieve plugin.
# (Note: Be careful if we want to use multiple plugins later.)
# The sieve scripts are installed by users_update.sh.
sudo sed -i "s/#mail_plugins = .*/mail_plugins = \$mail_plugins sieve/" /etc/dovecot/conf.d/20-lmtp.conf
# Enable the antispam plugin to detect when a message moves between folders so we can
# pass it to sa-learn for training. (Be careful if we use multiple plugins later.)
sudo sed -i "s/#mail_plugins = .*/mail_plugins = \$mail_plugins antispam/" /etc/dovecot/conf.d/20-imap.conf
# When mail is moved in or out of the dovecot Spam folder, re-train.
# from http://wiki2.dovecot.org/Plugins/Antispam
cat > /usr/bin/sa-learn-pipe.sh << EOF;
cat<&0 >> /tmp/sendmail-msg-\$\$.txt
/usr/bin/sa-learn \$* /tmp/sendmail-msg-\$\$.txt > /dev/null
rm -f /tmp/sendmail-msg-\$\$.txt
exit 0
EOF
chmod a+x /usr/bin/sa-learn-pipe.sh
cat > /etc/dovecot/conf.d/99-local-spampd.conf << EOF;
plugin {
antispam_backend = pipe
antispam_spam_pattern_ignorecase = SPAM
antispam_allow_append_to_spam = yes
antispam_pipe_program_spam_arg = /usr/bin/sa-learn-pipe.sh --spam
antispam_pipe_program_notspam_arg = /usr/bin/sa-learn-pipe.sh --ham
antispam_pipe_program = /bin/bash
}
EOF
# Initial training?
# sa-learn --ham storage/mail/mailboxes/*/*/cur/
# sa-learn --spam storage/mail/mailboxes/*/*/.Spam/cur/
sudo service spampd restart
sudo service dovecot restart

22
scripts/users_update.sh Normal file
View file

@ -0,0 +1,22 @@
# Install dovecot sieve scripts to automatically move spam into the Spam folder.
db_path=$STORAGE_ROOT/mail/users.sqlite
for user in `echo "SELECT email FROM users;" | sqlite3 $db_path`; do
maildir=`echo $user | sed "s/\(.*\)@\(.*\)/\2\/\1/"`
# Write the sieve file to move mail classified as spam into the spam folder.
mkdir -p $STORAGE_ROOT/mail/mailboxes/$maildir; # in case user has not received any mail
cat > $STORAGE_ROOT/mail/mailboxes/$maildir/.dovecot.sieve << EOF;
require ["regex", "fileinto", "imap4flags"];
if allof (header :regex "X-Spam-Status" "^Yes") {
setflag "\\\\Seen";
fileinto "Spam";
stop;
}
EOF
done

View file

@ -1,7 +1,9 @@
import imaplib, os
username = "testuser@" + os.environ.get("DOMAIN", "testdomain.com")
M = imaplib.IMAP4_SSL(os.environ["INSTANCE_IP"])
M.login("testuser@testdomain.com", "testpw")
M.login(username, "testpw")
M.select()
print("Login successful.")
typ, data = M.search(None, 'ALL')

View file

@ -4,20 +4,27 @@ import sys, re
# sanity check
if len(sys.argv) < 3:
print("usage: python3 editconf.py /etc/file.conf NAME=VAL [NAME=VAL ...]")
print("usage: python3 editconf.py /etc/file.conf [-s] NAME=VAL [NAME=VAL ...]")
sys.exit(1)
# parse command line arguments
filename = sys.argv[1]
settings = sys.argv[2:]
delimiter = "="
delimiter_re = r"\s*=\s*"
if settings[0] == "-s":
settings.pop(0)
delimiter = " "
delimiter_re = r"\s+"
# create the new config file in memory
found = set()
buf = ""
for line in open(filename):
for i in range(len(settings)):
name, val = settings[i].split("=", 1)
m = re.match("\s*" + re.escape(name) + "\s*=\s*(.*?)\s*$", line)
m = re.match("\s*" + re.escape(name) + delimiter_re + "(.*?)\s*$", line)
if m:
# If this is already the setting, do nothing.
if m.group(1) == val:
@ -33,7 +40,7 @@ for line in open(filename):
break
# add the new setting
buf += name + "=" + val + "\n"
buf += name + delimiter + val + "\n"
# note that we've applied this option
found.add(i)
@ -46,7 +53,8 @@ for line in open(filename):
# Put any settings we didn't see at the end of the file.
for i in range(len(settings)):
if i not in found:
buf += settings[i] + "\n"
name, val = settings[i].split("=", 1)
buf += name + delimiter + val + "\n"
# Write out the new file.
with open(filename, "w") as f: