Install packages

The setup needs a whole load of packages to work. Either install them separately, or do it in a single command line.

Postfix

apt-get install postfix postfix-mysql postfix-doc

Configuration options:

General type of configuration? <-- Internet site
System mail name: <-- hostname.domain.tld

OpenSSL

apt-get install openssl

General Setup

vmail user

There’s an entry for it in master.cf, but it seems the vmail user doesn’t actually get created.

useradd vmail -d /var/vmail -m -s /bin/false

Which will create the root folder under which all the mail will be stored as well as setting its ownership appropriately.

MySQL tables

User to access the Postfix tables:

CREATE USER 'postfix'@'localhost' IDENTIFIED BY 'password';
CREATE DATABASE 'postfix';
GRANT SELECT , INSERT , UPDATE , DELETE ON `postfix` . * TO 'postfix'@'localhost';
FLUSH PRIVILEGES;

MySQL queries to create required tables for Postfix:

USE postfix;

 CREATE TABLE postfix_alias (
  id int(11) unsigned NOT NULL auto_increment,
  alias varchar(128) NOT NULL default '',
  destination varchar(128) NOT NULL default '',
  PRIMARY KEY (id)
  UNIQUE KEY alias (alias)
 ) TYPE=MyISAM;
 
 CREATE TABLE postfix_relocated (
  id int(11) unsigned NOT NULL auto_increment,
  email varchar(128) NOT NULL default '',
  destination varchar(128) NOT NULL default '',
  PRIMARY KEY (id)
 ) TYPE=MyISAM;
 
 CREATE TABLE postfix_transport (
  id int(11) unsigned NOT NULL auto_increment,
  domain varchar(128) NOT NULL default '',
  destination varchar(128) NOT NULL default '',
  PRIMARY KEY (id),
  UNIQUE KEY domain (domain)
 ) TYPE=MyISAM;
 
 CREATE TABLE postfix_users (
  id int(11) unsigned NOT NULL auto_increment,
  email varchar(128) NOT NULL default '',
  clear varchar(128) NOT NULL default '',
  crypt varchar(128) NOT NULL default '',
  name tinytext NOT NULL,
  uid int(11) unsigned NOT NULL default '1001',
  gid int(11) unsigned NOT NULL default '1001',
  homedir tinytext NOT NULL,
  maildir tinytext NOT NULL,
  quota tinytext NOT NULL,
  access enum('Y','N') NOT NULL default 'Y',
  postfix enum('Y','N') NOT NULL default 'Y',
  PRIMARY KEY (id),
  UNIQUE KEY email (email)
 ) TYPE=MyISAM;
 
 CREATE TABLE postfix_virtual (
  id int(11) unsigned NOT NULL auto_increment,
  email varchar(128) NOT NULL default '',
  destination varchar(128) NOT NULL default '',
  PRIMARY KEY (id)
 ) TYPE=MyISAM;
 
 CREATE TABLE postfix_access (
  id int(10) unsigned NOT NULL auto_increment,
  source varchar(128) NOT NULL default '',
  access varchar(128) NOT NULL default '',
  type enum('recipient','sender','client') NOT NULL default 'recipient',
  PRIMARY KEY (id)
 ) TYPE=MyISAM;

The purposes of the tables used here is explained in postfix SQL tables.

Postfix

/etc/postfix/main.cf:

mydestination = hostname.domain.tld, localhost.domain.tld, localhost, $transport_maps

local_recipient_maps = $virtual_alias_maps $virtual_mailbox_maps

transport_maps = mysql:/etc/postfix/mysql-transport.cf

virtual_alias_maps = mysql:/etc/postfix/mysql-aliases.cf
virtual_mailbox_base = /var/vmail
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-maps.cf
virtual_transport = virtual
virtual_uid_maps = static:1001
virtual_gid_maps = static:1001
virtual_minimum_uid = 1001

smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_hostname, reject_non_fqdn_sender, reject_non_fqdn_recipient, reject_unauth_destination, reject_unauth_pipelining, reject_invalid_hostname

smtpd_sasl_auth_enable = yes
smtpd_sasl_local_domain = $myhostname
smtpd_sasl_security_options = noanonymous
smtpd_use_tls = yes
smtpd_tls_cert_file = /etc/postfix/smtpd.cert
smtpd_tls_key_file = /etc/postfix/smtpd.key

[http://www.postfix.org/postconf.5.html#virtual_maps virtual_maps] is deprecated in favour of separate virtual_alias_domains and virtual_alias_maps controls.

Mysql files

Create the following in /etc/postfix:

mysql-aliases.cf

# mysql-aliases.cf
user = postfix
password = password
dbname = postfix
table = postfix_alias
select_field = destination
where_field = alias
hosts = 127.0.0.1

mysql-relocated.cf

# mysql-relocated.cf
user = postfix
password = password
dbname = postfix
table = postfix_relocated
select_field = destination
where_field = email
hosts = 127.0.0.1

mysql-transport.cf

# mysql-transport.cf
user = postfix
password = password
dbname = postfix
table = postfix_transport
select_field = destination
where_field = domain
hosts = 127.0.0.1

mysql-virtual.cf

# mysql-virtual.cf
user = postfix
password = password
dbname = postfix
table = postfix_virtual
select_field = destination
where_field = email
hosts = 127.0.0.1

mysql-recipient.cf

# mysql-recipient.cf
user = postfix
password = password
dbname = postfix
table = postfix_access
select_field = access
where_field = source
additional_conditions = and type = 'recipient'
hosts = 127.0.0.1

mysql-sender.cf

# mysql-sender.cf
user = postfix
password = password
dbname = postfix
table = postfix_access
select_field = access
where_field = source
additional_conditions = and type = 'sender'
hosts = 127.0.0.1

mysql-client.cf

# mysql-client.cf
user = postfix
password = password
dbname = postfix
table = postfix_access
select_field = access
where_field = source
additional_conditions = and type = 'client'
hosts = 127.0.0.1

mysql-virtual-maps.cf

# mysql-virtual-maps.cf
user = postfix
password = password
dbname = postfix
table = postfix_users
select_field = maildir
where_field = email
additional_conditions = and postfix = 'y'
hosts = 127.0.0.1

mysql-virtual-uid.cf

# mysql-virtual-uid.cf
user = postfix
password = password
dbname = postfix
table = postfix_users
select_field = uid
where_field = email
additional_conditions = and postfix = 'y'
hosts = 127.0.0.1

mysql-virtual-gid.cf

# mysql-virtual-gid.cf
user = postfix
password = password
dbname = postfix
table = postfix_users
select_field = gid
where_field = email
additional_conditions = and postfix = 'y'
hosts = 127.0.0.1

Create a user

 INSERT INTO postfix_transport (domain,destination) VALUES ("domain.tld", "virtual:");
 INSERT INTO postfix_users (email, clear, homedir, maildir) VALUES ("user@domain.tld", "password", "/var/vmail", "domain.tld/user/Maildir/");

The maildir record in postfix_users must end with a forward slash to make Postfix work with a Maildir.

Testing

SMTP

Valid user:

telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 example.domain.tld ESMTP Postfix (Debian/GNU)
EHLO localhost
250-example.domain.tld
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-AUTH PLAIN CRAM-MD5 LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
MAIL FROM: test@localhost
250 2.1.0 Ok
rcpt to: user@domain.tld
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
This is a test
.
250 2.0.0 Ok: queued as 720554B082
quit
221 2.0.0 Bye

Invalid user:

telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 example.domain.tld ESMTP Postfix (Debian/GNU)
EHLO localhost
250-example.domain.tld
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-AUTH PLAIN CRAM-MD5 LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
MAIL FROM: root@localhost
250 2.1.0 Ok
RCPT TO: nonexistanttestuser@domain.tld
550 5.1.1 <nonexistanttestuser@domain.tld>: Recipient address rejected: User unknown in local recipient table
quit
221 2.0.0 Bye

IMAP

telnet localhost 143
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
* OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION STARTTLS] Courier-IMAP ready. Copyright 1998-2005 Double Precision, Inc.  See COPYING for distribution information.
a login user@domain.tld password
a OK LOGIN Ok.
a examine inbox
* FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
* OK [PERMANENTFLAGS ()] No permanent flags permitted
* 1 EXISTS
* 1 RECENT
* OK [UIDVALIDITY 1200873832] Ok
* OK [MYRIGHTS "acdilrsw"] ACL
a OK [READ-ONLY] Ok
a logout
* BYE Courier-IMAP server shutting down
a OK LOGOUT completed

Troubleshooting

The Courier-MTA site has commands required to manually perform a login with IMAP/POP3, with and without SSL.