Setting up amavisd-new to run in a chroot jail
==============================================
2003-03, Mark Martinec
Last updated: 2003-06-26

This is not for inexperienced Unix administrators!

This is not an automated script, but rather a checklist, guidelines
and ideas to help you set up the environment in which amavisd-new
and its external services can run in a Unix chroot jail to reduce
the possible security threats and protect the rest of your system.

Details vary greatly from one Unix system to another. The following
examples are based on FreeBSD and Linux, but should be useful for
other Unix systems. Some of your paths are likely to be different.

The following is based on setting a SMTP-based amavisd-new (e.g. to be
use with Postfix or dual-MTA setup), and tried out with all external
decoding programs, with SpamAssassin and with the following virus scanners:
Clam Antivirus clamscan and clamd, Sophos sweep, SAVI-Perl and Sophie



exit   # This is NOT an automatic script!!!
       # Don't execute commands without knowing what they will do!!!

# ESSENTIAL, DON'T FORGET to cd to your new chroot home directory
# before running further commands:
#
mkdir /var/amavis
cd /var/amavis
umask 022

# make directory structure within the current directory (/var/amavis)
mkdir -p etc dev bin lib tmp var/run var/virusmails
mkdir -p usr/bin usr/lib usr/libexec usr/share usr/lib/zoneinfo
mkdir -p usr/share/misc usr/share/spamassassin etc/mail/spamassassin

# make dev/null - adjust MAJOR/MINOR as appropriate (see ls -l /dev/null)
mknod dev/null c 2 2   # FreeBSD?
mknod dev/null c 1 3   # Linux?

# make a symbolic link so that chrooted processes can refer to the
# home directory as /var/amavis (same as not-chrooted), and do not have
# to handle it differently (i.e. referring to it as  / )
ln -s / var/amavis

# copy required binaries to /var/amavis/usr/bin
for j in \
  /usr/bin/file /usr/bin/gzip /usr/bin/bzip2 \
  /usr/local/bin/nomarch /usr/local/bin/arc \
  /usr/local/bin/unrar \
  /usr/local/bin/arj /usr/local/bin/unarj \
  /usr/local/bin/zoo /usr/local/bin/lha \
  /usr/local/bin/lzop /usr/local/bin/freeze /bin/cpio \
  /usr/local/bin/sweep /usr/local/bin/clamscan /usr/local/sbin/sophie
do cp -p $j usr/bin/; done

# copy needed /etc files to /var/amavis/etc
for j in \
  /etc/amavisd.conf \
  /etc/protocols /etc/services /etc/hosts \
  /etc/resolv.conf /etc/nsswitch.conf /etc/host.conf \
  /etc/group /etc/passwd
do cp -p $j etc/; done

# copy shared libraries to /var/amavis/lib
#   (check:  ldd /var/amavis/usr/bin/*  to see which ones are needed)
#
#FreeBSD: 
for j in \
  /usr/lib/libc.so.5 /usr/lib/libm.so.2 /usr/lib/libstdc++.so.4 \
  /usr/lib/libz.so.2 \
  /usr/local/lib/libsavi.so.3 /usr/local/lib/libclamav.so.1
do cp -p $j usr/lib/; done
cp -p /usr/libexec/ld-elf.so.1 usr/libexec/

#Linux:
for j in \
  /lib/libc.so.6 /lib/libm.so.6 /lib/ld-linux.so.2 \
  /lib/libpthread.so.0 /lib/libresolv-2.3.2.so /lib/libnss_*.so* \
  /lib/libgcc_s.so.1 /usr/lib/libstdc++.so.5 \
  /usr/lib/libz.so.1 /usr/lib/libbz2.so.1 /lib/libdb*.so* \
  /usr/local/lib/libsavi.so.3 /usr/local/lib/libclamav.so.1
do cp -p $j lib/; done


# needed by SpamAssassin:
cp -p /etc/mail/spamassassin/local.cf etc/mail/spamassassin/
cp -pR /usr/local/share/spamassassin usr/share/  # FreeBSD
cp -pR /usr/share/spamassassin       usr/share/  # Linux

# needed by file(1):
cp -p /usr/share/misc/magic*  usr/share/misc/ # FreeBSD
cp -p /usr/share/magic        usr/share/      # Linux

# needed by AV scanners (Sophos, ClamAV, H+BEDV AntiVir, ...)
mkdir -p usr/local/sav
cp -pR /usr/local/sav usr/local/

mkdir -p usr/local/share/clam
cp -pR /usr/local/share/clam usr/local/share/

mkdir -p usr/lib/AntiVir
cp -pR /usr/lib/AntiVir usr/lib/



# what about /etc/localtime and /usr/lib/zoneinfo ?
# If you have Postfix, check its chroot setup script for further ideas:
# postfix-xxx/examples/chroot-setup/YOUR-OS

# set protections
chmod 1777 tmp
chmod 666 dev/null

# FreeBSD:
chown -R root:wheel /var/amavis
chown vscan:vscan /var/amavis
chown -R vscan:vscan var/virusmails
chown -R vscan:vscan amavis.* amavisd.* tmp
chown -R vscan:vscan *.log razor* .razor* .spamassassin

# Linux
chown -R root:root /var/amavis
chown amavis:amavis /var/amavis
chown -R amavis:amavis var/virusmails
chown -R amavis:amavis amavis.* amavisd.* tmp
chown -R amavis:amavis *.log razor* .razor* .spamassassin

# Daemonized virus scanners (e.g. Sophie, ClamD) may be
# started in the same chroot jail, or not.  E.g.
#   chroot /var/amavis /usr/bin/sophie -D
#
# If you want, you may now remove /usr/local/sav and make a link instead,
# to avoid having two copies of Sophos database:
#   ln -s /var/amavis/usr/local/sav /usr/local/sav 
# consider:
#   ln -s /var/amavis/var/run/sophie      /var/run/     # Sophie socket
#   ln -s /var/amavis/var/run/sophie.pid  /var/run/


# Programs may be tested individually whether they are happy
# in the chroot jail:
#
perl -e '$uid=getpwnam("vscan")   or die "E1:$!";
         chroot "/var/amavis"     or die "E2:$!";
         $> = $< = $uid;
         open(STDIN,"</dev/null") or die "E3:$!";
         exec qw(file /etc/amavisd.conf)'; echo $?
# or...
#    ... exec qw(file /usr/bin/gzip)'; echo $?
#    ... exec qw(gzip -d 0.lis)'; echo $?
#    ...


Edit /var/amavis/etc/amavisd.conf, setting:
  $MYHOME = '/var/amavis';
  $TEMPBASE = $ENV{TMPDIR} = "$MYHOME/tmp";
  $daemon_chroot_dir = $MYHOME;

Logging should be set to syslog.

Because the program starts outside the chroot jail and brings-in all Perl
modules first, there is fortunately no need to make a copy of Perl modules
inside the jail. As a consequence, the 'reload' (HUP) can not work
and you need to kill and restart the daemon from outside the chroot jail.

If Perl complains about missing modules, add them to the list
in file amavisd:

    fetch_modules('REQUIRED BASIC MODULES', qw(
	Exporter POSIX Fcntl Socket Errno Time::HiRes
	IO::File IO::Socket IO::Wrap IO::Stringy
    ...

With earlier version of Perl you might need to add autoloaded modules
to the list, such as:
        auto::POSIX::setgid auto::POSIX::setuid


If quarantine files are to be gzipped, shell is needed in
chroot jail as well - or else replace the line (in file 'amavisd'):
  'spam-quarantine'  => sub { ($QUARANTINEDIR, "$VIRUSFILE.gz") },
with:
  'spam-quarantine'  => sub { ($QUARANTINEDIR, $VIRUSFILE) },
to disable compressing quarantined spam.


NOTE:
  OpenBSD chroot specifics are described in the document
  http://lawmonkey.org/anti-spam.html, by Scott Vintinner.

NOTE:
  With Net::Server 0.85 you may see a message:

    Net::Server: Couldn't POSIX::setuid to ... []

  If the process UID remained 0 (root), the program will terminate,
  otherwise consider the message just as informative.

  This is because POSIX::setuid() returns a string "0 but true",
  which is not a UID, as expected bu Net::Server.


NOTE:
  On FreeBSD (and possibly on other systems) you may need to apply
  the following patch to Net::Server (based on version 0.85), perhaps
  located in the directory /usr/local/lib/perl5/site_perl/5.8.0/Net

--- Server.pm~	2003-03-07 15:51:35.000000000 +0100
+++ Server.pm	2003-04-25 13:59:12.000000000 +0200
@@ -475,8 +475,10 @@
     }
   };
   if( $@ ){
-    if( $< == 0 || $> == 0 ){
+    if( $> == 0 ){
       $self->fatal( $@ );
+    } elsif( $< == 0){
+      $self->log(2,"NOTICE: Effective UID changed, but Real UID is 0: $@");
     }else{
       $self->log(2,$@);
     }
--- Server/Daemonize.pm~	2002-09-24 20:55:15.000000000 +0200
+++ Server/Daemonize.pm	2003-04-25 13:58:16.000000000 +0200
@@ -204,8 +204,7 @@
     die "Couldn't become uid \"$uid\"\n";
   }
   my $result = POSIX::setuid( $uid );
-  if( ! defined($result)
-      || $result != $uid ){ # assuming this is true for all systems
+  if( ! defined($result) ){
     die "Couldn't POSIX::setuid to \"$uid\" [$!]\n";
   }
   return 1;
