Nearly two years after the release of Slackware Linux 14.2 and three years after my original blog post, I'm glad to finally provide an update. Sorry for the delay. The core packages that make up the mail server have received lots of updates. Besides the version and configuration updates I've also added some information for setting up Dovecot Pigeonhole and configuring email filtering.
This post contains some suggestions for a mildly secure mail server running on a Slackware Linux host. The guide assumes a default, fresh installation of Slackware64 14.2 that includes at least the A/, AP/, D/, L/, and N/ package series. By the end of the tutorial, you will have:
The nginx webserver is at version 1.14.2 as of this writing, up from 1.8.0 used in my previous guide. HTTP/2 is now natively supported instead of being provided by the SPDY module. You can still hide the server headers in the source using the patches from the previous post. This is in addition to the server_tokens configuration option. You can also compile nginx with the ngx_headers_more module. Keep in mind there are still many ways to detect which web server you're running and tools like nmap are particularly good at doing it. It's up to you to decide how much effort you want to put into this.
Create your user and use that to run the SlackBuild. For newer nginx versions, simply updating the version number in the nginx.SlackBuild file before running it usually does the trick.
Once again, if you want to enable syntax highlighlting for the nginx.conf file in vim, copy the contents of the contrib/vim directory from the extracted nginx source to ~/.vim.
In the previous post I talked about some SSL certificate providers and curves. We use ECDSA certificates and the secp384r1 curve here.
At minimum, your nginx.conf file will need an events section which can be left blank to activate the defaults or modified depending on the load you're expecting on the server. Set worker_processes equal to the number of real CPU cores on the machine.
Create your /etc/nginx/conf.d/site.conf
We're going to skip setting up memcached for now. The newer versions are for PHP 7 which will not be a Slackware default until version 15 is out or so. Rerfer to my old guide for help setting up memcached.
Change the permissions of /var/lib/php since we're using nginx instead of httpd to run PHP:
If you're getting any compilation errors for nginx, PHP, or any package regarding graphics, you'll need some X11 libraries. These are most likely not installed if you're working on a headless server. Get the needed dependencies from a Slackware mirror:
PHP-FPM with FastCGI in nginx
We'll set up PHP-FPM a bit different this time around by putting its configuration in a separate file. First you'll need to make sure the startup script is executable. Start php-fpm at least once to let it create its default configuration files:
We'll add our own /etc/php-fpm.d/mailserver.conf with the following content:
Make sure this file is included in /etc/php-fpm.conf. Edit the error_log to log/php-fpm/php-fpm.log to keep everything in place while you're there.
Install the php-imagick extension too. This will come in handy for Roundcube later.
Lots of development has been done on Dovecot since my last guide. Dovecot is now on version 2.3.4 as of this writing, up from version 2.2.16 from my past guide. We are still going to use Postfix Admin to store the user information in this setup instead of creating a Unix account for each mailbox. The email will be stored in /var/vmail organized by domain and user, so the email for email@example.com would be stored in /var/vmail/example.org/admin. We'll create a single user to own the mailboxes on the system and let Dovecot manage them. Dovecot will also need its own user and group:
You can use whatever names and IDs you want but make sure it doesn't interfere with what Slackware already uses. Copy over the sample configuration files and replace some of them
We'll set up /etc/dovecot/dovecot-sql.conf.ext first with the database connection information
Of course, set dbname, user, and password appropriately. These are the same credentials Postfix Admin will use, so keep that in mind. We're not creating the database or user yet. That will come later. Now let's add the password_query and user_query to /etc/dovecot/dovecot-sql.conf.ext:
Edit /etc/dovecot/conf.d/10-auth.conf and enable the SQL configuration file we just modified. You will need to disable plaintext authentication unless it's already encrypted through TLS. We'll also disable the auth-system.conf.ext file that's loaded by default. Don't worry about authenticating with plain text; your connection will be secured with TLS so this is safe:
Next, change the UIDs and mail location in /etc/dovecot/conf.d/10-mail.conf to whatever you set up earlier:
Dovecot has changed some of its TLS options. I use the secp384r1 curve for my ECDSA SSL certificates. My /etc/dovecot/conf.d/10-ssl.conf file will looks like this:
In order to have Dovecot authenticate, uncomment the user, group, and mode lines in the unix_listener auth-userdb section of the service auth block in /etc/dovecot/conf.d/10-master.conf. Additionally, set up a unix listener for **Postfix. Uncomment that section as well and add postfix as the user and group. We'll create those later when we're setting up *Postfix**. Set up the stats-writer permissions, too.
A new addition to this guide is Dovecot Pigeonhole. This adds support for the Sieve language (RFC 5228) and the ManageSieve protocol (RFC 5804) to Dovecot. Coupled with a Roundcube plugin, this will allow us to filter email based on any number of factors. For example, you can create an rule in Roundcube that will automatically place email coming from @bank.com to a Bank Notifications folder.
Grab the SlackBuild and run it. The package will install some configuration files in the Dovecot documentation directory. Copy these to /etc/dovecot/conf.d:
Let's make some changes to these files
First, edit /etc/dovecot/conf.d/20-lmtp.conf, and add
For /etc/dovecot/conf.d/15-lda.conf, add
For /etc/dovecot/conf.d/10-mail.conf, add
In /etc/dovecot/conf.d/20-managesieve.conf, add
Lastly, edit /etc/dovecot/conf.d/90-sieve.conf, and add
Now we need to create some files that are needed for our configuration to work:
Add postfix to the dovecot group. This is needed by amavisd-new later.
Postfix is now at version 3.3.2 instead of 3.0.3 we used in the last guide. There haven't been any major configuration changes so we'll just proceed as normal. Create the required user and group before going any further:
Run the SlackBuild with support for MySQL and install it
We're going to strip the email client and IP address of each mail you send. Remember this is just security through obscurity and you'll be violating RFC 2045 if you remove the MIME-Version string. It's still useful to prevent uninitiated recipients from figuring out where you are sending your mail from. Add these to the file /etc/postfix/header_checks for now. We'll enable it in the Postfix configuration later.
Postfix Main Configuration
We only need to make some minor adjustments to /etc/postfix/main.cf:
The Dovecot authentication section will remain the same as in my previous guide too
With newer versions of Postfix, you can now include both ECDSA and RSA certificates in your configuration. I'm disabling SSLv2, SSLv3, and TLSv1.0 in the configuration below. We're excluding known insecure ciphers and setting the encryption level to may. You can set this to encrypt, if you want, but the Postfix documentation strongly advises against this for a public facing server.
Feel free to set these parameters to whatever fits your needs
Now we set up milters along with the Postgrey and OpenDKIM sockets
We need to point Postfix to our database map files. Use the same UID and GID that was used for the Dovecot
Last, set up Dovecot and Amavis
Postfix Master Configuration
Moving on to /etc/postfix/master.cf, we'll set up SMTP with TLS on port 587 and SMTPS on port 465. Most options can just be enabled by uncommenting them. Make sure you comment out the -o syslog_name=postfix/$service_name option right under relay.
For Amavis, just make sure you set the max number of processes it's allowed to run. In this example, it's 4 (the same as the nginx configuration).
Before you continue, make an /etc/aliases file and generate the database:
Amavis with ClamAV and SpamAssassin
Let's set up virus and spam checking. We are using amavisd-new here. It will be the interface bewtween Postfix, ClamAV and SpamAssassin.
We need a ton of things for Amavis and SpamAssassin. You can go the SlackBuilds route or just use CPAN. If you need to keep track of what you have installed with sbopkg, slackpkg or whatever Slackware "package manager" you use then go the SlackBuilds route. Before getting started you may want to install the re2c, zeromq, pyzor, unrar, arj, cabextract, lzop, nomarch, p7zip, libmspack, and GeoIPpackages to allow SpamAssassin and ClamAV to handle different compressed files and to satisfy some of their dependencies.
If you're using CPAN, here's what you need to install all the Perl modules:
After SpamAssassin is installed, edit the /etc/spamassassin.conf file and set the following options. You only really need ENABLED but the rest are a good idea.
The SpamAssassin source no longer includes rules, so you'll have to download them. Run sa-update to do this.
You'll need a user and group created first.
Run the SlackBuild with the COUNTRY variable set to your country of choice.
Edit the /etc/clamd.conf file and change the LocalSocket and LocalSocketGroup options
Let's install amavisd-new before we update the ClamAV virus definitions.
Create a user and group before you run the script.
While we're at it, go ahead and add the amavis user to the clamav group and vice versa.
Uncomment the lines @bypass_virus_checks_maps and @bypass_spam_checks_maps at the top of /etc/amavisd.conf and add the following
Go down a bit further and uncomment @lookup_sql_dsn, then modify it to connect to your database using the Unix socket and the proper credentials. Amavis uses the DBD::mysql Perl module. The documentation states setting the host value to localhost will use the socket. This configuration will enable spam checking for the domains you've added to your database either manually or through Postfix Admin
There are a couple of other settings we can change. For instance, make sure you also set $max_servers to the same number of processes you allowed Amavis to use in /etc/postfix/master.cf. Setting the $sa_tag_level_deflt opton to a large negative number will ensure that spam headers are added to every single email. Change the user and group to the ones you created earlier, set up a home directory for configuraiton files and quarantine emails, and set your domain name (not the same as your hostname). Make sure you also uncomment the amavis section in @av_scanners. We are also setting the Unix socket for Amavis here too.
Comment out $inet_socket_port and $interface_policy('10026'). There may already be an $interface_policy set up for AM.PDP-SOCK. Comment it out or modify it to what I have above. Notice we're placing the socket in the Postfix queue directory, /var/spool/postfix. We'll need to create the directory to hold the socket and assign some permissions as well.
Use the following commands to let Postfix create the amavis-accept PID:
In order for the above to work, we need to set a $forward_method in /etc/amavisd.conf. In my setup, amavisd-new would ignore that configuration option and insist on using TCP port 10025. I could not for the life of me figure out how to get it to use a socket no matter what value I tried. I ended up having to modify the /usr/sbin/amavisd Perl script directly. Look around line 926 or search for 10025. Comment out the existing $forward_method and replace it with this:
Go through the rest of /etc/amavisd.conf file and modify any settings you might want changed.
Now go ahead and update your virus database by running freshclam as root. Don't worry if you get a message from freshclam saying clamd was not updated. This is because we have not started clamd yet. We also need to fix some permissions to get all three to play nicely.
Set up the user and group first, then run the SlackBuild.
Set POSTGREYUSR, POSTGREYGRP, POSTGREYUID and POSTGREYGID in the SlackBuild to the values you set earlier when you created them. You can get an updated version of postgrey_whitelist_clients from the Postgrey site and place it in /etc/postfix, replacing the one included with the SlackBuild.
Edit the /etc/rc.d/rc.postgrey script and set USER, GROUP, and HOST to their proper values. Feel free to get rid of PORT. Find the postgrey_start() function and edit the postgrey flags to make sure it uses a socket instead of TCP.
The extracted source includes an init script, too. It's in contrib/postgrey.init if you want to use it.
OpenDKIM, DNS and Building Trust
The setup up to this point should be pretty much complete and meet most people's needs. Some mail servers are quite picky when it comes to receiving email. Gmail particularly doesn't like when an email is not signed. The next section will walk you through signing your email with DomainKeys Identified Mail and setting up Sender Policy Framework. If you are using a hosting provider for your server, you will need to contact them and have them set up a PTR record for your IP address. This is also known as rDNS. Some mail servers will reject your email if the IP you are sending from does not point back to your domain name. In general, it should look something like this:
188.8.131.52.in-addr.arpa PTR 600 example.org
If you're hosting at home, you can try asking your ISP to set this up for you but it is unlikely they'll want to. They may be willing if you purchase a static IP. You'll also need to add MX records to your domain's DNS records. You can add something like this
example.org MX 600 10 mail.example.org
That's assuming mail.example.org points to your mail sever's IP and you want a priority of 10. You can ask your DNS provider to add these for you. Check that the record has propagated with host:
We are using MariaDB here so set the USE_MYSQL variable to yes and run the SlackBuild. I used a modified version of CentOS's init script for rc.opendkim, but feel free to grab the one included in the source in the contrib/init/generic/ directory.
Once it's installed, we'll need to set up a basic configuration file. You can copy the sample one from opendkim/opendkim.conf.simple in the extracted source to /etc/opendkim and add the user and group we created earlier. Note that the SlackBuild already does this for you:
You'll notice my init script will automatically create some default keys for you in /etc/opendkim/keys and create the directory if it doesn't exist. We're using Unix sockets in this guide, so let's change a line in /etc/opendkim.conf:
We'll generate a 2048 bit key with the opendkim-genkey command. You can try something stronger like 4096 bits, but RFC 6376 suggests it might not fit in a 512 byte DNS UDP response. See section 3.3.3 Key Sizes for more information.
The selector is simply something to tell your key apart once you add it to your DNS records. You'll end up with two files, mailsvr.private, which is your key, and mailsvr.txt which has a nicely formatted record you'll need to add to your DNS zone. If you don't manage your own DNS or have access to your zone file, simply copy the text starting with v=DKIM1 as a TXT record in whatever control panel your DNS provider uses. For the example above, this is what I got (truncated for demonstration):
You'll need to wait a while before the DNS record propagates but once it does you can check it with dig
Sender Policy Framework
This basically consists of adding another TXT record to your DNS zone. There used to be an SPF type record but this was removed in RFC 7208. You'll want to add something like this:
We'll set up MariaDB first (the scripts are still named rc.mysqld):
Now create the user and database we'll be using for Roundcube, Postfix Admin, and all other components.
Start Up Email
Let's start up the services we have so far in order to get all necessary files created properly. We'll need the services running in order to set up Roundcube and Postfix Admin correctly.
You can place your startup commands in /etc/rc.d/rc.local and the stop commands in /etc/rc.d/rc.local_shutdown. Make sure both are executable and add this content
Roundcube and Postfix Admin
The same advice from my previous post regarding web based interfaces applies.
The most notable change between the 2.92 version used in my previous guide 3.2.1 as of this writing are the ability to reset passwords via email/SMS and the move of most of the files to the public/ directory.
Make a copy of config.inc.php as config.local.php and make your changes there. The ones we need for this guide are
This tells Postfix Admin to use Dovecot's crypt() scheme for passwords and to connect to MariaDB using Unix sockets. Next, visit https://mail.example.org/postfixadmin/setup.php and complete the setup. Make sure you add your $CONF['setup_password'] obtained from setup.php to config.local.php. Do this before clicking on Add Admin.
We will need to move all our Dovecot configuration to a single file in order to appease Postfix Admin. It seems it has trouble parsing the !include conf.d/*.conf line in /etc/dovecot/dovecot.conf
Add Email Domains and Mailboxes
Log in to https://mail.example.org/postfixadmin and head over to Domain List > New Domain. Fill in whatever works for you here to add a new domain, then head to Virtual List > Add Mailbox and create your first user. I set up example.org as an email domain and firstname.lastname@example.org as my first user. Doing this will generate the needed database schema that Postfix will use. Go ahead and play around with this and make sure your aliases are set up
The latest version of Roundcube is now 1.3.8, up from 1.1.1 used in my previous guide. We've already set up the nginx configuration for Postfix Admin and Roundcube so now we just have to download their files and run the installers.
We'll need to create a database for Roundcube to use. Log in to MariaDB with mysql -u root -p and create it. Make sure you use a strong password.
Roundcube includes an SQL file that can create the necessary database structure for you
I recommend you check out the INSTALL file included in the source for a more complete guide on the installation. Now head over to https://mail.example.org/installer and make sure everything is OK in the Check environment section. You will probably need to temporarily comment out the location blocks in /etc/nginx/conf.d/mail.example.org.conf that block access to Roundcube URLs such as /installer.
My installation complained about the date.timezone setting in php.ini so I had to set that. Get a list of supported time zones from here. You may need to restart nginx and php-fpm for the changes to take effect.
Click Next when you're done to move on to the Create config section. You can leave most of these settings alone. If you want to know what a specific setting does, check out Roundcube's wiki. Fill in the Database setup section with the user and database you created earlier.
Make sure you do not enable spellchecking support. If you do, Roundcube will connect to external services to check your spelling. Why would we go through all this trouble to have a third party see every word we type?
IMAP and SMTP Settings
We're going to set default_host to tls://mail.example.org in the IMAP Settings section. In the SMTP Settings section, set smtp_server to tls://mail.example.org , smtp_port to 587 and check the Use the current IMAP username and password for SMTP authentication option. Click on Create config.
After it's done writing your configuraiton file, go through config/config.inc.php and see if you'd like to change anything.
You can enable whichever plugins best fit your needs. The only one we really need to set up the email filtering we discussed is managesieve.
Go ahead and create some filters in Roundcube and make sure emails are going to the right folders. Test sending email to and from your own domain as well as to external domains. Connect your email clients. They should automatically detect your sever's open ports but you can use the following settings to set it up manually. You can substitute the hostname for example.org if that points to your server's IP as well.
The first time you receive an email, it'll be greylisted thanks to Postgrey. Check /var/log/maillog:
Most mail servers will try once more after some time and will be allowed the second time they send. Spammers generally only try once so this should stop some most of the common spam you could receive. Once the mail passes through, Amavis will check for viruses and other malware.
You can also test your anti-virus using the EICAR test file. Send yourself an email from another mail server with only the following string in the body:
It will be detected as a virus and you'll see this in the log as well:
In a similar fashion, you can test your spam filter using the GTUBE. Send a message from another mail server to yourself with only the following string in the body:
It will be detected as spam and you'll see this in the log as well:
You'll notice in your /var/log/maillog file that there will be a ton of bots and compromised third party servers trying to relay using your server, trying to log in using common usernames (admin, support, test, etc) and generally just trying to wreak havoc on your mail server. The setup suggestions I described in this article should prevent most of those attacks. You may consider trying something like fail2ban to automatically ban these IPs. Get yourself a nice firewall using AlienBob's Easy Firewall Generator and block anything you don't need.
I tested this set up by following it step by step on a fresh server and it worked for me. I may have missed some things. Contact me if you have any comments. Encrypted email is strongly encouraged and preferred.
This post contains some suggestions for a mildly secure mail server running on a Slackware Linux host. The guide assumes a default, fresh installation of Slackware64 14.1 that includes at least the A/, AP/, D/, L/, and N/ package series. By the end of the tutorial, you will have:
Postfix for encrypted connections over SMTP and removing identifying information from the email headers
Dovecot for local mail directories and encrypted connections over POP and IMAP
MariaDB a drop-in replacement for MySQL to store mailbox information
Postgrey which will require unknown senders to resend their mail, eliminating most spam
SpamAssassin for e-mail spam filtering based on content-matching rules
ClamAV to detect trojans, viruses, malware and other malicious threats in your email
There is a line of a song I enjoy that says, "How come 24 hours sometimes seem to slip into days?" The song goes on about homesickness and being lonely on a trip. For the longest time, I identified with that line in regards to aging. The older we get, the faster time seems to pass us by. What are we doing with the limited time we have available?
After a week of testing and development, I've finally switched my site over to Jekyll, the static site generator. This allowed me to get rid of PHP and MySQL, which I feel cause too much overhead for a simple site like mine. I also felt Apache had too many features I did not use so I got rid of that and switched to nginx instead. Also note the TLD change, gerardozamudio.mx
The great thing about Jekyll is it's easy to use and configure. There's a great installation guide at the official Jekyll site already so I won't go over that again. Just note you'll need Node.js and Ruby installed in order for it to work.
First, I created the directory that will hold my new site in my shell user's home directory:
My site used to run on WordPress so most of the content was stored in the MySQL database. I ran the Jekyll WordPress importer with the following options:
Note that Slackware uses the default PID location of /var/run/mysql/mysql.pid for MySQL so you'll have to set that. This gave me a nice structured directory with all my posts converted to Markdown complete with YAML Front Matter containing the author, comments, tags, etc. WordPress keeps images in /wp-content/uploads by default, but I made an images directory in the root of my site for simpler management, so a little sed magic fixed that:
I also noticed the posts had been saved with a .markdown extension. Those are not really important in Linux, but I wanted to fix them for my own sanity:
Then it was just a matter of downloading the uploads directory from wp-content in my WordPress site's root directory into my new Jekyll site's root directory and renaming it to images.
There are some features of WordPress that Jekyll, being a static site generator, does not implement by default. Luckily I future proofed my site so it was easy to port images as well as HTML used in the posts. I didn't have many posts that used a featured image so it was simply a matter of adding a new ![Alt text](/images/leading-image.jpg) line to the top of each post.
Speaking of images, I am using the captionss CSS library to easily add a nice looking caption to my images. Again, Jekyll does not offer this out of the box but it was a good excuse to get familiar with Liquid and write my own tag. I didn't want to bother with surrounding my images with HTML every time I wanted to add a caption so I created a cap.html file in my _includes folder inside my Jekyll site directory with the following contents:
Whenever I want to use an image with a caption, I can just use the following:
WordPress uses the <!--more--> tag to signal post excerpts, and automatically generates a Read more... link. Jekyll does not have this by default, but it can easily be implemented using a loop in the site's posts index with the splitLiquid filter:
I didn't bother reinventing the wheel with this one, and shamelessly stole Reyhan Dhuny's archive.html code to make mine. I only added a hyphen between post names and dates and I was good to go.
Finally, I built the site and put my files in the web server's directory:
Good old nginx. It's hard to believe it's been a decade since this web server came about and it's certainly matured a lot since then. Development is fast and looks to be stable, so I decided to go with the mainline releases. My main goal was serving plain static content so I did not need any of the extra modules that are built in by default. I created a system user to run the daemon:
I talked about adding a certificate in a previous post, but I decided to take that a step further and switch to ECDSA for my SSL certificate. Unfortunately Gandi does not offer such certificates so I had to move to Comodo in order to get one. I also set up HSTS and PFS to completely get rid of plain HTTP access to the server. At least Qualys seems to think I did a good job. If you look closely at the configure options above you'll notice I also enabled SPDY, the starting point for HTTP 2.0. Feel free to check it out.