# $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 .include /etc/exim4/exim_local.conf smtp_accept_max = 50 smtp_accept_max_per_host = 10 smtp_accept_queue_per_connection = 50 tls_advertise_hosts = * tls_certificate = ${if exists{/etc/ssl/letsencrypt/${sg{${tls_sni}}{^smtp\.}{mail.}}.pem}{/etc/ssl/letsencrypt/${sg{${tls_sni}}{^smtp\.}{mail.}}.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='${quote_mysql:$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}} .ifdef GREYLIST_ENABLED # 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 # 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 = :at_start_rfc: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 = :at_start_rfc:X-DKIM-Status: fail: $dkim_verify_reason set acl_m_junk = yes # DKIM invalid accept dkim_status = invalid logwrite = DKIM test invalid: $dkim_verify_reason add_header = :at_start_rfc:X-DKIM-Status: invalid: $dkim_verify_reason set acl_m_junk = yes # DKIM none accept dkim_status = none logwrite = DKIM test none add_header = :at_start_rfc:X-DKIM-Status: none # DKIM pass accept dkim_status = pass logwrite = DKIM test passed add_header = :at_start_rfc: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 # 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_mboxes WHERE vm_mboxes.mbox="${quote_mysql:$sender_address_local_part}" AND vm_mboxes.domain="${quote_mysql:$sender_address_domain}"}{$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 ###################################################################### # SRS configs taken from: https://ente.limmat.ch/kb/exim/exim_v4_srs.html ## SRS checks are temporarily disabled. srsd is currently broken on Ubuntu 22.04. ## These could be updated to use "run{/usr/bin/srs" instead of readsocket. ## # 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 = :at_start_rfc:X-Whitelist-Flag: YES set acl_m_whitelist = yes ###################################################################### # 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 = :at_start_rfc:X-DNS-Whitelist: $sender_host_address is listed in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}} .ifdef SPF_ENABLED # Check SPF. Failures are marked as Junk and accepted - this skips further checks (e.g. DNSBL) and filters messages to the Junk folder accept spf = fail:softfail set acl_m_junk = yes add_header = :at_start_rfc:$spf_received warn spf = pass:neutral:permerror add_header = :at_start_rfc:$spf_received .endif .ifdef DNSBL_JUNK # check DNSBL(s) and if found add header for filtering to Junk accept !condition = ${if eq {$header_X-Whitelist-Flag:}{YES}} dnslists = psbl.surriel.com : b.barracudacentral.org logwrite = Warning: $sender_host_address is listed in DNSBL $dnslist_domain ${if def:dnslist_text {($dnslist_text)}} add_header = :at_start_rfc:X-DNS-Blacklist: $sender_host_address is listed in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}} set acl_m_junk = yes .elifdef DNSBL_REJECT # check DNSBL(s) and if found reject message deny !condition = ${if eq {$header_X-Whitelist-Flag:}{YES}} dnslists = psbl.surriel.com : b.barracudacentral.org logwrite = Warning: $sender_host_address is listed in DNSBL $dnslist_domain ${if def:dnslist_text {($dnslist_text)}} .endif .ifdef GREYLIST_ENABLED # greylisting # 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 sender listed in skip_greylisting_hosts accept hosts = +skip_greylisting_hosts log_message = skipping greylisting due to match in skip_greylisting_hosts # 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 = :at_start_rfc:X-DNS-Greylist: known resender logwrite = skipping greylisting for $acl_m_sender due to match in GREYLIST_RESENDERS_TABLE # run greylisting acl 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}} # Check if 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) 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 = :at_start_rfc:X-RFC2822-Error: Your message does not conform to RFC2822 standard set acl_m_junk = 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 = :at_start_rfc:X-Sender-Verify-Failed: No valid sender in message header .ifdef VIRUS_REJECT # Deny if the message contains a virus. Before enabling this check, you # must install a virus scanner and set the av_scanner option above. # deny malware = */defer_ok log_message = This message contains a virus ($malware_name). .elifdef VIRUS_JUNK # Filter virus messages as spam/junk warn malware = */defer_ok log_message = This message contains a virus ($malware_name). add_header = :at_start_rfc:X-Virus-Warning: This message contains a virus ($malware_name). set acl_m_junk = yes .endif # 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_mboxes WHERE vm_mboxes.mbox="${quote_mysql:$sender_address_local_part}" AND vm_mboxes.domain="${quote_mysql:$sender_address_domain}" }} 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 = ${run{/usr/bin/srs --secretfile=/etc/exim4/srsd.secret --hashlength=24 --reverse --address=$local_part_prefix$local_part@$domain}{$value}{:defer: SRS failure}} ## srsd is broken on ubuntu 22.04. using above "run" command instead # 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='${quote_mysql:$original_domain}' AND vm_domains.status = '1'}} address_data = ${run{/usr/bin/srs --secretfile=/etc/exim4/srsd.secret --hashlength=24 --forward --address=$sender_address_local_part@$sender_address_domain --alias=$original_domain}{$value}{:defer: SRS failure}} ## srsd is broken on ubuntu 22.04. using above "run" command instead # 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). junk_filter: driver = accept domains = +local_domains condition = ${if eq{$acl_m_junk}{yes}} condition = ${if !eq{$acl_m_whitelist}{yes}} condition = ${if !eq {$received_protocol}{spam-scanned}} local_part_suffix = +* local_part_suffix_optional = true local_parts = ${lookup mysql{SELECT vm_mboxes.mbox FROM vm_mboxes WHERE vm_mboxes.mbox='${quote_mysql:$local_part}' AND vm_mboxes.domain='${quote_mysql:$domain}' AND vm_mboxes.status > '0' AND vm_mboxes.filter > '0'}} headers_add = X-Junk-Flag: YES transport = junk_delivery spam_filter: driver = accept domains = +local_domains condition = ${if eq {$received_protocol}{spam-scanned}} condition = ${if eq {$header_X-Spam-Flag:}{YES}} ## condition = ${if !eq {$header_X-Whitelist-Flag:}{YES}} local_part_suffix = +* local_part_suffix_optional = true local_parts = ${lookup mysql{SELECT vm_mboxes.mbox FROM vm_mboxes WHERE vm_mboxes.mbox='${quote_mysql:$local_part}' AND vm_mboxes.domain='${quote_mysql:$domain}' AND vm_mboxes.status > '0' AND vm_mboxes.filter = '2'}} transport = junk_delivery mailman3_router: driver = accept domains = +local_domains condition = ${if !eq {$received_protocol}{spam-scanned}} require_files = /var/lib/mailman3/lists/${local_part}.${domain} local_part_suffix_optional local_part_suffix = \ -bounces : -bounces+* : \ -confirm : -confirm+* : \ -join : -leave : \ -owner : -request : \ -subscribe : -unsubscribe transport = mailman3_transport 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_aliases.mbox,'@','${quote_mysql:$domain}') FROM vm_mboxes, vm_aliases WHERE vm_mboxes.mbox=vm_aliases.mbox AND vm_mboxes.domain='${quote_mysql:$domain}' AND vm_mboxes.status > '0' AND vm_aliases.alias='${quote_mysql:$local_part}' AND vm_aliases.domain='${quote_mysql:$domain}'}} virtual_vacation: driver = accept domains = +local_domains # currently configured to *not* autorespond to + aliases #local_part_suffix = +* #local_part_suffix_optional = true local_parts = ${lookup mysql{SELECT vm_mboxes.mbox FROM vm_mboxes, vm_autoresponders WHERE vm_mboxes.mbox='${quote_mysql:$local_part}' AND vm_mboxes.domain='${quote_mysql:$domain}' AND vm_mboxes.status > '0' AND vm_autoresponders.mbox='${quote_mysql:$local_part}' AND vm_autoresponders.domain='${quote_mysql:$domain}' AND vm_autoresponders.mode='Vacation' AND vm_autoresponders.status='1'}} # add options for start & end date fields to above query # do not reply to errors or lists or spam-scanned messages, require vacation message in db condition = ${if !match {$h_precedence:} {(?i)junk|bulk|list}} condition = ${if !eq{$acl_m_junk}{yes}} condition = ${if !eq {$received_protocol}{spam-scanned}} condition = ${if !eq {$sender_address} {}} 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 local_parts = ${lookup mysql{SELECT vm_mboxes.mbox FROM vm_mboxes, vm_autoresponders WHERE vm_mboxes.mbox='${quote_mysql:$local_part}' AND vm_mboxes.domain='${quote_mysql:$domain}' AND vm_mboxes.status > '0' AND vm_autoresponders.mbox='${quote_mysql:$local_part}' AND vm_autoresponders.domain='${quote_mysql:$domain}' AND vm_autoresponders.mode='Autoresponder' AND vm_autoresponders.status='1'}} # add options for start & end date fields to above query # do not reply to errors or lists or spam-scanned messages, require autoresponder message in db condition = ${if !match {$h_precedence:} {(?i)junk|bulk|list}} condition = ${if !eq{$acl_m_junk}{yes}} condition = ${if !eq {$received_protocol}{spam-scanned}} condition = ${if !eq {$sender_address} {}} 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_mboxes, vm_forwards WHERE vm_mboxes.mbox='${quote_mysql:$local_part}' AND vm_mboxes.domain='${quote_mysql:$domain}' AND vm_mboxes.status > '0' AND vm_forwards.mbox='${quote_mysql:$local_part}' AND vm_forwards.domain='${quote_mysql:$domain}' 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('${quote_mysql:$local_part}@${quote_mysql:$domain}\n', vm_forwards.forward_to) FROM vm_mboxes, vm_forwards WHERE vm_mboxes.mbox='${quote_mysql:$local_part}' AND vm_mboxes.domain='${quote_mysql:$domain}' AND vm_mboxes.status > '0' AND vm_forwards.mbox='${quote_mysql:$local_part}' AND vm_forwards.domain='${quote_mysql:$domain}' AND vm_forwards.save_local='1'}} spamcheck_router: driver = accept # uncomment next line to bypass spamcheck when testing address routing with "exim -bt user@example.com" #address_test = false domains = +local_domains condition = ${if !eq {$received_protocol}{spam-scanned}} condition = ${if !eq {$sender_address_domain}{$domain}} condition = ${if !eq{$acl_m_whitelist}{yes}} condition = ${if < {$message_size}{512k}} local_part_suffix = +* local_part_suffix_optional = true local_parts = ${lookup mysql{SELECT vm_mboxes.mbox FROM vm_mboxes WHERE vm_mboxes.mbox='${quote_mysql:$local_part}' AND vm_mboxes.domain='${quote_mysql:$domain}' AND vm_mboxes.status > '0' AND vm_mboxes.filter = '2'}} headers_remove = X-Spam-Checker-Version:X-Spam-Flag:X-Spam-Level:X-Spam-Status:X-Spam-Score:X-Spam-Report transport = spamcheck # add mailman3 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_mboxes, vm_filters WHERE vm_mboxes.mbox='${quote_mysql:$local_part}' AND vm_mboxes.domain='${quote_mysql:$domain}' AND vm_mboxes.status > '0' AND vm_filters.mbox='${quote_mysql:$local_part}' AND vm_filters.domain='${quote_mysql:$domain}'}} 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_mboxes WHERE vm_mboxes.mbox='${quote_mysql:$local_part}' AND vm_mboxes.domain='${quote_mysql:$domain}' AND vm_mboxes.status > '0'}} 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_aliases.mbox,'@','${quote_mysql:$domain}') FROM vm_mboxes, vm_aliases WHERE vm_mboxes.mbox=vm_aliases.mbox AND vm_mboxes.domain='${quote_mysql:$domain}' AND vm_mboxes.status > '0' AND vm_aliases.alias='catchall' AND vm_aliases.domain='${quote_mysql:$domain}'}} # 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, and User Forwards below, are only enabled for primary_hostname & qualify_domain. # primary_hostname is the server hostname (FQDN) # qualify_domain can be set in exim_local.conf and defaults to the server domain name, # which is the primary_hostname without the local part - what "hostname -d" returns. # These routers do not get invoked for any other virtual email domains configured on the server. system_aliases: driver = redirect domains = $primary_hostname:$qualify_domain:$qualify_recipient 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 domains = $primary_hostname:$qualify_domain:$qualify_recipient 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 # run{/bin/echo part is required to de-taint the domain dkim_domain = ${run{/bin/echo ${lc:${domain:$h_from:}}}{$value}} 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/vmail/$local_part_data # maildir_format = true # directory = /home/$local_part_data/Maildir # create_directory = true # directory_mode = 770 # delivery_date_add # envelope_to_add # return_path_add # user = $local_part # group = $local_part # 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 ## for vacation mail vacation_transport: driver = autoreply log = /var/vmail/${domain_data}/${local_part_data}/vacation_log once = /var/vmail/${domain_data}/${local_part_data}/vacation_once_db return_path = ${local_part}@${domain} to = ${sender_address} from = ${local_part}@${domain} subject = ${lookup mysql{SELECT vm_autoresponders.subject FROM vm_autoresponders WHERE vm_autoresponders.mbox='${quote_mysql:$local_part}' AND vm_autoresponders.domain='${quote_mysql:$domain}' AND vm_autoresponders.status='1' AND vm_autoresponders.mode='Vacation'}{$value}{"Auto Reply"}} text = ${lookup mysql{SELECT vm_autoresponders.body FROM vm_autoresponders WHERE vm_autoresponders.mbox='${quote_mysql:$local_part}' AND vm_autoresponders.domain='${quote_mysql:$domain}' AND vm_autoresponders.status='1' AND vm_autoresponders.mode='Vacation'}{$value}fail} user = vmail ## for autoresponder autoresponder_transport: driver = autoreply log = /var/vmail/${domain_data}/${local_part_data}/autoresponder_log return_path = ${local_part}@${domain} to = ${sender_address} from = ${local_part}@${domain} subject = ${lookup mysql{SELECT vm_autoresponders.subject FROM vm_autoresponders WHERE vm_autoresponders.mbox='${quote_mysql:$local_part}' AND vm_autoresponders.domain='${quote_mysql:$domain}' AND vm_autoresponders.status='1' AND vm_autoresponders.mode='Autoresponder'}{$value}{"Auto Reply"}} text = ${lookup mysql{SELECT vm_autoresponders.body FROM vm_autoresponders WHERE vm_autoresponders.mbox='${quote_mysql:$local_part}' AND vm_autoresponders.domain='${quote_mysql:$domain}' AND vm_autoresponders.status='1' AND vm_autoresponders.mode='Autoresponder'}{$value}fail} user = vmail #maildir_delivery: # driver = appendfile # maildir_format = true # directory = /var/vmail/${domain_data}/${local_part_data}/Maildir # create_directory = true # directory_mode = 750 # user = vmail user_filter_maildir_delivery: driver = appendfile maildir_format = true user = vmail junk_delivery: driver = appendfile maildir_format = true directory = /var/vmail/${domain_data}/${local_part_data}/Maildir/.Junk create_directory = true 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_data@$domain_data 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 = mailman3_transport: driver = smtp protocol = lmtp allow_localhost hosts = localhost #hosts_override port = 8024 rcpt_include_affixes = true #mailman_transport: # driver = pipe # command = MAILMAN_WRAP \ # '${if def:local_part_suffix \ # {${sg{$local_part_suffix}{-(\\w+)(\\+.*)?}{\$1}}} \ # {post}}' \ # $local_part # current_directory = MAILMAN_HOME # home_directory = MAILMAN_HOME # user = MAILMAN_UID # group = MAILMAN_GID # 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