Introduction
I thought I might present a rough overview of the Sieve filters at work on my mail server. I’m using Dovecot Pigeonhole and I do not know if these will run on any other server. I think that most of this is pretty standard, though.
I try to introduce interesting Sieve extension RFCs with a link here, and maybe some text.
Structure
I have about ten different script files at work, which are all included in the default filter. This helps to keep things clean, and it is mostly clear what happens when, and where I have to look for changes.
Everything starts out with the simple default file.
require "include"; (1)
include :personal "labels"; (2)
# ATTENTION; Below are scripts which use fileinto and
# will also STOP processing afterwards.
# Scripts doing other stuff should be above
# this comment.
include :personal "auto-accounts";
include :personal "mailing-lists";
include :personal "spam";
-
Need the
include
(rfc6609) extension. -
:personal
is the default, but I try to be a bit more explicit here.
The first script will just apply some labels. These are the labels you can configure in your MUA. I use this to highlight some messages with a specific colour assigned to the label.
Note that this does not end with a stop;
command, because I would very much
like to have the other scripts execute, too.
require "imap4flags"; (1)
if header :contains ["to", "cc"] "example.org" {
setflag "secret_organization";
}
-
Need the
Imap4flags
(rfc5232) extension. Useaddflag
to add a flag to the set, usesetflag
to replace the whole set.
I have a large number of account-specific addresses. The following script is used to create corresponding folders. This is a very handy quasi-automation: I just create an alias for the account and everything else will solve itself.
This used to use address
instead of envelope
, allowing anyone to force
creation of folders. Luckily I finally found out why envelope
was never
quite working — I passed ${recipient}
to dovecot-lda
instead of
${original_recipient}
. Stupid oversight!
With that little bug fixed, I can go on and publish the script below, because now only valid addresses are accepted.
Anyway, a bit of an explanation is warranted here:
With “account-specific address” I mean an address that contains some sort of identifier plus a “secret”. The “secret” should make it hard to guess the right address and as such give me some assurance that the sender is not some third party. Its just an additional layer to protect me from clever scam mails — and they surely become more and more sophisticated.
Note that I do not have a catch-all; all addresses are explicitly created.
If, for example, a message to the alias
general-foobar-12345@folders.example.net
arrives, it will be sorted into the
folder General
. If a message arrives for santa-s3cr3t@folders.example.net
,
it will be put into a folder called Santa
. Parts are separated by a dash, the
first part is taken and stored into ${1}
. The script then takes it,
capitalizes the first character, and stores it in the variable foldername
.
#
# Automatically create mailboxes for mails going to
# some_name-some_secret@folders.example.net
#
require "regex"; (1)
require "mailbox";
require "envelope";
require "fileinto";
require "variables"; (2)
if envelope :domain "to" "folders.example.net" {
# Slurp until first dot, dash, etc.
if envelope :localpart :regex "to" "^([a-z0-9]+)" {
set :upperfirst "foldername" "${1}";
# If foldername is "General", put it into the
# generic 'Folders' mailbox, otherwise create a
# new one.
if string :is "${foldername}" "General" {
fileinto :create "INBOX.Folders";
} else {
fileinto :create "INBOX.Folders.${foldername}";
}
stop;
}
}
-
regex
is one of those forever drafts, the most recent version is draft-ietf-sieve-regex-01. The Dovecot wiki references some older version, but the more recent one seems to be more appropriate, because it also documents how variables (such as${1}
) are used. -
The
variables
(rfc5229) extension provides, well, variables and a number of modifiers. I useset
with:upperfirst
here, to get the nicer folder names.
Handling of mailing lists is mostly self-explanatory. I do keep an explicit
list here. Everything else will be sorted into the general INBOX.Lists
folder. The real list is quite a bit larger, but I still try to keep the number
of folders low.
require "fileinto";
require "mailbox"; (1)
if header :contains "list-id" "internal.example.org" {
fileinto :create "INBOX.Lists.Internal";
stop;
} elsif header :contains "list-id" "press.example.org" {
fileinto :create "INBOX.Lists.Press";
stop;
}
# For quasi-lists which are simple aliases
#
elsif address :contains ["to", "cc"] ["foo-list@example.org"] {
fileinto :create "INBOX.Lists.Foolist";
stop;
}
# This must be the last rule, it will check if list-id is set, and file the
# message into the INBOX.Lists-folder for further investigation
elsif header :matches "list-id" "<?*>" {
fileinto :create "INBOX.Lists";
stop;
}
-
fileinto
is in the base specification for Sieve, butmailbox
(rfc5490) is needed for the:create
argument.
This will file all suspicious stuff into INBOX.Spam
. I used comments below to
explain things a bit. In reality I also have a few rules that look at specific
headers that I have noticed. All this works well for things that appear
legitimate, but are not.
To veer away from Example World: I used to have a rule here for stuff from
linkedin.com
. They would send a lot of very annoying messages an behalf of
some poor schmucks that allowed them to index their mailboxes — I will never
understand this. Despite this being probably illegal here (there was some
ruling in that direction lately) they would try to pressure me with messages
like “You have not reacted to Dude MacPants invitation.”. Needless to say, this
gave me the kick in the butt I needed to reject certain domains up-front at the
SMTP server. Thanks‽
require "fileinto";
require "imap4flags";
require "body";
# Some spam has been marked as such by some friendly filters.
#
if header :is "X-Spam-Flag" "yes" {
fileinto "INBOX.Spam";
stop;
}
# Nobody sends me ZIP files with good intentions!
#
if body :raw :contains "Content-Type: application/x-zip-compressed;" { (1)
fileinto "INBOX.Spam";
stop;
}
# Those folks at spam.example.com are really doing nothing except
# sending spam! I will treat all mail from that domain the same.
#
if address :domain :is "from" [
"spam.example.com"
] {
fileinto "INBOX.Spam";
stop;
}
# Personal address handed to friends whose computers are full
# of Trojans slurping their address books and whatnot.
#
if allof (
address :is "to" "me@example.net",
address :localpart :matches "from" "noreply.*"
) {
fileinto "INBOX.Spam";
stop;
}
# Some messages reek!
#
if anyof (
header :contains "subject" [ (2)
"phish", "stink"
],
body :raw :contains ["shady banking site"] (3)
) # anyof
{
fileinto "INBOX.Spam";
stop;
}
-
There is the
mime
(rfc5703) extension, but it is not in the list of supported features. -
Checking if the message subject contains certain words
-
Checking if the message body contains certain words. This needs the
body
(rfc5173) extension. Note that I use the:raw
transformation here,:text
would be the default.