Exim Specification - 24. The forwardfile director

Go to the first, previous, next, last section, table of contents.

# 24. The forwardfile director

The forwardfile director can be used for two different but related operations. Its effect is to replace a local part with a list of addresses, file names, or pipe commands, taken from a single file, or from an inline string. It gets its name from the common case where the file is in a user's home directory and is called ``.forward'`, but another common use is for expanding mailing lists, which are discussed in more detail in chapter 42.

A standard transport must not be specified for this director. That is, the generic transport option must not be set. A configuration error occurs if one is given. However, the special transports for handling files, pipes, and autoreplies must be set if needed.

When handling a user's ``.forward'` file, a uid, gid, and home directory are commonly obtained from the password file by calling getpwnam(). However, these may alternatively be specified by options to the director, in which case getpwnam() is not called.

## 24.1 Forward file items

The contents of the file or inline string are a list of addresses, file names, or pipe commands, separated by commas or newlines. Items that are empty are ignored. This includes items consisting solely of RFC 822 address comments. If an item is entirely enclosed in double quotes, these are removed, but otherwise double quotes are retained, because some forms of mail address require the use of double quotes, though never enclosing the whole address.

Lines starting with a # character are comments, and are ignored, and # may also appear following a comma, in which case everything between the # and the end of the line is ignored. If the file is empty, or contains only blank lines and comments, the director behaves as if it did not exist.

If a message is addressed to two or more different local parts, each of which results in an expansion that generates an identical file name or pipe command, different deliveries occur, though of course each delivery process runs with different values in the LOCAL_PART environment variable, and with different uids (in the common case). This happens only if the immediate ancestors of the pipes or files are different local parts. If several different local parts generate an intermediate alias which in turn generates a pipe or file delivery, only a single delivery is done, because the duplicate intermediate addresses are discarded.

- An address item may safely be the same local part as the one currently under consideration, because a director is automatically skipped if any ancestor has the same local part and was processed by that director. Thus a user with login name spqr who wants to preserve a copy of mail and also forward it somewhere else can set up a file such as

      spqr, spqr@st.else.where

  without provoking a loop. A backslash before an unqualified local part is permitted for compatibility with other mailers, but is not necessary for loop prevention. The presence or absence of a backslash does, however, make a difference when there is more than one local domain. Without a backslash, an unqualified local part is qualified with the contents of qualify_recipient unless qualify_preserve_domain is set, but if a backslash is present, the local part is always qualified with the domain of the incoming address. Care must be taken if there are alias names for local users. For example if the system alias file contains

      Sam.Reman: spqr

  then

      Sam.Reman, spqr@reme.else.where

  in spqr's forward file fails on an incoming message addressed to Sam.Reman, because the aliasfile director does not process Sam.Reman the second time round, having previously done so. The forward file should really contain

      spqr, spqr@reme.else.where

  but because this is such a common error, the check_ancestor option (see below) exists to provide a way to get round it.
- An item is interpreted as a file name if it begins with `/' and does not parse as a valid RFC 822 address that includes a domain. For example,

      /home/world/shadow

  is treated as a file name, but

      /s=molari/o=babylon/@x400gate.way

  is treated as an address. For a file name, a transport must be specified using the file_transport option. However, if the generated path name ends with a forward slash character, it is interpreted as a directory name rather than a file name, and directory_transport is used instead. If it ends with two slashes, directory2_transport is required. This makes it possible to support two different kinds of directory delivery simultaneously. If an item is /dev/null, delivery to it is bypassed at a high level, and the log entry shows `**bypassed**' instead of a transport name. This avoids the need for a user and group, which are necessary for a genuine delivery to a file. When the file name is not /dev/null, either the director or the transport must specify a user and group under which to run the delivery. If check_local_user is set, the uid and gid from the passwd file are used as defaults for the generic user and group options.
- An item is treated as a pipe command if it begins with `|' and does not parse as a valid RFC 822 address that includes a domain. A transport for running the command must be specified by the pipe_transport option. Either the director or the transport must specify a user and group under which to run the delivery. If check_local_user is set, the uid and gid from the passwd file are used as defaults for the generic user and group options. Both single and double quotes can be used for enclosing individual arguments to the pipe command; no interpretation of escapes is done for single quotes. If the command contains a comma character, it is necessary to put the whole item in double quotes, for example:

      "|/some/command ready,steady,go"

  since items are terminated by commas. Do not, however, quote just the command. An item such as

      |"/some/command ready,steady,go"

  is interpreted as a pipe with a rather strange command name, and no arguments.
- Instead of an address, file name, or pipe command, an item of the form

      :include:<path name>

  may appear, in which case a list of addresses is taken from the given file and included at that point, unless the forbid_include option is set. There are some security considerations when such an item is included in a user's ``.forward'` file:
  1. If the seteuid() function is being used to read the main file as a specific user (see seteuid below) then the included file is read as the same user.
  2. Otherwise Exim is running as root at this point. If check_local_user is set, or if an explicit directory is specified by file_directory, then any included files must be within the home or given directory, and no symbolic links are permitted below the directory name.
  3. If neither check_local_user nor file_directory is set when seteuid() is not in use, included files are not permitted.

## 24.2 Repeated forwarding expansion

When a message cannot be delivered to all of its recipients immediately, leading to two or more delivery attempts, forwarding expansion is carried out afresh each time for those addresses whose children were not all previously delivered. If a forward file is being used as a mailing list, this can lead to new members of the list receiving copies of old messages. The one_time option can be used to avoid this.

## 24.3 Errors in forward files

If skip_syntax_errors is set, a malformed address that causes a parsing error is skipped, and an entry is written to the main log. This may be useful for mailing lists that are automatically managed, but note the inherent danger. The option should never be set for users' ``.forward'` files. Otherwise, if any error is detected while generating the list of new addresses, the message is frozen, except for the special case of inability to open an included file when no_freeze_missing_include is set. In this case, delivery is simply deferred.

## 24.4 Filter files

As an alternative to treating the file as a simple list of addresses, the forwardfile director can be configured, by means of the filter option, to read a file and interpret it as a list of filtering instructions if it conforms to a specific format. The instructions can specify various actions such as appending the message to certain mail folders, or forwarding it to other users, predicated on the content of the message. Details of the syntax and semantics of filter files are described in a separate document entitled Exim's interface to mail filtering; this is intended for use by end users. If filters are permitted to generate mail messages (see forbid_reply) then the reply_transport option must be set.

## 24.5 The home directory

The $home expansion variable can be used in a number of local options for forwardfile. Its value depends on the way the options are set up, as follows:

- If check_local_user is set and file_directory is unset, $home is set to the user's home directory when expanding the file option that specifies a forward or filter file.
- If check_local_user is unset and file_directory is set, $home is set to the expanded value of file_directory when expanding the file option. If $home appears in file_directory, its substitution value is the empty string.
- If both check_local_user and file_directory are set, $home contains the user's home directory when expanding file_directory, but subsequently $home contains the value of file_directory when expanding the file option.
- If neither check_local_user not file_directory are set, $home is empty.

If the generic require_files option, or any other expanded option, contains $home, it takes the same value as it does when expanding the file option, and this value is also used for $home if encountered in a filter file, and as the default value to pass with the address when a pipe or file delivery is generated.

Note that the value of the home_directory generic option is not used during directing; it specifies a directory for use at transport time.

## 24.6 Special treatment of home_directory and current_directory

The generic options home_directory and current_directory (specified in chapter 21) are handled in a special way by the forwardfile director. Neither has any effect during the running of the director; they act only when it directs an address to a local transport because it specifies a file name, pipe command, or autoreply -- the values are passed with the address for use at transport time.

If home_directory is not set, the directory specified by file_directory, or if that is not set, the home directory obtained from check_local_user is used as the default value.

In installations where users' ``.forward'` files are not kept in their home directories, both check_local_user and file_directory may be set, which leads to the file_directory value being used as the default, when the actual home directory may be wanted. It is no good specifying

    home_directory = $home

because the same value is used for $home. A special string value is therefore provided for use in this case. If home_directory is set to the string `check_local_user' it is converted into the user's home directory path. The same magic string can also be used for current_directory.

## 24.7 Forwardfile private options

This section lists the private options that forwardfile does not have in common with aliasfile. Those that they share are given in chapter 22.

### allow_system_actions (forwardfile)

Type: boolean
Default: false

Setting this option permits the use of freeze and fail in filter files. This should not be set on the director for users' ``.forward'` files, but can be useful if you want to run a system-wide filter for each address, as opposed to the system filter, which runs just once per message. See chapter 47.

### check_group (forwardfile)

Type: boolean
Default: false

The group of the file is checked only when this option is set. If check_local_user is set, the user's default group is permitted; otherwise the group must be one of those listed in the owngroups option.

### check_local_user (forwardfile)

Type: boolean
Default: true

If this option is true, the local part that is passed to this director is checked to ensure that it is the login of a local user by calling the getpwnam() function. The director declines to handle the address if it is not. In addition, when this option is true, the string specified for the file option is taken as relative to the user's home directory if it is not an absolute path, unless the file_directory option is set.

When this option is set, the local user is always one of the permitted owners of the file, and the local user's uid is used when reading the forward file if the seteuid option is set or if the global security setting is not `setuid'. In addition the uid and gid read from the passwd file are used as defaults for the generic user and group options.

### data (forwardfile)

Type: string, expanded
Default: unset

This option is mutually exclusive with file. One or other of them must be set, but not both. The contents of data are expanded, and then used as the list of forwarding items, or as a set of filtering instructions, just as if they were the contents of the file. Essentially, data allows you to provide the filtering instructions inline, but because it is expanded, you can, for example, look them up in a database or indexed file. When filtering instructions are used, the string must start off with `#Exim filter', and all comments in the string, including this initial one, must be terminated with newline characters. For example:

    data = "#Exim filter\n\
           if $h_to: contains Exim then save $home/mail/exim endif"

If you are reading the data from a database where newlines cannot be included, you can use the ${sg} expansion item to turn the escape string of your choice into a newline.

### file (forwardfile)

Type: string, expanded
Default: unset

This option is mutually exclusive with data. One or other of them must be set, but not both. The contents of file are expanded -- see above for a discussion of the home expansion variable. If expansion fails, Exim panics. The expanded string is interpreted as a file name, and must start with a slash character unless check_local_user is true, or a file_directory option is set. A non-absolute path is interpreted relative to the file_directory setting if it exists; otherwise it is interpreted relative to the user's home directory. The contents of the file are the list of forwarding items or a set of filtering instructions.

If a non-absolute path is used, Exim uses the stat() function to check the directory before attempting to open the file therein. If the directory is inaccessible, the delivery to the current address is deferred. This distinguishes between the cases of a non-existent file (where the director cannot handle the address, and must decline) and an unmounted NFS directory (where delivery should be deferred). Thus the difference between the two settings

    file = .forward
    file = $home/.forward

is that in the second case the directory is not checked with stat().

If the file exists but is empty or contains only blank and comment lines starting with #, Exim behaves as if it did not exist, and the director declines to handle the address. Note that this is not the case when the file contains syntactically valid items that happen to yield empty addresses, for example, items containing only RFC 822 address comments.

### file_directory (forwardfile)

Type: string, expanded
Default: unset

The string is expanded before use -- see above for a discussion of the home expansion variable. The option sets a directory path which is used if the file option does not specify an absolute path. This on its own is not very useful, since the directory string could just as well be prepended to the file string. However, if a separate directory is given, it is treated like a directory obtained from check_local_user, and its existence is tested before trying to open the file. If the directory appears not to exist, delivery is deferred. Thus, a setting such as

    file_directory = /usr/forwards
    file = ${local_part}.forward

defers delivery if /usr/forwards appears not to exist. This can be useful if the directory is NFS mounted. If check_local_user is also set, file_directory takes precedence in determining the directory name for non-absolute files.

If forwardfile sets up a delivery to a file or a pipe command and the home_directory option is not set, the directory specified by file_directory, or if that is not set, the home directory obtained from check_local_user is associated with the address during delivery.

### filter (forwardfile)

Type: boolean
Default: false

If this option is set, and the forward file or inline forwarding data starts with the text `# Exim filter', it is interpreted as a set of filtering commands instead of a list of forwarding addresses. Details of the syntax and semantics of filter files are described in a separate document entitled Exim's interface to mail filtering; this is intended for use by end users.

In addition to the commands described therein, there are some extra commands that are permitted only in system filter files, or if allow_system_actions is set. These are described in chapter 47.

Filter files may contain string expansions, but some administrators may not want to permit those expansion features that involve accessing files. The options forbid_filter_existstest, forbid_filter_lookup, and forbid_filter_perl (see below) can be used to lock out these features.

The logging facility in filter files is available only if the filter is being run under some unprivileged uid. The system configuration must specify that seteuid() is available, either user or check_local_user must be set on the director, forbid_filter_log must not be set, and the global security setting must not be `setuid'. Writing the log takes place while the filter file is being interpreted, that is, at directing time. It does not queue up for later like the delivery commands. The reason for this is so that a log file need be opened only once for several write operations.

### forbid_filter_existstest (forwardfile)

Type: boolean
Default: false

If this option is true, string expansions in filter files are not allowed to make use of the exists condition.

### forbid_filter_logwrite (forwardfile)

Type: boolean
Default: false

If this option is true, use of the logging facility in filter files is not permitted. This is in any case available only if the filter is being run under some unprivileged uid, which is normally the case for ordinary users' ``.forward'` files on a system with seteuid() available.

### forbid_filter_lookup (forwardfile)

Type: boolean
Default: false

If this option is true, string expansions in filter files are not allowed to make use of lookup items.

### forbid_filter_perl (forwardfile)

Type: boolean
Default: false

This option is available only if Exim is built with embedded Perl support. If it is true, string expansions in filter files are not allowed to make use of the embedded Perl support.

### forbid_filter_reply (forwardfile)

Type: boolean
Default: false

If this option is true, this director may not generate an automatic reply message. If it attempts to do so, a delivery failure occurs. Automatic replies can be generated only from filter files, not from traditional forward files.

### ignore_eacces (forwardfile)

Type: boolean
Default: false

If this option is set and an attempt to open the forward file yields the EACCES error (permission denied) then forwardfile behaves as if the file did not exist.

### ignore_enotdir (forwardfile)

Type: boolean
Default: false

If this option is set and an attempt to open the forward file yields the ENOTDIR error (something on the path is not a directory) then forwardfile behaves as if the file did not exist.

### match_directory (forwardfile)

Type: string, expanded
Default: unset

If this option is set with check_local_user, the user's home directory, as obtained from getpwnam(), must match the given string. If it does not, the director declines to handle the address. The string is expanded before use. If the expansion fails, Exim panics, unless the failure was explicitly triggered by a `fail' item in a conditional sub-expression in the expansion, in which case the director just declines to handle the address.

If the expanded string starts with an asterisk, the remainder must match the end of the home directory name; if it starts with a circumflex, a regular expression match is performed. In fact, the matching process is the same as is used for domain list items and may include file lookups.

If the pattern starts with an exclamation mark, the user's home directory must not match the rest of the given string. For example, with

    match_directory = !^/group

the director declines if the user's home directory starts with /group.

### reply_transport (forwardfile)

Type: string, expanded
Default: unset

A forwardfile director sets up a delivery to an autoreply transport when a mail or vacation command is used in a filter file. The transport used is specified by this option, which, after expansion, must be the name of a configured transport.

### seteuid (forwardfile)

Type: boolean
Default: false

This option may not be set unless the compile-time configuration in the OS-specific configuration files specifies that the seteuid() function is available in the operating system. In addition, either the check_local_user or the generic user and group options must be set. A configuration error occurs if these conditions do not hold.

When this option is true, the seteuid() and setegid() functions are called to change the effective uid and gid before accessing the home directory and the file. If both check_local_user and user are set, the uid is taken from the latter. If the generic initgroups option is set, initgroups() is called to initialise the group list with all the user's groups. The user remains set during interpretation of a filter file; if it writes log entries the log file must be accessible to the uid or gid. Changing uid is necessary in two circumstances:

1. When Exim is configured to change the effective uid from root to the Exim user (using seteuid()) while running the directors. See chapter 55 for details.
2. When users' home directories are NFS mounted, and root access is not exported to the local host, to allow for cases when the files are not world-readable.

The forwardfile director can detect the first of these cases, and it always uses seteuid(), regardless of the setting of this option, since it does not make sense to do otherwise.

On a system without the seteuid() function, but with NFS home directories that do not export root, it is necessary for forward files to be world-readable.

Go to the first, previous, next, last section, table of contents.