Recipients block for processing individual recipients

A single mail
message may have many recipients, and in many cases the actions
of your spam filter should vary depending on the recipients (you
might, for example, want all messages to your account to get
through even if the same message would be blocked if sent to any
other user).

The recipient block
(recipients...end recipients) is processed once for each
recipient of the message.

Inside the
'recipients' block there is a dummy variable defined
'recipient' which is the specific recipient in question.

All the action's
(except, bounce, drop) refer to the recipient only, not to the
entire message, so when one of those actions that normally
terminates message processing is encountered (accept, bounce,
drop, etc) instead the action is applied only to that
recipient and the recipient block is restarted with the next
recipient defined.

(Example of mfilter
rule to do processing 'per recipient')

recipients
if (isin("recipient","manager@this.domain")) accept "Always accept for me so spammers can talk to me"
if (isin("recipient","sales@your.domain")) then
if (isin("subject","order")) then
# Make a Duplicate of sale order
call forward_cc("sales_copy@your.domain")
end if
end if
end recipients

Miscellaneous

Line Continuation

Lines can be
continued by ending the line in a '\' character

Quoting Strings

All strings
and header names should be within double quotes, sometimes you
may get away without doing this, but we don't guarantee this
will work in future. e.g. use: exists("Supersedes") not
exists(Supersedes); quotes can be escaped in the usual way, e.g.
"This \"Word\" has quotes around it"

Assignments

Assignments
are processed at compile time, variables DO NOT exist at run
time. Do not think of this as a programming language, but rather
as a list of rules that are processed with each incoming
message. Real run-time variables only exist in the form of the
ifflag("xxx") function and the setflag("xxx") action.

For example, the
following is NOT VALID, as the assignment is processed before
the rules are run. The rejection would always read "big
message"

$fred = "small message"
if (lines()>100) then
$fred = "big message" (this will not work as expected)
end if
reject $fred

Odd stuff

The statement 'do_bounce_fast'
should appear at the end of your mfilter.rul file, and it is
used by the rexp_fast() rules. rexp_fast acts just
like rexp() but it is much faster because it searches the
message once for all of the rules in question, each rule must
start with two simple non 'regular expression' characters.
This enables mfilter to generate a hash table of all the
regular expressions it's going to search for and then it can
efficiently apply only the ones that appear to match as it
runs through the message. Also rexp_fast includes the score to
apply if the message matches the rule.

time_hour() - returns the 'hours' 0-23, useful for rules that
apply at different times of day
time_min() - returns the minutes
isimage() - True if message contains an image
isjpg() - True if message contains a jpeg image
ispdf() - True if message contains a pdf file
image_size() - Approx size of image in bytes
nimage() - Approx number of images found in message
islocal() - Message is to a local user not an outgoing message
isloggedin() - Message is from a logged in local user
is_dayofweek("monday,tuesday") - True on those days of the
week.

Notes

The "header"
parameter can be any normal header, such as "Subject", "From" or
"To". However, the are some additional pseudo-headers than can
also be used as parameters in any function which takes a
"header" parameter:

"head": refers to the entire message header.
"body": refers only to the message body (after any necessary
decoding)
"urls": refers to any urls found in the body

Function Descriptions

call add_header("Header: header
information")

Used to add a header to a message. eg

if (isin("x-spamdetect","****") then
call add_header("X-MailScanner-SpamCheck: LEVEL=****")
end if

NOTE: This will cause bounces if used in local.rul or
simple.rul, it can only be used in mftiler.rul

Requires Version 3.8 or
later.

allmod("header")

This returns true if all
the newsgroups in the specified header are moderated.

exists("header")

This is true if the header
exists in the message and is non zero in length, eg: if
(exists("supersedes")) then reject "We don't like supersedes
headers"

head_len("header")

Returns the length of the
named header, e.g.

if (head_len("date")>60) bounce "Naughty message"

isbase64()

This is true if the
message appears to contain base64 binary encoded data.

isbinary()

This is true if the
message has binary data either base64 encoding or uuencoded data.

isencodedhtml()

This is true if the
message appears to contain MIME or uuencoded HTML instead of plain
text data.

isencodedtext()

This is true if the
message appears to contain MIME or uuencoded text data. This
will always be true if isencodedhtml() returns true.

isencodedurl()

This is true if the
message appears to contain an uuencoded URL reference.

isflag("flag-name")

Used to check whether a
flag variable has been defined as true. This can be done with the
setflag("flag-name") action, e.g.

if (size()>100000) setflag("bigitem")
if (isimage()) setflag("bigitem")
if (isflag("bigitem")) reject "It was a big item or had a picture in it"

ishtml()

This is true if the
message appears to contain HTML instead of plain text data.

isimage()

This is true if the
message appears to contain a picture (either MIME or uuencoded)

isin("header","string-not-case-sensitive")

This is a simple 'content'
searching function if the named header contains the string (a non
case sensitive match is used) eg:

This would reject a
message containing a subject of "Get your Free pictures here" it
would also reject a message containing a subject of "Is there any
real freedom in the world?" so it's probably not a good rule :-)

lines()

This returns the number of
lines in the message.

match("header","wildcard")

This function applies a
simple wild card matching algorithm as is typically used to match
file names, eg:

match("From","*@netwin.co.nz*")

would match against a message from that domain.

matchall("header","wildcardlist")

Used for matching a
single wild card
against a header which contains a list of values, like
Newsgroups:, Path: etc..., The match is TRUE only if all entries
in the list match, eg:

if (matchall("Newsgroups","news.filters.*")) accept "It is only in the filters list so we will accept it"

matchone("header","wildcardlist")

Identical to the above
function but returns 'TRUE' if any match occurs.

rexp("header","regular-expression") This function searches the named header
for a regular expression, the matching is not case sensitive,
use rexp_case() for a case sensitive version.

This is just like
rexp, but it does the search more efficiently, the first 2
characters of regular expression must be plain ascii (not a
regular expression) if it's found in the body of the message
then the score is added to the spam_detect header

size()

Returns the size in
bytes of the current message can be used with > and <
operators.

call spamdetect(n,"reason")

This function can be
used to mark a message as possible spam, the 'n' is a
(floating-point) number and each time this function is called
for a message the total is increased, then finally a header is
added to the message;

X-SpamDetect: <stars>: <score> <reason1>
[reason2 [reason3 ... ]]

<stars> is a string of n stars, where n is the total score
(capped at 20)
<score> is the total spam score

The idea is that
users can then set their mail clients to filter messages based
on this pseudo header. For instance, filtering any message
with "******" in its X-SpamDetect header will throw out any
message with a score of 6 or more.

Please note that "local.rul" should be used for adding scoring
for ASPAM not mfilter.rul.

call spawn("program.exe $FILE$")

This function runs a
program on each message the $FILE$ macro is replaced by a
temporary file name containing the actual mail message. The
return value of the program (return n; in main() function) is
returned by this 'spawn' function, so it can be used to filter
the message or allow it to continue. eg:

if (spawn("d:/path/xfilter.exe $FILE$")) reject "That was spam according to xfilter"

NOTE: The mfilter
is only passed the first 14k of each message, and so the
spawned program also only gets the first 14k not the entire
message.

Actions

accept "reason"

Accepts the current
article reporting the "reason" specified in the log files.

clearflag("flag-name")

Used to set the
specified flag variable to the false state.

forward "remote@address.com"

Forwards the message to
the specified address and terminates processing.

call forward_cc("new@email.address")

Sends the current
message to this new Email address in addition to any existing
destination users.

reject "reason" (or bounce "reason")

Rejects the current
article reporting the "reason" specified in the log files and to
the user

If the named header
matches the 'wildcard_match_pattern' then the replacement
pattern is applied, e.g.

replace("from","*@*.domain.name","BOB_%1@%2.other.name")

Subject:
"joe@this.domain.name"

Would be translated to:

Subject:
"BOB_joe@this.other.name"

call
report("manger@email.address","subject of message")

Sends an Email,
including the top part of the offending message, to the
specified person, with the specified subject. This is intended
when you want to be alerted to something but don't want to
simply forward the message itself which may be 'confusing' as it
would look like the message had been sent to the manager
directly.

setflag("flag-name")

Used to set the
specified flag variable to the true state.

Regular Expression Syntax - In Brief

Please note you need
to escape spaces in this implementation.
eg:

sweepstake lottery / international program
sweepstake lottery/ international program
sweepstake lottery /international program

. (period)
represents any one character. [] (brackets) contain a set of characters from which a
match can be made. It corresponds to one character in the
search string. \ (backslash) is an escape character which means that
the next character will not have a special meaning. * (asterisk) is a multiplier. It will match zero or
more of the previous character. (Note: it is not a wildcard
character as in file names.) ? (question mark) is a multiplier. It will match zero
or one of the previous character. (Note: it is not a wildcard
character as in file names.) + (plus) is a multiplier. It will match one or more of
the previous character. {} (squiggly brackets) contain a number which specifies
an exact number of the previous character, or range {2,3} [^] (brackets containing caret and other characters)
means any characters except the character(s) after the caret
symbol
in the brackets. ^ (caret) is the start of the line. $ (dollar) is the end of the line. (Note the following \< \> (begin and end word) are
not implemented, use \b instead)

# Do some processing which is specific to individual recipients
recipients
if (isin("recipient","manager@this.domain")) accept "Always accept for me so spammers can talk to me"
if (isin("recipient","sales@your.domain")) then
if (isin("subject","order")) then
# Make a Duplicate of sale order
call forward_cc("sales_copy@your.domain")
end if
end if
end recipients

# Check for some known spammers and naughty subjects
if (rexp(subject,$free_pictures)) bounce "No emails about free pictures"
if (rexp(from,$bad_guys)) bounce "No emails from black listed people thanks"
# Strip local node names from from addresses:
call replace("From","*@*.parts.co.nz","%1@parts.co.nz")

accept "Great, we liked the message"

Example 2:

We want to block any message that has been found in SURBL
database. We will use the exists function to check if that
header exists.

if (exists("X-Surbl")) reject "Your SPAM is not wanted here."

You can easily change that to drop the message silently if you
prefer

if (exists("X-Surbl")) drop "SURBL SPAM is not wanted here."

(The reason will be logged so still important to put there)

Example 3:

We want to block any message with no subject header. SurgeMail
adds a subject header if it is missing so we have to match on
the text that SurgeMail adds.

if (isin("Subject","(No subject header)")) bounce "No Subject header"

Example 4:

We want to block any message with an empty subject header.

if (head_len("Subject")<1) bounce "Emtpy Subject header"

Example 5:

I have a user fred in one of my local domains localdomain.com I
only want him to be able to send to other users at
localdomain.com and not to any other domains.

recipients
if (isin("from","fred@localdomain.com")) then
if (!isin("recipient", "localdomain.com")) bounce "Sorry you can only send to localdomain.com"
end if