[exim] Exim with virtual domains and Sieve

Top Page
Delete this message
Reply to this message
Author: Yves Goergen
Date:  
To: Exim-users list
Subject: [exim] Exim with virtual domains and Sieve
Hello,

I want to setup Sieve with my Exim server but can't quite figure out how
it should be integrated into my configuration.

My server provides web and mail services for multiple users. The domains
and mailbox accounts are stored in a MySQL database and Exim is set up
to look up everything from there. No local system accounts involved.
Each mailbox has a Maildir directory in /var/mail/virtual and all these
directories belong to the system user mail and group mail.

All Sieve tutorials I've found expect that I have traditional local
system users for my mailboxes and only a single domain. I know nothing
about Sieve because I haven't seen it anywhere else yet. But it seems
that Sieve uses script file(s) that should be stored in the mailbox
user's home directory. Which assumes that the user's mail is also stored
in their home directory. Like in the 70s. Unfortunately I don't have that.

I've attached my Exim configuration for reference. Can anybody please
explain how Sieve would be integrated there?

I'm especially interested in using the Sieve features of message
forwarding*, auto responders and moving messages into folders (mostly
the Junk folder) directly on the server.

*) My config has mailboxes that store mail locally as well as forwards
that only forward mail to one or more addresses. I'd also like to
temporarily forward mail that is addressed to a mailbox to another address.

The Exim version is 4.86 on Ubuntu 16.04 server. Here's the full output
from 'exim --version':

> Exim version 4.86_2 #1 built 05-Jan-2017 13:29:10
> Copyright (c) University of Cambridge, 1995 - 2015
> (c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2015
> Berkeley DB: Berkeley DB 5.3.28: (September 9, 2013)
> Support for: crypteq iconv() IPv6 PAM Perl Expand_dlfunc GnuTLS move_frozen_messages Content_Scanning DKIM Old_Demime DNSSEC PRDR OCSP
> Lookups (built-in): lsearch wildlsearch nwildlsearch iplsearch cdb dbm dbmjz dbmnz dnsdb dsearch ldap ldapdn ldapm mysql nis nis0 passwd pgsql sqlite
> Authenticators: cram_md5 cyrus_sasl dovecot plaintext spa tls
> Routers: accept dnslookup ipliteral iplookup manualroute queryprogram redirect
> Transports: appendfile/maildir/mailstore/mbx autoreply lmtp pipe smtp
> Fixed never_users: 0
> Size of off_t: 8
> Configuration file is /etc/exim4/exim4.conf


Yves
MYSQL_SERVER = 127.0.0.1
MYSQL_USER   = <<DB_USER_MAIL>>
MYSQL_PASS   = <<DB_PASS_MAIL>>
MYSQL_DB     = <<DB_NAME>>
MAIN_HOST    = <<HOST>>
MAIN_IP4     = <<IP4_1>>
MAIN_IP6     = <<IP6_1>>


# Recipient address of the mail
VAR_RCPT_ADDR = m0
# New subject header
VAR_SUBJECT = m1
# Spam-Score header
VAR_SPAM_SCORE = m2
# Spam-Report header
VAR_SPAM_REPORT = m3

MYSQL_Q_LDOMAIN   = SELECT DISTINCT domain FROM mailusers WHERE domain='${quote_mysql:$domain}' LIMIT 1
MYSQL_Q_MAILDIR   = SELECT maildir FROM mailusers WHERE local='${quote_mysql:$local_part}' AND domain='${quote_mysql:$domain}' AND maildir!='' LIMIT 1
MYSQL_Q_AUTH_CR1  = SELECT cryptpass FROM mailusers WHERE CONCAT(local, '@', domain)='${quote_mysql:$1}' LIMIT 1
MYSQL_Q_AUTH_CR2  = SELECT cryptpass FROM mailusers WHERE CONCAT(local, '@', domain)='${quote_mysql:$2}' LIMIT 1
MYSQL_Q_FORWARD   = SELECT forward FROM mailusers WHERE (local='${quote_mysql:$local_part}' OR local='*') AND domain='${quote_mysql:$domain}' AND forward!='' LIMIT 1
MYSQL_Q_SENDERS   = SELECT senders FROM mailusers WHERE (local='${quote_mysql:$local_part}' OR local='*') AND domain='${quote_mysql:$domain}' AND forward!='' LIMIT 1
MYSQL_Q_QUOTA     = SELECT quota FROM mailusers WHERE local='${quote_mysql:$local_part}' AND domain='${quote_mysql:$domain}' AND forward='' LIMIT 1
MYSQL_Q_SPAMFILTER_MAIL = SELECT spamfilter FROM mailusers WHERE CONCAT(local, "@", domain)='${quote_mysql:$acl_VAR_RCPT_ADDR}' LIMIT 1
#MYSQL_Q_SPAMFILTER_RCPT = SELECT spamfilter FROM mailusers WHERE local='${quote_mysql:$local_part}' AND domain='${quote_mysql:$domain}' LIMIT 1
MYSQL_Q_VIRUSFILTER_MAIL = SELECT virusfilter FROM mailusers WHERE CONCAT(local, "@", domain)='${quote_mysql:$recipients}' LIMIT 1
#MYSQL_Q_VIRUSFILTER_RCPT = SELECT virusfilter FROM mailusers WHERE local='${quote_mysql:$local_part}' AND domain='${quote_mysql:$domain}' LIMIT 1


hide mysql_servers = "MYSQL_SERVER/MYSQL_DB/MYSQL_USER/MYSQL_PASS"
primary_hostname = MAIN_HOST
domainlist local_domains = localhost : @ : mysql;MYSQL_Q_LDOMAIN
domainlist relay_to_domains =
hostlist relay_from_hosts = 127.0.0.1
acl_smtp_mail = acl_check_mail
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data
acl_smtp_mime = acl_check_mime
never_users = root
host_lookup = *
rfc1413_query_timeout = 0s
# message_size_limit = 50M
ignore_bounce_errors_after = 2d
timeout_frozen_after = 7d

tls_advertise_hosts = *
tls_certificate = /etc/letsencrypt/live/MAIN_HOST/fullchain.pem
tls_privatekey = /etc/letsencrypt/live/MAIN_HOST/privkey.pem
#tls_certificate = /etc/ssl/private/MAIN_HOST
#tls_privatekey = /etc/ssl/private/MAIN_HOST
tls_on_connect_ports = 465
tls_require_ciphers = NORMAL:!VERS-SSL3.0

local_interfaces = <; MAIN_IP4 ; MAIN_IP6 ; 127.0.0.1 ; ::1
daemon_smtp_ports = 25 : 465 : 587

untrusted_set_sender = "^\\$"
# smtp_enforce_sync = true
log_selector = +delivery_size +sender_on_delivery +received_recipients
log_file_path = /var/log/exim4/%s-%D.log : syslog
syslog_timestamp = false
syslog_duplication = false

#spamd_address = /var/run/spamd_socket
spamd_address = 127.0.0.1 783

av_scanner = clamd:/var/run/clamav/clamd.ctl

errors_reply_to = <<MAIL_ADDR_ADMIN>>
bounce_message_file = /etc/exim4/bounce_message.txt
warn_message_file = /etc/exim4/warn_message.txt

add_environment = <; PATH=/bin:/usr/bin
keep_environment =

begin acl

acl_check_mail:
  # Rate limiting on all messages per host
  defer   ratelimit     = 50 / 5m / strict
          message       = Sending rate exceeded. Try again later.
          log_message   = Sending rate exceeded: $sender_rate/$sender_rate_period (max $sender_rate_limit)


  # Keep authenticated users under control
  deny    authenticated = *
          ratelimit     = 10 / 5m / strict / $authenticated_id


  # System-wide rate limit
  defer   message       = Sorry, too busy. Try again later.
          ratelimit     = 100 / 10s / $primary_hostname


accept

acl_check_rcpt:
  #warn    control       = dkim_disable_verify


  accept  hosts         = :
  deny    domains       = +local_domains
          message       = restricted characters in address
          local_parts   = ^[.] : ^.*[@%!/|]
  deny    domains       = !+local_domains
          message       = restricted characters in address
          local_parts   = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
  require verify        = sender
  accept  hosts         = +relay_from_hosts
  accept  authenticated = *


  defer   message       = only one recipient at a time
          condition     = ${if def:acl_VAR_RCPT_ADDR {1}{0}}


  accept  domains       = +local_domains
          control       = submission/sender_retain
          endpass
          message       = unknown user
          verify        = recipient
          set acl_VAR_RCPT_ADDR = $local_part@$domain


  accept  domains       = +relay_to_domains
          endpass
          message       = unrouteable address
          verify        = recipient
          set acl_VAR_RCPT_ADDR = $domain


  deny    message       = relay not permitted


acl_check_data:
  # put headers in all messages (no matter if spam or not)
  warn   !authenticated = *
          condition     = ${if <{$message_size}{5242880}{1}{0}}
          spam          = nobody:true
          set acl_VAR_SPAM_SCORE = $spam_score ($spam_bar)
          set acl_VAR_SPAM_REPORT = $spam_report


  warn   !authenticated = *
          condition     = ${if <{$message_size}{5242880}{1}{0}}
          spam          = nobody:true
          condition     = ${if >={$spam_score_int}{50}{1}{0}}
          set acl_VAR_SUBJECT = $header_Subject: *** Spam $spam_score


  # drop spam from mailing lists
#  discard !authenticated = *
#          condition     = ${if <{$message_size}{5242880}{1}{0}}
#          spam          = nobody:true
#          condition     = ${if >{${strlen:$header_List-Id }}{0}}
#          condition     = ${if >={${lookup mysql{MYSQL_Q_SPAMFILTER_MAIL}{$value}}}{30} {1} {0}}
#          condition     = ${if >={$spam_score_int}{${lookup mysql{MYSQL_Q_SPAMFILTER_MAIL}{$value}}} {1} {0}}
#          add_header    = X-Spam-Score: $spam_score ($spam_bar)
#          add_header    = X-Spam-Report: $spam_report
#          message       = This message scored $spam_score spam points.


  # reject spam at high scores (see database value, minimum score for reject is 10 = 1.0)
  deny   !authenticated = *
          condition     = ${if <{$message_size}{5242880}{1}{0}}
          spam          = nobody:true
          condition     = ${if >={${lookup mysql{MYSQL_Q_SPAMFILTER_MAIL}{$value}}}{10} {1} {0}}
          condition     = ${if >={$spam_score_int}{${lookup mysql{MYSQL_Q_SPAMFILTER_MAIL}{$value}}} {1} {0}}
          add_header    = X-Spam-Score: $spam_score ($spam_bar)
          add_header    = X-Spam-Report: $spam_report
          message       = This message scored $spam_score spam points.


  deny    condition = ${lookup mysql{MYSQL_Q_VIRUSFILTER_MAIL}{$value}}
          malware   = *
          message   = This message was detected as possible malware ($malware_name).


accept

acl_check_mime:
  deny    condition = ${lookup mysql{MYSQL_Q_VIRUSFILTER_MAIL}{$value}}
          condition = ${if match{${lc:$mime_filename}}{\.lnk\$|\.pif\$|\.scr\$}{true}}
          message   = This message contains unwanted attachments.
  accept


begin routers

dnslookup:
    driver = dnslookup
    domains = ! +local_domains
    transport = remote_smtp
    ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
    no_more


virtual_forward:
    driver = redirect
    forbid_file
    forbid_pipe
    data = ${lookup mysql{MYSQL_Q_FORWARD}{$value}}
    #                                             ^-- insert :unknown: here (?)


virtual_user:
    driver = redirect
    allow_fail
    allow_defer
    data = ${lookup mysql{MYSQL_Q_MAILDIR}{$value}fail}
    address_data = ${lookup mysql{MYSQL_Q_QUOTA}{$value}fail}
    file_transport = address_directory
    cannot_route_message = Unknown user


begin transports

remote_smtp:
    driver = smtp


address_pipe:
    driver = pipe
    return_output


address_file:
    driver = appendfile
    delivery_date_add
    envelope_to_add
    return_path_add


address_directory:
    driver = appendfile
    maildir_format
    maildir_tag = ,S=$message_size
    maildir_use_size_file
    user = mail
    quota = $address_data
    delivery_date_add
    envelope_to_add
    return_path_add
    quota_warn_threshold = 80%
    quota_warn_message = "\
    To: $local_part@$domain\n\
    Subject: Ihr Postfach ist fast voll!\n\
    \n\
    (...)\n"


    headers_remove = X-Virus*:X-Virus-Scanned:X-Spam-DCC:X-SpamChecker-Version:\
        X-Spam-Status:X-Spam-RBL:X-Spam-Eval\
        ${if !eq {$acl_VAR_SUBJECT}{} {::Subject}{}}\
        ${if !eq {$acl_VAR_SPAM_SCORE}{} {::X-Spam-Score}{}}\
        ${if !eq {$acl_VAR_SPAM_REPORT}{} {::X-Spam-Report}{}}


    headers_add    = ${if !eq {$acl_VAR_SUBJECT}{} {Subject: $acl_VAR_SUBJECT\n}{}}\
        ${if !eq {$acl_VAR_SPAM_SCORE}{} {X-Spam-Score: $acl_VAR_SPAM_SCORE\nX-Spam-mysite-Info: Diese Nachricht wurde vom mysite-Mailserver geprueft.\n}{}}\
        ${if !eq {$acl_VAR_SPAM_REPORT}{} {X-Spam-Report: $acl_VAR_SPAM_REPORT\n}{}}


address_reply:
    driver = autoreply


begin retry

# Domain               Error       Retries
*                      *           F,2h,15m; G,16h,1h,1.5; F,4d,6h


begin rewrite

begin authenticators

fixed_plain:
    driver = plaintext
    public_name = PLAIN
    server_prompts = :
    server_condition = ${if \
        crypteq {$3}{${lookup mysql{MYSQL_Q_AUTH_CR2}{$value}fail}} \
        {yes}{no}}
    server_set_id = $2


login:
    driver = plaintext
    public_name = LOGIN
    server_prompts = Username:: : Password::
    server_condition = ${if \
        crypteq {$2}{${lookup mysql{MYSQL_Q_AUTH_CR1}{$value}fail}} \
        {yes}{no}}
    server_set_id = $1