vsftpd setup – the missing information

So I set up vsftpd on my Debian box the other day. I wanted a simple virtual users setup, so I created /etc/vsftpd, moved and symlinked my vsftpd.conf into this directory (to keep Debian happy) and also added a /etc/vsftpd/users.txt file – the source for my db(1) database that pam should use, after I compiled it with

$ cd /etc/vsftpd && db4.6_load -T -t hash -f users.txt users.db

Now that the database existed I went to /etc/pam.d/vsftpd and configured it there

session optional    pam_keyinit.so force revoke
auth    required    /lib/security/pam_userdb.so \
                    db=/etc/vsftpd/users.db
account required    /lib/security/pam_userdb.so \
                    db=/etc/vsftpd/users.db

but when I tried to log in, vsftpd always reported 530: Login incorrect (and of course I specified an existing user with a correct password). So what went wrong?

After struggling with it for quite some time I noticed that my /var/log/auth.log contained output from pam:

vsftpd: pam_userdb(vsftpd:auth): Verify user `foo' with a password
vsftpd: pam_userdb(vsftpd:auth): user_lookup: could not open database `/etc/vsftpd/users.db': No such file or directory

Huh?! Of course /etc/vsftpd/users.db exists – though it is only read-/writable by root (600), this shouldn’t matter much, because vsftpd runs as root anyways.

Well, the nice thing about the internet is that there is usually at least one person who already had the same problem like you and eventually solved it – and that was the case here as well:

It turned out that pam_userdb.so silently appends .db to the given path, so all I had to do to make it work was stripping off my .db in /etc/pam.d/vsftpd:

session optional    pam_keyinit.so force revoke
auth    required    /lib/security/pam_userdb.so \
                    db=/etc/vsftpd/users
account required    /lib/security/pam_userdb.so \
                    db=/etc/vsftpd/users

If you look into pam_userdb(8) you won’t find any hint about that – even worse, the example in the man page uses the explicit .db suffix as well (at least here on Lenny).

Anyways, I have now an easy-to-manage ftp server and one reason less to trust anyhow into DropBox and friends 🙂

Simple script to build Dovecot with sieve / managesieve support

One important principle in software engineering is DRY – Don’t Repeat Yourself. This can of course also be applied on tasks a system administrator has to do over and over again. One of this tasks for me was recently to have a working dovecot installation with sieve support.

Unfortunately the authors of dovecot and its sieve plugin decided to make the process of getting there not quite easy, but at least straight forward enough to automate it. I’d still rather like to use some packaged debs, but I could not find any (of course stock Debian Lenny debs do not support it), but for now the process for me is as easy as picking up the new version strings, edit the following script and hit run. I hope its useful for somebody else:

#!/bin/bash

DOVECOT_VERSION=1.2.3
SIEVE_VERSION=0.1.11
MANAGESIEVE_VERSION=0.11.8

BUILDDIR="$(dirname $(readlink -f $0))/build"

rm -rf $BUILDDIR
mkdir $BUILDDIR
cd $BUILDDIR

#
# step 1: fetch dovecot, patch it with managesieve and build it
#
echo "fetching, patching, building and installing dovecot-$DOVECOT_VERSION"
wget -qO - "http://www.dovecot.org/releases/${DOVECOT_VERSION:0:3}/dovecot-$DOVECOT_VERSION.tar.gz" |
    tar -xzf -

cd dovecot-$DOVECOT_VERSION
wget -qO - "http://www.rename-it.nl/dovecot/${DOVECOT_VERSION:0:3}/dovecot-$DOVECOT_VERSION-managesieve-$MANAGESIEVE_VERSION.diff.gz" |
    gzip -dc | patch -p1 >/dev/null

./configure --enable-header-install >>$BUILDDIR/build.log

make install >>$BUILDDIR/build.log

cd $BUILDDIR

#
# step 2: fetch and build dovecot-sieve 
#
echo "fetching, building and installing dovecot-sieve-$SIEVE_VERSION"
wget -qO - "http://www.rename-it.nl/dovecot/${DOVECOT_VERSION:0:3}/dovecot-${DOVECOT_VERSION:0:3}-sieve-$SIEVE_VERSION.tar.gz" |
    tar -xzf -

cd dovecot-${DOVECOT_VERSION:0:3}-sieve-$SIEVE_VERSION

./configure --with-dovecot=$BUILDDIR/dovecot-$DOVECOT_VERSION >>$BUILDDIR/build.log

make install >>$BUILDDIR/build.log

cd $BUILDDIR

#
# step 3: fetch and build dovecot-managesieve
#
echo "fetching, building and installing dovecot-managesieve-$MANAGESIEVE_VERSION"
wget -qO - "http://www.rename-it.nl/dovecot/${DOVECOT_VERSION:0:3}/dovecot-${DOVECOT_VERSION:0:3}-managesieve-$MANAGESIEVE_VERSION.tar.gz" |
    tar -xzf -

cd dovecot-${DOVECOT_VERSION:0:3}-managesieve-$MANAGESIEVE_VERSION

./configure --with-dovecot=$BUILDDIR/dovecot-$DOVECOT_VERSION \
            --with-dovecot-sieve=$BUILDDIR/dovecot-${DOVECOT_VERSION:0:3}-sieve-$SIEVE_VERSION >>$BUILDDIR/build.log

make install >>$BUILDDIR/build.log

cd $BUILDDIR

echo "all done - build log is available in $BUILDDIR/build.log"

Useful Gotcha #27

If you get this error ssl_error_rx_record_too_long when browsing an SSL-secured virtual host and wonder what the heck is going on (hey, it worked the day before), ensure that you’ve noticed that your admins have changed the IP address of the machine and that you have to adapt the IP-based VHost configuration accordingly… not that this SSL error wouldn’t have said that in first place!

Enable more locales in stock Debian installations

If you wonder why

 $ php -r "setlocale(LC_TIME, 'de_DE.UTF-8'); echo strftime ('%A %e %B %Y', mktime (0, 0, 0, 12, 22, 1978));"
gives you Freitag 22 Dezember 1978 on most systems like f.e. openSuSE and Ubuntu, but Friday 22 December 1978 on Debian, you need to remember that the Debian guys outsmart everything and everybody with a shell script and a configuration file, just to keep your installation lean and clean.

In this particular example, edit /etc/locale.gen and add de_DE.UTF-8 UTF-8 (or anything else listed in /usr/share/i18n/SUPPORTED), hit save and then execute

 $ sudo /usr/sbin/locale-gen

and voila, the above PHP call (and everything else which requests a German locale) works!

Server-side email filtering (update)

If you’re having more than one computer where you look regularily for your emails (f.e. at home, at work and while you’re on the way) and you get a reasonable amount of (non-spam) emails every day, you probably know the problem: Client-side email filters just don’t do it.

Being a novice with all the mail software stuff my initial simple idea was “hey, lets look for a web-based procmailrc frontend” – but all I found didn’t really catch it. So I looked a bit further and stumbled across the sieve mail filtering language (RFC). Here is an example sieve file (taken from libsieve-php, a PHP Sieve library):

require ["fileinto"];

if header :is "Sender" "owner-ietf-mta-filters@imc.org"
        {
        fileinto "filter"; # move to "filter" mailbox
        }
elsif address \:DOMAIN :is ["From", "To"] "example.com"
        {
        keep;                # keep in "In" mailbox
        }
elsif anyof (NOT address :all :contains
                ["To", "Cc", "Bcc"] "me@example.com",
              header :matches "subject"
                ["*make*money*fast*", "*university*dipl*mas*"])
        {
        fileinto "spam";    # move to "spam" mailbox
        }
else
        {
        fileinto "personal";
        }

To let this work you need to setup an LDA (Local Delivery Agent) for your MTA (Mail Transfer Agent, such as Exim) which puts incoming emails into the local user’s mailboxes. This LDA reads in the Sieve script (which f.e. resides in the user’s home directory) and evaluates the expressions to figure out where it should go.

Now while the script itself is already easier to read and understand than a cryptic procmail script, it’s far from being perfect:

  • Non-technical people will still have a hard time to write these rules
  • Users need physical (i.e. ftp / shell) access to the mail server to edit the script, this is especially problematic if your email users are virtual

The solution: ManageSieve

ManageSieve is a protocol specification which is relatively new and still pretty much in flux, but gains support pretty quickly. It especially targets problem number two, i.e. it allows the management of sieve scripts without giving a user shell access to the machine. ManageSieve clients authenticate via the IMAP login credentials and run their commands against a dedicated server port (usually 2000).

KMail already supports the ManageSieve protocol since KDE 3.5.9 and there is a Thunderbird plugin in the work. KMail was not an option, being on Mac and while Thunderbird is my main email client, its not the client of my girl (which uses Apple’s Mail). Even if I’d have been able to persuade her using Thunderbird again, it would have been a no-go area for her anyways: The Thunderbird plugin has no nice end user interface as of now, but merely comes across as a managed script editor (though a “real” UI is planned).

So I was very happy to find out that somebody at least wrote a ManageSieve plugin for my webmail client of choice, roundcube.

The setup: Dovecot’s ManageSieve server + Dovecot’s Sieve plugin for deliver + Roundcube’s managesieve patch

Since the ManageSieve standard is not yet completed, implementations tend to differ. The roundcube managesieve implementation was built around and only tested with Dovecot, a popular POP3/IMAP server, so my initial setup (the Exim/Courier IMAP tandem) didn’t fit. I quickly read dovecot’s docs with respect to Exim integration and decided to give the Courier replacement a try since it seemed well supported. This was supposed to be the easiest part, sudo apt-get remove courier-imapd && sudo apt-get install dovecot-imap, until I noticed that the installed dovecot version in Hardy (1.0.10) did not include the needed sieve patches, so I had to compile and patch everything myself (again, since the ManageSieve specification is not yet finished, its not part of the main dovecot distribution, either). Luckily, the exact workflow – downloading and patching Dovecot, downloading managesieve, installing and configuring everything – is documented here.

The final missing piece now was roundcube. Downloading and applying the patch was a no-brainer and worked out as expected – after patching a new “Filters” menu popped up in roundcube’s settings view:

Fine, until my first test showed that the rules weren’t applied. So I checked back into my server – everything seemed to be in place:

$ ls -lh .dovecot.sieve* sieve
lrwxrwxrwx 1 me me   21 2008-11-16 12:09 .dovecot.sieve -> sieve/roundcube.sieve
-rw------- 1 me me  560 2008-11-16 12:16 .dovecot.sievec

sieve:
total 16K
-rwx------ 1 me me  459 2008-11-16 12:09 roundcube.sieve
drwx------ 2 me me 4,0K 2008-11-16 12:09 tmp

(The ManageSieve specification allows to activate and deactivate multiple existing sieve scripts, dovecot’s implementation does this by symlinking to the correct one from .dovecot.sieve into sieve/<scriptname>. The .dovecot.sievec is the compiled, i.e. syntax checked version of the script, another implementation detail.)

And yes, my rules editing from within roundcube found their way into the file:

$ cat sieve/roundcube.sieve
require ["fileinto"];
# rule:[spam]
if anyof (header :contains "Subject" "*****SPAM*****")
{
    fileinto "Trash";
}
[...]

Looking into the logfile of deliver (dovecots LDA) shed light into the darkness:

deliver(me): 2008-11-16 02:20:28 Info: msgid=: save failed to Trash: Unknown namespace
deliver(me): 2008-11-16 02:20:28 Info: sieve runtime error: Fileinto: Generic Error
deliver(me): 2008-11-16 02:20:28 Error: sieve_execute_bytecode(/home/me/.dovecot.sievec) failed

Since dovecot 1.1 deliver respects the IMAP prefix setting in dovecot.conf, which I had to set during my courier-imap -> dovecot transition. This basically “virtually” adds a string prefix like “INBOX.” or something else to all mailbox names. (The actual use case is to have distinct “public” and “private” IMAP folders with namespaces, but I don’t use that.)

A simple example: If your Maildir folder structure looks like this on harddisk

cur
new
tmp
.Foo
.Foo.Bar

than this means that your mail client actually gets reported this structure on IMAP’s LIST command:

INBOX
INBOX.Foo
INBOX.Bar

This was actually what was reported to roundcube as well, but roundcube’s IMAP code removes the INBOX.-prefix for some reason, thus reporting the ManageSieve plugin the wrong mailbox path, “Trash” instead of “INBOX.Trash”.

After diving a bit through roundcube’s PHP code I could fix the issue with the rather ugly usage of a meant-to-be private function of roundcube’s IMAP API (patch is available here for the interested), but wohoo, now finally everything works as expected!

And again, a weekend is gone. The outcome? I can filter emails server-side and – I wrote this blog. I feel its hell about time to do some more substantial things again…

Update: If you managed to set up server-side filtering and wonder why your favourite Mail reader Thunderbird does not show you new emails in various IMAP target folders even though you’ve subscribed to them, ensure you’ve set the preference “mail.check_all_imap_folders_for_new” to “true (source).

If you’re setting up exim from scratch…

…and you’re a bloody novice like me, you’ll probably stumble upon Marc Merlin’s “Very detailled and featureful configuration example”. If you use that one and you wonder why on earth people can’t authenticate against your local SMTP via PAM, you seek hours and hours in different places, forums, IRC and whatnot, and all you get in /var/log/exim4/mainlog is a couple of these:

2008-09-26 22:35:15 svr_auth_login authenticator failed for <hostname> [<clientip>]:61588 I=[<serverip>]:25: 535 Incorrect authentication data (set_id=<login>)

make sure /etc/shadow is actually readable by Debian-exim, the exim4 user, f.e. by adding him to the shadow group… D’oh!

Don’t ask me how this worked in the original debian configuration (which unfortunately did not work for me in a couple of other places, otherwise I’d have stuck to it) – from what I’ve seen I believe it somehow used the courier installed on the same machine to do the authentification.

Kudos to this page which made me find the problem.

$_POST empty?!

So I was about to SSL-secure my new webmail setup, created a new cert on CAcert, installed it, configured my vhost accordingly, went to the webmail login page and… boom. Login was not possible. No error message, no log message, nothing.

What happened?

To make a long story short, the PHP superglobal $_POST which stores data from POST requests was completly empty, though a valid POST request has been triggered. Not even $HTTP_RAW_POST_DATA was set and a hint I found on the net about a not set content-type didn’t help either.

So I went back to my vhost configuration again, where I configured a simple redirect for the *:80 vhost to the *:443 vhost. I copied over my original configuration (PHP FCGI) from the SSL one over to the non-ssl one to check if the problem also persists on non-SSL connections. And apparently it did not! Weird enough, now it even worked when doing the request over SSL…! Even weirder was, as soon as I commented out certain (uneccessary) options like ErrorLog from the SSL one, it broke again…

Something must have been messed with my FastCGI php processes – since I only did a reload after each configuration change before, I decided to make a hard apache restart – and voila! The problem was gone completly!

Hrm… this reminds me that there was this one operating system which could also be fixed by a restart. If I could only remember its name…

New server setup

I finally got sick of my SuSE 9.3 V-Server when a good friend of mine pointed me to this really fancy and sexy IMAP web frontend called RoundCube last week. Written entirely in PHP 5 there was no real chance that I could get this easily working on my oldish PHP 4.3 installation without recompiling everything. I wanted to upgrade to some Ubuntu LTS server in short to middleterm anyways because I got sick of Plesk as well, and while I had these upgrade thoughts now for a while, the price tag for a temporary setup to make a clean transition was just too high: It would have cost me at least 65 Euro to get a throw-away V-Server for about three months – whereas two weeks would have been enough.

So, being a little kid with these things sometimes in terms of being not able to wait for stuff to happen, I did this one thing what one really should not do at all: Touching a running system.

I backed up all important stuff to a special directory inside the virtual machine and told the automatic installer procedure to start installing Ubuntu 8.04 LTS. After approximately two hours my working setup was gone. No emailing, no webserver, nothing.

So I started with a clean ubuntu server instance from scratch yesterday evening and it took me the whole last night to get some things working. While the mail setup is still a beast (need to read myself into exim and try out a couple of tutorials), I’m already quite proud of my Apache / FastCGI PHP setup. I copied a lot of ideas from the www setup at work where we have implemented separated, secured V-Host users, suexec-protected php wrappers and more. Tonight I added another little puzzle piece into the mix: SFTP/SCP access for individual users to their virtual hosts.

Again, the Ubuntu community was very helpful – I found a HOWTO for getting an sftp server up and running in a jailed environment. There was actually very little I had to change so it fitted my use case – instead of using /home/chroot as jail, I put the jail into /var/www/vhostjail and all websites / vhosts which should get file transfer access below that directory. The biggest plus with this setup – beside the security point (people can only sftp or scp to the jail and cannot break anything on the rest of the system) – is now that the user who uploads files and the webuser who executes the request (i.e. the Apache user) is one and the same. No need to make files world-readable or even -writable when setting up a web application which has to read or write data. No need to change the owner or the access of uploaded files because the webuser could otherwise not read, write or execute them.

Wow. I like that setup.

Now, if only exim would so easy to understand and master…