NAME

proctut6 - Anatomy of a Procmail Recipe, Part II

SYNOPSIS

In Anatomy of a Procmail Recipe, Part I we discussed the three basic components of a procmail recipe. This tutorial goes further and examines one of two classes of recipe: the delivering recipe (the other being a non-delivering recipe).

DESCRIPTION

Procmail has two kinds of recipes: those that deliver mail (delivering recipes), and those that, well, don't (non-delivering recipes). The procmailrc(5) manpage states it succinctly enough:

    Delivering recipes are those that cause header and/or body of the
    mail to be: written into a file, absorbed by a program or
    forwarded to a mailaddress[sic].

It also states:

    If a delivering recipe is found to match, procmail considers the
    mail (you guessed it) delivered and will cease processing the
    rcfile after having successfully executed the action line of the
    recipe.

If the mail message being scanned does not meet the conditions of the recipe, then the recipe is considered "false" and the action line is not triggered. The mail is not delivered and the mail message is then scanned by subsequent recipes until a delivering recipe is found.

If no delivering recipes are found, mail is delivered to the file specified by $DEFAULT (usually /var/mail/$LOGNAME where $LOGNAME is the account owner of the mail being delivered) and procmail ceases processing.

The remainder of this document will give examples of ways a message is considered "delivered".

Written Into A File

Many procmail recipes write to a file. Most of the examples we've covered in previous tutorials have been recipes that write to a file. Here is another example:

    :0:
    /var/mail/bob

Any mail passing through this recipe will be written to the file /var/mail/bob and procmail will consider the mail "delivered". As we mentioned in Anatomy of a Procmail Recipe, Part I, /var/mail/bob is the action line of this recipe. Each recipe has one action line[7].

Here is another recipe with no conditions and one action line:

    :0
    /dev/null

Any mail passing through this recipe will be delivered to /dev/null: the bottomless pit of Unix[1]. The file system treats /dev/null as a real file, for most purposes. We can read from and write to /dev/null and so can procmail. Once a mail message is delivered to /dev/null we can't, of course, retrieve it again, but procmail considers it delivered all the same.

The previous two recipes did not have any condition lines. Not all recipes work this way (i.e., without conditions). Many recipes have conditions which determine whether the action line will be triggered or not. Consider this recipe:

    :0:
    * H ?? ^Subject:.*(     )[ ]*\(?[0-9a-z]+
    /var/tmp/spam

Mail whose subject line contains 5 or more consecutive spaces followed by one or more letters or numbers will be delivered to /var/tmp/spam.

If the mail message being scanned does not meet the conditions described above (i.e., the subject line does not contain 5 or more consecutive spaces, for example), the recipe is considered a "non-match" or "false" and the recipe is skipped and the message is then scanned by any subsequent recipes.

Absorbed By A Program

Besides writing to files, we can also write mail messages to pipes. A pipe is the Unix way of communicating between two programs. For example, you may sometimes see commands like this in your Unix shell:

    ls -l | less

This executes the program ls with the -l option and "pipes" its output to a program called less. We might do this because the output of ls is longer than will fit on one screen, so we invoke a pager program (a program that shows screenfuls of data at a time, such as less and more) to view the output in a more desirable fashion.

Procmail can also make use of pipes. Consider the following simple example:

    :0 h
    | /bin/date >> /var/tmp/my_mail

In this case, we pipe the headers of our message to the date program, which prints the date to /var/tmp/my_mail. Why do we pipe the headers to date (as opposed to headers and body)?

Well, we don't have to, actually. We could just do this:

    :0
    | /bin/date >> /var/tmp/my_mail

which pipes the entire message (headers and body) to date. The reason we might want to limit our pipe to headers only is because message bodies can sometimes be very big. We know that the date program doesn't accept anything from a pipe (it just discards it), but procmail has the pipe open anyway and will send through what we tell it. The 'h' flag reduces the amount of data sent over the pipe, making our recipe more efficient.

Why would we want a recipe like this? Well, perhaps this is a "spam harvester" account (i.e., a dummy email account with no real user) whose sole purpose is to detect when spam is being actively sent. We can easily plot a histogram from the date lines in the log.

We write to pipes for other things too. Consider the following common (and somewhat irresponsible) example:

    :0 h
    * ! ^FROM_DAEMON
    * ! ^X-Loop: scott_was_here
    | ( formail -rt -A "X-Loop: scott_was_here; \
        echo "I will never read your message."; \
        ) | $SENDMAIL -t

Let's step through this in English. It's a bit tricky, because we have two pipes. Don't be too concerned with unfamiliar syntax in the condition lines; concentrate on the action line. This is a classic procmail autoresponder and should become familiar in time as we study how it works.

    IF the mail message:

       - is NOT autogenerated (e.g., a bounce message or other
         automated notification--this is what ^FROM_DAEMON does) and
       - does NOT have our X-Loop header in it (to avoid endless loops
         with ourselves),

    THEN we pipe the headers of the message to 'formail' (formail with
    -r will create an empty autoreply). We also 'echo' some text as
    the body of our message. The output of 'formail' and 'echo'
    together are both piped (notice that both of these commands are
    "wrapped" in parentheses--this is a Bourne shell feature for
    combining actions into one "chunk") to the sendmail program (i.e.,
    whatever the variable $SENDMAIL contains, usually
    /usr/sbin/sendmail or /sbin/sendmail, etc. depending on your
    operating system).

In this example, the original message was discarded, but because the message was written to a pipe, procmail considers this message "delivered".

Forwarded To A Mail Address

The final way procmail considers a message delivered is if we send it on to another email address via the "bang" operator (exclamation point: '!'). Here is simple example of forwarding a message:

    :0
    * ^TO_jane@schmoe\.org
    ! jane@public\.org

This is often accomplished using sendmail's .forward mechanism, aliases, or virtusertable features, but sometimes is quicker and easier to remember to do in procmail. We also introduce another built-in procmail variable, the '^TO_' variable. We read this recipe in English like this:

    If the message is To, Cc, X-Envelope-To, or Apparently-To
    'jane@schmoe.org', then we re-send it to jane@public.org.

We can see that the ^TO_ variable[2] is powerful and concise and usually more accurate than our own regular expressions that try to accomplish the same thing (see procmailrc(5) for more details on '^TO_').

The bang operator means "forward this" email message only when it appears as the first character of the action line. When the bang operator appears in at the beginning of a condition, it means logical "not" (i.e., invert the condition).

Cease Processing

What does it mean for procmail to "cease processing"? Let's review the manpage entry:

    Delivering recipes are those that cause header and/or body of the
    mail to be: written into a file, absorbed by a program or
    forwarded to a mailaddress[sic].

and:

    If a delivering recipe is found to match, procmail considers the
    mail (you guessed it) delivered and will cease processing the
    rcfile after having successfully executed the action line of the
    recipe.

To "cease processing" means that procmail, as it states, "[executes] the action line of the recipe"; that is to say delivers the mail. Once the mail is delivered, procmail stops and returns control to the program that invoked it (e.g., sendmail, qmail, etc.)[3].

Once procmail ceases processing, no further recipes are executed for this mail message. For example, let's say we have a procmailrc file with 20 delivering recipes. The first 5 recipes are "false"--their action lines do not trigger because not all of their condition lines were satisfied. But our 6th recipe's conditions do match; the action line is triggered and the mail is delivered. Our mail message never passes through recipes 7 through 20 because procmail, once recipe 6 has delivered, stops processing the mail.

It may seem pedantic to belabor a point that seems simple, but this idea of delivering versus non-delivering is important to know, especially when we discuss our next tutorial on non-delivering recipes, where our mail messages continue to pass through subsequent recipes even after an action line is triggered.

SUMMARY

Delivering recipes are recipes that 1) writes a mail message to a file, 2) writes a mail message to a program, or 3) forwards a mail message to another mail address. Once a mail message has passed through a delivering recipe whose action line has triggered, procmail stops processing this message.

NOTES

Note 1

You might notice that the recipe that delivers to /dev/null doesn't have a trailing colon in the flags line. This is because we don't ever need to lock /dev/null; we don't care if data is written to /dev/null from multiple sources at the same time!

Note 2

'To:' and 'Cc:' are probably clear enough, but 'X-Envelope-To:' and 'Apparently-To:'? These headers are often inserted by the mail transport agent (e.g., sendmail, qmail) when mail is initially received.

Note 3

Mail Transport Agents (or MTA, as they are commonly referred to) are what make the mail go 'round the world. Sendmail is probably the most famous MTA. Sendmail works well under the Unix philosophy:

    Write programs that do one thing and do it well. Write programs
    to work together. Write programs to handle text streams, because
    that is a universal interface.  --Doug McIlroy (once head of Bell
    Labs research department where Unix was born)
Procmail is a tool of the same forge as sendmail: it does mail processing. Fast. Its unfortunately terse syntax reflects its humble beginnings, but there is a lot of elegance to its rule-based parsing methodology.

PREVIOUS

Thinking "Regex"

NEXT

Anatomy of a Procmail Recipe, Part III

SEE ALSO

procmail(1), procmailrc(5), procmailex(5)

AUTHOR

Scott Wiersdorf <scott@perlcode.org>

COPYRIGHT

Copyright (c) 2003 Scott Wiersdorf. All rights reserved.

REVISION

$Id: proctut6.pod,v 1.6 2003/10/23 04:29:11 deep Exp $