Configure A FreeBSD Email Server Using Postfix
Configure A FreeBSD Email Server Using Postfix
Introduction
In this tutorial, we are going to configure a FreeBSD email server with virtual domains and users,
using Postfix, Dovecot, MySQL and SpamAssassin, ClamAV (w/o Amavis), DKIM, DMARC,
CalDAV, CardDAV, Dashboard and RoundCube on FreeBSD 11.0.
1
session php56-simplexml php56-sqlite3 php56-tokenizer php56-xml php56-xmlreader
php56-xmlwriter php56-zip php56-zlib pkg
pkgconf png portmaster postfix postgresql93-client postgresql93-server postgrey
printproto pwgen py27-Babel py27-Jinja2 py27-MarkupSafe py27-alabaster py27-
authres py27-dns py27-docutils py27-fail2ban
py27-imagesize py27-ipaddr py27-postfix-policyd-spf-python py27-pygments py27-
pyspf py27-pystemmer py27-pytz py27-setuptools py27-six py27-snowballstemmer
py27-sphinx py27-sphinx_rtd_theme py27-sqlite3
python2 python27 re2c readline rhash rsync scons screen spamassassin sqlite3
t1lib unzoo vsftpd-ssl xextproto xproto yajl
File: /etc/rc.conf
zfs_enable="YES" # For ZFS filesystems
ntpd_enable="YES"
sshd_enable="YES"
sendmail_enable="NO"
sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"
mysql_enable="YES"
postgresql_enable="YES"
apache24_enable="YES"
postfix_enable="YES"
dovecot_enable="YES"
spamd_enable="YES"
spamd_flags="-d -m5 -x -q -Q -u nobody"
milteropendkim_enable="YES"
milteropendkim_uid="opendkim"
opendmarc_enable="YES"
opendmarc_runas="opendmarc"
clamav_clamd_enable="YES"
clamav_freshclam_enable="YES"
clamav_milter_enable="YES"
loginscript_enable="YES"
File: /usr/local/etc/apache24/httpd.conf
ServerName demo.domain.tld
Execute:
cp /usr/local/etc/php.ini-production /usr/local/etc/php.ini
File: /usr/local/etc/php.ini
date.timezone = UTC
Configure Dovecot
mkdir -p /etc/skel/Maildir/{cur,new,tmp}
mkdir -p /usr/local/vhosts
mkdir -p /usr/local/etc/postfix/keys
2
Create vmail user, group
pw groupadd -n vmail -g 5000
pw adduser -n vmail -c "Virtual mail user" -u 5000 -g 5000 -d /usr/local/vhosts -
s /usr/sbin/nologin
File: /usr/local/etc/dovecot/dovecot.conf
auth_mechanisms = plain login
auth_verbose = yes
default_client_limit = 2560
default_process_limit = 512
dict {
acl = mysql:/usr/local/etc/dovecot/dovecot-dict-sql.conf.ext
quota = mysql:/usr/local/etc/dovecot/dovecot-dict-sql.conf.ext
}
log_path = /var/log/dovecot.log
mail_home = /usr/local/vhosts/mail/%d/%n
mail_location = maildir:/usr/local/vhosts/mail/%d/%n:LAYOUT=fs
mail_max_userip_connections = 20
mail_plugins = quota acl
mail_privileged_group = vmail
mail_shared_explicit_inbox = yes
managesieve_notify_capability = mailto
managesieve_sieve_capability = fileinto reject envelope encoded-character
vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy
include variables body enotify environment mailbox date index ihave duplicate
mime foreverypart extracttext
mbox_write_locks = fcntl
namespace {
inbox = no
list = children
location =
maildir:/usr/local/vhosts/mail/%%d/%%n:LAYOUT=fs:INDEX=/usr/local/vhosts/indexes
/%d/%n/shared/%%u:INDEXPVT=/usr/local/vhosts/indexes/%d/%n/shared/%%u
prefix = shared/%%d/%%n/
separator = /
subscriptions = no
type = shared
}
namespace inbox {
inbox = yes
list = yes
location =
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
auto = subscribe
special_use = \Junk
3
}
mailbox Sent {
auto = subscribe
special_use = \Sent
}
mailbox Trash {
auto = subscribe
special_use = \Trash
}
prefix =
separator = /
type = private
}
passdb {
args = /usr/local/etc/dovecot/dovecot-sql.conf.ext
driver = sql
}
plugin {
acl = vfile
acl_shared_dict = proxy::acl
quota = dict:User quota::proxy::quota
quota_rule2 = Trash:storage=+100M
sieve = /usr/local/vhosts/mail/%d/%n/.dovecot.sieve
sieve_before = /usr/local/vhosts/sieve/before.d/
sieve_dir = /usr/local/vhosts/mail/%d/%n
sieve_global_dir = /usr/local/vhosts/sieve/%d
sieve_global_path = /usr/local/vhosts/sieve/%d/default.sieve
}
protocols = imap lmtp sieve
service auth-worker {
user = vmail
}
service auth {
unix_listener /var/spool/postfix/private/auth {
group = postfix
mode = 0660
user = postfix
}
unix_listener auth-userdb {
mode = 0600
user = vmail
}
user = dovecot
}
service dict {
unix_listener dict {
mode = 0600
user = vmail
}
}
service imap-login {
inet_listener imap {
port = 143
}
}
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
group = postfix
mode = 0660
user = postfix
}
}
service managesieve-login {
inet_listener sieve {
port = 4190
}
4
process_min_avail = 0
service_count = 1
vsz_limit = 64 M
}
ssl_cert = </usr/local/etc/postfix/keys/server.crt
ssl_key = </usr/local/etc/postfix/keys/server.key
userdb {
args = /usr/local/etc/dovecot/dovecot-sql.conf.ext
driver = sql
}
protocol lmtp {
mail_plugins = quota acl sieve
}
protocol lda {
mail_plugins = quota acl sieve acl
postmaster_address = root
}
protocol imap {
imap_client_workarounds = tb-extra-mailbox-sep
mail_plugins = quota acl imap_quota imap_acl
}
File: /usr/local/etc/dovecot/dovecot-dict-sql.conf.ext
connect = host=127.0.0.1 dbname=mailserver user=mailadmin password=< password >
map {
pattern = priv/quota/storage
table = quota2
username_field = username
value_field = bytes
}
map {
pattern = priv/quota/messages
table = quota2
username_field = username
value_field = messages
}
map {
pattern = shared/shared-boxes/user/$to/$from
table = user_shares
value_field = dummy
fields {
from_user = $from
to_user = $to
}
}
map {
pattern = shared/shared-boxes/anyone/$from
table = anyone_shares
value_field = dummy
fields {
from_user = $from
}
}
5
File: /usr/local/etc/dovecot/dovecot-sql.conf.ext
driver = mysql
connect = host=127.0.0.1 dbname=mailserver user=mailuser password=< password >
default_pass_scheme = SHA512-CRYPT
user_query = SELECT CONCAT('*:messages=1000000:bytes=', quota) as quota_rule,
5000 AS uid, 5000 AS gid FROM mailbox WHERE username = '%u' AND active = '1'
password_query = SELECT username as user, password FROM mailbox WHERE username =
'%u' AND active = '1'
iterate_query = SELECT username AS user FROM mailserver.mailbox
Configure POSTFIX
mv /usr/local/etc/postfix/main.cf{,.orig}
mv /usr/local/etc/postfix/master.cf{,.orig}
File: /usr/local/etc/postfix/main.cf
alias_database = hash:/etc/aliases
alias_maps = hash:/etc/aliases
append_dot_mydomain = no
biff = no
command_directory = /usr/local/sbin
compatibility_level = 2
daemon_directory = /usr/local/libexec/postfix
data_directory = /var/db/postfix
debug_peer_level = 2
debugger_command = PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin ddd
$daemon_directory/$process_name $process_id & sleep 5
delay_warning_time = 4h
dovecot-spamass_destination_recipient_limit = 1
html_directory = /usr/local/share/doc/postfix
inet_interfaces = all
inet_protocols = all
mail_owner = postfix
mailbox_command = /usr/local/libexec/dovecot/deliver
mailbox_size_limit = 0
mailq_path = /usr/local/bin/mailq
manpage_directory = /usr/local/man
message_size_limit = 52428800
milter_default_action = accept
milter_protocol = 2
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
newaliases_path = /usr/local/bin/newaliases
non_smtpd_milters = $smtpd_milters
policyd-spf_time_limit = 3600
queue_directory = /var/spool/postfix
readme_directory = /usr/local/share/doc/postfix
recipient_bcc_maps =
proxy:mysql:/usr/local/etc/postfix/recipient_bcc_maps_user.cf,
proxy:mysql:/usr/local/etc/postfix/recipient_bcc_maps_domain.cf
recipient_delimiter = +
relay_domains = proxy:mysql:/usr/local/etc/postfix/mysql-relay-domains-maps.cf
sample_directory = /usr/local/etc/postfix
sender_bcc_maps = proxy:mysql:/usr/local/etc/postfix/sender_bcc_maps_user.cf,
proxy:mysql:/usr/local/etc/postfix/sender_bcc_maps_domain.cf
sender_dependent_default_transport_maps =
hash:/usr/local/etc/postfix/sender_transport
6
sendmail_path = /usr/local/sbin/sendmail.postfix
setgid_group = maildrop
smtp_tls_session_cache_database = btree:$data_directory/smtp_tls_session_cache
smtp_use_tls = yes
smtpd_banner = $myhostname ESMTP $mail_name
smtpd_milters = inet:127.0.0.1:8891, inet:127.0.0.1:8893,
unix:/var/run/clamav/clmilter.sock
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks,
reject_unauth_destination, check_policy_service unix:private/policyd-spf,
reject_rbl_client bl.spamcop.net, reject_rbl_client zen.spamhaus.org, permit
smtpd_sasl_auth_enable = yes
smtpd_sasl_path = private/auth
smtpd_sasl_type = dovecot
smtpd_sender_restrictions = hash:/usr/local/etc/postfix/access
smtpd_tls_cert_file = /usr/local/etc/postfix/keys/server.crt
smtpd_tls_key_file = /usr/local/etc/postfix/keys/server.key
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_security_level = may
smtpd_tls_session_cache_timeout = 3600s
smtpd_use_tls = yes
tls_random_source = dev:/dev/urandom
transport_maps = hash:/usr/local/etc/postfix/transport
unknown_local_recipient_reject_code = 550
virtual_alias_maps = proxy:mysql:/usr/local/etc/postfix/mysql-virtual-alias-
maps.cf, proxy:mysql:/usr/local/etc/postfix/mysql-virtual-alias-domain-maps.cf,
proxy:mysql:/usr/local/etc/postfix/mysql-virtual-alias-domain-catchall-maps.cf,
regexp:/usr/local/etc/postfix/virtual_regexp
virtual_mailbox_domains = proxy:mysql:/usr/local/etc/postfix/mysql-virtual-
mailbox-domains.cf
virtual_mailbox_maps = proxy:mysql:/usr/local/etc/postfix/mysql-virtual-mailbox-
maps.cf, proxy:mysql:/usr/local/etc/postfix/mysql-virtual-alias-domain-mailbox-
maps.cf
virtual_transport = dovecot-spamass
7
File: /usr/local/etc/postfix/master.cf
smtp inet n - n - - smtpd
-o myhostname=built102.domain.tld
submission inet n - - - - smtpd
smtps inet n - - - - smtpd
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtp_helo_name=demo.domain.tld
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
policyd-spf unix - n n - 0 spawn
user=nobody argv=/usr/local/bin/policyd-spf
pickup unix n - n 60 1 pickup
cleanup unix n - n - 0 cleanup
qmgr unix n - n 300 1 qmgr
tlsmgr unix - - n 1000? 1 tlsmgr
rewrite unix - - n - - trivial-rewrite
bounce unix - - n - 0 bounce
defer unix - - n - 0 bounce
trace unix - - n - 0 bounce
verify unix - - n - 1 verify
flush unix n - n 1000? 0 flush
proxymap unix - - n - - proxymap
proxywrite unix - - n - 1 proxymap
smtp unix - - n - - smtp
relay unix - - n - - smtp
showq unix n - n - - showq
error unix - - n - - error
retry unix - - n - - error
discard unix - - n - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - n - - lmtp
anvil unix - - n - 1 anvil
scache unix - - n - 1 scache
dovecot-spamass unix - n n - - pipe
flags=DRhu user=vmail:vmail argv=/usr/local/bin/spamc -u ${recipient} -e
/usr/local/libexec/dovecot/dovecot-lda -f ${sender} -d ${recipient}
8
Configure Postfix to work with MySQL (based on
PostfixAdmin DB schema)
File: /usr/local/etc/postfix/recipient_bcc_maps_user.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT recipient_bcc_user.bcc_address FROM recipient_bcc_user,domain
WHERE recipient_bcc_user.username='%s' AND recipient_bcc_user.domain='%d' AND
recipient_bcc_user.domain=domain.domain AND domain.backupmx=0 AND
domain.active=1 AND recipient_bcc_user.active=1
File: /usr/local/etc/postfix/recipient_bcc_maps_domain.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT bcc_address FROM recipient_bcc_domain WHERE domain='%d' AND
active=1
File: /usr/local/etc/postfix/mysql-relay-domains-maps.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT domain FROM domain WHERE domain='%s' and backupmx = '1'
File: /usr/local/etc/postfix/sender_bcc_maps_user.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT sender_bcc_user.bcc_address FROM sender_bcc_user,domain WHERE
sender_bcc_user.username='%s' AND sender_bcc_user.domain='%d' AND
sender_bcc_user.domain=domain.domain AND domain.backupmx=0 AND domain.active=1
AND sender_bcc_user.active=1
File: /usr/local/etc/postfix/sender_bcc_maps_domain.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT bcc_address FROM sender_bcc_domain WHERE domain='%d' AND active=1
File: /usr/local/etc/postfix/mysql-virtual-alias-maps.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
9
File: /usr/local/etc/postfix/mysql-virtual-alias-domain-maps.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain =
'%d' and alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND
alias.active = 1 AND alias_domain.active='1'
File: /usr/local/etc/postfix/mysql-virtual-alias-domain-catchall-maps.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain =
'%d' and alias.address = CONCAT('@', alias_domain.target_domain) AND
alias.active = 1 AND alias_domain.active='1'
File: /usr/local/etc/postfix/mysql-virtual-mailbox-domains.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '0' AND
active = '1'
File: /usr/local/etc/postfix/mysql-virtual-mailbox-maps.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
File: /usr/local/etc/postfix/mysql-virtual-alias-domain-mailbox-maps.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain
= '%d' and mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND
mailbox.active = 1 AND alias_domain.active='1'
File: /usr/local/etc/postfix/mysql-virtual-mailbox-limit-maps.cf
user = mailuser
password = < password >
hosts = 127.0.0.1
dbname = mailserver
query = SELECT quota FROM mailbox WHERE username='%s' AND active = '1'
10
Create the following files if they don’t exist and then execute
postmap hash:/usr/local/etc/postfix/sender_transport
postmap hash:/usr/local/etc/postfix/access
postmap hash:/usr/local/etc/postfix/transport
touch /usr/local/etc/postfix/virtual_regexp
newaliases
/usr/local/etc/mysql/my.cnf
[mysqld]
user = mysql
port = 3306
socket = /tmp/mysql.sock
bind-address = 127.0.0.1
basedir = /usr/local
datadir = /var/db/mysql
tmpdir = /var/db/mysql_tmpdir
slave-load-tmpdir = /var/db/mysql_tmpdir
secure-file-priv = /var/db/mysql_secure
log-bin = mysql-bin
log-output = TABLE
master-info-repository = TABLE
relay-log-info-repository = TABLE
innodb_data_home_dir = /var/db/mysql
innodb_log_group_home_dir = /var/db/mysql
--
-- Current Database: `mailserver`
--
11
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `mailserver` /*!40100 DEFAULT CHARACTER
SET latin1 */;
USE `mailserver`;
--
-- Table structure for table `admin`
--
--
-- Table structure for table `alias`
--
--
-- Table structure for table `alias_domain`
--
--
-- Table structure for table `anyone_shares`
--
12
DROP TABLE IF EXISTS `anyone_shares`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `anyone_shares` (
`from_user` varchar(100) NOT NULL,
`dummy` char(1) DEFAULT '1',
PRIMARY KEY (`from_user`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `cake_d_c_users_phinxlog`
--
--
-- Table structure for table `config`
--
--
-- Table structure for table `domain`
--
--
13
-- Table structure for table `domain_admins`
--
--
-- Table structure for table `fetchmail`
--
--
-- Table structure for table `log`
--
14
--
-- Table structure for table `mailbox`
--
--
-- Table structure for table `quota`
--
--
-- Table structure for table `quota2`
--
--
-- Table structure for table `recipient_bcc_domain`
--
15
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`expired` datetime NOT NULL DEFAULT '9999-12-31 00:00:00',
`active` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`domain`),
KEY `bcc_address` (`bcc_address`),
KEY `expired` (`expired`),
KEY `active` (`active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `recipient_bcc_user`
--
--
-- Table structure for table `sender_bcc_domain`
--
--
-- Table structure for table `sender_bcc_user`
--
16
`created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`expired` datetime NOT NULL DEFAULT '9999-12-31 00:00:00',
`active` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`username`),
KEY `bcc_address` (`bcc_address`),
KEY `domain` (`domain`),
KEY `expired` (`expired`),
KEY `active` (`active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `share_folder`
--
--
-- Table structure for table `social_accounts`
--
--
-- Table structure for table `user_shares`
--
17
`to_user` varchar(100) NOT NULL,
`dummy` char(1) DEFAULT '1',
PRIMARY KEY (`from_user`,`to_user`),
KEY `to_user` (`to_user`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `users`
--
--
-- Table structure for table `vacation`
--
--
-- Table structure for table `vacation_notification`
--
18
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
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='Postfix Admin - Virtual Vacation
Notifications';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
--
-- Current Database: `spamassassin`
--
USE `spamassassin`;
--
-- Table structure for table `awl`
--
19
`count` int(11) NOT NULL DEFAULT '0',
`totscore` float NOT NULL DEFAULT '0',
`signedby` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`username`,`email`,`signedby`,`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `bayes_expire`
--
--
-- Table structure for table `bayes_global_vars`
--
--
-- Table structure for table `bayes_seen`
--
--
-- Table structure for table `bayes_token`
--
20
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `bayes_vars`
--
--
-- Table structure for table `userpref`
--
21
Initializing data for table ‘userpref’ (suggested values)
LOCK TABLES `userpref` WRITE;
/*!40000 ALTER TABLE `userpref` DISABLE KEYS */;
INSERT INTO `userpref` VALUES
('$GLOBAL','required_hits','5.0',1),('$GLOBAL','report_safe','1',2),('$GLOBAL','
use_bayes','1',3),('$GLOBAL','use_dcc','1',4),('$GLOBAL','score
SPF_FAIL','4.5',21),('$GLOBAL','score SPF_HELO_FAIL','4.5',22),('$GLOBAL','score
SPF_SOFTFAIL','2.0',23),('$GLOBAL','score
SPF_HELO_SOFTFAIL','2.0',24),('$GLOBAL','score
SPF_NEUTRAL','2.0',25),('$GLOBAL','score
T_DKIM_INVALID','2.0',45),('$GLOBAL','score
DKIM_ADSP_NXDOMAIN','2.0',46),('$GLOBAL','score
SPF_NONE','2.0',47),('$GLOBAL','score RDNS_NONE','1.5',48),('$GLOBAL','score
RCVD_IN_SORBS_DUL','3.0',49),('$GLOBAL','score
RCVD_IN_SORBS_WEB','3.0',50),('$GLOBAL','score
RCVD_IN_SORBS_SMTP','3.0',51),('$GLOBAL','score
RCVD_IN_SORBS_HTTP','3.0',52),('$GLOBAL','score
URIBL_WS_SURBL','3.0',53),('$GLOBAL','score
RCVD_IN_XBL','3.0',54),('$GLOBAL','score RCVD_IN_BRBL_LASTEXT','7.0',55);
/*!40000 ALTER TABLE `userpref` ENABLE KEYS */;
UNLOCK TABLES;
--
-- Current Database: `a0001`
--
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `a0001` /*!40100 DEFAULT CHARACTER SET
latin1 */;
USE `a0001`;
--
-- Table structure for table `accounts`
--
22
`description` varchar(255) NOT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `cake_d_c_users_phinxlog`
--
--
-- Table structure for table `shards`
--
--
-- Table structure for table `shards_accounts`
--
--
-- Table structure for table `social_accounts`
--
23
`username` varchar(255) DEFAULT NULL,
`reference` varchar(255) NOT NULL,
`avatar` varchar(255) DEFAULT NULL,
`description` text,
`link` varchar(255) NOT NULL,
`token` varchar(500) NOT NULL,
`token_secret` varchar(500) DEFAULT NULL,
`token_expires` datetime DEFAULT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1',
`data` text NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `users`
--
--
-- Table structure for table `users_shards`
--
24
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
FLUSH PRIVILEGES;
Configure Opendkim
mv /usr/local/etc/mail/opendkim.conf{,.orig}
mkdir -p /usr/local/etc/opendkim/
mkdir -p /usr/local/etc/opendkim/keys
25
Create if not exist
touch /usr/local/etc/opendkim/SigningTable
touch /usr/local/etc/opendkim/KeyTable
File: /usr/local/etc/mail/opendkim.conf
AutoRestart Yes
AutoRestartRate 10/1h
LogWhy Yes
Syslog Yes
SyslogSuccess Yes
Mode sv
Canonicalization relaxed/simple
ExternalIgnoreList refile:/usr/local/etc/opendkim/TrustedHosts
InternalHosts refile:/usr/local/etc/opendkim/TrustedHosts
KeyTable refile:/usr/local/etc/opendkim/KeyTable
SigningTable refile:/usr/local/etc/opendkim/SigningTable
SignatureAlgorithm rsa-sha256
Socket inet:8891@localhost
UMask 022
UserID opendkim:opendkim
TemporaryDirectory /var/tmp
File: /usr/local/etc/opendkim/TrustedHosts
127.0.0.1
Fix ownership
chown -R opendkim:opendkim /usr/local/etc/opendkim
Configure Opendmarc
mkdir -p /usr/local/etc/opendmarc/
File: /usr/local/etc/mail/opendmarc.conf
AuthservID demo.domain.tld
FailureReports true
FailureReportsSentBy postmaster@demo.domain.tld
HistoryFile /var/run/opendmarc/opendmarc.dat
IgnoreAuthenticatedClients true
IgnoreHosts /usr/local/etc/opendmarc/ignore.hosts
IgnoreMailFrom demo.domain.tld
MilterDebug 0
RejectFailures false
ReportCommand /usr/local/sbin/sendmail -t
RequiredHeaders true
Socket inet:8893@localhost
SPFSelfValidate true
26
Syslog true
SyslogFacility mail
TrustedAuthservIDs demo.domain.tld
UserID opendmarc
File: /usr/local/etc/opendmarc/ignore.hosts
127.0.0.1
Fix ownership
chown -R opendmarc:opendmarc /usr/local/etc/opendmarc
Configure SPAMASSASSIN
mv /usr/local/etc/mail/spamassassin/local.cf{,.orig}
File: /usr/local/etc/mail/spamassassin/local.cf
loadplugin Mail::SpamAssassin::Plugin::AWL
loadplugin Mail::SpamAssassin::Plugin::DCC
user_scores_dsn DBI:mysql:spamassassin:localhost
user_scores_sql_username mailadmin
user_scores_sql_password < password >
user_scores_sql_custom_query SELECT preference, value FROM _TABLE_ WHERE
username = _USERNAME_ OR username = '$GLOBAL' OR username = CONCAT('%',_DOMAIN_)
ORDER BY username ASC
auto_whitelist_factory Mail::SpamAssassin::SQLBasedAddrList
user_awl_dsn DBI:mysql:spamassassin:localhost
user_awl_sql_username mailadmin
user_awl_sql_password < password >
user_awl_sql_table awl
auto_whitelist_distinguish_signed 1
bayes_store_module Mail::SpamAssassin::BayesStore::MySQL
bayes_sql_dsn DBI:mysql:spamassassin:localhost
bayes_sql_username mailadmin
bayes_sql_password < password >
bayes_auto_expire 0
bayes_expiry_max_db_size 1000000
27
Configure DAVICAL
service postgresql initdb
mv /usr/local/www/davical/config/config.php{,.orig}
File: /usr/local/share/postgresql/pg_hba.conf
# TYPE DATABASE USER CIDR-ADDRESS METHOD
# davical
local davical davical_app trust
local davical davical_dba trust
File: /usr/local/www/davical/config/config.php
<?php $c->authenticate_hook['call'] = 'IMAP_PAM_check';
$c->authenticate_hook['config'] = array('imap_url' =>
'{localhost:143/imap/tls/novalidate-cert}');
include('drivers_imap_pam.php');
$c->admin_email = 'support@domain.tld';
$c->system_name = 'demo.domain.tld';
$c->pg_connect[] = 'dbname=davical port=5432 user=davical_app';
$c->enable_auto_schedule = false;
?>
chmod -x /usr/local/share/davical/dba/create-database.sh
** REMEMBER: Take note of the shown password for the 'admin' user
Configure APACHE
mkdir -p /usr/local/etc/ssl/apache/
28
File: /usr/local/etc/apache24/Includes/davical-localhost.conf
<VirtualHost 127.0.0.1:80>
<IfModule security2_module>
SecRuleEngine Off # Either enable or disable mod_security
</IfModule>
DocumentRoot /usr/local/www/davical/htdocs
DirectoryIndex index.php index.html
<Directory "/usr/local/www/davical/htdocs">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
File: /usr/local/etc/apache24/httpd.conf
Uncomment:
Add:
<FilesMatch "\.php$">
SetHandler application/x-httpd-php
</FilesMatch>
<FilesMatch "\.phps$">
SetHandler application/x-httpd-php-source
</FilesMatch>
Comment out:
/usr/local/etc/apache24/Includes/ssl.conf
Listen 443
SSLCipherSuite HIGH:MEDIUM:!MD5:!RC4
SSLProxyCipherSuite HIGH:MEDIUM:!MD5:!RC4
SSLHonorCipherOrder on
SSLProtocol all -SSLv3
SSLProxyProtocol all -SSLv3
SSLPassPhraseDialog builtin
SSLSessionCache "shmcb:/var/run/ssl_scache(512000)"
SSLSessionCacheTimeout 300
29
File: /usr/local/etc/apache24/Includes/mail.conf
(Including RoundCube and Webadmin virtual hosts)
<VirtualHost *:443>
SSLEngine On
SSLCertificateFile /usr/local/etc/ssl/apache/server.crt
SSLCertificateKeyFile /usr/local/etc/ssl/apache/server.key
DocumentRoot /usr/local/www/davical/htdocs
DirectoryIndex index.php index.html
<Directory "/usr/local/www/davical/htdocs">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Configure Dashboard
For details check: https://github.com/sophimail/webadmin/blob/master/INSTALLATION.md
composer clearcache (optional)
git clone https://github.com/sophimail/webadmin.git
cd webadmin
composer install
chown -R :your-web-server-group-name webadmin
30
Update file: /usr/local/www/webadmin/config/app.php
<?php return [ 'debug' => filter_var(env('DEBUG', false),
FILTER_VALIDATE_BOOLEAN),
'reCaptcha' => [
'secret' => '**** CHANGE ME ****',
'key' => '**** CHANGE ME ****'
],
'_Constants' => [
'encrypt' => 'SHA512-CRYPT',
'quota_multiplier' => '1048576', // Constant to convert Bytes to MB and
vice versa. Either use '1024000' or '1048576'
'password_length' => '5',
'strong_password' => true, // at least two letters, at least two numbers,
at least one special character (Validation rules: MailboxTable.php)
'aliases' => '10',
'mailboxes' => '10',
'maxquota' => '10',
'unlimited' => '10485760', // 10TB - define unlimited in MB (eg. total
storage capacity)
'domain_quota_default' => '2000',
'transport_options' => ['virtual' => 'virtual', 'local' =>
'local', 'relay' => 'relay'],
'transport_default' => 'virtual',
'domain_in_mailbox' => false,
],
'App' => [
'namespace' => 'App',
'title' => 'Company Dashboard',
'encoding' => env('APP_ENCODING', 'UTF-8'),
'defaultLocale' => env('APP_DEFAULT_LOCALE', 'es-ES'),
'base' => false,
'dir' => 'src',
'webroot' => 'webroot',
'wwwRoot' => WWW_ROOT,
// 'baseUrl' => env('SCRIPT_NAME'),
'fullBaseUrl' => false,
'imageBaseUrl' => 'img/',
'cssBaseUrl' => 'css/',
'jsBaseUrl' => 'js/',
'paths' => [
'plugins' => [ROOT . DS . 'plugins' . DS],
'templates' => [APP . 'Template' . DS],
'locales' => [APP . 'Locale' . DS],
],
],
'Security' => [
'salt' => env('SECURITY_SALT', '**** CHANGE ME ****'),
],
'Asset' => [
// 'timestamp' => true,
],
'Cache' => [
'default' => [
'className' => 'File',
'path' => CACHE,
'url' => env('CACHE_DEFAULT_URL', null),
],
31
'_cake_core_' => [
'className' => 'File',
'prefix' => 'myapp_cake_core_',
'path' => CACHE . 'persistent/',
'serialize' => true,
'duration' => '+2 minutes',
'url' => env('CACHE_CAKECORE_URL', null),
],
'_cake_model_' => [
'className' => 'File',
'prefix' => 'myapp_cake_model_',
'path' => CACHE . 'models/',
'serialize' => true,
'duration' => '+2 minutes',
'url' => env('CACHE_CAKEMODEL_URL', null),
],
],
'Error' => [
'errorLevel' => E_ALL & ~E_DEPRECATED,
'exceptionRenderer' => 'Cake\Error\ExceptionRenderer',
'skipLog' => [],
'log' => true,
'trace' => true,
],
'EmailTransport' => [
'default' => [
'className' => 'Mail',
// The following keys are used in SMTP transports
'port' => 25,
'timeout' => 30,
'client' => null,
'tls' => null,
'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
],
],
'Email' => [
'default' => [
'transport' => 'default',
'from' => 'support@domain.tld',
//'charset' => 'utf-8',
//'headerCharset' => 'utf-8',
],
],
'Datasources' => [
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
//'port' => 'non_standard_port_number',
'username' => 'mailadmin',
'password' => '< password >',
'database' => 'a0001',
'encoding' => 'utf8',
'timezone' => 'UTC',
'flags' => [],
'cacheMetadata' => true,
'log' => true,
32
'quoteIdentifiers' => false,
'DB1' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
//'port' => 'non_standard_port_number',
'username' => 'mailadmin',
'password' => '< password >',
'database' => 'mailserver',
'encoding' => 'utf8',
'timezone' => 'UTC',
'flags' => [],
'cacheMetadata' => true,
'log' => true,
'test' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
//'port' => 'non_standard_port_number',
'username' => 'my_app',
'password' => 'secret',
'database' => 'test_myapp',
'encoding' => 'utf8',
'timezone' => 'UTC',
'cacheMetadata' => true,
'quoteIdentifiers' => false,
'log' => false,
//'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
'url' => env('DATABASE_TEST_URL', null),
],
],
'Log' => [
'debug' => [
'className' => 'Cake\Log\Engine\FileLog',
'path' => LOGS,
'file' => 'debug',
'levels' => ['notice', 'info', 'debug'],
'url' => env('LOG_DEBUG_URL', null),
],
'error' => [
'className' => 'Cake\Log\Engine\FileLog',
'path' => LOGS,
'file' => 'error',
'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
'url' => env('LOG_ERROR_URL', null),
],
],
33
'Session' => [
'defaults' => 'php',
],
];
Important Changes:
1. 'Security' => [
'salt' => env('SECURITY_SALT', '****
CHANGE ME ****'),
],
2. 'reCaptcha' => [
'secret' => '**** CHANGE ME ****',
'key' => '**** CHANGE ME ****'
],
3. Datasources: DB Connections
Create Superadmin
Execute: /usr/local/www/webadmin/bin/cake users addSuperuser
Login: https://<IP-Address>/webadmin
Username: superadmin
Password: <copy the output of command above>
Configure Roundcube
cd /usr/local/www
fetch
https://github.com/roundcube/roundcubemail/releases/download/1.2.5/roundcubemail
-1.2.5-complete.tar.gz
tar zxvf roundcubemail-1.2.5-complete.tar.gz
mv roundcubemail-1.2.5 roundcube
chown -R root:wheel roundcube/
--
-- Current Database: `roundcubemail`
--
34
USE `roundcubemail`;
--
-- Table structure for table `attachments`
--
--
-- Dumping data for table `attachments`
--
--
-- Table structure for table `cache`
--
--
-- Dumping data for table `cache`
--
--
-- Table structure for table `cache_index`
--
35
`data` longtext NOT NULL,
PRIMARY KEY (`user_id`,`mailbox`),
KEY `expires_index` (`expires`),
CONSTRAINT `user_id_fk_cache_index` FOREIGN KEY (`user_id`) REFERENCES `users`
(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `cache_index`
--
--
-- Table structure for table `cache_messages`
--
--
-- Dumping data for table `cache_messages`
--
--
-- Table structure for table `cache_shared`
--
--
-- Dumping data for table `cache_shared`
--
--
-- Table structure for table `cache_thread`
--
36
DROP TABLE IF EXISTS `cache_thread`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `cache_thread` (
`user_id` int(10) unsigned NOT NULL,
`mailbox` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`expires` datetime DEFAULT NULL,
`data` longtext NOT NULL,
PRIMARY KEY (`user_id`,`mailbox`),
KEY `expires_index` (`expires`),
CONSTRAINT `user_id_fk_cache_thread` FOREIGN KEY (`user_id`) REFERENCES `users`
(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `cache_thread`
--
--
-- Table structure for table `caldav_props`
--
--
-- Dumping data for table `caldav_props`
--
--
-- Table structure for table `calendars`
--
37
--
-- Dumping data for table `calendars`
--
--
-- Table structure for table `carddav_addressbooks`
--
--
-- Dumping data for table `carddav_addressbooks`
--
--
-- Table structure for table `carddav_contacts`
--
38
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `carddav_contacts`
--
--
-- Table structure for table `carddav_group_user`
--
--
-- Dumping data for table `carddav_group_user`
--
--
-- Table structure for table `carddav_groups`
--
--
-- Dumping data for table `carddav_groups`
--
--
-- Table structure for table `carddav_migrations`
--
39
DROP TABLE IF EXISTS `carddav_migrations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `carddav_migrations` (
`ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`filename` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
`processed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`ID`),
UNIQUE KEY `filename` (`filename`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `carddav_migrations`
--
--
-- Table structure for table `carddav_xsubtypes`
--
--
-- Dumping data for table `carddav_xsubtypes`
--
--
-- Table structure for table `contactgroupmembers`
--
--
-- Dumping data for table `contactgroupmembers`
--
40
--
-- Table structure for table `contactgroups`
--
--
-- Dumping data for table `contactgroups`
--
--
-- Table structure for table `contacts`
--
--
-- Dumping data for table `contacts`
--
--
-- Table structure for table `dictionary`
--
41
`user_id` int(10) unsigned DEFAULT NULL,
`language` varchar(5) NOT NULL,
`data` longtext NOT NULL,
UNIQUE KEY `uniqueness` (`user_id`,`language`),
CONSTRAINT `user_id_fk_dictionary` FOREIGN KEY (`user_id`) REFERENCES `users`
(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `dictionary`
--
--
-- Table structure for table `events`
--
--
-- Dumping data for table `events`
--
--
-- Table structure for table `identities`
--
42
CREATE TABLE `identities` (
`identity_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`del` tinyint(1) NOT NULL DEFAULT '0',
`standard` tinyint(1) NOT NULL DEFAULT '0',
`name` varchar(128) NOT NULL,
`organization` varchar(128) NOT NULL DEFAULT '',
`email` varchar(128) NOT NULL,
`reply-to` varchar(128) NOT NULL DEFAULT '',
`bcc` varchar(128) NOT NULL DEFAULT '',
`signature` longtext,
`html_signature` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`identity_id`),
KEY `user_identities_index` (`user_id`,`del`),
KEY `email_identities_index` (`email`,`del`),
CONSTRAINT `user_id_fk_identities` FOREIGN KEY (`user_id`) REFERENCES `users`
(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `identities`
--
--
-- Table structure for table `itipinvitations`
--
--
-- Dumping data for table `itipinvitations`
--
--
-- Table structure for table `searches`
--
43
PRIMARY KEY (`search_id`),
UNIQUE KEY `uniqueness` (`user_id`,`type`,`name`),
CONSTRAINT `user_id_fk_searches` FOREIGN KEY (`user_id`) REFERENCES `users`
(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `searches`
--
--
-- Table structure for table `session`
--
--
-- Dumping data for table `session`
--
--
-- Table structure for table `system`
--
--
-- Dumping data for table `system`
--
--
-- Table structure for table `users`
--
44
`created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`last_login` datetime DEFAULT NULL,
`failed_login` datetime DEFAULT NULL,
`failed_login_counter` int(10) unsigned DEFAULT NULL,
`language` varchar(5) DEFAULT NULL,
`preferences` longtext,
PRIMARY KEY (`user_id`),
UNIQUE KEY `username` (`username`,`mail_host`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `users`
--
--
-- Dumping routines for database 'roundcubemail'
--
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
File: /usr/local/www/roundcube/config/config.inc.php
<?php
$config = array();
$config['db_dsnw'] = 'mysql://mailadmin:< password >@localhost/roundcubemail';
$config['default_host'] = 'localhost';
$config['smtp_server'] = 'localhost';
$config['smtp_port'] = 25;
$config['smtp_user'] = '';
$config['smtp_pass'] = '';
$config['support_url'] = 'http://www.domain.tld';
$config['product_name'] = 'Company Webmail';
$config['des_key'] = 'rcmail-!24ByteDESkey*Str';
$config['plugins'] = array(
'archive',
'zipdownload',
'managesieve',
'acl',
'password',
'carddav',
'calendar'
);
$config['skin'] = 'larry';
/usr/local/www/roundcube/plugins/password/config.inc.php
Copy config.inc.php.dist to config.inc.php
$config['password_db_dsn'] = 'mysqli://mailadmin:< password
>@localhost/mailserver';
$config['password_query'] = 'UPDATE mailserver.mailbox SET
password=%c,modified=now() WHERE username=%u and domain=%d LIMIT 1';
45