Postfix Admin configuration

1. Requirements

    Note: If you can not install Postfixadmin, you have to create required database and tables manually! (See Appendix 2)

2. References

3. Postfix installation

Create a new jail (e.g. name mail) and add storage to the jail so we can keep our e-mails in a secure place. I'll mount my storage in /mnt/mail inside the jail.

SSH to your FreeNAS, enter the jail (jexec mail tcsh) and install postfix. We have to build postfix using portsnap and configure it with PostgreSQL support. When postfix will be installed answer 'yes' to the question 'Would you like to activate Postfix in /etc/mail/mailer.conf [n]?'

pkg install perl5 pcre postgresql94-client
portsnap fetch
portsnap extract
cd /usr/ports/mail/postfix/
make config
# for postfix configuration select:
# set PGSQL=on: PostgreSQL maps (uses DEFAULT_PGSQL_VER)
# set TLS=on: SSL and TLS support
# set DOVECOT2=on: Dovecot 2.x SASL authentication method
# set VDA=on: VDA (Virtual Delivery Agent 32Bit)
#
# for dovecot2 configuration select:
# set PGSQL=on: PostgreSQL maps (uses DEFAULT_PGSQL_VER)
# set SSL=on: SSL protocol support
make install clean
pkg install dovecot-pigeonhole

To enable postfix to starts automatically during start of the jail and disable sendmail update the file '/etc/rc.conf'.

postfix_enable="YES"

sendmail_enable="NO"
sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"

You can disable some sendmail specific daily maintenance routines in your '/etc/periodic.conf' file. If the file does not exist please create it and add values:

cat << EOF >> /etc/periodic.conf
daily_clean_hoststat_enable="NO"
daily_status_mail_rejects_enable="NO"
daily_status_include_submit_mailq="NO"
daily_submit_queuerun="NO"
EOF

4. Postfix configuration

Create and secure the SMTP SSL certificate (or use your existing one).

mkdir -p /etc/ssl/postfix
cd /etc/ssl/postfix
openssl req -new -x509 -nodes -out smtpd.pem -keyout smtpd.pem -days 3650
chmod 640 /etc/ssl/postfix/*
chgrp -R postfix /etc/ssl/postfix

Next edit the file '/usr/local/etc/postfix/main.cf' and add/update variable mentioned below:

...
# SOFT BOUNCE
#
# The soft_bounce parameter provides a limited safety net for
# testing.  When soft_bounce is enabled, mail will remain queued that
# would otherwise bounce. This parameter disables locally-generated
# bounces, and prevents the SMTP server from rejecting mail permanently
# (by changing 5xx replies into 4xx replies). However, soft_bounce
# is no cure for address rewriting mistakes or mail routing mistakes.

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,
  reject_rbl_client bl.spamcop.net

smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks

virtual_mailbox_base = /mnt/mail
virtual_mailbox_maps = pgsql:/usr/local/etc/postfix/pgsql_virtual_mailbox_maps.cf
virtual_mailbox_domains = pgsql:/usr/local/etc/postfix/pgsql_virtual_mailbox_domains.cf
virtual_alias_maps = pgsql:/usr/local/etc/postfix/pgsql_virtual_alias_maps.cf
local_recipient_maps = $virtual_mailbox_maps $virtual_alias_maps
relay_domains = pgsql:/usr/local/etc/postfix/pgsql_relay_domains.cf

# use static uid:gid, dynamic can caused permission related problems
virtual_uid_maps = static:26
virtual_gid_maps = static:6

smtpd_delay_reject = yes
smtpd_helo_required = yes

mailbox_command = /usr/lib/dovecot/deliver-lda -f "$SENDER" -a "$RECIPIENT"
virtual_transport = dovecot
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes

smtp_use_tls = yes
smtp_tls_note_starttls_offer = yes

smtpd_use_tls = yes
smtpd_tls_auth_only = yes
smtpd_tls_key_file = /etc/ssl/postfix/smtpd.pem
smtpd_tls_cert_file = /etc/ssl/postfix/smtpd.pem
smtpd_tls_CAfile = /etc/ssl/postfix/smtpd.pem
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s

tls_random_source = dev:/dev/urandom

...
myhostname = mail.example.com
...
mydomain = example.com
...
myorign = $mydomain
...

In the file '/usr/local/etc/postfix/master.cf' uncomment these lines. 'Message_size_limit' will change message size limit from 10M (default) to 25M.

smtps     inet  n       -       n       -       -       smtpd
    -o smtpd_tls_wrappermode=yes
    -o smtpd_sasl_auth_enable=yes
    -o smtpd_client_restrictions=permit_sasl_authenticated,reject
    -o message_size_limit=26214400

dovecot    unix -        n       n       -       -       pipe
  flags=DRhu user=mailnull:mail argv=/usr/local/libexec/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -m ${extension}

Create files below to query defined MX domains and e-mail address from database storage. As hosts you can use IP address, FQDN or path to PostgreSQL socket.

cat << EOF > /usr/local/etc/postfix/pgsql_relay_domains.cf
user = postfix
password = postfix_password
hosts = /tmp
dbname = mail
query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = true
EOF
cat << EOF > /usr/local/etc/postfix/pgsql_virtual_alias_maps.cf
user = postfix
password = postfix_password
hosts = /tmp
dbname = mail
query = SELECT goto FROM alias WHERE address='%s' AND active = true
EOF
cat << EOF > /usr/local/etc/postfix/pgsql_virtual_mailbox_domains.cf
user = postfix
password = postfix_password
hosts = /tmp
dbname = mail
#query = SELECT domain FROM mailbox WHERE domain = '%s' AND active = true
query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = false and active = true
EOF
cat << EOF > /usr/local/etc/postfix/pgsql_virtual_mailbox_limit_maps.cf
user = postfix
password = postfix_password
hosts = /tmp
dbname = mail
query = SELECT quota FROM mailbox WHERE username = '%u' AND domain = '%d' AND active = true
EOF
cat << EOF > /usr/local/etc/postfix/pgsql_virtual_mailbox_maps.cf
user = postfix
password = postfix_password
hosts = /tmp
dbname = mail
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = true
EOF

Secure postfix PostgreSQL files.

chmod 640 /usr/local/etc/postfix/pgsql_*
chgrp postfix /usr/local/etc/postfix/pgsql_*

Create your virtual mail directory, if you can not use mounted storage (see step 3), where e-mails will be storred. The directory was defined in the file '/usr/local/etc/postfix/main.cf' as 'virtual_mailbox_base = /mnt/mail'.

mkdir -p /mnt/mail
chown mailnull:mail /mnt/mail

5. Start and check postfix service

Start postfix service

service postfix start

Now you can test SSL connection by command:

openssl s_client -connect smtp.example.com:smtps

And SMTP connection by command:

echo test | mail postmaster@example.com

Appendix 1. MX DNS configuration

For correct sending and receiving e-mails to/from other mail server you have to set up proper DNS records of your mail server. Correct DNS forward and reverse (PTR) records are required for domain name and name of the mail server.

DNS records for the domain name of your mail server defined in the variable 'myhostname = mail.example.com' can be:

MX  10 mail.example.com.
mail.example.com. IN A 012.345.678.9

DNS PTR record (in-addr.arpa) can be:

9.678.345.012 IN PTR mail.example.com.
  • Note: Without correct DNS PTR record you will not be able to send e-mails to most of the other mail servers (e.g. Gmail)!

To test yours DNS record you can use these commands:

dig example.com MX
dig mail.example.com ANY
dig -x ip_address_of_mail.example.com

Appendix 2. PostgreSQL database and tables

To create user, database and tables for postfix manually connect to your Postgresql server and execute commands below:

su pgsql
createuser --pwprompt --encrypted --no-createrole --no-createdb postfix
createdb --encoding=UTF8 --owner=postfix mail
psql mail
  • Note: Don't forget to set up authentication file '/mnt/sql/pgsql/data/pg_hba.conf' for user 'postfix' and reload configuration of PostgreSQL.

When you are connected to the mail database, enable crypto module:

-- enable crypto module for crypted passwords
CREATE EXTENSION pgcrypto;

Create alias table:

-- Table: alias
-- DROP TABLE alias;
CREATE TABLE alias
(
  address character varying(255) NOT NULL,
  goto text NOT NULL,
  domain character varying(255) NOT NULL,
  created timestamp with time zone DEFAULT now(),
  modified timestamp with time zone DEFAULT now(),
  active boolean NOT NULL DEFAULT true,
  CONSTRAINT alias_key PRIMARY KEY (address),
  CONSTRAINT alias_domain_fkey FOREIGN KEY (domain)
      REFERENCES domain (domain) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
  OIDS=FALSE
);

ALTER TABLE alias OWNER TO postfix;
GRANT ALL ON TABLE alias TO postfix;

-- Index: alias_address_active
-- DROP INDEX alias_address_active;
CREATE INDEX alias_address_active
  ON alias
  USING btree
  (address COLLATE pg_catalog."default", active);

-- Index: alias_domain_idx
-- DROP INDEX alias_domain_idx;
CREATE INDEX alias_domain_idx
  ON alias
  USING btree
  (domain COLLATE pg_catalog."default");

Create domain table:

-- Table: domain
-- DROP TABLE domain;
CREATE TABLE domain
(
  domain character varying(255) NOT NULL,
  description character varying(255) NOT NULL DEFAULT ''::character varying,
  aliases integer NOT NULL DEFAULT 0,
  mailboxes integer NOT NULL DEFAULT 0,
  maxquota bigint NOT NULL DEFAULT 0,
  quota bigint NOT NULL DEFAULT 0,
  transport character varying(255) DEFAULT NULL::character varying,
  backupmx boolean NOT NULL DEFAULT false,
  created timestamp with time zone DEFAULT now(),
  modified timestamp with time zone DEFAULT now(),
  active boolean NOT NULL DEFAULT true,
  CONSTRAINT domain_key PRIMARY KEY (domain)
)
WITH (
  OIDS=FALSE
);

ALTER TABLE domain OWNER TO postfix;

-- Index: domain_domain_active
-- DROP INDEX domain_domain_active;
CREATE INDEX domain_domain_active
  ON domain
  USING btree
  (domain COLLATE pg_catalog."default", active);

And create mailbox table:

-- Table: mailbox
-- DROP TABLE mailbox;
CREATE TABLE mailbox
(
  username character varying(255) NOT NULL,
  password character varying(255) NOT NULL DEFAULT ''::character varying,
  name character varying(255) NOT NULL DEFAULT ''::character varying,
  maildir character varying(255) NOT NULL DEFAULT ''::character varying,
  quota bigint NOT NULL DEFAULT 0,
  created timestamp with time zone DEFAULT now(),
  modified timestamp with time zone DEFAULT now(),
  active boolean NOT NULL DEFAULT true,
  domain character varying(255),
  local_part character varying(255) NOT NULL,
  CONSTRAINT mailbox_key PRIMARY KEY (username),
  CONSTRAINT mailbox_domain_fkey1 FOREIGN KEY (domain)
      REFERENCES domain (domain) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
  OIDS=FALSE
);

ALTER TABLE mailbox OWNER TO postfix;
GRANT ALL ON TABLE mailbox TO postfix;

-- Index: mailbox_domain_idx
-- DROP INDEX mailbox_domain_idx;
CREATE INDEX mailbox_domain_idx
  ON mailbox
  USING btree
  (domain COLLATE pg_catalog."default");

-- Index: mailbox_username_active
-- DROP INDEX mailbox_username_active;
CREATE INDEX mailbox_username_active
  ON mailbox
  USING btree
  (username COLLATE pg_catalog."default", active);

When all tables are created you can insert new user with aliases for 'example.com' domain:

-- create virtual domain
INSERT INTO domains VALUES ('example.com');

-- create e-mail user
# MD5-CRYPT
#INSERT INTO mailbox VALUES ('root@example.com', crypt('password', gen_salt('md5')), '', '', 0, now(), now(), true, 'example.com', 'root');
# BLF-CRYPT
INSERT INTO mailbox VALUES ('rootexample.com', crypt('password', gen_salt('bf',5)), '', '', 0, now(), now(), true, 'example.com', 'root');
# PLAIN-MD5
#INSERT INTO mailbox VALUES ('root@example.com', md5('password'), '', '', 0, now(), now(), true, 'example.com', 'root');

-- create virtual aliases
INSERT INTO alias VALUES ('postmaster@example.com', 'root@example.com', 'example.com');
INSERT INTO alias VALUES ('abuse@example.com', 'root@example.com', 'example.com');
INSERT INTO alias VALUES ('example@example.com', 'root@example.com', 'example.com');

Comments