Raspberry Pi2 and Pi3 running pure Debian and the Linux Mainline/Vanilla Kernel

Update 2017 Feb 25: I have updated the step-by-step instructions based on the suggested fixes and improvements contained in the reader comments. I also have copied the step-by-step instructions from this blog post to the README.md file hosted on https://github.com/michaelfranzl/rpi23-gen-image. From now on I will update the instructions only on github, so expect that the instructions in this blog post will grow slightly out of date.

Update 2017 Mar 4: 64 bit kernel and Debian OS now works on the RPi3.

Update 2018 May: Debian 10 “Buster” works, including wireless LAN on RPi3

Gallium graphics drivers for Raspberry Pi’s VC4 chip are now fully supported by Linux Mainline

This has been a long way coming. In February 2014, Broadcom announced that they would release the formerly closed-source drivers for the VideoCore IV (VC4) GPU of their BCM283x family of System-on-a-chip (SoC), powering Raspberry Pi’s.

To make a long story short, Eric Anholt started porting the Open Source drivers, as documented by this presentation early 2015, and contributed code to the Linux (Mainline) Kernel, libdrm, Mesa, and X.org. I’m sure it was a long and painful work. But the results are worth it. A picture says more than 1000 words:

Debian 9 ("Stretch") running on a Raspberry Pi 2, powered by Linux 4.9.0-rc3 Mainline/Vanilla Kernel. Notable in this image: Graphics driver is "Gallium" running on the VC4 GPU of the Broadcom 2836 system-on-a-chip (SOC). Glxgears runs with 60 FPS and consumes very little CPU. I2C interface is recognized.
Debian 9 (“Stretch”) running on my Raspberry Pi 2 (and 3), powered by Linux 4.9.0-rc3 Mainline/Vanilla Kernel. Notable in this image: Graphics driver is recognized as “Gallium” running on the VC4 GPU of the Broadcom 2836 system-on-a-chip (SOC). The glxgears benchmark runs with 60 FPS (the vsync of the monitor) and consumes very little CPU. Even the Raspberry I2C interface is recognized by the Linux Mainline Kernel.

To emphasize the point: It is no longer necessary to run specialized distributions (like Raspbian) or Linux kernels (like the Raspbian flavor) in order to have the Raspberry Pi well supported. And this is good news. Debian is a well established and maintained standard Distribution. And even though the Raspberry Pi is not powerful enough for the professional desktop user, it is powerful enough for the casual desktop user, and it is very small and cheap, which opens up a whole lot of possibities for new real-world applications.

I ran an additional test: Gnome even runs on Wayland (modern replacement for the X Window System) on the Raspberry Pi 2 (and 3):

Gnome on Wayland on Raspberry Pi 2
Gnome on Wayland on Raspberry Pi 2

 

Remarks

It still is not a matter of a one-click installer to reproduce these results, you need some experience when you run into barriers. But it has gotten a whole lot easier. Github user drtyhlpr thankfully published the script rpi23-gen-image that can create a standard Debian distribution for the Raspberry Pi that can simply be copied to a SD card.

I have created a fork of this script to use the official Linux kernel instead of the Raspberry flavor one. Above screenshots are taken from a system that I’ve created with this script. My fork is developed into a slightly different direction:

  • Only official Debian releases 9 (“Stretch”) and newer are supported.
  • Only the official/mainline/vanilla Linux kernel is supported (not the raspberry flavor kernel).
  • The Linux kernel must be pre-cross-compiled on the PC running this script (instructions below).
  • Only U-Boot booting is supported.
  • The U-Boot sources must be pre-downloaded and pre-cross-compiled on the PC running this script (instructions below).
  • An apt caching proxy server must be installed to save bandwidth (instructions below).
  • The installation of the system to an SD card is done by simple copying or rsyncing, rather than creating, shrinking and expanding file system images.
  • The FBTURBO option is removed in favor or the working VC4 OpenGL drivers of the mainline Linux kernel.

All of these simplifications are aimed at higher bootstrapping speed and maintainability of the script. For example, we want to avoid testing of all of the following combinations:

RPi2 with u-boot, with official kernel
RPi2 without u-boot, with official kernel
RPi2 with u-boot, with raspberry kernel
RPi2 without u-boot, with raspberry kernel
RPi3 with u-boot, with official kernel
RPi3 without u-boot, with official kernel
RPi3 with u-boot, with raspberry kernel
RPi3 without u-boot, with raspberry kernel

Thus, the script only supports:

RPi2 with u-boot with official kernel
RPi3 with u-boot with official kernel

RPi2 (setting RPI_MODEL=2) is well supported. It will run the arm architecture of Debian, and a 32-bit kernel. You should get very good results, see my related blog posts:

https://blog.michael.franzl.name/2016/10/31/raspberry-pi-debian-stretch/

The newer RPi3 (setting RPI_MODEL=3) is supported too. It will run the arm64 architecture of Debian, and a 64-bit kernel. The support of this board by the Linux kernel will very likely improve over time.

In general, this script is EXPERIMENTAL. I do not provide ISO file system images. It is better to master the process rather than to rely on precompiled images. In this sense, use this project only for educational purposes.

How to do it

Basically, we will deboostrap a minimal Debian 9 (“Stretch”) system for the Raspberry on a regular PC running also Debian 9 (“Stretch”). Then we copy that system onto a SD card, then boot it on the Raspberry.

We will work with the following directories:

~/workspace
  |- rpi23-gen-image
  |- linux
  |- u-boot
  |- raspberry-firmware

Set up your working directory:

mkdir workspace
cd workspace

Do the following steps as root user.

Set up caching for apt

This way, you won’t have to re-download hundreds of megabytes of Debian packages from the Debian server every time you run the rpi23-gen-image script.

apt-get install apt-cacher-ng

Check its status page:

http://localhost:3142

Install dependencies

The following list of Debian packages must be installed on the build system because they are essentially required for the bootstrapping process.

apt-get install debootstrap debian-archive-keyring qemu-user-static binfmt-support dosfstools rsync bmap-tools whois git bc device-tree-compiler

For a RPi2, you also need:

apt-get install crossbuild-essential-armhf

For a RPi3, you also need:

apt-get install crossbuild-essential-arm64

Kernel compilation

Get the latest Linux mainline kernel. This is a very large download, about 2GB. (For a smaller download of about 90 MB, consider downloading the latest stable kernel as .tar.xz from https://kernel.org.)

git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux

Confirmed working revision (approx. version 4.10, Feb 2017): 60e8d3e11645a1b9c4197d9786df3894332c1685

git checkout 60e8d3e116

Working configuration files for this Linux kernel revision are included in this repository. (working-rpi2-linux-config.txt and working-rpi3-linux-config.txt).

If you want to generate the default .config file that is also working on the Raspberry, execute

make mrproper

For a RPi2:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- multi_v7_defconfig

For a RPi3:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig

Whichever .config file you have at this point, if you want to get more control as to what is enabled in the kernel, you can run the graphical configuration tool at this point:

apt-get install libglib2.0-dev libgtk2.0-dev libglade2-dev

For a RPi2:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- gconfig

For a RPi3:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- gconfig

Before compiling the kernel, back up your .config file so that you don’t lose it after the next make mrproper:

cp .config ../kernelconfig-backup.txt

Compiling the kernel

Clean the sources:

make mrproper

Optionally, copy your previously backed up .config:

cp ../kernelconfig-backup.txt .config

Find out how many CPU cores you have to speed up compilation:

NUM_CPU_CORES=$(grep -c processor /proc/cpuinfo)

Run the compilation on all CPU cores. This takes about 10 minutes on a modern PC:

For a RPi2:

make -j${NUM_CPU_CORES} ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

For a RPi3:

make -j${NUM_CPU_CORES} ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

Verify that you have the required kernel image.

For a RPi2 this is:

./arch/arm/boot/zImage

For a RPi3 this is:

./arch/arm64/boot/Image.gz

U-Boot bootloader compilation

cd ..
git clone git://git.denx.de/u-boot.git

Confirmed working revision: b24cf8540a85a9bf97975aadd6a7542f166c78a3

git checkout b24cf8540a

Let’s increase the maximum kernel image size from the default (8 MB) to 64 MB. This way, u-boot will be able to boot even larger kernels. Edit ./u-boot/include/configs/rpi.h  and add above the very last line (directly above “#endif”):

#define CONFIG_SYS_BOOTM_LEN (64 * 1024 * 1024)

Find out how many CPU cores you have to speed up compilation:

NUM_CPU_CORES=$(grep -c processor /proc/cpuinfo)

Compile for a RPi model 2 (32 bits):

make -j${NUM_CPU_CORES} ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- rpi_2_defconfig all

Compile for a RPi model 3 (64 bits):

make -j${NUM_CPU_CORES} ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- rpi_3_defconfig all

Verify that you have the required bootloader file:

./u-boot.bin

Pre-download Raspberry firmware

The Raspberry Pi still needs some binary proprietary blobs for booting. Get them:

cd ..
mkdir -p raspberry-firmware/boot
cd raspberry-firmware/boot
wget https://github.com/raspberrypi/firmware/raw/master/boot/bootcode.bin
wget https://github.com/raspberrypi/firmware/raw/master/boot/fixup_cd.dat
wget https://github.com/raspberrypi/firmware/raw/master/boot/fixup.dat
wget https://github.com/raspberrypi/firmware/raw/master/boot/fixup_x.dat
wget https://github.com/raspberrypi/firmware/raw/master/boot/start_cd.elf
wget https://github.com/raspberrypi/firmware/raw/master/boot/start.elf
wget https://github.com/raspberrypi/firmware/raw/master/boot/start_x.elf

Confirmed working revision: bf5201e9682bf36370bc31d26b37fd4d84e1cfca

Build the system!

This is where you call the rpi23-gen-image.sh script.

cd ../..
git clone https://github.com/michaelfranzl/rpi23-gen-image.git
cd rpi23-gen-image

For example:

DEBIAN_RELEASE="stretch" \
USER_NAME="pi" \
PASSWORD="xxx" \
APT_INCLUDES="i2c-tools,rng-tools,avahi-daemon,rsync,vim" \
UBOOTSRC_DIR="$(pwd)/../u-boot" \
KERNELSRC_DIR="$(pwd)/../linux" \
RPI_MODEL=2 \
HOSTNAME="rpi2" \
RPI_FIRMWARE_DIR="$(pwd)/../raspberry-firmware" \
ENABLE_IPTABLES=true \
ENABLE_REDUCE=true \
REDUCE_SSHD=true \
./rpi23-gen-image.sh

You may want to modify the variables according to the section “Command-line parameters” below.

The file example.sh in my github repostory contains a working example.

Install the system on a SD card

Insert a SD card into the card reader of your host PC. You’ll need two partitions on it. I’ll leave as an exercise for the reader the creation of a partition table according to the following output of fdisk for a 64GB card:

Device         Boot  Start        End    Sectors    Size   Id  Type
/dev/mmcblk0p1        2048     500000     497953  243.1M    c  W95 FAT32 (LBA)
/dev/mmcblk0p2      501760   62552063   62050304   29.6G   83  Linux

The following commands will erase all contents of the SD card and install the system (copy via rsync) on the SD card:

umount /dev/mmcblk0p1
umount /dev/mmcblk0p2

mkfs.vfat /dev/mmcblk0p1
mkfs.ext4 /dev/mmcblk0p2

mkdir -p /mnt/raspcard

mount /dev/mmcblk0p2 /mnt/raspcard
mkdir -p /mnt/raspcard/boot/firmware
mount /dev/mmcblk0p1 /mnt/raspcard/boot/firmware

rsync -a ./images/stretch/build/chroot/ /mnt/raspcard

umount /dev/mmcblk0p1
umount /dev/mmcblk0p2

Note about SD cards: Cheap (or sometimes even professional) SD cards can be weird at times. I’ve repeatedly noticed corrupt/truncated files even after proper rsync and proper umount on different brand new SD cards. TODO: Add a method to verify all file checksums after rsync.

Try booting the Raspberry

Insert the SD card into the Raspberry Pi, and if everything went well, you should see a console-based login prompt on the screen. Login with the login details you’ve passed into the script (USER_NAME and PASSWORD).

Alternatively, if you have included “avahi-daemon” in your APT_INCLUDES, you don’t need a screen and keyboard and can simply log in via SSH from another computer, even without knowing the Rasberry’s dynamic/DHCP IP address (replace “hostname” and “username” with what you have set as USER_NAME and HOSTNAME above):

ssh username@hostname.local

Finishing touches directly on the Raspberry

Remember to change usernames, passwords, and SSH keys!

Check uber-low RAM usage

Running top shows that the freshly booted system uses only 23 MB out of the availabl 1GB RAM! Confirmed for both RPi2 and RPi3.

Network Time Synchronization

The Raspberry doesn’t have a real time clock. But the default systemd conveniently syncs time from the network. Check the output of timedatectl. Confirmed working for both RPi2 and RPi3.

Hardware Random Number Generator

The working device node is available at /dev/hwrng. Confirmed working for both RPi2 and RPi3.

I2C Bus

Also try I2C support:

apt-get install ic2-tools
i2cdetect -y 0

Confirmed working for both RPi2 and RPi3.

Test onboard LEDs

As of the kernel revision referenced above, this only works on the RPi2. The RPi3 has only the red PWR LED on all the time, but otherwise is working fine.

By default, the green onboard LED of the RPi blinks in a heartbeat pattern according to the system load (this is done by kernel feature LEDS_TRIGGER_HEARTBEAT).

To use the green ACT LED as an indicator for disc access, execute:

echo mmc0 > /sys/class/leds/ACT/trigger

To toggle the red PWR LED:

echo 0 > /sys/class/leds/PWR/brightness # Turn off
echo 1 > /sys/class/leds/PWR/brightness # Turn on 

Or use the red PWR LED as heartbeat indicator (kernel option for this must be enabled):

echo heartbeat > /sys/class/leds/PWR/trigger

Notes about systemd

systemd now replaces decades-old low-level system administration tools. Here is a quick cheat sheet:

Reboot machine:

systemctl reboot

Halt machine (this actually turns off the RPi):

systemctl halt

Show all networking interfaces:

networkctl

Show status of the Ethernet adapter:

networkctl status eth0

Show status of the local DNS caching client:

systemctl status systemd-resolved

Install GUI

Successfully tested on the RPi2 and RPI3.

If you want to install a graphical user interface, I would suggest the light-weight LXDE window manager. Gnome is still too massive to run even on a GPU-accelerated Raspberry.

apt-get install lightdm lxde lxde-common task-lxde-desktop

Reboot, and you should be greeted by the LightDM greeter screen!

Test GPU acceleration via VC4 kernel driver

Successfully tested on the RPi2 and RPI3.

apt-get install mesa-utils
glxgears
glxinfo | grep '^OpenGL'

Glxinfo should output:

OpenGL vendor string: Broadcom
OpenGL renderer string: Gallium 0.4 on VC4
OpenGL version string: 2.1 Mesa 12.0.3
OpenGL shading language version string: 1.20
OpenGL ES profile version string: OpenGL ES 2.0 Mesa 12.0.3
OpenGL ES profile shading language version string: OpenGL ES GLSL ES 1.0.16

Kernel compilation directly on the Rasberry

Only successfully tested on the RPi2. Not yet tested on the RPI3.

In case you want to compile and deploy another Mainline Linux kernel directly on the Raspberry, proceed as described above, but you don’t need the ARCH and CROSS_COMPILE flags. Instead, you need the -fno-pic compiler flag for modules. The following is just the compilation step (configuration and installation omitted):

make -j5 CFLAGS_MODULE="-fno-pic"
make modules_install

Follow-up articles

.

Resume rsync transfers with the –partial switch

Note: This post is 6 years old. Some information may no longer be correct or even relevant. Please, keep this in mind while reading.

Recently I wanted to rsync a 16GB file to a remote server. The ETA was calculated as 23 hours and, as it usually happens, the file transfer aborted after about 20 hours due to a changing dynamic IP address of the modem. I thought: “No problem, I just re-run the rsync command and the file transfer will resume where it left off.” Wrong! Because by default, on the remote side, rsync creates a temporary file beginning with a dot, and once rsync is aborted during the file transfer, this temporary file is deleted without a trace! Which, in my case, meant that I wasted 20 hours of bandwidth.

It turns out that one can tell rsync to keep temporary files by passing the argument –partial . I tested it, and the temporary file indeed is kept around even after the file transfer aborts prematurely. Then, when simply re-running the same rsync command, the file transfer is resumed.

In my opinion, rsync should adopt this behavior by default. Simple thing to fix, but definitely an argument that should be passed every time!

Added: Simply use -P  ! This implies –partial  and you’ll also see a nice progress output for free!

Debian 7: Setting up production Exim MTA as an e-mail service provider

Note: This post is 8 years old. Some information may no longer be correct or even relevant. Please, keep this in mind while reading.

With a mis-configured email server, it is very easy to get your IP address blacklisted!

Recently, my Exim mail server was hopelessly spammed to such an extent that I wasn’t even able to clear the mail queue using 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 is documented 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. This blog post documents my steps in Debian 7 “Wheezy”.

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.

If you are serious about the topic, I would suggest you grab your own copy of the Official Guide to Exim from Amazon.

How to install Exim

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:

dc_other_hostnames='mydomain.com'

Make Exim listen on 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 called spamd  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 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 behavior 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 already 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 should be 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 necessary (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 cron job 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, un-comment 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, un-comment 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

More information about these parameters see this section of the official Exim documentation.

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 necessary).

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

Similar in function to ADSP seems to be DMARC. I’ll write about this in a future blog post.

For details on DKIM see: RFC specification

Test

Now check if your zone records have been saved and are effective:

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.

Additional Anti-Spam measures

A characteristic behavior 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 sent spam emails, it will be very difficult to re-gain a good reputation in the eyes of email providers like Google or Yahoo. Legitimate human senders however will only send a few single messages, one message per SMTP connection, and it will take them at least 10 seconds to write a short email. So, we can rate-limit the submission of messages, having a big impact on spammers, but little impact on legitimate human senders. Exim has a rate-limiting feature, but I decided for an easier, more robust, low-level approach using the iptables firewall.

First, we will configure Exim 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 a spam protection built directly 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 this:

acl_check_connect:
  accept
    delay = 10s

You can test this by telneting 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:

http://www.allaboutspam.com/email-server-test/

Exim is a very complex program (the most complex one I’ve encountered so far) and you can go very, very deep studying it. The complexity seems to stem from the complexity of the email delivery process itself. Despite that fact, this tutorial enables you to set up Exim with Anti-Malware and Anti-Spam measures in less than 1 hour. It is by no means exhaustive, but it at least bounces Spam emails above a certain threshold which is the most important thing when you don’t want your server and IP address to be blacklisted all over the internet. It also adds value to your customers when you are operating Exim as a business.

But: Anyone who would like to operate Exim for customers – would like to be a professional email hosting company – should think twice if it’s really worth it. Things do go wrong all the time, and if you don’t have a deep knowledge about what exactly to do in case of an technical or security-relevant incident – immediately, right there and then – you could upset all your customers pretty quickly. So, my advice would be: “hands-off” unless you know what you are doing. As I like to say: “You pays your money and you takes your chances!”

Follow up:

Exim and Spamassassin: Rewriting Subject lines, adding SPAM and Score