Software used:

After using qmail for almost a year, I decided to switch to exim for a few reasons:

Compiling exim

Easy. The included it with options to link against OpenSSL and OpenLDAP. Can't get much simpler than:

    $ cd /usr/ports/mail/exim
    $ make WITH_LDAP=yes WITH_PAM=yes WITH_TLS=yes            WITH_PERL=yes LDAP_LIB_TYPE=OPENLDAP2
    $ sudo make install
I did comment out the start line in the rc.d script for now till I get it running.

Compiling Cyrus

Again, easy. Just

    $ cd /usr/ports/security/cyrus-sasl; make; sudo make install
    $ cd /usr/ports/mail/cyrus; make; sudo make install
It prompted me for what sort of authentication I wanted to use, and of course I said "OpenLDAP".

Integrating Exim with Cyrus

Since exim's config is in /usr/local/etc/exim and qmail's configuration resides in /var/qmail, I could now set up exim to deliver directly to cyrus without interfering with anything.

Outfitting Exim with RBL

Added the following to the exim config:

    rbl_domains = blackholes.mail-abuse.org:dialups.mail-abuse.org:relays.mail-abuse.org

Converting dot-qmail files to /etc/alias entries

I used the following script to change my .qmail* files to entries in the /etc/alias file:

#!/bin/sh

# User of qmail's alias
alias=alias

# Where we want the aliases kept
aliasfile=/usr/local/etc/exim/aliases

# DBM maker
exim_dbm=/usr/local/sbin/exim_dbmbuild

# User mailman runs as
mailman=mailman

# find all non-mailman/alias users
users=`awk 'BEGIN {FS=":"} !/'$alias'|'$mailman'/ {print $1}' < /etc/passwd | sort | xargs`

for user in $users; do
    homedir=`sh -c "echo ~$user"`
    for q in $homedir/.qmail*; do
        if [ -f $q ]; then
            echo $user -\> $q
            echo ------------
            sudo -u $user sh -c "cat $q | sed 's,^&,,' > $homedir/.forward"
            cat $homedir/.forward && sudo rm $q
            echo
        fi
    done
done;

sudo rm $aliasfile $aliasfile.db
homedir=`sh -c "echo ~alias"`
for f in $homedir/.qmail*; do
    if [ -f $f ]; then
        name_from=`echo $f | sed 's,.*/\.qmail-,,'`
        name_from=`echo $name_from | sed 's,^/.*,alias,'`
        name_to=`cat $f | sed 's,^&,,'`
        echo $name_from =\> $name_to
        sudo sh -c "echo $name_from $name_to >> $aliasfile"
    fi
done;
sudo $exim_dbm $aliasfile $aliasfile.db

Converting mailman aliases

Very, very simple. Just follow the directions for mailman integration on the website.

Testing

At this point, I had a working setup to test. Using the following configure file, this is simply the drop-in replacement of exim for qmail. There are no LDAP lookups; there is no cyrus delivery. This is simply to have something in place that works. I will build on this.

Note the mailman components of this configuration. Exim is set up so that all one has to do is run newlist for mailman and Exim will automatically begin to deliver those messages. Also, note that the mailing lists are in the lists.everybody.org domain.

# home dir for mailman
MAILMAN_HOME=/home/websites/lists.everybody.org/

# wrapper script for mailman
MAILMAN_WRAP=MAILMAN_HOME/mail/wrapper

# user and group for mailman
MAILMAN_UID=mailman
MAILMAN_GID=mailman

######################################################################
#                    MAIN CONFIGURATION SETTINGS                     #
######################################################################

primary_hostname = everybody.org
local_domains = everybody.org:everyhost.com:slideshow.org:lists.everybody.org

# No local deliveries will ever be run under the uids of these users
exim_user = root
exim_group = mail
never_users = root : root

# Local relays are necessary for mailman, and they don't really hurt.
host_accept_relay = "127.0.0.1/32"

# We only accept unqualified email addresses from localhost
receiver_unqualified_hosts = "127.0.0.1/32"
sender_unqualified_hosts = "127.0.0.1/32"

# We block all of RBL, DUL, and RSS.  Later we may turn this to
# warnings and filter on the warnings.
rbl_domains = blackholes.mail-abuse.org:dialups.mail-abuse.org:relays.mail-abuse.org

pid_file_path = /var/run/exim%s.pid

end



######################################################################
#                      TRANSPORTS CONFIGURATION                      #
######################################################################
#                       ORDER DOES NOT MATTER                        #
#     Only one appropriate transport is called for each delivery.    #
######################################################################

remote_smtp:
  driver = smtp

# We use procmail for local delivery
procmail_pipe:
  driver = pipe
  command = "/usr/local/bin/procmail -d ${local_part}"
  return_path_add
  delivery_date_add
  envelope_to_add
  check_string = "From "
  escape_string = ">From "
  user = $local_part
  group = mail

# This is the normal local delivery
local_delivery:
  driver = appendfile
  file = /var/mail/${local_part}
  delivery_date_add
  envelope_to_add
  return_path_add
  group = mail
  mode = 0660


address_pipe:
  driver = pipe
  return_output

address_file:
  driver = appendfile
  delivery_date_add
  envelope_to_add
  return_path_add

address_reply:
  driver = autoreply


## Three transports for list mail, request mail and admin mail
## respectively
## Mailman is installed in MAILMAN_HOME
## Mailman is configured to be invoked as user MAILMAN_UID
list_transport:
           driver = pipe
           command = MAILMAN_WRAP post ${lc:$local_part}
           current_directory = MAILMAN_HOME
           home_directory = MAILMAN_HOME
           user = MAILMAN_UID
           group = MAILMAN_GID

list_request_transport:
           driver = pipe
           command = MAILMAN_WRAP mailcmd ${lc:$local_part}
           current_directory = MAILMAN_HOME
           home_directory = MAILMAN_HOME
           user = MAILMAN_UID
           group = MAILMAN_GID

list_admin_transport:
           driver = pipe
           command = MAILMAN_WRAP mailowner ${lc:$local_part}
           current_directory = MAILMAN_HOME
           home_directory = MAILMAN_HOME
           user = MAILMAN_UID
           group = MAILMAN_GID

### end of transports section fragment

end



######################################################################
#                      DIRECTORS CONFIGURATION                       #
#             Specifies how local addresses are handled              #
######################################################################
#                          ORDER DOES MATTER                         #
#   A local address is passed to each in turn until it is accepted.  #
######################################################################

# Local addresses are those with a domain that matches some item in the
# "local_domains" setting above, or those which are passed back from the
# routers because of a "self=local" setting (not used in this configuration).


system_aliases:
  driver = aliasfile
  file = /usr/local/etc/exim/aliases
  search_type = lsearch
  file_transport = address_file
  pipe_transport = address_pipe
  user = root

## Directors section [this deals with local addresses]
## 
## First 2 directors rewrite list-owner or owner-list to list-admin
## This is only done if the list exists.
## List existence checks are done by seeing if the file
## MAILMAN_HOME/lists//config.db
## exists.  

list_owner_director:
   driver = smartuser
   domains = lists.everybody.org
   require_files = MAILMAN_HOME/lists/${lc:$local_part}/config.db
   suffix = "-owner"
   new_address = "${lc:$local_part}-admin@${domain}"

owner_list_director:
   driver = smartuser
   domains = lists.everybody.org
   require_files = MAILMAN_HOME/lists/${lc:$local_part}/config.db
   prefix = "owner-"
   new_address = "${lc:$local_part}-admin@${domain}"

##
## Next 3 directors direct admin, request and list mail to the appropriate
## transport.  List existence is checked as above.

list_admin_director:
   driver = smartuser
   domains = lists.everybody.org
   suffix = -admin
   require_files = MAILMAN_HOME/lists/${lc:$local_part}/config.db
   transport = list_admin_transport

list_request_director:
   driver = smartuser
   domains = lists.everybody.org
   suffix = -request
   require_files = MAILMAN_HOME/lists/${lc:$local_part}/config.db
   transport = list_request_transport

list_director:
   driver = smartuser
   domains = lists.everybody.org
   require_files = MAILMAN_HOME/lists/${lc:$local_part}/config.db
   transport = list_transport

## End of directors fragment


# Handle forward files
userforward:
  driver = forwardfile
  file = .forward
  no_verify
  no_expn
  check_ancestor
# filter
  file_transport = address_file
  pipe_transport = address_pipe
  reply_transport = address_reply


# Do the local delivery with procmail
procmail:
  driver = localuser
  transport = procmail_pipe

# Conventional local delivery
localuser:
  driver = localuser
  transport = local_delivery


end



######################################################################
#                      ROUTERS CONFIGURATION                         #
#            Specifies how remote addresses are handled              #
######################################################################
#                          ORDER DOES MATTER                         #
#  A remote address is passed to each in turn until it is accepted.  #
######################################################################

# Remote addresses are those with a domain that does not match any item
# in the "local_domains" setting above.


# This router routes to remote hosts over SMTP using a DNS lookup with
# default options.
lookuphost:
  driver = lookuphost
  transport = remote_smtp


# This router routes to remote hosts over SMTP by explicit IP address,
# given as a "domain literal" in the form [nnn.nnn.nnn.nnn]. The RFCs
# require this facility, which is why it is enabled by default in Exim.
# If you want to lock it out, set forbid_domain_literals in the main
# configuration section above.
literal:
  driver = ipliteral
  transport = remote_smtp


end



######################################################################
#                      RETRY CONFIGURATION                           #
######################################################################

# This single retry rule applies to all domains and all errors. It specifies
# retries every 15 minutes for 2 hours, then increasing retry intervals,
# starting at 1 hour and increasing each time by a factor of 1.5, up to 16
# hours, then retries every 8 hours until 4 days have passed since the first
# failed delivery.

# Domain               Error       Retries
# ------               -----       -------

*                      *           F,2h,15m; G,16h,1h,1.5; F,4d,8h

end



######################################################################
#                      REWRITE CONFIGURATION                         #
######################################################################

# There are no rewriting specifications in this default configuration file.

# End of Exim configuration file

Getting PAMized LDAP authentication working

Converting dot-qmail files to LDAP entries

Using LDAP to look up deliveries

Setting up SSL

Most of this comes from example configuration C027 in the configuration samples. Especially the following:

$ cd /usr/local/openssl/certs Directory already existed in this case
$ sudo sh -c 'openssl genrsa 1024 > exim.rsa' RSA key
$ sudo sh -c 'openssl gendh -rand /dev/urandom > exim.dh' Diffie-Hellman parameters
Using configuration from /etc/ssl/openssl.cnf
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [US]:
State or Province Name (full name) [Louisiana]:
Locality Name (eg, city) [New Orleans]:
Organization Name (eg, company) [Everybody Networks]:
Organizational Unit Name (eg, section) [webserver]:
Common Name (eg, YOUR name) [everybody.org]:
Email Address [mah@everybody.org]:
Q & A
sudo sh -c 'cat everybody-org.rsa everybody-org.x509 everybody-org.dh > everybody-org.pem'

The signed cert is necessary for Outlook which won't take an unsigned cert.

Added the following line to /etc/inetd.conf:

# Following is for cyrus imap over ssl
imaps  stream  tcp  nowait  cyrus  /usr/local/sbin/stunnel exim -l /usr/local/cyrus/bin/imapd -p /usr/local/openssl/certs/everybody-org.pem -- imapd
# SMTP Authentication only over SSL
smtps  stream  tcp  nowait  mail   /usr/local/sbin/stunnel exim -l /usr/local/sbin/exim -p /usr/local/openssl/certs/everybody-org.pem -- exim -bs

Alternatively, I could have put this in the rc script:

  /usr/local/bin/stunnel -d 465 -l /usr/local/sbin/exim           -p /usr/local/openssl/certs/everybody-org.pem           -- exim -bs
and
  /usr/local/bin/stunnel -d 993 -l /usr/local/cyrus/bin/imapd           -p /usr/local/openssl/certs/everybody-org.pem           -- imapd

Authentication

I found these entries for the configuration file in C034: