From e4e5c41bbc007ca618867d5237ec76a1abc675e1 Mon Sep 17 00:00:00 2001 From: Matthew Saunders Brown Date: Fri, 14 Jun 2024 14:36:22 -0700 Subject: [PATCH] Debian 12 bookworm compat --- etc/exim4/autowhitelist.filter | 12 +- etc/exim4/exim4-bookworm.conf | 1203 ++++++++++++++++++++ etc/exim4/{exim4.conf => exim4-jammy.conf} | 116 +- install.sh | 46 +- 4 files changed, 1320 insertions(+), 57 deletions(-) create mode 100644 etc/exim4/exim4-bookworm.conf rename etc/exim4/{exim4.conf => exim4-jammy.conf} (87%) diff --git a/etc/exim4/autowhitelist.filter b/etc/exim4/autowhitelist.filter index 498dd1f..f661a45 100644 --- a/etc/exim4/autowhitelist.filter +++ b/etc/exim4/autowhitelist.filter @@ -3,17 +3,17 @@ if error_message then finish endif # check for and delete type = MANUAL. don't need to insert anything??? -if "${lookup mysql{SELECT COUNT(*) FROM vm_greylisting WHERE sender = \"$local_part@$domain\" AND recipient = '$sender_address' AND type = 'MANUAL'}}" is 0 then - if "${lookup mysql{INSERT INTO vm_greylisting SET sender = \"$local_part@$domain\", recipient = '$sender_address', create_time = NOW()}}" is not 0 then - if "${lookup mysql{DELETE FROM vm_greylisting WHERE sender = \"$local_part@$domain\" AND recipient = '$sender_address' AND type = 'AUTO'}}" is not 0 then +if "${lookup mysql{SELECT COUNT(*) FROM vm_greylisting WHERE sender = \"${quote_mysql:$local_part}@${quote_mysql:$domain}\" AND recipient = '${quote_mysql:$sender_address}' AND type = 'MANUAL'}}" is 0 then + if "${lookup mysql{INSERT INTO vm_greylisting SET sender = \"${quote_mysql:$local_part}@${quote_mysql:$domain}\", recipient = '${quote_mysql:$sender_address}', create_time = NOW()}}" is not 0 then + if "${lookup mysql{DELETE FROM vm_greylisting WHERE sender = \"${quote_mysql:$local_part}@${quote_mysql:$domain}\" AND recipient = '${quote_mysql:$sender_address}' AND type = 'AUTO'}}" is not 0 then # done updated greylisting endif endif endif -if "${lookup mysql{SELECT COUNT(*) FROM sa_userpref WHERE username = '$sender_address' AND preference = 'whitelist_from' AND value = \"$local_part@$domain\"}}" is 0 then - if "${lookup mysql{INSERT INTO sa_userpref SET username = '$sender_address', preference = 'whitelist_from', value = \"$local_part@$domain\"}}" is not 0 then - if "${lookup mysql{DELETE FROM sa_userpref WHERE username = '$sender_address' AND preference != 'whitelist_from' AND value = \"$local_part@$domain\"}}" is not 0 then +if "${lookup mysql{SELECT COUNT(*) FROM sa_userpref WHERE username = '${quote_mysql:$sender_address}' AND preference = 'whitelist_from' AND value = \"${quote_mysql:$local_part}@${quote_mysql:$domain}\"}}" is 0 then + if "${lookup mysql{INSERT INTO sa_userpref SET username = '${quote_mysql:$sender_address}', preference = 'whitelist_from', value = \"${quote_mysql:$local_part}@${quote_mysql:$domain}\"}}" is not 0 then + if "${lookup mysql{DELETE FROM sa_userpref WHERE username = '${quote_mysql:$sender_address}' AND preference != 'whitelist_from' AND value = \"${quote_mysql:$local_part}@${quote_mysql:$domain}\"}}" is not 0 then # done updated sa_userpref endif endif diff --git a/etc/exim4/exim4-bookworm.conf b/etc/exim4/exim4-bookworm.conf new file mode 100644 index 0000000..d8cbafd --- /dev/null +++ b/etc/exim4/exim4-bookworm.conf @@ -0,0 +1,1203 @@ +# $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/${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}} + +# 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}' + +# 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 +# +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}} + +# 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="$sender_address_local_part" AND vm_mboxes.domain='$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)}} + + # 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 + + # 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 = :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 + + # 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 + + # 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 + + # 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 + # 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 + + # 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 diff --git a/etc/exim4/exim4.conf b/etc/exim4/exim4-jammy.conf similarity index 87% rename from etc/exim4/exim4.conf rename to etc/exim4/exim4-jammy.conf index 405cf21..f07fa9f 100644 --- a/etc/exim4/exim4.conf +++ b/etc/exim4/exim4-jammy.conf @@ -72,7 +72,7 @@ log_selector = +all # +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 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 @@ -275,7 +275,7 @@ greylist_acl: condition = ${lookup mysql{GREYLIST_OK_BOUNCE}} deny - add_header = X-DNS-Greylist: mail from $sender_address to $local_part@$domain accepted by greylisting + 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}} # This access control list is used at the start of an incoming @@ -323,27 +323,27 @@ acl_check_dkim: accept dkim_status = fail logwrite = DKIM test failed: $dkim_verify_reason - add_header = X-DKIM-Status: fail: $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 = X-DKIM-Status: 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 = X-DKIM-Status: none + add_header = :at_start_rfc: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. + add_header = :at_start_rfc:X-DKIM-Status: passed: (address=$sender_address domain=$dkim_cur_signer), signature is good. # Accept the message. accept @@ -524,7 +524,7 @@ acl_rcpt_to: accept condition = WHITELISTED logwrite = From: $sender_address To: $local_part@$domain is whitelisted in sa_userpref - add_header = X-Whitelist-Flag: YES + add_header = :at_start_rfc:X-Whitelist-Flag: YES set acl_m_whitelist = yes ###################################################################### @@ -546,9 +546,9 @@ acl_rcpt_to: ## 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_header = :at_start_rfc:X-DNS-Whitelist: $sender_host_address is listed in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}} - # add SPF header + # Check SPF. Failures are marked as Junk and accepted - this skips further checks (e.g. DNSBL) and filters messages to the Junk folder accept !senders = : condition = ${run{/usr/bin/spfquery --scope mfrom \ @@ -599,7 +599,7 @@ acl_rcpt_to: !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 = :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 # greylisting @@ -622,7 +622,7 @@ acl_rcpt_to: 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 + 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 @@ -685,7 +685,7 @@ acl_data: !hosts = +relay_from_hosts !senders = : postmaster@* !verify = header_syntax - add_header = X-RFC2822-Error: Your message does not conform to RFC2822 standard + 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 @@ -693,7 +693,7 @@ acl_data: warn !verify = header_sender log_message = No valid sender in message header - add_header = X-Sender-Verify-Failed: No valid sender in message header + add_header = :at_start_rfc: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. @@ -701,7 +701,7 @@ acl_data: #accept # 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 = :at_start_rfc:X-Virus-Warning: This message contains a virus ($malware_name). # set acl_m_junk = yes # Accept the message. @@ -723,7 +723,7 @@ 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="$sender_address_local_part" AND vm_mboxes.domain="$sender_address_domain" }} + 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 @@ -749,7 +749,7 @@ 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'}} + 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}\ @@ -779,14 +779,6 @@ dnslookup: # 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_aliases.mbox,'@','${domain}') FROM vm_mboxes, vm_aliases WHERE vm_mboxes.mbox=vm_aliases.mbox AND vm_mboxes.domain='${domain}' AND vm_mboxes.status > '0' AND vm_aliases.alias='${local_part}' AND vm_aliases.domain='${domain}'}} - junk_filter: driver = accept domains = +local_domains @@ -795,7 +787,7 @@ junk_filter: 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='${local_part}' AND vm_mboxes.domain='${domain}' AND vm_mboxes.status > '0' AND vm_mboxes.filter > '0'}} + 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 @@ -807,16 +799,38 @@ spam_filter: ## 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='${local_part}' AND vm_mboxes.domain='${domain}' AND vm_mboxes.status > '0' AND vm_mboxes.filter = '2'}} + 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='${local_part}' AND vm_mboxes.domain='${domain}' AND vm_mboxes.status > '0' AND vm_autoresponders.mbox='${local_part}' AND vm_autoresponders.domain='${domain}' AND vm_autoresponders.mode='Vacation' AND vm_autoresponders.status='1'}} + 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}} @@ -841,7 +855,7 @@ virtual_autoresponder: # 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='${local_part}' AND vm_mboxes.domain='${domain}' AND vm_mboxes.status > '0' AND vm_autoresponders.mbox='${local_part}' AND vm_autoresponders.domain='${domain}' AND vm_autoresponders.mode='Autoresponder' AND vm_autoresponders.status='1'}} + 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}} @@ -866,7 +880,7 @@ virtual_forward_and_drop: 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='${local_part}' AND vm_mboxes.domain='${domain}' AND vm_mboxes.status > '0' AND vm_forwards.mbox='${local_part}' AND vm_forwards.domain='${domain}' AND vm_forwards.save_local='0'}} + 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 @@ -874,7 +888,7 @@ virtual_forward_and_keep: 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_mboxes, vm_forwards WHERE vm_mboxes.mbox='${local_part}' AND vm_mboxes.domain='${domain}' AND vm_mboxes.status > '0' AND vm_forwards.mbox='${local_part}' AND vm_forwards.domain='${domain}' AND vm_forwards.save_local='1'}} + 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 @@ -887,16 +901,18 @@ spamcheck_router: 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='${local_part}' AND vm_mboxes.domain='${domain}' AND vm_mboxes.status > '0' AND vm_mboxes.filter = '2'}} + 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='${local_part}' AND vm_mboxes.domain='${domain}' AND vm_mboxes.status > '0' AND vm_filters.mbox='${local_part}' AND vm_filters.domain='${domain}'}} + 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 @@ -913,7 +929,7 @@ lmtp_localuser: 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='${local_part}' AND vm_mboxes.domain='${domain}' AND vm_mboxes.status > '0'}} + 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 @@ -922,7 +938,7 @@ virtual_alias_catchall: driver = redirect domains = +local_domains ## condition = ${if !eq {$received_protocol}{spam-scanned}} - data = ${lookup mysql{SELECT CONCAT(vm_aliases.mbox,'@','${domain}') FROM vm_mboxes, vm_aliases WHERE vm_mboxes.mbox=vm_aliases.mbox AND vm_mboxes.domain='${domain}' AND vm_mboxes.status > '0' AND vm_aliases.alias='catchall' AND vm_aliases.domain='${domain}'}} + 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, @@ -1062,8 +1078,8 @@ vacation_transport: 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='${local_part}' AND vm_autoresponders.domain='${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='${local_part}' AND vm_autoresponders.domain='${domain}' AND vm_autoresponders.status='1' AND vm_autoresponders.mode='Vacation'}{$value}fail} + 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 @@ -1073,8 +1089,8 @@ autoresponder_transport: 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='${local_part}' AND vm_autoresponders.domain='${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='${local_part}' AND vm_autoresponders.domain='${domain}' AND vm_autoresponders.status='1' AND vm_autoresponders.mode='Autoresponder'}{$value}fail} + 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: @@ -1103,7 +1119,7 @@ spamcheck: driver = pipe command = /usr/sbin/exim -oMr spam-scanned -bS use_bsmtp = true - transport_filter = /usr/bin/spamc -f -u $local_part@$domain + 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! @@ -1115,6 +1131,28 @@ spamcheck: 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 diff --git a/install.sh b/install.sh index 91df8a5..e8ad2d6 100755 --- a/install.sh +++ b/install.sh @@ -5,13 +5,11 @@ if [ "${EUID}" -ne 0 ]; then exit fi -# check for Ubuntu 20.04 -if ! grep -q "Ubuntu 22.04" /etc/issue; then - echo "This installer is only tested on Ubuntu 22.04. If you are on a" - echo "different version of Ubuntu or a Debian/Debian based distro" - echo "and want to try running this installer open this script and" - echo "comment out the exit command below this line and re-run." - exit +# check for Ubuntu 22.04 (jammy) or Debian 12 (bookworm) +os_codename=`lsb_release -cs` +if [ $os_codename != jammy ] && [ $os_codename != bookworm ]; then + echo "This installer only runs on Ubuntu 22.04 (jammy) or Debian 12 (Bookworm), bailing out." + exit 1 fi # check if install is already in place @@ -93,13 +91,19 @@ sed -i "s|userpref|sa_userpref|g" /usr/share/doc/spamassassin/sql/userpref_mysql sed -i "s|username varchar(100)|username varchar(255)|g" /usr/share/doc/spamassassin/sql/userpref_mysql.sql sed -i "s|TYPE=MyISAM||g" /usr/share/doc/spamassassin/sql/userpref_mysql.sql mysql vmail < /usr/share/doc/spamassassin/sql/userpref_mysql.sql -sed -i 's|OPTIONS="--create-prefs --max-children 5 --helper-home-dir"|OPTIONS="-x -q -v -u Debian-exim -m 5"|g' /etc/default/spamassassin -sed -i 's|CRON=0|CRON=1|g' /etc/default/spamassassin +if [ $os_codename = jammy ]; then + sed -i 's|OPTIONS="--create-prefs --max-children 5 --helper-home-dir"|OPTIONS="-x -q -v -u Debian-exim -m 5"|g' /etc/default/spamassassin + sed -i 's|CRON=0|CRON=1|g' /etc/default/spamassassin +elif [ $os_codename != bookworm ]; then + sed -i 's|OPTIONS="--create-prefs --max-children 5 --helper-home-dir"|OPTIONS="-x -q -v -u Debian-exim -m 5"|g' /etc/default/spamd +else + echo "WARNING: Unexpected OS codename. This should never happen due to previous checks." +fi cp etc/spamassassin/*.cf /etc/spamassassin/ sed -i "s|user_scores_sql_password password|user_scores_sql_password $VMAILPASS|g" /etc/spamassassin/sql.cf chown root:root /etc/spamassassin/local.cf chmod 644 /etc/spamassassin/local.cf -chown debian-spamd:mail /etc/spamassassin/sql.cf +chown Debian-exim:mail /etc/spamassassin/sql.cf chmod 640 /etc/spamassassin/sql.cf # create local systemd dir, used by srsd & vmail-cron @@ -123,6 +127,13 @@ maildomain=`hostname -d` sed -i 's/size 10M/daily/g' /etc/logrotate.d/exim4-paniclog install --owner=Debian-exim --group=Debian-exim --mode=640 /dev/null /etc/exim4/relay_domains cp etc/exim4/* /etc/exim4/ +if [ $os_codename = jammy ]; then + cp /etc/exim4/exim4-jammy.conf /etc/exim4/exim4.conf +elif [ $os_codename != bookworm ]; then + cp /etc/exim4/exim4-bookworm.conf /etc/exim4/exim4.conf +else + echo "WARNING: Unexpected OS codename. This should never happen due to previous checks." +fi chmod 640 /etc/exim4/exim4.conf chown Debian-exim:Debian-exim /etc/exim4/autowhitelist.filter chmod 640 /etc/exim4/autowhitelist.filter @@ -140,6 +151,9 @@ chown Debian-exim:Debian-exim /etc/exim4/srsd.secret pwgen -N 1 64 > /etc/exim4/srsd.secret sed -i "s|^QUEUERUNNER.*|QUEUERUNNER='separate'|g" /etc/default/exim4 sed -i "s|^QUEUEINTERVAL.*|QUEUEINTERVAL='15m'|g" /etc/default/exim4 +if [[ ! -f /var/log/exim4/rejectlog ]]; then + install --owner=Debian-exim --group=adm --mode=640 /dev/null /var/log/exim4/rejectlog +fi # dovecot config mkdir /etc/dovecot/sites.d @@ -155,8 +169,15 @@ chmod 750 /usr/local/libexec/vmail-quota-warning.sh chown dovecot:mail /usr/local/libexec/vmail-quota-warning.sh # restart services -systemctl enable spamassassin -systemctl restart spamassassin +if [ $os_codename = jammy ]; then + systemctl enable spamassassin + systemctl restart spamassassin +elif [ $os_codename != bookworm ]; then + systemctl enable spamd + systemctl restart spamd +else + echo "WARNING: Unexpected OS codename. This should never happen due to previous checks." +fi systemctl restart exim4 systemctl restart dovecot @@ -201,6 +222,7 @@ else echo "fail2ban not installed, skipping fail2ban email configs." fi +sed -i '/^root/d' /etc/aliases echo "webmaster@$maildomain" > /root/.forward echo echo "System emails are all configured to alias to root@$fqdn,"