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 no conventional 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 run time configuration files, 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.
Some installations require to run Exim in an unprivileged state almost all the time, for added security. Support for this mode of operation is provided by the setting
security = unprivileged
When this is done, all deliveries take place under the Exim user/group (which must be defined), and there are restrictions on the features that can be used in the configuration. There are two possibilities if you want to run Exim in this way:
security = unprivilegedin this case, because this setting stops Exim from trying to re-invoke itself to do a delivery after a message has been received. Such a re-invocation is a waste of time because it would have no effect.
When using the second style (setuid to the Exim user), unless called by root (in which case it behaves as in the first style), Exim is running with the real uid and gid set to those of the calling process, and the effective uid/gid set to Exim's values. Ideally, any association with the calling process' uid/gid should be dropped, that is, the real uid/gid should be reset to the effective values so as to discard any privileges that the caller may have. While some operating systems have a function that permits this action for a non-root effective uid, quite a number of them do not. Because of this lack of standardization, Exim does not address this problem at this time. For this reason, the first style is perhaps the better approach to take.
Because Exim no longer needs to re-exec itself when starting a delivery process after receiving a message, using
security = unprivileged
is more efficient than either of
security = setuid security = setuid+seteuid
However, to achieve this extra efficiency you have to submit to the following restrictions:
You can deliver only as the Exim user/group. You should explicitly use the user and group options to override directors or transports that normally deliver as the recipient. (This makes sure that configurations that work in this mode function the same way in normal mode.) Any implicit or explicit specification of another user causes an error.
Use of `.forward' files is severely restricted, such that it is usually not worthwhile to include a forwardfile director in the configuration.
Users who wish to use `.forward' would have to make their home directory and the file itself accessable to the Exim user. Pipe and append-to-file entries, and their equivalents in Exim filters, cannot be used. While they could be enabled in the Exim user's name, that would be insecure and not very useful.
Unless the user mailboxes are all owned by the Exim user (possible in some POP3 or IMAP-only environments):
There are no additional restrictions on message reception or external (SMTP) delivery.
Exim can be run with an alternate configuration file by means of the -C option, and macros for use in its configuration can be set on the command line using the -D option. If the -C option specifies a file other than the one whose name is built into the binary, or if there is any use of the -D option, and the caller is not root or the Exim user, Exim immediately gives up its privilege, and runs with the real and effective uid and gid set to those of the caller.
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, 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, 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 15.
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 hosts by setting smtp_expn_hosts, and there is a similar option 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_groups 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, 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[0], 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[0] 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.
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.
These are 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.