L'uso di questo sito
autorizza anche l'uso dei cookie
necessari al suo funzionamento.
(Altre informazioni)

Wednesday, May 21, 2014

Sendmail and procmail mailer for egress mail filtering.

Ok, this is basically an annotated version of recipe 6.8 from O'Reilly's sendmail cookbook. As a bonus, though, it contains also a useful (if spooky) working example AND the relevant procmail configuration. It takes about a day to setup and test without guidance, so enjoy. If you need to be told how to rebuild sendmail.cf from .mc or what is mailertable, this post will be Tralfamadorian to you - go read the Bat Book.

The italian version is here.


  • Create the sendmail configuration to use mailertable and the procmail mailer.
dnl Enable support for the mailertable
dnl Add procmail to the list of available mailers
  • Ath the bottom, insert local rules to fend off loops (watch out for the TAB between '>' e '$')
# Add .PROCMAIL to the pseudo-domain list
# Strip .PROCMAIL and send via esmtp
R$+ < @ $+ .PROCMAIL . >        $#esmtp $@ $2 $: $1<@$2>
  • Set up mailertable to route mail sent to specific domains (or even all mail, as in the last line) through the procmail mailer:
example.com       procmail:/etc/mail/procmailrcs/spam-filter
wrotethebook.net  procmail:/etc/mail/procmailrcs/spam-filter
fake.ora.com      procmail:/etc/mail/procmailrcs/uce-filter
.                 procmail:/etc/mail/procmailrcs/any.rc
  • Create procmail recipe files (owned by root.root, perms 644) in  /etc/mail/procmailrcs (root.root, perms 755). Forwarded mail should extend with the .PROCMAIL pseudo-domain all addresses liable to be rerouted through the procmail mailer.

Discussion and example

The procmail mailer and mailertable

The MAILER(procmail) macro adds the  procmail mailer defintion. This mailer is independent and separate form the local_procmail feature (used to filter incoming local mail).

Using the mailer requires routing mail to it through mailertable:
example.com       procmail:/etc/mail/procmailrcs/spam-filter
.                 procmail:/etc/mail/procmailrcs/any.rc
First rule sends mail going to example.com to the rules defined in spam-filter, the second line sends all mail to any.rc.

# sendmail -bv crooks@example.com
crooks@example.com... deliverable: mailer procmail, host /etc/mail/procmailrcs/spam-filter, user crooks@example.com
# sendmail -bv spammers@spammers.net
spammers@spammers.net... deliverable: mailer procmail, host /etc/mail/procmailrcs/any.rc, user spammers@wrotethebook.net

sendmail invokes procmail as follows:

procmail -Y -m $h $f $u

The -Y flag selects Berkely Unix mailbox format, -m tells procmail that it's being invokde as a general mail filter, $h is set to the full path of the rule file, $f is set to the envelope sender address, $u to the envelope recipient address: these last two can ber referenced in tha rc file as $1 and $2.

Local rules

If, after processing, the message is going to be forwarded again, care must be taken as to avoid mail loop when (if) the message gets back to mailertable processing. The standard technique is to add the pseudo domain .PROCMAIL to the  recipient address within the procmail recipe files, and intercept this domain within sendmai.cf  adding rules to the bottom of  sendmail.mc like this (there must be  a literal  TAB between '<' and '$'):

# Add .PROCMAIL to the pseudo-domain list
# Strip .PROCMAIL and send via esmtp
R$+ < @ $+ .PROCMAIL . >        $#esmtp $@ $2 $: $1<@$2>

  • LOCAL_CONFIG starts the section that will be copied to sendmail.cf verbatim;
  • CP.PROMAIL inserts .PROCMAIL in class P: pseudo-domains that sendmail does not look up in the DNS;
  • LOCAL_RULE_0 starts a section whose content is added to  ruleset 0—also called the  parse ruleset. The code is entered in the  ParseLocal ruleset (ruleset 98). The parse ruleset risolvs an addresse in a delivery triplet.
  • The R rule matches addresses of the form user@domain.PROCMAIL, and rewrites them as a triplet with with “esmtp”, as mailer, “domain” as hast and “user@domain” as recipient.
 After regenerating sendmail.cf, we have:

# sendmail -bv crooks@example.com.PROCMAIL
crooks@example.com.PROCMAIL... deliverable: mailer esmtp, host example.com, user 

Procmail rule files, and an example

The  (objectionable) purpose of this example is creating a sendmail instance who does a stealth forward of a copy of all transient mail to a set of configured addresses, while also delivering it to the intended address.
  • /etc/mail/sendmail.mc is as above.
  • Mailertable:
.                 procmail:/etc/mail/procmailrcs/any.rc
/etc/mail/procmailrcs has perms 755, owner root.root; the files within, 644, root.root. SELinux users: no doubt SELinux will mess this up, as it does with everything else when it's turned on - I can offer no guidance, suit yourselves as best you can.
  • We use two auxiliary rule files to set-up and tear down tha stage.

# Debugging (use =no and =no to turn off)
LOGFILE=/var/log/proc_log   #recommended
#this is clearer
#Echoed to logfile at the end
TRAP="echo '---------- End of procmailer ------' "
# Antiloop belt+suspender. This should never be triggered.
:0 w
* 1^0 ^X-Our-Anti-Loop: SET
000_setup must be included before any other ruel: besides some standard setting, it copies $1 and $2 to the more mnemonic $OSENDER, $ORECIPIENT and checks wether a header tht we add on exit exists (it should never be present): if it does it shortcircuits the processing with a copy of the final rule. This is also found in the file 999_end:
# Insert Anti-loop header, add .PROCMAIL to recipient
:0 w
| (formail \
  -A"X-Our-Loop: SET" \
the message is sent to the final recipient, after .PROCMAIL has been tacked to the recipient address, to trigger the sendmail.cf rules. This file must be included at the very end.
  • In any.rc is where the meat and potatoes are:

# Setup 
#flag 'c' creates a carbon copy for processing
# Avoid sending duplicates
:0 Whc
| formail -D 16384 /tmp/idcache
#flag 'c' creates a carbon copy for processing
#flag 'e' executes only if previous command failed.
#espiones must contain addresses that are either local or .PROCMAIL terminated
:0 ec
| (formail -A"X-Our-Processed: SET" ) | $SENDMAIL -oi -f $OSENDER espiones
#clean up and forward

After setup, the second rule  checks if the message ID is already in a cache file, and add it there if it isn't. This will avoid sending multiple copies of the message to the  “espiones” when we have several recipients  (in  from:,  cc: or bcc:). Lastly the message gets added a watermark header, for debugging.
  • Since espiones is an aliased adress, non-local addresses in the alias need protection, too (as would literal addresses specified in the rule)
      espiones: foo@bar.com.PROCMAIL, baz@sna.fu.PROCMAIL, snafu
snafu, being local, is not  .PROCMAIL protected.

Additional Considerations

No doubt a task like this could be performed with a milter. Usage of the procmail mailer, as everything,  has pros and cons.


  • You do not need to select, write, or buy a suitable milter
  • Yu do not have to learn the ins and outs of yet another piece of MTA related software
  • You do not have to set up/debug the relevant communication between milter and sendmail
  • You have one less demon (the milter) to take care of


  • The mail flow becomes more involved and it is now controlled by yet another layer of configuration files (the rule files), and the same message will go through sendmail more than once. 
  • God help you if you come to rely on multiple loops of this machinery (it is tempting, I know).
  • Procmail syntax sucks.

No comments: