User Tools

Site Tools


postfix:postvirt

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).

Postfix

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.

Database

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

pam

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:

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

Squeeze

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

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

Configure postfix for sasl and TLS

# 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

Virtual user mailboxes

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.

courier-authdaemond

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

courier-imap

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
postfix/postvirt.txt · Last modified: 2013/03/08 04:07 by pyro