E-mail: The SUNY Potsdam Model: Scalable Open-Source Infrastructure

E-mail: The SUNY Potsdam Model: Scalable Open-Source Infrastructure


E-mail is ever the complicated endeavor, from storage challenges at the core, to database quarantining complexities, to all-out combat with mail exchangers across the Internet.

SUNY Wizard Fall 2012 Presentation
Campus Overview 2007





Introduction

Audience

This document is intended primarily for experienced system administrators or mail administrators interested in building mail infrastructure on top of open-source technologies. It is written comprehensively, inspired by the likes of Life with qmail, and presented in a modular fashion down lines of function. Because of that, it should also be suitable for entry-level admins who are looking for guidance dealing with the complexities of email.

Technical Overview

This entire solution is built on open-source technology on open standards: Qmail, Amavisd-new, Maia Mailguard, ClamAV, SpamAssassin, Dovecot, SquirrelMail, 389DS (LDAP), ATA over Ethernet storage, Apache, and dozens of purpose-built Perl scripts and systems to keep it all humming. All on Linux.

The mail exchangers run qmail in a multi-MTA setup, with Amavisd-new/Maia Mailguard doing the heavy lifting between them to disassemble messages for content scanning and virus and spam checks using ClamAV and SpamAssassin, and potential quarantine into a MySQL database.

10.137.0.1
qmail-outside
port 25
rbldnsd
greetdelay
validrcptto
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
maia
port 10024
clamav
spamassassin
127.0.0.1
qmail-inside
port 10025

Messages passing all checks are delivered to the mail store cluster running qmail-ldap. There they are forwarded to the correct mailhost and subject to other directions based on user LDAP attributes. Webmail is provided by SquirrelMail, with the Maia Mailguard frontend integrated for user quarantine management, on top of the Apache web server.

NOTE: DNS, LDAP, PAM/NSS, MySQL, and Apache system setup and configuration are not covered in great depth, and are generally outside the scope of this guide. However, we do touch upon application-level requirements among the various services.

Legacy

This solution has continually evolved to keep up with emerging email trends for over ten years. Beginning from a POP mailstore, then IMAP, adding a dedicated antivirus mail gateway, becoming an anti-spam tagging and later quarantining exchanger, eventually taking on SMTP-Auth. Mail storage has moved from local storage, to DAS, NAS, and SAN storage through numerous iterations. Directory integration began with NSS and some scripts, reaching its zenith with qmail-ldap.

This solution is currently scaled out minimally to handle a relatively small user base of approximately 10,000 active mail accounts and aliases/lists. The mail exchangers see hundreds of thousands of connections per day, and about 100,000 final deliveries to user mailboxes. The principles outlined here can and have been scaled out to much much larger installations. Thoughts about scaling out the various pieces are provided at the end of each section.

Email, and the challenges it has presented, have driven innovation throughout the entire host and network systems environment over the last decade. User management, storage, high-availability, authentication, database, LDAP, DNS, web administration interfaces, and untold other aspects of our operation have benefitted greatly from having to meet the requirements of email. It is the core of our central services.

Assumptions

In any document such as this, it is best to build for a somewhat realistic case. We will assume the following throughout:

  • madstop.edu is the domain name
  • 10.137.0.0/16 is the local network range and is magically Internet-routable


Mail Storage

We begin with the central piece of the puzzle: the mailstore. To handle transfer of messages into user home directories, a stock netqmail will suffice. However, we desire the extra features that qmail-ldap can provide: centralized storage and lookup of user and alias information, and the mobility and flexibility afforded by the clustering extensions. We will also employ the Dovecot IMAP/POP server for user mail retrieval. As an optional third component, we will install a second instance of qmail to handle forwarding to a backup machine.

Setting up multiple qmails on one box is actually very straightforward, as they can each share the same ucspi-tcp and daemontools install, and all just need different install directory locations defined.

For now we will concentrate on one machine known as hermes.madstop.edu, to which all the exchangers will smtproute their deliveries. It will also carry the aliases pop.madstop.edu and imap.madstop.edu, which will be advertised for POP and IMAP client configuration. Ideas for scaling this out are at the end of this section.


Qmail

The first qmail instance on TCP port 25 is a qmail-ldap installation. We also set it up to listen on TCP port 628 for QMQP connections from other qmail-ldap hosts in the cluster. We sometimes refer to this as qmail-outside to differentiate it from the other qmails in the chain. It is installed in the standard place: /var/qmail.

Begin with qmail-ldap installation noting the differences below before moving on to qmail configuration and startup. The following sections build from there, highlighting changes and enhancements to that base.

Qmail Pre-compilation

We need to ensure required pieces are turned on at compile-time in the heavily-commented qmail-ldap Makefile. Some salient configuration details follow:

LDAPFLAGS=-DALTQUEUE -DEXTERNAL_TODO -DDASH_EXT -DCLEARTEXTPASSWD -DQLDAP_CLUSTER
- enable qmailqueue, external todo, dash-extension addresses, and cluster extensions
LDAPLIBS=-L/usr/lib64 -lldap -llber
- adjust as necessary
LDAPINCLUDES=-I/usr/include
- adjust as necessary
MDIRMAKE=-DAUTOMAILDIRMAKE
- enable auto-creation of user Maildirs on mail delivery
HDIRMAKE=-DAUTOHOMEDIRMAKE
- enable auto-creation of user home directories on mail delivery
SHADOWLIBS=-lcrypt
- may or may not be necessary

We also need to take a look at qmail-ldap.h to ensure LDAP attributes are mapped correctly. In our case, since we expect that mail will be delivered and owned by individual users, and the uidNumbers and gidNumbers we have defined in LDAP will suffice for each, we do the following:

#define LDAP_QMAILUID		"uidNumber"
#define LDAP_QMAILGID		"gidNumber"

Qmail Configuration Files: /var/qmail/control

We need to ensure that qmail is configured for our ldap environment across a number of files in /var/qmail/control. Some salient configuration details follow:

dirmaker: /var/qmail/bin/dirmaker
- full path to script for autohomedirmake feature
ldapbasedn: o=madstop.edu
- site-specific distinguished name on which ldap searches are based
ldapcluster: 1
- toggles clustering extensions on/off with 1|0
- lookups for user ldap attribute "mailhost" determine where mail is forwarded via qmqp across the cluster
ldapdefaultdotmode: both
- control how user delivery instructions are looked up
- both (ldap attribute "deliveryProgramPath" and .qmail files are used)
- dotonly (only .qmail files are used)
- ldaponly (ldap attribute "deliveryProgramPath" and .qmail files are ignored)
- ldapwithprog (attribute "deliveryProgramPath is used if existent, .qmail files are ignored)
ldapserver: ldap.madstop.edu
- site-specific hostname or address and/or port number of ldap server

The defaultdelivery control file is quite special in this setup:

#./Maildir/
|/var/qmail-forward/bin/qmail-inject -a $USER@madstop.edu
|/var/qmail/bin/preline -f /usr/libexec/dovecot/deliver

Rather than standard ./Maildir/ delivery, we first inject the message into the qmail-forward queue (for double-delivery to backup box), and then pass it on to the Dovecot local delivery agent (gaining features such as sieve filtering), for final delivery into user homes. It is done in this order because every command must be successful or the message is re-delivered. With Dovecot deliver last, we ensure that users will not get duplicate messages due to a problem with injection into the forward queue.

NOTE: If you elect not to install the qmail-forward instance for backup, make sure to remove this directive from defaultdelivery.

Qmail Startup Scripts

Our stock setup suffices for the most part with but few changes. Ensure that the "name" for this qmail instance retains the default setting at the top of the startup and run scripts:

QMAIL=qmail
TCPRULES=tcp.smtp

Next, while the our standard qmail-smtpd and qmail-send run scripts suffice, we will also be running qmail-qmqpd to handle cluster deliveries. These scripts are based off of the qmail-smtpd setup with substiution s/smtpd/qmqpd/g for the most part:

mkdir -p /var/qmail/supervise/qmail-qmqpd/log

/var/qmail/supervise/qmail-qmqpd/run

#!/bin/sh
QMAIL=qmail
TCPRULES=tcp.qmqp

QMAILDUID=`id -u qmaild`
NOFILESGID=`id -g qmaild`
MAXSMTPD=`cat /var/qmail/control/concurrencyincoming`
LOCAL=`head -1 /var/qmail/control/me`

if [ -z "$QMAILDUID" -o -z "$NOFILESGID" -o -z "$MAXSMTPD" -o -z "$LOCAL" ]; then
	echo QMAILDUID, NOFILESGID, MAXSMTPD, or LOCAL is unset in
	echo /var/$QMAIL/supervise/qmail-qmqpd/run
	exit 1
fi

if [ ! -f /var/$QMAIL/control/rcpthosts ]; then
	echo "No /var/$QMAIL/control/rcpthosts!"
	echo "Refusing to start SMTP listener because it'll create an open relay"
	exit 1
fi

exec /usr/local/bin/softlimit -m 8000000 \
	/usr/local/bin/tcpserver -v -R -l "$LOCAL" -x /etc/$TCPRULES.cdb -c "$MAXSMTPD" \
		-u "$QMAILDUID" -g "$NOFILESGID" 0 628 /var/$QMAIL/bin/qmail-qmqpd 2>&1

/var/qmail/supervise/qmail-qmqpd/log/run

#!/bin/sh
QMAIL=qmail
exec /usr/local/bin/setuidgid qmaill /usr/local/bin/multilog t s16777215 n50 /var/log/$QMAIL/qmqpd

Chmod, mkdir, chown, and symlink the qmqpd service into place:

chmod 755 /var/qmail/supervise/qmail-qmqpd/run
chmod 755 /var/qmail/supervise/qmail-qmqpd/log/run
mkdir -p /var/log/qmail/qmqpd
chown qmaill /var/log/qmail/qmqpd
ln -s /var/qmail/supervise/qmail-qmqpd /service

One last thing remains: integrating the qmail-qmqpd service into the qmailctl script. This is nothing more than adding the equivalent calls to qmqpd alongside all the qmail-smtpd invocations and setup

/var/qmail/bin/qmailctl

#!/bin/sh

# description: the qmail MTA

QMAIL=qmail
TCPRULES=tcp.smtp
TCPRULESQMQ=tcp.qmqp

PATH=/var/$QMAIL/bin:/bin:/usr/bin:/usr/local/bin:/usr/local/sbin
export PATH

QMAILDUID=`id -u qmaild`
NOFILESGID=`id -g qmaild`

case "$1" in
	start)
		echo "Starting $QMAIL"
		if svok /service/$QMAIL-send ; then
			svc -u /service/$QMAIL-send /service/$QMAIL-send/log
		else
			echo "qmail-send supervise not running"
		fi
		if svok /service/$QMAIL-smtpd ; then
			svc -u /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		else
			echo "qmail-smtpd supervise not running"
		fi
		if svok /service/$QMAIL-qmqpd ; then
			svc -u /service/$QMAIL-qmqpd /service/$QMAIL-qmqpd/log
		else
			echo "qmail-qmqpd supervise not running"
		fi
		if [ -d /var/lock/subsys ]; then
			touch /var/lock/subsys/$QMAIL
		fi
		sleep 1;
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		svstat /service/$QMAIL-qmqpd
		svstat /service/$QMAIL-qmqpd/log
		/var/$QMAIL/bin/qmail-qstat
		;;
	stop)
		echo "Stopping $QMAIL..."
		echo "  qmail-smtpd"
		svc -d /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		echo "  qmail-qmqpd"
		svc -d /service/$QMAIL-qmqpd /service/$QMAIL-qmqpd/log
		echo "  qmail-send"
		svc -d /service/$QMAIL-send /service/$QMAIL-send/log
		if [ -f /var/lock/subsys/$QMAIL ]; then
			rm /var/lock/subsys/$QMAIL
		fi
		sleep 1;
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		svstat /service/$QMAIL-qmqpd
		svstat /service/$QMAIL-qmqpd/log
		/var/$QMAIL/bin/qmail-qstat
		;;
	stat)
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		svstat /service/$QMAIL-qmqpd
		svstat /service/$QMAIL-qmqpd/log
		/var/$QMAIL/bin/qmail-qstat
		;;
	doqueue|alrm|flush)
		echo "Flushing timeout table and sending ALRM signal to qmail-send."
		/var/$QMAIL/bin/qmail-tcpok
		svc -a /service/$QMAIL-send
		;;
	queue)
		/var/$QMAIL/bin/qmail-qstat
		/var/$QMAIL/bin/qmail-qread
		;;
	reload|hup)
		echo "Sending HUP signal to qmail-send."
		svc -h /service/$QMAIL-send
		;;
	pause)
		echo "Pausing qmail-send"
		svc -p /service/$QMAIL-send
		echo "Pausing qmail-smtpd"
		svc -p /service/$QMAIL-smtpd
		echo "Pausing qmail-qmqpd"
		svc -p /service/$QMAIL-qmqpd
		;;
	cont)
		echo "Continuing qmail-send"
		svc -c /service/$QMAIL-send
		echo "Continuing qmail-smtpd"
 		svc -c /service/$QMAIL-smtpd
 		echo "Continuing qmail-qmqpd"
 		svc -c /service/$QMAIL-qmqpd
		;;
	restart)
		echo "Restarting $QMAIL:"
		echo "* Stopping qmail-smtpd."
		svc -d /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		echo "* Stopping qmail-qmqpd."
		svc -d /service/$QMAIL-qmqpd /service/$QMAIL-qmqpd/log
		echo "* Sending qmail-send SIGTERM and restarting."
		svc -t /service/$QMAIL-send /service/$QMAIL-send/log
		echo "* Restarting qmail-smtpd."
		svc -u /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		echo "* Restarting qmail-qmqpd."
		svc -u /service/$QMAIL-qmqpd /service/$QMAIL-qmqpd/log
		sleep 1;
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
 		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		svstat /service/$QMAIL-qmqpd
		svstat /service/$QMAIL-qmqpd/log
		/var/$QMAIL/bin/qmail-qstat
		;;
	cdb)
		tcprules /etc/$TCPRULES.cdb /etc/$TCPRULES.tmp < /etc/$TCPRULES
		tcprules /etc/$TCPRULESQMQ.cdb /etc/$TCPRULESQMQ.tmp < /etc/$TCPRULESQMQ
		chmod 644 /etc/$TCPRULES.cdb
		chmod 644 /etc/$TCPRULESQMQ.cdb
		echo "Reloaded: /etc/$TCPRULES /etc/$TCPRULESQMQ"
		;;
	sendstop)
		echo "Stopping qmail-send"
		svc -d /service/$QMAIL-send /service/$QMAIL-send/log
		;;
	help)
		cat <<HELP
    stop -- stops mail service (smtp connections refused, nothing goes out)
   start -- starts mail service (smtp connection accepted, mail can go out)
   pause -- temporarily stops mail service (connections accepted, nothing leaves)
    cont -- continues paused mail service
    stat -- displays status of mail service
     cdb -- rebuild the tcpserver cdb file for smtp
 restart -- stops and restarts smtp, sends qmail-send a TERM & restarts it
 doqueue -- schedules queued messages for immediate delivery
  reload -- sends qmail-send HUP, rereading locals and virtualdomains
   queue -- shows status of queue
    alrm -- same as doqueue
   flush -- same as doqueue
     hup -- same as reload
sendstop -- stop qmail-send
HELP
    ;;
	*)
		echo "Usage: $0 {start|stop|restart|doqueue|flush|reload|stat|pause|cont|cdb|queue|sendstop|help}"
		exit 1
		;;
esac

exit 0

Tcpserver Configuration

There is not much to do here except make sure the config file exists.

/etc/tcp.smtp

127.:allow,RELAYCLIENT=""

/etc/tcp.qmqp

127.:allow,RELAYCLIENT=""

Auto Home/Maildir Creation

Compile-time options AUTOHOMEDIRMAKE and AUTOMAILDIRMAKE provide the nifty benefit of (you guessed it) home directory and Maildir creation at time of mail delivery, running the script specified in the dirmaker control file. This eliminates any need for the complexities of out-of-band scripts and such to create the same. The script runs as the recipient user's uid/gid, so for actual home directory creation, that user will require write permissions to the parent directory. One way to handle this is with group write permissions, another is with a virtual user install where all users are mapped to a single userid. We prefer the former.

AUTOHOMEDIRMAKE expects to find a dirmaker file in /var/qmail/control. This contains the full path to a script responsible for home creation, for example: /var/qmail/bin/dirmaker. The script gets the path for the homedir as first parameter and aliasempty as the second one. Here is an example dirmaker script:

#!/bin/sh
/bin/mkdir -m 700 -p $1

We must ensure the dirmaker script is executable, or messages to new users will bounce with a permanent error:

chmod 755 /var/qmail/bin/dirmaker

Dovecot

We proceed now with Dovecot installation to provide POP/IMAP service. We intend to end up with Dovecot, authenticating against LDAP, listening on TCP ports 110/995 for POP/POPS, TCP ports 143/993 for IMAP/IMAPS, and TCP port 2000 for Managesieve. Lastly, we will setup Dovecot's local delivery agent, called simply "deliver," for final qmail mail delivery into user homes. This LDA supports server-side filtering of mail using sieve.

Installation

Dovecot packages should exist for your distribution:

yum install dovecot dovecot-sieve dovecot-managesieve

NOTE: These package names represent Fedora 11, Dovecot 1.2.6.

Configuration

Dovecot configuration is in the conventional place, /etc/doveconf.conf. We highlight directives that setup LDAP integration. However, instead of authenticating to LDAP directly, we instead elect to use the checkpassword interface and our own perl script for maximum flexibility.

Tweaks

protocols = imap imaps pop3 pop3s managesieve
- ensure managesieve is enabled
listen = *
- listen on all IPv4 interfaces (we disable IPv6, bug in this version of Dovecot)
login_process_per_connection = no
- login process allowed to handle multiple connections, faster
login_processes_count = 6
- increase number of listening login processes
login_max_processes_count = 1024
- increase max number of login processes
login_max_connections = 2048
- max connections per login process
mail_location = maildir:~/Maildir:INDEX=/var/indexes/%u
- we take advantage of the flexibility to store indexes on a different high-performance volume outside of user homes
mail_drop_priv_before_exec = no
- this is required for our dovecot home creation process to work properly
max_mail_processes = 4096
- increase the maximum number of mail processes

IMAP Namespaces

The namespace parameter allows for tweaking the IMAP namespace. IMAP namespaces are a source of contention across different IMAP servers and clients, and the following block allows for supporting more modern clients that do not require a prefix, as well as older setups that are still hardcoded to look for an INBOX. For example, this setup prevents folders from being nested beneath "Inbox" in Thunderbird regardless of whether or not the "IMAP server directory" is set in the account preferences dialog.

namespace private {
	separator = .
	prefix =
	inbox = yes
}

namespace private {
	separator = .
	prefix = INBOX.
	inbox = no
	hidden = yes
	list = no   # for v1.1+
}

Protocols

The protocol imap stanza contains more than the following, but these are changes. Note in particular that we override the mail executable with our own perl. We also increase the number of connections per ip, since some devices are gluttons for imap. Finally, we point to our SSL cert.

protocol imap {
	mail_executable = /opt/bin/dovelogin.pl imap
	mail_max_userip_connections = 20
	ssl_cert_file = /etc/pki/dovecot/certs/pop.crt
	ssl_key_file = /etc/pki/dovecot/private/pop.key
}

The protocol pop3 stanza sees changes analogous to the imap section.

protocol pop3 {
	mail_executable = /opt/bin/dovelogin.pl pop3
	ssl_cert_file = /etc/pki/dovecot/certs/pop.crt
	ssl_key_file = /etc/pki/dovecot/private/pop.key
}

The protocol lda stanza configures the local delivery agent. Only changes are presented here. The hostname parameter is important as that will be the domain name in the return address of vacation messages. And we need to enable sieve support.

protocol lda {
	postmaster_address = postmaster@madstop.edu
	hostname = madstop.edu
	mail_plugins = sieve
}

The protocol managesieve stanza sees no changes from the defaults.

protocol managesieve {
	#...
}

Authentication

Authentication is highly flexible in Dovecot. There are two aspects to deal with, passdb to auth users, and userdb to retrieve info about users. In our case, we plan to use the checkpassword interface and my chkpassldap script to bind against ldap. This has the added benefit of being "prefetch-capable," which in Dovecot parlance means that you can get away without doing a separate userdb lookup, as long as the script sets certain environment variables. So we turn on the prefetch userdb. Despite this, we still must turn on a userdb so that the Dovecot LDA can look up information at delivery time. We choose the plain passwd interface, since our machine is setup to do nss-ldap. The below is all inside the auth default stanza.

auth default {
	mechanisms plain
	user root
	passdb checkpassword {
		args = /opt/bin/chkpassldap.pl
	}
	userdb prefetch {
	}
	userdb passwd {
		args = blocking=yes
	}
}	

Plugins

There is not much to do here, but we do change the sieve plugin's sieve_dir value.

plugin {
	sieve_dir=~/.sieve
}

Chkpassldap

With the chkpassldap script installed in /opt/bin, we need to configure it for our environment, starting with typical variables to define ldap settings:

my $ldap_server='ldap.madstop.edu:389';
my $ldap_base = 'o=madstop.edu';
my $people_base = "ou=People,$ldap_base";
my $group_base = "ou=Groups,$ldap_base";
my $search_scope = 'sub';
my $group = 'svc_email';
my $attribute = '';
my $envset = 1;

The group and attribute variables (if set) require group membership and/or an attribute=value pairing for a user to be authorized, while the envset variable enables the environment mangling we do next.

Dovecot offers some very complex manipulation of the login process through the use of a variety of environment variables, and this is controlled in the %envmap hash:

my %envmap = (
	'HOME' =>		{ ldap=>'homeDirectory' },
	'USER' =>		{ ldap=>'uid' },
	'userdb_uid' =>		{ ldap=>'uidNumber', extra=>1 },
	'userdb_gid' =>		{ ldap=>'gidNumber', extra=>1 },
	'host' =>		{ ldap=>'mailHost', extra=>1, optional=>1, nslookup=>1 },
	'proxy_maybe' =>	{ value=>1, extra=>1, depends=>'host' },
);

The above sets the environment variables HOME, USER, userdb_uid, userdb_gid, and host to ldap attribute values as specified. Those flagged as extra are saved in the EXTRA environment variable as a space-separated list by variable name, a requirement as laid out in the Dovecot docs. The proxy_maybe variable is hardcoded to 1, as long as the host was successfully returned, itself being looked up in DNS to provide an IP number. This is because we reuse the mailHost qmail attribute from the qmail-ldap schema, and Dovecot expects an IP address and not a name.

You must plan on reading the Dovecot authentication documentation to see if this is the approach for you, and what it all does.


Qmail-Forward

The second qmail instance on tcp port 10025 is a netqmail installation named qmail-forward. This serves as nothing more than a queuing and forwarding instance that will be used to double-deliver mail to our mail backup box.

NOTE: If this backup scheme is not to your taste, you can skip this section.

Begin with netqmail installation noting the differences below before moving on to qmail configuration and startup. The following sections build from there, highlighting changes and enhancements to that base.

Qmail Pre-compilation

Because our primary qmail (outside) instance is installed in /var/qmail, we need a new home for qmail-forward. Before compiling, we need to edit the conf-qmail file in the qmail source directory and change it thus:

/var/qmail-forward

All other differences are handled in the run scripts.

Qmail Configuration Files: /var/qmail-forward/control

Standard qmail control considerations apply as always. Some salient configuration details follow. Because this is merely the forward MTA, we are mainly concerned with the smtproutes file:

:hades.madstop.edu

This indicates that mail to all domains is forwarded to hades.madstop.edu.

Qmail Startup Scripts

Our stock setup suffices for the most part with but few changes. First we need to "name" this qmail instance in the qmailctl script and run scripts so the various bits can find their homes and log locations. At the top of each script, change the following:

QMAIL=qmail-forward
TCPRULES=tcp.smtp-forward

Next, because this is our second qmail instance and we intend to run on port 10025, we need to adjust /var/qmail-forward/supervise/qmail-smtpd/run. We already adjusted the script variables, so we concentrate on the relevant lines:

exec /usr/local/bin/softlimit -m 5000000 \
	/usr/local/bin/tcpserver -v -R -l "$LOCAL" -x /etc/$TCPRULES.cdb -c "$MAXSMTPD" \
		-u "$QMAILDUID" -g "$NOFILESGID" 127.0.0.1 10025 /var/$QMAIL/bin/qmail-smtpd 2>&1

Last, we link this qmail's qmailctl script into place as qmailctlforward:

ln -s /var/qmail-forward/bin/qmailctl /usr/local/bin/qmailctlforward

Tcpserver Configuration

There is not much to do here except make sure it exists.

/etc/tcp.smtp-forward

127.:allow,RELAYCLIENT=""

Scalability

This setup is designed to be scaled at the application layer, adding additional mailstores and spreading user mail storage and accounts across multiple boxes as needs require.

Assume all users have their mailHost LDAP attribute set to one mailstore, example hermes.madstop.edu. If we decide to augment this setup with an additional machine, we can do so completely transparently. We need only clone the machine, call it hermes2 and configure appropriately. Anyone whose mailHost attribute is set to hermes2.madstop.edu will have mail delivered there and will see their POP/IMAP sessions proxied there. With autohome and automaildir creation via all protocols, new users just need to be created in LDAP.

NOTE: Existing users need to be moved with care, lest mail delivered or POP/IMAP login create new homes during the move. One way to accomplish this is to chmod a-wx the home directory before move, as that will cause mail to stay in the queue with a tempfail. Copy the mail to the destination mailstore, change the mailHost, and then change the perms back to normal.

The flexibility offered by this setup allows for many different approaches:

  • The smtproutes from the mail exchangers can hit one mailstore, which delivers locally to itself when me=mailHost, otherwise qmqpd to other mailHost. Likewise, IMAP/POP clients can be configured to hit one mailstore, which logs in locally when ip=mailHost, otherwise proxies to other mailHost. This is not ideal for any kind of high-availability, but may ease migrations.
  • All smtproutes and mail clients could instead deliver/login to a round-robin DNS name that resolves to all the mailstores. They will all forward/proxy appropriately.
  • All smtproutes and mail clients could instead deliver/login to dedicated QMQP forwarding and Dovecot proxying machine(s) that have no local mail. These machines could take advantage of whatever high-availability scheme is desired.
  • The mail exchangers themselves could have their qmail-inside instances built with the qmail-ldap patch, so they forward directly to the appropriate mailstore over QMQP. Dovecot proxying for POP/IMAP would still require a separate scheme as above.


Mail Backup

Our backup strategy consists of setting up a qmail backup MTA to receive mail from our primary mailstores doing double-delivery. This machine is for mail receipt and storage only, so there are no POP/IMAP user services setup. In addition to double-delivery, we also run a nightly sync script to pickup mail from user's Sent folders. If this is not to your liking, skip this section (and remember to disable the qmail-forward bits on the mailstores).

This machine will be known as hades.madstop.edu, to which all the mailstores will forward all messages via double delivery out of their defaultdelivery instructions. Ideas for scaling this out are at the end of this section.


Qmail

The one and only qmail instance on TCP port 25 is a qmail-ldap installation. There are two main reasons for this:

  1. We can use its autohomedir/automaildir make features.
  2. We can set this up as a "vmail" instance where all mail is owned and delivered as one user, avoiding unnecessary LDAP pressure from NSS

It is installed in the standard place: /var/qmail.

Begin with qmail-ldap installation noting the differences below before moving on to qmail configuration and startup. The following sections build from there, highlighting changes and enhancements to that base.

NOTE: Since this will be a vmail installation, ensure the vmail user and group is created.

Qmail Pre-compilation

We need to ensure required pieces are turned on at compile-time in the heavily-commented qmail-ldap Makefile. Some salient configuration details follow:

LDAPFLAGS=-DALTQUEUE -DEXTERNAL_TODO -DDASH_EXT -DCLEARTEXTPASSWD
- enable qmailqueue, external todo, dash-extension addresses
LDAPLIBS=-L/usr/lib64 -lldap -llber
- adjust as necessary
LDAPINCLUDES=-I/usr/include
- adjust as necessary
MDIRMAKE=-DAUTOMAILDIRMAKE
- enable auto-creation of user Maildirs on mail delivery
HDIRMAKE=-DAUTOHOMEDIRMAKE
- enable auto-creation of user home directories on mail delivery
SHADOWLIBS=-lcrypt
- may or may not be necessary

NOTE: The main difference between this and the mailstore qmail-ldap install is that we do not bother compiling in the cluster extensions.

We also need to take a look at qmail-ldap.h to ensure LDAP attributes are mapped correctly. This is a very special-case qmail-ldap install, designed purely for offline reception. Since it is LDAP-aware, it would normally expect to read and follow the directions of the various user attributes to determine delivery instructions and other features. Because these will be set for the use of our mailstores, we do not want this box to read those at all. As an example, if a user had mailReplyText set, you could risk sending a reply from the backup box in addition to the mailstore. Or a user may have mailHost set to one of the mailstores, and our backup box would see that when it attempted to deliver, and try to deliver it back the way it came (if clustering was on)!

To prevent this, we need to cripple this install and disable all of these features. The best way to turn these off is to map the attributes to values that will never exist:

#define LDAP_QMAILUID		"skipqmailUID"
#define LDAP_QMAILGID		"skipqmailGID"
#define LDAP_MAILSTORE		"skipmailMessageStore"
#define LDAP_QUOTA			"skipmailQuota"
#define LDAP_QUOTA_SIZE		"skipmailQuotaSize"
#define LDAP_QUOTA_COUNT	"skipmailQuotaCount"
#define LDAP_MAXMSIZE		"skipmailSizeMax"
#define LDAP_FORWARDS		"skipmailForwardingAddress"
#define LDAP_PROGRAM		"skipdeliveryProgramPath"
#define LDAP_MAILHOST		"skipmailHost"
#define LDAP_MODE			"skipdeliveryMode"
#define LDAP_REPLYTEXT		"skipmailReplyText"
#define LDAP_DOTMODE		"skipqmailDotMode"
#define LDAP_ISACTIVE		"skipaccountStatus"
#define LDAP_PURGE			"skipqmailAccountPurge"

After all of that, we really just need uid, mail, mailAlternateAddress, and homeDirectory. We leave qmailUID and qmailGID as is, since we override them anyway in the ldapuid and ldapgid files to values for the vmail user next.

NOTE: It is wise to minimize these kinds of scenarios further with an egress firewall rule preventing any traffic destined for port 25.

Qmail Configuration Files: /var/qmail/control

We need to ensure that qmail is configured for our ldap environment across a number of files in /var/qmail/control. Some salient configuration details follow:

dirmaker: /var/qmail/bin/dirmaker
- full path to script for autohomedirmake feature
ldapbasedn: o=madstop.edu
- site-specific distinguished name on which ldap searches are based
ldapdefaultdotmode: both
- ldaponly (ldap attribute "deliveryProgramPath" and .qmail files are ignored)
ldapgid: 402
- group id of the vmail user we created
ldapuid: 407
- user id of the vmail user we created
ldapserver: ldap.madstop.edu:389
- site-specific hostname or address and/or port number of ldap server

NOTE: The ldapgid and ldapuid must match the vmail user we created. They override the normal user values, so all mail will be owned by this user and group.

Qmail Startup Scripts

Our stock setup suffices for the most part. Ensure that the "name" for this qmail instance retains the default setting at the top of the startup and run scripts:

QMAIL=qmail
TCPRULES=tcp.smtp

NOTE: No cluster means no need for qmqpd.

Auto Home/Maildir Creation

Compile-time options AUTOHOMEDIRMAKE and AUTOMAILDIRMAKE provide the nifty benefit of (you guessed it) home directory and Maildir creation at time of mail delivery.

AUTOHOMEDIRMAKE expects to find a dirmaker file in /var/qmail/control. This contains the full path to a script responsible for home creation, for example: /var/qmail/bin/dirmaker. The script gets the path for the homedir as first parameter and aliasempty as the second one. Here is an example dirmaker script:

#!/bin/sh
/bin/mkdir -m 700 -p $1

Since we override user uid and gid values with the vmail user, we simply need to ensure the vmail user has read/write access to the directory in which user homes will be created. Example:

chown vmail:vmail /home
chmod 755 /home

Scalability

Scaling out the backup boxes is less seamless than our mailstores. Even though we do not need to worry about user logins, we cannot rely on user mailHost forwarding since we expressly disabled that to prevent delivery loops. One approach would be to set each mailHost box to forward to a specific mail backup machine.



Local Mail Exchanger

While we have a perfectly valid setup for mail delivery and reception in our primary mailstores, we wish to concentrate SMTP user delivery services on dedicated local mail exchangers. These will provide standard SMTP on TCP port 25 for the LAN, as well as SMTPS on TCP port 465, and Sumbission on TCP port 587. Both SMTPS and Submission will be encrypted using stunnel.

The local exchangers run qmail in a multi-MTA setup, with Amavisd-new doing the heavy lifting between them to disassemble messages for content scanning and virus checks using ClamAV. The three qmails on the box will share the same ucspi-tcp and daemontools installs, each forwarding to the next in the chain, including Amavisd-new, using smtproutes.

10.137.0.1
qmail-outside
port 25,465
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
amavisd
port 10024
clamav
127.0.0.1
qmail-inside
port 10025

This machine will be known as smtp.madstop.edu, which will be advertised for SMTP client configuration. Ideas for scaling this out are at the end of this section.

Depending on local network circumstance and policy, there may not be a reason to separate local mail exchange from Internet mail exchange. The Internet MXes run the gamut of real-time blacklists, antivirus and anti-spam tests, and the same versions of qmail capable of supporting all of the above SMTP-Auth features. You may wish to subject your own users to the same scrutiny as incoming Internet delivery. In our own course of development, we had two reasons for separating local exchange:

First, we initially rolled out SMTP-Auth on SMTPS port 465 on an Internet-facing mail exchanger. It was a self-signed certificate, and it required authentication to proceed. Some remote mail exchangers, possibly misconfigured and/or ill-administered, will see port 465 available and automatically try to use it to send mail. It is really rather pointless outside of special-case delivery situations to want to encrypt all mail generally. If the remote exchangers do not balk on the self-signed certificate, we will not allow delivery because they cannot authenticate anyway.

This could be seen as a slight mis-use of port 465, since submission should really use port 587 (with STARTTLS to secure passwords), and if that is followed, remote mail exchangers will probably be just fine. Unfortunately, all things SMTP are not so clearly defined. We support authentication on both, implicitly encrypted with SSL (no STARTTLS).

Second, we had been a longtime Amavisd-new user in our mail setups when it came time to look at quarantining mail. When we rolled out Maia Mailguard, we decided it was better not to subject official campus mailings to the whims of user blacklisting and repeated attempts at improper Bayes classification, envisioning a day when something important was stuck in quarantine. Running stock Amavisd-new doing anti-virus scanning gave us what we needed, and potential for future malware handling.


Qmail

10.137.0.1
qmail-outside
port 25,465

The first qmail instance on TCP port 25 is a qmail JMS Combined installation. The JMS Combined patch provides a lot of enhancements, and here we are interested in one main one: SMTP-Auth. We tend to refer to this as qmail-outside to differentiate it from the other qmails in the chain. It is installed in the standard place: /var/qmail.

Begin with qmail JMS Combined installation noting the differences below before moving on to qmail configuration and startup. The following sections build from there, highlighting changes and enhancements to that base.

Qmail Configuration Files: /var/qmail/control

Because this is the user-facing MTA, much of this is SMTP related, taking advantage of features offered by the JMS Combined setup. Salient details follow:

badmailfrom
- qmail-smtpd considers these unacceptable envelope sender addresses and will reject every recpient address
- easy way to block some incoming spam
concurrencyincoming
- we bump this up to 550
concurrenyremote
- qmail-send maximum number of simultaneous remote delivery attempts, default 10
- all deliveries for this qmail are remote to the next in the chain
- we set to something like 200
databytes
- qmail-smtp maximum number of bytes allowed in message, default 0 unlimited
defaulthost
- qmail-inject adds this name to any address without a host name
- only mail from the exchanger itself would be injected
doublebounceto
- qmail-send user to receive double-bounces
- with trim patch if file begins with a newline doublebounces are discarded
envnoathost
- qmail-send appends this domain name for recipient addresses without @ signs
locals
- we set this to localhost to ensure no deliveries are considered local
rcpthosts
- list all domains for which this exchanger will receive mail
smtproutes
- qmail-remote artificial smtp routes in form domain:relay
- we set this to :127.0.0.1:10023 to hand-off all deliveries to the next qmail in the chain

Qmail Startup Scripts

Our stock setup suffices for the most part but we will be running two additional services. First, ensure that the "name" for this qmail instance retains the default setting at the top of the startup and run scripts:

QMAIL=qmail
TCPRULES=tcp.smtp

Next, while the our standard qmail-smtpd and qmail-send run scripts suffice, we will also be running SMTPS on port 465 and encrypted Submission on port 587. There are several approaches to doing this, but we will use stunnel in the chain with qmail-smtpd. These scripts are based off of the qmail-smtpd setup.

First we setup for SMTPS on port 465:

mkdir -p /var/qmail/supervise/qmail-smtpd-ssl/log

/var/qmail/supervise/qmail-smtpd-ssl/run

#!/bin/sh
QMAIL=qmail
TCPRULES=tcp.smtps

QMAILDUID=`id -u qmaild`
NOFILESGID=`id -g qmaild`
MAXSMTPD=`cat /var/qmail/control/concurrencyincoming`
LOCAL=`head -1 /var/qmail/control/me`

if [ -z "$QMAILDUID" -o -z "$NOFILESGID" -o -z "$MAXSMTPD" -o -z "$LOCAL" ]; then
	echo QMAILDUID, NOFILESGID, MAXSMTPD, or LOCAL is unset in
	echo /var/$QMAIL/supervise/qmail-qmqpd/run
	exit 1
fi

if [ ! -f /var/$QMAIL/control/rcpthosts ]; then
	echo "No /var/$QMAIL/control/rcpthosts!"
	echo "Refusing to start SMTP listener because it'll create an open relay"
	exit 1
fi

exec /usr/local/bin/softlimit -m 20000000 \
	/usr/local/bin/tcpserver -v -R -l "$LOCAL" -x /etc/$TCPRULES.cdb -c "$MAXSMTPD" \
		-u "$QMAILDUID" -g "$NOFILESGID" 0 smtps \
	/usr/sbin/stunnel /etc/stunnel/smtp.conf 2>&1

/var/qmail/supervise/qmail-qmqpd/log/run

#!/bin/sh
QMAIL=qmail
exec /usr/local/bin/setuidgid qmaill /usr/local/bin/multilog t s16777215 n50 /var/log/$QMAIL/smtpd-ssl

Chmod, mkdir, chown, and symlink the smtpd-ssl service into place:

chmod 755 /var/qmail/supervise/qmail-smtpd-ssl/run
chmod 755 /var/qmail/supervise/qmail-smtpd-ssl/log/run
mkdir -p /var/log/qmail/smtpd-ssl
chown qmaill /var/log/qmail/smtpd-ssl
ln -s /var/qmail/supervise/qmail-smtpd-ssl /service

Now repeat the process for Submission on port 587:

mkdir -p /var/qmail/supervise/qmail-submission/log

/var/qmail/supervise/qmail-submission/run

#!/bin/sh
QMAIL=qmail
TCPRULES=tcp.submission

QMAILDUID=`id -u qmaild`
NOFILESGID=`id -g qmaild`
MAXSMTPD=`cat /var/qmail/control/concurrencyincoming`
LOCAL=`head -1 /var/qmail/control/me`

if [ -z "$QMAILDUID" -o -z "$NOFILESGID" -o -z "$MAXSMTPD" -o -z "$LOCAL" ]; then
	echo QMAILDUID, NOFILESGID, MAXSMTPD, or LOCAL is unset in
	echo /var/$QMAIL/supervise/qmail-qmqpd/run
	exit 1
fi

if [ ! -f /var/$QMAIL/control/rcpthosts ]; then
	echo "No /var/$QMAIL/control/rcpthosts!"
	echo "Refusing to start SMTP listener because it'll create an open relay"
	exit 1
fi

exec /usr/local/bin/softlimit -m 20000000 \
	/usr/local/bin/tcpserver -v -R -l "$LOCAL" -x /etc/$TCPRULES.cdb -c "$MAXSMTPD" \
		-u "$QMAILDUID" -g "$NOFILESGID" 0 smtps \
	/usr/sbin/stunnel /etc/stunnel/submission.conf 2>&1

/var/qmail/supervise/qmail-submission/log/run

#!/bin/sh
QMAIL=qmail
exec /usr/local/bin/setuidgid qmaill /usr/local/bin/multilog t s16777215 n50 /var/log/$QMAIL/submission

Chmod, mkdir, chown, and symlink the submission service into place:

chmod 755 /var/qmail/supervise/qmail-submission/run
chmod 755 /var/qmail/supervise/qmail-submission/log/run
mkdir -p /var/log/qmail/submission
chown qmaill /var/log/qmail/submission
ln -s /var/qmail/supervise/qmail-submission /service

One last thing remains: integrating the qmail-smtpd-ssl and qmail-submission services into the qmailctl script. This is nothing more than adding the equivalent calls alongside all of the qmail-smtpd invocations and setup.

/var/qmail/bin/qmailctl

#!/bin/sh

# description: the qmail MTA

QMAIL=qmail
TCPRULES=tcp.smtp
TCPRULESSSL=tcp.smtps
TCPRULESSUB=tcp.submission

PATH=/var/$QMAIL/bin:/bin:/usr/bin:/usr/local/bin:/usr/local/sbin
export PATH

QMAILDUID=`id -u qmaild`
NOFILESGID=`id -g qmaild`

case "$1" in
	start)
		echo "Starting $QMAIL"
		if svok /service/$QMAIL-send ; then
			svc -u /service/$QMAIL-send /service/$QMAIL-send/log
		else
			echo "qmail-send supervise not running"
		fi
		if svok /service/$QMAIL-smtpd ; then
			svc -u /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		else
			echo "qmail-smtpd supervise not running"
		fi
		if svok /service/qmail-smtpd-ssl ; then
			svc -u /service/$QMAIL-smtpd-ssl /service/qmail-smtpd-ssl/log
		else
			echo "qmail-smtpd-ssl supervise not running"
		fi
		if svok /service/qmail-submission ; then
			svc -u /service/$QMAIL-submission /service/qmail-submission/log
		else
			echo "qmail-submission supervise not running"
		fi
		if [ -d /var/lock/subsys ]; then
			touch /var/lock/subsys/$QMAIL
		fi
		sleep 1;
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		svstat /service/$QMAIL-smtpd-ssl
		svstat /service/$QMAIL-smtpd-ssl/log
		svstat /service/$QMAIL-submission
		svstat /service/$QMAIL-submission/log
		/var/$QMAIL/bin/qmail-qstat
		;;
	stop)
		echo "Stopping $QMAIL..."
		echo "  qmail-smtpd"
		svc -d /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		echo "  qmail-smtpd-ssl"
		svc -d /service/$QMAIL-smtpd-ssl /service/$QMAIL-smtpd-ssl/log
		echo "  qmail-submission"
		svc -d /service/$QMAIL-submission /service/$QMAIL-submission/log
		echo "  qmail-send"
		svc -d /service/$QMAIL-send /service/$QMAIL-send/log
		if [ -f /var/lock/subsys/$QMAIL ]; then
			rm /var/lock/subsys/$QMAIL
		fi
		sleep 1;
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		svstat /service/$QMAIL-smtpd-ssl
		svstat /service/$QMAIL-smtpd-ssl/log
		svstat /service/$QMAIL-submission
		svstat /service/$QMAIL-submission/log
		/var/$QMAIL/bin/qmail-qstat
		;;
	stat)
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		svstat /service/$QMAIL-smtpd-ssl
		svstat /service/$QMAIL-smtpd-ssl/log
		svstat /service/$QMAIL-submission
		svstat /service/$QMAIL-submission/log
		/var/$QMAIL/bin/qmail-qstat
		;;
	doqueue|alrm|flush)
		echo "Flushing timeout table and sending ALRM signal to qmail-send."
		/var/$QMAIL/bin/qmail-tcpok
		svc -a /service/$QMAIL-send
		;;
	queue)
		/var/$QMAIL/bin/qmail-qstat
		/var/$QMAIL/bin/qmail-qread
		;;
	reload|hup)
		echo "Sending HUP signal to qmail-send."
		svc -h /service/$QMAIL-send
		;;
	pause)
		echo "Pausing qmail-send"
		svc -p /service/$QMAIL-send
		echo "Pausing qmail-smtpd"
		svc -p /service/$QMAIL-smtpd
		echo "Pausing qmail-smtpd-ssl"
		svc -p /service/$QMAIL-smtpd-ssl
		echo "Pausing qmail-submission"
		svc -p /service/$QMAIL-submission
		;;
	cont)
		echo "Continuing qmail-send"
		svc -c /service/$QMAIL-send
		echo "Continuing qmail-smtpd"
 		svc -c /service/$QMAIL-smtpd
		echo "Continuing qmail-smtpd-ssl"
		svc -c /service/$QMAIL-smtpd-ssl
		echo "Continuing qmail-submission"
		svc -c /service/$QMAIL-submission
		;;
	restart)
		echo "Restarting $QMAIL:"
		echo "* Stopping qmail-smtpd."
		svc -d /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		echo "* Stopping qmail-smtpd-ssl."
		svc -d /service/$QMAIL-smtpd-ssl /service/$QMAIL-smtpd-ssl/log
		echo "* Stopping qmail-submission."
		svc -d /service/$QMAIL-submission /service/$QMAIL-submission/log
		echo "* Sending qmail-send SIGTERM and restarting."
		svc -t /service/$QMAIL-send /service/$QMAIL-send/log
		echo "* Restarting qmail-smtpd."
		svc -u /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		echo "* Restarting qmail-smtpd-ssl."
		svc -u /service/$QMAIL-smtpd-ssl /service/$QMAIL-smtpd-ssl/log
		echo "* Restarting qmail-submission."
		svc -u /service/$QMAIL-submission /service/$QMAIL-submission/log
		sleep 1;
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
 		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		svstat /service/$QMAIL-smtpd-ssl
		svstat /service/$QMAIL-smtpd-ssl/log
		svstat /service/$QMAIL-submission
		svstat /service/$QMAIL-submission
		/var/$QMAIL/bin/qmail-qstat
		;;
	cdb)
		tcprules /etc/$TCPRULES.cdb /etc/$TCPRULES.tmp < /etc/$TCPRULES
		tcprules /etc/$TCPRULESSSL.cdb /etc/$TCPRULESSSL.tmp < /etc/$TCPRULESSSL
		tcprules /etc/$TCPRULESSUB.cdb /etc/$TCPRULESSUB.tmp < /etc/$TCPRULESSUB
		chmod 644 /etc/$TCPRULES.cdb
		chmod 644 /etc/$TCPRULESSSL.cdb
		chmod 644 /etc/$TCPRULESSUB.cdb
		echo "Reloaded: /etc/$TCPRULES /etc/$TCPRULESSSL /etc/$TCPRULESSUB"
		;;
	sendstop)
		echo "Stopping qmail-send"
		svc -d /service/$QMAIL-send /service/$QMAIL-send/log
		;;
	help)
		cat <<HELP
    stop -- stops mail service (smtp connections refused, nothing goes out)
   start -- starts mail service (smtp connection accepted, mail can go out)
   pause -- temporarily stops mail service (connections accepted, nothing leaves)
    cont -- continues paused mail service
    stat -- displays status of mail service
     cdb -- rebuild the tcpserver cdb file for smtp
 restart -- stops and restarts smtp, sends qmail-send a TERM & restarts it
 doqueue -- schedules queued messages for immediate delivery
  reload -- sends qmail-send HUP, rereading locals and virtualdomains
   queue -- shows status of queue
    alrm -- same as doqueue
   flush -- same as doqueue
     hup -- same as reload
sendstop -- stop qmail-send
HELP
    ;;
	*)
		echo "Usage: $0 {start|stop|restart|doqueue|flush|reload|stat|pause|cont|cdb|queue|sendstop|help}"
		exit 1
		;;
esac

exit 0

Tcpserver Configuration

There are a lot of features available in the JMS Combined patch, and we need to ensure that environment variables expressing those we want enabled/disabled for each sending IP range are set. We start with plain SMTP, setting DENY_TLS so that STARTTLS support is not offered:

/etc/tcp.smtp

127.:allow,RELAYCLIENT="",RBLSMTPD="",DENY_TLS="1"
10.137.:allow,RELAYCLIENT="",RBLSMTPD="",DENY_TLS="1"
:allow,RBLSMTPD="",DENY_TLS="1"

NOTE: We do not anticipate ever allowing this port to be visible to the Internet, but if it is somehow, we at least prevent relaying.

The configuration for SMTPS requires authentication. We still disable STARTTLS, but because we are encrypting the entire session using Stunnel, we need to indicate so by setting SSL=1, so qmail-smtpd will allow authentication to proceed:

/etc/tcp.smtps

127.:allow,RELAYCLIENT="",RBLSMTPD="",SSL="1",DENY_TLS="1"
10.137.:allow,RELAYCLIENT="",RBLSMTPD="",SSL="1",DENY_TLS="1",REQUIRE_AUTH="1"
:allow,RBLSMTPD="",SSL="1",DENY_TLS="1",REQUIRE_AUTH="1"

The JMS patchset allows authenticated users to relay, so users off-network will be able to use this to send email anywhere.

The configuration for Submission is identical to SMTPS:

/etc/tcp.submission

127.:allow,RELAYCLIENT="",RBLSMTPD="",SSL="1",DENY_TLS="1"
10.137.:allow,RELAYCLIENT="",RBLSMTPD="",SSL="1",DENY_TLS="1"
:allow,RBLSMTPD="",SSL="1",DENY_TLS="1",REQUIRE_AUTH="1"

NOTE: As mentioned above, it is probably more in-line with expectation not to implicitly encrypt Submission port 587, instead offering STARTTLS. JMS supports that and can be configured to require it before authentication can proceed. We have seen client issues on our SSL port 587 since they did not expect to encounter full session encryption. YMMV.

Stunnel

With a pair of qmail services setup to use stunnel, we need to define them. Configuration is in /etc/stunnel:

/etc/stunnel/smtp.conf

cert = /etc/stunnel/smtp.pem
exec = /var/qmail/bin/qmail-smtpd
execargs = qmail-smtpd smtp.madstop.edu /opt/bin/chkpassldap.pl /bin/true
foreground = yes

/etc/stunnel/submission.conf

cert = /etc/stunnel/smtp.pem
exec = /var/qmail/bin/qmail-smtpd
execargs = qmail-smtpd smtp.madstop.edu /opt/bin/chkpassldap.pl /bin/true
foreground = yes

We now need to create the certificate that both configurations reference. We will use openssl and create a self-signed certificate as an example.

cd /etc/stunnel
openssl req -new -x509 -days 999 -nodes -out smtp.pem -keyout smtp.pem
NOTE: This will obviously result in certificate warnings for most clients. For public-facing servers, you will probably want to pay the necessary extortion fees to get certificates from a recognized CA.

Chkpassldap

With the chkpassldap script installed in /opt/bin, we need to configure it for our environment, starting with typical variables to define ldap settings:

my $ldap_server='ldap.madstop.edu:389';
my $ldap_base = 'o=madstop.edu';
my $people_base = "ou=People,$ldap_base";
my $group_base = "ou=Groups,$ldap_base";
my $search_scope = 'sub';
my $group = 'svc_email';
my $attribute = 'accountStatus=active';
my $envset = 0;

The group and attribute variables (if set) require group membership and/or an attribute=value pairing for a user to be authorized, while setting envset=0 disables any of the environment mangling (suitable mainly for Dovecot).


Qmail-Fixup

10.137.0.1
qmail-outside
port 25,465
127.0.0.1
qmail-fixup
port 10023

The second qmail instance on TCP port 10023 is a netqmail installation named qmail-fixup. The entire purpose of this qmail instance is to facilitate address rewriting for consistency, so user@mail.example.com becomes user@example.com. For this, we additionally require the mess822 package, from which we run the ofmipd daemon instead of qmail-smtpd to handle address rewrites.

NOTE: This entire qmail instance could be considered overkill. Because we are so familiar with its workings on our mail exchangers, for the sake of consistency and because it is nice to have header and address rewriting in line for future needs, we install it. If you choose to skip it, you merely need to change the smtproute in qmail-outside to hit amavisd-new at :127.0.0.1:10024.

Begin with netqmail installation noting the differences below before moving on to qmail configuration and startup.

We also require mess822 installation. The following sections build from there, highlighting changes and enhancements to that base.

Qmail Pre-compilation

Because our primary qmail (outside) instance is installed in /var/qmail, we need a new home for qmail-fixup. Before compiling, we need to edit the conf-qmail file in the qmail and mess822 source directories and change it thus:

/var/qmail-fixup

All other differences are handled in the run scripts.

Qmail Configuration Files: /var/qmail-fixup/control

Standard qmail control considerations apply as always. Some salient configuration details follow. Because this is the fixup MTA, we are mainly concerned with the rewrite file:

rewrite
- ofmipd rewrites headers according to this file
*.:
-localhost:
=:madstop.edu
=bugz.madstop.edu:madstop.edu
=mx1.madstop.edu:madstop.edu
=mx2.madstop.edu:madstop.edu
=mail.madstop.edu:madstop.edu
=pop.madstop.edu:madstop.edu
=hermes.madstop.edu:madstop.edu
smtproutes
- qmail-remote artificial smtp routes in form domain:relay
- we set this to :127.0.0.1:10024 to hand-off all deliveries to Amavisd-new

Qmail Startup Scripts

Our stock setup suffices for the most part with but few changes. First we need to "name" this qmail instance in the qmailctl script and run scripts so the various bits can find their homes and log locations. At the top of each script, change the following:

QMAIL=qmail-fixup
TCPRULES=tcp.smtp-fixup

Next, because we are actually running ofmipd instead of qmail-smtpd in this qmail instance, and we need to run it on an alternate port, we need to adjust /var/qmail-fixup/supervise/qmail-smtpd/run. We already adjusted the script variables, so we concentrate on the relevant lines:

exec /usr/local/bin/softlimit -m 5000000 \
	/usr/local/bin/tcpserver -v -R -l "$LOCAL" -x /etc/$TCPRULES.cdb -c "$MAXSMTPD" \
		-u "$QMAILDUID" -g "$NOFILESGID" 127.0.0.1 10023 /usr/local/bin/ofmipd 2>&1

Last, we link this qmail's qmailctl script into place as qmailctlfixup:

ln -s /var/qmail-fixup/bin/qmailctl /usr/local/bin/qmailctlfixup

Tcpserver Configuration

There is not much to do here except make sure it exists.

/etc/tcp.smtp-fixup

127.:allow,RELAYCLIENT=""

Qmail-Inside

10.137.0.1
qmail-outside
port 25,465
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
qmail-inside
port 10025

The third qmail instance on TCP port 10025 is also a netqmail installation, named qmail-inside. The purpose of this qmail instance is to act as the remote delivery agent to our Mailstores and the Internet after processing by Amavis.

Begin with netqmail installation noting the differences below before moving on to qmail configuration and startup. The following sections build from there, highlighting changes and enhancements to that base.

Qmail Pre-compilation

Because our primary qmail (outside) instance is installed in /var/qmail, we need a new home for qmail-inside. Before compiling, we need to edit the conf-qmail file in the qmail source directory and change it thus:

/var/qmail-inside

All other differences are handled in the run scripts.

Qmail Configuration Files: /var/qmail-inside/control

Standard qmail control considerations apply as always. Some salient configuration details follow. Because this is the inside MTA, we are mainly concerned with sending implications:

smtproutes
- qmail-remote artificial smtp routes in form domain:relay
- this is our most complicated routing but still simple
somehost.madstop.edu:somehost.madstop.edu
otherhost.madstop.edu:otherhost.madstop.edu
lists.madstop.edu:lists.madstop.edu
madstop.edu:hermes.madstop.edu
	

The above picks out some specific hosts which receive mail on their own behalf. All other mail to the madstop.edu domain gets forwarded to the mailstore. Remaining mail is delivered by standard Internet mail exchanger lookup.

Qmail Startup Scripts

Our stock setup suffices for the most part with but few changes. First we need to "name" this qmail instance in the qmailctl script and run scripts so the various bits can find their homes and log locations. At the top of each script, change the following:

QMAIL=qmail-inside
TCPRULES=tcp.smtp-inside

Next, because this is our third qmail instance and we intend to run on port 10025, we need to adjust /var/qmail-forward/supervise/qmail-smtpd/run. We already adjusted the script variables, so we concentrate on the relevant lines:

exec /usr/local/bin/softlimit -m 5000000 \
	/usr/local/bin/tcpserver -v -R -l "$LOCAL" -x /etc/$TCPRULES.cdb -c "$MAXSMTPD" \
		-u "$QMAILDUID" -g "$NOFILESGID" 127.0.0.1 10025 /var/$QMAIL/bin/qmail-smtpd 2>&1

Last, we link this qmail's qmailctl script into place as qmailctlinside:

ln -s /var/qmail-inside/bin/qmailctl /usr/local/bin/qmailctlinside

Tcpserver Configuration

There is not much to do here except make sure it exists.

/etc/tcp.smtp-inside

127.:allow,RELAYCLIENT=""
#10.137.:allow,RELAYCLIENT=""

HINT: We could add lines for specific systems or ranges to allow for relaying. Certain machines can be setup to send mail via this box on port 10025 and avoid the entire chain of checks, fixes, and content-scanning. Care must be taken, but when setup properly in tandem with appropriate iptables restrictions, this is a great way to have automated systems send bulk messages without bogging down the content-scanner.


DNS and BIND

Email can and will generate a lot of DNS queries. We will install BIND as a local caching nameserver to help take pressure off the DNS infrastructure.

NOTE: We will build off of this idea later to handle RBLs on the Internet mail exchangers.

Installation:

yum install bind

NOTE: Install bind-chroot if you are paranoid, and work out of /var/named/chroot.

Configuration for BIND is in /etc/named.conf. We will forward requests for .madstop.edu to our normal DNS resolvers for the domain, and all other requests will be handled locally. There are other ways to accomplish this, and there is probably some other configuration in the file already, but the following stanzas do the trick:

options {
	listen-on port 53 { 127.0.0.1; };
	listen-on-v6 port 53 { ::1; };
	directory       "/var/named";
	dump-file       "/var/named/data/cache_dump.db";
	statistics-file "/var/named/data/named_stats.txt";
	memstatistics-file "/var/named/data/named_mem_stats.txt";
	allow-query     { localhost; };
	recursion yes;
	dnssec-enable no;
	dnssec-validation no;
	// dnssec-lookaside . trust-anchor dlv.isc.org.;
};

view "localhost_resolver"
{
/*
* This view sets up named to be a localhost resolver with forwarding to 
* remote nameserver for .madstop.edu and to local rbldnsd instance for .dnsbl
*/
	match-clients		{ localhost; };
	match-destinations	{ localhost; };
	recursion yes;

	/* 
	* These are zones that contain definitions for all the localhost
	* names and addresses, as recommended in RFC1912 - these names should
	* ONLY be served to localhost clients:
	*/
	include "/etc/named.rfc1912.zones";

	zone "." IN {
		type hint;
		file "named.ca";
	};

	zone "madstop.edu" IN {
		type forward;
		forward only;
		forwarders {
			10.137.110.101 port 53;
		};
	};
};

We update /etc/resolv.conf so that the resolver queries only the localhost:

search madstop.edu
nameserver 127.0.0.1

Content-Scanning: Amavisd-new

10.137.0.1
qmail-outside
port 25,465
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
amavisd
port 10024
127.0.0.1
qmail-inside
port 10025

The most important and complex piece of the chain: content scanning. Amavisd-new is a content-scanner written in Perl that takes messages over SMTP (tcp port 10024 by default), unpacks them, scans them for viruses, bad headers, attachment restrictions, spam scores, and determines the ultimate fate of messages based on configuration surrounding all of the above. It relies on a separate antivirus scanner (or multiple ones) and anti-spam in the guise of SpamAssassin.

Amavisd-new originally offered mainly central configuration of message destiny based on virus, bad header, or spam status. Over time it has taken on a number of features like database quarantining and the like. In our case, since this is just outgoing mail, it will be used only for basic scanning and no spam handling.

NOTE: Given how outgoing spam and such from phished users and compromised accounts are possibly the biggest issue facing email admins these days, this would be a logical place to do more.

Installation

Amavisd-new requires a number of Perl libraries and external programs for it to function fully. The install docs are comprehensive, and we will concentrate only on the few things that deviate or that we consider best practice for our setup.

The easiest way to pull in all the depencies is to make use of the package management system for your distro:

yum install amavisd-new

This will create the amavis user, with home in /var/spool/amavisd. Since a lot of documentation and some third-party scripts refer to /var/amavis or /var/amavisd, we will create symlinks for good measure:

ln -s spool/amavisd /var/amavis
ln -s spool/amavisd /var/amavisd

We plan to configure file-based logging, so we may as well create the log directory while we are at it:

mkdir -p /var/log/amavis
chown amavis:amavis /var/log/amavis

Configuration

All amavisd-new configuration is stored in /etc/amavisd/amavisd.conf. There are myriad features available, but the following will concern tweaks and relevant deviations from the defaults (amavisd-new-2.6.4):

$max_servers = 20;
- number of pre-forked children bumped up, 2-30 common
$max_requests = 25;
- retire child after this many requests, default 10
$mydomain = 'madstop.edu';
- convenient default for other settings
$MYHOME = '/var/spool/amavisd';
- convenient default for other settings
$QUARANTINEDIR = "$MYHOME/quarantine";
- defaults to undef for some reason
$DO_SYSLOG = 0;
- we prefer a logfile
$LOGFILE = "/var/log/amavis/amavis.log";
- path to the dedicated logfile, matching it to the rpm-provided amavisd location
$enable_db = 0;
- we do not care about snmp or the nanny
$X_HEADER_TAG = 'X-Virus-Scanned';
- set the x-header to be added to all messages
$X_HEADER_LINE = "Madstop (amavisd-new)";
- set the x-header line to be added to all messages

We then set all the destinies to be discard:

$final_virus_destiny      = D_DISCARD;
$final_banned_destiny     = D_DISCARD;
$final_spam_destiny       = D_DISCARD;
$final_bad_header_destiny = D_DISCARD;

And then make everyone spam and bad header lovers, so these things will not even be checked:

@bypass_spam_checks_maps = (1);
@spam_lovers_maps = (1);
@bypass_banned_checks_maps = (1);
@banned_files_lovers_maps = (1);
@bypass_header_checks_maps = (1);
@bad_header_lovers_maps = (1);

Amavisd-new can use an array (literally) of antivirus scanners, and several are defined. Amavisd-new will use whichever ones it finds, but we should ensure our ClamAV favorite is listed:

['ClamAV-clamd',
	\&ask_daemon, ["CONTSCAN {}\n", "/var/spool/amavisd/clamd.sock"],
	qr/\bOK$/m, qr/\bFOUND$/m,
	qr/^.*?: (?!Infected Archive)(.*) FOUND$/m ],

The antivirus backup scanners array contains non-daemonized versions of the same typically. The non-daemonized clamscan will have trouble keeping up on a busy box should clamd fail:

['ClamAV-clamscan', 'clamscan',
	"--stdout --no-summary -r --tempdir=$TEMPBASE {}",
	[0], qr/:.*\sFOUND$/m, qr/^.*?: (?!Infected Archive)(.*) FOUND$/m ],

Antivirus: ClamAV

10.137.0.1
qmail-outside
port 25,465
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
amavisd
port 10024
clamav
127.0.0.1
qmail-inside
port 10025

Antivirus is a core aspect of email content-scanning, and we configured Maia to use ClamAV.

Installation

Our initial amavisd-new yum install should have taken care of this, but if not:

yum install clamav clamav-db clamd

This installation stores the database files in /var/lib/clamav, and is configured in /etc/clamd.d

Configuration: /etc/clamd.d/amavisd.conf

The defaults are mostly fine, but we also turn on file-based logging into our amavisd log directory:

LogFile /var/log/amavis/clamd.amavis
LogFileMaxSize 0
LogTime yes

NOTE: Because amavisd initiates scanning as the amavis user, hence an amavisd.conf file in clamd.d

Updates Configuration: /etc/freshclam.conf

An anti-virus solution is nothing without updates. Typically this will run as the clam user and update the database in /var/lib/clamav. Make sure to comment out the Example line. Salt to taste:

#Example

Updates are downloaded using the freshclam binary. Our yum install may have dropped a file in /etc/cron.daily to handle this. Either use that, or schedule in cron with crontab -e as root. The following updates at 17 minutes after every hour:

17 * * * * /usr/bin/freshclam

NOTE: Remember to comment out everything in crond.daily if you use this. If you simply remove the file, another update might put it back.

Third-party Updates

Because of the flexibility and open nature of clamav, third-party virus definitions are available. We will pull down Sanesecurity clamav definitions. Sanesecurity provides several download scripts, and we will use the second one by Gerard.

NOTE: The first one works great but no longer seems to be available.

Download the script, untar it, install it:

tar -zxvf scamp-5.3b.tar.gz
cd scamp-5.3b
cp scamp.sh /usr/local/bin
cp scamp.1 /usr/local/share/man/man1

Now create the config file by running scamp.sh. It will create an /etc/scamp/default file like the below. We follow its suggestions most of the way but need to match up directory locations and the clam user.

SCAMP_VERSION=5.3b
CLAMAV_DB=/var/lib/clamav
T_DIR=/var/lib/clamav/tmp
C_GROUP=clam
C_PID=/var/run/amavisd/clamd.pid
C_USER=clam
GET_LDB=1
GET_MALWARE=1
GET_MSRBL=0
GET_SANE=1
GET_SECURITE=4
GET_WINNOW=1
gpg_key_url=http://www.sanesecurity.net/publickey.gpg
L_TYPE=0
MK_LOG=1
MSRBL=rsync://rsync.mirror.msrbl.com/msrbl/
msrbl_Images=MSRBL-Images.hdb
msrbl_SPAM=MSRBL-SPAM.ndb
msrbl_SPAM_CR=MSRBL-SPAM-CR.ndb
MSR_DIR=/var/lib/clamav/tmp/msr
MW_DIR=/var/lib/clamav/tmp/malware
MW_FILE=mbl.ndb
MW_URL=http://www.malwarepatrol.com.br/cgi/submit?action=list_clamav_ext
RELOAD=0
REST=0
SANE=rsync://rsync.sanesecurity.net/sanesecurity
SANE_DB=/var/lib/clamav/tmp/sane
SI_DIR=/var/lib/clamav/tmp/securite
SYS_LOG=1
WPC=3
W_SUM=0

We now schedule this in cron with crontab -e as root. The following updates at 19 minutes after every hour:

19 * * * * /usr/local/bin/scamp.sh

Update Schedules

We noted a typical cron entry for each of our update sources: the official clam sources and our third-party locations. Throughout, we disabled in-script randomization and auto-updates because we want to manually reload clamd once we pull down updates from all sources on our schedule. For example:

17 * * * * /usr/bin/freshclam
19 * * * * /usr/local/bin/scamp.sh
25 * * * * /etc/init.d/clamd.amavisd reload

Scalability

10.137.0.1
qmail-outside
port 25,465
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
amavisd
port 10024
clamav
127.0.0.1
qmail-inside
port 10025

Scaling out the local mail exchangers is straightforward. We simply clone the setup and use some kind of high-availability setup on smtp.madstop.edu as needs require.

If we have a split-zone DNS setup, and we care about MX redundancy for the local zone, it is easy to take advantage of the inherent ability to define multiple MX records in DNS. For example:

@				IN		MX		0 smtp1.madstop.edu.
@				IN		MX		0 smtp2.madstop.edu.

NOTE: @ is a BIND shortcut for the domain in question, here madstop.edu.

NOTE: It is critical that our local mail exchanger records do not propagate to public DNS, and are available only to our local domain. This requires separate internal/external DNS, either provided by separate machines or split-zone DNS. This is handy to ensure that machines on the LAN doing deliveries by MX lookup use the local exchanger, keeping mail out of the Internet exchanger database. If split-zone DNS is not available, or we do not care, it is not the end of the world to simply skip setting up MX records for the local exchanger in DNS.



Internet Mail Exchanger

We now focus on the dedicated Internet mail exchangers, providing an array of features geared towards handling the never-ending onslaught of Internet email:

  1. Real-time blacklists
  2. Valid recipient checks
  3. Antivirus scanning
  4. Anti-spam examination
  5. Message quarantine

The Internet exchangers run qmail in a multi-MTA setup, with Maia Mailguard doing the heavy lifting between them to disassemble messages for content scanning and virus and spam checks using ClamAV and SpamAssassin. The three qmails on the box will share the same ucspi-tcp and daemontools installs, each forwarding to the next in the chain, including Amavisd-maia, using smtproutes. If the message does not pass muster according to each recipient's settings, it is quarantined into a MySQL database for possible user review.

10.137.0.1
qmail-outside
port 25
rbldnsd
greetdelay
validrcptto
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
maia
port 10024
clamav
spamassassin
127.0.0.1
qmail-inside
port 10025

These machines will be known as mx1.madstop.edu, mx2.madstop.edu, etc. These will be advertised as the MX records for the domain. Ideas for scaling this out are at the end of this section.

NOTE: The basic structure of the Internet MX is virtually identical to the local MX. Because there are subtle differences in numerous components, we document it separately in (near) entirety. That said, it is worth it to review the introduction to the local MX which provides an overview and rationale for the separation. At the very least, we provide complete documentation for exchangers based on both Amavisd-new and Maia Mailguard and either/both may be suitable for your network in either capacity.


Qmail

10.137.0.1
qmail-outside
port 25
 
greetdelay
validrcptto

The first qmail instance on TCP port 25 is a qmail JMS Combined installation. JMS Combined provides a lot of enhancements, and we are interested in two main ones: validrcptto recipient checking, and greetdelay SMTP delayed response. We tend to refer to this as qmail-outside to differentiate it from the other qmails in the chain. It is installed in the standard place: /var/qmail.

Begin with qmail JMS Combined installation noting the differences below before moving on to qmail configuration and startup. The following sections build from there, highlighting changes and enhancements to that base.

Qmail Configuration Files: /var/qmail/control

Because this is an Internet-facing MTA, much of this is related to SMTP defense, taking advantage of features offered by the JMS Combined setup. Salient details follow:

badhelo
- qmail-smtp considers these unacceptable HELO/EHLO host names and regexes and will reject every recpient address, ex:
!\.
mx1\.madstop\.edu
mx2\.madstop\.edu
^[0-9.]+$
	
badmailfrom
- qmail-smtp considers these unacceptable envelope sender addresses and will reject every recpient address
- easy way to block some incoming spam
concurrencyincoming
- we bump this up to 550
concurrenyremote
- qmail-send maximum number of simultaneous remote delivery attempts, default 10
- all deliveries for this qmail are remote to the next in the chain
- we set to something like 200
databytes
- qmail-smtp maximum number of bytes allowed in message, default 0 unlimited
defaulthost
- qmail-inject adds this name to any address without a host name
- only mail from the exchanger itself would be injected
doublebounceto
- qmail-send user to receive double-bounces
- with trim patch if file begins with a newline doublebounces are discarded
envnoathost
- qmail-send appends this domain name for recipient addresses without @ signs
locals
- we set this to localhost to ensure no deliveries are considered local
rcpthosts
- list all domains for which this exchanger will receive mail
smtproutes
- qmail-remote artificial smtp routes in form domain:relay
- we set this to :127.0.0.1:10023 to hand-off all deliveries to the next qmail in the chain
validrcptto.cdb
- qmail-smtp rejects any recipient not listed in this cdb file
- file built on periodic basis by mkvalidrcptto script

Qmail Startup Scripts

Our stock setup suffices for the most part with but few changes. Ensure that the "name" for this qmail instance retains the default setting at the top of the startup and run scripts:

QMAIL=qmail
TCPRULES=tcp.smtp

Next, because we want to add RBL checks to this Internet-facing qmail instance, we need to adjust /var/qmail/supervise/qmail-smtpd/run. We already adjusted the script variables, so we concentrate on the relevant rbldnsd lines:

exec /usr/local/bin/softlimit -m 40000000 \
	/usr/local/bin/tcpserver -v -R -l "$LOCAL" -x /etc/$TCPRULES.cdb -c "$MAXSMTPD" \
		-u "$QMAILDUID" -g "$NOFILESGID" 0 smtp \
	/usr/local/bin/rblsmtpd \
		-r sbl-xbl.dnsbl \
	/var/$QMAIL/bin/qmail-smtpd 2>&1

Tcpserver Configuration

There are a lot of features available in the JMS Combined patch, and we need to ensure that environment variables expressing those we want enabled/disabled for each sending IP range are set.

/etc/tcp.smtp

127.:allow,RELAYCLIENT="",RBLSMTPD="",DENY_TLS="1"
10.137.:allow,RELAYCLIENT="",RBLSMTPD="",DENY_TLS="1"
# Baddies
10.100.1.1:deny
10.101.1.1:deny
# Greetdelay exceptions
10.200.1.1:allow,DENY_TLS="1"
10.201.1.1:allow,DENY_TLS="1"
# Default
:allow,VALIDRCPTTO_CDB="/var/qmail/control/validrcptto.cdb",
	VALIDRCPTTO_LIMIT="10",GREETDELAY="30",DROP_PRE_GREET="0",DENY_TLS="1",
	LOGREGEX="1"

We start by allowing localhost and our local network range the ability to relay (send to any domain), though that is unlikely outside of testing scenarios: localhost will probably inject, and the local net range will use another local smtp server dedicated to local sending. We then may have some network ranges we declare off-limits for incoming mail with deny statements. Following that, in practice some mail exchangers do not adhere to RFCs and do not like waiting for our HELO after our artificial greetdelay wait, so we provide specific records exclusion the delay.

We end with our default line (should all be one line) for all other hosts, which provides the path to our validrcptto file, our validrcptto limit which defines how many invalid recipients before an entire message is dropped, our artifical greetdelay in seconds to throw spammers off an apparently slow SMTP server, drop pre greet turned off because of too many false positives for servers that send data before waiting for HELO, TLS off because this is our Internet-facing mail exchanger and we do not care if other sites send to us over TLS/SSL (some would try solely if it was available), and finally we log regexes tripped by the badhelo check. Not too shabby. On to the next qmail.


Qmail-Fixup

10.137.0.1
qmail-outside
port 25
 
greetdelay
validrcptto
127.0.0.1
qmail-fixup
port 10023

The second qmail instance on TCP port 10023 is a netqmail installation named qmail-fixup. The entire purpose of this qmail instance is to facilitate address rewriting for consistency, so user@mail.madstop.edu becomes user@madstop.edu. For this, we additionally require the mess822 package, from which we run the ofmipd daemon instead of qmail-smtpd to handle address rewrites.

Begin with netqmail installation noting the differences below before moving on to qmail configuration and startup.

We also require mess822 installation. The following sections build from there, highlighting changes and enhancements to that base.

Qmail Pre-compilation

Because our primary qmail (outside) instance is installed in /var/qmail, we need a new home for qmail-fixup. Before compiling, we need to edit the conf-qmail file in the qmail and mess822 source directories and change it thus:

/var/qmail-fixup

All other differences are handled in the run scripts.

Qmail Configuration Files: /var/qmail-fixup/control

Standard qmail control considerations apply as always. Some salient configuration details follow. Because this is the fixup MTA, we are mainly concerned with the rewrite file:

rewrite
- ofmipd rewrites headers according to this file
*.:
-localhost:
=:madstop.edu
=bugz.madstop.edu:madstop.edu
=mx1.madstop.edu:madstop.edu
=mx2.madstop.edu:madstop.edu
=mail.madstop.edu:madstop.edu
=pop.madstop.edu:madstop.edu
=hermes.madstop.edu:madstop.edu
	
smtproutes
- qmail-remote artificial smtp routes in form domain:relay
- we set this to :127.0.0.1:10024 to hand-off all deliveries to Amavisd-new/Maia Mailguard

Qmail Startup Scripts

Our stock setup suffices for the most part with but few changes. First we need to "name" this qmail instance in the qmailctl script and run scripts so the various bits can find their homes and log locations. At the top of each script, change the following:

QMAIL=qmail-fixup
TCPRULES=tcp.smtp-fixup

Next, because we are actually running ofmipd instead of qmail-smtpd in this qmail instance, and we need to run it on an alternate port, we need to adjust /var/qmail-fixup/supervise/qmail-smtpd/run. We already adjusted the script variables, so we concentrate on the relevant lines:

exec /usr/local/bin/softlimit -m 5000000 \
	/usr/local/bin/tcpserver -v -R -l "$LOCAL" -x /etc/$TCPRULES.cdb -c "$MAXSMTPD" \
		-u "$QMAILDUID" -g "$NOFILESGID" 127.0.0.1 10023 /usr/local/bin/ofmipd 2>&1

Last, we link this qmail's qmailctl script into place as qmailctlfixup:

ln -s /var/qmail-fixup/bin/qmailctl /usr/local/bin/qmailctlfixup

Tcpserver Configuration

There is not much to do here except make sure it exists.

/etc/tcp.smtp-fixup

127.:allow,RELAYCLIENT=""

Qmail-Inside

10.137.0.1
qmail-outside
port 25
 
greetdelay
validrcptto
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
qmail-inside
port 10025

The third qmail instance on TCP port 10025 is also a netqmail installation, named qmail-inside. The purpose of this qmail instance is to act as the remote delivery agent to our Mailstores.

Begin with netqmail installation noting the differences below before moving on to qmail configuration and startup. The following sections build from there, highlighting changes and enhancements to that base.

Qmail Pre-compilation

Because our primary qmail (outside) instance is installed in /var/qmail, we need a new home for qmail-inside. Before compiling, we need to edit the conf-qmail file in the qmail source directory and change it thus:

/var/qmail-inside

All other differences are handled in the run scripts.

Qmail Configuration Files: /var/qmail-inside/control

Standard qmail control considerations apply as always. Some salient configuration details follow. Because this is the inside MTA, we are mainly concerned with sending implications:

smtproutes
- qmail-remote artificial smtp routes in form domain:relay
- this is our most complicated routing but still simple
somehost.madstop.edu:somehost.madstop.edu
otherhose.madstop.edu:otherhost.madstop.edu
lists.madstop.edu:lists.madstop.edu
madstop.edu:mailstore.madstop.edu
	

The above picks out some specific hosts which receive mail on their own behalf. All other mail to the madstop.edu domain gets forwarded to the mailstore. Remaining mail is delivered by standard Internet mail exchanger lookup.

Qmail Startup Scripts

Our stock setup suffices for the most part with but few changes. First we need to "name" this qmail instance in the qmailctl script and run scripts so the various bits can find their homes and log locations. At the top of each script, change the following:

QMAIL=qmail-inside
TCPRULES=tcp.smtp-inside

Next, because this is our third qmail instance and we intend to run on port 10025, we need to adjust /var/qmail-forward/supervise/qmail-smtpd/run. We already adjusted the script variables, so we concentrate on the relevant lines:

exec /usr/local/bin/softlimit -m 5000000 \
	/usr/local/bin/tcpserver -v -R -l "$LOCAL" -x /etc/$TCPRULES.cdb -c "$MAXSMTPD" \
		-u "$QMAILDUID" -g "$NOFILESGID" 127.0.0.1 10025 /var/$QMAIL/bin/qmail-smtpd 2>&1

Last, we link this qmail's qmailctl script into place as qmailctlinside:

ln -s /var/qmail-inside/bin/qmailctl /usr/local/bin/qmailctlinside

Tcpserver Configuration

There is not much to do here except make sure it exists.

/etc/tcp.smtp-inside

127.:allow,RELAYCLIENT=""

Realtime Block Lists: RBLDNSD and BIND

10.137.0.1
qmail-outside
port 25
rbldnsd
greetdelay
validrcptto
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
qmail-inside
port 10025

Now that the qmails are in place, we will fill in the first missing piece: RBLs. The qmail(-outside) instance is configured to query rbldnsd in its qmail-smtpd run script so we will set that up. We subscribe to Spamhaus feeds, but this will easily apply to others as well.

While that will cover direct RBL checks in the MTA, we also plan to have spamassassin query these new dedicated RBL zones. This requires making the local machine a full-fledged caching/forwarding nameserver using BIND. In a nutshell:

  1. Two nameservers: bind and rbldnsd
  2. Rbldnsd listening on port 530 serving up the blocklist zones
  3. Bind configured to be a caching nameserver on port 53, but forwarding requests to fake domain suffix .dnsbl to localhost port 530
  4. Local resolver set to query only the local host caching nameserver (/etc/resolv.conf)
  5. Outside qmail set to call rbldnsd in the binary chain (done above)
  6. Spamassassin Spamhaus RBL checks modified to query sbl.dnsbl (done below)

Installation:

yum install rbldnsd bind

NOTE: Install bind-chroot if you are paranoid, and work out of /var/named/chroot.

Configuration for rbldnsd is in /etc/sysconfig/rbldnsd. We will create an imaginary ".dnsbl" top-level domain. Add the following to the bottom of the file:

RBLDNSD="- -r/var/lib/rbldnsd -q -b127.0.0.1/530 -f \
	sbl.dnsbl:ip4set:spamhaus/sbl \
	xbl.dnsbl:ip4set:spamhaus/xbl \
	sbl-xbl.dnsbl:ip4set:spamhaus/sbl \
	sbl-xbl.dnsbl:ip4set:spamhaus/xbl \
"

We need to create the subdirectory for our zone files and put them there:

mkdir -p /var/lib/rbldnsd/spamhaus
cp /path/to/sbl /var/lib/rbldnsd/spamhaus/sbl
cp /path/to/xbl /var/lib/rbldnsd/spamhaus/xbl

NOTE: This scheme scales pretty well for various RBL subscriptions.

Test the setup with the following:

dig A 2.0.0.127.sbl.dnsbl @localhost -p 530
dig A 2.0.0.127.xbl.dnsbl @localhost -p 530
dig A 2.0.0.127.sbl-xbl.dnsbl @localhost -p 530

These tests should all return A records like the following:

2.0.0.127.sbl.dnsbl.	300	IN	A	127.0.0.2

It is now time to move on to the local caching nameserver using BIND.

Configuration for BIND is in /etc/named.conf. We will forward requests for .dnsbl hosts to the local rbldnsd instance on udp port 530, requests for .madstop.edu to our normal DNS resolvers for the domain, and all other requests will be handled locally. There are other ways to accomplish this, and there is probably some other configuration in the file already, but the following stanzas do the trick:

options {
	listen-on port 53 { 127.0.0.1; };
	listen-on-v6 port 53 { ::1; };
	directory       "/var/named";
	dump-file       "/var/named/data/cache_dump.db";
	statistics-file "/var/named/data/named_stats.txt";
	memstatistics-file "/var/named/data/named_mem_stats.txt";
	allow-query     { localhost; };
	recursion yes;
	dnssec-enable no;
	dnssec-validation no;
	// dnssec-lookaside . trust-anchor dlv.isc.org.;
};

view "localhost_resolver"
{
/*
* This view sets up named to be a localhost resolver with forwarding to 
* remote nameserver for .madstop.edu and to local rbldnsd instance for .dnsbl
*/
	match-clients		{ localhost; };
	match-destinations	{ localhost; };
	recursion yes;

	/* 
	* These are zones that contain definitions for all the localhost
	* names and addresses, as recommended in RFC1912 - these names should
	* ONLY be served to localhost clients:
	*/
	include "/etc/named.rfc1912.zones";

	zone "." IN {
		type hint;
		file "named.ca";
	};

	zone "madstop.edu" IN {
		type forward;
		forward only;
		forwarders {
			10.137.110.101 port 53;
		};
	};

	zone "dnsbl" IN {
		type forward;
		forward only;
		forwarders {
			127.0.0.1 port 530;
		};
	};
};

We update /etc/resolv.conf so that the resolver queries only the localhost:

search madstop.edu
nameserver 127.0.0.1

Now the dnsbl domain is seamlessly available to the resolver:

dig A 2.0.0.127.sbl.dnsbl

2.0.0.127.sbl.dnsbl.	300	IN	A	127.0.0.2

NOTE: For setups that do not use SpamAssassin, or where SpamAssassin will not be configured to query RBLs, we can skip all the BIND and resolver setup since qmail queries rbldnsd directly.


Content-Scanning: Maia Mailguard

10.137.0.1
qmail-outside
port 25
rbldnsd
greetdelay
validrcptto
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
maia
port 10024
127.0.0.1
qmail-inside
port 10025

The most important and complex piece of the chain: content scanning. Maia Mailguard is a fork of Amavisd-new, a content-scanner written in Perl that takes messages over SMTP (TCP port 10024 by default), unpacks them, scans them for viruses, bad headers, attachment restrictions, scores them for spam, and ultimately determines their fate based on configuration surrounding all of the above. It relies on a separate antivirus scanner (or multiple ones) and anti-spam in the guise of SpamAssassin.

The main benefit Maia Mailguard provides over stock Amavisd-new is that it interacts with a database to determine message destiny based on user preferences associated with all of these threshholds. That same database provides a quarantine for messages that fail these tests, with a web frontend for users both to interact with the application, and for quarantine review and ham/spam SpamAssassin Bayesian learning.

Amavisd-new itself has taken on many of the features Maia Mailguard provides, and it may be more in line with your needs. See the Maia Mailguard FAQ and the specific article on their differences.

Installation

Maia Mailguard requires all the pieces that an Amavisd-new install would, (same thing plus database), and that is a lot of dependencies. The install documentation is comprehensive, and we will concentrate only on the few things that deviate or that we consider best practice for our setup.

The easiest way to pull in all the depencies is to make use of the package management system for your distro:

yum install amavisd-new

This will create the amavis user that Maia needs, homed in /var/spool/amavisd. We will build around that space, but since much documentation and many third-party scripts (inluding Maia) tend to assume /var/amavis or /var/amavisd, we create symlinks for good measure:

ln -s spool/amavisd /var/amavis
ln -s spool/amavisd /var/amavisd

With zillions of dependencies installed and the OS framework in place for launching the daemon, we will now move it out of the way to make room for Maia Mailguard. Find the stock daemon with 'which amavisd' and back it up:

mv /usr/sbin/amavisd /usr/sbin/amavisd-new
mv /etc/amavisd/amavisd.conf /etc/amavisd/amavisd-new.conf

NOTE: Pay heed to the fact that future updates might update this package and affect our work. It is a good idea to exclude amavisd-new from your package management system.

Now download the latest package at http://www.maiamailguard.com/download.php

tar -zxvf /path/to/maia-1.0.2c.tar.gz
cd maia-1.0.2c
cp amavisd-maia /usr/sbin
ln -s amavisd-maia /usr/sbin/amavisd
cp amavisd.conf.dist /etc/amavisd/amavisd-maia.conf
ln -s amavisd-maia.conf /etc/amavisd/amavisd.conf

NOTE: These symlinks are just here to preserve the original amavisd-new alongside amavisd-maia for comparison.

We plan to configure file-based logging, so we may as well create the log directory while we are at it:

mkdir -p /var/log/amavis
chown amavis:amavis /var/log/amavis

Configuration

All amavisd-maia configuration is stored in /etc/amavisd/amavisd.conf (which should now be a symlink to amavisd-maia.conf as above). There are myriad features available, but the following will concern tweaks and relevant deviations from the defaults (maia-1.0.2c):

$max_servers = 10;
- number of pre-forked children bumped up, 2-15 common
$max_requests = 1;
- retire child after this many requests, default 10
- normally better to let a child handle numerous requests, but a bug prevents file logging after one request
$child_timeout=5*60;
- abort child if it does not finish in n secs, default 8*60
$mydomain = 'madstop.edu';
- convenient default for other settings
$MYHOME = '/var/spool/amavisd';
- convenient default for other settings, matching it to the rpm-provided amavisd home
$QUARANTINEDIR = "$MYHOME/quarantine";
- we quarantine to the database but set this anyway, matching it to the rpm-provided amavisd location
$LOGFILE = "/var/log/amavis/amavis.log";
- path to the dedicated logfile, matching it to the rpm-provided amavisd location
$enable_db = 0;
- we do not care about snmp or the nanny
$enable_global_cache = 0;
- we do not care about snmp or the nanny
$sa_local_tests_only = 1;
- only test which do not require internet access, probably for performance?
@lookup_sql_dsn = ( ['DBI:mysql:maia:localhost', 'amavis', 'password'] );
- set this appropriate for mysql access to the database we will create next
$myhostname = 'mx1.madstop.edu';
- set this appropriately
$X_HEADER_LINE = "Madstop (Maia Mailguard 1.0.2a)";
- add a handy header to scanned messages
$banned_filename_re = ...
- check and adjust for local needs
@av_scanners = ...
- check and adjust for local needs, default is clamd

The @av_scanners array defines a list of antivirus scanners to run on messages. It defaults to clamd:

['ClamAV-clamd',
	\&ask_daemon, ["CONTSCAN {}\n", "/var/amavisd/clamd.sock"],
	qr/\bOK$/, qr/\bFOUND$/,
	qr/^.*?: (?!Infected Archive)(.*) FOUND$/ ],	

The @av_scanners_backup array defines a list of fallback antivirus scanners, typically non-daemonized versions of the same. It defaults to clamscan, but that will have trouble keeping up on a busy box should clamd fail:

['ClamAV-clamscan', 'clamscan',
	"--stdout --disable-summary -r --tempdir=$TEMPBASE {}", [0], [1],
	qr/^.*?: (?!Infected Archive)(.*) FOUND$/m ],

MySQL Database

10.137.0.1
qmail-outside
port 25
rbldnsd
greetdelay
validrcptto
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
maia
port 10024
127.0.0.1
qmail-inside
port 10025

Maia Mailguard requires a decent database server, and in our case this is a separate box with 4GB of RAM. Again, the install documentation has plenty of info. The basics are:

[root]# mysql -u root -p mysql
mysql> CREATE DATABASE maia;
[root]# mysql -u root -p maia < maia-mysql.sql

Grant the appropriate privileges:

[root]# mysql -u root -p maia
mysql> GRANT CREATE, DROP, ALTER, SELECT, INSERT, UPDATE, DELETE 
	ON maia.* TO amavis@localhost IDENTIFIED BY 'passwd';

Mysql installations should ensure these tables are specified to use the InnoDB engine (the current maia-mysql.sql file creates them this way). This database will face constant read-writes from an unyielding mail stream. This mysql configuration has been tuned for our setup, in line with recommendations from the Maia install docs. Your mileage may vary.

/etc/my.cnf

[mysqld]
datadir=/mnt/dbfiles/mysqldata
socket=/var/lib/mysql/mysql.sock
user=mysql
# Default to using old password format for compatibility with mysql 3.x
# clients (those using the mysqlclient10 compatibility package).
old_passwords=1
max_allowed_packet=50M
skip-external-locking
#max_connections=200
read_buffer_size=1M
sort_buffer_size=1M
# Default InnoDB config
innodb_data_file_path=ibdata1:10M:autoextend
# Set buffer pool size to 50-80% of your computer's memory,
# but make sure on Linux x86 total memory usage is < 2GB
innodb_buffer_pool_size=2G
innodb_additional_mem_pool_size=20M
# Set the log file size to about 25% of the buffer pool size (changing these details is a pain)
innodb_log_file_size=250M
innodb_log_buffer_size=8M
innodb_flush_log_at_trx_commit=0
innodb_lock_wait_timeout=50
# Uncomment the next lines if you want to use them
#innodb_thread_concurrency=5

NOTE: You will want to check the Maia install docs for the latest info on all of the above.

Maintenance Scripts and Templates

Maia Mailguard relies on number of Perl maintenance scripts to keep things humming. These, along with templates, are available in the Maia installation archive. We copy them into place and tighten up permissions:

mkdir -p /var/spool/amavisd/maia
cp -a /path/to/maia-1.0.2c/scripts /var/spool/amavisd/maia
cp -a /path/to/maia-1.0.2c/templates /var/spool/amavisd/maia
chown -R amavis:amavis /var/spool/amavisd/maia
chmod 640 /var/spool/amavisd/maia/templates/*.tpl
chmod 750 /var/spool/amavisd/maia/scripts/*.pl

The scripts all expect to read configuration bits from /etc/maia.conf, a sample provided in the Maia install archive:

cp /path/to/maia-1.0.2c/maia.conf /etc/maia.conf

We need to ensure the database connection information is correct, auth set to ldap, and that we re-path around our amavisd home:

$dsn = "DBI:mysql:maia:mailguarddb.madstop.edu:3306";
$password = "password";
$script_dir = "/var/spool/amavisd/maia/scripts";
$auth_method = "ldap";
$pid_file = "/var/spool/amavisd/.process-quarantine.pid";
#$key_file = "/var/spool/amavisd/maia.key";
$report_options = 0;
$base_url = "http://mailguard.madstop.edu/";
$template_dir = "/var/spool/amavisd/maia/templates";
NOTE: We comment out the key file. This is an interesting option if we desire encryption on all messages stored in the database.

The scripts will run out of cron at various periods. This example root cron schedule will capture output to syslog:

0 0 * * * su - amavis -c "/var/spool/amavisd/maia/scripts/send-quarantine-reminders.pl | logger -t maia"
0 0 * * * su - amavis -c "/var/spool/amavisd/maia/scripts/send-quarantine-digests.pl | logger -t maia"
5 * * * * su - amavis -c "/var/spool/amavisd/maia/scripts/stats-snapshot.pl | logger -t maia"
10 */3 * * * su - amavis -c "/var/spool/amavisd/maia/scripts/process-quarantine.pl | logger -t maia"
10 0 * * * su - amavis -c "/var/spool/amavisd/maia/scripts/expire-quarantine-cache.pl | logger -t maia"
NOTE: There is a load-sa-rules script that also needs to run, and we will handle that during SpamAssassin setup.

Antivirus: ClamAV

10.137.0.1
qmail-outside
port 25
rbldnsd
greetdelay
validrcptto
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
maia
port 10024
clamav
127.0.0.1
qmail-inside
port 10025

Antivirus is a core aspect of email content-scanning, and we configured Maia to use ClamAV.

Installation

Our initial amavisd-new yum install should have taken care of this, but if not:

yum install clamav clamav-db clamd

This installation stores the database files in /var/lib/clamav, and is configured in /etc/clamd.d

Configuration: /etc/clamd.d/amavisd.conf

The defaults are mostly fine, but we also turn on file-based logging into our amavisd log directory:

LogFile /var/log/amavis/clamd.amavis
LogFileMaxSize 0
LogTime yes

NOTE: Because amavisd initiates scanning as the amavis user, hence an amavisd.conf file in clamd.d

Updates Configuration: /etc/freshclam.conf

An anti-virus solution is nothing without updates. Typically this will run as the clam user and update the database in /var/lib/clamav. Make sure to comment out the Example line. Salt to taste:

#Example

Updates are downloaded using the freshclam binary. Our yum install may have dropped a file in /etc/cron.daily to handle this. Either use that, or schedule in cron with crontab -e as root. The following updates at 17 minutes after every hour:

17 * * * * /usr/bin/freshclam

NOTE: Remember to comment out everything in crond.daily if you use this. If you simply remove the file, another update might put it back.

Third-party Updates

Because of the flexibility and open nature of clamav, third-party virus definitions are available. We will pull down Sanesecurity clamav definitions. Sanesecurity provides several download scripts, and we will use the second one by Gerard.

NOTE: The first one works great but no longer seems to be available.

Download the script, untar it, install it:

tar -zxvf scamp-5.3b.tar.gz
cd scamp-5.3b
cp scamp.sh /usr/local/bin
cp scamp.1 /usr/local/share/man/man1

Now create the config file by running scamp.sh. It will create an /etc/scamp/default file like the below. We follow its suggestions most of the way but need to match up directory locations and the clam user.

SCAMP_VERSION=5.3b
CLAMAV_DB=/var/lib/clamav
T_DIR=/var/lib/clamav/tmp
C_GROUP=clam
C_PID=/var/run/amavisd/clamd.pid
C_USER=clam
GET_LDB=1
GET_MALWARE=1
GET_MSRBL=0
GET_SANE=1
GET_SECURITE=4
GET_WINNOW=1
gpg_key_url=http://www.sanesecurity.net/publickey.gpg
L_TYPE=0
MK_LOG=1
MSRBL=rsync://rsync.mirror.msrbl.com/msrbl/
msrbl_Images=MSRBL-Images.hdb
msrbl_SPAM=MSRBL-SPAM.ndb
msrbl_SPAM_CR=MSRBL-SPAM-CR.ndb
MSR_DIR=/var/lib/clamav/tmp/msr
MW_DIR=/var/lib/clamav/tmp/malware
MW_FILE=mbl.ndb
MW_URL=http://www.malwarepatrol.com.br/cgi/submit?action=list_clamav_ext
RELOAD=0
REST=0
SANE=rsync://rsync.sanesecurity.net/sanesecurity
SANE_DB=/var/lib/clamav/tmp/sane
SI_DIR=/var/lib/clamav/tmp/securite
SYS_LOG=1
WPC=3
W_SUM=0

We now schedule this in cron with crontab -e as root. The following updates at 19 minutes after every hour:

19 * * * * /usr/local/bin/scamp.sh

Update Schedules

We noted a typical cron entry for each of our update sources: the official clam sources and our third-party locations. Throughout, we disabled in-script randomization and auto-updates because we want to manually reload clamd once we pull down updates from all sources on our schedule. For example:

17 * * * * /usr/bin/freshclam
19 * * * * /usr/local/bin/scamp.sh
25 * * * * /etc/init.d/clamd.amavisd reload

Anti-spam: SpamAssassin

10.137.0.1
qmail-outside
port 25
rbldnsd
greetdelay
validrcptto
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
maia
port 10024
clamav
spamassassin
127.0.0.1
qmail-inside
port 10025

Anti-spam is the other core aspect of email content-scanning, and we configured Maia to use SpamAssassin.

Installation

Our initial amavisd-new yum install should have taken care of this, but if not:

yum install spamassassin

This installation stores the rules files in /usr/share/spamassassin, and is configured in /etc/mail/spamassassin.

Configuration: /etc/mail/spamassassin/local.cf

The default configuration is sparse. In our case, settings in amavisd.conf override several things, and user preferences out of the Maia Mailguard database determine spam threshholds. However, we will tune some rules based on experience, and it is critical that we do the following:

  1. Override the rules that govern RBL checks so that they query our fake local .dnsbl domain instead of nailing the Internet. Aside from increased performance, this is more important for some blocklists, as their terms of service might shut you out if you keep hitting their public servers.
  2. Configure Bayes and the Autowhitelist to use a MySQL database to allow for easy scaling to multiple mail exchangers and to reduce filesystem contention. Bayes is one of the main strengths of the Maia Mailguard solution, as users confirming message status allows for site-wide learning of ham/spam tendencies. Autowhitelist (unfortunately named) is an automatic heuristic system for adjusting spam scores up/down based on previous sender behavior. Tables in the format SpamAssassin expects were installed in the maia database we already installed.

NOTE: While we could consider turning off Autowhitelist, we definitely want Bayes running. In any case, both features run off of standalone databases on the filesystem by default. This may be perfectly suitable for small sites and/or those with no plans for multiple mail exchangers. But since the tables already exist in the databse, we may as well use them and avoid any potential for filesystem performance issues.

This configuration handles the above, and provides other tweaks gleaned from experience:

# These values can be overridden by editing ~/.spamassassin/user_prefs.cf 
# (see spamassassin(1) for details)

# These should be safe assumptions and allow for simple visual sifting
# without risking lost emails.

required_hits 5
report_safe 0
#rewrite_header Subject ***SPAM***
ok_languages en
ok_locales en

# Bayes
use_bayes 1
bayes_store_module					Mail::SpamAssassin::BayesStore::MySQL
bayes_sql_dsn						DBI:mysql:maia:mailguarddb.madstop.edu:3306
bayes_sql_username					amavis
bayes_sql_password					password
bayes_sql_override_username			amavis
bayes_auto_expire 0
bayes_auto_learn 0
#bayes_auto_learn_threshold_nonspam -0.5
#bayes_auto_learn_threshold_spam 10.0
bayes_ignore_header ReSent-Date
bayes_ignore_header ReSent-From
bayes_ignore_header ReSent-Message-ID
bayes_ignore_header ReSent-Subject
bayes_ignore_header ReSent-To
bayes_ignore_header Resent-Date
bayes_ignore_header Resent-From
bayes_ignore_header Resent-Message-ID
bayes_ignore_header Resent-Subject
bayes_ignore_header Resent-To
bayes_ignore_header X-Received-From-IP
bayes_ignore_header X-Virus-Scanned
bayes_ignore_header X-Spam-Status
bayes_ignore_header X-Spam-Level
bayes_ignore_header X-Sender
bayes_ignore_header X-Mailer

# Auto-whitelist
use_auto_whitelist 1
auto_whitelist_factory				Mail::SpamAssassin::SQLBasedAddrList
user_awl_dsn						DBI:mysql:maia:mailguarddb.madstop.edu:3306
user_awl_sql_username				amavis
user_awl_sql_password				password

# Network test settings
dns_available test: madstop.edu
skip_rbl_checks 1
rbl_timeout 10
use_pyzor 0
use_razor2 0

# Custom scores
score URIBL_SBL 0
score URIBL_SC_SURBL 0
score URIBL_WS_SURBL 0
score URIBL_PH_SURBL 0
score URIBL_OB_SURBL 0
score URIBL_AB_SURBL 0
score URIBL_JP_SURBL 0
score URIBL_BLACK 0
score URIBL_GREY 0
score URIBL_RED 0

score BAYES_00 0
score BAYES_05 0
score BAYES_20 0
score BAYES_40 0.5
score BAYES_50 1
score BAYES_60 2
score BAYES_80 3
score BAYES_95 4
score BAYES_99 5

# Custom rule to enable local sbl
uridnsbl        URIBL_SBL       sbl.dnsbl.       TXT
body            URIBL_SBL       eval:check_uridnsbl('URIBL_SBL')
describe        URIBL_SBL       Contains an URL listed in the SBL blocklist
score           URIBL_SBL       10
tflags          URIBL_SBL       net

# Custom rule to enable local xbl (no xbl period for that matter)
# Note: Justin Mason on the SA mailing list said the reason XBL was not included
# was that "it doesn't correlate well with spam."  Since these are mostly infected
# machines rather than those that host spam domains, it is fairly useless at the moment,
# but someone else mentioned past experiments with bot networks of web servers and
# redirectors.  We shall see.
#uridnsbl        URIBL_XBL       xbl.dnsbl.       TXT
#body            URIBL_XBL       eval:check_uridnsbl('URIBL_XBL')
#describe        URIBL_XBL       Contains an URL listed in the XBL blocklist
#score           URIBL_XBL       6
#tflags          URIBL_XBL       net

There are some other configuration bits remaining. In /etc/mail/spamassassin/v310.pre, we need to enable a few modules:

loadplugin Mail::SpamAssassin::Plugin::AWL
loadplugin Mail::SpamAssassin::Plugin::TextCat

Third-Party Plugins

We have run some third-party plugins over the years, and each has their own installation method. I will briefly cover a few of them here:
FuzzyOcr: http://fuzzyocr.own-hero.net/ (no longer maintained as of 2009-06-01)
- combat image spam using optical character recognition
PDFInfo: http://www.rulesemporium.com/plugins.htm#pdfinfo (no longer available)
- combat pdf spam using a variety of rules obtained from the pdf information
NOTE: While we have used these in the past with some success, given the current state of the projects (as of late 2012), we will not cover installation.

Updates

SpamAssassin rules do see updates, and our Maia Mailguard setup requires some special consideration. The following root cron schedule handles updates, and also loading rules into the Maia Mailguard database so they can be interpreted at scanning time and for display in the web interface:

30 1 * * * /usr/bin/sa-update && /var/spool/amavisd/maia/scripts/load-sa-rules.pl
40 1 * * * su - amavis -c "/var/spool/amavisd/maia/scripts/maiadbtool.pl --expire-bayes | logger -t spamassassin"
50 1 * * * su - amavis -c "/var/spool/amavisd/maia/scripts/maiadbtool.pl --prune-awl --debug | logger -t spamassassin"

Additionally, we use facilities provided by the maiadbtool to prune the Bayes and Autowhitelist information in the database.


Scalability

10.137.0.1
qmail-outside
port 25
rbldnsd
greetdelay
validrcptto
127.0.0.1
qmail-fixup
port 10023
127.0.0.1
maia
port 10024
clamav
spamassassin
127.0.0.1
qmail-inside
port 10025

Scaling out the Internet mail exchangers is trivial because the ability to define multiple MX records is inherent to DNS. We simply clone the setup and add an MX record for each exchanger in the external zone. For example:

@				IN		MX		0 mx1.madstop.edu.
@				IN		MX		0 mx2.madstop.edu.

NOTE: @ is a BIND shortcut for the domain in question, here madstop.edu.

If this is not sufficient, some kind of high-availability setup on mx1 and mx2 can be added as needs require.



Webmail

We have our mailstores providing IMAP and POP and content-scanning mail exchangers for incoming SMTP. We will round our our client access needs with a webmailer. We will do this on a separate machine, using the venerable IMAP webmail client Squirrelmail. We will also add the bits necessary for users to manage their server-side sieve scripts over the Managesieve protocol. Lastly, we will finish off the Maia Mailguard installation with its PHP frontend, and integrate it for single sign-on with Squirrelmail.


Squirrelmail

Squirrelmail is a PHP application, which obviously requires a web environment capable of PHP. We will assume Apache. And though packages probably exist for your distribution, we will download the latest source. It is fairly standard as far as PHP installations go, but we will highlight a few tweaks and changes.

Installation

First download the latest package at http://sourceforge.net/projects/squirrelmail/files and untar to a directory of your choice. We will do it in the typical web root:

cd /var/www/html
tar -zxvf /path/to/squirrelmail-webmail-1.4.22.tar.gz
mv squirrelmail-webmail-1.4.22 squirrelmail

We will also create two more directories necessary for preferences and attachments:

mkdir /var/sqdata
mkdir /var/sqattach

Configuration

We now have a squirrelmail folder in our webroot. Squirrelmail configuration is stored in a typical config/config.php file, but there is also a provided perl /config/conf.pl script to step through configuration in a menu-driven interface. It is easiest to simply run conf.pl at least initially.

Organization Preferences
- customize to local site requirements
Server Settings
- change domain
Server Settings/IMAP Settings
- imap server points to our mailstore
- server software set to dovecot
- enable ssl if you wish
Server Settings/SMTP Settings
- smtp server points to our local exchanger
Folder Defaults
- trash, sent, drafts folders remove INBOX. prefix
- default sub of inbox false
- folder delete bypasses trash true
General Options
- data directory /var/sqdata
- attachment directory /var/sqattach
- force usernames lowercase true
- hide sm attributions true
- allow server-side sorting true
- php session name PHPSESSID

NOTE: Recent SquirrelMail versions have an option at the main menu to set pre-defined settings for specific IMAP servers. That may be worth a spin.

NOTE: Older SquirrelMail versions defaulted the data and attachment directories inside the source tree. Recent squirrelmails default this outside the tree. In any event make sure the paths are outside the tree and therefore outside the web server document root.

NOTE: Changing the PHP session name is only necessary for tying authentication to a patched Maia Mailguard, and does no harm either way.

Plugins

Squirrelmail offers maximal flexibility through its plugin architecture. We make use of several, including some of our own. This list includes some we find useful, and following sections will detail setup for some of the more generally applicable ones. Review the list, check the Squirrelmail site, and salt to taste:

compatibility
- provides backward and forward compatiblity for various plugins
msg_flags
- visual enhancements to bearmail (requires patching the squirrelmail source)
image_buttons
- convert top row text links to buttons based on user preference
retrieveuserdata
- retrieve user data from ldap upon first login to fill in base preferences
filters
- disabled, included in base squirrelmail but we will use avelseive instead
avelsieve (and javascript_libs)
- managesieve filters interface to configure server-side filtering
login_check
- prevent multiple tabs/browsers in same session in effort to prevent preference cross-contamination
squirrel_logger
- more logging info
html_inject
- custom plugin to stuff html into various places in the interface
phishhook
- custom plugin to lock out users based on phishy heuristics
login_log
- custom plugin to log user logins in digestible form for troubleshooting and for phishhook
- must come after the phishhook plugin in the enable list for phishhook to operate properly

Plugin: Avelsieve

Dovecot on the mailstore also provides a Managesieve server running on port 2000. Managesieve is a standard network protocol to allow for sieve capability negotiation and management of server-side sieve filters. We will add Avelsieve to SquirrelMail to take advantage of it.

Download the latest development package at http://code.uoa.gr/p/avelsieve/download.php and untar it into the squirrelmail plugins directory of your choice. We will also need the javascript_libs plugin for full functionality:

cd /path/to/squirrelmail/plugins
wget http://email.uoa.gr/download/squirrelmail/avelsieve/avelsieve-1.9.9.tar.gz
tar -zxvf avelsieve-1.9.9.tar.gz
wget http://email.uoa.gr/download/squirrelmail/javascript_libs/javascript_libs-0.1.2.tar.gz
tar -zxvf javascript_libs-0.1.2.tar.gz

NOTE: We would normally take the stable package, but the development package is much more up-to-date. We have used it for years.

Configuration is straightforward starting from the sample config:

cd avelsieve
cp config_sample.php config.php

The following will turn on debugging, and disable a few features we do not want exposed as an example:

define('AVELSIEVE_DEBUG', 2);
$disable_avelsieve_capabilities = array("notify", "date", "reject");

We need to run the main squirrelmail config/conf.pl and enable the plugin:

Plugins
XX. avelsieve
YY. javascript_libs

Plugin: Login Log

Login Log is a custom plugin we developed to provide an easy to parse log of user logins, one-per-line.

cd /path/to/squirrelmail/plugins
wget http://fritz.potsdam.edu/projects/squirrelmail_plugins/login_log-1.0.0-1.4.x.tar.gz
tar -zxvf login_log-1.0.0-1.4.x.tar.gz

NOTE: While this plugin provides a concise listing of login activity, the Squirrel Logger plugin vastly exceeds this if you are looking for more detail. For now, Phishook currently requires login_log. You may not find any of this useful.

Configuration consists merely of setting the log file location in config.php:

$login_log_file = '/var/log/squirrelmail/login_log';

NOTE: The apache user will require write access to this directory.

We need to run the main squirrelmail config/conf.pl and enable the plugin:

Plugins
XX. login_log

NOTE: The login_log plugin must come after the phishhook function in the plugin list. This is because they are both called on the same hook, and if login_log is first, phishhook will see "this" login as the "last" login and never fire.

Plugin: Phishhook

NOTE: Phishhook has been superseded by various tests in Qmail-Skim during SMTP-Auth on the local mail exchanger (as of mid 2013). To be documented.

Phishhook is a site-specific plugin that checks for two things we have determined to be phishy behavior:

  • Has this user's country of login changed within the last few hours? (country-hopping)
  • Is the user mailing a large batch of recipients using a from address that is not their standard username? (mass-mailing)

If either of these conditions are detected, based on a number of configurable variables, the plugin runs some associated Perl to reach out across a range of systems to do the following:

  1. Create a ticket in our RT tracking system.
  2. Blacklist the user's IP address/range in the main border firewall.
  3. Scramble the account password
  4. Add them to an LDAP group marking them as someone requiring education and manual intervention to regain account access from Helpdesk staff.

Due to its highly site-specific behavior, Phishhook is not (yet) generally available, but hopefully this gives an idea of some of the possibilities.

Plugin: HTML Inject

HTML Inject is a custom plugin we developed to target various Squirrelmail hooks to provide easy access for HTML insertion.

cd /path/to/squirrelmail/plugins
wget http://fritz.potsdam.edu/projects/squirrelmail_plugins/html_inject-1.0.0-1.4.x.tar.gz
tar -zxvf html_inject-1.0.0-1.4.x.tar.gz

Configuration consists of picking which locations (hooks) to enable in setup.php, and then altering the associated function to display what we wish. This example enables the menuline hook in the main init function, and then calls a function to display a link, with the result that we have a Mailguard link among the top row of buttons in the main Squirrelmail display:

function squirrelmail_plugin_init_html_inject() {
	global $squirrelmail_plugin_hooks;
	$squirrelmail_plugin_hooks['menuline']['html_inject'] = 'inject_menuline';
	// ...
}

function inject_menuline() {
	displayInternalLink('mailguard/xlogin.php',_("Mailguard"),'mailguard');
	echo "  \n";
}

NOTE: We actually target the xlogin.php file within Mailguard, since that will allow for single-sign on if we setup integration properly (next).


Mailguard Web

After all of our work on the mail exchanger, we are left with one remaining piece to setup for Maia Mailguard: the web interface. We install this on our webmail box alongside Squirrelmail.

Installation

The install documentation is very comprehensive and is the best resource for setting things up. We will concentrate only on the basics, and the few things that deviate or that we consider best practice for our setup.

The web scripts are in the php subdirectory of the Maia install archive. We already downloaded this for work on the mail exchanger, or grab it from http://www.maiamailguard.com/download.php. We will extract the web files alongside squirrelmail in the typical web root:

cp -a /path/to/maia-1.0.2c/php /var/www/html/mailguard

Maia Mailguard relis on the Smarty template engine, and a variety of other php components. We specifically expect LDAP support. Your system may vary, but this command will hit a lot of them:

yum install php php-pear php-mysql php-gd php-Smarty php-imap php-ldap php-mcrypt

The template engine requires that the user under which the web server runs has write access to the templates directory. You may see nothing but a blank page otherwise:

cd /var/www/html/mailguard
chown -R apache:apache themes

Configuration

A PHP standard config.php stores configuration information, and Maia Mailguard ships with a sample starting config:

cp config.php.dist config.php

We must at least configure our database connection information and user authentication parameters. We will tweak a few other things as well:

$loglevel = PEAR_LOG_INFO;		// messages >= this level are sent to php log
$default_session_timeout = 300;		// 15 minute default
$maia_sql_dsn = "mysql://amavis:password@tcp(mailguarddb.madstop.edu:3306)/maia";

The protection array requires some deep thought and excellent hand-eye coordination to get right. This sets the four pre-defined protection levels users can select in the web interface. A full description of all of this is available in the in the install documentation. We define the four levels thus:

  • off => spam scanning disabled
  • low => spam scanning enabled, spam at score 5 is passed through with modified subject
  • medium => spam scanning enabled, spam at score 5 is passed through with modified subject, mail with invalid headers is quarantined
  • high => spam scanning enabled, spam at score 5 is quarantined, mail with invalid headers is quarantined

In all cases, we discard viruses and files with banned attachments. We decided there was no reason to quarantine these, least of all because we did not want users faced with the decision to release the horde upon themselves. In line with this, we actually modified the template we assign to all users to hide these other quarantine areas in the web interface.

All these decisions weigh out thus in the protection array:

$protection = array(
	'off' => array ('N','Y','N','Y','N','Y','N','Y','Y','N','Y','N','N','999','999','999'),
	'low' => array ('N','Y','N','Y','N','N','N','N','Y','N','Y','N','Y','-999','5','999'),
	'medium' => array ('N','Y','N','N','N','N','N','N','Y','N','Y','N','Y','-999','5','999'),
	'high'   => array ('N','N','N','N','N','N','N','N','Y','N','Y','N','N','-999','5','5')
);

NOTE: Do yourself a favor and read up on the protection array in the install documentation.

Lastly we configure authentication for LDAP:

$auth_method = "ldap";
$auth_ldap_server = "ldap.madstop.edu";
$auth_ldap_password = "";
$auth_ldap_query = "uid=%%USER%%";
$auth_ldap_bind_dn = "o=madstop.edu";
$auth_ldap_base_dn = "o=madstop.edu";
$auth_ldap_attribute = "mail";

Testing PHP and Database

The best way to test that things are in order is to go to the admin/configtest.php page. This will present a nice report on the status of all of the needed pieces. You can probably assume something will be missing.

Domain Configuration

The next steps will involve logging in to the web interface to create an administrator:

http://mailguard.madstop.edu/mailguard/login.php?super=register

From there, it is on to create a domain account that will serve as a default for all users of that domain. User accounts are created at time of login, or at time of email receipt.

As an administrator:

Admin -> Domains -> New domain: @madstop.edu -> Add Domain

When configuring the domain, it is best to match all of the settings to those of one of the protection levels we pre-configured earlier. If it matches exactly, the "Current Protection Level" box on the home page will have the level selected when users first login, rather than a confusing note about a custom level being in use. If users deviate from the standard levels, it is by their own choice. We expect that users will want "High" protection and match it accordingly.

SquirrelMail Integration

If the SquirrelMail and Maia Mailguard web scripts are installed in such a way that they can share a PHP session, as we have done here, my session auth patch to Maia Mailguard will allow for seamless login for already authenticated SquirrelMail users.

NOTE: See the SquirrelMail Maia Integraton writeup for more info.

There are some limitations with this setup currently:

  • The Maia PHP scripts must run on the same box as Squirrelmail so they may share a PHP session
  • The Maia patch presented here only works with LDAP authentication ($auth_method = "ldap")
  • The LDAP server must allow retrieval of a user's email attribute via an anonymous bind

Download and apply the patch:

wget http://fritz.potsdam.edu/projects/maia_extras/maia-1.0.2a-sessauth-1.0.0.patch
cd /path/to/maia/php
patch -p1 </path/to/patch

It is easy to test whether this is working by logging into SquirrelMail, and then browsing directly to http://url/to/maikguard/xlogin.php (the link we placed with the html_inject plugin). If the patch is not applied, or not working, or you are not authenticated already as a Squirremail user, you should get a "Login for user failed. ( )" message. If it works, you should skip right in to the Maia welcome page as your Squirrelmail user. You should also test that normal logins work by going to your main Maia URL and logging in normally. You should also note that logging out of the Maia application does not end your Squirrelmail session.


Scalability

Scaling out webmail is straightforward, since we just need to clone the machine. Mailguard reads all of its configuration and user preferences/data out of the database. SquirrelMail requires a bit more work, since user preferences are stored on the filesystem. There are plugins and enhancements to store this in a database instead.



Other Topics

Storage

Email is a very IO intensive service, and storage must be capable of sustained random access loads. We rely on local SCSI and SAS disk for qmail queues and Dovecot indexes. In the past we have stored user mail on direct attached U320 SCSI drives, and ATAoE SATA drives in various RAID configurations.

Current storage is ATAoE SAS drives in RAID10 configurations. In our primary mailstore, home directory locations are symlinks, pointing to real homes on one of three LUNs. This was an effort to introduce more drive spindles to the storage equation on older drives, and is probably no longer necessary.

Perhaps this gives an idea of the need for storage to be elastic to keep up with changing loads. Things will vary greatly from one configuration, site, and network to the next.

Account Management

We took great pains to ensure that user homes and Maildirs are created automatically on qmail-ldap delivery, or POP/IMAP login. How these user accounts are actually created and managed deserves a page of its own. We have users stored in two information stores: the student information system is the master, and a purpose-built creation and syncronization system (CASL) keeps LDAP up-to-date. A secondary system handles user access changes over time (WAM), changing email service groups and the qmail accountStatus attribute based on local policy. The ZeusAdmin web interface allows for user and alias creation and management. All make use of our extensive Perl LDAP library.

Calendaring

Calendar solutions that work well integrate tightly with email. We are working to replace our proprietary vendor solution with SOGo, an open-source solution based on the CalDAV protocol. This merits mention because SOGo offers a rich web interface for email as well as calendaring, and it will probably come to replace our webmailer.

Future Work

The evolving landscape of email inanity now includes phish, turning our own users against us. In particular, we see our webmailer used to send outgoing spam once an account has been compromised. For years we have included an in-house Squirrelmail plugin "phishhook," described in detail in our webmailer setup. That has kept the lid on a lot of phish behavior, and also doubles as a bit of a honeypot for compromised accounts: webmail is an attractive target.

Work on the phish problem proceeds in the guise of qmail-skim. Qmail-skim is a qmail-queue replacement that incorporates a number of heuristics that we have determined over the years to be indicative of phish behavior. It can easily be inserted into the qmail chain on qmail instances that include the qmailqueue-patch (all of our installs). The advantage of tying this in on our qmail-outside instances is that if it determines a message should be rejected, it is rejected during the SMTP conversation. It is a work in progress.



A: Scripts

A comprehensive listing of all of the scripting and custom coding that plays an essential role in the core of the email system. Most of these are Perl scripts, and most (not all) were authored by me. Since many of them play a part across multiple components, we document them separately.

chkpassldap
My implementation of DJB's checkpassword interface, used on both the mailstores and the mail exchangers for Dovecot and SMTP authentication.
dovelogin
A script to create user homes and Maildirs on Dovecot login. It also touches a .last_login_imap or .last_login_pop file in in user homes so that we can track last login times. It also drops these in a separate cache location for LDAP update.
doveloginsync
Updates LDAP user leaf custom attribute "mailLastLoginTime" from the cache created by dovelogin. Will also set accountStatus=active (qmail-ldap attribute that determines deliverability) for users who have left their accounts fallow long enough that they have been disabled.
hermesbackups
Complements double-delivery for mail backup on the backup boxes. Responsible both for backup up of user Sent folders from the mailstores and culling old mail from the backup set based on a configurable number of days (30).
maialinksync
Links qmail aliases to user accounts in Maia, using the provided maiadbtool to interace with the database directly. Aliases that cannot be linked, either because they are a list or to a remote address, are assigned to an administrator account.
mkvalidrcptto
Builds the validrcptto.cdb file for the recipient checking feature provided by the JMS patch on the Internet mail exchangers. Pulls in LDAP users with accountStatus=active (as determined by the WAM system), LDAP aliases, filesystem dotqmail files in user homes, and filesystem aliases on the mailstores.
qmqtool
An incredibly handy tool written by Jeremy Kister to administer qmail queues. We use it on numerous qmail installs for deleting the occassional swathe of mail originating from a phished user (the $qmail variable must be altered to reflect the appropriate qmail instance on multi-qmail boxes).


B: Ucspi-tcp Installation

http://cr.yp.to/ucspi-tcp.html

One of two crucial pieces of DJB software (along with daemontools) for qmail, ucspi-tcp provides the tcpserver which listen on the network on behalf of qmail-smtp/qmail-qmqpd/ofmipd, etc.

Download the source tarball to a directory of your choice:
http://cr.yp.to/ucspi-tcp/ucspi-tcp-0.88.tar.gz

Also pull down the errno patch here, or available in the other-patches directory of the netqmail distribution:

diff -u ucspi-tcp-0.88.old/error.h ucspi-tcp-0.88/error.h
--- ucspi-tcp-0.88.old/error.h	2000-03-18 09:18:20.000000000 -0600
+++ ucspi-tcp-0.88/error.h	2003-01-08 13:39:12.000000000 -0600
@@ -1,7 +1,7 @@
 #ifndef ERROR_H
 #define ERROR_H
 
-extern int errno;
+#include <errno.h>
 
 extern int error_intr;
 extern int error_nomem;

Unpack the ucspi-tcp source and apply patches.

tar -zxvf ucspi-tcp-0.88.tar.gz
cd ucspi-tcp-0.88
patch -p1 < /path/to/ucspi-tcp-0.88.errno.patch

Installation.

make
make setup check

End result: Several binaries will end up in /usr/local/bin.



C: Daemontools Installation

http://cr.yp.to/daemontools.html

One of two crucial pieces of DJB software (along with ucspi-tcp) for qmail, daemontools provides the processes necessary for program initialization and daemonization.

Download the source tarball to a directory of your choice:
http://cr.yp.to/daemontools/daemontools-0.76.tar.gz

Also pull down the errno patch here, or available in the other-patches directory of the netqmail distribution:

diff -ur daemontools-0.76.old/src/error.h daemontools-0.76/src/error.h
--- daemontools-0.76.old/src/error.h	2001-07-12 11:49:49.000000000 -0500
+++ daemontools-0.76/src/error.h	2003-01-09 21:52:01.000000000 -0600
@@ -3,7 +3,7 @@
 #ifndef ERROR_H
 #define ERROR_H
 
-extern int errno;
+#include <errno.h>
 
 extern int error_intr;
 extern int error_nomem;

Unpack the daemontools source and apply patches. The deamontools install is a bit peculiar and should be unpacked into /package where it will create an admin/deamontools subdirectory.

mkdir -p /package
chmod 1755 /package
cd /package
tar -zxvf /path/to/daemontools-0.76.tar.gz
cd /package/admin/daemontools-0.76
patch -p1 < /path/to/daemontools-0.76.errno.patch

Installation.

cd /package/admin/daemontools-0.76
./package/install

The daemontools install includes svscan, which is configured to run via init. If your system does not use old-school init, as modern Fedora do not for instance, other changes must be made.

Init

The daemontools installer already creates the following entry in /etc/inittab which should work fine for most init-based systems:

SV:123456:respawn:/command/svscanboot

NOTE: If you want to keep qmail services down at boot, you must ensure symlinks in /service are deleted.

Upstart

The following /etc/event.d/svscanboot will work for Fedora 9+ upstart-based systems:

# svscanboot
start on runlevel 2
start on runlevel 3
start on runlevel 4
start on runlevel 5
stop on runlevel 0
stop on runlevel 1
stop on runlevel 6

console output
exec /command/svscanboot
respawn

Upstart configuration changed in later versions, and the following /etc/init/svscanboot.conf will work for Fedora 13+ upstart-based systems:

# svscanboot
start on runlevel [2345]
stop on runlevel [016]

console output
exec /command/svscanboot
respawn

Fire it up:

# initctl start svscanboot

NOTE: If you want to keep qmail services down at boot, simply adjust the start on and stop on configs accordingly.

End result: Several symlinks will end up in /usr/local/bin, which point to symlinks in /command, which point to binaries in /package/admin/daemontools-0.76/command. Also, a /service directory will be created, into which we symlink our services.



D: Mess822 Installation

http://cr.yp.to/mess822.html.

An interesting piece of DJB software, mess822 provides several tools for qmail pertaining to address handling and rewriting.

Download the source tarball to a directory of your choice:
http://cr.yp.to/software/mess822-0.58.tar.gz

Also pull down the errno patch here, or available in the other-patches directory of the netqmail distribution:

diff -u mess822-0.58.old/cdb_seek.c mess822-0.58/cdb_seek.c
--- mess822-0.58.old/cdb_seek.c 1998-09-04 21:33:37.000000000 -0500
+++ mess822-0.58/cdb_seek.c     2003-01-13 23:17:30.000000000 -0600
@@ -1,6 +1,5 @@
 #include 
 #include 
-extern int errno;
 #include "cdb.h"

 #ifndef SEEK_SET
diff -u mess822-0.58.old/error.h mess822-0.58/error.h
--- mess822-0.58.old/error.h    1998-09-04 21:33:37.000000000 -0500
+++ mess822-0.58/error.h        2003-01-13 23:18:09.000000000 -0600
@@ -1,7 +1,7 @@
 #ifndef ERROR_H
 #define ERROR_H

-extern int errno;
+#include 

 extern int error_intr;
 extern int error_nomem;
diff -u mess822-0.58.old/leapsecs_read.c mess822-0.58/leapsecs_read.c
--- mess822-0.58.old/leapsecs_read.c    1998-09-04 21:33:37.000000000 -0500
+++ mess822-0.58/leapsecs_read.c        2003-01-13 23:19:17.000000000 -0600
@@ -2,7 +2,6 @@
 #include 
 #include 
 #include 
-extern int errno;
 #include "tai.h"
 #include "leapsecs.h"

Unpack the mess822 source and apply patches.

tar -zxvf mess822-0.58.tar.gz
cd mess822-0.58.tar.gz
patch -p1 < /path/to/mess822-0.58.errno.patch

The mess822 package assumes /var/qmail, and we change this in conf-qmail on multi-qmail boxes.

Installation.

make
make setup check



E: Netqmail Installation

http://www.qmail.org/netqmail

"A motley krewe of qmail contributors (see the README) has put together a netqmail-1.06 distribution of qmail. It is derived from Daniel Bernstein's qmail-1.03 plus bug fixes, a few feature enhancements, and some documentation."

Source

Download the source tarballs to a directory of your choice:

http://www.qmail.org/netqmail-1.06.tar.gz

Download additional qmail patches:

big-concurrency: http://qmail.org/big-concurrency.patch
- raises concurrency limits allowing more deliveries at once
ext-todo: http://www.nrg4u.com/qmail/ext_todo-20030105.patch
- eliminates bottleneck by separating todo processing into a separate binary
dns patch for large queries: http://www.ckdhr.com/ckd/qmail-103.patch
- handles sites that return large responses to mx queries
doublebounce-trim: http://qmail.org/doublebounce-trim.patch
- prevents bounces of bounces from being queued using doublebounceto control file
qmail-inject null sender fix: http://notes.sagredo.eu/sites/notes.sagredo.eu/files/qmail/patches/qmail-inject-null-sender.patch
- fixes qmail inject so it no longer tacks the defaultdomain onto the null sender, a la <>@madstop.edu

Unpacking and Patching

Unpack the qmail source and apply patches. If applied in the following order, they should apply cleanly to netqmail-1.06 with nothing more than small offsets:

tar -zxvf netqmail-1.06.tar.gz
cd netqmail-1.06
patch -p1 < /path/to/big-concurrency.patch
patch -p1 < /path/to/ext_todo-20030105.patch
patch -p1 < /path/to/qmail-103.patch	# big dns
patch -p1 < /path/to/doublebounce-trim.patch
patch -p3 < /path/to/qmail-inject-null-sender.patch

Users and Directories

First, qmail needs a home:

mkdir /var/qmail

NOTE: This is defined in conf-qmail in the qmail source tree. This is what we will change on multi-qmail boxes.

Qmail requires several users to be created for its various components. There is an INSTALL.ids file in the qmail source tree for reference. Use the following to keep the uidnumbers below 500: in the system range and out of the user range:

groupadd -g 400 nofiles
useradd -u 400 -g nofiles -d /var/qmail/alias alias
useradd -u 401 -g nofiles -d /var/qmail qmaild
useradd -u 402 -g nofiles -d /var/qmail qmaill
useradd -u 403 -g nofiles -d /var/qmail qmailp
groupadd -g 401 qmail
useradd -u 404 -g qmail -d /var/qmail qmailq
useradd -u 405 -g qmail -d /var/qmail qmailr
useradd -u 406 -g qmail -d /var/qmail qmails

Building and Installation

We now compile and install qmail:

cd netqmail-1.06
make setup check
./config



F: Qmail JMS Combined Installation

http://qmail.jms1.net/patches/combined.shtml

John Simpson has created and maintained a mega patch set to qmail which offers a plethora of features geared at Internet-facing SMTP. We use it for a number of things, as outlined on the mail exchangers.

Source

Download the source tarball of stock qmail-1.03 to a directory of your choice:

qmail-1.03: http://cr.yp.to/software/qmail-1.03.tar.gz

Download the JMS combined patch. It includes many commonly employed qmail patches, including most of those in netqmail, but we also want a few additional ones:

jms combined: http://qmail.jms1.net/patches/qmail-1.03-jms1.7.08.patch
- provides several enhancements and edge smtp features
qmail-inject null sender fix: http://notes.sagredo.eu/sites/notes.sagredo.eu/files/qmail/patches/qmail-inject-null-sender.patch
- fixes qmail inject so it no longer tacks the defaultdomain onto the null sender, a la <>@madstop.edu

Unpacking and Patching

Unpack the qmail source and apply patches. If applied in the following order, they should apply cleanly to qmail-1.03:

tar -zxvf qmail-1.03.tar.gz
cd qmail-1.03
patch -p1 < /path/to/qmail-1.03-jms1.7.08.patch
patch -p3 < /path/to/qmail-inject-null-sender.patch

Users and Directories

First, qmail needs a home:

mkdir /var/qmail

NOTE: This is defined in conf-qmail in the qmail source tree. This is what we will change on multi-qmail boxes.

Qmail requires several users to be created for its various components. There is an INSTALL.ids file in the qmail source tree for reference. Use the following to keep the uidnumbers below 500: in the system range and out of the user range:

groupadd -g 400 nofiles
useradd -u 400 -g nofiles -d /var/qmail/alias alias
useradd -u 401 -g nofiles -d /var/qmail qmaild
useradd -u 402 -g nofiles -d /var/qmail qmaill
useradd -u 403 -g nofiles -d /var/qmail qmailp
groupadd -g 401 qmail
useradd -u 404 -g qmail -d /var/qmail qmailq
useradd -u 405 -g qmail -d /var/qmail qmailr
useradd -u 406 -g qmail -d /var/qmail qmails

Building and Installation

We now compile and install qmail:

cd qmail-1.03
make setup check
./config
NOTE: you may need to install the openssl-devel libraries if make cannot find openssl/ssl.h

G: Qmail-ldap Installation

http://www.nrg4u.com/

"qmail-ldap is a patch to qmail 1.03 to retrieve all user data from a ldap-directory rather then from files on the disk. This allows easier administration, especially in distributed environments. There is also clustering support builtin making qmail-ldap very well suited for big mail installations at ISPs."

Source

Download the source tarball of stock qmail-1.03 to a directory of your choice: qmail-1.03: http://cr.yp.to/software/qmail-1.03.tar.gz

Download the qmail-ldap patch. It includes many commonly employed qmail patches, such as most of those in netqmail, but we also want a few additional ones:

qmail-ldap: http://www.nrg4u.com/qmail/qmail-ldap-1.03-20120221.patch.gz
- allows for retrieval of user data from ldap and application clustering
doublebounce-trim: http://qmail.org/doublebounce-trim.patch
- prevents bounces of bounces from being queued using doublebounceto control file
qmail-inject null sender fix: http://notes.sagredo.eu/sites/notes.sagredo.eu/files/qmail/patches/qmail-inject-null-sender.patch
- fixes qmail inject so it no longer tacks the defaultdomain onto the null sender, a la <>@madstop.edu

NOTE: We have run the 20060221 version of the patch for years. There is a newer 20120221 version of the patch that is a convergence of some common features of JMS and other qmail distros. It seems to require some Makefile touchup to disable the auth_dovecot module in order to get it to compile and install cleanly. Your mileage my vary.

Unpacking and Patching

Installation is covered more fully elsewhere, but in a nutshell:

tar -zxvf qmail-1.03.tar.gz
cd qmail-1.03
patch -p1 < /path/to/qmail-ldap-1.03-xxxxxxxx.patch
patch -p1 < /path/to/qmail-inject-null-sender.patch

Users and Directories

First, qmail needs a home:

mkdir /var/qmail

NOTE: This is defined in conf-qmail in the qmail source tree. This is what we will change on multi-qmail boxes.

Qmail requires several users to be created for its various components. There is an INSTALL.ids file in the qmail source tree for reference. Use the following to keep the uidnumbers below 500: in the system range and out of the user range:

groupadd -g 400 nofiles
useradd -u 400 -g nofiles -d /var/qmail/alias alias
useradd -u 401 -g nofiles -d /var/qmail qmaild
useradd -u 402 -g nofiles -d /var/qmail qmaill
useradd -u 403 -g nofiles -d /var/qmail qmailp
groupadd -g 401 qmail
useradd -u 404 -g qmail -d /var/qmail qmailq
useradd -u 405 -g qmail -d /var/qmail qmailr
useradd -u 406 -g qmail -d /var/qmail qmails

And for vmail setups, additionally:

groupadd -g 402 vmail
useradd -u 407 -g vmail vmail

Compile-time Configuraton: Makefile

Qmail-ldap is a somewhat unique qmail installation as it has a number of features that can easily be left out of compilation entirely in the heavily commented Makefile. There are a few to which you might pay particular attention:

LDAPFLAGS=-DALTQUEUE -DEXTERNAL_TODO -DDASH_EXT -DCLEARTEXTPASSWD -DQLDAP_CLUSTER
- a number of core features
LDAPLIBS=-L/usr/lib64 -lldap -llber
- adjust as necessary
LDAPINCLUDES=-I/usr/include
- adjust as necessary
MDIRMAKE=-DAUTOMAILDIRMAKE
- enables auto-creation of user Maildirs on mail delivery
HDIRMAKE=-DAUTOHOMEDIRMAKE
- enables auto-creation of user home directories on mail delivery
SHADOWLIBS=-lcrypt
- may or may not be necessary

Compile-time LDAP Attributes: qmail-ldap.h

This file maps the queried ldap attributes for qmail-ldap, so you will have to look things over and ensure they are set to LDAP attributes that make sense for your local installation. Most of these will be provided with the qmail-ldap LDAP schema, and others will be set to logical defaults. This is the place to override them.

For example, LDAP_QMAILUID and LDAP_QMAILGID default to qmailUID and qmailGID which are provided as part of the qmail-ldap ldap schema. If we want to re-use our existing uidNumbers and gidNumbers rather than maintain yet more attributes, we can do the following:

#define LDAP_QMAILUID		"uidNumber"
#define LDAP_QMAILGID		"gidNumber"

Building and Installation

We now compile and install qmail:

cd qmail-1.03
make setup check
./config

NOTE: You wil almost certainly require the openldap-devel libraries on your system.

LDAP Schema

Qmail-ldap ships with an LDAP schema that needs to be installed into your LDAP infrastructure. That is documented fully in the LDAP install documentation, but to give an idea what kind of information is retrieved, the following is sample output from the qmail-ldaplookup troubleshooting tool:
# /var/qmail/bin/qmail-ldaplookup -m hardyjm@madstop.edu
Searching ldap for: (|(mail=hardyjm@madstop.edu)(mailAlternateAddress=hardyjm@madstop.edu))
under dn: o=madstop.edu
Found 1 entry:

dn: uid=hardyjm,ou=People,o=madstop.edu
-------------------------------------------------------
objectClass: top
objectClass: account
objectClass: posixAccount
objectClass: sambaSamAccount
objectClass: person
objectClass: organizationalPerson
objectClass: inetorgPerson
objectClass: spotperson
objectClass: qmailuser
objectClass: sunyPerson
mail: hardyjm@madstop.edu
mailAlternateAddress: hardy@madstop.edu
mailAlternateAddress: hardyjm-null@madstop.edu
uid: hardyjm
accountStatus: active
mailHost: hermes.madstop.edu
homeDirectory: /mnt/home/hardyjm
aliasEmpty: using default
qmailDotMode: both
uidNumber: 16814
gidNumber: 100
mailQuotaSize: 0 (unlimited)
mailQuotaCount: 0 (unlimited)
mailSizeMax: 0 (unlimited)
mailReplyText: undefined

As you can see, everything centers around the mail and mailAlternateAddress attributes.



H: Qmail Configuration and Startup

Qmail relies on daemontools run scripts for initialization. These scripts setup the environment for each of the moving parts of qmail. For those pieces exposed to the network by tcpserver (qmail-smtpd, qmail-ofmipd, qmail-qmqpd, etc), tcpserver configuration can also set environment variables, exposing different features and functions to different networks.

The following qmail startup scripts, daemontools run scripts, and tcp configs are stock examples based on those at Life With Qmail, and as such, are placed in /var/qmail (or qmail-fixup, inside, etc) and symlinked into the daemontools /service directory. They are largely universal across all machines in our setup. The exception are those that provide smtps/ofmpid/qmqpd service. The stock example below for smtp serves as a good starting point, and where this differs in our installs, it will be noted.

NOTE: The following assumes installation in /var/qmail. If this is a second instance of qmail on the same box, that will be /var/qmail-fixup, /var/qmail-inside, /var/qmail-forward, etc. Adjust accordingly.


Qmail Configuration: /var/qmail/control

Qmail is controlled by a variety of files in /var/qmail/control. The stock qmail ./config creates the following control files. Many more controls are available, and patches may provide even more. See man qmail-control.

defaultdomain
- qmail-inject appends this to any hostname without dots, default me
locals
- qmail-send delivers mail locally to current host for all listed domains, default me
me
- default for other hostname-related control files, ./config probably set this to your full hostname
- qmail can survive with only this in the most basic install
plusdomain
- qmail-inject adds this name to any host name that ends with a plus sign, default me
rcpthosts
- qmail-smtpd will reject any evelope recipient address with a domain not listed in rcpthosts if rcpthosts exists

Qmail Startup Scripts

/var/qmail/rc

#!/bin/sh

# Using stdout for logging
# Using control/defaultdelivery from qmail-local to deliver messages by default

QMAIL=qmail

exec env - PATH="/var/$QMAIL/bin:$PATH" qmail-start "`cat /var/$QMAIL/control/defaultdelivery`"

NOTE: defaultdelivery is not a standard qmail control file, but is used by our scripts

echo ./Maildir/ >/var/qmail/control/defaultdelivery

Set the proper perms and create the log directory:

chmod 755 /var/qmail/rc
mkdir /var/log/qmail

/var/qmail/bin/qmailctl

A handy script for exposing all of the commonly used administrative functions:

#!/bin/sh

# description: the qmail MTA

QMAIL=qmail
TCPRULES=tcp.smtp

PATH=/var/$QMAIL/bin:/bin:/usr/bin:/usr/local/bin:/usr/local/sbin
export PATH

QMAILDUID=`id -u qmaild`
NOFILESGID=`id -g qmaild`

case "$1" in
	start)
		echo "Starting $QMAIL"
		if svok /service/$QMAIL-send ; then
			svc -u /service/$QMAIL-send /service/$QMAIL-send/log
		else
			echo "qmail-send supervise not running"
		fi
		if svok /service/$QMAIL-smtpd ; then
			svc -u /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		else
			echo "qmail-smtpd supervise not running"
		fi
		if [ -d /var/lock/subsys ]; then
			touch /var/lock/subsys/$QMAIL
		fi
		sleep 1;
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		/var/$QMAIL/bin/qmail-qstat
		;;
	stop)
		echo "Stopping $QMAIL..."
		echo "  qmail-smtpd"
		svc -d /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		echo "  qmail-send"
		svc -d /service/$QMAIL-send /service/$QMAIL-send/log
		if [ -f /var/lock/subsys/$QMAIL ]; then
			rm /var/lock/subsys/$QMAIL
		fi
		sleep 1;
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		/var/$QMAIL/bin/qmail-qstat
		;;
	stat)
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		/var/$QMAIL/bin/qmail-qstat
		;;
	doqueue|alrm|flush)
		echo "Flushing timeout table and sending ALRM signal to qmail-send."
		/var/$QMAIL/bin/qmail-tcpok
		svc -a /service/$QMAIL-send
		;;
	queue)
		/var/$QMAIL/bin/qmail-qstat
		/var/$QMAIL/bin/qmail-qread
		;;
	reload|hup)
		echo "Sending HUP signal to qmail-send."
		svc -h /service/$QMAIL-send
		;;
	pause)
		echo "Pausing qmail-send"
		svc -p /service/$QMAIL-send
		echo "Pausing qmail-smtpd"
		svc -p /service/$QMAIL-smtpd
		;;
	cont)
		echo "Continuing qmail-send"
		svc -c /service/$QMAIL-send
		echo "Continuing qmail-smtpd"
 		svc -c /service/$QMAIL-smtpd
		;;
	restart)
		echo "Restarting $QMAIL:"
		echo "* Stopping qmail-smtpd."
		svc -d /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		echo "* Sending qmail-send SIGTERM and restarting."
		svc -t /service/$QMAIL-send /service/$QMAIL-send/log
		echo "* Restarting qmail-smtpd."
		svc -u /service/$QMAIL-smtpd /service/$QMAIL-smtpd/log
		sleep 1;
		svstat /service/$QMAIL-send
		svstat /service/$QMAIL-send/log
 		svstat /service/$QMAIL-smtpd
		svstat /service/$QMAIL-smtpd/log
		/var/$QMAIL/bin/qmail-qstat
		;;
	cdb)
		tcprules /etc/$TCPRULES.cdb /etc/$TCPRULES.tmp < /etc/$TCPRULES
		chmod 644 /etc/$TCPRULES.cdb
		echo "Reloaded: /etc/$TCPRULES"
		;;
	sendstop)
		echo "Stopping qmail-send"
		svc -d /service/$QMAIL-send /service/$QMAIL-send/log
		;;
	help)
		cat <<HELP
    stop -- stops mail service (smtp connections refused, nothing goes out)
   start -- starts mail service (smtp connection accepted, mail can go out)
   pause -- temporarily stops mail service (connections accepted, nothing leaves)
    cont -- continues paused mail service
    stat -- displays status of mail service
     cdb -- rebuild the tcpserver cdb file for smtp
 restart -- stops and restarts smtp, sends qmail-send a TERM & restarts it
 doqueue -- schedules queued messages for immediate delivery
  reload -- sends qmail-send HUP, rereading locals and virtualdomains
   queue -- shows status of queue
    alrm -- same as doqueue
   flush -- same as doqueue
     hup -- same as reload
sendstop -- stop qmail-send
HELP
    ;;
	*)
		echo "Usage: $0 {start|stop|restart|doqueue|flush|reload|stat|pause|cont|cdb|queue|sendstop|help}"
		exit 1
		;;
esac

exit 0

NOTE: $QMAIL and $TCPRULES "name" the qmail instance to ease script re-use

NOTE: sendstop is another additon of mine to target sending shutdown

chmod 755 /var/qmail/bin/qmailctl
ln -s /var/qmail/bin/qmailctl /usr/local/bin

Daemontools Run Scripts

We create these inside the qmail tree and symlink them later into /service

mkdir -p /var/qmail/supervise/qmail-send/log
mkdir -p /var/qmail/supervise/qmail-smtpd/log

Qmail-send and logging

/var/qmail/supervise/qmail-send/run

#!/bin/sh
QMAIL=qmail
exec /var/$QMAIL/rc

/var/qmail/supervise/qmail-send/log/run

#!/bin/sh
QMAIL=qmail
exec /usr/local/bin/setuidgid qmaill /usr/local/bin/multilog t s16777215 n50 /var/log/$QMAIL

NOTE: ssize and nnum default to 99999 bytes and 10 log files so adjust accordingly

Qmail-smtpd and logging

/var/qmail/supervise/qmail-smtpd/run

#!/bin/sh
QMAIL=qmail
TCPRULES=tcp.smtp

QMAILDUID=`id -u qmaild`
NOFILESGID=`id -g qmaild`
MAXSMTPD=`cat /var/qmail/control/concurrencyincoming`
LOCAL=`head -1 /var/qmail/control/me`

if [ -z "$QMAILDUID" -o -z "$NOFILESGID" -o -z "$MAXSMTPD" -o -z "$LOCAL" ]; then
	echo QMAILDUID, NOFILESGID, MAXSMTPD, or LOCAL is unset in
	echo /var/$QMAIL/supervise/qmail-smtpd/run
	exit 1
fi

if [ ! -f /var/$QMAIL/control/rcpthosts ]; then
	echo "No /var/$QMAIL/control/rcpthosts!"
	echo "Refusing to start SMTP listener because it'll create an open relay"
	exit 1
fi

exec /usr/local/bin/softlimit -m 8000000 \
	/usr/local/bin/tcpserver -v -R -l "$LOCAL" -x /etc/$TCPRULES.cdb -c "$MAXSMTPD" \
		-u "$QMAILDUID" -g "$NOFILESGID" 0 25 /var/$QMAIL/bin/qmail-smtpd 2>&1

NOTE: This script is typically all that is changed between qmail instances, as we may need to setup RBLs or run a different service.

NOTE: concurrencyincoming is not a standard control file

/var/qmail/supervise/qmail-smtpd/log/run

#!/bin/sh
QMAIL=qmail
exec /usr/local/bin/setuidgid qmaill /usr/local/bin/multilog t s16777215 n50 /var/log/$QMAIL/smtpd

Final tasks

Create the concurrencyincoming control file to limit the number of concurrent smtp sessions:

echo 20 > /var/qmail/control/concurrencyincoming
chmod 644 /var/qmail/control/concurrencyincoming

Make the supervise run scripts executable:

chmod 755 /var/qmail/supervise/qmail-send/run
chmod 755 /var/qmail/supervise/qmail-send/log/run
chmod 755 /var/qmail/supervise/qmail-smtpd/run
chmod 755 /var/qmail/supervise/qmail-smtpd/log/run

Create the log directories:

mkdir -p /var/log/qmail/smtpd
chown qmaill /var/log/qmail /var/log/qmail/smtpd

Link the supervise directories into the deamontools /service directory:

ln -s /var/qmail/supervise/qmail-send /var/qmail/supervise/qmail-smtpd /service
qmailctl stop

NOTE: We issue a qmailctl stop because services begin as soon as the symlinks are created.


Tcpserver config

The final bit of initial configuration is the tcpserver configuration. Variables set here can impact more deeply than just the qmail-smtpd process depending on the feature concerned. This initial configuration allows localhost and our local network to relay (send emails to other domains).

/etc/tcp.smtp

127.:allow,RELAYCLIENT=""
10.137.:allow,RELAYCLIENT=""

Our qmail-smtpd run script expects to find a cdb created from this file:

qmailctl cdb

TODO http://toaster.godshell.com/index.php/Software/FakeMTA



I: Resources