Instructions for filtering messages come from a file, using a simple language. The language is loosely structured, it is based on pattern matching. The language has a distinct lexical, and syntactical structure, but uses common features found in other UNIX pseudo-languages.
If the filtering instructions do not exist, maildrop delivers the message to the default mailbox, without doing any additional processing, making it indistinguishable from the usual mail delivery agent.
It is important to note that maildrop reads and parses the filter file before doing anything. maildrop will print an error message, and terminate with the exit code set to EX_TEMPFAIL, if the filter file has any syntax errors. A compliant mail transport agent should re-queue the message for a later delivery attempt. Hopefully, most simple syntax errors will not cause mail to be bounced back, if the error is caught and fixed quickly.
FILE="IN.junk"This statement puts the text "IN.junk" (without the quotes) into a variable whose name is FILE. Later, the contents of a variable are accessed by using the $ symbol and the name for the variable. For example:
to $FILEThis will deliver the current message to the mailbox file (or a maildir directory) named "IN.junk".
maildrop initially creates variables from the environment variables of the operating system, UNLESS maildrop runs in delivery mode. Each variable in the operating system environment becomes a variable in maildrop. When running in delivery mode, maildrop does not import any environment for security reasons. In all cases, maildrop resets the HOME, DEFAULT, SHELL, PATH, LOCKEXT, LOCKREFRESH, LOCKSLEEP, LOCKTIMEOUT, SENDMAIL and LOGNAME variables to their default values.
All environment variables become internal variables in the filtering language. When maildrop runs a command, all internal variables are exported back as environment variables. Changes to internal variables, made by the filter file, are reflected in the exported environment.
In maildrop the end of line is a lexical token. In order to continue a long statement on the next line, terminate the line with a backslash character.
NOTE: a backslash followed by either a backslash, or a matching quote, is the only situation where the backslash character is actually removed, leaving only the following character in the actual text literal. If a backslash character is followed by any other character, the backslash is NOT removed.
Multiple text literals in a row are automatically concatenated, even if they use different quotes. For example:
FOOBAR="Foo"'bar'Sets the variable FOOBAR to the text "Foobar".
MAILBOX="$HOME/Mailbox"Sets the variable MAILBOX to the contents of the variable HOME followed by "/Mailbox". Variable names must begin with an uppercase letter, a lowercase letter, or an underscore. Following that, all letters, digits, and underscores are taken as a variable name, and its contents replace the $ sign, and the variable name. It is possible to access variables whose name includes other characters, by using braces as follows:
MAILBOX="${HOME-WORD}/Mailbox"Inserts the contents of the HOME-WORD variable. If the variable does not exist, the empty text literal is used to replace the variable name. It is not possible to access variables whose names include the } character.
If the $ character is not followed by a left brace, letter, or an underscore, the $ character remains unmolested in the text literal. A backslash followed by the $ character results in a $ character in the text literal, without doing any variable substitution.
Variable substitution is not done in text literals which are surrounded by single quotes.
Text that includes ONLY letters, digits, and the following characters: _-.:/${}@ may appear without quotes. Note that this does not allow spaces, or backslashes to be entered, however the text is still variable-substituted, and the substituted text may contain other characters.
Also, note that patterns (see below) begin with the slash character. Normally, anything that begins with the slash is interpreted as a pattern. However, text immediately after "VARIABLE=" is interpreted as a string even if it begins with a slash. This is why something like
MAILDIR=/var/spool/mailworks as expected. Using quotes, though, is highly recommended. You must use quotes to set a variable to a lone slash, because an unquoted slash is interpreted as a division sign.
DIR=`ls`Loads the names of the files in the current directory into the DIR variable.
The output of the command will have all newline characters replaced by spaces, and leading and trailing spaces will be stripped (multiple spaces are not removed, though). Also, the contents of the message being delivered is made available to the command on standard input.
/pattern/:options"pattern" specifies the text to look for in the message.
"pattern" may not start with a space, because the leading slash will be interpreted as a division sign. If you must search for something that starts with a space, use something like "/[ ] ... /".
With the exception of the following characters, an exact match is looked for. The following special characters are used to specify complex patterns. If it is necessary to look for one of the following characters, verbatim, in the message, put a backslash in front of it. "x" and "abc" designates an arbitrary character, or a pattern, which may include other special characters as well.
To include the dash, or the left or the right bracket characters in
the set itself, prefix them with a backslash. Use two backslashes to include
the backslash character itself. (Other, historical ways of including these
special characters are permitted, but discouraged).
Normally, the pattern can be found anywhere within the message header or body (see below). However, putting the character ^ at the beginning of the pattern forces the pattern to be matched against the beginning of the line only. Putting the character $ at the end of the pattern forces the pattern to be matched against the end of the line only.
Elsewhere in the pattern, the $ sign is used for variable substitution (see above). To include the $ character in the pattern, prefix it with a backslash.
TEXT="This is a long \ text string"The backslash, the newline, and all leading whitespace on the next line is removed, resulting in "This is a long text string".
If neither 'h' or 'b' is specified, the pattern is matched against the header only. Specifying the 'b' option causes the pattern to be matched against the message body. Specifying both causes the pattern to be matched against the entire message.
Normally, each line in the message gets matched against the pattern individually. When applying patterns to a header, multi-line headers (headers split on several lines by beginning each continuation line with whitespace) are silently combined into a single line, before the pattern is applied. Specifying the 'w' flag causes the pattern to be applied to the whole part of the message that's being searched (which is specified via the presence, or the absence, of the h and b flags). Also, if the 'w' flag is used, but neither 'h' nor 'b' flags are used, the 'b' flag is the default, instead of the 'h' flag.
This flag also changes the way that ^ and $ is interpreted in patterns. Normally, those characters anchor the pattern at the beginning, or the end, of each line. When the 'w' flag is specified, ^ and $ anchors the pattern against the beginning, or the end, of either the header, body, or both.
maildrop can also do weighted scoring. In weighted scoring, multiple occurrences of the same pattern are used to calculate a numerical value.
To use a weighted search, specify the pattern as follows:
/pattern/:options,xxx,yyywhere xxx and yyy are two numbers. yyy is optional, if missing, it defaults to 1.
The first occurrence of the pattern is evaluated as xxx. The second occurrence of the pattern is evaluated as xxx*yyy, the third as xxx*yyy*yyy, etc... All occurrences of the pattern are added up to calculate the final score.
IMPORTANT: when the w option is not specified, maildrop does not recognize multiple occurrences of the same pattern in the same line. This is required in order to have the ^ and $ operators working correctly. For example:
/^Received:/:1This pattern counts how many Received: headers the message has, and does not recognize any occurrences of the text "Received:" anywhere else in the headers.
You must specify the 'w' option in order to take into account multiple occurrences of the same pattern in the message. This also activates all the usual semantics of the 'w' option. For example:
/[:upper:]/:wbD,1Counts the number of uppercase letters in the body of the message.
/^From:.*/matches a line of the form:
From: postmaster@localhostIn this case, the variable MATCH will be set to "From: postmaster@localhost", which can be used in subsequent statements. It is possible to use selective parts of the matched string by using the ! character in patterns. For example:
/^From: *!.*/matched against the same line will set MATCH to "From: " and MATCH2 to "postmaster@localhost". More than one ! character may be used. Subsequent matched text will be assigned to MATCH3, MATCH4, and so on.
When there is more than one way to match a string, maildrop favors matching as much as possible initially. For example:
/^To:.*,!.*/when matched against
To: joe@somewhere,bob@somewhere.else,gary@whoknowswherewill set MATCH to "To: joe@somewhere,bob@somewhere.else," and MATCH2 to "gary@whoknowswhere".
The MATCH variables are NOT set when weighted scoring is used,
since the same pattern is matched multiple times.
|| && < <= > >= == != lt le gt ge eq ne | & + - * / =~ /pattern/ /pattern/ ! ~ function()
maildrop uses the following concept of true/false: an empty text literal, or a text literal that consists of the single character "0" is a logical false value. Anything else is a logical true value.
maildrop uses the following concept of true/false: an empty text literal, or a text literal that consists of the single character "0" is a logical false value. Anything else is a logical true value.
NOTE - comparisons are not associative: a < b < c is an error. If it is absolutely necessary, use (a < b) < c.
NOTE - comparisons are not associative: a lt b lt c is an error. If it is absolutely necessary, use (a lt b) lt c. (But why would you?).
See "Patterns" for more information.
See "Patterns" for more information.
maildrop uses the following concept of true/false: an empty text literal, or a text literal that consists of the single character "0" is a logical false value. Anything else is a logical true value.
The result of the ~ operator is a bitwise complement of its right hand side expression. The right hand side expression is evaluated as a 32 bit integer, and the result of this operator is a bitwise complement of the result.
if ( /^From: *!.*/ ) { MATCH2=escape($MATCH2) if ( /^Subject:.*$MATCH2/ ) { . . . } }Performs the indicated activity if the contents of the From: header can also be found in the Subject: header. If the escape function is not used, then any special characters in the From: header would introduce unpredictable behavior, most likely a syntax error.
joe@domain.com (Joe Brown), "Alex Smith" <alex@domain.com>, tom@domain.comThe result of the getaddr function is the following string:
joe@domain.com<NL>alex@domain.com<NL>tom@domain.com<NL>Where <NL> is the newline character.
Note - because getaddr() interprets RFC822 loosely, it is not necessary to strip off the "To:" or the "Cc:" header from the string, before feeding it to getaddr(). For example, the following snippet of code takes all addresses in the message, and concatenates them into a single string, separated by spaces:
ADDRLIST="" foreach /^(To|Cc): .*/ { foreach (getaddr $MATCH) =~ /.+/ { ADDRLIST="$ADDRLIST $MATCH" } }Please note that in certain rare situations, RFC822 permits spaces to be included in E-mail addresses, so this example is just educational.
This is more than just a simple text search. Each header is parsed according to RFC822. Addresses found in the header are extracted, ignoring all comments and names. The remaining addresses are checked, and if "string" is one of them, hasaddr returns 1, otherwise it returns 0.
The comparison is case-insensitive. This actually violates RFC822 (and several others) a little bit, because the user part of the address may be (but is not required to be) case sensitive.
Leading whitespace (but not trailing whitespace, take care) is removed, and the remaining contents of each line are interpreted as a pattern which is matched against expr. As soon as the match is found, lookup returns "1". If no match is found after reading the entire file, lookup returns "0". For example:
if ( /^To: *!.*/ && lookup( $MATCH2, "badto.dat" ) { exit }And the file badto.dat contains the following two lines:
friend@public ^[^@]*$If a message has a To: header that contains the text "friend@public", or does not contain at least one @ character, then the message will be silently dropped on the floor ( maildrop will terminate without delivering the message anywhere).
options are the pattern matching options to use. The only supported option is "D" (the rest are meaningless, in this case).
NOTE: be careful with discarding messages like that. Pattern matching can be tricky, and a slight miscalculation can cause mail to be unintentionally discarded. It is much desirable to first deliver message to a separate folder or mailbox, and once the filter is verified to work correctly, change it so the messages are discarded completely.
In embedded mode, the lookup() function is restricted as follows. The name of the file may not begin with a slash, or contain periods. Furthermore, ".mailfilters/.lists/" is automatically prepended to the filename. When writing filtering recipes that go into $HOME/.mailfilters, please keep in mind that any files loaded by the lookup() function must be placed in $HOME/.mailfilters/.lists.
Statements are listed one per line. Multiple statements may be listed on the same line by separating them with semicolons. To continue a long statement on the next line, terminate the line with a backslash character.
VARIABLE=expressionAssigns the result of the expression to VARIABLE (note no leading $ in front of variable).
Please note that if VARIABLE is NOT surrounded by quotes that it may contain only letters, numbers, underscores, dashes, and a selected few other characters. in order to initialize a variable whose name contains non-standard punctuation marks, surround the name of the variable with quotes.
cc expressionThe cc statement is very similar to the to statement, except that after delivering the message maildrop continues to process the filter file, unlike the to statement which immediately terminates maildrop after the delivery is complete. Essentially, the message is carbon copied to the given mailbox, and may be delivered again to another mailbox by other cc or to statement.
See the to statement for more details.
dotlock expression { ... }maildrop automatically creates a lock when a message is delivered to a mailbox. Depending upon your system configuration, maildrop will use either dot-locks, or the flock() system call.
The dotlock statement creates an explicit dot-lock file. Use the flock statement to create an explicit flock() lock.
The expression is a filename that should be used as a lock file. maildrop creates the indicated dot-lock, executes the filtering instructions contained within the { ... } block, and removes the lock. The expression must be the name of the dot-lock file itself, NOT the name of the mailbox file you want to lock.
WARNING: with manual locking, it is possible to deadlock multiple maildrop processes (or any other processes that try to claim the same locks).
No deadlock detection is possible with dot-locks, and since maildrop automatically refreshes all of its dot-locks regularly, they will never go stale. You'll have maildrop processes hanging in limbo, until their watchdog timers go off, aborting the mail delivery.
echo expressionmaildrop will print the given text. This is usually used when maildrop is used in embedded mode, but can be used for debugging purposes. Normally, a newline is printed after the text. If text is terminated with a \c, no newline will be printed.
exception { . . . }The exception statement traps errors that would normally cause maildrop to terminate. If a fatal error is encountered anywhere within the block of statements enclosed by the exception clause, execution will resume immediately following the exception clause.
exitThe exit statement immediately terminates filtering. maildrop's return code is set to the value of the EXITCODE variable. Normally, maildrop terminates immediately after successfully delivering the message to a mailbox. The exit statement causes maildrop to terminate without delivering the message anywhere.
The exit statement is usually used when maildrop is invoked in embedded mode, when message delivery instructions are not allowed.
flock expression { ... }maildrop automatically creates a lock when a message is delivered to a mailbox. Depending upon your system configuration, maildrop will use either dot-locks, or the flock() system call.
The flock statement creates a manual flock() lock. Use the dotlock statement to create a manual dot-lock file.
The expression is the name of the file that should be locked. maildrop creates the lock on the indicated file, executes the filtering instructions contained within the { ... } block, and removes the lock.
WARNING: with manual locking, it is possible to deadlock multiple maildrop processes (or any other processes that try to claim the same locks). The operating system will automatically break flock() deadlocks. When that happens, one of the maildrop processes will terminate immediately. Use the exception statement in order to trap this exception condition, and execute an alternative set of filtering instructions.
foreach /pattern/:options { ... } foreach (expression) =~ /pattern/:options { ... }The foreach statement executes a block of statements for each occurrence of the given pattern in the given message, or expression. On every iteration MATCH variable will be set to the matched string. All the usual options may be applied to the pattern match, EXCEPT the following:
if (expression) { ... } else { ... }Conditional execution. If the expression evaluates to a logical true (note - parenthesis are required) then the first set of statements is executed. The else keyword, and the subsequent statements, are optional. If present, and the expression evaluates to a logical false, the else part is executed.
maildrop evaluates all expression as text strings. In the context of a logical expression, an empty string, or the number 0 constitutes a logical false value, anything else is a logical true value.
If the if part, or the else part consists of only one statement, the braces may be omitted.
NOTE: some grammatical contraptions here that may not be very obvious. If you get baffling syntax errors from maildrop, make sure that the braces, and the if statement, appear on separate lines. Specifically: the closing parenthesis, the closing braces, and the else statement, must be at the end of the line (comments are allowed), and there may not be any blank lines in between (not even ones containing comments only).
include expressionThe include statement reads a file, and executes filtering instructions contained in that file. Note that the include statement is processed when the current filter file is being executed. When maildrop reads the initial filter file, any syntax errors in the filtering instructions are immediately reported, and maildrop will terminate with a return code of EX_TEMPFAIL. Any errors in files specified by include statements are NOT reported, because those files will not be read until the include statement is itself executed.
If the specified file does not exist, or if there are any syntax errors in the file, maildrop reports the error, and terminates with a return code of EX_TEMPFAIL.
logfile expression log expressionLogging in maildrop is normally turned off. The logfile statement will instruct maildrop to log how the message has been disposed to the given file. The parameter is then name of the file. If the file exists maildrop will append to the file.
For each delivery (which means the to and cc statements, including delivery by default) maildrop will record the From: and the Subject: fields, together with the current time, in the log file.
The log statement may be used to add additional logging text to the log file. The log statement works exactly like the echo statement, except that the text is written to the logfile, instead of standard output.
to expressionThe to statement delivers the message to a mailbox. expression must evaluate to a valid mailbox. A valid mailbox is either a mailbox file, a maildir, or an external program (which includes forwarding to another address).
The to statement is the final delivery statement. maildrop delivers message, then immediately terminates, with the return code set to the EXITCODE environment variable. If there was an error while delivering the message, maildrop terminates with the EX_TEMPFAIL exit code. A properly-written mail transport agent should re-queue the message, and re-attempt delivery at some later time.
If expression begins with the | symbol, the remainder of the expression specifies an external program to run to handle the actual delivery. The SHELL variable specifies the shell to execute the given command. The message is provided to the command on standard input.
If expression begins with an exclamation mark - "!" - the remainder of the expression specifies a list of E-mail addresses to forward the message to. The list of E-mail addresses must be separated by spaces. The mail delivery agent specified by the SENDMAIL variable is run as an external program, with the list of E-mail addresses provided as parameters to the program.
Otherwise, maildrop delivers the message to the actual mailbox indicated. If expression is a directory, maildrop will assume that the directory is a maildir directory. maildir is a directory-based format used by Qmail. Otherwise, maildrop will deliver the message to a file, formatted in traditional mailbox format. maildrop will use either dot-locking, or flock()-locking when delivering the message to the file.
while (expression) { ... }The expression is repeatedly evaluated. Each time it evaluates to a logical true, the statements inside the braces are executed. When expression evaluates to a logical false, the while loop is over. Take care to avoid infinite loops.
xfilter expressionmaildrop will run the given program as a mail filter. The current message will be piped to the filter program as standard input. The output of the filter program replaces the current message being delivered. The external program must terminate with an exit code of 0. If the external program does not terminate with an exit code of 0, or if it does not read the message from the standard input, maildrop will terminate with an exit code of EX_TEMPFAIL.
hasaddr() is completely case insensitive. This actually violates a few
RFCs, because the userid portion of the address could be case-sensitive,
but it's not in too many cases, so there.