CentOS: postfix, dovecot, MUA – virtual domains, virtual users

by Kliment Andreev
18 comments
Reading Time: 7 minutes

The goal of this post is to have postfix (SMTP, SSL/TLS and SASL) and dovecot (POP3s, IMAPs) serving multiple non-U*ix users in multiple e-mail domains on one CentOS box. I am using CentOS 6.3, postfix 2.6.6 and dovecot 2.0.9. The configuration files shouldn’t be different on any other Linux distro but there might be some slight differences in how things get accomplished.

Postfix

First, install postfix and configure it to start on boot with:

yum install postfix
chkconfig postfix on

You might get a notice that postfix is already installed and that’s fine. The configuration files for postfix are in /etc/postfix. There are two main files, main.cf and master.cf. Make a copy of both these files. Usually I do:

cd /etc/postfix
cp main.cf main.cf.20131124
cp master.cf master.cf.20131124

20131124 represents the date the copy was taken.

Now, edit main.cf and change the following values:

myhostname = www.example.com
mydomain = example.com
myorigin = $mydomain
inet_interfaces = all
home_mailbox = Maildir/

Create a user that will have access to the mailboxes.

adduser vpostfix
tail /etc/passwd

Make sure to change the login shell to /sbin/nologin.

usermod -s /sbin/nologin vpostfix. 

Look at the UID:GUID for the vpostfix user. In my case it’s 502:502.

Add the following lines at the end of main.cf and replace the UID:GUID under virtual_minimum_uid, virtual_maximum_uid, virtual_uid_maps and virtual_gid_maps with the values that you got from tail /etc/passwd or grep vpostfix /etc/passwd.

# Virtual domain config
virtual_mailbox_domains = /etc/postfix/virtual_domains
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
# Make sure you replace these UID:GUID numbers
virtual_minimum_uid = 502
virtual_maximum_uid = 502
virtual_uid_maps = static:502
virtual_gid_maps = static:502
virtual_alias_maps = hash:/etc/postfix/virtual

Edit or create /etc/postfix/virtual_domains. This is a file where all of your domains will be listed. Of course, you’ll have to make sure that MX records of your domains point to the IP of the CentOS box.

#  Put each domain in a separate line.
domain-one.com
domain-two.net
domain-three.org

As you can see, we have three virtual domains, but the myhostname points to www.example.com. If you put one of these three domains as myhostname and mydomain values, you’ll get a warning that the local domain is already listed in the virtual domains list. In addition, if you keep e.g. domain-one.com as a local domain and remove it from the virtual domains list, then you can’t treat domain-one.com as a virtual domain and deliver its mail to /var/mail/vhosts. Don’t use myhostname = localhost either, because if some other mail server has the same config, you’ll get an error that the destination loops back and the mail won’t be delivered. Don’t use www.example.com either, because you won’t be able to send to www.example.com or any other server that have www.example.com as myhostname. So, pick something that’s unique even if you don’t own that domain or that domain is not registered. Mind that if you send an email from this server and analyze the headers, you’ll see that www.example.com is in the headers. Most e-mail servers won’t complain and treat this e-mail as spam, as long as you have a valid MX record for the virtual domains that point to that IP.

01

Create the mail folder, subfolders for the domains and assign the proper permissions.

mkdir /var/mail/vhosts
chgrp -R vpostfix /var/mail
cd /var/mail/vhosts
mkdir domain-one.com
mkdir domain-two.net
mkdir domain-three.org
cd ..
chown -R vpostfix:vpostfix vhosts

Once you do that, postfix will create the “Maildir” folders automatically and assign the proper permissions. Finally, create a file /etc/postfix/vmailbox and add all of the users that will receive e-mails. Here is an example:

joe@domain-one.com        domain-one.com/joe/
bill@domain-one.com       domain-one.com/bill/
@domain-one.com           domain-one.com/catch-all/
joe@domain-two.net        domain-two.net/joe/

Make sure you end up each line with “/”, otherwise mail won’t be delivered.
Virtual user “joe@domain-one.com” (mind that there is no CentOS login for this user, these are all virtual users) will have his email delivered under /var/mail/vhosts/domain-one.com/joe folder. You don’t have to create these subfolders. Once everything is up and running, postfix will take care of creating the Maildir structure (cur, new, tmp).

If you want you can create a catch-all address, see the example above (catch-all). This line tells postfix to get all the emails for the non-existing users in that domain (domain-one.com), which means a lot of spam.

But what if you have a valid CentOS user named bill? Where that email goes? In this case, nowhere. If we want this OS user to receive an email, we’ll have to treat him as a virtual user and add him to a virtual domain. It’s much easier to maintain one list of virtual users and hosts than deal with separate configuration files.

Maybe you’ve noticed that the file with the e-mail addresses (vmailbox) has a hash: prefix in the config file. This is to speed-up lookups. Postfix can use hash: (Berkeley-DB), mySQL or PostgreSQL database to store the e-mail accounts. Check the postfix howto if you want to use mySQL or PostgreSQL. We’ll be dealing with Berkeley DB.

Once we are done with the editing of these files, do the following to create the hashed files (extension .db). You should execute these lines anytime you make a change to these files.

postmap /etc/postfix/virtual
postmap /etc/postfix/vmailbox

Now, everything is ready for postfix to receive an email. Make sure that postfix is running and that it actually listens on port 25. Also make sure that port 25 is opened on the firewall.

ps -eaf | grep postfix
netstat -an | grep :25
iptables --list | grep smtp

You can restart postfix with postfix stop and postfix start or reload the configuration files with postfix reload.

From another domain (e.g. your hotmail or gmail account) send an e-mail to joe@domain-one.com or whatever your domain is. Do :

tail -f /var/log/maillog

You should see something like this.

02

If you check /var/mail/vhosts/domain-one/joe/new folder you’ll see a file with some gibberish name. This is your e-mail that you just sent to joe. But, how will this virtual user retrieve this e-mail? There is a login (the e-mail address), but what’s the password?

Dovecot

In order to retrieve the e-mails, we’ll install Dovecot. Dovecot is an open-source POP and IMAP client.

yum install dovecot
chkconfig dovecot on

As of version 2.0, there are multiple configuration files for Dovecot. The main file is /etc/dovecot/dovecot.conf, but you’ll see a lot of include directives there that point to /etc/dovecot/conf.d folder where we have about 22 configuration files.

Make a copy of dovecot.conf and change only one line.

protocols = imap pop3

Then, go to conf.d folder and change the following lines in the following files.

10-auth.conf

disable_plaintext_auth = no
#!include auth-system.conf.ext
!include auth-passwdfile.conf.ext

10-logging.conf

log_path = /var/log/dovecot.log
auth_verbose = no
auth_debug = no
verbose_ssl = no

10-mail.conf

mail_location = maildir:/var/mail/vhosts/%d/%n
mail_uid = 502
mail_gid = 502
mail_privileged_group = vpostfix

10-master.conf

  unix_listener auth-userdb {
    mode = 0600
    user = postfix
    group =  postfix
  }
  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
  }

10-ssl.conf

ssl = no
# ssl_cert = </etc/ssl/certs/dovecot.pem
# ssl_key = </etc/ssl/private/dovecot.pem

If you look at 10-auth.conf, we commented the line #!include auth-system.conf.ext and uncommented the !include auth-passwdfile.conf.ext. Take a look at this file and you’ll see:

passdb {
  driver = passwd-file
  args = scheme=CRYPT username_format=%u /etc/dovecot/users
}

userdb {
  driver = passwd-file
  args = username_format=%u /etc/dovecot/users
}

This describes how our username/password database will be in the file /etc/dovecot/users. To generate a password with SHA512-CRYPT password scheme do:

doveadm pw -s SHA512-CRYPT

You’ll be prompted to enter a password twice and the output will be similar to this:

03

If you want to use a different password scheme, take a look at this link.

Now, create or open /etc/dovecot/users and copy and paste the password after the username. In my case, I have joe@domain-one.com with some password that I just generated. So the line will be like this.

05

Don’t forget to add 4 colons after the password “::::”. Even if you use the same password for the users, they’ll be encrypted differently.

The problem with this scenario is that the end users won’t have the ability to change their passwords. So, you’ll have to provide them with the password and they won’t be able to reset them. But, there are plenty of perl scripts that can take care of this or you can write your own.

Start dovecot, by simply typing dovecot and check for any errors with tail -f /var/log/dovecot.log. At this point, we should have dovecot running and listening for pop and imap connections.

netstat -an | grep :110
netstat -an | grep :143

If needed, open the firewall for these two ports and let’s check our e-mail. You can do that from the server using the telnet command (yum install telnet if it’s not installed).

06

In the above example, I am testing POP3. For IMAP, do the following.

07

If you want you can test retrieving these emails with a mail client such as Outlook, Opera Mail or any MUA of your preference.

At this point the server can receive e-mails from others and you can retrieve those e-mails from outside using POP and IMAP. What we need to do now is to be able to reply to those e-mails from outside (using MUA of your choice). Nowadays port 25 is blocked at some major providers (Verizon, Comcast for example), so we’ll use SASL in Postfix and we’ll use Dovecot to authenticate the users using the same username/password combination. In addition, we’ll use certificates, so instead of POP3 and IMAP, we’ll use their secure equivalents, POP3s and IMAPs running on ports 995 and 993 respectively. Dovecot should already listen on these ports, so you can allow these ports on the firewall and close 110 and 143.

Postfix

Edit /etc/postfix/main.cf and add the following lines at the end.

# TLS
smtpd_use_tls = yes
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_key_file = /etc/postfix/myserver.key
smtpd_tls_cert_file = /etc/postfix/server.crt
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom

# SASL
smtpd_sasl_type = dovecot
broken_sasl_auth_clients = yes
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination

Then, edit master.cf and remove the comments from the submission part.

submission inet n       -       n       -       -       smtpd
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

Restart postfix after these changes. For information of what these values mean, check the links at the end of this post. If you do netstat -an, you’ll see that postfix is also listening on port 587. Allow this port on the firewall, but don’t close port 25. This port is used for server to server communication.
If you do telnet localhost 587 and type EHLO something.com you should see that postfix replies with STARTTLS.

08

Dovecot

Edit 10-auth.conf and change disable_plaintext_auth = no to disable_plaintext_auth = yes. Then, edit 10-ssl.conf (you did a backup of this file, didn’t you?) and change the following values.

ssl = yes
ssl_cert = </etc/postfix/server.crt
ssl_key = </etc/postfix/myserver.key

We’ll use self-signed certificates, but check www.startssl.com for free certificates. Unlike virtual Apache domains, you don’t need multiple certificates for each virtual domain. Self-signed certificates are fake, so you’ll get a prompt to accept a fake certificate when you try to send/receive an email, but the goal is to show you how to use them, not to be a 100% compliant.

openssl genrsa -out myserver.key 1024
openssl req -new -key myserver.key -out myserver.csr
openssl x509 -req -days 3650 -in myserver.csr -signkey myserver.key -out server.crt

Coopy server.crt and myserver.key under /etc/postfix and restart both postfix and dovecot.

You can test SMTP SSL/TLS on submission port 587.

openssl s_client -starttls smtp -connect localhost:587

Then type ehlo something.com , hit ENTER and then mail from:joe@domain-one.com. If these steps work, you should be OK. To test SASL with postfix and dovecot, type:

doveadm auth -a /var/spool/postfix/private/auth joe@domain-one.com joe'spasswd

You should receive passdb: joe@domain-one.com auth succeeded.

At this point, you should be able to send e-mails from your favorite MUA, but you’ll have to make some changes in order to send and receive. For example, in Outlook, you should use these settings.

09
10

So, no more port 110 and 143. Instead use 995 for POP3s, 587 for SMPT (SASL) and 993 for IMAPs. The username is your e-mail address and the password is the one that you generated with doveadm pw command.

References:

http://www.postfix.org/VIRTUAL_README.html#virtual_mailbox
http://www.postfix.org/SASL_README.html
http://wiki2.dovecot.org/HowTo/PostfixAndDovecotSASL
http://mxtoolbox.com/

Related Articles

18 comments

kavitama April 10, 2014 - 11:10 AM

WOW.
Simple and efficient.
Just one only question: How can i maintain both secure (995+587+993) and non secure (1110+143+25) environments.
Bettere said , which changes should i do to have both configs?

Kliment Andreev April 15, 2014 - 9:04 PM

I’ve checked my config and locally the ports are open (995,587,993,110, 143 and 25). Make sure you are not blocking them with iptables.

webzuru April 17, 2014 - 1:55 PM

Hi, I just registered to ask this question. I like the way you present the tutorials, but I am going crazy about it now working for me. here is the screen shot. ( http://minus.com/i/d3BuvJmTtdwl )

I’ve been searching for tutorial all over the web, but only yours seems good. Can you help me?

My environment:
I am on 2 VPSs, running CentOs 6.5, 32bit fresh install with root access.
one deals with web hosting. i.e. IP 192.168.995.2 points to http://www.my-domain.tld

I am using the second as a mail server (Where I am trying out your tutorials). I have added a record for this server eg: 192.168.77.98 point to mail.my-domain.tld

Can you help me?

Kliment Andreev April 18, 2014 - 12:39 PM

Send me the output of both “postconf” and “postconf -d”. Don’t copy and paste them here. Use pastebin.com

Thanks

webzuru April 22, 2014 - 1:07 PM

Hi Kliment. Thanks for replying back.

Here is pastebin for ” postconf ”
http://pastebin.com/geVpbQqS
and for ” postconf -d”
http://pastebin.com/jnc2eMGD

Just so you know, I have stopped iptables, disabled SELinux. I did all you asked on a fresh CentOs 6.5 VPS again, and I still got the same issue as before. One thing that you may want to know is that, my VPS server is for hosting only emails. So no domain is actually pointing to my VPS, because all my domains point to my other (secondary) VPS machine, which is taking care of hosting my websites. So, I have two VPS. One for email, and other for hosting.

So, I filled “myhostname = http://www.my-site.com” and “mydomain = my-site.com” even though those sites point to my other VPS. But I had no choice. But, I have added A record and MX to mail.my-site.com pointing to my email VPS.

Thanks again

webzuru April 30, 2014 - 12:38 PM

Hey, sorry to disturb you but I am still waiting for any answers you have about my question.

Kliment Andreev April 30, 2014 - 12:51 PM

Sorry I am late. I’ll take a look at the conf files and probably have an answer for you this Friday.

Thanks

Kliment Andreev April 30, 2014 - 4:14 PM

From the screenshot, I can’t tell if the postfix crashes or are you sending a stop signal? Do this:

# service postfix stop
# service postfix start
# tail /var/log/maillog

(send me the last 10-20 lines)

are you able to telnet to port 25?

# yum install telnet (if you don’t have it)
# telnet localhost 25

CTRL-] to quit.

webzuru May 7, 2014 - 11:02 AM

Sorry. I kida had given up :).
Here is a screen host. I did what you asked, first I didn’t have telnet, but I installed it. And still the same problem.

http://i3.minus.com/jku5ffGmrDT0D.jpg

I know this is a big DONT DO, but I could give you my password to my VPS so you could take a look if you wanted too. Since I have nothing in it, and will be re-installing it again, it won’t be a problem.
Sorry for this whole mess. I just can’t find a clue of what the problem could be,

Kliment Andreev May 12, 2014 - 12:58 PM

Send the password to klimenta@iandreev.com and I’ll take a look. I can’t guarantee that I’ll fix it though…BTW, I am using Digital Ocean VPS and all my posts are tested on CentOS 6.5 hosted there.

skzahid July 4, 2014 - 12:18 AM

Dear Kliment Andreev,
I can’t understand How to create specify 3 domain email user id
domain-one.com
i.e-john@domain-one.com
domain-two.net
ie-mou@domain-two.net
domain-three.org
i,e-sona@domain-three.org
pls tell me how to create new email ID

Kliment Andreev July 4, 2014 - 8:31 AM

It is simple. How many domains do you own and want postfix to be a mail server? Let’s say only one e.g. something.com.
How many users need email? Let’s say three e.g. user1, user2 and user3. In that case, create/edit this file (/etc/postfix/virtual_domains) and add the following line.

something.com

Edit or create /etc/postfix/vmailbox and add the following lines.

user1@something.com something.com/user1/
user2@something.com something.com/user2/
user3@something.com something.com/user3/

Tuyen Nguyen July 28, 2014 - 10:01 AM

Hi Kliment,

I just want to say thank you so much for writing this tutorial. I does help me a lot.

I wish you the best.

Tuyen Nguyen

prapdm September 6, 2014 - 6:47 PM

Good job. Very good tutorial, and everything work perfect.

brianhill September 25, 2014 - 6:13 AM

Very useful tutorial. Thank you very much.
But I have one problem for retrieving catch-all email.
After setting the catch-all email like below, which user should i set in dovecot user to retrieve its mail?
@domain-one.com domain-one.com/catch-all/

Kliment Andreev September 27, 2014 - 8:19 AM

Consider catch-all as a regular user that receives e-mail for non-existing users on @domain-one.com. As you can see there is no username in front of

@domain-one.com domain-one.com/catch-all/

So, if I send an email to asdfasdf@domain-one.com and there is no such user, it will end up in catch-all mailbox, therefore you have to create this user in dovecot.

shameeksl October 14, 2014 - 5:44 AM

Thanks for this tutorial. But is it possible to enable end users to change their passwords?

Kliment Andreev October 16, 2014 - 10:10 AM

One option is to create a web page that allows users to reset their passwords, but you need to implement some sort of double authentication. Otherwise everyone can reset anyone’s password by simply knowing their e-mail. It’s not that easy, you have to implement a whole web mail type of program.

Leave a Comment

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More