2. Exim
2. Exim
We're doing a vanilla port install, so there is nothing to do other than the usual make install. Unless, you want exim to use SASL authentication. In this case you will have to edit exims Makefile (/usr/ports/mail/exim/Makefile), search for WITH_SASLAUTHD= yes and uncomment that line. Then make install.
Note
Why use SASL? Your users will be able to use SMTP-Authentication with their IMAP/POP credentials (default for most mail clients, anyway, so less support calls for you!) and you won't have to deal with two parallel user/passwd instances. Please note, however, that saslauthd will not work with CRAM-MD5, so for those users you will still have to serve something else (see section below).
-
activate /usr/local/etc/rc.d/exim.sh (by renaming the .sh-dist)
-
edit /usr/local/etc/exim/configure:
-
edit domainlist local_domains to match your domains:
domainlist local_domains = tomster.org : cyrus-local-delivery.tomster.org
Note
you might need to set primary_hostname explicitly, i.e. to smtp.tomster.org or else you could get a truncated version of gethostname (i.e. ds80-237-202-128.dedicated.host) in headers or HELO commands, which some SMTP servers choke on (i.e. freebsd.org)
Note
the cyrus-local-delivery.tomster.org hostname is explained later.
-
you may want to allow certain networks to relay without authentication (i.e. localhost and/or local networks):
hostlist relay_from_hosts = 127.0.0.1 : 192.168.0.0/24
Note
For some applications (such as recent versions of squirrelmail) it may be necessary to use the address 127.0.0.1 rather than the hostname localhost as they may resolve that hostname to the IPV6 address [::1]. Of course, theoretically you could also include the V6 address in the hostlist, but I haven't tried that out...
Also, by default rfc1413 checking is activated. This means, that exim tries to verify sending hosts, which is normally a good thing. But if you've got an internal network these lookups could fail and thus cause a penalty time-delay. To avoid this use these directives instead of the default ones:
rfc1413_hosts = ! 192.168.0.0/24
rfc1413_query_timeout = 5s -
in the transports section replace the standard local_delivery transport with the following for cyrus:
local_delivery: driver = lmtp command =
"/usr/local/cyrus/bin/deliver -l" batch_max = 20 user =
cyrus -
Also, you must make the following in /etc/mail/mailer.conf:
sendmail /usr/exim/bin/exim
send-mail /usr/exim/bin/exim
mailq /usr/exim/bin/exim -bp
newaliases /usr/bin/true
-
The default mail.pem installed by the port usually will not match the hostname by which your clients access the server. Some mail clients will just display an initial alert (fine), some, such as older versions of Mozilla will complain after every startup (stupid), others such as Eudora or Outlook will refuse to use encryption (braindead). To change this, issue the following command as root and enter the hostname (i.e. smtp.tomster.org) for 'Common Name')
openssl req -new -x509 -nodes -out /var/cert/smtp-cert.pem -keyout /var/cert/smtp-key.pem -days 3650
Then, in /usr/local/etc/exim/configure (in the main configuration section) change the cert-references to your new certificate and key:
# tls configuration
tls_advertise_hosts = *
tls_certificate = /var/cert/smtp-cert.pem
tls_privatekey = /var/cert/smtp-key.pem
There are three methods of authentication that we support in our setup here: LOGIN, PLAIN and CRAM-MD5. All three authenticators are set up to look up credentials in a simple passwd-style file located in /usr/local/etc/exim/passwd in a username:passwd per line. Make sure, the file has the following permissions:
-rw-rw---- 1 mailnull wheel 13 Aug 12 22:27 /usr/local/etc/exim/passwd
Here are the authenticators:
begin authenticators
plain:
driver = plaintext
public_name = PLAIN
server_condition = "\
${if and {{!eq{$2}{}}{!eq{$3}{}} \
{eq{$3}{${extract{1}{:} \
{${lookup{$2}lsearch{/usr/local/etc/exim/passwd}{$value}{*:*}}}}}}}{1}{0}}"
server_set_id = $2
fixed_login:
driver = plaintext
public_name = LOGIN
server_prompts = UserName:: : Password::
server_condition = "\
${if and {{eq{$1}{USERNAME}}{eq{$2}{SECRET}}}{yes}{no}}"
server_set_id = $1
cram:
driver = cram_md5
public_name = CRAM-MD5
server_secret = "${if eq{$1}{$1} \
{${lookup{$1}lsearch{/usr/local/etc/exim/passwd}{$value}{*:*}}}fail}"
server_set_id = $1
Note
the fixed_login authenticator is currently not using the passwd file: I couldn't figure out the lookup syntax... So I just hardcoded the values into the configure file, as per the exim documentation.
If you want to use SASL for PLAIN and LOGIN use the following authenticators (it has the advantage, that now LOGIN will also use dynamic lookup instead of hardcoded credentials):
begin authenticators
fixed_login:
driver = plaintext
public_name = LOGIN
server_prompts = UserName:: : Password::
server_condition = ${if saslauthd{{$1}{$2}}{1}{0}}
server_set_id = $1
plain:
driver = plaintext
public_name = PLAIN
server_condition = ${if saslauthd{{$2}{$3}}{1}{0}}
server_set_id = $2
Matching recpient emailaddresses of an incoming email to an appropriate (Cyrus) user is done with the following directives using a 'virtusertable' similar to the one used with sendmail. The table consists of a textfile with a email-address-tab-boxname entry per line, including the @cyrus-local-delivery.tomster.org suffix:
tom@tomster.org tom_tomster_org@cyrus-local-delivery.tomster.org
[...]
#multiple recipients are okay:
info@domain.tld chef_domain_tld@cyrus-local-delivery.tomster.org, praktikant_domain_tld@cyrus-local-delivery.tomster.org
#as are, of course, external addresses
newsletter@somedomain.tld clueless_somedomain_tld@cyrus-local-delivery.tomster.org, somebody@otherdomain.com
Then, make the following the first(!) entry in the routers section:
# This router matches messages addressed to xxx@cyrus-local-delivery.tomster.org
# which are created by the address_to_mailbox router down below
cyrus_deliver:
driver = accept
domains = cyrus-local-delivery.tomster.org
transport = local_delivery_cyrus
Then further down (after any mailman routers!) i.e. at the end add the following router:
# this router matches username@domain.tld to mailbox_name@cyrus-local-delivery.tomster.org
# thus passing in effect the proper mailbox name to the cyrus_deliver router
address_to_mailbox:
driver = redirect
allow_fail
allow_defer
data = ${lookup{$local_part@$domain} lsearch*@ {/usr/local/etc/exim/virtusertable}}
user = mailnull
Then in the transporter section include this:
local_delivery_cyrus:
driver = pipe
command = "/usr/local/cyrus/bin/deliver $local_part"
group = mail
user = cyrus
return_output
log_output
message_prefix =
message_suffix =
So, what happens is this: a mail addressed to <tom@tomster.org> arrives and gets ignored by the first router (cyrus_deliver) because its domain is not cyrus-local-delivery.tomster.org. The second router however grabs it, looks up the mailbox name (tom_tomster_org) in the virtuser file and redirects to <tom_tomster_org@cyrus-local-delivery.tomster.org>. It thus reenters the process and this time round gets handled by the cyrus_deliver router and passed on to cyrus via the local_delivery_cyrus transport.
Note
If this seems a bit roundabout and unkosher to you: well, it does to me, too! It's just that I've not found a single how-to or other documentation on how to achieve this on the net. Even after getting a few answers to this issue after posting it on the official exim-user mailing list. I then posted the above solution asking whether that was an acceptable way of doing things - and never got a reply. I guess, "no news is good news!" ;-)
Anyway: the above solution has been working like a charm for a couple of weeks now on a production system...
Note
2004-02-15: One drawback of the abovementioned scenario is, that when people use e.g. Squirrelmail, their emails have a generic from: and reply-to: header username@host.tld i.e. info_hashtable_de@tomster.org.
If people answer to that, the reply does not reach its intended recipient. For this, we could either manually include the necessary matchings in the virtusertable, or we could write a little script, that polls cyrus for the list of current mailboxes and creates the file for us!
The following perl script does just that and in addition creates a file suitable for 'feeding' to Mailman: you can use the mailmanlist file to create or maintain a mailinglist that automatically contains all your mail users. Very handy for announcements such as scheduled downtimes etc.
(You will have to adjust permissions and group membership for user cyrus in order for the script to be able to create the mailmanlist file, though.)
#!/usr/bin/perl -w
use Cyrus::IMAP::Admin;
#
# CONFIGURATION PARAMS
#
my $cyrus_server = "localhost";
my $cyrus_user = "cyrus";
my $cyrus_pass = "XXXXX";
my $hostname = "tomster.org";
my $mechanism = "login";
my $mailmanlist = "mailmanlist";
my $genericmailbox = "/usr/local/etc/exim/generic-mailboxlist";
open(MMAN, ">$mailmanlist");
open(EXIM, ">$genericmailbox");
my @mailboxes;
my $cyrus = Cyrus::IMAP::Admin->new($cyrus_server);
$cyrus->authenticate($mechanism,'imap','',$cyrus_user,'0','10000',$cyrus_pass);
@mailboxes = $cyrus->list('%', 'user.');
foreach my $box (@mailboxes) {
$u = $box->[0];
$u =~ s/^user\.//;
print MMAN "$u\@$hostname\n";
print EXIM "$u\@$hostname\t$u\n";
}
close(MMAN);
close(EXIM);
In order for Exim to consider the file created above, you will have to add the following router before the address_to_mailbox router in /usr/local/exim/configure. (the sequence is important, because otherwise any 'catch-all' matching in virtusertable for your domain (i.e. tomster.org) would override any matchings in the generic-mailboxlist file.
# this router does the same as the one below, only it consults a script-generated file
# with generic matchings such as username@tomster.org -> username
# it is listed first, in order to enable the *@tomster.org entry in virtusertable used below
generic_to_mailbox:
driver = redirect
allow_fail
allow_defer
data = ${lookup{$local_part@$domain} lsearch*@ {/usr/local/etc/exim/generic-mailboxlist}}@cyrus-local-delivery.tomster.or\
g
user = mailnull
Worms have become quite the plague... installing a anti-virus gateway is not trivial. But luckily, a lot of malcontent can be filtered out with simple measures, directly in exim.
Update: On 2003-08-25 I added the following acl rules to the config file to deny Sobig-generated mails at smtp time:
#
# Sobig checks from http://www.enyo.de/fw/software/exim/sobig.html
#
check_helo:
# Accept locally generated mail.
accept hosts = :
# Accept only arguments with a ".".
accept condition = ${if match{$sender_helo_name}{\\.}{yes}{no}}
deny message = syntactically invalid argument
check_data:
deny condition = \
${if match{$message_body} \
{(Please s|S)ee the attached file for details} \
{yes}{no}}
condition = ${if >{$message_size}{98000}{yes}{no}}
condition = \
${if eq{$header_X-MailScanner:}{Found to be clean} \
{yes}{no}}
message = "Sobig virus detected"
accept
along with the follwowing two lines in the main section:
acl_smtp_data = check_data
acl_smtp_helo = check_helo
Note
I disabled the check_helo acl because lots of crappy outlook (express) clients send their windows name instead of proper hostname. duh!
After receiving the 20th fake "Microsoft Update"-patch with an .exe file in on morning I simply had enough! I inserted the two statements below in the check_data section, just before the accept keyword:
# block messages with certain extensions
deny message = contains $found_extension file (blacklisted).
demime = ade:adp:bas:bat:chm:cmd:com:cpl:crt:exe:hlp:hta:inf:ins:isp:js:jse:lnk:mdb:mde:msc:msi:msp:mst:pcd:pif:reg:sc\
r:sct:shs:shb:url:vb:vbe:vbs:wsc:wsf:wsh
Note
make sure to inform your users!
To keep an eye on what's going on you can watch the log files and/or invoke exim with special parameters from the command line (as root or mailnull).
tail -f /var/log/exim/mainlog
tail -f /var/log/exim/rejectlog
For further maintenance, I found the following parameters (out of the myriad of possible ones!) useful. Invoke exim with the following parameters (excerpts from man exim), i.e. exim -bp. Typically you will first print out the queue, look for the appropriate message and then use its ID with one of the other options (i.e. -Mrm)
-qff force all frozen messages to thaw
-bp print the mailqueue to stdout
-bpc This option counts the number of messages on the queue, and
writes the total to the standard output. It is restricted to
admin users, unless "queue_list_requires_admin" is set false.
-Mrm <message id> <message id> ...
This option requests Exim to remove the given messages from
the queue. No bounce messages are sent; each message is sim-
ply forgotten. However, if any of the messages are active,
their status is not altered. This option can be used only by
an admin user or by the user who originally caused the mes-
sage to be placed on the queue.
-Mvb <message id>
This option causes the contents of the message body (-D)
spool file to be written to the standard output. This option
can be used only by an admin user.
-Mvh <message id>
This option causes the contents of the message headers (-H)
spool file to be written to the standard output. This option
can be used only by an admin user.
Useful for debugging router / transports configuration:
exim -d -bt <email-address>
This will print out the path that an email with the given address would take, if it were delivered via smtp. This method is a) more informative and b) much less timeconsuming than actually sending millions of testmails to the server!
When making changes to configure it's a good idea to test the changes before actually reloading exim: exim -C /usr/local/etc/exim/configure -bV
To set up a vacation feature do the following (these instructions are a variation of what I found at afp548.com).
Add this to your router section, before the cyrus_deliver section:
user_vacation:
driver = accept
domains = cyrus-local-delivery.tomster.org
# do not reply to errors or lists
condition = "${if or {{match {$h_precedence:} {(?i)junk|bulk|list}} {eq {$sender_address} {}}} {no} {yes}}"
no_expn
require_files = /var/spool/imap/vacation/${local_part}/vacation.msg
# do not reply to errors and bounces or lists
senders = " ! ^.*-request@.*:\
! ^owner-.*@.*:\
! ^postmaster@.*:\
! ^listmaster@.*:\
! ^mailer-daemon@.*\
! ^root@.*"
transport = vacation_reply
unseen
no_verify
The difference to the afp548.com example is that we don't use the $local_part of the original email, but the local part of the mail redirected by address_to_mailbox (and thus, in effect, the cyrus user). We do this by removing their check_local_user bit and inserting the domains statement in line three. Also, I've adopted the path to the vacation message to the FreeBSD standards and removed the user= statement, as we don't deal with systemusers in this setup.
The transports section then is pretty straightforward, having nothing modified from the example except for the file paths and the contents of the reply message.
vacation_reply:
driver = autoreply
file = /var/spool/imap/vacation/$local_part/vacation.msg
file_expand
from = Autoreply System <$original_local_part@$original_domain>
log = /var/spool/imap/vacation/$local_part/vacation.log
once = /var/spool/imap/vacation/$local_part/vacation.db
once_repeat = 14d
subject = ${if def:h_Subject: {Re: ${quote:${escape:${length_50:$h_Subject:}}} (autoreply)} {I am on vacation} }
text = "\
This is an automatic reply. Feel free to send additional\n\
mail, as only this one notice will be generated. The following\n\
is a prerecorded message, sent for $original_local_part@$original_domain:\n\
====================================================\n\n\
"
to = "$sender_address"
Note
This setup with vacation messages residing in /var/spool/imap/vacation is perhaps not ideal. It requires to add the user mailnull to the group mail and one must ensure that this folder and its contents have got the right permissions after any manual changes (chown -R cyrus:mail /var/spool/imap/vacation/ and chmod -R 770 /var/spool/imap/vacation/).
As per request (this cookbook gets several visits a day, actually, whoha!) I've appended a full version of my /usr/local/etc/exim/configure (cleaned of all hostnames and client-details, of course)
primary_hostname = smtp.tomster.org domainlist local_domains = tomster.org domainlist relay_to_domains = hostlist relay_from_hosts = localhost # acces controls acl_smtp_rcpt = acl_check_rcpt acl_smtp_data = check_data #acl_smtp_data = check_message # helo check temp. disabled due to crappy Outlook cllents sending # invalid HELOs as well! #acl_smtp_helo = check_helo # qualify_domain = # qualify_recipient = # allow_domain_literals exim_user = mailnull exim_group = mail never_users = root host_lookup = * rfc1413_hosts = * rfc1413_query_timeout = 30s # sender_unqualified_hosts = # recipient_unqualified_hosts = # tls configuration tls_advertise_hosts = * tls_certificate = /var/cert/smtp.tomster.org.pem tls_privatekey = /var/cert/smtp.tomster.org-key.pem # # percent_hack_domains = ignore_bounce_errors_after = 2d timeout_frozen_after = 7d ### # Mailman ### # Home dir for your Mailman installation -- aka Mailman's prefix # directory. MAILMAN_HOME=/usr/local/mailman MAILMAN_WRAP=MAILMAN_HOME/mail/mailman # User and group for Mailman, should match your --with-mail-gid # switch to Mailman's configure script. MAILMAN_USER=mailman MAILMAN_GROUP=mail #MAILMAN_USER=mailnull #MAILMAN_GROUP=mailnull ###################################################################### # ACL CONFIGURATION # # Specifies access control lists for incoming SMTP mail # ###################################################################### begin acl acl_check_rcpt: accept hosts = : deny local_parts = ^.*[@%!/|] : ^\\. accept local_parts = postmaster domains = +local_domains # require verify = sender accept domains = +local_domains endpass message = unknown user verify = recipient accept domains = +relay_to_domains endpass message = unrouteable address verify = recipient accept hosts = +relay_from_hosts accept authenticated = * deny message = relay not permitted # # Sobig checks from http://www.enyo.de/fw/software/exim/sobig.html # check_helo: # Accept locally generated mail. accept hosts = : # Accept only arguments with a ".". accept condition = ${if match{$sender_helo_name}{\\.}{yes}{no}} deny message = syntactically invalid argument check_data: deny condition = \ ${if match{$message_body} \ {(Please s|S)ee the attached file for details} \ {yes}{no}} condition = \ ${if eq{$header_X-MailScanner:}{Found to be clean} \ {yes}{no}} message = "Sobig virus detected" # block messages with certain extensions deny message = contains $found_extension file (blacklisted). demime = ade:adp:bas:bat:chm:cmd:com:cpl:crt:exe:hlp:hta:inf:ins:isp:js:jse:lnk:mdb:mde:msc:msi:msp:mst:pcd:pif:reg:scr:sct:shs:shb:url:vb:vbe:vbs:wsc:wsf:wsh accept ###################################################################### # ROUTERS CONFIGURATION # # Specifies how addresses are handled # ###################################################################### # THE ORDER IN WHICH THE ROUTERS ARE DEFINED IS IMPORTANT! # # An address is passed to each router in turn until it is accepted. # ###################################################################### begin routers # This router matches messages addressed to xxx@cyrus-local-delivery.tomster.org # (which are created by the address_to_mailbox router down below) BEFORE they # are actually delivered to cyrus and sends the message to the vacation transport user_vacation: driver = accept domains = cyrus-local-delivery.tomster.org # do not reply to errors or lists condition = "${if or {{match {$h_precedence:} {(?i)junk|bulk|list}} {eq {$sender_address} {}}} {no} {yes}}" no_expn require_files = /var/spool/imap/vacation/${local_part}/vacation.msg # do not reply to errors and bounces or lists senders = " ! ^.*-request@.*:\ ! ^owner-.*@.*:\ ! ^postmaster@.*:\ ! ^listmaster@.*:\ ! ^mailer-daemon@.*\ ! ^root@.*" transport = vacation_reply unseen # user = "tomster" # user = ${local_part} no_verify # This router matches messages addressed to xxx@cyrus-local-delivery.tomster.org # which are created by the address_to_mailbox router down below cyrus_deliver: driver = accept domains = cyrus-local-delivery.tomster.org transport = local_delivery_cyrus dnslookup: driver = dnslookup domains = ! +local_domains transport = remote_smtp ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 no_more system_aliases: driver = redirect allow_fail allow_defer data = ${lookup{$local_part}lsearch{/etc/aliases}} user = mailnull group = mail file_transport = address_file pipe_transport = address_pipe ### # Mailman ### mailman_router: driver = accept domains = lists.tomster.org : lists.klingendes-museum.de : lists.surprising-enterprises.de require_files = MAILMAN_HOME/lists/$local_part/config.pck local_part_suffix_optional local_part_suffix = -bounces : -bounces+* : \ -confirm+* : -join : -leave : \ -owner : -request : -admin transport = mailman_transport # this router handles forwarding to external mailboxes, so that they don't end up # in the virtuser router where they would be resolved to illegal addresses # (targetmailaddress + mailbox_name@cyrus-local-delivery.tomster.org) address_to_external: driver = redirect allow_fail allow_defer data = ${lookup{$local_part@$domain} lsearch*@ {/usr/local/etc/exim/forwardtable}} user = mailnull # this router matches username@domain.tld to mailbox_name@cyrus-local-delivery.tomster.org # thus passing in effect the proper mailbox name to the cyrus_deliver router address_to_mailbox: driver = redirect allow_fail allow_defer data = ${lookup{$local_part@$domain} lsearch*@ {/usr/local/etc/exim/virtusertable}}@cyrus-local-delivery.tomster.org user = mailnull userforward: driver = redirect check_local_user file = $home/.forward no_verify no_expn check_ancestor # allow_filter file_transport = address_file pipe_transport = address_pipe reply_transport = address_reply condition = ${if exists{$home/.forward} {yes} {no} } # This router matches local user mailboxes. localuser: driver = accept check_local_user transport = local_delivery ###################################################################### # TRANSPORTS CONFIGURATION # ###################################################################### # ORDER DOES NOT MATTER # # Only one appropriate transport is called for each delivery. # ###################################################################### begin transports # This transport is used for delivering messages over SMTP connections. remote_smtp: driver = smtp local_delivery: driver = lmtp command = "/usr/local/cyrus/bin/deliver -l" batch_max = 20 user = cyrus address_pipe: driver = pipe return_output address_file: driver = appendfile delivery_date_add envelope_to_add return_path_add address_reply: driver = autoreply ### # Mailman ### mailman_transport: driver = pipe command = MAILMAN_WRAP \ '${if def:local_part_suffix \ {${sg{$local_part_suffix}{-(\\w+)(\\+.*)?}{\$1}}} \ {post}}' \ $local_part current_directory = MAILMAN_HOME home_directory = MAILMAN_HOME user = MAILMAN_USER group = MAILMAN_GROUP # # Cyrus # local_delivery_cyrus: driver = pipe command = "/usr/local/cyrus/bin/deliver $local_part" group = mail user = cyrus return_output log_output message_prefix = message_suffix = # # vacation # vacation_reply: driver = autoreply file = /var/spool/imap/vacation/$local_part/vacation.msg file_expand from = Autoreply System <$original_local_part@$original_domain> log = /var/spool/imap/vacation/$local_part/vacation.log once = /var/spool/imap/vacation/$local_part/vacation.db once_repeat = 14d subject = ${if def:h_Subject: {Re: ${quote:${escape:${length_50:$h_Subject:}}} (autoreply)} {I am on vacation} } text = "\ This is an automatic reply. Feel free to send additional\n\ mail, as only this one notice will be generated. The following\n\ is a prerecorded message, sent for $original_local_part@$original_domain:\n\ ====================================================\n\n\ " to = "$sender_address" ###################################################################### # RETRY CONFIGURATION # ###################################################################### begin retry * * F,2h,15m; G,16h,1h,1.5; F,4d,6h ###################################################################### # REWRITE CONFIGURATION # ###################################################################### begin rewrite ###################################################################### # AUTHENTICATION CONFIGURATION # ###################################################################### begin authenticators plain: driver = plaintext public_name = PLAIN server_condition = "\ ${if and {{!eq{$2}{}}{!eq{$3}{}} \ {eq{$3}{${extract{1}{:} \ {${lookup{$2}lsearch{/usr/local/etc/exim/passwd}{$value}{*:*}}}}}}}{1}{0}}" server_set_id = $2 fixed_login: driver = plaintext public_name = LOGIN server_prompts = UserName:: : Password:: server_condition = "\ ${if and {{eq{$1}{XXXX}}{eq{$2}{XXXX}}}{yes}{no}}" server_set_id = $1 cram: driver = cram_md5 public_name = CRAM-MD5 server_secret = "${if eq{$1}{$1} \ {${lookup{$1}lsearch{/usr/local/etc/exim/passwd}{$value}{*:*}}}fail}" server_set_id = $1 # server_prompts = UserName:: : Password:: # End of Exim configuration file
Previous:
1. Overview
