http:// qmail.jms1.net / clamav / qmail-scanner.shtml

Fixing clamav to work with qmail-scanner

I use Qmail-Scanner to scan the message traffic on my server for viruses, and Clam Anti-Virus (or "clamav") as the actual virus scanning engine.

One of the common problems that people regularly run into when setting these programs up to work together is that they both run as dedicated non-root users who cannot normally read each others' files. People who are experiencing this issue generally see log messages which look like this:

clamdscan: corrupt or unknown clamd scanner error or memory/resource/perms problem

This page describes two ways I have found to resolve this problem on my own server and on my clients' servers, along with a third way which may end up making the entire issue go away...

2006-04-05 The author of qmail-scanner has elected to take my suggestion (the "Best solution (maybe)" listed below) in upgrading qmail-scanner. I'm leaving the rest of the page intact, but my recommendation now is that you upgrade to qmail-scanner 2.0.


Background on clamav

One of the problems with clamav, indeed with any virus scanner, is that it takes time to load and parse all of the virus definitions into memory when the scanner starts up. For an interactive process it's not usually a big deal, but for scanning email on a busy server it can slow things down a lot- to the point where a message might take five seconds to be scanned. That may not sound like a long time, but imagine having to do that several thousand times an hour.

ClamAV comes with a program called clamd which loads the virus definitions into memory and then sits in the background, waiting to be told which files to scan. Having clamd run the actual scan is a lot faster, simply because it doesn't have to load and parse the virus definitions into memory.

In order to have a file scanned by clamd, you use the program clamdscan. It takes the same command line parameters as the normal clamscan command line scanner program, but instead of loading the virus definitions and doing the work itself, it passes a command to clamd and waits for the results.

In order for clamd to be able to scan a file, the file itself must be readable to the userid which clamd runs as. For security reasons, clamd does not (and should not be) run as root. Therefore any file which is to be scanned with clamd has to be world readable, or at least made readable to the clamav user.

ClamAV also comes with a program called freshclam, which runs periodically to download new virus definitions. Whenever new definitions are available, freshclam connects to clamd and tells it to reload the definitions from the disk. This way it's not even necessary to stop and restart clamd when new definitions arrive.


Background on Qmail-Scanner

Qmail-Scanner is a perl script which runs as a QMAILQUEUE replacement. It works by breaking the incoming message into a series of individual files for each MIME segment, storing them in a tree structure which mirrors their parent/child relationships of the message's MIME structure. It then tells clamav to scan that entire directory tree.

When qmail-scanner installs on a system, it searches for several virus scanners that it knows about. When it sees ClamAV, it configures itself to use clamdscan, because it is so much faster than clamscan. On a busy server this can mean the difference between a working mail server and a dead mail server.

The directions which come with qmail-scanner have you create a userid called qscand, which is used for running qmail-scanner and the various virus and spam scanners it calls.


The Problem

When qmail-scanner writes out the directory tree of MIME parts for the original message, these files and directories are owned by the userid that qmail-scanner runs as, and not readable by anybody else. And because clamd normally runs as the clamav user, it is not able to read the files which qmail-scanner is asking it to scan.


A BAD Solution

There are a few web pages on the internet, including at least one version of the qmailrocks directions, which are telling people to delete their clamdscan binary and copy the clamscan binary into its place.

While this will "make it work", this is about as STUPID as buying a new car every day because you don't know how to put fuel in the tank. What this does is forces every incoming message to load and parse the virus definitions into memory, totally throwing away the advantages of running clamd in the first place- in particular, the speed advantage.

The process of scanning a typical email for viruses normally takes less than 1/10 of a second on my server- using clamscan instead of clamdscan and clamd raises this time to almost six seconds. DO NOT DO THIS TO YOUR SERVER.


The Current Solution

One solution, which is how most people do it, what the qmail-scanner FAQ says to do (see "Content scanner issues", items 7 and 12) and how I did it myself for a few years, is to make clamd run as the qscand user (which is a separate userid that qmail-scanner runs as.) This way clamd will have permission to read the files written by qmail-scanner. To do this, and continue to have clamd work, the files it uses (the virus definitions, the log files, and the "pid" file which is used to stop and restart the running instance of clamd) also need to be onwed by the qscand user as well.

This change is done in the configuration files for clamd and freshclam. Examples of what to change are shown below. Note that the filenames here are the filenames used by the RedHat/Fedora RPM files- the filenames on your system may be different.


A better solution

I had a thought while I was originally typing up this web page... a much easier way to make these programs work together work might be to install qmail-scanner so that it runs as the clamav user.

This is done by adding --qs-user clamav to the "./configure" command line when building qmail-scanner.

I have been running this way on my own server since 2005-05-15, and it seems to be working beautifully so far. Note that this change also causes all of the other programs qmail-scanner uses- reformime/ripmime, tnef, and other virus scanners- to run as the clamav user as well. For the most part this is harmless, but if you have a customized setup which requires that some programs be run as a specific userid, you may need to adjust things there as well.

To illustrate why this is makes things easier, here is a walk-through from my own server (running White Box Linux 3, a clone of RedHat Enterprise 3) which shows how to download the source RPM, build binary RPMs (as a non-root user, of course) and then upgrade the software. The whole process, including keeping notes for this web page, took about five minutes.

% cd
% cat .rpmmacros
%_topdir /home/jms1/rpm
%packager John Simpson <jms1@jms1.net>
% wget http://crash.fce.vutbr.cz/crash-hat/4/clamav/clamav-0.86.2-1.src.rpm
...
% rpm -i clamav-0.86.2-1.src.rpm
warning: clamav-0.86.2-1.src.rpm: V3 DSA signature: NOKEY, key ID 6cdf2cc1
% cd rpm/SPECS
% rpmbuild -ba --without milter clamav.spec
...
+ exit 0
% su -
Password:
# cd ~jms1/rpm/RPMS/i386
# service clamd stop
Stopping Clam AV daemon:                 [  OK  ]
# rpm -Fvh clamav-0.86.2-1.i386.rpm
...
# service clamd start
Starting Clam AV daemon:                 [  OK  ]
# freshclam
ClamAV update process started at Thu Aug 11 22:03:56 2005
main.cvd is up to date (version: 33, sigs: 36102, f-level: 5, builder: tkojm)
daily.cvd is up to date (version: 1011, sigs: 2451, f-level: 5, builder: diego)
# exit
% clamdscan -V
ClamAV 0.86.2/1011/Tue Aug 9 05:20:28 2005

This assumes that you have already configured the home directory of a non-root user in order to facilitate building RPM files. If not, here's a quick walk-through...

% cd
% echo '%_topdir /home/userid/rpm' > .rpmmacros
% echo '%packager Your Name <email@domain.xyz>' >> .rpmmacros
% mkdir rpm
% cd rpm
% mkdir BUILD RPMS SOURCES SPECS SRPMS


The best solution

The best possible solution, in my opinion, would be to change qmail-scanner so that after it expands a message's MIME structure into a directory tree, it then makes the directories and files group-readable. Then, other userid's which need to read the files (such as the "clamav" user that clamd normally runs as, or the "spamd" user that spamassassin's "spamd" process runs as) could be added to the appropriate group, and the other scanners would have permission to read the files as needed.

Unfortunately, I'm not the maintainer of qmail-scanner. While I can certainly change my own qmail-scanner-queue.pl file to make this happen, this doesn't help everybody who's already using qmail-scanner. I have emailed the author of qmail-scanner with this suggestion, and it turns out he had already been thinking about the same kind of solution. In fact, he tried my earlier suggestion (adding explicit "chmod" and "chgrp" commands to the script) but they didn't work, but then he remembered how to make it work, and has tested it, and plans to incorporate it into the next version of qmail-scanner.

2006-04-05 The author of qmail-scanner HAS incorporated this idea into qmail-scanner 2.0. My recommendation is that you upgrade to version 2.0 or higher. I am leaving the text below intact, so that you can see what the differences are, and what the change was.

For those who want to try this on their own without waiting on the next version of qmail-scanner, I have a patch file which can be applied to the qmail-scanner-1.25 source before you run the ./configure command, which should make your qmail-scanner installation work without any other special privileges, as long as the userid's of any other programs which need to access the unpacked messages (i.e. spamassassin and your virus scanners) are members of the qscand group.

If you would rather not use the patch file, you can make the following changes to the qmail-scanner-queue.template file in the qmail-scanner source package by hand before running the ./configure command, or to the qmail-scanner-queue.pl file if you have already installed it on your system. (The code and line numbers here are from qmail-scanner-queue.template from qmail-scanner version 1.25. If you are editing a "live" qmail-scanner-queue.pl file, your line numbers may not exactly match the numbers shown here.)

If it's not obvious, the lines in RED should be removed, and the lines in BLUE should be inserted in their place. The actual characters being changed within each line are highlighted in green.

Line 312 of qmail-scanner-queue.template
if ( $opt_v ) { &show_version; exit 0; }
chdir($scandir); umask(0077); umask(0027);
if (! -d "$scandir/tmp") { mkdir("$scandir/tmp",0700) || &error_condition("cannot create $scandir/tmp - $!"); mkdir("$scandir/tmp",0750) || &error_condition("cannot create $scandir/tmp - $!"); }
my ($quarantine_event,$quarantine_event_tmp,$quarantine_DOS)=0;

Line 352 of qmail-scanner-queue.template
if ($DEBUG ) { open(LOG,">>$scandir/$debuglog"); select(LOG);$|=1; &debug("+++ starting debugging for process $$ by uid=$real_uid"); }
&debug("setting UID to EUID so subprocesses can access files generated by this script"); $< = $>; # set real to effective uid #$( = $); # set real to effective gid $( = $); # set real to effective gid
&debug("program name is $prog, version $VERSION"); if ($opt_z) { &scan_queue; exit 0; }

Line 524 of qmail-scanner-queue.template
sub working_copy { my ($hdr,$last_hdr,$value,$num_of_headers,$last_header,$last_value,$attachment_filename); select(STDIN); $|=1;
&debug("w_c: mkdir $ENV{'TMPDIR'}"); mkdir("$ENV{'TMPDIR'}",0700)||&error_condition("$ENV{'TMPDIR'} exists - try again later..."); mkdir("$ENV{'TMPDIR'}",0750)||&error_condition("$ENV{'TMPDIR'} exists - try again later..."); chdir("$ENV{'TMPDIR'}")||&error_condition("cannot chdir to $ENV{'TMPDIR'}/"); if (-f "$scandir/$wmaildir/tmp/$file_id" || -f "$scandir/$wmaildir/new/$file_id") { &error_condition("$file_id exists, try again later"); } &debug("w_c: start dumping incoming msg into $scandir/$wmaildir/tmp/$file_id [",&deltatime,"]");

We also need to set the setgid bit on the qmail-scanner-queue.pl script (or the qmail-scanner-queue wrapper, if you're running without setuid perl.) If you are editing the package before installing it, make this change in the configure script so that when it installs the actual qmail-scanner-queue.pl script, it will set both the setuid and setgid bits:

Line 1724 of configure
if [ "$INSTALLIT" != "0" ]; then if [ "$DONOTCONFIRM" != "1" ]; then echo "Hit RETURN to create initial directory structure under $AS_QQ," printf "and install qmail-scanner-queue.pl under $BINDIR: " read ans fi mv -f $BINDIR/qmail-scanner-queue.pl $BINDIR/qmail-scanner-queue.pl.old 2>/dev/null cp -f qmail-scanner-queue.pl $BINDIR/qmail-scanner-queue.pl chown $QS_USER:$QS_USER $BINDIR/qmail-scanner-queue.pl chmod 4755 $BINDIR/qmail-scanner-queue.pl chmod 6755 $BINDIR/qmail-scanner-queue.pl if [ -f "$BINDIR/antivirus-qmail-queue.pl" -a ! -L "$BINDIR/antivirus-qmail-queue.pl" ]; then mv -f $BINDIR/antivirus-qmail-queue.pl $BINDIR/antivirus-qmail-queue.pl.old ln -s $BINDIR/qmail-scanner-queue.pl $BINDIR/antivirus-qmail-queue.pl cat<<EOF

If you are changing an existing install of qmail-scanner, run these commands to turn on the setuid and setgid bits:

# cd /var/qmail/bin
# chmod 6755 qmail-scanner-queue.pl if you have setuid perl
# chmod 6755 qmail-scanner-queue if you DON'T have setuid perl

Then add the clamav user to the qscand group (which is normally done with a command like usermod, see the man pages on your system) and then restart clamd. It should now be able to read the files that qmail-scanner needs it to scan. If you are using spamassassin, you should also add the spamd user to the qscand group, and restart spamd.

Note that this is something that I believe should work, but I have not yet tried it on my own server. However, Jason (the developer of qmail-scanner) tells me it's working on his server, and as I type this I am getting ready to change my own server so that it does this as well.

Also, JT Justman sent an EXCELLENT list of what he had to do in order to make this idea work on his non-setuid-perl system. I have updated the web page with his changes (the two "mkdir" commands with changed permissions) and added "turn on the setgid bit" as an explicit step. The changes also prompted me to create a patch file to automate the procedure. Thanks!

I will update this page with the status of my own testing. If somebody else has a test server and can try it and let me know how it works, I would appreciate it. In particular, all of the OS's that I run have setuid perl- so if somebody can test this on a system which doesn't have setuid perl, that would be extremely helpful.

2007-02-27 There is one other detail which needs to be checked... the AllowSupplementaryGroups option in the /etc/clamd.conf needs to be ENABLED in order for clamd to be able to read files which are accessible via group id. Thanks to Ventsyslav Vassilev for pointing this out on the qmailrocks mailing list.