Configuring Procmail as a Sendmail rule-invoked mailer

Last modified 2003 JUL 28 20:41:58 GMT
Read the site Disclaimer

This is a basic tutorial on how to invoke procmail from within a sendmail ruleset (not as Mlocal, but actually invoked during mail processing, with the express intention of having sendmail continue to process a message which procmail may have changed in some fashion).

John Hardin has a writeup on configuring Procmail within a sendmail gateway, which is a similar process.

Let me start with a few disclaimers - these are rough notes from when I performed such a test on a Sendmail 8.10.x system (i.e. some time ago) with the mere intent of learning how to do it myself so that users who insist on repeatedly asking for this info on the procmail list can be directed to a page laying out the details for them, even though it is really SENDMAIL trickery and the procmail manpage ('man procmail') already provides the basic framework. I do not utilize this approach - I personally have no need for invoking procmail from within sendmail rulesets, and certainly not enough of one to justify the additional processing overhead which it involves. I WILL NOT answer private inquiries for assistance, unless they accompany a service contract and an appropriatley negotiated retainer (US$ only please). I am dead serious. I'm not in the business of taking blame or becoming someone's tecnical crutch or emergency repairman when they take it upon themselves to diddle with the internal mechanisms of tools which they don't fully comprehend.

If you don't maintain your sendmail config using .M4 files, then shame on you. If you don't know about the .M4 config files, and the "make" process for them, then you want to get acquainted with that before proceeding here. It is enough that I'm spelling out this hack, but I am NOT going to re-document sendmail too.

Let me give you clear warning: screwing with your procmail config can cause mail to JUST DISSAPPEAR. Gone. Poof. The only trace you'll have might be mention of it in your system logs. Experienced techs know that the best way to test sendmail configurations is in a sandbox - on a server set aside specifically for performing those tests. Well, at least not a server used for regular mail processing - I use a security monitoring host which isn't used in any fashion for email - it isn't an MX, and there are no users on it (or a web server, etc) to be generating messages. If you do development changes to a live mail server, you won't find any sympathy with me if something goes sour on you.

Your application of any information gained from this document is entirely at your own risk - there are many variables in play, not the least of which are your sendmail version and other sendmail configuration options you may have in effect.

Having said that, using procmail from within sendmail isn't really all that difficult - if you understand the sendmail config file syntax (having a copy of the ORA "Sendmail" book (or the ORA Bookshelf CD version) to reference would be a good idea).

Many people errantly use MAILER(`procmail') in their sendmail M4 config files when initially setting up sendmail to use procmail, when all they're trying to do is set up sendmail to use procmail as the LDA. For that, they need to use FEATURE(`local_procmail') - which sets the Mlocal properly. the MAILER definition has *NOTHING* to do with procmail as the LDA, and is unnecessary unless you're trying to invoke procmail from within sendmail rulesets.

In your you'd add MAILER(`procmail') on a line BEFORE MAILER(`smtp') (remember, the config is processed in reverse or something like that). Build the file, make any manual edits you might have to make, then (usually in ruleset 0, or "Sparse=0"), add something like the following (note that with the supplied .m4 file below, this is already done):

R$*	$: $>ProcmailHandler $1		# procmail filtering

I'm using the newer sendmail syntax for ruleset labelling - you can use names instead of just numerics. Now, somewhere else in the file - I typically do it down below, but in any event, you should be careful to ensure that it is BETWEEN rulesets or definitions, not dropped in the middle of one, add:

You may download this file as ProcmailHandler.m4.

# Copyright (c) 2002 Professional Software Engineering.
#	All rights reserved.
# By using this file, you agree to hold the author and it's agents harmless.
# The rules provided herein are intended only to demonstrate the basic
# functionality of procmail as a sendmail rule-invoked mailer.

ifelse(defn(`_ARG_'), `',

VERSIONID(`$Id: procmailhandler.m4,v 8.12 2002/02/01 12:58:08 sbs Exp $')


R$*	$: $>ProcmailHandler $1		# procmail filtering

# Following three lines are from Adrezej Filip on comp.mail.sendmail 20030412
# This allows sendmail to ignore the hack when it processes newaliases
R$*	$: <${opMode}> $1
R<i>$*	$@ $1
R<$*>$*	$: $2

# parse for local users (single token, no domain)
# note that depending upon where you place the hook for this ruleset,
# this may occur before or after sendmail expands aliases which may
# resolve an apparent local delivery to a remote address - place your
# hook as you see fit.
R$- .procmail
	$@ $1
	# procmail has already looked at this
	$#procmail $@/etc/procmailrcs/outbound.rc $: $1 . procmail
	# invoke procmail

# parse for canonical domains
# (resolved domains will have a dot trailing the domain name)
R$* < @ $+ .procmail >
	$@ $1 < @ $2 . >
	# procmail has already looked at this.
R$* < @ $+ . >
	$#procmail $@/etc/procmailrcs/outbound.rc $: $1 < @ $2 .procmail >
	# invoke procmail

# parse for non-resolveable canonical domains
# (unresolved domains will NOT have a dot trailing the domain name)
R$* < @ $+ .xprocmail >
	$@ $1 < @ $2 >
	# procmail has already looked at this.
R$* < @ $+ >
	$#procmail $@/etc/procmailrcs/outbound.rc $: $1 < @ $2 .xprocmail >
	# invoke procmail

# other examples would include filtering for a specific user address or
# hostname/domain or inbound vs. outbound.  Handle more specific cases FIRST.


As with all sendmail rules, there is a TAB between the LHS and RHS, and if you continue a line, it should start with a TAB. The trailing dot in the above expressions is there because in my sendmail config (which other current sendmails should be very similar to) canonicalizes the address to include a trailing dot.

In your /etc/procmailrcs/outbound.rc, you'd have something like:

# This is a simple script intended to demonstrate sendmail rule-invoked
# procmail filtering.


# grab the arguments from the commandline and put them into variables.

# copy *ALL* messages.  This is just an example.

# rewrite certain message to include a trailing disclaimer
* USER ?? someusername
* ^From:.*someusername@host\.domain\.tld
| cat - /etc/procmailrcs/trailer.sig

# Pass along all other mail (or filtered messages)
! -f "$@"

This file should have appropriate ownership and security settings (i.e. don't make it group or world writeable - and probably, you don't even want it readable by anyone other than owner).

Now, to explain. Mprocmail is the procmail mailer - it tells sendmail how to invoke procmail and what options to apply to it, etc. The specifics are uninmportant to the basic implementation - it is handled just like Mlocal or Mprog according to your config. In the standard MAILER definition, procmail is handed the rcfile, the sender address/username, and the recipient username/addresses(s). The rcfile above takes the two commandline options (things specified after the rcfile) and assigns them to variables which it can then use.

For EXAMPLE purposes only, we have VERBOSE logging on and are dumping to a logfile. You wouldn't want that enabled on a regular high-traffic mailspool. Also for EXAMPLE purposes, there is a rule which uses the 'c'opy flag to make a copy of the messages passing through this rc and store them to a mailbox file. Yup, instant copy of inbound and outbound message traffic (at the moment, the rcfile has no idea whether the message is inbound or outbound - that's a function of the sendmail ruleset you use to invoke it, and where you perform that invocation). Next in the example is something that checks whether the sending user is a specific username and the From: field contains their address (you might do either, but this is intended to catch LOCALLY generated messages where the user MIGHT change the From: field, or be using procmail to forward a message (which you might not want to append the disclaimer to). Anyway, the filter rule pipes through to cat which takes the original message and follows that with the signature text (in my test case, it is a truely obnoxious parody of the legal disclaimers some offices stuff on all of their outbound messages). Now, this was just a FILTER rule, so the (modified) message continues along to the next rule - as would any message which didn't meet the conditions to be filtered - that last rule (which you should recognize from the procmail manpage, 'cept for the omission of the '-oi') delivers the message to sendmail ensuring to set the -f (sender) address to the same address we received it from.

Yes, it is unfortunate that the invocation of mailers from sendmail doesn't allow them to simply be piped through, but in sendmail's mind, once it invoked the procmail mailer (Mprocmail), it was delivered, so for procmail to have sendmail continue processing it, it must invoke sendmail with the message.

Now, we've covered the back end, but I haven't yet explained the sendmail rules. I should preface this by saying that the specific rules you might choose to use from sendmail are your business, and are something you should follow up in a sendmail support forum (news:comp.mail.sendmail perhaps), as, with the exception of invoking procmail, they don't really pertain to procmail at all. That said:

We inserted the following rule into the parse ruleset:

R$*	$: $>ProcmailHandler $1		# procmail filtering

The LHS says "does this match ANYTHING?" If so, then the RHS gets processed, which says "Okay, then, call the ProcmailHandler ruleset with the argument we're parsing".

Elsewhere in the .cf, we introduced the ProcmailHandler ruleset (see above).

It is important to remember that the rules there are separate - although related - rules. On a call to the ProcmailHandler ruleset, ONE of these will take effect (or none will, if there is no match), and will return to the caller without invoking the other. It is for this reason that we check for the .procmail ones first within each pair (since the final rule above would gladly match most messages even if .procmail is already appended).

The first pair of rules operate on single token addresses: that being a local type address consisting of just a username. The second pair operate on canonically resolved addresses. Within each pair, the first one says "if we match an address with a .procmail at the end, then strip that .procmail bit out (this is what cleans up the rewritten address after procmail is invoked). The other rule says that if we have an address (presumed at this point to NOT have a trailing .procmail), then invoke our procmail mailer and also change the address (this is how we ensure that we don't execute procmail a second time when the message is re-introduced into sendmail).

It might help if you create a config file with the above rulesets, then execute:

sendmail -bt
> 3,0 someuser
> 3,0 someuser.procmail
> 3,0 someuser@domain.tld
> 3,0 someuser@domain.tld.procmail
> 3,0 someuser@bogusdomain
> 3,0 someuser@bodusdomain.procmail

You should find that the non-.procmail versions end up parsing into a .procmail version and showing that the procmail mailer would have been called. If you parse the .procmail versions, you'll see that they get resolved down to a local or (E)SMTP delivery as appropriate, with the .procmail portion stripped out. Note that with the canonical domain stuff here, the domain.tld should actually be RESOLVEABLE - if you need to deal with unresolvable domains (why? Surely if you're dealing with a bogus domain, you'd at least locally resolve them in /etc/hosts, right?), you'll need to clone the second pair of rules and remove the trailing dot and tweak the .procmail bit to something slightly different (so that the original rule above doesn't add a dot where it isn't appropriate when rewriting). In the ruleset above (and in the .M4 file linked below), I've included this rule, but you really shouldn't need it.

Because of the broadness of the last expression, you'd want this AFTER the version which handles resolveable hosts ( the $+ would gladly match a hostname with a trailing dot). Specifically, you don't want a rewritten host like : somehost.domain.tld..procmail (note TWO dots). Go ahead, and move the last two rules up and run the address tests again and see what you get, then switch them back so that it isn't broken.

In any event, if the second rule (in any pair) matches, then we'll invoke the procmail mailer on this message and rewrite the recipient address to include the .procmail bit. The above address test will demonstrate this at work.

I've created a HACK (that's a real sendmail macro - just like FEATURE and MAILER) file for the above, which you can add to your sendmail config tree:


Then add the following to your file AFTER your MAILER(`smtp') line:


This will add the MAILER(`procmail') and the necessary rules to your generated .cf file. You'll still need to verify the procmail path (which you can conveniently specify as a second parameter to the HACK macro above), and create the /etc/procmailrc/outbound.rc file and necessary filters, but otherwise, it should work.

Something to note is that in doing this, virtually every email passing through your sendmail will generate an extra process (procmail), plus additional processes beyond that (if procmail runs any shells or other programs in pursuit of the checks/filters), and another sendmail process if procmail actually filters the message rather than discarding it. The additional invocation of sendmail will carry with it the introduction of an additional Received: header as well, indicating the message was received from root@localhost.