Dual-instance sendmail with amavisd-new --------------------------------------- Mark Martinec, 2003-05-06 (based on initial research by Ricardo Stella) updated on: 2005-09-22 (added a reference to 'milter-ahead'); updated on: 2005-09-29 (added custom rules to reject unknown users outright, provided by Matej Vela, thanks to Simone Marx) updated on: 2006-09-15 (placement of DKIM/DK milters, mention the feature FEATURE(`nocanonify',`canonify_hosts'), the absence of which can make header procesing by sm *very* long) The most recent version of this document can be found at: http://www.ijs.si/software/amavisd/README.sendmail-dual ========================================================================== The setup is very similar to the one described in README.sendmail (by Rainer Link) in section 'Scanning incoming/outgoing and relayed mail', except that it uses SMTP protocol over inet socket (instead of pipes to commands) to transfer files between MTA and amavisd-new and back, and that it uses a permanently running second sendmail instance in 'queue only' delivery mode, instead of bringing it up every time a new checked mail comes from amavisd. ========================================================================== Comparing the setup described in this document with the sendmail milter setup, as described in README.milter: milter - reasons in favour: - can REJECT on the original SMTP session, instead of generating a bounce (sending a non-delivery notification _after_ the mail has been enqueued); - only one sendmail daemon need be running, only one config file needed, no additional queue area needed (although starting with sendmail 8.12 more than one queue area is already a norm: clientmqueue, queue groups; and MSP already uses a different .cf file); dual-MTA - reasons in favour: - Full amavisd-new functionality is available, including adding spam and virus information header fields, adding address extensions and removing certain recipients from delivery while delivering the same message to the rest (*_lovers). Also a message can be split if different recipients need different header edits. All this is not available when using amavis-milter helper program. - Content scanning need not be performed at the time of mail reception. This allows better control on CPU-intensive content filtering: mail checking can be streamlined and performed at optimum throughput setting (number of content checker processes) so as not to overwhelm host resources, instead of leaving it at the mercy of the current number of incoming SMTP sessions where available crude controls are mostly based on system load. Typically the number of incoming SMTP sessions (tiny processes) is desired to be many times above the number of content filtering processes (heavy resource consumers). - No helper programs needed, MTA communicates with amavisd-new directly via SMTP, saves on creating one directory and one file for each message, and deleting it (at the cost of one additional transfer); - Receiving sendmail daemon (MTA-RX) need not run as root (using option RunAsUser) since it does not need to run any local delivery agents (LDA) or to access user .forward files. This avoids external SMTP clients talking directly to a process running as root. ========================================================================== The following setup is described in this document: ............................ ............................ : sendmail instance MTA-RX : : sendmail instance MTA-TX : : : : : 25 -----> \ (mqueue-rx) : : (mqueue) / -------> forward 587 -----> > -queue- : : -queue- ->-----+ : ^ : / | MAIL_HUB, : : | \ -------> local | : v SMART_HOST : : ^ : delivery msp ...........|................ ....|....................... | ^ loopback interface v | port 10025 loopback interf.| port 10024 | .....|.......................|............. : $inet_socket_port=10024 | : : | : : $forward_method='smtp:127.0.0.1:10025' : : $notify_method ='smtp:127.0.0.1:10025' : : : : amavisd-new : ........................................... The setup is based on the recent sendmail (8.12.9 or later) with its set of m4 configuration macros. Because of several security problems with earlier versions of sendmail it is advised to stick to the most recent version, although the functionality needed for this setup has long been available. If a particular macro or feature is not available with some older version, it is usually possible to achieve the same or similar by manually writing a new 'mailer' specification and/or tweaking the .cf file. We'll prepare two sendmail daemon instances (processes), let's call them MTA-RX (receiving, accepting) and MTA-TX (transmitting, delivering). For convenience we keep the name of the configuration file and the queue (spool) area at default names for one mailer instance, and choose non-default names for the other. Let's choose the MTA-TX to keep default names, and supply non-default names to MTA-RX explicitly. This will make admin utilities like mailq, newaliases, hoststat and purgestat operate on the outgoing mailer instance unless explicitly told otherwise. It could just as well be the other way around. MTA-RX (receiving mailer) will be responsible for accepting mail from the Internet or from internal hosts on port 25, optionally accepting local message submissions on tcp port 587 (rfc2476), and for message submissions via sendmail program. It will forward all mail (both for local and for nonlocal recipients) via SMTP protocol (or LMTP) to 127.0.0.1 (a loopback interface) on tcp port 10024, where amavisd daemon will be listening. - its queue: /var/spool/mqueue-rx - its config file: /etc/mail/sendmail-rx.cf, /etc/mail/submit.cf - the source (.mc) of the configuration file: thishost-rx.mc (where 'thishost' is often by convention the name of the system (uname)) MTA-TX (transmitting mailer) will be responsible for accepting checked mail and notifications from amavisd-new via SMTP on the loopback interface (127.0.0.1) at tcp port 10025, and will forward all mail to its final destinations, either for local delivery, or delivering outgoing mail to the Internet or to other internal mailers. - its queue: /var/spool/mqueue - its config file: /etc/mail/sendmail.cf - the source (.mc) of the configuration file: thishost-tx.mc In-between the two MTAs an amavisd daemon will accept mail via SMTP (or LMTP) protocol on tcp port 10024, check it, and forward checked mail and notifications via SMTP to MTA-TX. If you already have an existing sendmail installation, you already have a queue directory /var/spool/mqueue and the configuration file(s) (.mc source and the compiled .cf file). Most of the existing settings in your .mc file can be reused, and are to be moved to the new files thishost-rx.mc or thishost-tx.mc, or (some of them) to both. The settings pertaining to receiving mail, including recource limits, should go to thishost-rx.mc; settings pertaining to delivering mail (locally or to other mailers) should go to thishost-tx.mc, and general settings should go to both. The MTA-TX should have none or hardly any resource limits, or at least have them larger than MTA-RX. Large messages, common errors in mail, and mail rush-ins should be stopped or limited at their entry to the system. Accepting them first, but choking later can lead to trouble or at least to wasted resources. The file names thishost-rx.mc and thishost-tx.mc are arbitrary, they only serve as source (to the m4 macro processor) for producing .cf files, which control sendmail's behaviour. Sendmail never uses .mc files directly. MTA-TX already got its queue directory during sendmail installation. For MTA-RX a new queue directory needs to be created where incoming mail can be collected. Use the same ownership and protection as used for /var/spool/mqueue, e.g: # mkdir /var/spool/mqueue-rx # chown root:wheel /var/spool/mqueue-rx # chmod 700 /var/spool/mqueue-rx SECURITY NOTE: starting with sendmail 8.12 it is possible to start sendmail daemon as root and let it drop privileges (become user specified by RunAsUser) after binding to port 25. This is normally used by MSP, and it can just as well be used by MTA-RX, since it has no need to access user mailboxes and .forward files. To use this feature, specify user and group in the macro confRUN_AS_USER (file thishost-rx.mc), and set the ownership of mqueue-rx to this user and group: # chown smmsp:smmsp /var/spool/mqueue-rx # chmod 770 /var/spool/mqueue-rx More complex queue setup is possible if needed, like separating sendmail work area and core dump area from actual queues. For details about queue groups see sendmail documentation. Create file thishost-rx.mc: ---cut-here------------------------------ dnl To be used for MTA-RX, the first MTA instance (receiving mail) dnl Insert here the usual .mc preamble, including OSTYPE and DOMAIN calls. dnl Specify here also access controls, relayable domains, anti-spam measures dnl including milter settings if needed, mail submission settings, client dnl authentication, resource controls, maximum mail size and header size, dnl confMIN_FREE_BLOCKS, and other settings needed for receiving mail. dnl dnl NOTE: dnl confMIN_FREE_BLOCKS at MTA-RX should be kept higher than the same dnl setting at MTA-TX to quench down clients when disk space is low, dnl and not to stop processing the already received mail. dnl dnl In particular, here are some settings to be considered: dnl ( see also http://www.sendmail.org/m4/anti_spam.html ) dnl dnl FEATURE(`access_db') dnl VIRTUSER_DOMAIN(`sub1.example.com')dnl list valid users here dnl VIRTUSER_DOMAIN(`sub2.example.com')dnl list valid users here dnl FEATURE(`virtusertable') dnl define(`confUSERDB_SPEC', `/etc/mail/userdb.db') dnl FEATURE(`blacklist_recipients') dnl FEATURE(`use_cw_file') dnl FEATURE(`use_ct_file') dnl FEATURE(`nocanonify', `canonify_hosts')dnl dnl INPUT_MAIL_FILTER(...) dnl define(`confPRIVACY_FLAGS', `noexpn,novrfy,authwarnings') nobodyreturn ? dnl define(`confDONT_PROBE_INTERFACES') dnl MASQUERADE_AS(...) FEATURE(`allmasquerade') FEATURE(`masquerade_envelope') dnl define(`confTO_IDENT', `0')dnl Disable IDENT dnl define(`confMAX_MESSAGE_SIZE',`10485760') dnl define(`confMAX_MIME_HEADER_LENGTH', `256/128') dnl define(`confNO_RCPT_ACTION', `add-to-undisclosed') dnl define(`confBIND_OPTS', ...) dnl define(`confTO_RESOLVER_*... ) dnl define(`confDELAY_LA, 8) dnl define(`confREFUSE_LA', 12) dnl define(`confMAX_DAEMON_CHILDREN',20) dnl define(`confMIN_FREE_BLOCKS', `10000') dnl define(`confDEF_USER_ID', ...) define(`confRUN_AS_USER',`smmsp:smmsp')dnl Drop privileges (see SECURITY NOTE) define(`confPID_FILE', `/var/run/sendmail-rx.pid')dnl Non-default pid file define(`STATUS_FILE', `/etc/mail/stat-rx')dnl Non-default stat file define(`QUEUE_DIR', `/var/spool/mqueue-rx')dnl Non-default queue area define(`confQUEUE_SORT_ORDER',`Modification')dnl Modif or Random are reasonable dnl Match the number of queue runners (R=) to the number of amavisd-new child dnl processes ($max_servers). 2 to 7 OK, 10 is plenty, 20 is too many QUEUE_GROUP(`mqueue', `P=/var/spool/mqueue-rx, R=2, F=f')dnl dnl Direct all mail to be forwarded to amavisd-new at 127.0.0.1:10024 FEATURE(stickyhost)dnl Keep envelope addr "u@local.host" when fwd to MAIL_HUB define(`MAIL_HUB', `esmtp:[127.0.0.1]')dnl Forward all local mail to amavisd define(`SMART_HOST', `esmtp:[127.0.0.1]')dnl Forward all other mail to amavisd define(`LOCAL_RELAY',`esmtp:[127.0.0.1]')dnl define(`confDELIVERY_MODE',`q')dnl Delivery mode: queue only (a must, dnl ... otherwise the advantage of this setup of being able to specify dnl ... the number of queue runners is lost) define(`ESMTP_MAILER_ARGS',`TCP $h 10024')dnl To tcp port 10024 instead of 25 MODIFY_MAILER_FLAGS(`ESMTP', `+z')dnl Speak LMTP (this is optional) define(`SMTP_MAILER_MAXMSGS',`10')dnl Max no. of msgs in a single connection define(`confTO_DATAFINAL',`20m')dnl 20 minute timeout for content checking DAEMON_OPTIONS(`Name=MTA-RX')dnl Daemon name used in logged messages dnl Disable local delivery, as all local mail will go to MAIL_HUB undefine(`ALIAS_FILE')dnl No aliases file, all local mail goes to MAIL_HUB define(`confFORWARD_PATH')dnl Empty search path for .forward files undefine(`UUCP_RELAY')dnl undefine(`BITNET_RELAY')dnl undefine(`DECNET_RELAY')dnl MAILER(smtp) dnl The following solution to reject unknown recipients outright dnl is provided by Matej Vela , see: dnl http://groups.google.com/group/comp.mail.sendmail/ dnl browse_thread/thread/88cc72d7c4d3a6e/ee2a9474b3a4558d dnl The FEATURE(stickyhost) short-circuits FEATURE(luser_relay) so that a: dnl define(`LUSER_RELAY',`error:5.1.1:"550 User unknown"') can't be used. dnl A simple solution is to disable FEATURE(stickyhost). If this is not dnl possible, the alternative is to replace FEATURE(luser_relay) with custom dnl rules below. The latter has the advantage of properly handling special dnl aliases like ("|program", "/mailbox", and ":include:/list"). If choosing dnl this route, one should NOT use `undefine(`ALIAS_FILE')dnl', and use the dnl following custom rules: dnl LOCAL_CONFIG Kaliasp hash -m /etc/mail/aliases Kuserp user -m LOCAL_RULESETS SLocal_check_rcpt R$* $: $&{rcpt_addr} R $+ @ $=w $: <@> $1 mark local address R $* @ $* $@ OK ignore remote address R $+ $: <@> $1 mark unqualified user R<@> $+ + $* $: < $(aliasp $1+$2 $: @ $) > $1 + * plussed alias? R<@> $+ + $* $: < $(aliasp $1+$2 $: @ $) > $1 +* alias? R<@> $+ $: < $(aliasp $1 $: @ $) > $1 normal alias? R<@> $+ $: < $(userp $1 $: @ $) > $1 system user? R<@> $+ $#error $@ 5.1.1 $: "550 User unknown" nope, go away ---end----------------------------------- Create file thishost-tx.mc: ---cut-here------------------------------ dnl To be used for MTA-TX, the second MTA instance dnl (delivering outgoing and local mail) dnl Insert here the usual .mc preamble, including OSTYPE and DOMAIN calls. dnl Specify here also the required outgoing mail processing and dnl local delivery settings such as mailertables, needed mailers, aliases, dnl local delivery mailer settings, smrsh, delivery mode, queue groups, ... dnl Don't use milters here - for all common filtering purposes they belong dnl to MTA-RX; an exception to this rule would be DKIM or DomainKeys mail dnl signing milters (signature _verification_ milters still belong to MTA-RX). define(`confREFUSE_LA',999)dnl Disable the feature, limiting belongs to MTA-RX define(`confMAX_DAEMON_CHILDREN',0)dnl Disable, limiting belongs to MTA-RX FEATURE(`no_default_msa')dnl No need for another MSA, MTA-RX already has one FEATURE(`nocanonify')dnl Host/domain names are considered canonical DAEMON_OPTIONS(`Addr=127.0.0.1, Port=10025, Name=MTA-TX')dnl Listen on lo:10025 define(`confSMTP_LOGIN_MSG', `$w.tx.$m Sendmail $v/$Z; $b')dnl define(`confTO_IDENT', `0')dnl Disable IDENT MAILER(smtp) MAILER(local) ---end----------------------------------- Now macro-expand .mc files into .cf files: (adjust the path if needed to where your cf/m4/cf.m4 file resides) # m4 /usr/share/sendmail/cf/m4/cf.m4 thishost-rx.mc >/etc/mail/sendmail-rx.cf # m4 /usr/share/sendmail/cf/m4/cf.m4 thishost-tx.mc >/etc/mail/sendmail.cf Start MTA-RX and MTA-TX daemons: # /usr/sbin/sendmail -C/etc/mail/sendmail-rx.cf -L sm-mta-rx -bd -qp # /usr/sbin/sendmail -L sm-mta-tx -bd -q15m Start queue runner for the MSP client queue as usual, if using it: # /usr/sbin/sendmail -Ac -L sm-msp-queue -q10m Start amavisd-new: # amavisd Test if MTA-RX is listening: # telnet localhost 25 QUIT Test if MTA-RX is listening on MSA port 587 (a newer sendmail invention) # telnet localhost 587 QUIT Test if MTA-TX is listening: # telnet localhost 10025 QUIT Test if amavisd is listening: # telnet localhost 10024 QUIT For convenience some shell aliases may be defined: alias mailq-rx='mailq -C/etc/mail/sendmail-rx.cf' alias mailq-tx='mailq' alias sendmail-rx='/usr/sbin/sendmail -C/etc/mail/sendmail-rx.cf' alias sendmail-tx='/usr/sbin/sendmail' All done! NOTES - In amavisd.conf file follow the 'POSTFIX or EXIM V4 or dual MTA setup', which is also the default. - The $final_*_destiny should not specify D_REJECT. The D_BOUNCE (or D_PASS or D_DISCARD) is preferred. - To make MTA-RX content-check only some mail but not all, one may use mailertables instead of MAIL_HUB and SMART_HOST. For example setting some recipient domains to be passed to MTA-TX at 127.0.0.1:10025 directly (e.g. via mailer 'esmtp'), while sending all the rest to amavisd at 127.0.0.1:10024. To be able to specify the port number, a new 'mailer' needs to be defined, let's call it 'amavis', with similar settings as the already defined 'esmtp', except with port number 10024. - depending on how local addresses are translated by MTA-RX, the %local_domains (or @local_domains_maps) in amavisd.conf needs to be adjusted accordingly to be able to recognize local domains. Check the amavisd-new log what recipient addresses it sees for local recipients. The '[127.0.0.1]' may need to be added to the @local_domains. - To make MTA-RX reject mail for nonexistent local users by itself (instead of generating a bounce later on), one may use the 'virtusertable' in thishost-rx.mc, listing all known recipients, and rejecting the rest, e.g.: VIRTUSER_DOMAIN(`example.com')dnl FEATURE(`virtusertable', `hash /etc/mail/virtusertable')dnl jim@example.com %1%3 joe@example.com %1%3 postmaster@example.com %1 @example.com error:5.7.0:550 No such user here You may use the righthand side of the map to specify local user (e.g. %1%3, or just jim, without domain name) in which case MAIL_HUB will be used for forwarding, or specify an explicit domain name that is not in the {w} class, in which case the SMART_HOST will get consulted. Perhaps what Stephane Lentz writes is even better: Dictionary attacks and messages to retired accounts can be bounced with sendmail: just replicate your aliases or write some SLocal_check_rcpt rule-set that checks addresses of your domain against a map of valid users (valid_addresses.db). I hope some standard FEATURE will be provided with sendmail - something like FEATURE(checkdomainaddresses) and CHECKDOMAINADDRESSES(mydomain.com). An alternative solution is to use a milter to do address verification against the second MTA in chain. See the milter-ahead project: http://www.milter.info/sendmail/milter-ahead/ PERFORMANCE NOTES - Mail handling is I/O-intensive. For better performance one may place the two mail queue areas (/var/spool/mqueue and /var/spool/mqueue-rx), and the /var/amavis work directory ($TEMPBASE) on three separate disks. The /var/amavis/tmp may be a tmpfs or a RAMdisk or delayed-sync fs. - One of the important arguments for choosing the dual-MTA setup is to be able to keep the number of content filtering processes under control, and not at the mercy of current mail inflow. Don't blow this advantage by setting the number of amavisd-new processes and MTA-RX queue runners too high! Throughput optimum is somewhere between 3 and 10 with fast daemonized av-scanner (or no av scanner) (with or without SpamAssassin), and between 2 and 3 with many command line scanners (regardless of SA). If the host is low on memory and when spam checking (SpamAssassin) is used, even 2 may be a lot for an elderly host. Start conservatively, e.g. at 2 or 3, and if everything works normally and higher throughput is needed, try a bit more. Anything above the point where throughput function levels off is just a waste of memory and gains nothing! The optimum may be higher if high-latency external SpamAssassin tests are enabled (e.g. Razor, RBL), Still, never go beyond available memory. For example with SpamAssassin enabled, the 20..25 processes on a 256 MB machine is where throughput begins to drop rapidly on a way to a swapping tar pit.