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.

default

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";
  1. Need the include (rfc6609) extension.

  2. :personal is the default, but I try to be a bit more explicit here.

labels

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";
}
  1. Need the Imap4flags (rfc5232) extension. Use addflag to add a flag to the set, use setflag to replace the whole set.

auto-accounts

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;
  }
}
  1. 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.

  2. The variables (rfc5229) extension provides, well, variables and a number of modifiers. I use set with :upperfirst here, to get the nicer folder names.

mailing-lists

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;
}
  1. fileinto is in the base specification for Sieve, but mailbox (rfc5490) is needed for the :create argument.

spam

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;
}
  1. There is the mime (rfc5703) extension, but it is not in the list of supported features.

  2. Checking if the message subject contains certain words

  3. 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.