Access Control Lists (ACLs) are defined in a separate section of the run time configuration file, headed by begin acl. Each ACL definition starts with a name, terminated by a colon. Here is a complete ACL section which contains just one very small ACL:
begin acl small_acl: accept hosts = one.host.only
You can have as many lists as you like in the ACL section, and the order in which they appear does not matter. The lists are self-terminating.
The majority of ACLs are used to control Exim's behaviour when it receives certain SMTP commands. This applies both to incoming TCP/IP connections, and when a local process submits a message over a pipe (using the -bs option). The most common use is for controlling which recipients are accepted in incoming messages. In addition, you can also define an ACL that is used to check local non-SMTP messages. The default configuration file contains an example of a realistic ACL for checking RCPT commands. This is discussed in chapter 7.
The -bh command line option provides a way of testing your ACL configuration locally by running a fake SMTP session with which you interact. The host relay-test.mail-abuse.org provides a service for checking your relaying configuration (see section 38.27 for more details).
In order to cause an ACL to be used, you have to name it in one of the relevant options in the main part of the configuration. These options are:
| acl_not_smtp | ACL for non-SMTP messages |
| acl_smtp_auth | ACL for AUTH |
| acl_smtp_connect | ACL for start of SMTP connection |
| acl_smtp_data | ACL after DATA |
| acl_smtp_etrn | ACL for ETRN |
| acl_smtp_expn | ACL for EXPN |
| acl_smtp_helo | ACL for HELO or EHLO |
| acl_smtp_mail | ACL for MAIL |
| acl_smtp_mailauth | ACL for the AUTH parameter of MAIL |
| acl_smtp_rcpt | ACL for RCPT |
| acl_smtp_starttls | ACL for STARTTLS |
| acl_smtp_vrfy | ACL for VRFY |
For example, if you set
acl_smtp_rcpt = small_acl
the little ACL defined above is used whenever Exim receives a RCPT command in an SMTP dialogue. The majority of policy tests on incoming messages can be done when RCPT commands arrive. A rejection of RCPT should cause the sending MTA to give up on the recipient address contained in the RCPT command, whereas rejection at other times may cause the client MTA to keep on trying to deliver the message. It is therefore recommended that you do as much testing as possible at RCPT time.
However, you cannot test the contents of the message, for example, to verify addresses in the headers, at RCPT time. Such tests have to appear in the ACL that is run after the message has been received, before the final response to the DATA command is sent. This is the ACL specified by acl_smtp_data. At this time, it is no longer possible to reject individual recipients. An error response should reject the entire message. Unfortunately, it is known that some MTAs do not treat hard (5xx) errors correctly at this point they keep the message on their queues and try again later, but that is their problem, though it does waste some of your resources.
The ACL test specified by acl_smtp_connect happens after the test specified by host_reject_connection (which is now an anomaly) and any TCP Wrappers testing (if configured).
The non-SMTP ACL applies to all non-interactive incoming messages, that is, it applies to batch SMTP as well as to non-SMTP messages. (Batch SMTP is not really SMTP.) This ACL is run just before the local_scan() function. Any kind of rejection is treated as permanent, because there is no way of sending a temporary error for these kinds of message. Many of the ACL conditions (for example, host tests, and tests on the state of the SMTP connection such as encryption and authentication) are not relevant and are forbidden in this ACL.
The result of running an ACL is either accept or deny, or, if some test cannot be completed (for example, if a database is down), defer. These results cause 2xx, 5xx, and 4xx return codes, respectively, to be used in the SMTP dialogue. A fourth return, error, occurs when there is an error such as invalid syntax in the ACL. This also causes a 4xx return code.
The ACLs that are relevant to message reception may also return discard. This has the effect of accept, but causes either the entire message or an individual recipient address to be discarded. In other words, it is a blackholing facility. Use it with great care.
If the ACL for MAIL returns discard, all recipients are discarded, and no ACL is run for subsequent RCPT commands. The effect of discard in a RCPT ACL is to discard just the one address. If there are no recipients left when the message's data is received, the DATA ACL is not run. A discard return from the DATA or the non-SMTP ACL discards all the remaining recipients.
The local_scan() function is always run, even if there are no remaining recipients; it may create new recipients.
The default actions when any of the acl_xxx options are unset are not all the same. Note: These defaults apply only when the relevant ACL is not defined at all. For any defined ACL, the default action if control reaches the end of the ACL statements is deny.
For acl_not_smtp, acl_smtp_auth, acl_smtp_connect, acl_smtp_data, acl_smtp_helo, acl_smtp_mail, acl_smtp_mailauth, and acl_smtp_starttls, the action when the ACL is not defined is accept.
For the others (acl_smtp_etrn, acl_smtp_expn, acl_smtp_rcpt, and acl_smtp_vrfy), the action when the ACL is not defined is deny. This means that acl_smtp_rcpt must be defined in order to receive any messages over an SMTP connection. For an example, see the ACL in the default configuration file.
When an ACL for MAIL, RCPT, or DATA is being run, the variables that contain information about the host and the message's sender (for example, $sender_host_address and $sender_address) are set, and can be used in ACL statements. In the case of RCPT (but not MAIL or DATA), $domain and $local_part are set from the argument address.
When an ACL for the AUTH parameter of MAIL is being run, the variables that contain information about the host are set, but $sender_address is not yet set.
The $message_size variable is set to the value of the SIZE parameter on the MAIL command at MAIL and RCPT time, or -1 if that parameter was not given. Its value is updated to the true message size by the time the ACL after DATA is run.
The $rcpt_count variable increases by one for each RCPT command received. The $recipients_count variable increases by one each time a RCPT command is accepted, so while an ACL for RCPT is being processed, it contains the number of previously accepted recipients. At DATA time, $rcpt_count contains the total number of RCPT commands, and $recipients_count contains the total number of accepted recipients.
When an ACL for AUTH, ETRN, EXPN, STARTTLS, or VRFY is being run, the remainder of the SMTP command line is placed in $smtp_command_argument. This can be tested using a condition condition. For example, here is an ACL for use with AUTH, which insists that either the session is encrypted, or the CRAM-MD5 authentication method is used. In other words, it does not permit authentication methods that use cleartext passwords on unencrypted connections.
acl_check_auth:
accept encrypted = *
accept condition = ${if eq{${uc:$smtp_command_argument}}\
{CRAM-MD5}{yes}{no}}
deny message = TLS encryption or CRAM-MD5 required
(Another way of applying this restriction is to arrange for the authenticators that use cleartext passwords not to be advertised when the connection is not encrypted. You can use the generic server_advertise_condition authenticator option to do this.)
The value of an acl_smtp_xxx option is expanded before use, so you can use different ACLs in different circumstances, and in fact the resulting string does not have to be the name of a configured list. Having expanded the string, Exim searches for an ACL as follows:
If the string begins with a slash, Exim attempts to open the file and read its contents as an ACL. The lines are processed in the same way as lines in the Exim configuration file. In particular, continuation lines are supported, blank lines are ignored, as are lines whose first non-whitespace character is #. If the file does not exist or cannot be read, an error occurs (typically causing a temporary failure of whatever caused the ACL to be run). For example:
acl_smtp_data = /etc/acls/\
${lookup{$sender_host_address}lsearch\
{/etc/acllist}{$value}{default}}
This looks up an ACL file to use on the basis of the host's IP address, falling back to a default if the lookup fails. If an ACL is successfully read from a file, it is retained in memory for the duration of the Exim process, so that it can be re-used without having to re-read the file.
If the string does not start with a slash, and does not contain any spaces, Exim searches the ACL section of the configuration for a list whose name matches the string.
If no named ACL is found, or if the string contains spaces, Exim parses the string as an inline ACL. This can save typing in cases where you just want to have something like
acl_smtp_vrfy = accept
in order to allow free use of the VRFY command. Such a string may contain newlines; it is processed in the same way as an ACL that is read from a file.
An individual ACL consists of a number of statements. Each statement starts with a verb, optionally followed by a number of conditions and other modifiers. If all the conditions are met, the verb is obeyed. The same condition may be used (with different arguments) more than once in the same statement. This provides a means of specifying an and conjunction between conditions. For example:
deny dnslists = list1.example dnslists = list2.example
If there are no conditions, the verb is always obeyed. What happens if any of the conditions are not met depends on the verb (and in one case, on a special modifier). Not all the conditions make sense at every testing point. For example, you cannot test a sender address in the ACL that is run for a VRFY command.
The verbs are as follows:
accept: If all the conditions are met, the ACL returns accept. If any of the conditions are not met, what happens depends on whether endpass appears among the conditions (for syntax see below). If the failing condition precedes endpass, control is passed to the next ACL statement; if it follows endpass, the ACL returns deny. Consider this statement, used to check a RCPT command:
accept domains = +local_domains endpass verify = recipient
If the recipient domain does not match the domains condition, control passes to the next statement. If it does match, the recipient is verified, and the command is accepted if verification succeeds. However, if verification fails, the ACL yields deny, because the failing condition is after endpass.
defer: If all the conditions are met, the ACL returns defer which, in an SMTP session, causes a 4xx response to be given. For a non-SMTP ACL, defer is the same as deny, because there is no way of sending a temporary error. For a RCPT command, defer is much the same as using a redirect router and :defer: while verifying, but the defer verb can be used in any ACL, and even for a recipient it might be a simpler approach.
deny: If all the conditions are met, the ACL returns deny. If any of the conditions are not met, control is passed to the next ACL statement. For example,
deny dnslists = blackholes.mail-abuse.org
rejects commands from hosts that are on a DNS black list.
discard: This verb behaves like accept, except that it returns discard from the ACL instead of accept. It is permitted only on ACLs that are concerned with receiving messages, and it causes recipients to be discarded. If the log_message modifier is set when discard operates, its contents are added to the line that is automatically written to the log.
If discard is used in an ACL for RCPT, just the one recipient is discarded; if used for MAIL, DATA or in the non-SMTP ACL, all the message's recipients are discarded. Recipients that are discarded before DATA do not appear in the log line when the log_recipients log selector is set.
drop: This verb behaves like deny, except that an SMTP connection is forcibly closed after the 5xx error message has been sent. For example:
drop message = I don't take more than 20 RCPTs
condition = ${if > {$rcpt_count}{20}{yes}{no}}
There is no difference between deny and drop for the connect-time ACL. The connection is always dropped after sending a 550 response.
require: If all the conditions are met, control is passed to the next ACL statement. If any of the conditions are not met, the ACL returns deny. For example, when checking a RCPT command,
require verify = sender
passes control to subsequent statements only if the message's sender can be verified. Otherwise, it rejects the command.
warn: If all the conditions are met, a header line is added to an incoming message and/or a line is written to Exim's main log. In all cases, control passes to the next ACL statement. The text of the added header line and the log line are specified by modifiers; if they are not present, a warn verb just checks its conditions and obeys any immediate modifiers such as set and logwrite.
If any condition on a warn statement cannot be completed (that is, there is some sort of defer), no header is added and the configured log line is not written. No further conditions or modifiers in the warn statement are processed. The incident is logged, but the ACL continues to be processed, from the next statement onwards.
When testing an incoming message, the message modifier can be used on a warn statement to add an extra header line, as in this example:
warn message = X-blacklisted-at: $dnslist_domain dnslists = blackholes.mail-abuse.org : \ dialup.mail-abuse.org
If an identical header line is requested several times (provoked, for example, by multiple RCPT commands), only one copy is actually added to the message. If the text of the message modifier is not a valid header line, X-ACL-Warn: is added to the front of it.
Header lines that are added by an ACL at MAIL or RCPT time are not visible in string expansions in the ACL for subsequent RCPT commands. However they are visible in string expansions in the ACL that is run after DATA. If you want to preserve data between MAIL and RCPT ACLs, you can use ACL variables, as described in the next section. If a message is rejected after DATA, all added header lines are included in the entry that is written to the reject log.
If a message modifier is present on a warn verb in an ACL that is not testing an incoming message, it is ignored, and the incident is logged.
A warn statement may use the log_message modifier to cause a line to be written to the main log when the statement's conditions are true. Just as for message, if an identical log line is requested several times in the same message, only one copy is actually written to the log. If you want to force duplicates to be written, use the logwrite modifier instead.
When one of the warn conditions is an address verification that fails, the text of the verification failure message is in $acl_verify_message. If you want this logged, you must set it up explicitly. For example:
warn !verify = sender log_message = sender verify failed: $acl_verify_message
At the end of each ACL there is an implicit unconditional deny.
As you can see from the examples above, the conditions and modifiers are written one to a line, with the first one on the same line as the verb, and subsequent ones on following lines. If you have a very long condition, you can continue it onto several physical lines by the usual \ continuation mechanism. It is conventional to align the conditions vertically.
There are some special variables that can be set during ACL processing. They can be used to pass information between different ACLs, different invocations of the same ACL in the same SMTP connection, and between ACLs and the routers, transports, and filters that are used to deliver a message. There are two sets of these variables:
The values of $acl_c0 to $acl_c9 persist throughout an SMTP connection. They are never reset. Thus, a value that is set while receiving one message is still available when receiving the next message on the same SMTP connection.
The values of $acl_m0 to $acl_m9 persist only while a message is being received. They are reset afterwards. They are also reset by MAIL, RSET, EHLO, HELO, and after starting up a TLS session.
When a message is accepted, the current values of all the ACL variables are preserved with the message and are subsequently made available at delivery time.
The ACL variables are set by modifier called set. For example:
accept hosts = whatever set acl_m4 = some value
Note that the leading dollar sign is not used when naming a variable that is to be set. If you want to set a variable without taking any action, you can use a warn verb without any other modifiers.
An exclamation mark preceding a condition negates its result. For example,
deny domains = *.dom.example !verify = recipient
causes the ACL to return deny if the recipient domain ends in dom.example, but the recipient address cannot be verified.
The arguments of conditions and modifiers are expanded. A forced failure of an expansion causes a condition to be ignored, that is, it behaves as if the condition is true. Consider these two statements:
accept senders = ${lookup{$host_name}lsearch\
{/some/file}{$value}fail}
accept senders = ${lookup{$host_name}lsearch\
{/some/file}{$value}{}}
Each attempts to look up a list of acceptable senders. If the lookup succeeds, the returned list is searched, but if the lookup fails the behaviour is different in the two cases. The fail in the first statement causes the condition to be ignored, leaving no further conditions. The accept verb therefore succeeds. The second statement, however, generates an empty list when the lookup fails. No sender can match an empty list, so the condition fails, and therefore the accept also fails.
ACL modifiers appear mixed in with conditions in ACL statements. Some of them specify actions that are taken as the conditions for a statement are checked; others specify text for messages that are used when access is denied or a warning is generated.
The positioning of the modifiers in an ACL statement important, because the processing of a verb ceases as soon as its outcome is known. Only those modifiers that have already been encountered will take effect. For the accept and require statements, this means that processing stops as soon as a false condition is met. For example, consider this use of the message modifier:
require message = Can't verify sender verify = sender message = Can't verify recipient verify = recipient message = This message cannot be used
If sender verification fails, Exim knows that the result of the statement is deny, so it goes no further. The first message modifier has been seen, so its text is used as the error message. If sender verification succeeds, but recipient verification fails, the second message is used. If recipient verification succeeds, the third message becomes current, but is never used because there are no more conditions to cause failure.
For the deny verb, on the other hand, it is always the last message modifier that is used, because all the conditions must be true for rejection to happen. Specifying more than one message modifier does not make sense, and the message can even be specified after all the conditions. For example:
deny hosts = ... !senders = *@my.domain.example message = Invalid sender from client host
The deny result does not happen until the end of the statement is reached, by which time Exim has set up the message.
The ACL modifiers are as follows:
This modifier may appear only in ACLs for commands relating to incoming messages. It affects the subsequent processing of the message, provided that the message is eventually accepted. The text must be one of the words freeze, queue_only, or submission (in the latter case, optionally followed by slash-delimited options). The first two cause the message to be frozen or just queued (without immediate delivery), respectively. The third tells Exim that this message is a submission from a local MUA. In this case, Exim applies certain fixups to the message if necessary. For example, it add a Date: header line if one is not present. Details are given in chapter 44.
Once one of these controls is set, it remains set for the message. For example, if control is used in a RCPT ACL, it applies to the whole message, not just the individual recipient. The control modifier can be used in several different ways. For example:
It can be at the end of an accept statement:
accept ...some conditions... control = queue_only
In this case, the control is applied when this statement yields accept.
It can be in the middle of an accept statement:
accept ...some conditions... control = queue_only ...some more conditions...
If the first set of conditions are true, the control is applied, even if the statement does not accept because one of the second set of conditions is false. In this case, some subsequent statement must yield accept for the control to be relevant.
It can be used with warn to apply the control, leaving the decision about accepting or denying to a subsequent verb. For example:
warn ...some conditions... control = freeze accept ...
This example of warn does not contain message, log_message, or logwrite, so it does not add anything to the message and does not write a log entry.
This modifier causes Exim to wait for the time interval before proceeding. The time is given in the usual Exim notation. This modifier may appear in any ACL. The delay happens as soon as the modifier is processed. However, when testing Exim using the -bh option, the delay is not actually imposed (an appropriate message is output).
Like control, delay can be used with accept or deny, for example:
deny ...some conditions... delay = 30s
The delay happens if all the conditions are true, before the statement returns deny. Compare this with:
deny delay = 30s ...some conditions...
which waits for 30s before processing the conditions. The delay modifier can also be used with warn and together with control:
warn ...some conditions...
delay = 2m
control = freeze
accept ...
endpass
This modifier, which has no argument, is recognized only in accept statements. It marks the boundary between the conditions whose failure causes control to pass to the next statement, and the conditions whose failure causes the ACL to return deny. See the description of accept above.
log_message = <text>
This modifier sets up a message that is used as part of the log message if the ACL denies access or a warn statement's conditions are true. For example:
require log_message = wrong cipher suite $tls_cipher encrypted = DES-CBC3-SHA
log_message adds to any underlying error message that may exist because of the condition failure. For example, while verifying a recipient address, a :fail: redirection might have already set up a message. Although the message is usually defined before the conditions to which it applies, the expansion does not happen until Exim decides that access is to be denied. This means that any variables that are set by the condition are available for inclusion in the message. For example, the $dnslist_<xxx> variables are set after a DNS black list lookup succeeds. If the expansion of log_message fails, or if the result is an empty string, the modifier is ignored.
If you want to use a warn statement to log the result of an address verification, you can use $acl_verify_message to include the verification error message.
If log_message is used with a warn statement, Warning: is added to the start of the logged message. If the same warning log message is requested more than once while receiving a single email message, only one copy is actually logged. If you want to log multiple copies, use logwrite instead of log_message. In the absence of log_message and logwrite, nothing is logged for a succesful warn statement.
If log_message is not present and there is no underlying error message (for example, from the failure of address verification), but message is present, the message text is used for logging rejections. However, if any text for logging contains newlines, only the first line is logged. In the absence of both log_message and message, a default built-in message is used for logging rejections.
This modifier writes a message to a log file as soon as it is encountered when processing an ACL. (Compare log_message, which, except in the case of warn, is used only if the ACL statement denies access.) The logwrite modifier can be used to log special incidents in ACLs. For example:
accept <some special conditions>
control = freeze
logwrite = froze message because ...
By default, the message is written to the main log. However, it may begin with a colon, followed by a comma-separated list of log names, and then another colon, to specify exactly which logs are to be written. For example:
logwrite = :main,reject: text for main and reject logs logwrite = :panic: text for panic log only
message = <text>
This modifier sets up a text string that is expanded and used as an error message if the current statement causes the ACL to deny access. The expansion happens at the time Exim decides that access is to be denied, not at the time it processes message. If the expansion fails, or generates an empty string, the modifier is ignored. For ACLs that are triggered by SMTP commands, the message is returned as part of the SMTP error response.
The message modifier is also used with the warn verb to specify one or more header lines to be added to an incoming message when all the conditions are true. If message is used with warn in an ACL that is not concerned with receiving a message, it has no effect.
The text is literal; any quotes are taken as literals, but because the string is expanded, backslash escapes are processed anyway. If the message contains newlines, this gives rise to a multi-line SMTP response. Like log_message, the contents of message are not expanded until after a condition has failed.
If message is used on a statement that verifies an address, the message specified overrides any message that is generated by the verification process. However, the original message is available in the variable $acl_verify_message, so you can incorporate it into your message if you wish. In particular, if you want the text from :fail: items in redirect routers to be passed back as part of the SMTP response, you should either not use a message modifier, or make use of $acl_verify_message.
set <acl_name> = <value>
This modifier puts a value into one of the ACL variables (see section 38.9).
Not all conditions are relevant in all circumstances. For example, testing senders and recipients does not make sense in an ACL that is being run as the result of the arrival of an ETRN command, and checks on message headers can be done only in the ACLs specified by acl_smtp_data and acl_not_smtp. You can use the same condition (obviously with different parameters) more than once in the same ACL statement. This provides a way of specifying an and conjunction. The conditions are as follows:
acl = <name of acl or ACL string or file name >
The possible values of the argument are the same as for the acl_smtp_xxx options. The named or inline ACL is run. If it returns accept the condition is true; if it returns deny the condition is false; if it returns defer, the current ACL returns defer. If it returns drop and the outer condition denies access, the connection is dropped. If it returns discard, the verb must be accept or discard, and the action is taken immediately no further conditions are tested.
ACLs may be nested up to 20 deep; the limit exists purely to catch runaway loops. This condition allows you to use different ACLs in different circumstances. For example, different ACLs can be used to handle RCPT commands for different local users or different local domains.
If the SMTP connection is not authenticated, the condition is false. Otherwise, the name of the authenticator is tested against the list. To test for authentication by any authenticator, you can set
authenticated = *
This feature allows you to make up custom conditions. If the result of expanding the string is an empty string, the number zero, or one of the strings no or false, the condition is false. If the result is any non-zero number, or one of the strings yes or true, the condition is true. For any other values, some error is assumed to have occured, and the ACL returns defer.
dnslists = <list of domain names and other data>
This condition checks for entries in DNS black lists. These are also known as RBL lists, after the original Realtime Blackhole List, but note that the use of the lists at mail-abuse.org now carries a charge. There are too many different variants of this condition to describe briefly here. See sections 38.13--38.19 for details.
This condition is relevant only after a RCPT command. It checks that the domain of the recipient address is in the domain list. If percent-hack processing is enabled, it is done before this test is done. If the check succeeds with a lookup, the result of the lookup is placed in $domain_data until the next domains test.
If the SMTP connection is not encrypted, the condition is false. Otherwise, the name of the cipher suite in use is tested against the list. To test for encryption without testing for any specific cipher suite(s), set
encrypted = *Exim 4.40 Specification chapter 39 Previous Next Contents (Exim 4.40 Specification)
39. Adding a local scan function to Exim
In these days of email worms, viruses, and ever-increasing spam, some sites want to apply a lot of checking to messages before accepting them. You can do a certain amount through string expansions and the condition condition in the ACL that runs after the SMTP DATA command or the ACL for non-SMTP messages (see chapter 38), but this has its limitations.
An increasingly popular way of doing additional checking is to make use of the Exiscan patch for Exim, which adds ACL conditions that perform body scans of various kinds. This is available from /?http://duncanthrax.net/exiscan-acl/?\.
To allow for even more general checking that can be customized to a site's own requirements, there is the possibility of linking Exim with a private message scanning function, written in C. If you want to run code that is written in something other than C, you can of course use a little C stub to call it.
The local scan function is run once for every incoming message, at the point when Exim is just about to accept the message. It can therefore be used to control non-SMTP messages from local processes as well as messages arriving via SMTP.
Exim applies a timeout to calls of the local scan function, and there is an option called local_scan_timeout for setting it. The default is 5 minutes. Zero means no timeout. Exim also sets up signal handlers for SIGSEGV, SIGILL, SIGFPE, and SIGBUS before calling the local scan function, so that the most common types of crash are caught. If the timeout is exceeded or one of those signals is caught, the incoming message is rejected with a temporary error if it is an SMTP message. For a non-SMTP message, the message is dropped and Exim ends with a non-zero code. The incident is logged on the main and reject logs.
39.1. Building Exim to use a local scan function
To make use of the local scan function feature, you must tell Exim where your function is before building Exim, by setting LOCAL_SCAN_SOURCE in your Local/Makefile. A recommended place to put it is in the Local directory, so you might set
LOCAL_SCAN_SOURCE=Local/local_scan.cfor example. The function must be called local_scan(). It is called by Exim after it has received a message, when the success return code is about to be sent. This is after all the ACLs have been run. The return code from your function controls whether the message is actually accepted or not. There is a commented template function (that just accepts the message) in the file src/local_scan.c.
If you want to make use of Exim's run time configuration file to set options for your local_scan() function, you must also set
LOCAL_SCAN_HAS_OPTIONS=yesin Local/Makefile (see section 39.3 below).
39.2. API for local_scan()
You must include this line near the start of your code:
#include "local_scan.h"This header file defines a number of variables and other values, and the prototype for the function itself. Exim is coded to use unsigned char values almost exclusively, and one of the things this header defines is a shorthand for unsigned char called uschar. It also contains the following macro definitions, to simplify casting character strings and pointers to character strings:
#define CS (char *) #define CCS (const char *) #define CSS (char **) #define US (unsigned char *) #define CUS (const unsigned char *) #define USS (unsigned char **)The function prototype for local_scan() is:
extern int local_scan(int fd, uschar **return_text);The arguments are as follows:
fd is a file descriptor for the file that contains the body of the message (the -D file). The file is open for reading and writing, but updating it is not recommended. Warning: You must not close this file descriptor.
The descriptor is positioned at character 19 of the file, which is the first character of the body itself, because the first 19 characters are the message id followed by -D and a newline. If you rewind the file, you should use the macro SPOOL_DATA_START_OFFSET to reset to the start of the data, just in case this changes in some future version.
return_text is an address which you can use to return a pointer to a text string at the end of the function. The value it points to on entry is NULL.
The function must return an int value which is one of the following macros:
LOCAL_SCAN_ACCEPT
The message is accepted. If you pass back a string of text, it is saved with the message, and made available in the variable $local_scan_data. No newlines are permitted (if there are any, they are turned into spaces) and the maximum length of text is 1000 characters.
LOCAL_SCAN_ACCEPT_FREEZE
This behaves as LOCAL_SCAN_ACCEPT, except that the accepted message is queued without immediate delivery, and is frozen.
LOCAL_SCAN_ACCEPT_QUEUE
This behaves as LOCAL_SCAN_ACCEPT, except that the accepted message is queued without immediate delivery.
LOCAL_SCAN_REJECT
The message is rejected; the returned text is used as an error message which is passed back to the sender and which is also logged. Newlines are permitted they cause a multiline response for SMTP rejections, but are converted to \n in log lines. If no message is given, Administrative prohibition is used.
LOCAL_SCAN_TEMPREJECT
The message is temporarily rejected; the returned text is used as an error message as for LOCAL_SCAN_REJECT. If no message is given, Temporary local problem is used.
LOCAL_SCAN_REJECT_NOLOGHDR
This behaves as LOCAL_SCAN_REJECT, except that the header of the rejected message is not written to the reject log. It has the effect of unsetting the rejected_header log selector for just this rejection. If rejected_header is already unset (see the discussion of the log_selection option in section 45.15), this code is the same as LOCAL_SCAN_REJECT.
LOCAL_SCAN_TEMPREJE