Installing and configuring Postfix

This article opens a series of articles about setting up a mail server. The whole story starts with the configuration of the so called mail transfer agent (MTA). The holy of holies and the main service that will send mail from the server, accept incoming mail and put it into the right places.

I will look at configuring Postfix with Mysql on CentOS.

First install the packages we need

yum install postfix postfix-mysql mysql-server openssl

Enable them at boot and start them:

chkconfig mysqld on  
chkconfig postfix on  
/etc/init.d/mysqld start  
/etc/init.d/postfix start
  1. The first step is creating the database that will hold the information about all the mailboxes, the mail users and their passwords:
mysql -uroot -p  
create database mail;  
grant all privileges on mail.* to postfix@localhost identified by 'password';

Next create the tables in the new database:

use mail;
Admin table:
CREATE TABLE `admin` (
  `username` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
alias table:
CREATE TABLE `alias` (
  `address` varchar(255) NOT NULL,
  `goto` text NOT NULL,
  `domain` varchar(255) NOT NULL,
  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`address`),
  KEY `domain` (`domain`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
alias_domain table
CREATE TABLE `alias_domain` (
  `alias_domain` varchar(255) NOT NULL,
  `target_domain` varchar(255) NOT NULL,
  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`alias_domain`),
  KEY `active` (`active`),
  KEY `target_domain` (`target_domain`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
config table:
CREATE TABLE `config` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL DEFAULT '',
  `value` varchar(20) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
domain table:
CREATE TABLE `domain` (
  `domain` varchar(255) NOT NULL,
  `description` varchar(255) CHARACTER SET utf8 NOT NULL,
  `aliases` int(10) NOT NULL DEFAULT '0',
  `mailboxes` int(10) NOT NULL DEFAULT '0',
  `maxquota` bigint(20) NOT NULL DEFAULT '0',
  `quota` bigint(20) NOT NULL DEFAULT '0',
  `transport` varchar(255) NOT NULL,
  `backupmx` tinyint(1) NOT NULL DEFAULT '0',
  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`domain`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
domain_admins table
CREATE TABLE `domain_admins` (
  `username` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT '1',
  KEY `username` (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
fetchmail table:
CREATE TABLE `fetchmail` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `mailbox` varchar(255) NOT NULL,
  `src_server` varchar(255) NOT NULL,
  `src_auth` enum('password','kerberos_v5','kerberos','kerberos_v4','gssapi','cram-md5','otp','ntlm','msn','ssh','any') DEFAULT NULL,
  `src_user` varchar(255) NOT NULL,
  `src_password` varchar(255) NOT NULL,
  `src_folder` varchar(255) NOT NULL,
  `poll_time` int(11) unsigned NOT NULL DEFAULT '10',
  `fetchall` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `keep` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `protocol` enum('POP3','IMAP','POP2','ETRN','AUTO') DEFAULT NULL,
  `usessl` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `extra_options` text,
  `returned_text` text,
  `mda` varchar(255) NOT NULL,
  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
log table:
CREATE TABLE `log` (
  `timestamp` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `username` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  `action` varchar(255) NOT NULL,
  `data` text NOT NULL,
  KEY `timestamp` (`timestamp`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
mailbox table:
CREATE TABLE `mailbox` (
  `username` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 NOT NULL,
  `maildir` varchar(255) NOT NULL,
  `quota` bigint(20) NOT NULL DEFAULT '0',
  `local_part` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`username`),
  KEY `domain` (`domain`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
quota table (not sure it is needed):
CREATE TABLE `quota` (
  `username` varchar(255) NOT NULL,
  `path` varchar(100) NOT NULL,
  `current` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`username`,`path`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
quota2 table (not sure it is needed):
CREATE TABLE `quota2` (
  `username` varchar(100) NOT NULL,
  `bytes` bigint(20) NOT NULL DEFAULT '0',
  `messages` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
transport table:
CREATE TABLE `transport` (
  `domain` varchar(128) NOT NULL DEFAULT '',
  `transport` varchar(128) NOT NULL DEFAULT '',
  UNIQUE KEY `domain` (`domain`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
vacation table:
CREATE TABLE `vacation` (
  `email` varchar(255) NOT NULL,
  `subject` varchar(255) CHARACTER SET utf8 NOT NULL,
  `body` text CHARACTER SET utf8 NOT NULL,
  `cache` text NOT NULL,
  `domain` varchar(255) NOT NULL,
  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `active` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`email`),
  KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Virtual Vacation';
vacation_notification table
CREATE TABLE `vacation_notification` (
  `on_vacation` varchar(255) CHARACTER SET latin1 NOT NULL,
  `notified` varchar(255) CHARACTER SET latin1 NOT NULL,
  `notified_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`on_vacation`,`notified`),
  CONSTRAINT `vacation_notification_pkey` FOREIGN KEY (`on_vacation`) REFERENCES `vacation` (`email`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Virtual Vacation Notifications';

Create the admin user:

INSERT INTO `admin` VALUES ('admin@localhost','$1$9fef2610$C4gS50VrdIXb0D.JEAyCK/','0000-00-00 00:00:00','0000-00-00 00:00:00',1);

Put the information about the admin user into the domain_admins table:

INSERT INTO `domain_admins` VALUES ('admin@localhost','ALL','0000-00-00 00:00:00',1);

Not sure this step is needed:

INSERT INTO `domain` VALUES ('ALL',`,0,0,0,0,`,0,'0000-00-00 00:00:00','0000-00-00 00:00:00',1);

An example of an insert into the transport table: INSERT INTO `transport` VALUES ('server.com','smtp:[mx.mailserver2.com]');

  1. Create the user that will work with the mail files:
useradd -r -u 1150 -g mail -d /var/vmail -s /sbin/nologin -c Virtual vmail

2.1. Make it the owner of the /var/spool/mail folder:

chown -R vmail:mail /var/spool/mail/
  1. Create the /etc/postfix/sql folder and go into it:
mkdir /etc/postfix/sql  
cd /etc/postfix/sql

3.1. Create the valias.cf file with the following contents:

valias.cf
user = postfix
password = password
hosts = 127.0.0.1
dbname = mail
table = alias
select_field = goto
where_field = address
#additional_conditions = active = 1
# query examples:
#query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
#query = SELECT CONCAT(domain,'/',maildir) FROM mailbox WHERE username='%s'AND active = '1'

3.2. Create the vdomains.cf file with the following contents:

vdomains.cf
user = postfix
password = password
hosts = 127.0.0.1
dbname = mail
table = domain
select_field = domain
where_field = domain
#additional_conditions = and backupmx = 0 and active = 1
# query example
#query = SELECT domain FROM domain WHERE domain='%s' AND backupmx='0' AND active='1'

3.3. Create the vmailbox.cf file with the following contents:

vmailbox.cf
user = postfix
password = password
hosts = 127.0.0.1
dbname = mail
table = mailbox
select_field = CONCAT(domain,'/',maildir)
where_field = username
#additional_conditions = and active = 1
# query example
#query = SELECT CONCAT(domain,'/',maildir) FROM mailbox WHERE username='%s'AND active = '1'
  1. Open the /etc/postfix/main.cf file in your favourite editor and start editing it without mercy:

4.1. Make sure the following line is not commented out::
mynetworks_style = subnet

4.2. Make sure the external and internal ip addresses of the server are present in mynetworks:

mynetworks = </strong>public_ip_address</strong>/32, 127.0.0.0/8, 192.168.0.0/16, 10.0.0.0/8

3.3. Declare the config files for the virtual mailboxes, domains and groups:

virtual_mailbox_domains = mysql:$config_directory/sql/vdomains.cf
virtual_mailbox_maps = mysql:$config_directory/sql/vmailbox.cf
virtual_alias_maps = mysql:$config_directory/sql/valias.cf
virtual_mailbox_base = /var/spool/mail
home_mailbox = Maildir/

4.4. Configure smtp authorization shared with pop3 (dovecot):

smtpd_sasl_auth_enable = yes
smtpd_sasl_exceptions_networks = $mynetworks
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
smtpd_sasl_path = /var/spool/postfix/private/dovecot-auth

4.5. Point to the user that works with the mail files (created in step 2). Specify the mail transport:

virtual_minimum_uid = 1150
virtual_uid_maps = static:1150
virtual_gid_maps = static:12
virtual_transport = dovecot

4.6. Configure the restrictions for access to the mail service:

smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, reject_non_fqdn_recipient, reject_unverified_recipient, permit

4.7 Enable ssl/tls if you need it:

smtpd_use_tls = yes
smtpd_tls_cert_file = /etc/ssl/certs/server.crt
smtpd_tls_key_file = /etc/ssl/certs/server.key
smtpd_sasl_auth_enable = yes
broken_sasl_auth_clients = yes
smtpd_tls_received_header = yes
smtpd_tls_ask_ccert = yes
smtpd_tls_loglevel = 1
tls_random_source = dev:/dev/urandom

4.7.1. Generate the ssl certificate:

mkdir /etc/ssl/certs/  
cd /etc/ssl/certs/  
openssl genrsa -out server.key 2048  
openssl req -new -key server.key -out server.csr -nodes  
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

4.7.2. If needed, convert them to pem:

openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt  
openssl pkcs12 -in server.pfx -out server.pem -nodes

Save. Close.

  1. Open the /etc/postfix/master.cf file in your favourite editor and start editing it without mercy:
    5.1. Make sure the following lines are present:
submission inet n       -       n       -       -       smtpd
		-o smtpd_tls_security_level=encrypt
		-o smtpd_sasl_auth_enable=yes

4.9. Allow delivery into dovecot:

dovecot unix - n n - - pipe
		-o flags=DRhu user=vmail:mail argv=/usr/libexec/dovecot/deliver -f ${sender} -d ${recipient}

Save. Close.

Restart postfix so the changes take effect:

/etc/init.d/postfix restart

Read on: Installing and configuring an imap/pop3 service based on Dovecot