This is a simple explaination of a virtual email server supporting tls and imap with a PostgreSQL database and using postfix.
Roughly, this can be divided into several logical steps
Initialize the database Configure basic postfix configure postfix for virtual hosting using the postgres database tables install sasl2-bin and configure saslauthd configure postfix for TLS and sasl auth install courier-imap and courier-authdaemon for imap services
TLS authentication Postfix depends on the sasl system to authenticate TLS sessions in order to permit relaying on a user by user basis. By default, libsasl2 uses /etc/sasldb for that purpose, but sasldb isn't very friendly towards automated systems (or at all).
Fortunately, it can also be configured to use saslauthd. In turn, Saslauthd can use the smtp service of pam. Finally, pam can use libpam-pgsql to access the postgres database. So, it's postfix→saslauthd→pam→libpam-pgsql→database
IMAP authentication
On the other side, we have the courier-imap daemon. It uses courier-authdaemon to authenticate users and determine where the maildir is located. In turn, courier-authdaemon supports lookups in a postgres database table (in fact, the same one we configure pam smtp to use).
apt-get install postfix-tls postfix-pgsql
postfix-tls is a virtual package that pulls in a few pieces postfix needs as well. postfix-pgsql will be needed for virtual mappings.
For now, we just want to configure the basics such as a domainname (to avoid looking spammy, this should be a real domain, but may be handled as a virtual). Consult postfix.org for details.
First the database
apt-get install postgresql apt-get install python-pgsql
Get in to postgres as the superuser:
su - postgres psql
CREATE DATABASE postfix WITH TEMPLATE = template0 ENCODING = 'SQL_ASCII';
Access to the database is split up amongst multiple postgres users (not necessarily system users). This is in support of the principlee of least privilege and need to know. That is, no subsystem is allowed access to information it doesn't need and especially isn't allowed to change information unless it has a real need.
For postfix itself:
create user postfix with password 'pass';
Cherryhost is the mailadmin:
create user cherryhost with password 'pass';
Chadmin is the mail domain admin
create user chadmin with password 'pass';
Pam is used by saslauthd to authenticate users for TLS.
create user pam with password 'pam';
Courier-authdaemon provides login and mailbox location for imap and pop3
create user courier with password 'pass';
Switch to the postfix database to create the tables:
\c postfix
The main table is passwd. It contains the fields needed to identify the user, authenticate with a password, locate the mailbox, and the domain the user's mailbox lives in
CREATE TABLE passwd ( id character varying(128), clear character varying(128), crypt character varying(128), uid integer, gid integer, home character varying(255), maildir character varying(255), name character varying, mbox character varying, "domain" character varying );
Now, the rights to the table. Cherryhost has all modify rights to the table. The courier-authdaemon is granted SELECT only.
REVOKE ALL ON TABLE passwd FROM PUBLIC; GRANT INSERT,SELECT,UPDATE,DELETE ON TABLE passwd TO cherryhost; GRANT SELECT ON TABLE passwd TO courier;
We create a view of the table presenting only the fields that pam needs to authenticate.
create VIEW pwdb AS SELECT passwd.id, passwd.clear, passwd.crypt from passwd;
And grant the needed rights to pam:
REVOKE ALL ON TABLE pwdb FROM PUBLIC; GRANT SELECT ON TABLE pwdb TO pam;
Now, a view for postfix to use for locating real mailboxes in virtual domains.
CREATE VIEW vmbox AS SELECT passwd.id, passwd.mbox FROM passwd;
And permissions to it.
REVOKE ALL ON TABLE vmbox FROM PUBLIC; GRANT SELECT ON TABLE vmbox TO postfix;
A table for virtual forwarding. That is, aliases with no login or mailbox on the system.
CREATE TABLE virtuals ( recipiant character varying, destination character varying, "domain" character varying );
Again, cherryhost has all rights. Postfix has select on the table.
REVOKE ALL ON TABLE virtuals FROM PUBLIC; GRANT SELECT ON TABLE virtuals TO postfix; GRANT INSERT,SELECT,UPDATE,DELETE ON TABLE virtuals TO cherryhost;
Finally, the tables for cherryhost itself:
CREATE TABLE domain_admins ( uname text, domain text );
CREATE TABLE mail_admins ( uname text, pass text );
GRANT INSERT,DELETE,UPDATE,SELECT on domain_admins,mail_admins to chadmin; GRANT SELECT on domain_admins, mail_admins to cherryhost;
Note that since several componants of the system run in a chroot environment, postgres MUST be configured to allow connections on localhost with md5 authentication.
Edit your pg_hba.conf (which can be in several different places, use locate or check your documentation) to include:
host all all 127.0.0.1 255.255.255.255 md5
Install the pam module for PostgreSQL databases:
apt-get install libpam-pgsql
Due to package updates, configuration of PAM_pgsql is different for Squeeze than for Etch and Lenny:
Create a file /etc/pam.d/smtp
:
auth required pam_pgsql.so debug user=pam password=pam host=127.0.0.1 database=postfix table=pwdb user_column=id pwd_column=clear pw_type=clear account sufficient pam_pgsql.so debug user=pam password=pam host=127.0.0.1 database=postfix table=pwdb user_column=id pwd_column=clear pw_type=clear
Create file /etc/pam_pgsql.conf:
database = postfix host = localhost user = pam password = pam table = pwdb user_column = id pwd_column = clear pw_type=clear acct_query=select False, False, False expired_column = acc_expired newtok_column = acc_new_pwreq debug = 1
Then, create a file /etc/pam.d/smtp
:
auth required pam_pgsql.so account sufficient pam_pgsql.so
While it would be preferable to only expose the user password in encrypted form, the pam module currently doesn't work with the md5() PostgreSQL function.
WARNING: be sure to set permissions on this file correctly since it contains a sensitive password!
chown root.root /etc/pam.d/smtp; chmod o= /etc/pam.d/smtp
This service will be used by saslauthd
apt-get install sasl2-bin libsasl2-modules
edit /etc/default/saslauthd
.
START=yes MECHANISMS="pam" OPTIONS="-c -r -m /var/spool/postfix/var/run/saslauthd"
The options include -r to add the realm to the username when authenticating it with PAM ( that is user@example.com
rather than just user
). -m places the unix socket inside postfix's chroot so that it can access it.
Add postfix to the sasl group (in /etc/group) to grant it access.
sasl:x:45:postfix
# TLS parameters smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key smtpd_use_tls=yes smtpd_tls_session_cache_database = btree:${queue_directory}/smtpd_scache smtp_tls_session_cache_database = btree:${queue_directory}/smtp_scache smtpd_sasl_auth_enable = yes smtpd_sasl2_auth_enable = yes smtpd_sasl_local_domain = smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination smtpd_sasl_authenticated_header = yes smtpd_sasl_application_name = smtpd #smtpd_sasl_path = smtpd broken_sasl_auth_clients = yes smtpd_sasl_security_options = noanonymous
Create /etc/postfix/sasl/smtpd.conf
:
pwcheck_method: saslauthd #mech_list: PLAIN CRAM-MD5 DIGEST-MD5 LOGIN mech_list: PLAIN DIGEST-MD5 LOGIN #allowanonymouslogin: no
Now to set up the virtual domains.
mkdir /var/spool/mail/virtual
chown 5000.5000 /var/spool/mail/virtual
chmod g+s /var/spool/mail/virtual
Add the following to /etc/postfix/main.cf
virtual_alias_maps = pgsql:/etc/postfix/pgsql-virtual.cf, pgsql:/etc/postfix/pgsql-virtual_allow_real.cf virtual_mailbox_base = /var/spool/mail/virtual virtual_mailbox_maps = pgsql:/etc/postfix/pgsql-mbox.cf virtual_mailbox_domains = /etc/postfix/virtual_domains virtual_minimum_uid = 100 virtual_uid_maps = static:5000 virtual_gid_maps = static:5000
Then create the postgresql map configuration files. /etc/postfix/pgsql-mbox.cf
:
user = postfix password = pass dbname = postfix hosts = 127.0.0.1 query = SELECT mbox FROM vmbox where id = '%s'
/etc/postfix/pgsql-virtual.cf
:
user = postfix password = pass dbname = postfix hosts = 127.0.0.1 query = SELECT destination FROM virtuals where recipiant = '%s'
/etc/postfix/pgsql-virtual_allow_real.cf
:
user = postfix password = pass dbname = postfix hosts = 127.0.0.1 query = SELECT id FROM vmbox where id = '%s'
WARNING: be sure to set permissions on these files correctly since they contain a sensitive password!
chown root.postfix /etc/postfix/pgsql-*; chmod o= /etc/postfix/pgsql-*
The virtual alias maps line calls for a bit of discussion:
virtual_alias_maps = pgsql:/etc/postfix/pgsql-virtual.cf, pgsql:/etc/postfix/pgsql-virtual_allow_real.cf
This tells postfix to perform 2 queries for user aliases. The first, as we might expect queries the virtuals table for a mapping from virtual@example.com to real@somedomain.com (possibly example.com). However, if a virtual_alias lookup returns nothing, postfix will then search for the catchall address '@example.com'. If one exists (often a highly desirable situation), it will also match real users. As a result, real@example.com will be delivered to the catchall address rather than the real user's mailbox!
To fix that, we add a second 'identity' lookup against the real mailboxes. because of that, postfix will find an identity mapping for real mailboxes (real@example.com = real@example.com) before it tries the catchall. Luckily it's smart enough to understand that an identity match means it should deliver to the real mailbox.
without this little hack, catchalls cannot be allowed and the real sysadmin will get stuck with all of the bounced crap for all of the virtual domains.
With all of that done, restart postfix:
/etc/init.d/postfix restart
We're almost there. Now all we have to do is give the users a way to check their mail.
apt-get install courier-authlib courier-authlib-postgresql courier-authdaemon
create /etc/courier/authpgsqlrc
:
PGSQL_HOST localhost PGSQL_PORT 5432 PGSQL_USERNAME courier PGSQL_PASSWORD pass PGSQL_DATABASE postfix PGSQL_USER_TABLE passwd DEFAULT_DOMAIN example.com PGSQL_UID_FIELD uid PGSQL_GID_FIELD gid PGSQL_LOGIN_FIELD id PGSQL_HOME_FIELD home PGSQL_NAME_FIELD name PGSQL_MAILDIR_FIELD maildir
WARNING: be sure to set permissions on this file correctly since it contains a sensitive password!
chown root.root /etc/courier/authpgsqlrc; chmod o= /etc/courier/authpgsqlrc
Edit /etc/courier/authdaemonrc
to use the pgsql method:
authmodulelist="authpgsql"
(Re)start the authdaemon:
/etc/init.d/courier-authdaemon restart
apt-get install courier-imap courier-pop courier-imap-ssl courier-pop-ssl
Even if you don't care about imaps and pop3s service, install the -ssl packages as well. That is the easiest way to get self-signed certs created so the non-ssl versions can do TLS.
Happily, the defaults work just fine
/etc/init.d/courier-imap restart /etc/init.d/courier-pop restart