VRT System/Mail Setup

From Wikitech

Mail setup

E-mail is sent and received through a special Exim instance on the hosting server. Its configuration follows the lines of the setup described in Mail, but VRT specific configuration is listed below.

Spam and Malware scanning

SpamAssassin and ClamAV are used for spam/malware scanning, in Exim ACL which is run at the DATA phase during the SMTP connection. Should SpamAssassin fail for some reason, mail is let through.

Mail being let through when spamassasin fails is a graceful degradation mechanism but comes with serious drawbacks. The various queues will get flooded with tons of spam and the Agents will be righteously irritated. So it needs to be fixed quickly when reported
    # skip spam-check for locally-submitted messages
    accept hosts = +relay_from_hosts
        set acl_m0 = trusted relay

    # skip if message is too large (>4M)
    accept condition = ${if >{$message_size}{4M}}
        set acl_m0 = n/a
        set acl_m1 = skipped, message too large

    # skip if whitelisted in exim
    accept condition = ${if eq{$acl_m2}{skip_spamd}}
        set acl_m0 = n/a
        set acl_m1 = skipped, exim whitelist

    # add spam headers...
    warn spam = nonexistent:true
        set acl_m0 = $spam_score ($spam_bar)
        set acl_m1 = $spam_report
        set acl_m3 = $spam_score_int

    # silently drop spam at high scores (> 12)
    discard log_message = spam detected ($spam_score)
        condition = ${if >{$spam_score_int}{120}{1}{0}}

    # silently discard messages with malware attached
    discard log_message = malware detected ($malware_name)
        demime = *
        malware = *


Message tagging

We use Exim filters to tag messages with headers that VRT can match for automatic queue routing. The Exim filters are in /etc/exim4/system_filter (see the inline comments):

# Exim filter

if first_delivery then
    # Remove headers that control OTRS - we don't want these
    headers remove X-OTRS-Priority:X-OTRS-Queue:X-OTRS-Lock:X-OTRS-Ignore:X-OTRS-State
    if $acl_m0 is not "trusted relay" then
        # Remove any SpamAssassin headers and add local ones
        headers remove X-Spam-Score:X-Spam-Report:X-Spam-Checker-Version:X-Spam-Status:X-Spam-Level:X-Spam-Flag
    if $acl_m0 is not "" and $acl_m0 is not "trusted relay" then
        headers add "X-Spam-Score: $acl_m0"
        headers add "X-Spam-Report: $acl_m1"
        # Add header for OTRS filters
        if $acl_m1 is not "" and $acl_m1 begins "yes" then
            headers add "X-Spam-Flag: YES"
        # overload X-Spam-Flag since OTRS doesn't do numeric comparison
        elif $acl_m3 is not "" and $acl_m3 is above 20 then
            headers add "X-Spam-Flag: MAYBE"
            headers add "X-Spam-Flag: NO"
        # add a hook for OTRS to filter list mail
            ($message_headers contains "\nList-Id:" or
            $message_headers contains "\nList-Help:" or
            $message_headers contains "\nList-Subscribe:" or
            $message_headers contains "\nList-Unsubscribe:" or
            $message_headers contains "\nList-Post:" or
            $message_headers contains "\nList-Owner:" or
            $message_headers contains "\nList-Archive:") and
            $header_precedence: does not match "^(bulk|junk|list)"
            headers remove Precedence
            headers add "Precedence: bulk"

VRT mail routing

Mail destined for VRT is served by a simple accept router otrs, which does a MySQL database query to determine the validity of the recipient address being routed, similar to the check done earlier by the main MX hosts.

# Mail destined for OTRS

        driver = accept
        condition = ${lookup mysql{SELECT value0 FROM system_address WHERE value0='${quote_mysql:$local_part@$domain}'}{true}fail}
        transport = otrs

On success, the message is handed over to the otrs pipe transport:

# OTRS pipe transport

        driver = pipe
        command = OTRS_POSTMASTER
        current_directory = OTRS_HOME
        home_directory = OTRS_HOME
        user = OTRS_USER
        group = OTRS_GROUP
        timeout = 1m

This transport pipes the full contents of the message to the command/path specified in the macro OTRS_POSTMASTER (defined at the top of the file). A current and home directory will be set as specified, and the command will be run as the otrs user and group. If the actual execution/invocation fails for some reason, the message will be frozen on the queue with a warning message sent to root. If the command invocation succeeds, but the return code is EX_TEMPFAIL (e.g. when OTRS cannot access the database), the message is deferred/queued, and will be retried later. Any output will be logged.

Outbound mail

Any mail destined for an address that is not an OTRS address, e.g. mail submitted by OTRS itself, will be forwarded to an outbound MX.


The server runs its own ClamAV instance, using the stock clamav-daemon package. The daemon runs as user clamav which has read access to the mail queue via membership in group Debian-exim. Per the stock config, the freshclam daemon is used to update virus definitions.

Exim accesses ClamAV via unix socket at /var/run/clamav/clamd.ctl and silently drops and logs messages containing an infected attachment.


The server runs its own SpamAssassin instance. The stock spamassassin package is used, with daily updates enabled. Stock rules/scores are kept and we make a few local modifications which are listed below.

Multiple user profiles are not used, SpamAssassin reads global configuration settings and runs as user otrs. Training databases are stored in that user's homedir.


# Change to one to enable spamd

# Options
# See man spamd for possible options. The -d option is automatically added.

# SpamAssassin uses a preforking model, so be careful! You need to
# make sure --max-children is not set to anything higher than 5,
# unless you know what you're doing.

OPTIONS="--max-children 8 --nouser-config --listen-ip= -u otrs -g otrs"

# Pid file
# Where should spamd write its PID to file? If you use the -u or
# --username option above, this needs to be writable by that user.
# Otherwise, the init script will not be able to shut spamd down.

# Set nice level of spamd
NICE="--nicelevel 10"

# Cronjob
# Set to anything but 0 to enable the cron job to automatically update
# spamassassin's rules on a nightly basis


Non-stock sections are shown here:

#   Set which networks or hosts are considered 'trusted' by your mail
#   server (i.e. not spammers)
trusted_networks 2620:0:860::/46

# short-format report template, starting with Yes/No, used for OTRS filters
report _YESNO_, score=_SCORE_ | host: _HOSTNAME_ | scores: _TESTSSCORES(,)_ | autolearn=_AUTOLEARN_

#   Set file-locking method (flock is not safe over NFS, but is faster)
lock_method flock

#   Set the threshold at which a message is considered spam (default: 5.0)
required_score 3.5
score RP_MATCHES_RCVD -0.500
score RCVD_IN_RP_SAFE 2.000
score SPF_SOFTFAIL 2.000

SpamAssassin Training

There a few steps to spam training:

  1. user moves spammy messages to the Junk queue
  2. OTRS Generic Agent:"Export_Spam" runs nightly, filtering for tickets which are not in state "Closed successful" passing MessageIDs to otrs.TicketExport2Mbox.pl
  3. otrs.TicketExport2Mbox.pl writes the messages to /var/spool/spam/spam, and changes the ticket's state to "Closed successful"
  4. /usr/local/bin/train_spamassassin picks up /var/spool/spam/spam and feeds it to sa-learn as spam

Ham training is similar:

  1. OTRS Generic Agent:"Export_Ham" runs nightly, filtering for tickets in non-Junk queues which are in states Open, or Closed successful" and feeding those TicketID's to otrs.TicketExport2Mbox.pl
  2. otrs.TicketExport2Mbox.pl writes the messages to /var/spool/spam/ham
  3. /usr/local/bin/train_spamassassin picks up /var/spool/spam/ham and feeds it to sa-learn as ham

The two scripts mentioned above are custom and are installed by puppet from operations/puppet/files/otrs/* in the git repository.