This chapter discusses a number of issues concerned with security, some of which are also covered in other parts of this manual.
For reasons that this author does not understand, some people have promoted Exim as a `particularly secure' mailer. Perhaps it is because of the existence of this chapter in the documentation. However, the intent of the chapter is simply to describe the way Exim works in relation to certain security concerns, not to make any specific claims about the effectiveness of its security as compared with other MTAs.
What follows is a description of the way Exim is supposed to be. Best efforts have been made to try to ensure that the code agrees with the theory, but an absence of bugs can never be guaranteed. Any that are reported will get fixed as soon as possible.
The Exim binary is normally setuid to root. In some special cases (for example, when the daemon is not in use and there are also no local deliveries), it may be possible to run it setuid to some user other than root. However, root privilege is usually required for two things:
It is not necessary to be root to do any of the other things Exim does, such as receiving messages and delivering them externally over SMTP, and it is obviously more secure if Exim does not run as root except when necessary.
If no user is specified for Exim in either the compile-time or runtime configuration files, then it runs as root all the time, except when performing local deliveries. When an alternative user is specified (which is recommended), it gives up root privilege when it can. Exactly how and when it does this depends on whether the operating system supports the `seteuid()' or the `setresuid()' function.
To avoid unnecessary complication, the discussion below talks about users, and functions for setting the uid. It should be understood that in all cases there is a corresponding group and gid, and that this is also changed whenever the uid is changed. The description is written in terms of `seteuid()', since this is more common than `setresuid()'. However, it is possible to specify at compile time that an operating system has `setresuid()' and not `seteuid()'.
On systems without `seteuid()', Exim uses `setuid()' to give up root privilege at certain times, at the expense of having to re-invoke itself (using `exec') in order to regain privilege when necessary. If `seteuid()' is available, there is a configuration choice as to which method is used for temporarily giving up the privilege. Using `setuid()' is more secure, and is the default, but uses more resources.
There are two instances in which Exim always uses `setuid()':
There are two instances in which Exim always uses `seteuid()' (provided it is available in the operating system):
For other operations, the `security' configuration option controls whether Exim uses `setuid()' or `seteuid()' to change to its own uid. It can be set to one of three strings:
On systems that do not support the `seteuid()' function, the only possible value for the `security' option is `setuid', and this is the default on such systems if an Exim user is defined. Otherwise the default is `setuid+seteuid' -- the most secure setting.
When forward files are read from users' home directories and those home directories are NFS mounted without root privilege, even a program running as root cannot read a forward file that does not have world read access.
If the `seteuid()' function is being used as described in the previous section, so that Exim is not root when running the directors, then the `forwardfile' director automatically uses `seteuid()' to become the local user when attempting to read a `.forward' file in a user's home directory. If `seteuid()' is not being used generally, but is available in the operating system, the `forwardfile' director can be configured to make use of it when reading files in home directories.
The `forwardfile' director does not necessarily have to read from users' home directories as obtained from `getpwnam()'. It can be given a directory explicitly, and a specific associated user and group. The above remarks are applicable in this case also.
On systems that do not have `seteuid()', the only way to support forward files on NFS file systems that do not export root is to insist that the files be world readable.
Forward files are permitted to contain :include: items unless forbidden by setting `forbid_include' in the director. If `seteuid()' is being used to read the forward file, then any included files are read as the same user. Otherwise Exim is running as root, and it insists that any included files are within the same directory as the forward file, and that there are no symbolic links below the directory. If no directory is specified (either explicitly or by looking up a local user's home directory) then included files are not permitted when `seteuid()' is not in use.
When the filtering option is enabled for forward files, users can construct pipe commands that contain data from the incoming message by quoting variables such as `$sender_address'. To prevent the contents of inserted data from interfering with a command, the string expansion is done after the command line is split up into separate arguments, and the command is run directly instead of passing the command line to a shell.
Full details of the checks applied by `appendfile' before it writes to a file are given in chapter "The appendfile transport".
Many operating systems suppress IP source-routed packets in the kernel, but some cannot be made to do this. Exim is configured by default to log incoming IPv4 source-routed TCP calls, and then to drop the call. These actions can be independently turned off. Alternatively, the IP options can be deleted instead of dropping the call. Things are all different in IPv6. No special checking is currently done.
Support for these SMTP commands is disabled by default. The VRFY command can be enabled by setting `smtp_verify'. The EXPN command can be enabled for specific sets of hosts or nets by setting `smtp_expn_hosts' or `smtp_expn_nets', and there is a similar pair of options controlling ETRN.
Exim recognises two sets of users with special privileges. Trusted users are able to submit new messages to Exim locally, but supply their own sender addresses and information about a sending host. For other users submitting local messages, Exim sets up the sender address from the uid, and doesn't permit a remote host to be specified.
However, an untrusted user is permitted to use the `-f' command line option in the special form `-f <>' to indicate that a delivery failure for the message should not cause an error report. This affects the message's envelope, but it does not affect the `Sender:' header.
Trusted users are used to run processes that receive mail messages from some other mail domain and pass them on to Exim for delivery either locally, or over the Internet. Exim trusts a caller that is running as root, as the Exim user (if defined), as any user listed in the `trusted_users' configuration option, or under any group listed in the `trusted_group' option.
Admin users are permitted to do things to the messages on Exim's queue. They can freeze or thaw messages, cause them to be returned to their senders, remove them entirely, or modify them in various ways. In addition, admin users can run the Exim monitor and see all the information it is capable of providing, which includes the contents of files on the spool.
By default, the use of the `-M' and `-q' options to cause Exim to attempt delivery of messages on its queue is restricted to admin users. However, this restriction can be relaxed by setting the `no_prod_requires_admin' option.
Exim recognises an admin user if the calling process is running as root or as the Exim user (if defined) or if any of the groups associated with the calling process is the Exim group (if defined). It is not necessary actually to be running under the Exim group. However, if admin users who are not root or exim are to access the contents of files on the spool via the Exim monitor (which runs unprivileged), Exim must be built to allow group read access to its spool files.
If a uid and gid are defined for Exim, then the spool directory and everything it contains will be owned by exim and have its group set to exim. The mode for spool files is defined in the `Local/Makefile' configuration file, and defaults to 0600. This should normally be changed to 0640 if a uid and gid are defined for Exim, to allow access to spool files via the Exim monitor by other members of the exim group.
Exim examines the last component of `argv', and if it matches one of a set of specific strings, Exim assumes certain options. For example, calling Exim with the last component of `argv' set to `rsmtp' is exactly equivalent to calling it with the option `-bS'. There are no security implications in this.
The only use made of `%f' by Exim is in formatting load average values. These are actually stored in integer variables as 1000 times the load average. Consequently, their range is limited and so therefore is the length of the converted output.
Exim uses its own path name, which is embedded in the code, only when it needs to re-exec in order to regain root privilege. Therefore it is not root when it does so. If some bug allowed the path to get overwritten, it would lead to an arbitrary program's being run as exim, not as root. If there's still paranoia about this, two separate copies of the name could be kept, or a checksum could be applied to the global data.
A large number of occurrences of `sprintf' in the code are actually calls to `string_sprintf()', a function which returns the result in malloc'd store. The intermediate formatting is done into a large fixed buffer by a function that runs through the format string itself, and checks the length of each conversion before performing it, thus preventing buffer overruns. [This was not true before Exim version 1.70.]
The remaining uses of `sprintf()' happen in controlled circumstances where the output buffer is known to be sufficiently long to contain the converted string.
Arbitrary strings are passed to both these functions, but they do their formatting by calling the function `string_vformat()', which runs through the format string itself, and checks the length of each conversion. [This was not true before Exim version 1.70.]
These should be used only in cases where the output buffer is known to be large enough to hold the result.
Go to the first, previous, next, last section, table of contents.