sendmail milter plugin for regular expression
plugin can be used with the
milter API of
to filter mails using regular expressions matching SMTP envelope parameters
and mail headers and body.
The options are as follows:
- Don't detach from controlling terminal and produce verbose debug output on
- Don't send to syslog messages with priority higher than LOG_NOTICE.
- Use the specified configuration file instead of the default,
- Use the specified pid file to write to. Default is:
- Change root to the specified directory.
- Only log messages up to and including the specified level. See
for the numerical values, e.g. LOG_INFO=6.
- Ignore mail body after the specified number of lines.
- Use the specified pipe to interface
Default is unix:/var/run/milter-regex/sock.
- Run as the specified user instead of the default, _milter-regex. When
milter-regex is started as root, it
to drop privileges. The non-privileged user should have read access to the
configuration file and read-write access to the pipe.
The plugin needs to be registered in the
configuration, by adding the following lines to the .mc file
rebuilding /etc/mail/sendmail.cf from the .mc file using
The configuration file consists of rules that, when matched, cause
to reject mails. Emtpy lines and lines starting with # are ignored, as well as
leading whitespace (blanks, tabs). Trailing backslashes can be used to wrap
long rules into multiple lines. Each rule starts with one of the following
- reject <message>
- Subsequent rules cause the mail to be rejected with a permanent error
consisting of the specified text part. The SMTP reply consists of the
three-digit code 554 (RFC 2821 "command rejected for policy
reasons"), the extended reply code 5.7.1 (RFC 1893 "Permanent
Failure", "Security or Policy Status", "Delivery not
authorized, message refused") and the text part (which defaults to
"Command rejected", if not specified). This is a permanent
failure, which causes the sender to remove the message from its queue
without trying to retransmit, commonly generating a bounce message to the
- tempfail <message>
- Subsequent matching rules cause the mail to be rejected with a temporary
error consisting of the specified text part. The SMTP reply consists of
the three-digit code 451 (RFC 2821 "Requested action aborted: local
error in processing"), the extended reply code 4.7.1 (RFC 1893
"Persistent Transient Failure", "Security or Policy
Status", "Delivery not authorized, message refused") and
the text part (which defaults to "Please try again later", if
not specified). This is a temporary failure, which causes the sender to
keep the message in its queue and try to retransmit it, commonly for
- Subsequent matching rules cause the mail to be accepted but then discarded
silently. Note that connect and
helo rules should not use
- quarantine <message>
- Subsequent matching rules cause the mail to be quarantined in
- Subsequent matching rules cause the mail to be accepted without further
rule evaluation. Can be used for whitelist criteria.
A command is followed by one or more expressions, each causing the previous
command to be executed when matched. The following expressions can be used:
- connect <hostname> <address>
- Reject the connection if both the sender's hostname and address match the
specified regular expressions. The numerical address is either dotted-quad
(IPv4) or coloned-hex (IPv6). The hostname is the result of a DNS reverse
resolution of the numerical address (which
performs independantly of the milter plugin). When resolution fails, the
hostname contains the numerical address in square brackets.
- helo <name>
- Reject the connection if the sender supplied HELO name matches the
specified regular expression. Commonly, the sender supplies his
fully-qualified hostname as HELO name.
- envfrom <address>
- Reject the mail if the sender supplied envelope MAIL FROM address matches
the specified regular expression. Addresses commonly have the form
- envrcpt <address>
- Reject the mail if the sender supplied envelope RCPT TO address matches
the specified regular expression.
- header <name> <value>
- Reject the mail if a header matches the specified name and value. For
instance, the header "Subject: Test" matches name Subject and
- body <line>
- Reject the mail if a body line matches the specified regular
- macro <name> <value>
- Reject the mail if a sendmail macro value matches.
The plugin regularly checks the configuration file for modification and reloads
it automatically. Signals like SIGHUP will terminate the plugin, according to
the milter signal handler. The plugin reacts to any kind of error, like syntax
errors in the configuration file, by failing open, accepting all messages.
When the plugin is not running,
will accept all messages.
The regular expressions used in the configuration rules are enclosed in
arbitrary delimiters, no further escaping is needed.
The first character of an argument is taken as the delimiter, and all subsequent
characters up to the next occurance of the same delimiter are taken literally
as the regular expression. Since the delimiter itself cannot be part of the
regular expression (no escaping is supported), a delimiter must be chosen that
doesn't occur in the regular expression itself. Each argument can use a
different delimiter, all characters except spaces and tabs are valid.
Two immediately adjacent delimiters form an empty regular expression, which
always matches and requires no
call. This can be used in rules requiring multiple arguments, to match only
for a detailed description of basic and extended regular expressions.
Optionally, the following flags can be used after the closing delimiter:
- Extended regular expression. This sets REG_EXTENDED for
- Ignore upper/lower case. This sets REG_ICASE.
- Not matching. Reverses the matching result, i.e. the mail is rejected if
the regular expression does not match.
A rule can consist of either a simple term or more complex expressions. A term
has the form
and expressions can be built combining terms with operators "and",
"or", "not" and parentheses, as in
header /From/ /domain/i and body /money/
( not header /From/ /domain/ ) and ( body /sex/ or body /fast/ )
Operator precedence should not be relied on, instead parentheses should be used
to resolve any ambiguities (they usually produce syntax errors from the
Macros allow to store terms or expressions as a name, and $name can be used as
term within other rules, expressions or macro definitions. Example:
friends = header /^Received$/ /^from [^ ]*(ork.net|home.com)/e
attachments = header ,^Content-Type$, ,multipart/mixed, and \
body ,^Content-Type: application/,
executables = $attachments and body ,name=".*.(pif|exe|scr)"$,e
reject "executable attachment from non-friends"
$executables and not $friends
Macro names must begin with a letter and may contain alphanumeric characters and
punctuation characters. Reserved keywords (like "reject" or
"header") cannot be used as macro names. Macros must be defined
before use, the definition must precede the use in the configuration file,
read from top to bottom.
Rules are evaluated in the order specified in the configuration file, from top
to bottom. When a rule matches, the corresponding action is taken, that is the
last action specified before the matching rule.
The plugin evaluates the rules every time a line of mail (or envelope) is
received. As soon as a rule matches, the action is taken immediately, possibly
before the entire mail is received, even if further lines might possibly make
other rules match, too. This means the first rule matching chronologically has
If evaluation for a line of mail makes two (or more) rules match, the rule that
comes first in the configuration file has precedence.
Boolean expressions are short-circuit evaluated, that means "a or b"
becomes true as soon as one of the terms is true and "a and b"
becomes false as soon as one of the terms is false, even if the other term is
not known, possibly because the relevant mail line has not been received yet.
# /usr/local/etc/milter-regex.conf example
# Accept anything encrypted, just to demonstrate sendmail macros
macro /tls_version/ /TLSv/
tempfail "Sender IP address not resolving"
connect /\[.*\]/ //
reject "Malformed HELO (not a domain, no dot)"
reject "Malformed RCPT TO (not an email address, not <.*@.*>)"
reject "HTML mail not accepted"
# use comma as delimiter here, as / occurs within RE
header /^Content-type$/i ,^text/html,i
body ,^Content-type: text/html,i
# Swen worm
header /^(TO|FROM|SUBJECT)$/e //
header /^Content-type$/i /boundary="Boundary_(ID_/i
header /^Content-type$/i /boundary="[a-z]*"/
body ,^Content-type: audio/x-wav; name="[a-z]*\.[a-z]*",i
# Some nasty spammer
reject "Business Corp spam, get lost"
body /^Business Corp. for W.& L. AG/i and \
( body /043.*317.*0285/ or body /0041.43.317.02.85/ )
sends log messages to
daemon and, with increasing
err, notice, info and debug. The
section can be used to log messages to a dedicated file:
file = ( rule | macro ) file
rule = action expr-list
action = "reject" msg | "tempfail" msg | "discard" |
"quarantine" msg | "accept"
msg = ( '"' | "'" ) string ( '"' | "'" )
expr-list = expr [ expr-list ]
expr = term | term "and" expr | term "or" expr | "not" term
term = '(' expr ')' | "connect" arg arg | "helo" arg |
"envfrom" arg | "envrcpt" arg | "header" arg arg |
"body" arg | "macro" arg arg | '$' name
arg = del regex del flags
del = '/' | ',' | '-' | ...
flags = [ 'e' ] [ 'i' ] [ 'n' ]
macro = name '=' expr
Simple Mail Transfer
Protocol, RFC 2821.
Enhanced Mail System Status
Codes, RFC 1893.
The first version of
in 2003. Boolean expression evaluation was added in 2004.
Daniel Hartmeier ⟨email@example.com⟩