The previous chapters describe checks that can be applied to messages before they are accepted by a host. There are also mechanisms for checking messages once they have been received, but before they are delivered. A system message filter can be run each time a delivery process is started for a message. It is also possible to run a centrally-defined filter file once for each local address, as part of the directing for that address.
The system message filter operates in a similar manner to users' filter files, but it is run just once per message (however many recipients is has) at the start of a delivery attempt, before any routing or directing is done. If a message fails to be completely delivered at the first attempt, the filter is run again at the start of the every retry.
There are two special conditions which, though available in users' filter files, are designed for use in system filters. The condition `first_delivery' is true only for the first attempt at delivering a message, while `manually_thawed' is true only if the message has been frozen, and subsequently thawed by an admin user. An explicit forced delivery counts as a manual thaw, but thawing as a result of the `auto_thaw' setting does not.
If the filter sets up any deliveries of its own, an extra header line is added to them with the name `X-Envelope-to:'. This contains up to 100 of the original message's envelope recipients. If the filter specifies any significant deliveries, then the message's own recipient list is ignored; otherwise it is added to any recipients set up by the filter.
The `message_filter' option names the filter file, while `message_filter_user' and `message_filter_group' specify the uid and gid to be used while processing it. If they are not set, then the exim uid is used if available and if `seteuid()' is available; otherwise root is used. There are also options for specifying which transports are to be used if the filter generates any file, pipe or autoreply deliveries.
The filter file can contain any of the normal filtering commands, as described in the separate document Exim's User interface to mail filtering. However, because the system filter is run just once per delivery attempt, the variable `$local_part' is not available, nor does the `personal' condition make any sense.
The filter variables `$n0' -- `$n9' can be used in a system filter; when it finishes, their values are copied into `$sn0' -- `$sn9' and are thereby made available to users' filter files. Thus a system filter can, for example, set up a `score' for a message to which users' filter files can refer.
In addition to the filter commands available in user's files there are some extra commands which are available only in system filter files:
fail freeze headers add <string> headers remove <string>
As well as the additional commands, there is also an extra expansion variable, `$recipients', containing a list of all the recipients of the message, separated by commas and white space. The extra commands and variable are not available in ordinary users' filter files. They are faulted in normal use and in testing via `-bf', but not if `-bF' is used.
The `freeze' and `fail' commands can optionally be followed by the word `text' and a string containing an error message, for example:
fail text "this message looks like spam to me"
If either `freeze' or `fail' is obeyed in a system filter file, no deliveries are done, not even those set up by `mail' commands in the filter. See the `freeze_tell_mailmaster' option for a way of having a message sent when a message is frozen.
The keyword `text' is optional if the next character is a double quote. The `fail' command causes all recipients to be failed, while `freeze' suspends all delivery attempts. It is ignored if the message has been manually unfrozen and not manually frozen since. This means that automatic freezing by a system filter can be used as a way of checking out suspicious messages. If a message is found to be all right, manually unfreezing it allows it to be delivered.
The argument for the `headers add' is a string which is expanded and then added to the end of the message's headers. It is the responsibility of the filter maintainer to make sure it conforms to RFC 822 syntax. Leading white space is ignored, and if the string is otherwise empty, or if the expansion is forced to fail, the command has no effect. A newline is added at the end of the string if it lacks one. More than one header may be added in one command by including `\n' within the string.
The argument for `headers remove' is a colon-separated list of header names. This command applies only to those headers that are stored with the message; ones such as `Envelope-To:' and `Return-Path:' that are added at delivery time cannot be removed by this means.
Take great care with the `fail' command when basing the decision to fail on the contents of the message, because this option causes a normal delivery error message to be generated, and it will of course include the contents of the original message and will therefore trigger the `fail' command again (causing a mail loop) unless steps are taken to prevent this. Testing the `error_message' condition is one way to prevent this. You could use, for example
if $message_body contains "this is spam" and not error_message then fail text "spam is not wanted here" endif
though of course that might still let through unwanted messages. The alternative is clever checking of the body and/or headers to detect error messages caused by the filter.
In contrast to the system filter, which is run just once per message for each delivery attempt, it is also possible to set up a system-wide filtering operation that runs once for each address, for local addresses only. In this case, variables such as `$local_part' and `$domain' can be used, and indeed, the choice of filter file could be made dependent on them. This is an example of a director which implements such a filter:
central_filter: driver = forwardfile file = /central/filters/${local_part} no_check_local_user no_verify filter allow_system_actions
The setting of `allow_system_actions' permits the use of `freeze' and `fail' in the filter file, but not the `headers' command or the `$recipients' variable.
Go to the first, previous, next, last section, table of contents.