Here is the original guid: Postfix+Dovecot+MySQL搭建邮件服务器
The reference above was written for Ubuntu, I found some changes must be done to apply the techniques with my CentOS scenario.
Another helpful guid: Email with Postfix, Dovecot and MariaDB on CentOS 7
The second one uses likely design of database. More important, it has some things the first reference doesn‘t talk about.
My scenario
I planed to build a mail server working with gitlab, for internal development using. The mail server is also the one running gitlab. The idea makes things simple.
1. DNS configuration
For local network, you have to use "/etc/hosts" as the alternative.
Let‘s say the host name of server is: devsrv,
Server File: /etc/hosts
# hosts for devsrv 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 # DNS configuration 192.168.1.10 mail.devsrv.lan www.devsrv.lan devsrv.lan devsrv
Client File: /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.1.10 devsrv devsrv.lan www.devsrv.lan mail.devsrv.lan imap.devsrv.lan smtp.devsrv.lan git.devsrv.lan
2. Installation
Under CentOS, the commands cloud be:
sudo yum install -y postfix dovecot dovecot-core mariadb
3. Initializing of database
Following the instruction of the first reference, initialize the database. I also wrote some procedures to help managing account:
Create Database
create database if not exists mailserver; create user [email protected]'localhost' identified by 'mailserver123'; grant all on mailserver.* to [email protected]' identified by 'mailserver123';
Create tables and procedures
use mailserver; delimiter // drop procedure if exists proc_log; create procedure proc_log(msg varchar(1024)) begin select ifnull(msg, '') as DEBUG; end // -- Procedure: proc_show_error -- Show error message with error code drop procedure if exists proc_log_error; create procedure proc_log_error(err_code int, msg varchar(255)) begin call proc_log(concat('ERROR(', err_code, '): ', msg)); end // -- Procedure proc_add_mail_account -- Create a new mail account drop procedure if exists proc_add_mail_account; create procedure proc_add_mail_account (uname varchar(64), pwd varchar(64)) begin declare def_domain varchar(100) default 'devsrv.lan'; declare err int default 0; declare num int; declare uname_len int default 0; declare at_pos int default null; declare domain varchar(100); declare domain_id int; declare pwd_len int; -- initialize set uname = lcase(ltrim(rtrim(uname))); set uname_len = length(uname); set at_pos = ifnull(instr(uname, '@'), 0); set pwd = ltrim(rtrim(pwd)); set pwd_len = length(pwd); -- validate username if uname is null or uname_len = 0 then set err = 1; call proc_log_error(err, 'Empty username'); end if; -- check '@' if err = 0 and at_pos > 0 then -- if username has only '@' character set domain = right(uname, uname_len - at_pos); if uname_len = 1 or length(domain) = 0 then set err = 2; call proc_log_error(err, 'Invalid username'); end if end if; end if; -- validate domain if err = 0 then if domain is null or length(domain) = 0 then set domain = def_domain; -- use default domain if not specified. set uname = concat(uname, '@', domain); end if; end if; -- validate domain existence if err = 0 then select id into domain_id from virtual_domains where name = domain; if domain_id is null then set err = 4; call proc_log_error(err, 'Invalid domain'); end if; end if; -- validate user existence if err = 0 then select count(*) into num from virtual_users where email=uname; if num > 0 then set err = 5; call proc_log_error(err, 'User exists'); end if; end if; -- validate password if err = 0 then if pwd_len = 0 then set err = 6; call proc_log_error(err, 'Empty password'); else -- validate security rules -- validate length if pwd_len < 6 then set err = 7; call proc_log_error(err, 'Password must be 6 characters long'); end if; -- NOTE: following rules are ignored now. -- validate uppercase letters -- validate digit end if; end if; if err = 0 then -- create account set pwd = encrypt(pwd, concat('X7X', substring(sha(rand()), -16))); insert virtual_users (domain_id, password, email) values(domain_id, pwd, uname); if last_insert_id() is null then set err = 8; call proc_log_error(err, 'Failed to create account'); else select 'Account has been created successfully!'; end if; end if; end // -- end of procedure drop procedure if exists proc_del_mail_account; create procedure proc_del_mail_account(uname varchar(64)) begin set uname = lcase(ltrim(rtrim(uname))); delete from virtual_users where email=uname; if row_count() = 0 then call proc_log('User does not exist'); else call proc_log('User account has been removed successfully!'); end if; end // drop procedure if exists proc_add_mail_alias; create procedure proc_add_mail_alias(src varchar(100), dest varchar(100)) begin declare err int default 0; declare num int; declare domain_id int; declare dest_len int; declare dest_at_pos int; declare dest_name varchar(100); declare dest_domain varchar(100); declare src_len int; declare src_at_pos int; declare src_name varchar(100); declare src_domain varchar(100); set dest = lcase(ltrim(rtrim(dest))); set dest_len = length(dest); set dest_at_pos = ifnull(instr(dest, '@'), 0); set src = lcase(ltrim(rtrim(src))); set src_len = length(src); set src_at_pos = ifnull(instr(src, '@'), 0); -- validate dest length if dest_len = 0 then set err = 1; call proc_log_error(err, 'Empty dest'); else -- check '@' if dest_at_pos = 0 then set err = 2; call proc_log_error(err, 'Invalid dest'); else set dest_name = left(dest, dest_at_pos - 1); set dest_domain = right(dest, dest_len - dest_at_pos); -- re-check dest if length(dest_name) = 0 or length(dest_domain) = 0 then set err = 3; call proc_log_error(err, 'Invalid dest'); else -- check existence select count(*) into num from virtual_users where email=dest; if num = 0 then set err = 4; call proc_log_error(err, 'dest does not exist'); end if; end if; end if; end if; -- validate if err = 0 then if src_len = 0 then set err = 5; call proc_log_error(err, 'Empty alias'); else if src_at_pos = 0 then set err = 6; call proc_log_error(err, 'Invalid alias'); else set src_name=left(src, src_at_pos - 1); set src_domain=right(src, src_len - src_at_pos); if length(src_name) = 0 or length(src_domain) = 0 then set err = 7; call proc_log_error(err, 'Invalid alias'); end if; end if; end if; end if; call proc_log(concat('Validate domains', err)); -- validate both domains if err = 0 then if dest_domain != src_domain then set err = 8; call proc_log_error(err, 'Domain does not match'); else call proc_log('Check domain id'); select id into domain_id from virtual_domains where name=dest_domain; call proc_log(concat('Get domain id:', ifnull(domain_id, 'null'))); if domain_id is null then set err = 9; call proc_log_error(err, 'Domain does not exist'); else select count(*) into num from virtual_aliases where source=src and destination=dest; call proc_log(concat('num of match:', num)); if num > 0 then set err = 10; call proc_log_error(err, 'Alias already exists'); else call proc_log('Insert into...'); insert into virtual_aliases(domain_id, source, destination) value(domain_id, src, dest); call proc_log('Inserted.'); if last_insert_id() is null then set err = 11; call proc_log_error(err, 'Failed'); else call proc_log('Alias has been added successfully'); end if; end if; end if; end if; end if; end // drop procedure if exists proc_del_mail_alias; create procedure proc_del_mail_alias(src varchar(100), dest varchar(100)) begin declare err int default 0; declare num int; declare src_len int; declare src_at_pos int; declare src_domain varchar(100); declare src_domain_len int; declare dest_len int; declare dest_at_pos int; declare dest_domain varchar(100); declare dest_domain_len int; declare alias_id int default null; set src = lcase(ltrim(rtrim(src))); set src_len = length(src); set src_at_pos = ifnull(instr(src, '@'), 0); set src_domain = right(src, src_len - src_at_pos); set src_domain_len = length(src_domain); set dest = lcase(ltrim(rtrim(dest))); set dest_len = length(dest); set dest_at_pos = ifnull(instr(dest, '@'), 0); set dest_domain = right(dest, dest_len -dest_at_pos); set dest_domain_len = length(dest_domain); if src_len = 0 or src_at_pos = 0 or src_domain_len = 0 then set err = 1; call proc_log_error(err, 'Invalid alias'); end if; if err = 0 and (dest_len = 0 or dest_at_pos = 0 or dest_domain_len = 0) then set err = 2; call proc_log_error(err, 'Invalid account'); end if; if err = 0 then if src_domain != dest_domain then set err = 3; call proc_log_error(err, 'Different domain'); end if; end if; if err = 0 then select id into alias_id from virtual_aliases where source = src and destination = dest; if alias_id is null then set err = 4; call proc_log_error(err, 'Not found'); else delete from virtual_aliases where id = alias_id; if row_count() = 0 then set err = 5; call proc_log_error(err, 'Failed to remove record'); else select 'Alias has been removed successfully'; end if; end if; end if; end //
NOTE: Change the default domain to your favorite one before run the script.
Copy and run the sql code in query window of MySql Workbench.
Or save the code to a file and run mysql command:
[[email protected]:~]$mysql -u root -u < untitled.sql
Account maintenance
Under mysql command prompt, run statements to maintenance mail account:
$mysql -u mailserver -p
Create user
call proc_add_mail_account('igame', '123456');
Create alias
call proc_add_mail_alias('[email protected]', '[email protected]');
4. Postfix configuration
Following the reference above to configure postfix, but some small differences :
File: /etc/postfix/main.cf
myhostname = mail.devsrv.lan mydomain = lan mydestination = $myhostname, localhost.$mydomain, localhost # restrict in intranet, for internet, should be 0.0.0.0/0 mynetworks = 192.168.1.0/24, 127.0.0.0/8
NOTE: CentOS stores dovecot certs in different path, if you use the default dovecot certs, keep that in mind:
smtpd_tls_cert_file=/etc/pki/dovecot/certs/dovecot.pem smtpd_tls_key_file=/etc/pki/dovecot/private/dovecot.pem
NOTE: If you have problems when sending mail, try make unset mydestination
mydestination =
The reference above doesn‘t talk about "/etc/postfix/master.cf" which is crucially important, here is how:
File: /etc/postfix/master.cf
# Postfix master process configuration file. For details on the format # of the file, see the master(5) manual page (command: "man 5 master"). # # Do not forget to execute "postfix reload" after editing this file. # # ========================================================================== # service type private unpriv chroot wakeup maxproc command + args # (yes) (yes) (yes) (never) (100) # ========================================================================== smtp inet n - n - - smtpd #smtp inet n - n - 1 postscreen #smtpd pass - - n - - smtpd #dnsblog unix - - n - 0 dnsblog #tlsproxy unix - - n - 0 tlsproxy submission inet n - n - - smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_reject_unlisted_recipient=no # -o smtpd_client_restrictions=$mua_client_restrictions # -o smtpd_helo_restrictions=$mua_helo_restrictions # -o smtpd_sender_restrictions=$mua_sender_restrictions -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject -o milter_macro_daemon_name=ORIGINATING smtps inet n - n - - smtpd -o syslog_name=postfix/smtps -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes -o smtpd_reject_unlisted_recipient=no # -o smtpd_client_restrictions=$mua_client_restrictions # -o smtpd_helo_restrictions=$mua_helo_restrictions # -o smtpd_sender_restrictions=$mua_sender_restrictions -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject -o milter_macro_daemon_name=ORIGINATING #628 inet n - n - - qmqpd # NOTE: do not forget dovecot dovecot unix - n n - - pipe flags=DRhu user=vmail:vmail argv=/usr/libexec/dovecot/deliver -f ${sender} -d ${recipient}
5. Dovecot configuration
Again, the most occurring error is the path of ssl certs, for default dovecot certs, it should be:
File: /etc/dovecot/dovecot.conf
ssl_cert = </etc/pki/dovecot/certs/dovecot.pem ssl_key = </etc/pki/dovecot/private/dovecot.pem
Under CentOS 7, there is no *.protocol files for dovecot.conf, so this following line coming from the first reference has no meaning:
!include_try /usr/share/dovecot/protocols.d/*.protocol
6. Create Account
In query dialog of "MySql Workbench Community" or mysql command line prompt, run the following sql statement:
use mailserver; call proc_add_mail_account('test', '123456');
7. Thunderbird Configuration
The Thunderbird is not smart enough if you choose to let it detect the configuration from mail server. The faster way is manually configuring:
Add new account:
Use manual config
NOTE: Remove the "." before server hostname which is a mistake. Configure the port, SSL and authentication like the following picture:
Re-test
Now the Thunderbird shows off how clever it is.
Add Security Exception
Probably Thunderbird warns about security exception, choose "Confirm Security Exception".
Overview of Account Settings
When all done, we can check the settings of account.
Server Settings:
Outgoing Server(SMTP) Settings:
Edit the smtp setting:
Congratulations! All done, now we can send and receive mail. Enjoy!
Good luck!