## Hardening WordPress against hacking attempts

The WordPress Codex states:

Security in WordPress is taken very seriously

This may be the case, but in reality, you yourself have to take some additional measures so that you won’t have a false sense of security.

With the default settings of WordPress and PHP, the minute you host Wordpress and give access to one non-security-conscientious administrative user, your entire hosting environment should be considered as compromised.

The general problem with WordPress and PHP is that rather than thinking about which few essential features to turn on (whitelisting), you have to think about dozens of insecure features to turn off (blacklisting).

This excellent article (“Common WordPress Malware Infections”) gives you an overview what you’re up against when it comes to protecting WordPress from Malware.

Below are a couple of suggestions that should be undertaken, starting with the most important ones.

## Disable WordPress File Editing

WordPress comes with the PHP file editor enabled by default. One of the most important rules of server security is that you never, ever, allow users to execute arbitrary program code. This is just inviting desaster. All it takes is the admin password to be stolen/sniffed/guessed to allow the WordPress PHP code to be injected with PHP malware. Then, if you haven’t taken other restricting measures in PHP.ini (see section below), PHP may now

• Include /etc/passwd and expose the names of all user accounts publicly
• Read database passwords from wp-config.php of all other WordPress installations and modify or even delete database records
• Read source code of other web applications
• etc.
• Modify writable files
• Inject more malware
• etc.
• Use PHP’s curl functions to make remote requests
• Turns your server into part of a botnet

So amongst the first things to do when hosting WordPress, is to disable file editing capabilities:

define('DISALLOW_FILE_EDIT', true);

But that measure assumes that WordPress plus third-party Plugins are secure enough to improve their own security, which one cannot assume, so it is better to…

## The “Big Stick”: Remove Write File Permissions

I’ll posit here something that I believe to be self-evident:

It is safer to make WordPress files read-only and thus disallow frequent WordPress (and third-party Plugin) upgrades than it is to allow Wordpress (and third-party Plugins) to self-modify.

Until I learn that this postulate is incorrect, I’ll propose that you make all WordPress files (with the obvious exception of the uploads directory) owned by root, and writable only to root, interpreted by a non-root user. This will leverage the security inherent in the Linux Kernel:

find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
chown -R root:root .
chown -R www-data:www-data wp-content/uploads

Note that you still can upgrade WordPress from time to time manually. You could even write a shell script for it.

## Restrict serving of files

Disable direct access to wp-config.php which contains very sensitive information which would be revealed should the PHP not be processed correctly. In Nginx:

location = /wp-config.php {
deny all;
}

Disable PHP execution in the uploads directory. In Nginx:

location ~* /(?:uploads|files)/.*.php${ deny all; }  ## Restrict PHP I’ll refer the reader to already written excellent external articles – please do implement the suggestions therein: Hardening PHP from PHP.ini 25 PHP Security Best Practices ## Host WordPress in a Virualization Environment In addition to all of the above, any kind of publicly exposed web application (not just WordPress) should really be hosted in an isolated environment. Docker seems promising for this purpose. I found the following great external tutorial about generating a LAMP Docker image: WordPress Developer’s Intro To Docker WordPress Developer’s Intro To Docker, Part Two ## no.php – Transparent reverse proxy written in PHP that allows you to not have to write PHP any more This little project will probably be my only contribution to the world of PHP. The code is at https://github.com/michaelfranzl/no.php This short, single-file, 80-line PHP script is a simple and fully transparent HTTP(S) reverse proxy written in PHP that allows you to never have to use PHP again for a new project, if you feel so inclined, for example if you are forced to host on a fully 3rd-party-managed server where you can’t do more than run PHP and upload files via FTP. The PHP script simply reads all requests from a browser pointed to it, forwards them (via PHP’s curl library) to a web application listening at another URL (e.g. on a more powerful, more secure, more private, or more capable server in a different data center), and returns the responses transparently and unmodified. Supports: • Regular and XMLHttpRequests (AJAX) • All HTTP headers without discrimination • GET and POST verbs • Content types (HTTP payload) without discrimination • Redirects (internal redirects are rewritten to relative URIs) Does not support (or not tested): • HTTP verbs other than GET and POST (but these are usually emulated anyway) • HTTP greater than version 1.1 (e.g. reusable connections) • Upgrade to websocket (persistent connections) • Multipart content type ## Usage illustrated by the standard example You have a non-PHP web application (called the “backend”) listening on https://myapp.backend.com:3000 but due to constraints you must make it available on a shared hosting server called https://example.com/subdir which only supports PHP and can’t be configured at all. On latter server, Apache (or Nginx, doesn’t matter) will usually do the following: 1. If a URI points to a .php file, this file will be interpreted 2. If a URI points to a file that is not existing, a 404 status will be returned. Using no.php, to accomodate the second case, all URIs of the proxied web app (including static files) must be appended to the URI https://example.com/subdir/no.php. For example: https://example.com/subdir/no.php/images/image.png https://example.com/subdir/no.php/people/15/edit If your backend app supports that extra /subdir/no.php prefix to all paths, you are all set and ready to use no.php. Then: 1. Simply copy no.php into the subdir directory of example.com 2. Change $backend_url in no.php to "https://myapp.backend.com:3000"
3. Point a browser to https://example.com/subdir/no.php

## Project status

Experimental. Use only if you know what you are doing.

## How to convert images to PDF with paper and image size, without ImageMagick

ImageMagick’s convert tool is handy for converting a series of images into a PDF. Just for future reference, here is one method how you can achieve the same without convert. It is useful if you have 1-bit PBM images (e.g. scanned text) at hand:

cat *.pbm | pnmtops -setpage -width 6 -height 8 -imagewidth 3 - | ps2pdf -dEPSFitPage - > book.pdf

This command concatenates all .pbm files, pipes the data to pnmtops to create an intermediary PostScript file on-the-fly, and pipes this PostScript file to ps2pdf to create the final pdf.

With the -width and -height parameter you specify the paper size of the generated PDF in inches, which should correspond to the paper size of the original book. With the -imagewidth parameter you specify the width that your scan/photo takes up on the page.

## 100% HTTPS in the internet? Non-Profit makes it possible!

HTTPS on 100% of websites in the internet? This just has gotten a lot easier! Let’s Encrypt is a free, automated, and open certificate authority (CA), run for the public’s benefit. Let’s Encrypt is a service provided by the Internet Security Research Group (ISRG), a Section 501(c)(3) Non-Profit entity dedicated to reduce financial, technological, and education barriers to secure communication over the Internet.

Let’s Encrypt offers free-of-cost certificates that can be used for HTTPS websites, even when these websites are ran for commercial purposes. Unlike traditional CA’s they don’t require cumbersome registration, paperwork, set-up and payment. The certificates are fetched in an automated way through an API (the ACME Protocol — Automatic Certificate Management Environment), which includes steps to prove that you have control over a domain.

Dedicated to transparency, generated certificates are registered and submitted to Certificate Transparency logs. Here is the generous legal Subscriber Agreement.

Automated API? This sounds too complicated! It is actually not. There are a number of API libraries and clients available that do the work for you. One of them is Certbot. It is a regular command-line program written in Python and the source code is available on Github.

After downloading the certbot-auto script (see their documentation), fetching certificates consists of just one command line (in this example certs for 3 domains are fetched in one command with the -d switch):

certbot-auto certonly --webroot -w /var/www/example -d example.com -d www.example.com -d blah.example.com

With the -w  flag you tell the script where to put temporary static files (a sub-folder .well-known  will be created) that, during the API control flow, serve as proof to the CA’s server that you have control over the domain. This is identical to Google’s method of verifying a domain for Google Analytics or Google Webmaster Tools by hosting a static text file.

Eventually, the (already chained, which is nice!) certificate and private key are copied into /etc/letsencrypt/live/example.com/ :

fullchain.pem
privkey.pem

Then it is only a matter of pointing your web server (Nginx, Apache, etc.) to these two files, and that’s trivial.

Let’s Encrypt certificates are valid for 90 days. The automatic renewal of ALL certificates that you have loaded to your machine is as easy as …

./certbot-auto renew

… which they suggest should be put into a Cron job, run twice daily. It will renew the certificates just in time. No longer do you have to set a reminder in your calendar to renew a certificate, and then copy-paste it manually!

A bit of a downside is that Let’s Encrypt unfortunately doesn’t support wildcard domain certificates. For these, you still have to pay money to some other CA’s who support them. But in above shown code example, you would generate only 1 certificate for the domains example.com and its two subdomains www.example.com and blah.example.com. The two subdomains are listed in the Subject Alternative Name field of the certificate, which is as close to wildcard subdomains as it gets. But except for SAAS providers and other specialized businesses, not having wildcard certificates should not be too big of an issue, especially when one can automate the certificate setup.

On the upside, they even made sure that their certificates work down to Windows XP!

Today, I set up 3 sites with Let’s Encrypt (one of them had several subdomains), and it was a matter of a few minutes. It literally took me longer to configure proper redirects in Nginx (no fault of Nginx, I just keep forgetting how it’s done properly) than to fetch all the certificates. And it even gave me time to write this blog post!

Honestly, I never agreed with the fact that for commercial certificate authorities, one has to pay 1000, 100 or even 30 bucks per certificate per year. Where’s the work invested into such a certificate that is worth so much? The generation of a certificate is automated, and is done in a fraction of a second on the CPU. Anyway, that now seems to be a thing of the past.

A big Thumbs-up and Thanks go to the Let’s Encrypt CA, the ISRG, and to Non-Profit enterprises in general! I believe that Non-Profits are the Magic Way of the Future!

Icon made by Freepik from www.flaticon.com

## OpenGL programming in Python: pyglpainter

This was a recent hobby programming project of mine for use in a CNC application, using Python and OpenGL. The source code is available at https://github.com/michaelfranzl/pyglpainter .

This Python module provides the class PainterWidget, extending PyQt5’s QGLWidget class with boilerplate code neccessary for applications which want to build a classical orthagnoal 3D world in which the user can interactively navigate with the mouse via the classical (and expected) Pan-Zoom-Rotate paradigm implemented via a virtual trackball (using quaternions for rotations).

This class is especially useful for technical visualizations in 3D space. It provides a simple Python API to draw raw OpenGL primitives (LINES, LINE_STRIP, TRIANGLES, etc.) as well as a number of useful composite primitives rendered by this class itself (Grid, Star, CoordSystem, Text, etc., see files in classes/items). As a bonus, all objects/items can either be drawn as real 3D world entities which optionally support “billboard” mode (fully camera-aligned or arbitrary- axis aligned), or as a 2D overlay.

It uses the “modern”, shader-based, OpenGL API rather than the deprecated “fixed pipeline” and was developed for Python version 3 and Qt version 5.

Model, View and Projection matrices are calculated on the CPU, and then utilized in the GPU.

Qt has been chosen not only because it provides the GL environment but also vector, matrix and quaternion math. A port of this Python code into native Qt C++ is therefore trivial.

Look at example.py, part of this project, to see how this class can be used. If you need more functionality, consider subclassing.

Most of the time, calls to item_create() are enough to build a 3D world with interesting objects in it (the name for these objects here is “items”). Items can be rendered with different shaders.

This project was originally created for a CNC application, but then extracted from this application and made multi-purpose. The author believes it contains the simplest and shortest code to quickly utilize the basic and raw powers of OpenGL. To keep code simple and short, the project was optimized for technical, line- and triangle based primitives, not the realism that game engines strive for. The simple shaders included in this project will draw aliased lines and the output therefore will look more like computer graphics of the 80’s. But “modern” OpenGL offloads many things into shaders anyway.

This class can either be used for teaching purposes, experimentation, or as a visualization backend for production-class applications.

Left Button drag left/right/up/down: Rotate camera left/right/up/down

Middle Button drag left/right/up/down: Move camera left/right/up/down

Wheel rotate up/down: Move camera ahead/back

Right Button drag up/down: Move camera ahead/back (same as wheel)

The FOV (Field of View) is held constant. “Zooming” is rather moving the camera forward alongs its look axis, which is more natural than changing the FOV of the camera. Even cameras in movies and TV series nowadays very, very rarely zoom.

## Exim and Spamassassin: Rewriting headers, adding SPAM and Score to Subject

This tutorial is a follow-up to my article Setting up Exim4 Mail Transfer Agent with Anti-Spam, Greylisting and Anti-Malware.

I finally got around solving this problem: If an email has a certain spam score, above a certain threshold, Exim should rewrite the Subject header to contain the string *** SPAM (x.x points) *** {original subject}

Spamassassin has a configuration option to rewrite a subject header in its configuration file /etc/spamassassin/local.cf  …

rewrite_header Subject ***SPAM***

… but this is misleading, because it is used only when Spamassassin is used stand-alone. If used in combination with a MTA (Mail Transfer Agent) like Exim, the MTA is ultimately responsible for modifying emails. So, the solution lies in the proper configuration of Exim. To modify an already accepted message, the Exim documentation suggests a System Filter. You can set it up like this:

Enable the system filter in your main Exim configuration file. Add to it:

system_filter = /etc/exim4/system.filter
system_filter_user = Debian-exim

Then create the file /etc/exim4/system.filter , set proper ownership and permission, then insert:

if $header_X-Spam-Score matches "^[^-0][0-9\.]+" and${sg{$header_X-Spam-Score:}{\\.}{}} is above 50 then headers add "Old-Subject:$h_subject"
headers add "Subject: *** SPAM ($header_X-Spam_score points) ***$h_old-subject"
endif

This means: If the header $header_X-Spam_score_int is present (has been added by Exim in the acl_check_data ACL section, see my previous tutorial), and is more than 50 (this is 5.0), rewrite the Subject header. The regular expression checks if the spam score is valid and not negative. Note that in the acl_check_data section of the Exim config, you can deny a message above a certain spam score threshold. This means, in combination with this System Filter, you can do the following: • If spam score is above 10, reject/bounce email from the ACL. • If spam score is above 5, rewrite the Subject. ## XeLaTeX: Unicode font fallback for unsupported characters Traditionally I only used to use LaTeX to typeset documents, and it works perfectly when you have a single language script (e.g. only English or German). But as soon as you want to typeset Unicode text in multiple languages, you’re quickly out of luck. LaTeX is just not made for Unicode, and you need a lot of helper packages, documentation reading, and complicated configuration in your document to get it all right. All I wanted was to typeset the following Unicode text. It contains regular latin characters, chinese characters, modern greek and polytonic (ancient) greek. Latin text. Chinese text: 紫薇北斗星 Modern greek: Διαμ πριμα εσθ ατ, κυο πχιλωσοπηια Ancient greek: Μῆνιν ἄειδε, θεά, Πηληϊάδεω Ἀχιλῆος. And regular latin text. I thought it was a simple task. I thought: let’s just use XeLaTeX, which has out-of-the-box Unicode support. In the end, it was a simple task, but only after struggling to solve a particular problem. To show you the problem, I ran the following straightforward code through XeLaTeX… \documentclass[]{book} \usepackage{fontspec} \begin{document} Latin text. Chinese text: 紫薇北斗星 Modern greek: Διαμ πριμα εσθ ατ, κυο πχιλωσοπηια Ancient greek: Μῆνιν ἄειδε, θεά, Πηληϊάδεω Ἀχιλῆος. And regular latin text. \end{document} … and the following PDF was produced: It turns out that the missing unicode characters are not XeLaTeX’s fault. The problem is that the used font (XeLaTeX by default uses a slightly more encompassing Computer Modern font) has not all unicode characters implemented. To implement all unicode characters in a single font (about 1.1 million characters) is a monumental task, and there are only a small handful of fonts whose maintainers aim to have full support of all characters (one of them is GNU FreeFont, which is already part of the Debian distribution, and therefore available to XeLaTeX). So, I thought, let’s just use a font which is dedicated to unicode. I selected in my document the pretty Junicode font: \setmainfont{Junicode} The result was: Now, greek worked, but still no chinese characters. It turned out that even fonts which are dedicated to unicode do not yet have all possible characters implemented. Because it’s a lot of work to produce high-quality fonts with matching styles for millions of possible characters. So, how do regular web browsers or office applications do it? They use a mechanism called font fallback. When a particular character is not implemented in the chosen main font, another font is silently used which does have this particular character implemented. XeLaTeX can do the same with a package called ucharclasses, and it gives you full control over the fallback font selection process. The ucharclasses documentation gives an example using the \fontspec font selection. I decided to use the font IPAexMincho which supports chinese characters. So I added to my document: \usepackage[CJK]{ucharclasses} \setTransitionsForCJK{\fontspec{IPAexMincho}}{\fontspec{Junicode}} … but when running XeLaTeX with this addition, ucharclasses somehow entered an endless loop with high CPU usage for the TexLive 2014 distribution (part of Debian). It hung at the line: (./ucharclass.aux) (/usr/share/texmf/tex/latex/tipa/t3cmr.fd)  Endless googling didn’t bring up any useful hints. Something must have changed in the internals, and the ucharclasses documentation needs updating. In any event, it took me 4 hours to find a fix. The solution was to use a font selection other than \fontspec{} — because it doesn’t seem to be compatible with ucharclasses any more. Instead, I used fontspec‘s suggested \newfontfamily mechanism. Here is the final working code: \documentclass[]{book} \usepackage{fontspec} \setmainfont{Junicode} \newfontfamily\myregularfont{Junicode} \newfontfamily\mychinesefont{IPAexMincho} \usepackage[CJK]{ucharclasses} \setTransitionsForCJK{\mychinesefont}{\myregularfont} \begin{document} Latin text. Chinese text: 紫薇北斗星 Modern greek: Διαμ πριμα εσθ ατ, κυο πχιλωσοπηια Ancient greek: Μῆνιν ἄειδε, θεά, Πηληϊάδεω Ἀχιλῆος. And regular latin text. \end{document} Here is the result: Mixed latin, chinese, and greek scripts with two different fonts: Junicode and IPAexMincho: Pretty! ## How to set up audio streaming (internet radio) in Linux This tutorial will show you how you can go live with your own internet radio station in a few minutes. ## Demystifying “streams” There is a lot of information, disinformation and irrelevant information about this in the internet. When you listen to internet radio, and you inspect the network requests in your Google Chrome Developer Tools (yes, you should use Chrome anyway), you will discover that a ‘magickal’ stream is nothing else than a blatantly simple HTTP download of a regular file which never finishes. Yup, jawdroppingly simple. ## What do you need? In order to broadcast audio (e.g. internet radio) into the internet, you need 1. a remote streaming server with high bandwidth to which many clients can collect 2. a local stream generator, which is sending a single stream to the streaming server The following tutorial shows how you can easily achieve this with free and open source tools which are part of the Debian (Ubuntu) distributions. It will take you 15 minutes to start your first rudimentary broadcast. We will use Icecast2 as a streaming server, simply for the reason that it is part of the Debian distribution and that I got it to work immediately. As the local stream generator we will use darkice, for the same reasons. Why not Windows? Well, since the majority of remote servers are running Linux distributions, you can use Icecast2 anyway. If you want to use a different stream generator for Windows, you can do so. This screencast shows you how it’s done. ## Icecast2 Is Icecast a professional-grade solution? According to a blog, Very much so. ICEcast is an industry standard platform used by thousands and thousands of radio stations all over the world. Its wide compatibility means people can listen with most players and operating systems. Listeners will be able to connect to your MP3 stream from all over the world, with all the popular media players including Windows Media Player, iTunes, Winamp, Realplayer, XMMS, and many more media players besides. Although incredibly simple, it can cope with even the heaviest demands and will not break under pressure. Its simplicity works to the broadcaster and listeners favor. According to Wikipedia, Version 2 [of Icecast] was started in 2001, a ground-up rewrite aimed at multi-format support (initially targeting Ogg Vorbis) and scalability. A ground-up rewrite for scalability certainly sounds like good news! So, let’s dive in! You would do the following steps on a server which is located at a large internet node with enough bandwidth to serve all your audience. To install, simply type apt-get install icecast2 During the installation you will be asked if you want to configure Icecast2. Answer yes. You will be asked the hostname. Here simply leave the default “localhost”. Next, you will be asked for source, relay and administration passwords. For testing, leave “hackme”. If you want to change the configuration at a later point, edit the configuration file /etc/icecast2/icecast.xml Next, you have to enable the Icecast2 server by setting ENABLE in the configuration file /etc/default/icecast2 to true . Now, start the server by typing service icecast2 start You now can access the web admin interface on port 8000 of your machine: The log file is in /var/log/icecast2/error.log and access.log . Best to tail -f both files to observe what is going on. ## Darkice Darkice is a stream generator. It encodes audio into various formats (e.g. ogg, mp3, etc.) from various inputs (e.g. microphone jack, line-in jack, or the stereo mix of your operating system) and sends a single stream to our Icecast2 server, which in turns re-broadcasts it to all connected clients. To install, simply type: apt-get install darkice By default it does not install a configuration file. But there is an example one in the documentation. Copy this to the /etc directory: cp /usr/share/doc/darkice/examples/darkice.cfg /etc You will need to edit this file according to your needs. Here is an example that worked for me: # this section describes general aspects of the live streaming session [general] duration = 0 bufferSecs = 5 reconnect = yes [input] device = default sampleRate = 44100 bitsPerSample = 16 channel = 2 [icecast2-0] bitrateMode = abr format = vorbis bitrate = 96 server = 192.168.0.250 port = 8000 password = hackme mountPoint = example1.ogg name = DarkIce trial description = This is only a trial url = http://www.yourserver.com genre = my own public = yes localDumpFile = dump.ogg Make sure that the password and the IP address of the Icecast2 server (which we installed earlier on the other machine) match. Also, remember the mountPoint of this stream. This is simply a label, in my case it is “example1”. Then you simply run as normal user darkice It is a console-only application and you will see some messages. This is what I get: DarkIce 1.0 live audio streamer, http://code.google.com/p/darkice/ Copyright (c) 2000-2007, Tyrell Hungary, http://tyrell.hu/ Copyright (c) 2008-2010, Akos Maroy and Rafael Diniz This is free software, and you are welcome to redistribute it under the terms of The GNU General Public License version 3 or any later version. Using config file: /etc/darkice.cfg Using ALSA DSP input device: default Could not set POSIX real-time scheduling, this may cause recording skips. Try to run darkice as the super-user. The note about the realtime stuff is just a warning, it works for me nevertheless. It would be easy to run it as superuser. ## Making a simple stream player We will simply make a small website with one <audio> element. That is enough to play streams. Create an empty file called streamtest.html with the following contents: <html> <body> <audio controls> <source src="http://192.168.0.250:8000/example1" /> </audio> </body> </html> Make sure that the IP address corresponds to the server where the Icecast2 server is running on. Open this html file in a browser and click the play button. Now you should hear the same audio that the darkice client has as its input. ## Changing the audio input for darkice In case you don’t have the Pulse Audio Volume control installed, install it with apt-get install pavucontrol Then run it. As soon as you have darkice running, the “Recording” tab will show the text “ALSA plug-in [darkice]: ALSA Capture from …” From the drop down you can select the input source. The text is a bit misleading. In my case “Monitor” means the stereo-mix of the entire computer (e.g. all system sounds, all played back audios). “Built-in Analog Stereo” means the microphone / line-in jack. For professional radio applications you of course would not use such a simple software mixer, but have an external hardware-based mixer to which all the microphones and the line-out of your computer are attached. Then you would connect the final output of the hardware mixer to your computer line-in and select “Built-in Analog Stereo” for darkice’s input. Linux has a more professional audio system called Jack as a replacement for the standard system Pulse Audio (we were using Pulse Audio in the above tutorial, which is similar to what Windows uses). Both are running on the Linux Kernel’s sound system called ALSA. ## Conclusion In the face of tons of documentation and blogs in the internet it is surpirisingly easy to set up your own, simple internet radio station with zero investment, all thanks to the Open Source movement. ## Citations within footnotes in LaTeX Writing a tutorial on programming, I needed citations within footnotes. Luckily, the biblatex package added support (see first comment on the sourceforge page of biblatex) for citations within footnotes in 2011. Apparently, this is not straightforward, since a low-level citation command has to be used to satisfy LaTeX. Anyway, this is now done automatically by thebiblatex package, so I didn’t have to make any changes. In my document, I only use the LaTeX command \autocite which behavior I can define in the document preamble. In my preamble I have: \usepackage[backend=biber,autocite=footnote,style=authortitle-ibid]{biblatex} \bibliography{bibliography.en.bib} Now, with the following TeX code… \chapter{My chapter} This is a test\footnote{TeX is awesome \autocite[see][p. 120]{Knuth}. I agree.} for a citation within a footnote. However, this is a citation\autocite[]{Ritchie75} in normal text. … you get the following output: You will notice that footnote 2 is a citation from within the normal text. Since I’ve specified autocite=footnote in the preamble, this is rendered as a footnote. However, the citation from within the footnote 1 is rendered in-line. ## Setting up Exim4 Mail Transfer Agent with Anti-Spam, Greylisting and Anti-Malware Recently my Exim mail server was hopelessly spammed to such an extent that I wasn’t even able to clear the mail queue with rm ./*, nor even list the files, nor even count the files with ls. How still I managed to delete probably millions of mail files in one folder can be read in my post “Removing a million files in a directory“. After this shock, I decided to integrate Anti-Spam and Anti-Malware systems into my Exim MTA, and within a few hours my server was back up and running, and this time, bouncing spam emails back to where they belong (hell). I did this in Ubuntu 10.04 LTS, but I know it also works in Debian 7 Wheezy, as I’ve recently used my own tutorial. My own knowledge about Exim comes from the excellent Official Guide to Exim by Exim’s author, Philip Hazel. Email servers are very complex. You won’t be able to go very far with ‘duct tape’ administration, nor come up with a good fix in a case of emergency. With a misconfigured email server, is very easy to get your IP address blacklisted in the entire internet. Therefore I would suggest you grab your own copy of the Official Guide to Exim from Amazon. ## How to install Exim I already was running Exim, but still, if you are interested, here is what to do. First, you can install Exim by running apt-get install exim4 but by default, this installs the package exim4-daemon-light, which does not have advanced capabilities compiled in. Since we want to do spam filtering, we need to install exim4-daemon-heavy: apt-get install exim4-daemon-heavy This will remove exim4-daemon-light automatically. We have to enable Exim for internet communications, since the default is only localhost communications. In /etc/exim4/update-exim4.conf.conf change the line dc_eximconfig_configtype=’local’ to dc_eximconfig_configtype='internet' Enter the domains you want to receive emails for to the line: dc_other_hostnames='mydomain.com' Make exim listen to all interfaces by emptying the following config line: dc_local_interfaces='' Enable TLS for Exim by running the script /usr/share/doc/exim4-base/examples/exim-gencert … and add the following line somewhere at the top of Exim’s configuration template file /etc/exim4/exim4.conf.template . MAIN_TLS_ENABLE=yes Every time you modify /etc/exim4/exim4.conf.template , you have to run update-exim4.conf and do service exim4 restart . Next, we will install and configure Spamassassin for Exim. Luckily, it is a Debian package. ## Spam Assassin ### Installation You can find instructions in this Debian Wiki https://wiki.debian.org/Exim, but you will find all commands here for convenience. apt-get install spamassassin This starts a daemon calledspamd automatically. However, it is disabled by default. Enable it by changing the following line in /etc/default/spamassassin : ENABLED=1 Debian-specific modification: In the same file, change this line OPTIONS="--create-prefs --max-children 5 --helper-home-dir" to this: OPTIONS="--create-prefs --max-children 5 --helper-home-dir -u debian-spamd" This instructs the spamd daemon to run as the user debian-spamd which is created when you install spamassassin. Its home directory is /var/lib/spamassassin . I had to do this because the following error messages was regularly logged into /var/log/syslog : config: cannot create user preferences file /nonexistent/.spamassassin/user_prefs: No such file or directory spamd: failed to create readable default_prefs: /nonexistent/.spamassassin/user_prefs spamd: failed to create readable default_prefs: /var/spool/exim4/.spamassassin/user_prefs Also, enable the cronjob to automatically update the spamassassin’s rules: CRON=1 The conjob file in question is /etc/cron.daily/spamassassin , which in turn calls sa-update . Next, you can configure the behaviour of spamassassin in the config file /etc/spamassassin/local.cf . Especially the entries “rewrite_header” and “required_score” are interesting, but later we will configure Exim to do these jobs for us directly. Restart the daemon to make the changes effective: service spamassassin restart ### Integration into Exim All of following modifications have to take place in /etc/exim4/exim4.conf.template. Now we have to enable spamd in Exim’s configuration file. Search for the line containing spamd_address and uncomment it like this: spamd_address = 127.0.0.1 783 It is a good idea to add special headers to each processed email which specify the spam score. The configuration is alredy there, we just have to uncomment it (see also official documentation of this here):  warn spam = debian-spamd:true add_header = X-Spam_score:$spam_score\n\
X-Spam_score_int: $spam_score_int\n\ X-Spam_bar:$spam_bar\n\
X-Spam_report: $spam_report Note that I have replaced spam = Debian-exim:true with spam = debian-spamd:true to match the user of the spamd daemon. If you want to know what the :true part means, study this section of the official Exim documentation. At this point, emails which are definitely spam will still be delivered. This is not good, since when a mail client of one of your customers is compromised, it could send thousands of spam emails per day, which still would cause your server and IP to be blacklisted. If you want to bounce emails that are over a certain spam point threshold, add the following lines directly below. In this case, the threshold is 12.0 (but you have to enter it without the comma):  deny message = This message scored$spam_score spam points.
spam = debian-spamd
condition = ${if >{$spam_score_int}{120}{1}{0}}

Generate the real Exim4 configuration from /etc/exim4/exim4.conf.template  that we’ve just edited by running

update-exim4.conf
service exim4 restart

### Test

Now, let’s test if our spamassassin setup was successful. Send an email to your Exim server that contains the following line. This code is taken from Spamassassin’s GTUBE (the Generic Test for Unsolicited Bulk Email):

XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X

The email should bounce back immediately and contain the message that we’ve entered above:

SMTP error from remote server after transfer of mail text:
host: mail.myserver.com
This message scored 1000.0 spam points.

Now, send yourself a normal email. After you have received it, in your mail client (I’m using Icedove) inspect the mail source by pressing Ctrl + U. It should contain the following special header lines:

X-Spam_score: 2.7
X-Spam_score_int: 27
X-Spam_bar: ++
X-Spam_report: snip

So far, so good.

## Greylisting

Next, let’s set up Greylisting, another spam defense measure. The tool of my choice was greylistd because Debian has its own package greylistd.

### Installation

apt-get install greylistd

You will be shown a configuration notice, instructing you how to enable greylistd in the Exim configuration. The following will show you what to do.

### Integration into Exim

greylistd-setup-exim4 add

As far as I could determine, this automatically adds some lines in the already existing Exim configuration templates exim4.conf.template and inside of the directory /etc/exim4/conf.d. It adds it right after acl_check_rcpt:.

At this point, greylistd is already running. In case you want to restart the service, run

service greylist restart

Of course, we have to restart Exim again so that our new configuration becomes active:

update-exim4.conf
service exim4 restart

### Test

Observe the contents of the Exim log file

tail -f /var/log/exim4/mainlog

and send yourself a regular email. You will see a line in the logfile similar to this:

F=<from@email.com> temporarily rejected RCPT <to@email.com>: greylisted.

When it says “temporarily rejected RCPT” and “greylisted” it means that greylistd is working.

## Anti-Malware and Anti-Virus

As an email provider you certainly want to have some anti-malware and anti-virus measures in place. My choice was ClamAV, since it’s part of Debian and rather easy to integrate with Exim. The following instructions are loosely based on the Ubuntu Wiki EximClamAV, but it contains one step too much which seems to be no longer neccessary (the part about creating a new file). In any case, here are my working steps.

### Installation

First, install the daemon:

apt-get install clamav-daemon

It will output the following failures, but don’t worry, they are harmless:

[FAIL] Clamav signatures not found in /var/lib/clamav ... failed!
[FAIL] Please retrieve them using freshclam ... failed!
[FAIL] Then run '/etc/init.d/clamav-daemon start' ... failed!

To get rid of the messages, you have to run

freshclam

This command updates your virus databases from the internet. You should create a cronjob to run it regularly. Now you are able to restart the daemon without failure messages:

service clamav-daemon restart

Next, add the clamav daemon user to the Debian-exim group, so that it can access the spool files:

usermod -a -G Debian-exim clamav

### Integration into Exim

Locate the line in /etc/exim4/exim4.conf.template which contains av_scanner, uncomment it, and change it to the following (as the Ubuntu Wiki correctly says, the default value does not work):

av_scanner = clamd:/var/run/clamav/clamd.ctl

Next, uncomment the following lines in /etc/exim4/exim4.conf.template (this is where the Ubuntu Wiki says that you should create a new file, but it’s already there):

  deny
malware = *
message = This message was detected as possible malware ($malware_name). As we have done before, we have to restart Exim so that our new configuration becomes active: update-exim4.conf service exim4 restart ### Test There is a special string that you can use to test Anti-Malware programs. It is taken from the eicar website. At the bottom of the linked page you will find it: X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!\$H+H*

Send an email to Exim which contains this string in a separate line. It should bounce immediately. The bounced message will contain:

SMTP error from remote server after transfer of mail text:
host: mail.myserver.com
This message was detected as possible malware (Eicar-Test-Signature).

When you get this, ClamAV is working correctly.

## Setting up Exim with DKIM (DomainKeys Identified Mail)

Google Mail suggests using DKIM for ISP providers. When you are signing outgoing messages with DKIM, you are reducing the chance that Google thinks you are a spammer. See also Google’s Bulk Senders Guidline as to how Google judges the Anti-Spam qualities of your mails. DKIM is not a strong Anti-Spam indicator, it only ensures (due to private/public key encryption) that an email was actually sent from the server it claims it was sent. Anyhow, here is how to do it (based on the articles here and here):

Generate a private key with openssl:

cd /etc/exim4
openssl genrsa -out dkim.private.key 1024

Extract the public key:

openssl rsa -in dkim.private.key -out dkim.public.key -pubout -outform PEM

Change permissions and ownership to make it readable for Exim and for security reasons:

chmod 640 dkim.p*
chown root:Debian-exim dkim.p*

Next add the following to exim4.conf.template  (or to your split-configuration files if you use that method), right before the line remote_smtp:

DKIM_DOMAIN = yourdomain.com
DKIM_SELECTOR = exim
DKIM_PRIVATE_KEY = CONFDIR/dkim.private.key
DKIM_CANON = relaxed
DKIM_STRICT = true

Update the configuration and restart Exim:

update-exim4.conf
service exim4 restart

Now send a test email to some address (e.g. free mail provider) which is not handled by your email server and inspect the sources of the received email. It should contain a line DKIM-Signature . To avoid confusion: If you are sending an email to yourself, which is received by the same server which you are configuring, no DKIM Signature is added (since not neccessary).

Next, you have to add the DKIM public key as a TXT “selector record” to the DNS zone of yourdomain.com. For DKIM_DOMAIN  and DKIM_SELECTOR  you have specified above, you have to add the following entry:

TXT  |  exim._domainkey  |  v=DKIM1; k=rsa; p=MIGfMA...;

where p=  gives the public key from /etc/exim4/dkim.public.key  without headers and without line breaks.

You also should add a “DKIM policy record” for the subdomain _domainkey to state that all emails must be signed with DKIM. Otherwise, DKIM could simply be omitted by a spoofer without consequences. Again, this is a simple TXT entry in the DNS:

TXT  |  _domainkey  |  o=~;

You can use this tester to check the policy record: http://domainkeys.sourceforge.net/policycheck.html

However, this “o” policy record does not seem to be documented in any RFC (I found it on various blog posts), and it is superseded by RFC5617 (DKIM ADSP Author Domain Signing Practices). For ADSP you would have to add:

TXT  |  _adsp._domainkey  |  dkim=all

For details on DKIM see: RFC specification

### Test

Now check if your zone records have been saved and taken:

dig TXT exim._domainkey.yourdomain.com

It should output the contents of the TXT zone entry which you’ve made above. If you can see them, send an email to the excellent SMTP tester email check-auth@verifier.port25.com

If it responds with

DKIM check:         pass

Then the DKIM set-up was successful.

A characteristic behaviour of malicious Spam senders is that they send a Spam Flood (as many messages as possible in the least time possible).  If possible, they will send many messages in just one SMTP connection (i.e. several MAIL commands in one session). If that happens, you will be blacklisted very soon, and with tens of thousands of send Spam emails, it will be very difficult to re-gain a good reputation for email providers like Google or Yahoo. Legitimate senders however will only send a few single messages, one message per SMTP connection, and it reasonably will take them at least 10 seconds to write a short email. So, we can rate-limit the submission of messages, having a great impact on Spammers, but little impact on legitimate senders. Exim has a rate-limiting feature, but since it’s a science to configure Exim properly, I decided for an easier, more robust, low-level approach using the iptables firewall.

First, we will lock Exim down to just allow one MAIL command per SMTP session. From the Exim Main Configuration Documentation, we can write somewhere in /etc/exim4/exim4.conf.template

smtp_accept_max_per_connection = 1

Next, we are going to limit the number of parallel SMTP connections per sending host to 1:

smtp_accept_max_per_host = 1

And we will limit the maximum SMTP connections which Exim will allow. You can set this to the approximate number of clients you have, plus a margin:

smtp_accept_max = 100

Now that we know that there only can be 1 message per SMTP connection, we limit the SMTP connection frequency in our firewall:

iptables -A INPUT -p tcp --dport 25 -m state --state NEW -m recent --set
iptables -A INPUT -p tcp --dport 25 -m state --state NEW -m recent --update --seconds 60 --hitcount 6 -j DROP

This will limit incoming SMTP connections to 6 in 1 minute, which is one connection per 10 seconds in average — enough for private or business emails, not enough for spammers.

#### SMTP banner delay

(inspiration from here and here) Exim drops the connection if a SMTP client attempts to send something before the SMTP banner is displayed, this is already spam protection built into Exim:

SMTP protocol synchronization error (input sent without waiting for greeting): rejected connection

To further slow down Spam we simply delay this banner. Somewhere at the beginning of the Exim config file, write:

acl_smtp_connect = acl_check_connect

In layman’s terms, this tells Exim which ACL to execute when a connection is initiated. Then, after begin acl  add it:

acl_check_connect:
accept
delay = 10s

You can test this by telnet’ing to your server. The banner should appear only after 10 seconds.

#### More Anti Spam measures

https://github.com/Exim/exim/wiki/BlockCracking (not tested)

Conclusion

If you have succeeded so far, test your new Exim installation with the great tool at: