Setting up Exim

Note: The following is part of a series of steps to setup an email server using Exim 4.x, with imap and webmail access. It will use winbind to get user information from an NT server. If you found this page via a search engine it may not cover what you need or you may need to start at the beginning to understand everything I have done.

(Note: The following is for Exim 4.2x. Some config options have changed in Exim since then.)

See http://www.exim.org/ for much more information.

Quick steps:

  1. Download and unpack
  2. Create a Local/Makefile
  3. Make and make install
  4. Building an RPM New
  5. Edit the /etc/aliases file
  6. Edit the configure file
  7. Test
  8. The init.d script
  9. Possible way around a winbind problem
  10. An example gateway configuration
  11. Rotating logs with logrotate
  12. Other things for Exim

Here is everything the way I did it.

Note: I am running Red Hat Linux 8.0, DB4, and Exim 4.20. The primary purpose of this box is to be an email server that gets it's user information from an NT PDC. See the Setting up Samba section for what I am doing there. The only downside is if the NT server goes down when an email comes in, then Exim can't retrieve the user information. See "Possible way around winbind problem" below for how I do a defer when the NT server is down..

1) Download and unpack

Go to http://www.exim.org/, find a mirror site, and download.

Next:
tar -xvzf name_of_file.tar.gz

Note: If you are going to use exiscan, look here before continuing.


2) Create a Local/Makefile

When you untared the file a directory was created. Copy /(exim-directory)/scr/EDITME to /(exim-directory)/Local/Makefile. Edit this file to suit your needs. Here is my test setup in short form:

###############################################################################
#                    THESE ARE THINGS YOU MUST SPECIFY                        #
###############################################################################
 
#...
BIN_DIRECTORY=/usr/sbin
#...
CONFIGURE_FILE=/etc/exim/exim.conf
#...
EXIM_USER=mail
#...
SPOOL_DIRECTORY=/var/spool/exim
 
###############################################################################
#           THESE ARE THINGS YOU PROBABLY WANT TO SPECIFY                     #
###############################################################################
 
#...
 
ROUTER_ACCEPT=yes
ROUTER_DNSLOOKUP=yes
ROUTER_IPLITERAL=yes
ROUTER_MANUALROUTE=yes
ROUTER_QUERYPROGRAM=yes
ROUTER_REDIRECT=yes
 
#...
 
TRANSPORT_APPENDFILE=yes
TRANSPORT_AUTOREPLY=yes
TRANSPORT_PIPE=yes
TRANSPORT_SMTP=yes
 
#...
 
#------------------------------------------------------------------------------
# The appendfile transport can write messages to local mailboxes in a number
# of formats. The code for three specialist formats, maildir, mailstore, and
# MBX, is included only when requested. If you do not know what this is about,
# leave these settings commented out.
#
# My notes: you can disable maildir if using UW-IMAP
 
SUPPORT_MAILDIR=yes
# SUPPORT_MAILSTORE=yes
# SUPPORT_MBX=yes
 
#...
 
LOOKUP_DBM=yes
LOOKUP_LSEARCH=yes
 
#...
 
#------------------------------------------------------------------------------
# Additional libraries and include directories may be required for some
# lookup styles (e.g. LDAP, MYSQL or PGSQL). LOOKUP_LIBS is included only on
# the command for linking Exim itself, not on any auxiliary programs. You
# don not need to set LOOKUP_INCLUDE if the relevant directories are already
# specified in INCLUDE.
 
# LOOKUP_INCLUDE=-I /usr/local/ldap/include -I /usr/local/mysql/include -I /usr/local/pgsql/include
# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq
 
# My notes: For RedHat 8.0 I needed:
# For mysql lookups:
#  LOOKUP_INCLUDE needs '-I /usr/include/mysql' instead of '-I /usr/local/mysql/include'
#  LOOKUP_LIBS needs '-L/usr/lib/mysql' added
# For postgresql lookups:
#  LOOKUP_INCLUDE needs '-I /usr/include/pgsql' instead of '-I /usr/local/pgsql/include'
# For pam:
#  LOOKUP_LIBS needs '-lpam' added
 
#...
 
###############################################################################
#                 THESE ARE THINGS YOU MIGHT WANT TO SPECIFY                  #
###############################################################################
 
#...
LOG_FILE_PATH=/var/log/exim/%s.log
#...
SYSLOG_LOG_PID=yes
#...
EXICYCLOG_MAX=10
#...
COMPRESS_COMMAND=/usr/bin/gzip
COMPRESS_SUFFIX=gz
#...
ZCAT_COMMAND=/usr/bin/zcat
#...
USE_TCP_WRAPPERS=yes
 
# You may well also have to specify a local "include" file and an additional
# library for TCP wrappers, so you probably need something like this:
 
#  USE_TCP_WRAPPERS=yes
#  CFLAGS=-O -I/usr/local/include
EXTRALIBS_EXIM=-lwrap -ldl
 
# but of course there may need to be other things in CFLAGS and EXTRALIBS_EXIM
# as well.
 
#...
 
SYSTEM_ALIASES_FILE=/etc/aliases
 
###############################################################################
#              THINGS YOU ALMOST NEVER NEED TO MENTION                        #
###############################################################################
 
# The settings in this section are available for use in special circumstances.
# In the vast majority of installations you need not change anything below.
 
#...
 
# End of EDITME for Exim 4. 

At this time I am running a binary built for all lookups, with PostgreSQL as the SQL lookup database. I have also modified appendfile.c to ignore Courier-IMAP's shared folder links when figuring quotas. See the quota tips below.


3) Make and make install

Once you are done editing the Makefile in the Local directory, cd back to the main exim directory.

If you are planning on using exiscan, stop and apply the patch now.

Make sure you have the db#-devel rpm installed, or the include files from the database you are using..

Now run make by simply typing:
make
at the command line. Lots of stuff will go by. If it exits with done, then everything is fine. If not, then look at what it failed at. You may need to specify some additional information if you are including support for PAM, SSL/TLS, LDAP, etc. Tip: If you are building Exim with these options do not forget to install the devel packages first.

Once make works properly, you can type:
make install
Or, if you want to test it without it doing anything, login as a non-root user. Now cd to the exim directory and then into the build-* directory. Type:
../scripts/exim_install -n

If you specified a log directory, remember to make sure it exists and the exim user you specified has rights to that directory.


4) Building an RPM

For Mandrake users you can find pre-built RPMs at rpmhelp.net. For RedHat users you can find pre-built RPMs at ftp://ftp.exim.org/pub/rpms-for-exim/

For those of you interested in compiling your own RPMs, here is what I did:

  • Went to rpmhelp.net and download a source rpm from exim. (See the 9.0 or 9.1 SRPM directories) Note that when I started this I knew of no RPMs for RH.
  • Installed with:
    rpm -ivh exim-[version]-[build].src.rpm
  • cd to /usr/src/redhat/SPECS
  • Changed the exim.spec file as I needed for my RedHat environment.
  • Install the necessary packages for compiling: tcp_wrappers, pam-devel, openssl, openssl-devel, XFree86-devel openldap-devel, and db4-devel. If building with sql support, include the mysql or postgresql -devel package.
  • Ran rpmbuild -bi exim.spec to test compiling.
  • Ran rpmbuild -bb exim.spec to build the binaries or rpmbuild -ba exim.spec to build all.

Note: I am running rpm 4.1.1-1.8x. See rpm.org for the latest.

To download my rpm or view my full spec file, go here.

Note that this builds Exim so that it has support for just about everything. If you need to customize the Makefile then uncompress the exim-4.14-config.patch.bz2 file and change it or make your own patch file and name it the same or update the name in the "Patch0" slot. Don't forget to re-compress it to bz2 or else change the relevant parts in the spec file. Or you can use perl for 'search and replace'.

If you want exiscan compiled in, look at the spec file for how it runs the Local/Makefile patch and/or see rpm.org for the "RPM How-To".

Once the binary rpm is built you can install it from the RPMS/i386 directory with:
rpm -ivh [package_name]*.rpm


5) Edit the /etc/aliases file.

At the very least reroute root to someone else and setup a postmaster person. Exim copies a default one over if you do not have one already.


6) Edit the configure file

Mine goes in /etc/exim/exim.conf. See your Local/Makefile if you have forgotten where it went. It is fairly detailed and you can find more information at http://www.exim.org/. At the minimum read and correctly fill out the "Main Configuration" section. The primary_hostname, domainlist local_domains, domainlist relay_to_domains, hostlist relay_from_hosts, and never_users are some of the most important settings for security reasons.

If you are using exiscan, look here for what goes in exim's config file.

6.1) Delivering to maildir mailboxes:

If you are using the maildir format, such as Courier-IMAP supports, you need to specify that option in the delivery section. Such as for local user delivery - example:

local_delivery:
  driver = appendfile
  group = mail
  mode = 0660
  maildir_format = true
  directory = /home/${local_part}/Maildir
  create_directory = true
  check_string = ""
  escape_string = ""

6.2) Delivering to Courier's shared folders:

In the routers section, below system_aliases:

# This router handles special mail addresses to be
# delivered directly into Maildir format shared folders.
# Separate addresses by colons.
# It is after system_aliases so that the aliases file can group things
# to fewer folders here.
 
shared_folders:
  local_parts = "local_part1:local_part2"
  driver = accept
  transport = maildir_shared_folder

And in the transports section (I placed mine above local_delivery):

# This transport saves messages in shared folders for special mail
# addresses defined in the 'shared_folders' router - to allow workgroup
# style mail handling with Courier IMAP server (and clients which
# support shared folders)
 
maildir_shared_folder:
  driver = appendfile
  maildir_format = true
  directory = /path/to/shared_directory/.${local_part}/
  create_directory = true
  check_string = ""
  escape_string = ""
  delivery_date_add
  envelope_to_add
  return_path_add
  mode = 0777
  no_mode_fail_narrower
  user = user_to_deliver_as
  group = shared_write_group

6.3) Storage quotas per Exim:
(vs OS quotas)

The following goes in the transports section of the configuration file, under the transport it is for (ie local_users).

To specify limit across the board for all users (not total, but per user):
quota = 10M

To specify limit per user different values using a file:
quota = ${lookup{$local_part}lsearch*{/etc/exim/quotafile}{$value}{4M}}
The file would look like (the last is a way to default for everyone else):

user1  10M
user2  5M
user3  50M
*      15M

Note: There are ways to use a database too. Just do a web search.

To allow for mailbox size as quota and not mailbox+incoming email:
quota_is_inclusive = false

For faster return of current disk useage when using maildir format mailboxes:
maildir_tag = ,S=$message_size
quota_size_regex = ,S=(\d+)

To warn at a certain percentage and the message that will be sent to the owner of the mailbox:

quota_warn_threshold = 75%
quota_warn_message ="\
   To: $local_part@$domain\n\
   Subject: Warning - Almost out of email space\n\
   This is an automated email.\n\
   \n\
   The storage space used by your email has \
   almost exceeded the allowed limit.\n\
   Please delete some emails, especially those \
   with attachments, to free up space.\n\
   Otherwise email will stop being delivered \
   until you do so.\n"

The following goes in the retry configuration section above the 'all errors' (*) rule.

To bounce incoming email once quota is reached (in the FAQ):

# Domain         Error      Retries
# ------         -----      -------
 
*@your.domain     quota

To bounce only if mailbox has not be accessed for 7 days or retry for up to 3 days otherwise (in the FAQ):

# Domain         Error      Retries
# ------         -----      -------
 
*@your.domain     quota_7d   
*@your.domain     quota     F,2h,15m; F,3d,1h

Two worries I had with using Exim's quotas were:

  • If, in /etc/aliases, User2=User1 what would happen to the bounce if User1's email was over the limit and they sent a message to User2? Not likely I know, but still a possible loop condition. My tests have shown nothing to worry about.
  • When using Courier's shared folders if the email stored there counted toward's the total, because there is a symlink back to the shared folder. At one time I thought my tests showed it was not included, but recently I noticed it is.

To make Exim's quota routine ignore Courier-IMAP shared folders that the user has subscribed I had to modify src/transports/appendfile.c, check_dir_size routine, just below the line:
if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
I added:
if (Ustrcmp(name, "shared-folders") == 0) continue;
It's ugly and I wanted to make it where you could turn it off and on with a variable and define the shared folder name with a variable, but I'm not good at C.

6.4) Tweaking things (examples):

See http://www.exim.org/exim-html-4.20/doc/html/spec_13.html

######################################################################
#                    MAIN CONFIGURATION SETTINGS                     #
######################################################################
# ...
 
# Incoming tweaks
#
# Max number of simultaneous SMTP calls to accept
# (aka max number of exim processes) Default is 20
smtp_accept_max = 20
 
# Max number of waiting SMTP connections.
# Gives some protection against denial-of-service attacks by SYN flooding
# Default is 20
smtp_connect_backlog = 20
 
# Max number of MAIL commands that Exim is prepared to accept over a
# single SMTP connection, after which a 421 is given. Default is 1000
smtp_accept_max_per_connection = 100
 
# Max number of simultaneous incoming SMTP calls before messages
# are just placed on the queue. Default is 0 (no limit)
smtp_accept_queue = 150
 
# Max number of delivery processes that Exim starts automatically when
# receiving messages via SMTP before starting to queue. Default is 10
smtp_accept_queue_per_connection = 15
 
# When smtp_accept_max is set greater than zero, this option specifies a
# number of SMTP connections that are reserved for connections from the
# hosts that are specified in smtp_reserve_hosts. Default is 0
#smtp_accept_reserve = 0
 
# Hosts for which SMTP connections are reserved (host list, expanded)
# Default is unset
#smtp_reserve_hosts =
 
# Max system load before not accepting SMTP calls,
# except for hosts defined in smtp_reserve_hosts
# Default is unset (fixed point)
#smtp_load_reserve
 
# Check disk resources before accepting mail
# A 452 temporary error response for SMTP.
# A message to stderr and return of non-zero for BSMTP.
# Check spool partion
# Default for all is 0
check_spool_inodes = 100
check_spool_space = 20M
# Check log file partion
# Set only if on a different disk than the spool
check_log_inodes = 2048
check_log_space = 3M
 
# Max message size to accept
# Default is 50M
message_size_limit = 20M
 
# Max bounce message size to send
# Default is 100K
return_size_limit = 10K
 
# Alternative to return_size_limit is to set
#bounce_return_message = false
 
# Time to wait for ident query response
# Default is 30s
rfc1413_query_timeout = 10s
 
#
# Clean up tweaks
#
# Redundant pairs of angle brackets around addresses are removed
# Default is false
strip_excess_angle_brackets = true
 
# Ignore a trailing dot at the end of a domain in an address
# Default is false
strip_trailing_dot = true
 
#
# Processing tweaks
#
# Intervals a warning message to the sender when there is a delay
# Default is 24h
delay_warning = 2h:8h:24h:48h
 
# Who to send a mail to when a message is frozen
freeze_tell = postmaster
 
# Time before a queue runner will try a new delivery attempt
# on any frozen message. Default is 0s
auto_thaw = 4d
 
# Set a reply-to for bounce messages from your host
# Default is unset
errors_reply_to = Reply-To: postmaster <postmaster@$qualify_domain>
 
# Retry a bounce message delivery and fail if not delivered
# Default is 10w
ignore_bounce_errors_after = 3d
 
# If a frozen message of any description that has been on the
# queue for longer than the given time is automatically cancelled
# at the next queue run. Default is 0s
timeout_frozen_after = 5d
 
# Abandon queue runs if system load is greater than this
# Default is unset
deliver_queue_load_max = 20
 
# If system load is higher than this queue incoming messages
# Default is unset
queue_only_load = 20
 
# Max queue-runner processes to run simultaneously
# Default is 5
queue_run_max = 30

6.5) Blocking fake HELOs:

begin acl
 
# This access control list is used for every RCPT command in an incoming
# SMTP message. The tests are run in order until the address is either
# accepted or denied.
 
acl_check_rcpt:
#...
 
  # Do not accept HELO/EHLO from hosts using our IP(s) in HELO
  # Could exclude internal IPs, but they should never HELO with our inet IP
  # Remember to update file if IP(s) change!
  deny message     = Forged IP in HELO.
       log_message = HELO is our IP
       condition   = ${lookup {$sender_helo_name} \
                     lsearch{/etc/exim/our_inet_ips.txt} \
                     {yes}{no}}
  # Also see http://www.exim.org/pipermail/exim-users/Week-of-Mon-20030811/057580.html

Then in the /etc/exim/our_inet_ips.txt file place your IPs one per line.

Remember that drop may also be used. It acts like deny except as soon as a 5xx is issued your server drops the connection. Drop was added in 4.11, IIRC.

6.6) Block from local_domains if sending ip not on LAN:

This is not something an ISP would use, but a company would want it if they have not setup authentication.

Want to force your users to stop using your mail server when they are not in the office? This trick can also be used to stop spammers from attempting to use a "From" address in an attempt to circumvent spam and relaying measures. It will still allow your users to set their reply-to address to their address at your domain, for times when they are off at a client's site and using the client's equipment. And of course you have web mail for them to use, don't you?

# Deny local_domains sending if they are not on the LAN
deny message        = Please use webmail when you are out of the \
                      office.\n\
     log_message    = LOCAL_DOMAINS relay prohibited outside \
                      LAN for $sender_address
     !hosts         = +relay_from_hosts
     sender_domains = +local_domains

6.7) Deny if sender cannot be verified:

begin acl
 
# This access control list is used for every RCPT command in an incoming
# SMTP message. The tests are run in order until the address is either
# accepted or denied.
 
acl_check_rcpt:
#...
 
  # Deny unless the sender address can be verified.
  deny    message = From email address must be valid
          # do not check address for lists or bounces
          # or people in our company contact database
          !senders = ^.*-request@.*:\
                    ^bounce-.*@.*:\
                    ^.*-bounce@.*:\
                    ^owner-.*@.*:\
                    ^listmaster@.*:\
                    pgsql;select field from \
                    database where \
                    field='${quote_pgsql:$sender_address}'
          # do not check for DSN-ignorant domains
          # iow those that don't accept MAIL FROM:<>
          !dnslists = dsn.rfc-ignorant.org/$sender_address_domain
          !verify  = sender/callout/defer_ok
 
   # Check that there is a MX record for those that do not
   # meet the deny statement requirements - ie bounces
   # No cost as previous lookup is cached if executed
   require verify = sender

6.8) Using a list of domains in a SQL database to control whether to warn or deny if in RBL:

######################################################################
#                    MAIN CONFIGURATION SETTINGS                     #
#...
 
# Connection information to postgresql database, hidden
# Format hostname/database/username/password[:(next server info)]
# Recommend using a user with read-only access
hide pgsql_servers = localhost/database/user/password
 
######################################################################
#                       ACL CONFIGURATION                            #
 
begin acl
 
# This access control list is used for every RCPT command in an incoming
# SMTP message. The tests are run in order until the address is either
# accepted or denied.
 
acl_check_rcpt:
#...
 
  # Check if on RBL list
            # Do not run RBL check for private internal address
  deny      !hosts           = 192.168.1.0/24
            # Do not run if in list
            !sender_domains  = ${lookup pgsql{select * from table where \
                               field='$sender_address_domain'}{$value}}
            message          = $sender_host_address is blacklisted at $dnslist_domain.\n\
                               See $dnslist_domain for more information.
            log_message      = $sender_host_address listed in (deny) $dnslist_domain
            dnslists         = one.rbl.list : \
                               two.rbl.list
 
            # Do not run RBL check for private internal address
  warn      !hosts           = 192.168.1.0/24
            # Only run if in list
            sender_domains   = ${lookup pgsql{select * from table where \
                               field='$sender_address_domain'}{$value}}
            message          = X-Spam-RBL: Yes - $sender_host_address is blacklisted at $dnslist_domain
            log_message      = $sender_host_address listed in (warn) $dnslist_domain
            dnslists         = one.rbl.list : \
                               two.rbl.list

Note that my company has its company address book in a PostgreSQL database, to which I've created a view called email_domains that simply lists all domains in our address book. (see here for more) I considered checking the full sender address, both local_part and domain, but decided to go with this method so that new contacts from the same company would not have to be added if their company happened to be on a blacklist.

Another thing I do is add "ADV:" to the beginning of the Subject line for all emails that meet the "warn" condition. To do this:

######################################################################
#                    MAIN CONFIGURATION SETTINGS                     #
#...
 
# System filter
system_filter = /etc/exim/system_filter.txt
message_body_visible = 5000
system_filter_user = mail
system_filter_group = mail
system_filter_file_transport = address_file
system_filter_reply_transport = address_pipe

Then in /etc/exim/system_filter.txt:

# Only run this on the first pass through the filter
if not first_delivery then
 finish
endif
 
# Trap errors
if error_message then
 finish
endif
 
# If found in RBL then stick "ADV: " in subject
if $h_X-Spam-RBL: is not "" then
 headers add "New-Subject: ADV: ${escape:$h_subject:}"
 headers remove subject
 headers add "Subject: $h_new-subject:"
 headers remove new-subject
endif

When first implementing RBL lists, I would suggest running everything through a warn condition to make sure it catches what you want and not what you don't. You can use different lists in each condition if you desire, since some RBL lists take an extreme approach.

6.9) Deny send at quota:

Warning: Still experimental! Use at your own risk!

The only feature I miss from Exchange is the ability to set a "deny send". What this did was when a user reached X space taken up they could no longer send. I found this much more effective in getting users to clean out their email boxes than only denying on receive when a quota was reached. By setting "send quota" slightly lower than "receive quota" email could still be delivered for a bit. While the following may not be elegant it is what I have arrived at and I am currently testing. If any one sees anything grossly wrong about this or a way to do it better, please email me. If anyone is good enough with C to modify Exim so something like this could be built-in I would love to hear about it (searching has returned nothing).

The following requires using sudo to allow the script to be run as root. This is because Exim runs as the user it was compiled to run as and you need to be either root or the owner of the email box files/directories in question to find their size. I am not thrilled about this solution, but hopefully my sudoers file (along with firewalling and other protections) is configured properly to not make this a potential breach. Use at your own risk!

Edit sudoers with the command visudo. Please read about sudo and sudoers if you are not familar with it.

# Host alias specification
Host_Alias LOCAL = localhost
# User alias specification
User_Alias EMAIL = user_exim_runs_as
# Runas alias specification
Runas_Alias DANGER = root
# Cmnd alias specification
Cmnd_Alias SENDQUOTA = /etc/exim/check-sendquota.sh
# User privilege specification
#...
EMAIL     LOCAL=(DANGER)          NOPASSWD: SENDQUOTA

The bash script can be downloaded here (to come). What it does is a du -s on the user's Maildir (can be changed to a file or other directory), then looks at the quota file for Exim (can be changed to a hardcoded amount, don't ask me about a SQL or LDAP lookup). From the quota listed for the user a percentage is calculated. If the storage space used is more than the percentage of the quota then it exits with a status of 1, otherwise a status of 0 is returned. Make sure the email client being used will display the error message echoed (Thunderbird does), else you may have to modify it to run later on, copy the body of the message and send it back to the user saying why it can't be sent. Look at the script and modify what you need to.

The acl:

acl_check_rcpt:
#...
 
  # Deny sending for local users if almost at quota
        # only run for local domains
  deny  sender_domains = +local_domains
        # list all addresses that are aliases
        # though people shouldn't be sending as
        # them, many scripts do
        !senders = ^postmaster@.*:\
                   ^root@.*:\
                   ^webmaster@.*:\
                   ^error-.*@.*:\
                   ^bounce-.*@.*
        # might need to exclude webmail server here
        # if it does not report the error message
        # note: squirrelmail tested ok for me
        hosts = +local_hosts
        # verify your email client expunges on emptying the trash
        message = You have too much email stored. Please delete some\n\
                  and empty the trash. Then you can send.
        log_message = $sender_address_local_part is over send quota
        condition = ${run{sudo -u root /etc/exim/check-sendquota.sh $sender_address_local_part}{no}{yes}}

It has been pointed out to me that this may cause undue loading on the system. As we average about 30-50 emails per day (not per user, but for all), the hit is acceptable to me. However, the suggestion was made to run the script as a cron job, say every 5 minutes, to check for all users and write the info to a file that the user Exim runs as could read as needed. I'm not quite sure how the loading compares, on a high volume system, between the two methods - run as needed or run as cron job for all then look up as needed.

Yes, I suppose you could modify this to deny incoming email when the user is over quota rather than using Exim's method of accepting it and then bouncing it.

6.10) Vacation messages

 

Using 'autoreply' transport method.

######################################################################
#                      ROUTERS CONFIGURATION                         #
#...
 
# This router delivers a "vacation" message if a file called 'vaction.msg'
# exists in the home directory.
uservacation:
  driver = accept
  domains = +local_domains
  # user to put away message in a file called vacation.msg
  require_files = $home/vacation.msg
  # do not reply to errors or lists or with ADV in the subject
  condition =  ${if or { \
                {match {$h_precedence:} {(?i)junk|bulk|list}} \
                {eq {$sender_address} {}} \
                {match {$h_subject:} {(ADV|Adv)}} \
                } {no} {yes}}
  no_expn
  # do not reply to errors or bounces or lists
  senders = ! ^.*-request@.*:\
            ! ^bounce-.*@.*:\
            ! ^.*-bounce@.*:\
            ! ^owner-.*@.*:\
            ! ^postmaster@.*:\
            ! ^webmaster@.*:\
            ! ^listmaster@.*:\
            ! ^mailer-daemon@.*:\
            ! ^root@.*
  transport = uservacation_transport
  unseen
  no_verify
 
# ...
userforward
# ...
 
######################################################################
#                      TRANSPORTS CONFIGURATION                      #
#...
 
local_delivery
#...
 
# This transport is used for vacation messages
uservacation_transport:
   driver = autoreply
   file = $home/vacation.msg
   file_expand
   # if using MailScanner setup with two config files
   # must be able to write as the user exim runs as
   # because calling with -C will not run as root
   # http://www.exim.org/pipermail/exim-users/Week-of-Mon-20020715/041328.html
   once = /var/log/exim/vacation/$local_part-vacation.db
   # if not using MailScanner setup try something like
   #once = $home/.vacation.db
   # to use a flat file instead of a db specify once_file_size
   #once_file_size = 2K
   once_repeat = 14d
   from = $local_part@domain.com
   to = $sender_address
   subject = "Re: $h_subject"
   # text that will be included in message above what is in user's vacation.msg
   text = "This is an automatic reply.  Please feel free to send additional\n\
           mail, as only this one notice will be generated.\n\
           ================================================\n\n"
#...
 
address_pipe:
#...

7) Test

After editing the config file, run:
exim -bV
If that works then there are no mistakes in the config file. Otherwise, go find what is wrong. If it says exim is unknown then you may need to do a symlink to /usr/bin or /usr/sbin like:
ln -s /path/to/exim /usr/sbin/exim

Tip: I also link sendmail to exim via:
ln -s /path/to/exim /usr/sbin/sendmail

Next, start exim. My init.d script is below. Once it is placed in /etc/rc.d/init.d you can start exim with:
service exim start

Now, check a local address:
exim -bt local_user@your.domain

Next check a remote address:
exim -bt remote_user@their.domain

Check sending an email:
exim -v mailbox_you_can_check@dom.ain

From: user@your.domain
To: mailbox_you_can_check@dom.ain
Subject: Testing exim
 
Testing exim
.

It should now spit out some information and let you know if the email was sent or what went wrong. Don't forget that period on a blank line, as it says "this is the end of the email".

How to test with full debug output using a specific config file:
exim -C /etc/exim/exim_example.conf -d -bt user@some.domain

How to test as if you were coming from a specified ip address:
exim -bh 192.168.1.10

HELO dom.ain or ip
MAIL FROM: <user@dom.ain>
RCPT TO: <another.user@dom.ain>
DATA
Subject: something
your message here
.

8) An init.d/exim script

Snagged from an older exim-*.rpm file. Change the path to exim to suit. Note: If you are setting a group to deliver as in the config file that is an NT group, change chkconfig to start after winbindd. Example:
chkconfig: 2345 95 30

#!/bin/bash
#
# exim    This shell script takes care of starting and stopping exim
#
# chkconfig: 2345 80 30
# description: Exim is a Mail Transport Agent, which is the program \
#              that moves mail from one machine to another.
# processname: exim
# config: /etc/exim/exim.conf
# pidfile: /var/run/exim.pid
 
# Source function library.
. /etc/init.d/functions
 
# Source networking configuration.
. /etc/sysconfig/network
 
# Source exim configuration.
if [ -f /etc/sysconfig/exim ] ; then
	. /etc/sysconfig/exim
else
	DAEMON=yes
	QUEUE=1h
fi
 
# Check that networking is up.
[ ${NETWORKING} = "no" ] && exit 0
 
[ -f /usr/sbin/exim ] || exit 0
 
start() {
        # Start daemons.
        echo -n $"Starting $0: "
        daemon /usr/sbin/exim $([ "$DAEMON" = yes ] && echo -bd) \
                              $([ -n "$QUEUE" ] && echo -q$QUEUE)
        RETVAL=$?
        echo
        [ $RETVAL = 0 ] && touch /var/lock/subsys/exim
}
 
stop() {
        # Stop daemons.
        echo -n $"Shutting down $0: "
        killproc exim
	RETVAL=$?
        echo
        [ $RETVAL = 0 ] && rm -f /var/lock/subsys/exim
}
 
restart() {
	stop
	start
}
 
# See how we were called.
case "$1" in
  start)
	start
	;;
  stop)
	stop
	;;
  restart)
	restart
	;;
  reload)
	restart
	;;
  condrestart)
 	[ -f /var/lock/subsys/exim ] && {
		stop
		[ -x /bin/chown ] && /bin/chown mail.mail -R /var/spool/exim
		start
	} || {
		[ -x /bin/chown ] && /bin/chown mail.mail -R /var/spool/exim
	}
	;;
  status)
	status exim
	;;
  *)
	echo $"Usage: $0 {start|stop|restart|reload|status|condrestart}"
	exit 1
esac
 
exit $RETVAL

9) Possible way around a winbind problem

One problem with using winbind to get the user information from an NT server is if the server goes down Exim doesn't realize this is should be a "defer" situation instead of a "fail". Now there is some caching going on, but I don't want to rely on that. Here is how I check for the NT server and if it is not up I send a temporary error instead of an outright fail. If I had both a PDC and a BDC I would not worry about this, not to mention this solution can only check one server. If you need to check multiples I saw a utility called fping that might work.

I created this router in the router section:

# A way to detect if the NT server is up or not
# so as to avoid winbind lookup problems.
server_down_defer:
   driver = redirect
   allow_defer
   condition = ${run{/bin/ping -c 1 -w 5 -q 192.168.1.3}{no}{yes}}
   data = :defer: Temporary server problem

Note: the '-w' option means 'wait for response' for x seconds. If you have a slow network, you might want to increase this. If you have a fast one, you can decrease it. The '-c' option means number of times to try and '-q' means quiet.

I would suggest placing it below 'domain_literal' and 'dnslookup' at the very least. After 'system_aliases' is fine as well, but remember that as soon as it hits this router it won't deliver. In my setup, where I have a 'shared_folders' router as mentioned above, which is not dependent on a winbind lookup, my routers order looks like:

begin routers
# domain_literal:
...
dnslookup:
...
system_aliases:
...
shared_folders:
...
server_down_defer:
...
userforward:
...
localuser:
...
#end routers

This allows me to continue to accept what would go into the shared folders and use /etc/aliases to redirect to shared folders. Yet if the NT server is down everything else is deferred. Since I don't have any local users on the Linux box I want to deliver to, this is not a problem.


10) An example gateway configuration

If you want an exim server that simply passes on anything it receives for the domain(s) it is set to accept here is an example. Notes are included for locking it down so that only the localhost can send, such as might be needed on a web server or for forwarding logs internally without an internal DNS & MX records available.

######################################################################
#                    MAIN CONFIGURATION SETTINGS                     #
######################################################################
# Specify your host's canonical name here.
primary_hostname = this.hosts.name
# There are no local deliveries
domainlist local_domains =
 
# These are the domains we will accept for
domainlist relay_to_domains = domains-to.accept
 
# Accept from all
hostlist   relay_from_hosts = *
# or Accept only from specified (ex localhost can send to relay_to_domains)
#hostlist   relay_from_hosts = 127.0.0.1
 
# ...
 
# No deliveries will ever be run under the uids of these users
never_users = root
 
# ...
 
#
# Virus checking is a good thing (tm)
# I'm using exiscan in this case
#
# YOUR EXISCAN CONFIGURATION GOES HERE
# Scan all messages
exiscan_condition = 1
# ... (See my Exiscan page for more) ...
 
# ...
 
######################################################################
#                       ACL CONFIGURATION                            #
#         Specifies access control lists for incoming SMTP mail      #
######################################################################
 
begin acl
 
# ...
 
######################################################################
#                      ROUTERS CONFIGURATION                         #
#               Specifies how addresses are handled                  #
######################################################################
#     THE ORDER IN WHICH THE ROUTERS ARE DEFINED IS IMPORTANT!       #
# An address is passed to each router in turn until it is accepted.  #
######################################################################
 
begin routers
 
# ...
 
system_aliases:
# ...
 
# Send all mail from this box to domains listed in
# relay_to_doamins to the internal mail server at
# specified address
 
send_to_gateway:
  driver = manualroute
  domains = +relay_to_domains
  transport = remote_smtp
	# place the FQDN or IP address of where the
	# messages are being relayed too here
  route_list = * 192.168.1.1
 
#userforward:
# ...
 
#localuser:
# ...
 
######################################################################
#                      TRANSPORTS CONFIGURATION                      #
######################################################################
#                       ORDER DOES NOT MATTER                        #
#     Only one appropriate transport is called for each delivery.    #
######################################################################
# A transport is used only when referenced from a router that successfully
# handles an address.
 
begin transports
 
 
# This transport is used for delivering messages over SMTP connections.
 
remote_smtp:
  driver = smtp
 
#local_delivery:
# ...

Note that if you simply want to use exim for handling the mail command that it is safer to not run the daemon (ie do not do 'chkconfig exim on' or 'service exim start').


11) Rotating logs with logrotate

If you have the logrotate package installed all you need to do is create a file /etc/logrotate.d/exim with:

/var/log/exim/*log {
        missingok
  notifempty
  sharedscripts
  postrotate
    /bin/kill -HUP `cat /var/run/exim.pid 2>/dev/null` 2> /dev/null || true
  endscript
}

Note that you may need to adjust the first line so that it only affects the exim log files (per how exim was configured when built).