# $Id: exim.conf 228 2005-07-27 17:06:36Z root $ ###################################################################### # Runtime configuration file for Exim # ###################################################################### disable_ipv6 = true keep_environment = add_environment = PATH=/usr/sbin:/usr/bin:/sbin:/bin smtp_enforce_sync = false bounce_message_file = /etc/exim4/bounce_message_text smtp_accept_max = 50 smtp_accept_max_per_host = 10 smtp_accept_queue_per_connection = 50 .include /etc/exim4/exim_local.conf tls_advertise_hosts = * tls_certificate = ${if exists{/etc/ssl/letsencrypt/${tls_sni}.pem}{/etc/ssl/letsencrypt/${tls_sni}.pem}} tls_on_connect_ports = 465 daemon_smtp_ports = 25 : 465 : 587 # This is a default configuration file which will operate correctly in # uncomplicated installations. Please see the manual for a complete list # of all the runtime configuration options that can be included in a # configuration file. There are many more than are mentioned here. The # manual is in the file doc/spec.txt in the Exim distribution as a plain # ASCII file. Other formats (PostScript, Texinfo, HTML, PDF) are available # from the Exim ftp sites. The manual is also online at the Exim web sites. # This file is divided into several parts, all but the first of which are # headed by a line starting with the word "begin". Only those parts that # are required need to be present. Blank lines, and lines starting with # # are ignored. ########### IMPORTANT ########## IMPORTANT ########### IMPORTANT ########### # # # Whenever you change Exim's configuration file, you *must* remember to # # HUP the Exim daemon, because it will not pick up the new configuration # # until you do. However, any other Exim processes that are started, for # # example, a process started by an MUA in order to send a message, will # # see the new configuration as soon as it is in place. # # # # You do not need to HUP the daemon for changes in auxiliary files that # # are referenced from this file. They are read every time they are used. # # # # It is usually a good idea to test a new configuration for syntactic # # correctness before installing it (for example, by running the command # # "exim -C /config/file.new -bV"). # # # ########### IMPORTANT ########## IMPORTANT ########### IMPORTANT ########### ###################################################################### # MAIN CONFIGURATION SETTINGS # ###################################################################### # Specify your host's canonical name here. This should normally be the fully # qualified "official" name of your host. If this option is not set, the # uname() function is called to obtain the name. In many cases this does # the right thing and you need not set anything explicitly. queue_only_load = 3 log_selector = +all # The next three settings create two lists of domains and one list of hosts. # These lists are referred to later in this configuration using the syntax # +local_domains, +relay_to_domains, and +relay_from_hosts, respectively. They # are all colon-separated lists: domainlist local_domains = ${lookup mysql{SELECT domain FROM vm_domains WHERE domain='$domain' AND status = '1'}} domainlist relay_to_domains = /etc/exim4/relay_domains hostlist relay_from_hosts = 127.0.0.1 hostlist skip_greylisting_hosts = /etc/exim4/skip_greylisting_hosts # overide default sender checks untrusted_set_sender = * local_from_check = false local_sender_retain = true # trusted users, needed by spamcheck trusted_users = mail # local whitelist check macro: WHITELISTED = ${lookup mysql{\ SELECT prefid FROM sa_userpref \ WHERE (username = '${quote_mysql:$local_part@$domain}' \ OR username = '\$GLOBAL' \ OR username = CONCAT('%','${quote_mysql:$domain}')) \ AND preference = 'whitelist_from' \ AND (value = '${quote_mysql:$sender_address}' \ OR value = CONCAT('\*@','${quote_mysql:$sender_address_domain}')) \ }{true}{false}} # greylist options # these need to be valid as xxx in mysql's DATE_ADD(..,INTERVAL xxx) # not valid, for example, are plurals: "2 HOUR" instead of "2 HOURS" GREYLIST_INITIAL_DELAY = 4 MINUTE GREYLIST_INITIAL_LIFETIME = 12 HOUR GREYLIST_WHITE_LIFETIME = 36 DAY GREYLIST_BOUNCE_LIFETIME = 0 HOUR # you can change the table names GREYLIST_TABLE=vm_greylisting GREYLIST_RESENDERS_TABLE=vm_greylisting_resenders .ifdef GREYLIST_ENABLED # database macros GREYLIST_TEST = SELECT CASE \ WHEN now() > block_expires THEN "accepted" \ ELSE "deferred" \ END AS result, id \ FROM GREYLIST_TABLE \ WHERE (now() < record_expires) \ AND (sender = '${quote_mysql:$sender_address}' \ OR (type='MANUAL' \ AND ( sender IS NULL \ OR sender = '${quote_mysql:*@$sender_address_domain}' \ ) \ ) \ ) \ AND (recipient = '${quote_mysql:$local_part@$domain}' \ OR (type = 'MANUAL' \ AND ( recipient IS NULL \ OR recipient = '${quote_mysql:$local_part@*}' \ OR recipient = '${quote_mysql:*@$domain}' \ ) \ ) \ ) \ AND (relay_hostname = '${quote_mysql:$acl_m_sender}' \ OR (type='MANUAL' \ AND relay_hostname IS NULL) \ ) \ ORDER BY result DESC LIMIT 1 GREYLIST_ADD = INSERT INTO GREYLIST_TABLE \ (relay_hostname, relay_ip, sender, recipient, block_expires, \ record_expires, create_time, type) \ VALUES ( '${quote_mysql:$acl_m_sender}', \ '${quote_mysql:$sender_host_address}', \ '${quote_mysql:$sender_address}', \ '${quote_mysql:$local_part@$domain}', \ DATE_ADD(now(), INTERVAL GREYLIST_INITIAL_DELAY), \ DATE_ADD(now(), INTERVAL GREYLIST_INITIAL_LIFETIME), \ now(), \ 'AUTO' \ ) GREYLIST_DEFER_HIT = UPDATE GREYLIST_TABLE \ SET blockcount=blockcount+1 \ WHERE id = $acl_m9 GREYLIST_OK_COUNT = UPDATE GREYLIST_TABLE \ SET passcount=passcount+1 \ WHERE id = $acl_m9 GREYLIST_OK_NEWTIME = UPDATE GREYLIST_TABLE \ SET record_expires = DATE_ADD(now(), INTERVAL GREYLIST_WHITE_LIFETIME) \ WHERE id = $acl_m9 AND type='AUTO' GREYLIST_OK_BOUNCE = UPDATE GREYLIST_TABLE \ SET record_expires = DATE_ADD(now(), INTERVAL GREYLIST_BOUNCE_LIFETIME) \ WHERE id = $acl_m9 AND type='AUTO' GREYLIST_RESENDERS_UPDATE = REPLACE INTO GREYLIST_RESENDERS_TABLE SET hostname = '${quote_mysql:$acl_m_sender}' .endif # No deliveries will ever be run under the uids of these users (a colon- # separated list). An attempt to do so causes a panic error to be logged, and # the delivery to be deferred. This is a paranoic safety catch. Note that the # default setting means you cannot deliver mail addressed to root as if it # were a normal user. This isn't usually a problem, as most sites have an alias # for root that redirects such mail to a human administrator. never_users = root # The setting below causes Exim to do a reverse DNS lookup on all incoming # IP calls, in order to get the true host name. If you feel this is too # expensive, you can specify the networks for which a lookup is done, or # remove the setting entirely. host_lookup = * # The settings below, which are actually the same as the defaults in the # code, cause Exim to make RFC 1413 (ident) callbacks for all incoming SMTP # calls. You can limit the hosts to which these calls are made, and/or change # the timeout that is used. If you set the timeout to zero, all RFC 1413 calls # are disabled. RFC 1413 calls are cheap and can provide useful information # for tracing problem messages, but some hosts and firewalls have problems # with them. This can result in a timeout instead of an immediate refused # connection, leading to delays on starting up an SMTP session. rfc1413_hosts = * rfc1413_query_timeout = 0s # This option unfreezes frozen bounce messages after two days, tries # once more to deliver them, and ignores any delivery failures. ignore_bounce_errors_after = 0s # This option cancels (removes) frozen messages that are older than a day. timeout_frozen_after = 1d # Treat DNS failures (SERVFAIL) as lookup failures. # This is so that we can later reject sender addresses # within non-existing domains, or domains for which no # nameserver exists. dns_again_means_nonexist = !+local_domains # Enable HELO verification in ACLs for all hosts helo_try_verify_hosts = * # Advertise ESMTP "PIPELINING" to all hosts pipelining_advertise_hosts = * acl_smtp_connect = acl_connect acl_smtp_helo = acl_helo acl_smtp_mail = acl_mail_from acl_smtp_dkim = acl_check_dkim acl_smtp_rcpt = acl_rcpt_to acl_smtp_data = acl_data # set the av scanner to clamav ##av_scanner = clamd:/run/clamav/clamd.ctl begin acl # this acl returns either deny or accept # since we use it inside a defer with acl = greylist_acl, # accepting here makes the condition TRUE thus deferring, # denying here makes the condition FALSE thus not deferring .ifdef GREYLIST_ENABLED greylist_acl: # For regular deliveries, check greylist. # check greylist tuple, returning "accepted", "deferred" or "unknown" # in acl_m8, and the record id in acl_m9 warn set acl_m8 = ${lookup mysql{GREYLIST_TEST}{$value}{result=unknown}} # here acl_m8 = "result=x id=y" set acl_m9 = ${extract{id}{$acl_m8}{$value}{-1}} # now acl_m9 contains the record id (or -1) set acl_m8 = ${extract{result}{$acl_m8}{$value}{unknown}} # now acl_m8 contains unknown/deferred/accepted # check if we know a certain triple, add and defer message if not accept # if above check returned unknown (no record yet) condition = ${if eq{$acl_m8}{unknown}{1}} # then also add a record condition = ${lookup mysql{GREYLIST_ADD}{yes}{no}} # check if the triple is still blocked accept # if above check returned deferred then defer condition = ${if eq{$acl_m8}{deferred}{1}} # and note it down condition = ${lookup mysql{GREYLIST_DEFER_HIT}{yes}{yes}} # use a warn verb to count records that were hit warn condition = ${lookup mysql{GREYLIST_OK_COUNT}} # use a warn verb to set a new expire time on automatic records, # but only if the mail was not a bounce, otherwise set to now(). warn !senders = : postmaster@* condition = ${lookup mysql{GREYLIST_OK_NEWTIME}} warn senders = : postmaster@* condition = ${lookup mysql{GREYLIST_OK_BOUNCE}} deny add_header = X-DNS-Greylist: mail from $sender_address to $local_part@$domain accepted by greylisting condition = ${lookup mysql{GREYLIST_RESENDERS_UPDATE}} .endif # This access control list is used at the start of an incoming # connection. The tests are run in order until the connection # is either accepted or denied. acl_connect: # In this pass, we do not perform any checks here. accept # This access control list is used for the HELO or EHLO command in # an incoming SMTP transaction. The tests are run in order until the # greeting is either accepted or denied. acl_helo: # Early rejection of brute-force bots under the name 'ylmf-pc' deny condition = ${if eq{$sender_helo_name}{ylmf-pc}{yes}{no}} # In this pass, we do not perform any checks here. accept # This access control list is used for the MAIL FROM: command in an # incoming SMTP transaction. The tests are run in order until the # sender address is either accepted or denied. # acl_mail_from: # Accept the command. accept # This access control list is used for every RCPT command in an # incoming SMTP message. The tests are run in order until the # recipient address is either accepted or denied. acl_check_dkim: ###################################################################### # DomainKeys ###################################################################### # DKIM fail accept dkim_status = fail logwrite = DKIM test failed: $dkim_verify_reason add_header = X-DKIM-Status: fail: $dkim_verify_reason add_header = X-Junk-Flag: YES # DKIM invalid accept dkim_status = invalid logwrite = DKIM test invalid: $dkim_verify_reason add_header = X-DKIM-Status: invalid: $dkim_verify_reason add_header = X-Junk-Flag: YES # DKIM none accept dkim_status = none logwrite = DKIM test none add_header = X-DKIM-Status: none # DKIM pass accept dkim_status = pass logwrite = DKIM test passed add_header = X-DKIM-Status: passed: (address=$sender_address domain=$dkim_cur_signer), signature is good. # Accept the message. accept acl_rcpt_to: # Accept mail received over local SMTP (i.e. not over TCP/IP). # We do this by testing for an empty sending host field. # Also accept mails received from hosts for which we relay mail. # # Recipient verification is omitted here, because in many # cases the clients are dumb MUAs that don't cope well with # SMTP error responses. # accept hosts = : +relay_from_hosts # Accept if the message arrived over an authenticated connection, # from any host. Again, these messages are usually from MUAs, so # recipient verification is omitted. # # Get rate limit for user and log current rate. # Hourly rate limit is extracted from db, multiplied by 10 to get daily rate limit. # The idea being that the hourly rate limit should be a maximum, peak rate, not a sustained rate. # The default ratelimit of 100000 is meant to be so high as to never be reached (no limit). # A second default of 10 is set for lookup failures. This shouldn't happen unless there is a misconfiguration somewhere or a database issue. # The primary purpose of rate limiting is fighting spam, either by an abusive user or a # compromised account, not to restrict legitimate users. Set your defaults & limits accordignly. warn authenticated = * set acl_m_ratelimit_hourly = ${lookup mysql{SELECT IFNULL(vm_mboxes.ratelimit, 100000) FROM vm_domains, vm_mboxes WHERE vm_domains.domain="$sender_address_domain" AND vm_mboxes.mbox="$sender_address_local_part" AND vm_domains.id = vm_mboxes.domain_id}{$value}{10}} set acl_m_ratelimit_daily = ${eval: (10 * $acl_m_ratelimit_hourly) } ratelimit = 0 / 1h / per_mail / strict / $authenticated_id log_message = Sender rate is $sender_rate/$sender_rate_period for $authenticated_id # enforce hourly rate limit deny authenticated = * ratelimit = $acl_m_ratelimit_hourly / 1h / per_mail / strict / $authenticated_id message = Rate Limit of $acl_m_ratelimit_hourly per hour exceeded. Try again later. log_message = $authenticated_id exceeded rate limit of $acl_m_ratelimit_hourly per hour # enforce daily rate limit deny authenticated = * ratelimit = $acl_m_ratelimit_daily / 1d / per_mail / strict / $authenticated_id message = Rate Limit of $acl_m_ratelimit_daily per day exceeded. Try again later. log_message = $authenticated_id exceeded rate limit of $acl_m_ratelimit_daily per day # authenticated user did not exceed rate limits, accept message now accept authenticated = * ###################################################################### # Hello checks ###################################################################### # If the remote host greets with an IP address, then reject the mail. # deny message = Message was delivered by ratware log_message = remote host used IP address in HELO/EHLO greeting condition = ${if isip {$sender_helo_name}{true}{false}} # Likewise if the peer greets with one of our own names # deny message = Message was delivered by ratware log_message = remote host used our name in HELO/EHLO greeting. condition = ${if match_domain{$sender_helo_name}\ {$primary_hostname:+local_domains}\ {true}{false}} deny message = Message was delivered by ratware log_message = remote host did not present HELO/EHLO greeting. condition = ${if def:sender_helo_name {false}{true}} # If HELO verification fails, we add a X-HELO-Warning: header in # the message. # warn !verify = helo message = X-HELO-Warning: Remote host $sender_host_address \ ${if def:sender_host_name {($sender_host_name) }}\ incorrectly presented itself as $sender_helo_name log_message = remote host presented unverifiable HELO/EHLO greeting. # if "!verify = helo" & $send_host_name = '' reject??? ###################################################################### # Sender Address Checks ###################################################################### # If we cannot verify the sender address, deny the message. # # You may choose to remove the "callout" option. In particular, # if you are sending outgoing mail through a smarthost, it will not # give any useful information. # # Details regarding the failed callout verification attempt are # included in the 550 response; to omit these, change # "sender/callout" to "sender/callout,no_details". # # deny # message = <$sender_address> does not appear to be a \ # valid sender address. # !verify = sender/callout ###################################################################### # Recipent Address Checks ###################################################################### # Ensure only valid SRS prefixed bounce message get accepted deny senders = : domains = +local_domains local_parts = ${if match {$local_part} {(?i)\N^SRS[01][=+-]\N} {$local_part}} control = caseful_local_part condition = ${if match{${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part@$domain}{5s}{\n}}}{^ERROR: .* Invalid hash at .*}} message = Invalid reverse path (SRS check failed on $local_part@$domain). warn senders = : domains = +local_domains local_parts = ${if match {$local_part} {\N^srs[01][=+-]\N} {$local_part}} control = caseful_local_part condition = ${if match{${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part@$domain}{5s}{ }}}{^SRS: Case insensitive hash match detected. Someone smashed case in the local-part. .*}} log_message = SRS hash smashed on the way for $local_part@$domain by case insensitive MTA. # this is for debugging only. can be safely removed any time warn senders = : domains = +local_domains local_parts = ${if match {$local_part} {(?i)\N^SRS[01][=+-]\N} {$local_part}} control = caseful_local_part condition = ${if !match{${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part@$domain}{5s}{\n}}}{^ERROR: .* Invalid hash at .*}} log_message = Incoming SRS bounce to $local_part@$domain # Deny if the local part contains @ or % or / or | or !. These are # rarely found in genuine local parts, but are often tried by people # looking to circumvent relaying restrictions. # # Also deny if the local part starts with a dot. Empty components # aren't strictly legal in RFC 2822, but Exim allows them because # this is common. However, actually starting with a dot may cause # trouble if the local part is used as a file name (e.g. for a # mailing list). # deny local_parts = ^.*[@%!/|] : ^\\. # Drop the connection if the envelope sender is empty, but there is # more than one recipient address. Legitimate DSNs are never sent # to more than one address. # drop message = Legitimate bounces are never sent to more than one recipient. log_message = Legitimate bounces are never sent to more than one recipient (count: $recipients_count). senders = : postmaster@* condition = $recipients_count # Reject the recipient address if it is not in a domain for # which we are handling mail. # deny message = relay not permitted !domains = +local_domains : +relay_to_domains # Reject the recipient if it is not a valid mailbox. # If the mailbox is not on our system (e.g. if we are a # backup MX for the recipient domain), then perform a # callout verification; but if the destination server is # not responding, accept the recipient anyway. # deny message = unknown user domains = +local_domains !domains = +relay_to_domains !verify = recipient/callout=no_cache,10s,defer_ok # skip any further checks if the address is whitelisted accept condition = WHITELISTED logwrite = From: $sender_address To: $local_part@$domain is whitelisted in sa_userpref add_header = X-Whitelist-Flag: YES # skip any further checks if the address is already marked Junk (by DKIM) accept condition = ${if eq {$header_X-Junk-Flag:}{YES}} logwrite = Skipping SPF/DNSBL/Greylist checks as message already failed DKIM ###################################################################### # DNS checks ###################################################################### # # The results of these checks are cached, so multiple recipients # does not translate into multiple DNS lookups. # # check whitelists # a match will: # add X-DNS-Whitelist header # skip the rest of the checks (DNS blacklist/greylist, SPF) ## as of Aug 2019 swl.spamhaus.org appears to be offline pending redesign ## list.dnswl.org does not work with large nameresolvers (over 100k queries / 24 hours) ## accept ## domains = +local_domains ## dnslists = swl.spamhaus.org : list.dnswl.org&0.0.0.2 ## logwrite = $sender_host_address is whitelisted in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}}, adding X-DNS-Whitelist header ## add_header = X-DNS-Whitelist: $sender_host_address is listed in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}} # add SPF header accept condition = ${run{/usr/bin/spfquery --scope mfrom \ --identity ${quote:$sender_address} \ --ip-address ${quote:$sender_host_address}} \ {no}{${if eq {$runrc}{1}{yes}{no}}}} add_header = Received-SPF: fail add_header = X-Junk-Flag: YES ## log_message = SPF check failed. warn condition = ${if eq {$runrc}{0}{yes}{no}} add_header = Received-SPF: pass ## log_message = SPF check: pass warn condition = ${if eq {$runrc}{2}{yes}{no}} add_header = Received-SPF: softfail ## log_message = SPF check: softfail warn condition = ${if eq {$runrc}{3}{yes}{no}} add_header = Received-SPF: neutral ## log_message = SPF check: neutral warn condition = ${if eq {$runrc}{4}{yes}{no}} add_header = Received-SPF: permerror ## log_message = SPF check: permerror warn condition = ${if eq {$runrc}{5}{yes}{no}} add_header = Received-SPF: temperror log_message = Temporary DNS error while checking SPF record. warn condition = ${if eq {$runrc}{6}{yes}{no}} add_header = Received-SPF: none ## log_message = SPF check: none warn condition = ${if >{$runrc}{6}{yes}{no}} log_message = Unexpected error in SPF check. spfquery returned $runrc # check DNSBL(s) and if found add header for filtering to Junk accept !condition = ${if eq {$header_X-Whitelist-Flag:}{YES}} dnslists = zen.spamhaus.org logwrite = Warning: $sender_host_address is listed in DNSBL $dnslist_domain ${if def:dnslist_text {($dnslist_text)}} add_header = X-DNS-Blacklist: $sender_host_address is listed in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}} add_header = X-Junk-Flag: YES # will never get here if X-Whitelist-Flag or X-Junk-Flag (gets set with X-DNS-Blacklist) or X-DNS-Whitelist headers are set above .ifdef GREYLIST_ENABLED # if $sender_host_name is set use that. # run command strips of leftmost subdomain if this is a third or higher level domain warn condition = ${if def:sender_host_name} set acl_m_sender = ${run{/bin/bash /etc/exim4/return-resender.sh $sender_host_name}{$value}{$sender_host_name}} # if $sender_host_name is not set use $sender_helo_name # if sender_helo_name not set email was already rejected (deny) earlier in this acl warn condition = ${if !def:sender_host_name} set acl_m_sender = $sender_helo_name # bypass greylisting if the sending host is a known resender accept condition = ${lookup mysql{SELECT id FROM GREYLIST_RESENDERS_TABLE WHERE hostname = '${quote_mysql:$acl_m_sender}'}{yes}{no}} condition = ${lookup mysql{UPDATE GREYLIST_RESENDERS_TABLE SET count=count+1, timestamp = NOW() WHERE hostname = '${quote_mysql:$acl_m_sender}'}{yes}{yes}} add_header = X-DNS-Greylist: known resender logwrite = skipping greylisting for $acl_m_sender due to match in GREYLIST_RESENDERS_TABLE accept hosts = +skip_greylisting_hosts log_message = skipping greylisting due to match in skip_greylisting_hosts defer !senders = : postmaster@* !hosts = +skip_greylisting_hosts acl = greylist_acl message = greylisted - try again later .endif # Otherwise, the recipient address is OK. # accept # This access control list is used for message data received via # SMTP. The tests are run in order until the recipient address # is either accepted or denied. acl_data: # Add Message-ID if missing in messages received from our own hosts. warn condition = ${if !def:h_Message-ID: {1}} hosts = : +relay_from_hosts message = Message-ID: # add domain keys status header # warn # message = DomainKey-Status: $dk_status # !condition = ${if eq{$dk_status}{}{1}{0}} # Accept mail received over local SMTP (i.e. not over TCP/IP). # We do this by testing for an empty sending host field. # Also accept mails received from hosts for which we relay mail. # accept hosts = : +relay_from_hosts # Accept if the message arrived over an authenticated connection, from # any host. # accept authenticated = * # Enforce a message-size limit # #deny # message = Message size $message_size is larger than limit of \ # MESSAGE_SIZE_LIMIT # condition = ${if >{$message_size}{MESSAGE_SIZE_LIMIT}{true}{false}} # skip any further checks if the address is already marked Junk accept condition = ${if eq {$header_X-Junk-Flag:}{YES}} logwrite = Skipping header_syntax and header_sender checks as message already marked Junk # Deny unless the address list header is syntactically correct. # Note that some specialized MTAs, such as certain mailing list # servers, do not automatically generate a Message-ID for bounces. # Thus, we add the check for a non-empty sender. # (email feedback reports from aol fail this check) #deny accept message = X-RFC2822-Error: Your message does not conform to RFC2822 standard log_message = message header failed RFC2822 syntax check !hosts = +relay_from_hosts !senders = : postmaster@* !verify = header_syntax add_header = X-RFC2822-Error: Your message does not conform to RFC2822 standard add_header = X-Junk-Flag: YES # Warn unless there is a verifiable sender address in at least # one of the "Sender:", "Reply-To:", or "From:" header lines. warn !verify = header_sender log_message = No valid sender in message header add_header = X-Sender-Verify-Failed: No valid sender in message header # Deny if the message contains a virus. Before enabling this check, you # must install a virus scanner and set the av_scanner option above. ## accept ## condition = ${if ! def:header_X-Junk-Flag: } ## malware = */defer_ok ## log_message = This message contains a virus ($malware_name). ## add_header = X-Virus-Warning: This message contains a virus ($malware_name). ## add_header = X-Junk-Flag: YES # Accept the message. # accept ###################################################################### # ROUTERS CONFIGURATION # # Specifies how addresses are handled # ###################################################################### # THE ORDER IN WHICH THE ROUTERS ARE DEFINED IS IMPORTANT! # # An address is passed to each router in turn until it is accepted. # ###################################################################### begin routers autowhitelist_filter: driver = redirect domains = ! +local_domains ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 condition = ${lookup mysql{SELECT vm_mboxes.id FROM vm_domains, vm_mboxes WHERE vm_domains.domain = "$sender_address_domain" AND vm_mboxes.mbox = "$sender_address_local_part" AND vm_domains.id = vm_mboxes.domain_id}} check_local_user = false user = Debian-exim file = /etc/exim4/autowhitelist.filter no_verify unseen allow_filter = true srs_bounce: senders = : driver = redirect domains = +local_domains allow_fail allow_defer local_part_prefix = srs0+ : srs0- : srs0= : srs1+ : srs1- : srs1= caseful_local_part address_data = ${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part_prefix$local_part@$domain}{5s}{ }{:defer: SRS daemon failure}} data = ${sg {$address_data} {^SRS: Case insensitive hash match detected. Someone smashed case in the local-part\. .* ([^ ]+)@([^ ]+)\$} {\N$1@$2\N} } headers_add = X-SRS: Decoded valid SRS return address to ${quote_local_part:${local_part:$address_data}}@${domain:$address_data} by $primary_hostname srs_forward: driver = redirect senders = ! : ! *@+local_domains domains = ! +local_domains : ! +relay_to_domains condition = ${lookup mysql{SELECT vm_domains.id FROM vm_domains WHERE vm_domains.domain='${original_domain}' AND vm_domains.status = '1'}} address_data = ${readsocket{/run/srsd/srsd.sock}\ {FORWARD $sender_address_local_part@$sender_address_domain $original_domain\n}\ {5s}{\n}{:defer: SRS daemon failure}} errors_to = ${quote_local_part:${local_part:$address_data}}@${domain:$address_data} data = ${quote_local_part:$local_part}@$domain headers_add = X-SRS-Forward: from $sender_address to $original_local_part@$original_domain forwarded to $local_part@$domain by $primary_hostname repeat_use = false allow_defer no_verify # This router routes addresses that are not in local domains by doing a DNS # lookup on the domain name. Any domain that resolves to 0.0.0.0 or to a # loopback interface address (127.0.0.0/8) is treated as if it had no DNS # entry. Note that 0.0.0.0 is the same as 0.0.0.0/32, which is commonly treated # as the local host inside the network stack. It is not 0.0.0.0/0, the default # route. If the DNS lookup fails, no further routers are tried because of # the no_more setting, and consequently the address is unrouteable. dnslookup: driver = dnslookup self = pass transport = remote_smtp ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 no_more # The remaining routers handle addresses in the local domain(s). virtual_alias: driver = redirect domains = +local_domains local_part_suffix = +* local_part_suffix_optional = true condition = ${if !eq {$received_protocol}{spam-scanned}} data = ${lookup mysql{SELECT CONCAT(vm_mboxes.mbox,'@${domain}') FROM vm_aliases, vm_mboxes, vm_domains WHERE vm_aliases.alias='${local_part}' AND vm_aliases.mbox_id = vm_mboxes.id AND vm_mboxes.domain_id = vm_domains.id AND vm_domains.domain='${domain}' AND vm_domains.status = '1' AND vm_mboxes.status = '1'}} junk_filter: driver = accept domains = +local_domains local_part_suffix = +* local_part_suffix_optional = true condition = ${if and { \ { !eq {$received_protocol}{spam-scanned}} \ { eq {$header_X-Junk-Flag:}{YES}} \ { !eq {$header_X-Whitelist-Flag:}{YES}} \ } {yes}{no}} condition = ${lookup mysql{SELECT vm_mboxes.id FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_mboxes.filter > '0'}} transport = junk_delivery spam_filter: driver = accept domains = +local_domains local_part_suffix = +* local_part_suffix_optional = true condition = ${if and { \ { eq {$received_protocol}{spam-scanned}} \ { eq {$header_X-Spam-Flag:}{YES}} \ { !eq {$header_X-Whitelist-Flag:}{YES}} \ } {yes}{no}} condition = ${lookup mysql{SELECT vm_mboxes.id FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_mboxes.filter = '2'}} transport = junk_delivery virtual_vacation: driver = accept domains = +local_domains # do not reply to errors or lists or spam-scanned messages, require vacation message in db condition = ${if and { \ { !match {$h_precedence:} {(?i)junk|bulk|list}} \ { !eq {$received_protocol}{spam-scanned}} \ { !eq {$sender_address} {}} \ { eq {${lookup mysql{SELECT vm_autoresponders.mode FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_autoresponders.status = '1'}{$value}fail}}{Vacation}} \ } {yes} {no}} # add options for start & end date fields no_expn # do not reply to errors and bounces or lists senders = " ! ^.*-request@.*:\ ! ^owner-.*@.*:\ ! ^postmaster@.*:\ ! ^listmaster@.*:\ ! ^mailer-daemon@.*\ ! ^root@.*" transport = vacation_transport unseen no_verify virtual_autoresponder: driver = accept domains = +local_domains # currently configured to *not* autorespond to + aliases #local_part_suffix = +* #local_part_suffix_optional = true # do not reply to errors or lists or spam-scanned messages, require autoresponder message in db condition = ${if and { \ { !match {$h_precedence:} {(?i)junk|bulk|list}} \ { !eq {$received_protocol}{spam-scanned}} \ { !eq {$sender_address} {}} \ { eq {${lookup mysql{SELECT vm_autoresponders.mode FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_autoresponders.status = '1'}{$value}fail}}{Autoresponder} } \ } {yes} {no}} # add options for start & end date fields no_expn # do not reply to errors and bounces or lists senders = " ! ^.*-request@.*:\ ! ^owner-.*@.*:\ ! ^postmaster@.*:\ ! ^listmaster@.*:\ ! ^mailer-daemon@.*\ ! ^root@.*" transport = autoresponder_transport unseen no_verify virtual_forward_and_drop: driver = redirect domains = +local_domains condition = ${if !eq {$received_protocol}{spam-scanned}} local_part_suffix = +* local_part_suffix_optional = true data = ${lookup mysql{SELECT vm_forwards.forward_to FROM vm_domains, vm_mboxes, vm_forwards WHERE vm_domains.domain='${domain}' AND vm_domains.id = vm_mboxes.domain_id AND vm_mboxes.mbox='${local_part}' AND vm_mboxes.id=vm_forwards.mbox_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_forwards.save_local='0'}} virtual_forward_and_keep: driver = redirect domains = +local_domains condition = ${if !eq {$received_protocol}{spam-scanned}} local_part_suffix = +* local_part_suffix_optional = true data = ${lookup mysql{SELECT CONCAT('${local_part}@${domain}\n', vm_forwards.forward_to) FROM vm_domains, vm_mboxes, vm_forwards WHERE vm_domains.domain='${domain}' AND vm_domains.id = vm_mboxes.domain_id AND vm_mboxes.mbox='${local_part}' AND vm_mboxes.id=vm_forwards.mbox_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_forwards.save_local='1'}} spamcheck_router: driver = accept domains = +local_domains local_part_suffix = +* local_part_suffix_optional = true condition = ${if and { \ { !eq {$received_protocol}{spam-scanned}} \ { !eq {$sender_address_domain}{$domain}} \ { < {$message_size}{512k}} \ { !eq {$header_X-Junk-Flag:}{YES}} \ { !eq {$header_X-Whitelist-Flag:}{YES}} \ { eq {${lookup mysql{SELECT vm_mboxes.status FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1' AND vm_mboxes.status = '1'}{$value}fail}}{1} } \ } {yes} {no}} # Check for other headers too? Blacklist, SPF, DKIM failers go directly to Spam folder without spam scan??? - actually they should go to spam folder before this router is hit? headers_remove = X-Spam-Checker-Version:X-Spam-Flag:X-Spam-Level:X-Spam-Status:X-Spam-Score:X-Spam-Report transport = spamcheck user_filter: driver = redirect domains = +local_domains local_part_suffix = +* local_part_suffix_optional = true data = ${lookup mysql{SELECT vm_filters.filter FROM vm_domains, vm_mboxes, vm_filters WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_mboxes.id = vm_filters.mbox_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' }} user = vmail no_verify no_expn check_ancestor allow_filter file_transport = address_file pipe_transport = address_pipe reply_transport = address_reply directory_transport = user_filter_maildir_delivery allow_fail lmtp_localuser: driver = accept domains = +local_domains local_part_suffix = +* local_part_suffix_optional = true condition = ${lookup mysql{SELECT vm_mboxes.id FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1' AND vm_mboxes.status = '1'}} # add checks to query for domain & mbox being active transport = dovecot_lmtp cannot_route_message = Unknown user # Support for catchall aliases. It is *not* recommended to use this. virtual_alias_catchall: driver = redirect domains = +local_domains condition = ${if !eq {$received_protocol}{spam-scanned}} data = ${lookup mysql{SELECT CONCAT(vm_mboxes.mbox,'@${domain}') FROM vm_aliases, vm_mboxes, vm_domains WHERE vm_aliases.alias='catchall' AND vm_aliases.mbox_id = vm_mboxes.id AND vm_mboxes.domain_id = vm_domains.id AND vm_domains.domain='${domain}' AND vm_domains.status='1' AND vm_mboxes.status='1'}} # This router handles aliasing using a linearly searched alias file with the # name SYSTEM_ALIASES_FILE. When this configuration is installed automatically, # the name gets inserted into this file from whatever is set in Exim's # build-time configuration. The default path is the traditional /etc/aliases. # If you install this configuration by hand, you need to specify the correct # path in the "data" setting below. # ##### NB You must ensure that the alias file exists. It used to be the case ##### NB that every Unix had that file, because it was the Sendmail default. ##### NB These days, there are systems that don't have it. Your aliases ##### NB file should at least contain an alias for "postmaster". # # If any of your aliases expand to pipes or files, you will need to set # up a user and a group for these deliveries to run under. You can do # this by uncommenting the "user" option below (changing the user name # as appropriate) and adding a "group" option if necessary. Alternatively, you # can specify "user" on the transports that are used. Note that the transports # listed below are the same as are used for .forward files; you might want # to set up different ones for pipe and file deliveries from aliases. system_aliases: driver = redirect domains = +local_domains allow_fail allow_defer data = ${lookup{$local_part}lsearch{/etc/aliases}} # user = exim # file_transport = address_file # pipe_transport = address_pipe # This router handles forwarding using traditional .forward files in users' # home directories. If you want it also to allow mail filtering when a forward # file starts with the string "# Exim filter", uncomment the "allow_filter" # option. # The no_verify setting means that this router is skipped when Exim is # verifying addresses. Similarly, no_expn means that this router is skipped if # Exim is processing an EXPN command. # The check_ancestor option means that if the forward file generates an # address that is an ancestor of the current one, the current one gets # passed on instead. This covers the case where A is aliased to B and B # has a .forward file pointing to A. # The three transports specified at the end are those that are used when # forwarding generates a direct delivery to a file, or to a pipe, or sets # up an auto-reply, respectively. #userforward: # driver = redirect # check_local_user # file = $home/.forward # no_verify # no_expn # check_ancestor ## allow_filter # file_transport = address_file # pipe_transport = address_pipe # reply_transport = address_reply # This router matches local user mailboxes. #localuser: # driver = accept # check_local_user # transport = local_delivery ###################################################################### # TRANSPORTS CONFIGURATION # ###################################################################### # ORDER DOES NOT MATTER # # Only one appropriate transport is called for each delivery. # ###################################################################### # A transport is used only when referenced from a router that successfully # handles an address. begin transports # This transport is used for delivering messages over SMTP connections. remote_smtp: driver = smtp dkim_domain = ${lc:${domain:$h_from:}} dkim_canon = relaxed dkim_selector = ${if exists{/etc/ssl/dkim/${dkim_domain}.selector}{${readfile{/etc/ssl/dkim/${dkim_domain}.selector}{}}}{0}} dkim_private_key = ${if exists{/etc/ssl/dkim/${dkim_domain}.pem}{/etc/ssl/dkim/${dkim_domain}.pem}{0}} # This transport is used for local delivery to user mailboxes in traditional # BSD mailbox format. By default it will be run under the uid and gid of the # local user, and requires the sticky bit to be set on the /var/mail directory. # Some systems use the alternative approach of running mail deliveries under a # particular group instead of using the sticky bit. The commented options below # show how this can be done. local_delivery: driver = appendfile file = /var/mail/$local_part delivery_date_add envelope_to_add return_path_add group = vmail mode = 0660 dovecot_lmtp: driver = lmtp socket = /run/dovecot/lmtp #return_path_add #maximum number of deliveries per batch, default 1 #batch_max = 200 #allow suffixes/prefixes (default unset) #rcpt_include_affixes #local_delivery: # driver = appendfile ## file = /var/vmail/$local_part # maildir_format = true # directory = /home/$local_part/Maildir # create_directory # directory_mode = 770 # delivery_date_add # envelope_to_add # return_path_add # user = $local_part # group = $local_part # mode = 0660 ## for vacation mail vacation_transport: driver = autoreply log = /var/vmail/${domain}/${local_part}/vacation_log once = /var/vmail/${domain}/${local_part}/vacation_once_db return_path = ${local_part}@${domain} to = ${sender_address} from = ${local_part}@${domain} # subject = ${lookup mysql{SELECT subject FROM vm_autoresponder WHERE local_part='${local_part}' AND domain='${domain}'}{$value}{"Auto Reply"}} # text = ${lookup mysql{SELECT message FROM vm_autoresponder WHERE local_part='${local_part}' AND domain='${domain}'}{$value}fail} subject = ${lookup mysql{SELECT vm_autoresponders.subject FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id}{$value}{"Auto Reply"}} text = ${lookup mysql{SELECT vm_autoresponders.body FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id}{$value}fail} user = vmail ## for autoresponder autoresponder_transport: driver = autoreply log = /var/vmail/${domain}/${local_part}/autoresponder_log return_path = ${local_part}@${domain} to = ${sender_address} from = ${local_part}@${domain} subject = ${lookup mysql{SELECT vm_autoresponders.subject FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id}{$value}{"Auto Reply"}} text = ${lookup mysql{SELECT vm_autoresponders.body FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id}{$value}fail} user = vmail maildir_delivery: driver = appendfile maildir_format directory = /var/vmail/${domain}/${local_part}/Maildir create_directory directory_mode = 750 user = vmail user_filter_maildir_delivery: driver = appendfile maildir_format user = vmail junk_delivery: driver = appendfile maildir_format directory = /var/vmail/${domain}/${local_part}/Maildir/.Junk create_directory directory_mode = 750 user = vmail # SpamAssassin spamcheck: driver = pipe command = /usr/sbin/exim -oMr spam-scanned -bS use_bsmtp = true transport_filter = /usr/bin/spamc -f -u $local_part@$domain home_directory = "/tmp" current_directory = "/tmp" # must use a privileged user to set $received_protocol on the way back in! user = mail group = mail log_output = true return_fail_output = true return_path_add = false message_prefix = message_suffix = # clamav #clamav_scan # driver = pipe # command = /usr/bin/clamdscan # user = clamav # user_bsmtp = true # This transport is used for handling pipe deliveries generated by alias or # .forward files. If the pipe generates any standard output, it is returned # to the sender of the message as a delivery error. Set return_fail_output # instead of return_output if you want this to happen only when the pipe fails # to complete normally. You can set different transports for aliases and # forwards if you want to - see the references to address_pipe in the routers # section above. address_pipe: driver = pipe return_output # This transport is used for handling deliveries directly to files that are # generated by aliasing or forwarding. address_file: driver = appendfile delivery_date_add envelope_to_add return_path_add # This transport is used for handling autoreplies generated by the filtering # option of the userforward router. address_reply: driver = autoreply ###################################################################### # RETRY CONFIGURATION # ###################################################################### begin retry # This single retry rule applies to all domains and all errors. It specifies # retries every 15 minutes for 2 hours, then every 2 hours until 1 full day # has passed since the first delivery failed. # Domain Error Retries # ------ ----- ------- * * F,2h,15m; F,1d,2h ###################################################################### # REWRITE CONFIGURATION # ###################################################################### # There are no rewriting specifications in this default configuration file. begin rewrite ###################################################################### # AUTHENTICATION CONFIGURATION # ###################################################################### # There are no authenticator specifications in this default configuration file. begin authenticators dovecot_plain: driver = dovecot public_name = PLAIN server_socket = /run/dovecot/auth-client server_set_id = $auth1 dovecot_login: driver = dovecot public_name = LOGIN server_socket = /run/dovecot/auth-client server_set_id = $auth1 # End of Exim configuration file