--- /dev/null
+*.conf
+hacks.m4
+local.mk
--- /dev/null
+### -*-makefile-*-
+###
+### Build script for Exim configuration
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+V = 0
+v_tag = $(call v_tag_$V,$1)
+v_tag_0 = @printf " %-6s %s\n" $1 $@;
+
+V_GEN = $(call v_tag,GEN)
+V_AT = $(V_AT_$V)
+V_AT_0 = @
+
+dir-nosl = $(patsubst %/,%,$(dir $1))
+
+all:
+.SECONDEXPANSION: # sorry
+
+CLEANFILES += $(TARGETS)
+
+EARLY = defs.m4 divmap.m4 config.m4
+MAIN = lists.m4 base.m4
+
+MODES =
+
+MODES += satellite
+OPTIONS_satellite = satellite.m4
+
+MODES += hub
+OPTIONS_hub = auth.m4 exchange.m4 local.m4 spam.m4 vhost.m4
+
+MODES += usersat
+OPTIONS_usersat = auth.m4 local.m4 satellite.m4
+
+-include local.mk
+
+HOST_MODES += $(foreach m, $(MODES), \
+ $(foreach h, $(HOSTS_$m), $h/$m))
+
+CONFIGS = $(foreach m, $(MODES), exim4-$m.conf)
+TARGETS += $(CONFIGS)
+$(CONFIGS): exim4-%.conf: $(EARLY) $$(HOOKS_$$*) $(MAIN) $$(OPTIONS_$$*)
+ $(V_GEN)m4 -P -DMODE=$* $^ >$@.new
+ $(V_AT)exim4 -C$@.new -bV >/dev/null
+ $(V_AT)mv $@.new $@
+
+all: $(TARGETS)
+
+THISHOST = $(shell hostname)
+
+ROOT = sudo
+
+INSTALL_TARGETS = $(addprefix install-, $(HOST_MODES))
+
+$(filter install-$(THISHOST)/%, $(INSTALL_TARGETS)): \
+install-$(THISHOST)/%: exim4-%.conf
+ $(ROOT) install -m644 $< /etc/exim4/exim4.conf
+ $(ROOT) service exim4 reload
+
+$(filter-out install-$(THISHOST)/%, $(INSTALL_TARGETS)): \
+install-%: exim4-$$(notdir $$*).conf
+ $(ROOT) scp $< root@$(call dir-nosl,$*):/etc/exim4/exim4.conf
+ $(ROOT) ssh root@$(call dir-nosl,$*) service exim4 reload
+
+install: $(INSTALL_TARGETS)
+
+clean:; rm -f $(CLEANFILES)
+.PHONY: clean
--- /dev/null
+The =distorted.org.uk= mail system
+
+* Delivery
+
+The mail delivery agent is Exim. If you don't do anything special, mail
+is delivered into =/var/mail/USER= on stratocaster, in mbox format.
+
+There are a number of ways you can affect mail delivery.
+
+** The =~/.forward= file
+
+In traditional Unix style, you can write delivery instructions into a
+file named =.forward= in your home directory. This file can contain a
+comma-separated list of email address and/or file or directory names to
+which your mail should be sent. Mail is written to files in traditional
+Unix `mbox' format, and to directories in `Maildir' format. The
+=:fail:= and =:defer:= items are permitted, but may not be very useful.
+
+This file can instead be an Exim or Sieve filter file, as marked by a
+special comment on the first line. See the document `Exim's interfaces
+to mail filtering', available via the command =info filter=, for details
+about these files.
+
+** The =~/.mail/forward= file
+
+If you prefer, you can write delivery instructions to =~/.mail/forward=
+instead. If you have lots of mail configuration files, you may find it
+tidier to keep them all together in =~/.mail=.
+
+** The =~/.mail/forward.suffix= file
+
+You will receive mail sent to =USER@distorted.org.uk=. You can also
+receive mail sent to =USER-SUFFIX@distorted.org.uk= or
+=USER+SUFFIX@distorted.org.uk=, for any =SUFFIX= string if you create a
+file =~/.mail/forward.suffix=. While this can be a simple forward file,
+it's probably much more useful to write an Exim filter file to analyse
+the suffix string and take appropriate action.
+
+If this file exists, it should be world-readable, because it will be
+used by the mail server at SMTP time in order to decide whether a
+particular =SUFFIX= string is valid.
+
+
+* Reading mail
+
+** Reading mail locally
+
+The servers =stratocaster= and =jem= have a few mail user agents
+installed, most notably trad BSD =mail=, =mutt=, and Emacs's various
+mail-reading interfaces; more can be added.
+
+** Fetching mail through IMAP
+
+There's an IMAP server running on =mail.distorted.org.uk=. ...
+
+** Forwarding mail off-site
+
+
+* Spam filtering
+
+The mail server checks incoming mail using SpamAssassin at SMTP time.
+Suspected spam is rejected immediately. There are no `junk' mail
+folders. Legitimate senders will likely receive bounces; spammers will
+probably ignore the error and continue.
+
+** SpamAssassin
+
+SpamAssassin works by having a large collection of rules: it tests an
+incoming message against these rules, and adds up the /scores/ for the
+rules that match. If the total score is above a given threshold then
+the message is declared to be probably spam, and rejected.
+
+If the mail server accepts a message, it adds two headers to it.
+
+ + =X-SpamAssassin-Score= has the form =SCORE/LIMIT (BAR)=, where
+ =SCORE= is the actual score for the message, =LIMIT= is the maximum
+ score allowed, and =BAR= is a little bar chart showing the score in
+ a way which can be matched easily using regular expressions. The
+ bar chart uses =+= or =-= signs, depending on whether the score is
+ positive or negative, or consists of a single =/= sign if it's close
+ to zero.
+
+ + =X-SpamAssassin-Status= consists of space-separated =KEY=VAUE=
+ pairs. The keys currently are: =score= and =limit=, which are the
+ message's score and limit again; and =tests=, which lists the rules
+ which matched the message and their individual scores, as a
+ comma-separated list of items of the form =RULE:SCORE=.
+
+** Custom spam limits
+
+The default spam limit is currently 5 points. However, you can override
+this limit for mail sent to you by creating a world-readable file
+=~/.mail/spam-limit= in your home directory on stratocaster. This file
+should contain lines of the form
+
+: PATTERN: LIMIT
+
+where =PATTERN= is an Exim =nwildlsearch= pattern matched against a
+string of the form =RECIPIENT/SENDER=, and the =LIMIT= is ten times the
+maximum SpamAssassin score you're willing to tolerate for this message.
+See the Exim manual for full details; in short, the pattern may be a
+literal string, a string beginning with a =*= to match a particular
+suffix (usually a sender address or domain, which is why the sender is
+on the right), or a Perl-style regular expression starting with =^=.
+
+You may not want information about who is sending you spam (or honest
+but spamlike mail) to be public knowledge, so instead you can make a
+file =~/.mail/spam-limit.userv= of the same format. This file need not
+be readable by anyone other than you.
+
+Be careful with this facility: if a single incoming message has multiple
+recipients, and they assign it different spam score limits (either
+explicitly, or implicitly by accepting the system default) then the
+sender will be told to defer delivery to some recipients. It's
+therefore probably a bad idea to apply custom spam score limits for mail
+for popular mailing lists, for example.
+
+** SAUCE
+
+I'm not currently running SAUCE, but I'm giving it some consideration.
+If you have comments on the matter, either way, I'm interested.
+
+
+* Sending mail
+
+** Submission mechanisms
+
+Mail can be sent in a number of ways.
+
+ + The =sendmail= program. This is really Exim in disguise.
+
+ + SMTP to =localhost= port 25. This doesn't require explicit
+ authentication, since it relies on an identd, which is running on
+ all =distorted.org.uk= hosts.
+
+ + SMTP to =mail.distorted.org.uk= port 587. You must establish TLS,
+ and authenticate using a username and password; the server uses a
+ short-lived certificate signed by the =distorted.org.uk= certificate
+ authority, whose root certificate is at =/etc/ca/ca.cert= on all
+ servers. Use [[https://www.distorted.org.uk/chpwd/][Chopwood]] to set or change this password.
+
+** Sender authenticity
+
+It is my intention that it be very hard for one =distorted.org.uk= user
+to impersonate another to a third. To this end, the mail server is
+rather picky about envelope sender addresses.
+
+ + It won't accept an apparently local sender address from an external
+ mail server at all.
+
+ + It will check locally submitted mail against the submitter's user
+ name. The precise details vary according to the submission
+ mechanism: mail submitted through =sendmail= will have additional
+ headers added; mail submitted through SMTP will be rejected unless
+ the envelope sender is acceptable.
+
+If I see something like DKIM catching on then this will also provide
+external users with some kind of (probably fairly weak) sender
+authenticity.
+
+On the other hand, the mail server is aware of vanity domains, extension
+addresses, and so on, and should let you send mail apparently from an
+such an address that you control. If you think the mail server is being
+unnecessarily strict about something then I'm willing to discuss your
+requirements.
+
+If I'm hosting your mail domain for you then you get to decide the
+appropriate policy.
+
+
+* Mail hosting and custom domains
+
+I think I have a fairly sane way to set up stratocaster (or some other
+server, but strat is the obvious choice) to receive mail for domains
+other than =distorted.org.uk=. I can easily arrange to accept mail for
+such domains and deliver them locally or to other hosts. Pester me if
+this sounds useful to you.
+
+
+* Quick reference
+
+
+
+* COMMENT Emacs cruft
+
+### Local variables:
+### mode: org
+### End:
--- /dev/null
+### -*-m4-*-
+###
+### Client authentication for distorted.org.uk Exim configuration
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### Authenticators.
+
+m4_define(<:CHECK_PASSWD:>,
+<:${lookup {$1} lsearch {CONF_sysconf_dir/passwd} \
+ {${if crypteq {$2} {$value}}} \
+ {false}}:>)
+
+m4_define(<:ALLOW_PLAINTEXT_AUTH_P:>,
+<:or {{match_ip {$sender_host_address}{+localnet}} \
+ {and {{def:tls_cipher} {eq{$acl_c_mode}{submission}}}}}:>)
+
+SECTION(auth)m4_dnl
+plain:
+ driver = plaintext
+ public_name = PLAIN
+ server_advertise_condition = ${if ALLOW_PLAINTEXT_AUTH_P}
+ server_prompts = :
+ server_condition = CHECK_PASSWD($auth2, $auth3)
+ server_set_id = $auth2
+
+login:
+ driver = plaintext
+ public_name = LOGIN
+ server_advertise_condition = ${if ALLOW_PLAINTEXT_AUTH_P}
+ server_prompts = <; Username: ; Password:
+ server_condition = CHECK_PASSWD($auth1, $auth2)
+ server_set_id = $auth1
+
+DIVERT(null)
+###--------------------------------------------------------------------------
+### Verification of sender address.
+
+SECTION(global, acl)m4_dnl
+acl_not_smtp_start = not_smtp_start
+SECTION(acl, misc)m4_dnl
+not_smtp_start:
+ ## Record the user's name.
+ warn set acl_c_user = $sender_ident
+
+SECTION(acl, mail-hooks)m4_dnl
+ ## Check that a submitted message's sender address is allowable.
+ require acl = mail_check_auth
+
+SECTION(acl, misc)m4_dnl
+mail_check_auth:
+
+ ## If this isn't a submission then it doesn't need checking.
+ accept condition = ${if !eq{$acl_c_mode}{submission}}
+
+ ## If the caller hasn't formally authenticated, but this is a
+ ## loopback connection, then we can trust identd to tell us the right
+ ## answer. So we should stash the right name somewhere consistent.
+ warn set acl_c_user = $authenticated_id
+ hosts = +localnet
+ !authenticated = *
+ set acl_c_user = $sender_ident
+
+ ## User must be authenticated.
+ deny message = Sender not authenticated
+ !hosts = +localnet
+ !authenticated = *
+
+ ## Make sure that the local part is one that the authenticated sender
+ ## is allowed to claim.
+ deny message = Sender address forbidden to calling user
+ !condition = ${LOOKUP_DOMAIN($sender_address_domain,
+ {${if and {{match_local_part \
+ {$acl_c_user} \
+ {+dom_users}} \
+ {match_local_part \
+ {$sender_address_local_part} \
+ {+dom_locals}}}}},
+ {${if and {{match_local_part \
+ {$sender_address_local_part} \
+ {+user_extaddr}} \
+ {or {{eq {$sender_address_domain} \
+ {}} \
+ {match_domain \
+ {$sender_address_domain} \
+ {+public}}}}}}})}
+
+ ## All done.
+ accept
+
+DIVERT(null)
+###--------------------------------------------------------------------------
+### Dealing with `AUTH' parameters and relaying.
+
+SECTION(global, acl)m4_dnl
+acl_smtp_mailauth = mailauth
+SECTION(acl, misc)m4_dnl
+## Check the `AUTH=...' parameter to a `MAIL' command.
+mailauth:
+ ## If the client has authenticated using TLS then we're OK. The
+ ## sender was presumably checked upstream, and we can believe that
+ ## the name has been transmitted honestly.
+ accept condition = ${if def:tls_peerdn}
+
+ ## If this is submission, and the client has authenticated, then we
+ ## check that the name matches the user.
+ accept condition = ${if eq {$authenticated_sender} \
+ {$authenticated_id@CONF_master_domain}}
+
+ ## Otherwise we can't tell who really sent it.
+ deny message = Authenticated user not authoritative for claimed sender.
+
+DIVERT(null)
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+### -*-m4-*-
+###
+### Basic settings for distorted.org.uk Exim configuration
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### Global settings.
+
+SECTION(global, priv)m4_dnl
+prod_requires_admin = false
+
+SECTION(global, logging)m4_dnl
+log_file_path = : syslog
+log_selector = \
+ +smtp_confirmation \
+ +tls_peerdn
+log_timezone = true
+syslog_duplication = false
+syslog_timestamp = false
+
+SECTION(global, daemon)m4_dnl
+local_interfaces = <; CONF_interfaces
+extra_local_interfaces = <; 0.0.0.0 ; ::
+
+SECTION(global, resource)m4_dnl
+deliver_queue_load_max = 8
+queue_only_load = 12
+smtp_accept_max = 16
+smtp_accept_queue = 32
+smtp_accept_reserve = 4
+smtp_load_reserve = 10
+smtp_reserve_hosts = +trusted
+
+SECTION(global, policy)m4_dnl
+host_lookup = *
+
+SECTION(global, users)m4_dnl
+gecos_name = $1
+gecos_pattern = ([^,:]*)
+
+SECTION(global, incoming)m4_dnl
+received_header_text = Received: \
+ ${if def:sender_rcvhost {from $sender_rcvhost\n\t} \
+ {${if def:sender_ident \
+ {from ${quote_local_part:$sender_ident} }}\
+ ${if def:sender_helo_name \
+ {(helo=$sender_helo_name)\n\t}}}}\
+ by $primary_hostname \
+ ${if def:received_protocol \
+ {with $received_protocol \
+ ${if def:tls_cipher {(cipher=$tls_cipher)\n\t}}}}\
+ (Exim $version_number)\n\t\
+ ${if def:sender_address \
+ {(envelope-from <$sender_address>\
+ ${if def:authenticated_id \
+ {; auth=$authenticated_id}})\n\t}}\
+ id $message_exim_id\
+ ${if def:received_for {\n\tfor $received_for}}
+
+SECTION(global, smtp)m4_dnl
+smtp_return_error_details = true
+accept_8bitmime = true
+
+SECTION(global, process)m4_dnl
+extract_addresses_remove_arguments = false
+headers_charset = utf-8
+qualify_domain = CONF_master_domain
+
+SECTION(global, bounce)m4_dnl
+delay_warning = 1h : 24h : 2d
+
+DIVERT(null)
+###--------------------------------------------------------------------------
+### Access control lists.
+
+SECTION(global, acl-after)
+SECTION(global, acl)m4_dnl
+acl_smtp_helo = helo
+SECTION(acl, misc)m4_dnl
+helo:
+ require message = The other one has bells on
+ verify = helo
+
+ accept
+
+SECTION(global, acl)m4_dnl
+acl_smtp_mail = mail
+SECTION(acl, mail)m4_dnl
+mail:
+
+ ## Always allow the empty sender, so that we can receive bounces.
+ accept senders = :
+
+ ## Ensure that the sender is routable. This is important to prevent
+ ## undeliverable bounces.
+ require message = Invalid sender; \
+ ($sender_verify_failure; $acl_verify_message)
+ verify = sender
+
+ ## If this is directly from a client then hack on it for a while.
+ warn condition = ${if eq{$acl_c_mode}{submission}}
+ control = submission
+
+SECTION(acl, mail-tail)m4_dnl
+ ## And we're done.
+ accept
+
+SECTION(global, acl)m4_dnl
+acl_smtp_connect = connect
+SECTION(acl, connect)m4_dnl
+connect:
+SECTION(acl, connect-tail)m4_dnl
+ warn acl = check_submission
+ accept
+
+check_submission:
+ ## See whether this message needs hacking on.
+ accept !hosts = +localnet
+ !condition = ${if ={$received_port}{CONF_submission_port}}
+ set acl_c_mode = relay
+
+ ## Remember to apply submission controls.
+ warn set acl_c_mode = submission
+
+ ## Done.
+ accept
+
+SECTION(global, acl)m4_dnl
+acl_smtp_rcpt = rcpt
+SECTION(acl, rcpt)m4_dnl
+rcpt:
+
+ ## Reject if the client isn't allowed to relay and the recipient
+ ## isn't in one of our known domains.
+ deny message = Relaying not permitted
+ !hosts = CONF_relay_clients
+ !authenticated = *
+ !domains = +known
+
+ ## Ensure that the recipient is routable.
+ require message = Invalid recipient \
+ ($recipient_verify_failure; $acl_verify_message)
+ verify = recipient
+
+SECTION(acl, rcpt-tail)m4_dnl
+ ## Everything checks out OK: let this one go through.
+ accept
+
+SECTION(global, acl)m4_dnl
+acl_smtp_data = data
+SECTION(acl, data)m4_dnl
+data:
+
+SECTION(acl, data-tail)m4_dnl
+ accept
+
+SECTION(global, acl)m4_dnl
+acl_smtp_expn = expn_vrfy
+acl_smtp_vrfy = expn_vrfy
+SECTION(acl)m4_dnl
+expn_vrfy:
+ accept hosts = +trusted
+ deny message = Suck it and see
+
+DIVERT(null)
+###--------------------------------------------------------------------------
+### Common options for forwarding routers.
+
+## We're pretty permissive here.
+m4_define(<:FILTER_BASE:>,
+ <:driver = redirect
+ modemask = 002
+ check_owner = false
+ check_group = false
+ allow_filter = true
+ allow_defer = true
+ allow_fail = true
+ forbid_blackhole = false
+ check_ancestor = true:>)
+
+## Common options for forwarding routers at verification time.
+m4_define(<:FILTER_VERIFY:>,
+ <:verify_only = true
+ user = CONF_filter_user
+ forbid_filter_dlfunc = true
+ forbid_filter_logwrite = true
+ forbid_filter_perl = true
+ forbid_filter_readsocket = true
+ forbid_filter_run = true
+ file_transport = dummy
+ directory_transport = dummy
+ pipe_transport = dummy
+ reply_transport = dummy:>)
+
+## Transports for redirection filters.
+m4_define(<:FILTER_TRANSPORTS:>,
+ <:file_transport = mailbox
+ directory_transport = maildir
+ pipe_transport = pipe
+ reply_transport = reply:>)
+
+DIVERT(null)
+###--------------------------------------------------------------------------
+### Some standard transports.
+
+m4_define(<:USER_DELIVERY:>,
+ <:delivery_date_add = true
+ envelope_to_add = true
+ return_path_add = true:>)
+
+SECTION(transports)m4_dnl
+## A standard transport for remote delivery. Try to do TLS, and don't worry
+## too much if it's not very secure: the alternative is sending in plaintext
+## anyway.
+smtp:
+ driver = smtp
+ tls_require_ciphers = CONF_acceptable_ciphers
+ tls_dh_min_bits = 1020
+ tls_tempfail_tryclear = true
+
+## Transport to a local SMTP server; use TLS and perform client
+## authentication.
+smtp_local:
+ driver = smtp
+ hosts_require_tls = *
+ tls_certificate = CONF_sysconf_dir/client.cert
+ tls_privatekey = CONF_sysconf_dir/client.key
+ tls_verify_certificates = CONF_ca_dir/ca.cert
+ tls_require_ciphers = CONF_good_ciphers
+ tls_dh_min_bits = 3070
+ tls_tempfail_tryclear = false
+ authenticated_sender = ${if def:authenticated_id \
+ ${authenticated_id@CONF_master_domain} \
+ fail}
+
+## A standard transport for local delivery.
+deliver:
+ driver = appendfile
+ file = /var/mail/$local_part
+ USER_DELIVERY
+
+## Transports for user filters.
+mailbox:
+ driver = appendfile
+ USER_DELIVERY
+
+maildir:
+ driver = appendfile
+ maildir_format = true
+ USER_DELIVERY
+
+pipe:
+ driver = pipe
+ return_output = true
+
+## A special dummy transport for use during address verification.
+dummy:
+ driver = appendfile
+ file = /dev/null
+
+DIVERT(null)
+###--------------------------------------------------------------------------
+### Retry configuration.
+
+SECTION(retry, default)m4_dnl
+## Default.
+* * \
+ F,2h,15m; G,16h,2h,1.5; F,4d,6h
+
+DIVERT(null)
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+### -*-m4-*-
+###
+### Basic configuration settings for distorted.org.uk Exim configuration
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+## Master domain name.
+DEFCONF(master_domain, distorted.org.uk)
+
+## The smarthost for satellite hosts.
+DEFCONF(smarthost, mail.distorted.org.uk)
+
+## The user who runs verification filters.
+DEFCONF(filter_user, Debian-exim)
+
+## Where the spam filter is.
+DEFCONF(spamd_address, 172.29.199.179)
+DEFCONF(spamd_port, 783)
+
+## Default spam limit for incoming mail (multiplied by ten).
+DEFCONF(spam_max, 50)
+
+## Which interfaces to listen on. Exim checks for the literal string `::0'
+## when setting things up: don't use `::', or we'll be tripped up by Linux's
+## demented non-`IPV6_V6ONLY' behaviour.
+DEFCONF(interfaces, m4_ifelse(MODE, satellite, 127.0.0.1 ; ::1,
+ 0.0.0.0 ; ::0))
+
+## Submission port number. (This is sometimes tweaked for testing.)
+DEFCONF(submission_port, 587)
+
+## Locations of other configuration files.
+DEFCONF(sysconf_dir, /etc/mail)
+DEFCONF(userconf_dir, $home/.mail)
+DEFCONF(alias_file, /etc/aliases)
+DEFCONF(ca_dir, /etc/ca)
+
+## User address suffix handling.
+DEFCONF(user_suffix_list, -* : +*)
+DEFCONF(user_extaddr_regexp, $acl_c_user([-+@]|\$))
+DEFCONF(user_extaddr_fixup, ${sg {$local_part_suffix}{^[-+]}{}})
+
+## Other hosts allowed to relay mail through us.
+DEFCONF(relay_clients, +trusted)
+
+## TLS-related settings. We're assuming GNUTLS here, rather than OpenSSL.
+## For local connections we are very strict. For random clients, we try
+## fairly hard to encourage any kind of crypto on the grounds that probably
+## nobody can verify our certificate anyway.
+DEFCONF(good_ciphers, NONE<::>m4_dnl
+:+VERS-TLS1.2:+VERS-TLS1.1<::>m4_dnl
+:+DHE-RSA:+DHE-DSS<::>m4_dnl
+:+AES-256-CBC:+AES-128-CBC<::>m4_dnl
+:+SHA256<::>m4_dnl
+:+SIGN-RSA-SHA512:+SIGN-RSA-SHA384:+SIGN-RSA-SHA256:+SIGN-DSA-SHA256<::>m4_dnl
+:+CTYPE-X.509<::>m4_dnl
+:+COMP-NULL<::>m4_dnl
+)
+DEFCONF(acceptable_ciphers, NORMAL<::>m4_dnl
+:-MD5<::>m4_dnl
+)
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+m4_divert(-1)m4_dnl ### -*-m4-*-
+###
+### Basic definitions for distorted.org.uk Exim configuration
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+m4_changequote(<:, :>)
+m4_changecom(<:#:#:>)
+
+###--------------------------------------------------------------------------
+### Output file preamble and postamble.
+
+m4_divert(0)m4_dnl
+### -*-conf-*- GENERATED FROM /etc/mail/m4/*.m4: DO NOT EDIT!
+###
+### Exim configuration for distorted.org.uk.
+
+m4_divert(1000)m4_dnl
+### GENERATED FROM exim4.conf.m4: DO NOT EDIT!
+m4_divert(-1)
+
+###--------------------------------------------------------------------------
+### Useful macros.
+
+## ONEOF(arg, ...)
+##
+## Output the first of its arguments that is non-empty.
+m4_define(<:ONEOF:>, <:m4_ifelse(<:$#:>, <:1:>, <:$1:>,
+ <:$1:>, <::>, <:ONEOF(m4_shift($@)):>, <:$1:>):>)
+
+## DEFCONF(CONF, DEFAULT)
+##
+## Define config variable CONF, assigning it the DEFAULT value if not
+## overridden by `SETCONF'.
+m4_define(<:DEFCONF:>,
+<:m4_ifdef(<:CONF_$1:>, <::>,
+<:m4_define(<:CONF_$1:>, <:$2:>):>):>)
+
+## SETCONF(CONF, VALUE)
+##
+## Set config variable VALUE.
+m4_define(<:SETCONF:>, <:m4_define(<:CONF_$1:>, <:$2:>):>)
+
+## FOREACH(what, list)
+##
+## The LIST is a comma-separated list of things, like an m4 argument list.
+## For each item in the list, expand WHAT as if it's the body of a macro with
+## the list item as its arguments. In other words, the list item itself can
+## be a list of comma-separated items, which are available as $1, $2, ...,
+## within WHAT.
+m4_define(<:_FOREACH:>, <:m4_dnl
+m4_ifelse(<:$#:>, <:1:>, <:_foreach_func($1):>,
+ <:_foreach_func($1)<::>_FOREACH(m4_shift($@)):>):>)
+m4_define(<:FOREACH:>, <:m4_dnl
+m4_pushdef(<:_foreach_func:>, <:$1:>)m4_dnl
+_FOREACH($2)<::>m4_dnl
+m4_popdef(<:_foreach_func:>):>)
+
+m4_define(<:DIVERT:>, <:m4_dnl
+m4_divert(m4_indir(<:_div:$1:>))m4_dnl
+:>)
+
+m4_define(<:SECTION:>, <:m4_dnl
+DIVERT(<:$1:>)m4_dnl
+m4_ifdef(<:_done:$1:>, <::>, <:m4_dnl
+###--------------------------------------------------------------------------
+m4_ifdef(<:_head:$1:>, <:m4_indir(<:_head:$1:>):>, <:begin $1:>)
+
+m4_define(<:_done:$1:>, <:1:>):>)m4_dnl
+m4_ifelse(<:$2:>, <::>, <::>, <:m4_dnl
+DIVERT(<:$1/$2:>)m4_dnl
+m4_ifdef(<:_done:$1/$2:>, <::>, <:m4_dnl
+m4_ifdef(<:_head:$1/$2:>, <:<:##:> m4_indir(<:_head:$1/$2:>)
+:>)m4_define(<:_done:$1/$2:>):>):>):>)
+
+m4_define(<:LOOKUP_DOMAIN:>,
+ <:if exists{CONF_sysconf_dir/domains.conf} \
+ {${lookup {$1}partial0-lsearch{CONF_sysconf_dir/domains.conf} \
+ m4_ifelse(<:$2$3:>, <::>, <::>,
+ <:$2:>, <::>, <:{$value}$3:>,
+ <:$2$3:>)}} \
+ $3:>)
+
+m4_define(<:KV:>, <:${extract {$1}{$value}$2}:>)
+m4_define(<:DOMKV:>, <:${extract {$1}{$domain_data}$2}:>)
+
+m4_divert(999)m4_dnl
+###----- That's all, folks --------------------------------------------------
+m4_divert(-1)
--- /dev/null
+### -*-m4-*-
+###
+### Diversion map for distorted.org.uk Exim configuration.
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### Numbering machinery.
+
+## Rather than maintain the diversion numbers by hand, this chunk of Emacs
+## Lisp will recompute them when they've been edited. All you have to do is
+## declare the diversions in the correct order.
+(save-excursion
+ (save-match-data
+ (goto-char (point-min))
+ (let ((next-div 0) div)
+ (while (re-search-forward (concat "\\(DEFDIVERSION(\\)" ;1
+ "\\([^,]+\\)" ;2 = diversion
+ "\\(,\\s-*\\)" ;3
+ "\\([0-9]+\\)" ;4 = number
+ "\\([,)]\\)") ;5
+ nil t)
+ (setq div (match-string 2))
+ (if (not (string-match-p "/" div))
+ (setq next-div (* 100 (/ (+ next-div 100) 100))))
+ (replace-match (concat "\\1\\2\\3"
+ (int-to-string next-div)
+ "\\5"))
+ (setq next-div (+ next-div 2))))))
+
+m4_define(<:_div:null:>, <:-1:>)
+
+m4_define(<:DEFDIVERSION:>, <:m4_dnl
+ m4_define(<:_div:$1:>, <:$2:>)m4_dnl
+ m4_ifelse(<:$3:>, <::>, <::>, <:m4_dnl
+ m4_define(<:_head:$1:>, <:$3:>):>):>)
+
+###--------------------------------------------------------------------------
+### The diversion map.
+
+## header 0
+
+DEFDIVERSION(global, 100, ### Global settings.)
+DEFDIVERSION(global/logging, 102, Logging.)
+
+DEFDIVERSION(global/lists, 104)
+
+DEFDIVERSION(global/misc, 106, Miscellaneous.)
+DEFDIVERSION(global/param, 108, Exim parameters.)
+DEFDIVERSION(global/priv, 110, Privilege controls.)
+DEFDIVERSION(global/frozen, 112, Frozen messages.)
+DEFDIVERSION(global/lookups, 114, Data lookups.)
+DEFDIVERSION(global/msgid, 116, Message ids.)
+DEFDIVERSION(global/perl, 118, Embedded Perl startup.)
+DEFDIVERSION(global/daemon, 120, Daemon.)
+DEFDIVERSION(global/resource, 122, Resource control.)
+DEFDIVERSION(global/policy, 124, Policy controls.)
+DEFDIVERSION(global/callout, 126, Callout cache.)
+DEFDIVERSION(global/tls, 128, TLS.)
+DEFDIVERSION(global/users, 130, Local user handling.)
+DEFDIVERSION(global/incoming, 132,
+ All incoming messages (SMTP and non-SMTP).)
+DEFDIVERSION(global/non-smtp, 134, Non-SMTP incoming messages.)
+DEFDIVERSION(global/smtp, 136, Incoming SMTP messages.)
+DEFDIVERSION(global/process, 138, Processing messages.)
+DEFDIVERSION(global/filter, 140, System filter.)
+DEFDIVERSION(global/routing, 142, Routing and delivery.)
+DEFDIVERSION(global/bounce, 144, Bounce and warning messages.)
+DEFDIVERSION(global/acl, 146, Access control lists.)
+DEFDIVERSION(global/acl-after, 148)
+
+DEFDIVERSION(acl, 200)
+DEFDIVERSION(acl/connect, 202)
+DEFDIVERSION(acl/connect-hooks, 204)
+DEFDIVERSION(acl/connect-tail, 206)
+DEFDIVERSION(acl/mail, 208)
+DEFDIVERSION(acl/mail-hooks, 210)
+DEFDIVERSION(acl/mail-tail, 212)
+DEFDIVERSION(acl/rcpt, 214)
+DEFDIVERSION(acl/rcpt-hooks, 216)
+DEFDIVERSION(acl/rcpt-tail, 218)
+DEFDIVERSION(acl/data, 220)
+DEFDIVERSION(acl/data-spam, 222)
+DEFDIVERSION(acl/data-tail, 224)
+DEFDIVERSION(acl/misc, 226)
+
+DEFDIVERSION(auth, 300, begin authenticators)
+
+DEFDIVERSION(routers, 400)
+DEFDIVERSION(routers/route, 402)
+DEFDIVERSION(routers/remote, 404)
+DEFDIVERSION(routers/virtual, 406)
+DEFDIVERSION(routers/real, 408)
+DEFDIVERSION(routers/alias, 410)
+DEFDIVERSION(routers/allspam, 412)
+DEFDIVERSION(routers/dispatch, 414)
+DEFDIVERSION(routers/forward, 416)
+DEFDIVERSION(routers/deliver, 418)
+
+DEFDIVERSION(transports, 500)
+
+DEFDIVERSION(retry, 600)
+DEFDIVERSION(retry/misc, 602)
+DEFDIVERSION(retry/default, 604)
+
+DEFDIVERSION(rewrite, 700)
+
+## warning trailer 1000
+
+DIVERT(null)
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+### -*-m4-*-
+###
+### Transmission to remote hosts for distorted.org.uk Exim configuration.
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### Listen for incoming connections.
+
+SECTION(global, tls)m4_dnl
+tls_certificate = CONF_sysconf_dir/server.cert
+tls_privatekey = CONF_sysconf_dir/server.key
+tls_advertise_hosts = *
+tls_dhparam = CONF_ca_dir/dh-param.pem
+tls_require_ciphers = ${if or {{={$received_port}{CONF_submission_port}} \
+ {match_ip {$sender_host_address}{+trusted}}} \
+ {CONF_good_ciphers} \
+ {CONF_acceptable_ciphers}}
+tls_verify_certificates = CONF_ca_dir/ca.cert
+tls_verify_hosts = ${if eq{$acl_c_mode}{submission} {} {+allnets}}
+
+DIVERT(null)
+###--------------------------------------------------------------------------
+### Check source addresses for apparently local senders.
+
+SECTION(acl, mail-hooks)m4_dnl
+ ## Check that a submitted message's sender address is allowable.
+ require acl = mail_client_addr
+
+ ## Insist that a local client connect through TLS.
+ deny message = Hosts within CONF_master_domain must use TLS
+ !condition = ${if eq{$acl_c_mode}{submission}}
+ hosts = +allnets
+ !encrypted = *
+
+SECTION(acl, misc)m4_dnl
+mail_client_addr:
+
+ ## If this is a message submission then that's handled elsewhere.
+ accept condition = ${if eq{$acl_c_mode}{submission}}
+
+ ## Make sure that the sender matches the client address.
+ require message = Client host invalid for sender domain
+ hosts = ${LOOKUP_DOMAIN($sender_address_domain,
+ {KV(hosts, {$value}{+allnets})},
+ {${if match_domain {$sender_address_domain} \
+ {+public} \
+ {+allnets}{! +allnets}}})}
+
+ ## OK.
+ accept
+
+DIVERT(null)
+###--------------------------------------------------------------------------
+### The obvious trivial router.
+
+SECTION(routers, remote)m4_dnl
+## Send mail on to a host in our own network. We must apply extra security.
+local:
+ driver = dnslookup
+ domains = ! +known : *.CONF_master_domain
+ self = fail
+ transport = smtp_local
+ no_more
+
+## Send mail on to unknown hosts.
+remote:
+ driver = dnslookup
+ domains = ! +known
+ self = fail
+ transport = smtp
+ no_more
+
+DIVERT(null)
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+### -*-m4-*-
+###
+### Lists of addresses and suchlike for distorted.org.uk Exim configuration
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+SECTION(global, lists)m4_dnl
+## Definitions for known networks.
+hostlist localnet = <; \
+ 127.0.0.0/8 ; ::1
+hostlist border = <; \
+ 62.49.204.144/28 ; 2001:470:1f09:1b98::/64 ; \
+ 212.13.198.64/28 ; 2001:ba8:0:1d9::/64
+hostlist trusted = <; \
+ +localnet ; +border ; \
+ 172.29.199.0/24 ; 2001:ba8:1d9::/49 ; 2001:470:9740::/49
+hostlist allnets = <; \
+ +localnet ; +border ; \
+ 172.29.198.0/23 ; 2001:ba8:1d9::/48 ; 2001:470:9740::/48
+
+## Domains we're authoritative for.
+domainlist thishost = @ : @[] : \
+ ${map {${extract {${extract {1}{.}{$primary_hostname}}} \
+ { telecaster=tele \
+ stratocaster=strat }}} \
+ {$item.$qualify_domain}}
+domainlist public = +thishost : distorted.org.uk
+domainlist known = +public : \
+ ${if exists{CONF_sysconf_dir/domains.conf} \
+ {partial0-lsearch; CONF_sysconf_dir/domains.conf} \
+ {}}
+
+## Some magic lists used because `match_local_parts' and friends don't expand
+## their right-hand sides.
+localpartlist dom_users = ${expand:KV(users, {$value}{*})}
+localpartlist dom_locals = ${expand:KV(locals, {$value}{+user_extaddr})}
+localpartlist user_extaddr = ^CONF_user_extaddr_regexp
+
+DIVERT(null)
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+### -*-m4-*-
+###
+### Local senders and recipients for distorted.org.uk Exim configuration
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+m4_define(<:FILTER_LOCALUSER:>,
+ <:FILTER_BASE
+ check_local_user
+ ignore_enotdir
+ sieve_useraddress = $local_part
+ sieve_subaddress = CONF_user_extaddr_fixup
+ sieve_vacation_directory = CONF_userconf_dir/vacation
+ condition = ${if exists {<:$1:>}}
+ file = <:$1:>:>)
+
+###--------------------------------------------------------------------------
+### Be flexible about originator addresses, as long as they probably work.
+
+SECTION(global, priv)m4_dnl
+local_from_suffix = CONF_user_suffix_list
+
+SECTION(global, process)m4_dnl
+## Restrict users to plausible envelope sender addresses. This is
+## surprisingly fiddly. What I actually want to say is that the local part
+## must match ^$sender_ident(\$|-) and the domain part must match an
+## appropriate domain; but writing a conjunction is rather tricky. And so we
+## must burn some addresslist variables.
+addresslist wrong_local = ! +user_extaddr
+addresslist wrong_domain = ! *@+public
+addresslist wrong_address = +wrong_local : +wrong_domain
+untrusted_set_sender = : \
+ ${LOOKUP_DOMAIN($sender_address_domain,
+ {${if and {{match_local_part {$sender_ident} {+dom_users}} \
+ {match_local_part {$sender_address_local_part} \
+ {+dom_locals}}} \
+ {*}}})} : \
+ ! +wrong_address
+
+###--------------------------------------------------------------------------
+### Forwarding and redirection for incoming mail.
+
+SECTION(routers, alias)m4_dnl
+## Look up the local part in the address map.
+alias:
+ driver = redirect
+ allow_fail = true
+ allow_defer = true
+ user = CONF_filter_user
+ FILTER_TRANSPORTS
+ data = ${lookup {$local_part} lsearch {CONF_alias_file}}
+
+SECTION(routers, real)m4_dnl
+## A special hack to get mail to a user who has a forward file. Only for
+## special effects.
+real:
+ driver = accept
+ check_local_user
+ local_part_prefix = real-
+ transport = deliver
+ condition = ${if match_ip {$sender_host_address} \
+ {<; ; 127.0.0.1 ; ::1}}
+
+SECTION(routers, forward)m4_dnl
+## Handle user forward files. Each user is granted an arbitrary number of
+## additional mailboxes named USER-SUFFIX. Such addresses are handled by a
+## filter file `~/.mail/forward.suffix' in the USER's home directory. The
+## filter may reject the incoming message (which is reported as an SMTP
+## rejection if possible). Mail sent directly to the user is handled through
+## `~/.mail/forward', or `~/.forward', or if neither of those exists, by
+## writing the message to `/var/mail/USER'.
+filter_verify:
+ FILTER_LOCALUSER(CONF_userconf_dir/forward.suffix)
+ FILTER_VERIFY
+ local_part_suffix = CONF_user_suffix_list
+filter_suffix:
+ FILTER_LOCALUSER(CONF_userconf_dir/forward.suffix)
+ local_part_suffix = CONF_user_suffix_list
+ FILTER_TRANSPORTS
+filter:
+ FILTER_LOCALUSER(CONF_userconf_dir/forward)
+ FILTER_TRANSPORTS
+dot_forward:
+ FILTER_LOCALUSER($home/.forward)
+ FILTER_TRANSPORTS
+
+SECTION(routers, deliver)m4_dnl
+## Deliver mail to a user, in the absence of any other instructions.
+deliver:
+ driver = accept
+ check_local_user
+ transport = deliver
+ cannot_route_message = Unknown local part
+
+DIVERT(null)
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+### -*-m4-*-
+###
+### Satellite hosts for distorted.org.uk Exim configuration
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+SECTION(acl, connect-hooks)m4_dnl
+ ## Check the caller.
+ deny message = This is not a public mail server
+ hosts = ! +allnets
+
+SECTION(routers, remote)m4_dnl
+satellite:
+ driver = manualroute
+ transport = smtp_local
+ domains = ! +thishost
+ host_find_failed = defer
+ same_domain_copy_routing = true
+ route_list = <! * CONF_smarthost byname
+
+DIVERT(null)
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+### -*-m4-*-
+###
+### Spam filtering for distorted.org.uk Exim configuration
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+DIVERT(null)
+###--------------------------------------------------------------------------
+### Spam filtering.
+
+SECTION(global, policy)m4_dnl
+spamd_address = CONF_spamd_address CONF_spamd_port
+
+SECTION(routers, allspam)m4_dnl
+## If we're verifying an address and the recipient has a `~/.mail/spam-limit'
+## file, then look up the recipient and sender addresses to find a plausible
+## limit and insert it into the `address_data' where the RCPT ACL can find
+## it. This router always declines, so it doesn't affect the overall outcome
+## of the verification.
+fetch_spam_limit:
+ driver = redirect
+ data = :unknown:
+ verify_only = true
+ local_part_suffix = CONF_user_suffix_list
+ local_part_suffix_optional = true
+ check_local_user
+ address_data = \
+ ${if def:address_data {$address_data}{}} \
+ ${if exists {CONF_userconf_dir/spam-limit} \
+ {${lookup {$local_part_prefix\
+ $local_part\
+ $local_part_suffix\
+ @$domain/\
+ $sender_address} \
+ nwildlsearch {CONF_userconf_dir/spam-limit} \
+ {spam_limit=$value} \
+ {}}} \
+ {}} \
+ ${if exists {CONF_userconf_dir/spam-limit.userv} \
+ {${run {timeout 5s -- \
+ userv $local_part exim-spam-limit \
+ $sender_address \
+ $local_part_prefix \
+ $local_part \
+ $local_part_suffix \
+ @$domain} \
+ {${if match{$value}{\N^[0-9]+$\N} \
+ {spam_limit=$value} \
+ {}}} \
+ {}}} \
+ {}}
+
+SECTION(acl, rcpt-hooks)m4_dnl
+ ## Do per-recipient spam-filter processing.
+ require acl = rcpt_spam
+
+SECTION(acl, misc)m4_dnl
+rcpt_spam:
+
+ ## If the client is trusted, don't bother with any of this.
+ accept hosts = +trusted
+
+ ## Collect the user's spam threshold from the `address_data'
+ ## variable, where it was left by the `fetch_spam_limit' router
+ ## during recipient verification. (This just saves duplicating this
+ ## enormous expression.)
+ warn set acl_m_this_spam_limit = \
+ ${sg {${extract {spam_limit} \
+ {${if def:address_data \
+ {$address_data}{}}} \
+ {$value}{nil}}} \
+ {^(|.*\\D.*)\$}{CONF_spam_max}}
+
+ ## If there's a spam limit already established, and it's different
+ ## from this user's limit, then the sender will have to try this user
+ ## again later.
+ defer !hosts = +trusted
+ message = "You'd better try this one later"
+ condition = ${if def:acl_m_spam_limit {true}{false}}
+ condition = ${if ={$acl_m_spam_limit} \
+ {$acl_m_this_spam_limit} \
+ {false}{true}}
+
+ ## There's no limit set yet, or the user's limit is the same as the
+ ## existing one, or the client's local and we're not checking for
+ ## spam anyway. Whichever way, it's safe to set it now.
+ warn set acl_m_spam_limit = $acl_m_this_spam_limit
+
+ ## All done.
+ accept
+
+SECTION(acl, data-spam)m4_dnl
+ ## Do spam checking.
+ require acl = data_spam
+
+SECTION(acl, misc)m4_dnl
+data_spam:
+
+ ## If the client is trusted, don't bother with any of this.
+ accept hosts = +trusted
+
+ ## Check the message for spam, comparing to the configured limit.
+ deny spam = exim:true
+ message = Tinned meat product detected ($spam_score)
+ condition = ${if >{$spam_score_int}{$acl_m_spam_limit} \
+ {true}{false}}
+
+ ## Insert headers from the spam check now that we've decided to
+ ## accept the message.
+ warn
+ ## Convert the limit (currently 10x fixed point) into a
+ ## decimal for presentation.
+ set acl_m_spam_limit_presentation = \
+ ${sg{$acl_m_spam_limit}{\N(\d)$\N}{.\$1}}
+
+ ## Convert the report into something less obnoxious. Plain
+ ## old SpamAssassin has an `X-Spam-Status' header which
+ ## lists the matched rules and provides some other basic
+ ## information. Try to extract something similar from the
+ ## report.
+ ##
+ ## This is rather fiddly.
+
+ ## Firstly, escape angle brackets, because we'll be using
+ ## them for our own purposes.
+ set acl_m_spam_tests = ${sg{$spam_report}{([!<>])}{!\$1}}
+
+ ## Trim off the blurb paragraph and the preview. The rest
+ ## should be fairly well behaved. Wrap double angle-
+ ## brackets around the remainder; these can't appear in the
+ ## body because we escaped them all earlier.
+ set acl_m_spam_tests = \
+ ${sg{$acl_m_spam_tests} \
+ {\N^(?s).*\n Content analysis details:(.*)$\N} \
+ {<<\$1>>}}
+
+ ## Extract the information about the matching rules and
+ ## their scores. Leave `<<...>>' around everything else.
+ set acl_m_spam_tests = \
+ ${sg{$acl_m_spam_tests} \
+ {\N(?s)\n\s*([\d.]+)\s+([-\w]+)\s\N} \
+ {>>\$2:\$1,<<}}
+
+ ## Strip everything still in `<<...>>' pairs, including any
+ ## escaped characters inside.
+ set acl_m_spam_tests = \
+ ${sg{$acl_m_spam_tests}{\N(?s)<<([^!>]+|!.)*>>\N}{}}
+
+ ## Trim off a trailing comma.
+ set acl_m_spam_tests = ${sg{$acl_m_spam_tests}{,\s*\$}{}}
+
+ ## Undo the escaping.
+ set acl_m_spam_tests = ${sg{$acl_m_spam_tests}{!(.)}{\$1}}
+
+ ## Insert the headers.
+ add_header = X-SpamAssassin-Score: \
+ $spam_score/$acl_m_spam_limit_presentation \
+ ($spam_bar)
+ add_header = X-SpamAssassin-Status: \
+ score=$spam_score, \
+ limit=$acl_m_spam_limit_presentation, \n\t\
+ tests=$acl_m_spam_tests
+
+
+ ## We're good.
+ accept
+
+DIVERT(null)
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+### -*-m4-*-
+###
+### Virtual host support for distorted.org.uk Exim configuration
+###
+### (c) 2012 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+m4_define(<:VHOST:>,
+ <:domains = partial0-lsearch; CONF_sysconf_dir/domains.conf
+ condition = DOMKV($1):>)
+m4_define(<:VHOST_FILTER:>,
+ <:FILTER_BASE
+ VHOST(<:$1:>)
+ $2 = ${expand:DOMKV($1, {$value} fail)}:>)
+m4_define(<:VHOST_USER:>,
+ <:user = DOMKV(owner, {$value}{CONF_filter_user})
+ errors_to = DOMKV(errors_to, {$value} fail):>)
+
+###--------------------------------------------------------------------------
+### Routers and transports for virtual hosting.
+
+SECTION(routers, route)m4_dnl
+## If we're a front-end for some other domain, or we have special information
+## about the domain, then pass stuff along as instructed.
+route:
+ driver = manualroute
+ self = fail
+ VHOST(route)
+ route_data = DOMKV(route, {$value} fail)
+ same_domain_copy_routing = true
+ transport = smtp
+ no_more
+
+SECTION(routers, virtual)m4_dnl
+## Remap recipients according to the virtual host's instructions. This must
+## be done in two passes, so that we can identify the correct user's spam
+## threshold during address verification.
+virtual_verify_data:
+ VHOST_FILTER(redirect, data)
+ FILTER_VERIFY
+virtual_verify_file:
+ VHOST_FILTER(filter, file)
+ FILTER_VERIFY
+
+virtual_filter_data:
+ VHOST_FILTER(redirect, data)
+ VHOST_USER
+virtual_filter_file:
+ VHOST_FILTER(filter, file)
+ VHOST_USER
+
+DIVERT(null)
+###----- That's all, folks --------------------------------------------------