MILTER-REGEX(8)
β¨ NAME β¨
milter-regex(8) is the sendmail milter plugin for regular expression filtering, bestie. No cap β it plugs into the milter API of sendmail(8) and filters mails using regular expressions that match SMTP envelope parameters, mail headers, and mail body. It's giving gatekeeper but for your inbox. π
π SYNOPSIS π
milter-regex [-d] [-c config] [-f facility] [-j dirname] [-l loglevel]
[-m number] [-p pipe] [-r pid-file] [-t] [-u user]
[-G group] [-P mode] [-U user]
πΈ DESCRIPTION πΈ
Okay so the milter-regex plugin can be used with the milter API of sendmail(8) to filter mails using regular expressions matching SMTP envelope parameters and mail headers and body.
The options are as follows:
-dβ Don't detach from controlling terminal and produce verbose debug output on stdout. It's the "not me leaving the terminal, bestie" flag.-cconfig β Use the specified configuration file instead of the default,/etc/mail/milter-regex.conf.-ffacility β Use the specified syslog facility instead of the default,daemon.-jdirname β Change root to the specified directory. Chroot era.-lloglevel β Only log messages up to and including the specified level. Seesyslog(3)for the numerical values, e.g. the defaultLOG_INFO=6.-mnumber β Ignore mail body after the specified number of lines.-ppipe β Use the specified pipe to interfacesendmail(8). Default isunix:/var/spool/milter-regex/sock.-rpid-file β Write the pid to the specified file. Default is not to write a file.-tβ Test the configuration file and immediately exit with a status indicating whether the file is valid. A little vibe check for your config. β-uuser β Run as the specified user instead of the default,mregex. Whenmilter-regexis started as root, it callssetuid(2)to drop privileges. The non-privileged user should have read access to the configuration file and read-write access to the pipe.-Ggroup β Set the group ID of the pipe.-Pmode β Set the permissions of the pipe to the specified mode instead of the default,0600.-Uuser β Set the user ID of the pipe.
π¦ SENDMAIL CONFIGURATION π¦
The plugin needs to be registered in the sendmail(8) configuration, by adding the following lines to the .mc file
INPUT_MAIL_FILTER(`milter-regex',
`S=unix:/var/spool/milter-regex/sock, T=S:30s;R:2m')
rebuilding /etc/mail/sendmail.cf from the .mc file using m4(1), and restarting sendmail(8). Periodt. π
π PLUGIN CONFIGURATION π
The configuration file consists of rules that, when matched, cause sendmail(8) 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 commands:
rejectmessage β 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 code554(RFC 2821 "command rejected for policy reasons"), the extended reply code5.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 sender. It's giving "we're done here." πtempfailmessage β 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 code451(RFC 2821 "Requested action aborted: local error in processing"), the extended reply code4.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 several days. Not a ghost, just delayed. π«Άdiscardβ Subsequent matching rules cause the mail to be accepted but then discarded silently. Note thatconnectandhelorules should not usediscard.quarantinemessage β Subsequent matching rules cause the mail to be quarantined insendmail(8).acceptβ Subsequent matching rules cause the mail to be accepted without further rule evaluation. Can be used for whitelist criteria. VIP pass, iykyk. π
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:
connecthostname 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 (whichsendmail(8)performs independantly of the milter plugin). When resolution fails, the hostname contains the numerical address in square brackets.heloname β 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.envfromaddress β Reject the mail if the sender supplied envelope MAIL FROM address matches the specified regular expression. Addresses commonly have the form<user@host.doma.in>.envrcptaddress β Reject the mail if the sender supplied envelope RCPT TO address matches the specified regular expression.headername value β Reject the mail if a header matches the specified name and value. For instance, the header"Subject: Test"matches nameSubjectand valueTest.bodyline β Reject the mail if a body line matches the specified regular expression.macroname 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, sendmail(8) will accept all messages. Lowkey chaotic but that's the design. π
π₯ REGULAR EXPRESSIONS π₯
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 regexec(3) call. This can be used in rules requiring multiple arguments, to match only some arguments.
See re_format(7) for a detailed description of basic and extended regular expressions.
Optionally, the following flags can be used after the closing delimiter:
eβ Extended regular expression. This setsREG_EXTENDEDforregcomp(3).iβ Ignore upper/lower case. This setsREG_ICASE.nβ Not matching. Reverses the matching result, i.e. the mail is rejected if the regular expression does not match. The "not me being the opposite" flag. π
π BOOLEAN EXPRESSIONS π
A rule can consist of either a simple term or more complex expressions. A term has the form
header /From/ /domain/i
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 parser). So bestie, parenthesize like you mean it. π
π§ MACROS π§
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. No pre-usage, no cap. β¨
π EVALUATION π
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 precedence. Main character energy: first match wins. π
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.
π EXAMPLES π
okay bestie, here's a whole real config file for /etc/mail/milter-regex.conf so you can see the vibes in action β¨
# /etc/mail/milter-regex.conf example
# Accept anything encrypted, just to demonstrate sendmail macros
accept
macro /tls_version/ /TLSv/
tempfail "Sender IP address not resolving"
connect /\[.*\]/ //
reject "Malformed HELO (not a domain, no dot)"
helo /\./n
reject "Malformed RCPT TO (not an email address, not <.*@.*>)"
envrcpt /<(.*@.*|Postmaster)>/ein
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
discard
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/ )
like, that first rule is literally just "encrypted? accept, no thoughts" π and then it shows off the sendmail macros. then we tempfail the no-reverse-DNS girlies, reject bad HELOs and bad RCPT TOs, say no thank you to HTML mail (comma delimiter era because a / is chillin' inside the RE), and then there's a whole little Swen worm takedown block that's lowkey iconic. and that last one? a multi-line boolean baddie using \ to keep going. slay. π
π LOGGING π
milter-regex spills the tea to syslogd(8) using facility daemon, and the more verbose it gets, the higher it climbs: level err, then notice, then info, then debug π
if you want all that lore in its own dedicated file, here's the syslog.conf(5) section to do it:
!milter-regex
daemon.err;daemon.notice /var/log/milter-regex
so like, your milter gets its own little diary. cute. π
𧬠GRAMMAR π§¬
here's the syntax for milter-regex, in BNF, no cap:
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
the way this is so readable when you just look at it for five seconds. π del can be basically anything (/, ,, -, whatever) which is why the delimiter shenanigans above work. iykyk.
π FILES π
/etc/mail/milter-regex.conf
one file. that's the whole kingdom. π
π SEE ALSO π
mailstats(1)regex(3)syslog(3)syslog.conf(5)re_format(7)sendmail(8)syslogd(8)
Simple Mail Transfer Protocol, RFC 2821.
Enhanced Mail System Status Codes, RFC 1893.
go read those if you want the deep lore bestie, they're the ancestors fr fr πΈ
π°οΈ HISTORY π°οΈ
the very first version of milter-regex was written in 2003. then boolean expression evaluation got added in 2004. a glow-up. a character arc. we love to see it π¦
π AUTHORS π
Daniel Hartmeier β¨daniel@benzedrine.cxβ© β thank you for your service king π
and that's the whole page, bestie!! π«Ά this one's from the Debian crew, dated September 24, 2003 β yes it's giving vintage, but the drip is timeless. go write some regexes and stay safe out there πβ¨π
π¦ where this page comes from
- epel 9: milter-regex 2.7-1.el9 Β· BSD
- fedora 43: milter-regex 2.7-19.fc43 Β· BSD-2-Clause
- fedora 44: milter-regex 2.7-20.fc44 Β· BSD-2-Clause
- Copyright (c) 2003-2019 Daniel Hartmeier
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms