Initial commit.
authorMark Wooding <mdw@distorted.org.uk>
Sun, 29 Dec 2013 19:39:09 +0000 (19:39 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 29 Dec 2013 19:39:09 +0000 (19:39 +0000)
14 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
auth.m4 [new file with mode: 0644]
base.m4 [new file with mode: 0644]
config.m4 [new file with mode: 0644]
defs.m4 [new file with mode: 0644]
divmap.m4 [new file with mode: 0644]
exchange.m4 [new file with mode: 0644]
lists.m4 [new file with mode: 0644]
local.m4 [new file with mode: 0644]
satellite.m4 [new file with mode: 0644]
spam.m4 [new file with mode: 0644]
vhost.m4 [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..d2c7cb1
--- /dev/null
@@ -0,0 +1,3 @@
+*.conf
+hacks.m4
+local.mk
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..a88c324
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,86 @@
+### -*-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
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..25149fd
--- /dev/null
+++ b/README
@@ -0,0 +1,188 @@
+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:
diff --git a/auth.m4 b/auth.m4
new file mode 100644 (file)
index 0000000..d4729c0
--- /dev/null
+++ b/auth.m4
@@ -0,0 +1,132 @@
+### -*-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 --------------------------------------------------
diff --git a/base.m4 b/base.m4
new file mode 100644 (file)
index 0000000..887a132
--- /dev/null
+++ b/base.m4
@@ -0,0 +1,289 @@
+### -*-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 --------------------------------------------------
diff --git a/config.m4 b/config.m4
new file mode 100644 (file)
index 0000000..5901edc
--- /dev/null
+++ b/config.m4
@@ -0,0 +1,80 @@
+### -*-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 --------------------------------------------------
diff --git a/defs.m4 b/defs.m4
new file mode 100644 (file)
index 0000000..f9b3333
--- /dev/null
+++ b/defs.m4
@@ -0,0 +1,106 @@
+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)
diff --git a/divmap.m4 b/divmap.m4
new file mode 100644 (file)
index 0000000..c7ed1d7
--- /dev/null
+++ b/divmap.m4
@@ -0,0 +1,128 @@
+### -*-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 --------------------------------------------------
diff --git a/exchange.m4 b/exchange.m4
new file mode 100644 (file)
index 0000000..08fe4a0
--- /dev/null
@@ -0,0 +1,92 @@
+### -*-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 --------------------------------------------------
diff --git a/lists.m4 b/lists.m4
new file mode 100644 (file)
index 0000000..85d48c2
--- /dev/null
+++ b/lists.m4
@@ -0,0 +1,57 @@
+### -*-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 --------------------------------------------------
diff --git a/local.m4 b/local.m4
new file mode 100644 (file)
index 0000000..78b3e64
--- /dev/null
+++ b/local.m4
@@ -0,0 +1,113 @@
+### -*-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 --------------------------------------------------
diff --git a/satellite.m4 b/satellite.m4
new file mode 100644 (file)
index 0000000..088dded
--- /dev/null
@@ -0,0 +1,39 @@
+### -*-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 --------------------------------------------------
diff --git a/spam.m4 b/spam.m4
new file mode 100644 (file)
index 0000000..705fbee
--- /dev/null
+++ b/spam.m4
@@ -0,0 +1,186 @@
+### -*-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 --------------------------------------------------
diff --git a/vhost.m4 b/vhost.m4
new file mode 100644 (file)
index 0000000..07ca79e
--- /dev/null
+++ b/vhost.m4
@@ -0,0 +1,69 @@
+### -*-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 --------------------------------------------------