Kerberized Postfix, Dovecot, Policyd on Solaris

These are my notes from compiling and configuring these services.

Note that I put all the stuff I personally compile in /opt/psa to keep it separate from other sources. I also package everything. If you want the packages, just ask.

Postfix

Postfix compilation and configuration is fairly well documented.

Compile

The important parts for including Dovecot SASL are:

-DUSE_SASL_AUTH
-DDEF_SERVER_SASL_TYPE=\"dovecot\"

Here's the full script I used:

\#!/bin/bash
export PATH="/opt/SUNWspro/bin:/usr/ccs/bin:/usr/bin"
make tidy 
make makefiles CC=cc \
  CCARGS='-DUSE_TLS -DHAS_PCRE -DUSE_SASL_AUTH  \
  -DDEF_SERVER_SASL_TYPE=\"dovecot\" \
  -DDEF_COMMAND_DIR=\"/opt/psa/sbin\" \
  -DDEF_CONFIG_DIR=\"/etc/opt/psa/postfix\" \
  -DDEF_DAEMON_DIR=\"/opt/psa/libexec/postfix\"  \
  -DDEF_MAILQ_PATH=\"/opt/psa/bin/mailq\" -DDEF_HTML_DIR=\"no\"  \
  -DDEF_MANPAGE_DIR=\"/opt/psa/share/man\"  \
  -DDEF_NEWALIAS_PATH=\"/opt/psa/bin/newaliases\" \
  -DDEF_QUEUE_DIR=\"/var/opt/psa/postfix\" \
  -DDEF_README_DIR=\"/opt/psa/share/readme/postfix\" \
  -DDEF_SENDMAIL_PATH=\"/opt/psa/sbin/sendmail\" \
  -I/usr/sfw/include -I/opt/sfw/include -I/opt/psa/include ' \
  AUXLIBS="-R/usr/sfw/lib -R/opt/sfw/lib  -R/opt/psa/lib \
  -L/usr/sfw/lib -L/opt/sfw/lib -L/opt/psa/lib -lssl -lcrypto -lpcre"
make
if [ -r postfix-install ]; then
  /bin/bash postfix-install -non-interactive -package install_root=/tmp/postfix-install mail_owner=postfix setgid_group=postdrop
fi

Configure

For the new built in TLS, they've changed the configuration to use X_tls_security_level so keep that in mind if you're upgrading from an older version.

Setting X_tls_security_level to may uses TLS where possible but allows for it not to be used and allows for certificates not to match (so self-signed certificates can still send you email).

My new TLS settings look like:

smtpd_tls_security_level = may
smtpd_tls_CAfile = /etc/sfw/openssl/cacert.pem
smtpd_tls_cert_file = /etc/opt/psa/dovecot/dovecot-cert.pem
smtpd_tls_key_file = /etc/opt/psa/dovecot/dovecot-req.pem
smtp_tls_security_level = may

Next comes SASL. The following configuration uses Dovecot SASL and turns off sending credentials over plain text (so usernames and passwords are forced over TLS or SSL connections).

smtpd_sasl_auth_enable = yes
smtpd_sasl_authenticated_header = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_security_options = noanonymous, noplaintext

To run Dovecot as the LDA, you need:

mailbox_command = /opt/psa/libexec/dovecot/deliver

If you're not running comsat (who does any more?), you probably want the following so it doesn't print lots of useless logs:

biff = no

The following client restrictions stop some spam by killing off anyone who doesn't wait for Postfix to answer the connection before sending commands (the sleep 1, followed by the reject_unauth_pipelining).

smtpd_client_restrictions = permit_sasl_authenticated,
  permit_mynetworks, 
  sleep 1, 
  reject_unauth_pipelining, 
  check_client_access pcre:/etc/opt/psa/postfix/maps/client_access,
  reject_unknown_client_hostname, 
  permit
\# Required to make the sleep and reject_unauth_pipelining work properly
smtpd_delay_reject = no

Now we allow any of our authenticated senders and do some more DNS based checks:

smtpd_sender_restrictions = permit_sasl_authenticated, 
  permit_mynetworks, 
  check_sender_access pcre:/etc/opt/psa/postfix/maps/sender_access, 
  reject_unknown_sender_domain, 
  reject_unauth_pipelining, 
  reject_non_fqdn_sender, 
  permit

And finally, our recipient restrictions. Note that policyd needs to go here so it gets all the information necessary to make a decision.

smtpd_recipient_restrictions = permit_sasl_authenticated, 
  permit_mynetworks, 
  reject_unknown_recipient_domain, 
  reject_unauth_pipelining, 
  reject_non_fqdn_recipient, 
  reject_unauth_destination, 
  check_policy_service inet:127.0.0.1:10031

Dovecot

Compile

Dovecot is also pretty straight forward to compile. The only issue I ran into was the version of krb5-config that ships with Solaris 10 identifies itself has using 1.2.1 which causes Dovecot's configure to barf (it is also old enough that it doesn't recognize krb5-config --libs gssapi). You need to update the configure script to as per the notes below to get it to work.

\#!/bin/bash
\# Notes:
\# Need to change krb5-config lines by:
\# - removing gssapi from --libs and --cflags calls
\# - add -lgss to the AUTH_LIBS lines
\# - don't check for version 1.2
export CC=cc
export CPPFLAGS="-I/usr/sfw/include"
export LDFLAGS="-R/usr/sfw/lib -L/usr/sfw/lib"
./configure --prefix=/opt/psa --sysconfdir=/etc/opt/psa/dovecot --localstatedir=/var/opt/psa/dovecot --with-gssapi --enable-header-install
make
make DESTDIR=/tmp/dovecot-install install

Configure

Dovecot configuration is very straight forward.

Note that the keytab for dovecot needs to have the following to allow Postfix and Dovecot to do GSSAPI:

imap/fully.qualified.host.name
smtp/fully.qualified.host.name

The FQDN needs to be whatever the client will resolve it as. So, if you've got a CNAME of imap.otoh.org pointing to suricate.otoh.org then you need to have imap/suricate.otoh.org rather than imap/imap.otoh.org. If you go through a load balancer and use A record of imap.company.com then you'll need to set auth_gssapi_hostname appropriately.

\# SSL 
ssl_cert_file = /etc/opt/psa/dovecot/dovecot-cert.pem
ssl_key_file = /etc/opt/psa/dovecot/dovecot-req.pem
ssl_ca_file =  /etc/sfw/openssl/cacert.pem
\# Don't allow sending passwords in the clear
disable_plaintext_auth = yes
\# Use a real timestamp
log_timestamp = "%Y-%m-%dT%H:%M:%S%z "
syslog_facility = mail
\# Don't advertise what we are
login_greeting = Ready.
\# Maildirs
mail_location = maildir:~/Maildir
\# We shouldn't use dotlocks, but if we do, use O_EXCL to open it
dotlock_use_excl = yes
\# Fast and NFS safe
lock_method = fcntl
\# Again, we're not using mbox, but if for some reason we do, use fcntl
mbox_read_locks = fcntl
mbox_write_locks = fcntl
\# System accounts don't use IMAP
first_valid_uid = 1000
\# Use hardlinks to save space
maildir_copy_with_hardlinks = yes
maildir_copy_preserve_filename = yes
\# Advertise capabilities in the greeting
protocol imap {
  login_greeting_capability = yes
}
  
protocol pop3 {
}
protocol lda {
  postmaster_address = postmaster@XXX \# yes, this is set properly in the configuration
  \# set these to null and set syslog_facility so everything is sent to syslog
  log_path =
  info_log_path =
  syslog_facility = mail
  \# Use Seive
  mail_plugins = cmusieve
}
\# Keytab has to have imap/HOSTNAME and smtp/HOSTNAME in it
auth_krb5_keytab = /etc/opt/psa/dovecot/dovecot.keytab
\# Allow both GSSAPI and username/password
auth default {
  mechanisms = plain gssapi
  passdb pam {
  }
  userdb passwd {
  }
  \# Have to run authd as root so we can open the shadow file
  user = root
  \# This is required for Postfix to talk to us
  socket listen {
    client {
      path = /var/opt/psa/postfix/private/auth
      mode = 0660
      user = postfix
      group = postfix
    }
  }
}

The other issue I found is that the Dovecot auth daemon segfaults if you have pam_kerb5 in it's auth stack. Add this to /etc/pam.conf to fix this.

dovecot auth requisite          pam_authtok_get.so.1
dovecot auth required           pam_dhkeys.so.1
dovecot auth required           pam_unix_cred.so.1
dovecot auth required           pam_unix_auth.so.1

Dovecot also uses the dovecot service by default, you can tell it to use specific services by setting:

auth default {
  passdb pam {
    args = *
  }
}

Then you'll need service specific (imap, pop for example) auth lines in your /etc/pam.conf.

Dovecot Sieve

This is very simple, just compile and install.

MySQL

Policyd requires MySQL :(

Compile

I took a look at the MySQL AB packages but they install in a nasty fashion (not zone aware and assume /usr/local) and the postinstall scripts are broken so I built my own :(

\#!/bin/bash
export PATH="/opt/SUNWspro/bin:/usr/ccs/bin:/usr/bin"
make clean 
arch="$(isainfo -k)"
export CC=cc
export CXX=CC
export CPPFLAGS="-I/usr/sfw/include"
export LDFLAGS="-R/usr/sfw/lib/${arch} -R/usr/sfw/lib -L/usr/sfw/lib/${arch} -L/usr/sfw/lib"
export ASFLAGS="-xarch=${arch}" 
export CFLAGS="-Xa -xstrconst -mt -D_FORTEC_ -m64"
export CXXFLAGS="-noex -mt -D_FORTEC_ -m64"
./configure --prefix=/opt/psa --sysconfdir=/etc/opt/psa/mysql --localstatedir=/var/opt/psa/mysql --with-mysqld-user=mysql --without-debug --enable-thread-safe-client --enable-local-infile --enable-assembler --with-extra-charsets=complex --with-openssl --with-openssl-includes=/usr/sfw/include --with-openssl-libs=/usr/sfw/lib
make 
make DESTDIR=/tmp/mysql-install install

Configure

Use the standard MySQL process to create a base DB. I just used the small systems my.cnf as this DB is tiny.

Policyd

Compile

Use the 1.9x series (yes, it's beta but it's got a proper configure setup and should have the patches I wrote to make it compile cleanly on Solaris applied to it by the time you read this).

#!/bin/bash
export PATH="/opt/SUNWspro/bin:/usr/ccs/bin:/usr/bin"
export CC=cc
export CPPFLAGS="-I/usr/sfw/include -I/opt/psa/include"
export LDFLAGS="-R/usr/sfw/lib -L/usr/sfw/lib -R/opt/psa/lib -L/opt/psa/lib -R/opt/psa/lib/mysql -L/opt/psa/lib/mysql"
export CFLAGS="-m64"
export CXXFLAGS="-m64"
make clean
./configure --prefix=/opt/psa --sysconfdir=/etc/opt/psa/policyd --localstatedir=/var/opt/psa/policyd --with-mysql-include=/opt/psa/include/mysql --with-mysql-lib=/opt/psa/lib/mysql
make
make DESTDIR=/tmp/policyd-install install

Configure

First, follow the instructions; setup the policyd database and import the base SQL into the database.

The default mysql user is postfix but given this is policyd not postfix, I prefer to use the policyd user for MySQL.

Configuration of policyd is pretty straight forward, there's little tweaking to be done just follow the instructions.

The only gotcha's I fell for are that all configuration items need to be specified (i.e. don't comment out the stuff you're not using, set it to a default or blank value). This means you need something like:

\# chroot:
\#
\#   directory to change to before binding
\#
CHROOT=/
\#
\# uid:
\#
\#   userid for the policy daemon to run as
\#
UID=125
\#
\# gid:
\#
\#   groupid for the policy daemon to run as
\#   
GID=125
even if you're starting it via SMF. In this case, 125 maps to the policyd user.

The other trap was that you need to set BLACKLISTING=1 to have it insert anything for any of the blacklist checks into the database. You can't just turn on BLACKLIST_HELO and have it populate the database so you can look at it before enabling blocking, it's either on or off.

References