If you have problems getting mfilter to run you can use these two settings in surgemail.ini, they will provide logging to show exactly what is going on.
g_mfilter_trace "true" g_mfilter_noisey "true"
then examine 'mail.log' after sending in a test message.
Assignment
Action
if (Conditional_Expression) [and (Conditional_Expression)...] Action
else
end if
call built_in_function()
$variable_name = "quoted string" [+ "quoted string" [+ $variable ...]]
$variable_name = function()
accept "reason" | bounce "reason" | drop "reason" | forward "user@domain" | then | setflag("flagname") | clearflag("flagname")
Any pre-defined function, e.g. isbinary()
isin("subject","free pictures")
Numeric comparisons, e.g. lines()>100
Simple NOT operator, e.g. if (!isbinary()) reject "Only binaries allowed here mate!"
Calculations are NOT permitted, e.g. lines()+10 would fail
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
Lines can be continued by ending the line in a '\' character
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 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
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.
accept"reason" (Terminates processing)
bounce "reason" (Terminates processing)
reject "reason" (same as bounce)
forward "reason" (Terminates processing) (redirect is a synonym for this action)
print "reason" (Prints debugging line to log file mail.log)
setflag("flagname") "reason"
clearflag("flagname") "reason"Functions that have actions but must be proceeded by the 'call' action as they are really functions and must be on a line of their own (not on the end of an if statement)
call forward_cc("new@email.address)
call replace("header_name","wildcard_match_pattern","replacement_pattern")
call report("manger@email.address","subject of message")
call add_header("Header: header information")
allmod()
exists("header")
head_len("header")
isbase64()
isbinary() )
isencodedhtml()
(isencodedtext()
isencodedurl()
isflag("flag-name")
ishtml()
isimage()
isin("header","string-not-case-sensitive")
lines()>3)
match("header","wildcard")
matchall("header","wildcardlist")
matchone("header","wildcardlist")
rexp("header","regular-expression")
size()
call spamdetect(n,"reason")
call spawn("d:/surge/filter.exe $FILE$")
New Functions
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.
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
Used to add a header to a message. eg
if (isin("x-spamdetect","****") then call add_header("X-MailScanner-SpamCheck: LEVEL=****") end if
Requires Version 3.8 or later.
if (head_len("date")>60) bounce "Naughty message"
if (size()>100000) setflag("bigitem") if (isimage()) setflag("bigitem") if (isflag("bigitem")) reject "It was a big item or had a picture in it"
This is true if the message appears to contain HTML instead of plain text data.
if (isin("Subject","Free")) reject "Probably a spammer selling something"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 :-)
match("From","*@netwin.co.nz*")would match against a message from that domain.
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"
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
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.
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.
Accepts the current article reporting the "reason" specified in the log files.
replace("from","*@*.domain.name","BOB_%1@%2.other.name")
Subject: "joe@this.domain.name"Would be translated to:
Subject: "BOB_joe@this.other.name"
Please note you need
to escape spaces in this implementation.
eg:
sweepstake lottery / international program
sweepstake lottery/ international program
sweepstake lottery /international program
So what you want is this. Just put slashes in front of the spaces.
sweepstake lottery( / | /|/ )international program
if (rexp("subject","sweepstake lottery(\ /\ |\ /|/)international program")) bounce "a"
\s = white
space
\S = not white space
\d = digit
\D = not digit
\b = word boundary
\B = not word boundary
\x00 = Hex character
. (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)
[:alpha:]
represents any alphabetic letter.
[:digit:] represents any single-digit number.
[:blank:] represents a space or tab.
Lookahead operator
Free(?!dom|bsd) matches freesex but not freedom or FreeBSD
OR operator
| (pipe) is OR. It requires that the joined expressions have
parentheses around them.
$sex = "fuck|xxx|sex" $free = "free(?!dom|bsd|nix|serve)" $pics = "pi[cx]" $free_pictures = $free + $pics $bad_guys = + "|freepictures|jus.?.?\.doi.?.?\.to|great\.site|webbinaries" + "|yad.?.?.?\.ion.?.?\.org|freehidden|joy.?.?\.to.?.?\.al|from.?behind" + "|love(youhon|ergirl|chatting|stofuck)|forever\.yours|\@ju.?.?\.sex|town.\girl|beachbums" \i
# 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" call replace("From","*@*.parts.co.nz","%1@parts.co.nz")
accept "Great, we liked the message"
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)
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"
We want to block any message with an empty subject header.
if (head_len("Subject")<1) bounce "Emtpy Subject header"
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