Does this site look plain?

This site uses advanced css techniques

[O'Reilly Sendmail book] Working with sendmail configuration files has been likened to "doing math with Roman numerals", and after struggling through many of these files for more than ten years, I concur heartily.

But it's gotten quite a bit easier over time owing to:

I've had quite a few very peculiar customer requests over the years that have stretched my skills, but they have given me a better-than-average sense for routing with sendmail. A few particular areas I work in:

Anti-Spam Configuration

Some of my earliest serious sendmail work was securing machines from third-party relay when a few customers got hijacked by spammers. This was at a time when antirelay configuration was not anywhere as easy as it is now, and testing sendmail.cf on a live machine was just a nightmare.

Nowadays, the configuration required to block third-party relay is dramatically easier, and this is often the default for modern versions. But there is still configuration required to whitelist trusted sites and trusted users.

[ORDB logo] In addition, configuring the mailer to reject mail from known spammers via the various blacklists has done wonders to cut down on inbound spam. I particularly like and support ORDB and install it in customer systems when they ask.

Custom sendmail ruleset writing

I've worked with enough odd configurations of sendmail with strange requests from customers that I've gotten a good sense for where these changes need to be made. The .mc macro files are very well designed and allow for quite a bit of customization, and the best way to explain my expertise is with an example.

I implemented a system for management of a 100,000 member emailing list for a customer in the retail industry, and this involved creating the system for processing the bounces, remove responses, and customer-service requests. All these responses were routed into a single mailbox which was picked apart and processed largely automatically by my software.

Originally the responses were to bounce@customer and remove@customer, but we found that associating these responses with the actual customer record was troublesome. So I extended the system to route responses to a much longer email address such as bounce-123456 address that encoded a message ID: this allowed us to know which customer email address generated the response. This step required tom sendmail rule to route bounce-anything@company and remove-anything@company to the single mailbox.

But once we implemented ORDB spam filtering for the company's, we found that customer remove and bounce requests were being rejected, and my customer asked that we find a way to whitelist these requests to get them past the spam filters. Though there are provisions to whitelist specific addresses via the access database files, we had to write a custom check_rcpt ruleset to handle this and their other needs.

The goals of this ruleset are to "whitelist" a few address forms past the spam filters, plus to implement a "user no longer here" message that's checked during the RCPT TO state of the SMTP stage of the conversation and not defer this until final delivery. This gone-users list is managed via the database macros.

#------------------------------------------------------------------------
# LOCAL RECIPIENT PROCESSING
#
# This is handled during the SMTP conversation when we get the "RCPT To"
# command, and this allows us to reject invalid users early without
# having to actually accept the whole message. Canonicalize the name to
# get the benefit of the really smart parsing, then "undo" it to strip
# off the < > business and trailing dots. But this is kind of tricky,
# and if we don't document it here we will NEVER figure this out again
# so we'll try hard to make it clear.
#
#
LOCAL_RULESETS
SLocal_check_rcpt

#
# Here our workspace contains the raw data as presented from the other end
# on the RCPT line, but it could be in a whole raft of different forms. It
# could have or not have <brackets>, and it might have a domain on it or
# not. We could roll our own, but there are existing rulesets that are
# happy to take care of this for us. These two sets ultimately end up with
# just the "user@domain.com" format.

R $*            $: $>Parse0 $>3 $1              canonicalize
R $*            $: $>final $1                   dump the < > and stuff


# ------------------------------------------------------------------------
# At this point the email address is in "regular" format, and we need an
# early check for our special bounce & remove addresses to allow them to
# always be accepted even if from blacklisted sources. The format is
# always remove.{stuff} or bounce.{stuff}, and these myst return something
# that tells the caller to bypass the check_relay stuff - we do not want
# any ORDB activity for these.
#
# $@            rewrite and return immediately
# $# {msg}      magic token the caller is expecting
#
# If the {msg} is "error" we fail the recipient, but anything else is
# enough to short-circuit the rest of the "check" checks
#

R remove $* @ customer.com       $@ $# OK - "remove" bypasses check_relay
R bounce $* @ customer.com       $@ $# OK - "bounce" bypasses check_relay
R service @ customer.com         $@ $# OK - "service" bypasses check_relay


# Do the DB lookups. The rules of the form
#
#       $(access To:$1  $: NOTFOUND )
#
# take the To:$1 part and look it up in the DB. If it's found, the text
# in the DB replaces the workspace, but otherwise we use the "NOTFOUND"
# text (which can be anything). Since we have no if/else code here, we
# have to use the workspace to carry our status. So by having an initial
# token of "OK", and use this in the defaults, we can always recognize a
# workspace that has not yet satisfied a match.
#
# We try matches of the name as provided to us, then we strip off the
# trailing domain part and try again. When we are done with all the
# matches we care to try, if we're still left with "OK" as the first
# token, this

R $*                            $: OK <$1>
R OK <$*>                       $: $(access To:$1 $: OK <$1> $)
R OK <$- @ $m >                 $: $(access To:$1 $: OK <$1 @ $m> $)

# If we got here with OK at the start of the buffer, there were no matches
# and therefor no restrictions.  We don't really need to report the
# "No RCPT restriction on <addr>", but this is helpful for debugging.
#

R OK $*                         $@ No RCPT restrictions on $1

#
# If we got here, the workspace contains the looked up value from the
# DB (without any "OK" prefix). We allow GONE or REJECT to mean the
# same failure, otherwise we use the message from the user directly.
#
# NOTE: 5.1.6 =  permanent/addressing syntax/mailbox moved
#
# NOTE: we require the $* after the REJECT and GONE words in case the user
# puts something like this the access file:
#
#       Relay:foo.com           RELAY           we like these guys
#
# Here, the "we like these guys" is part of the data value!
#

R REJECT $*     GONE
R GONE $*       $#error $@ 5.1.6 $: "550 User no longer here (no forwarding available)"
R $*            $#error $@ 5.1.6 $: $1

Conversion to Postfix

Even with all this background in Sendmail, some years ago I nevertheless migrated all of my own and my customer mailservers to Postfix — it's been a tremendous win for all of us.

I was nervous about making this jump because I found it hard to believe that postfix could possibly be as flexible as sendmail: I was delighted to discover that it really was.

Postfix has been a slam-dunk win in every way, and I've actually managed to convert customers from one to the other in less than two hours (though of course, not all installations will go this well.