
Send corrections, suggestions, and comments about this HOWTO to Mickey Hill <mickey@rudolphtire.com>. Please do not send installation or configuration questions to this address; instead, post them to the MIMEDefang mailing list.
MIMEDefang is a trademark of Roaring Penguin Software Inc.
Linux is a registered trademark of Linus Torvalds.
UNIX is a registered trademark of The Open Group in the United States and other countries.
Red Hat is a registered trademark of Red Hat, Inc.
Slackware is a registered trademark of Slackware Linux, Inc.
Solaris is a trademark or registered trademark of Sun Microsystems, Inc. in the United States and other countries.
BSD is a registered trademark of Berkeley Software Design, Inc.
AIX is a trademark or registered trademark of IBM Corporation.
Sendmail is a registered trademark of Sendmail, Inc.
QPopper is a trademark of QUALCOMM Inc.
Network Associates and McAfee are registered trademarks of Network Associates, Inc.
AntiVir is a registered trademark of H+BEDV Datentechnik GmbH.
All other registered and unregistered trademarks in this document are the property of their respective owners.
MIMEDefang consists of three major components: mimedefang, mimedefang.pl, and mimedefang-filter. mimedefang is written in C, and is the actual mail filter that interfaces with sendmail. mimedefang splits incoming messages into parts and executes mimedefang.pl, a Perl script. mimedefang.pl then operates on these message parts. It does not normally need modification. mimedefang-filter is a Perl script fragment that is read by mimedefang.pl, and serves as the configuration file. This configuration file is where the vast majority of user customization takes place.
More advanced configurations can entirely replace mimedefang.pl and mimedefang-filter with a custom filter that is executed by mimedefang.
MIMEDefang is licensed under the GNU General Public License.
In general, hardware requirements are the same as for sendmail.
Perl 5.001 or greater is required by MIMEDefang. Some other packages described in this document may require a later version.
This HOWTO was written using Red Hat Linux 7.2, and updated using Red Hat Linux 9. Installation on other versions of Linux or on UNIX should be similar; however, directory and file names and locations may be different, and installation techniques may vary.
Correct timekeeping is essential to any mail server. NTP (network time protocol) and ntpd (the NTP daemon) are recommended for this purpose.
A permanent connection to the Internet is assumed. While a mail server with sendmail can be run using only a dial-up or other intermittent connection, such a configuration is beyond the scope of this document.
MIMEDefang should compile on any modern Linux or UNIX system. It has been known to compile on the following systems:
Red Hat Linux 9
Red Hat Linux 8.0
Red Hat Linux 7.3
Red Hat Linux 7.2
Red Hat Linux 7.1
Red Hat Linux 7.0
Red Hat Linux 6.2
Red Hat Linux 6.1
Slackware Linux 8.1
Slackware Linux 8.0
Slackware Linux 7.1
Slackware Linux 7.0
SuSE Linux 7.3
Solaris 8
Solaris 7
Solaris 2.5.1
FreeBSD 4.5-STABLE
OpenBSD 3.0
AIX 5.1
AIX 4.3.3
HP-UX 11.00
MIMEDefang has been operated on mail servers processing fewer than 100 to as many as 1.5 million messages per day.
Sendmail version 8.12.0 or later is required by MIMEDefang. Version 8.12.9 is recommended. Earlier versions (8.11.x) contain errors in the libmilter code and may not work reliably. Versions prior to 8.12.9 contain known security vulnerabilities.
Download and unpack the source
Enable milter support in devtools/Site/site.config.m4
Build the source
Create cf/cf/sendmail.mc
Build and install the cf files
Create smmsp user and group.
Create symbolic link for man page directory.
Install
Build and install makemap
Build and install libmilter
Build and install smrsh
Install headers and libraries for MIMEDefang
Edit /etc/mail/local-host-names
Edit /etc/mail/relay-domains
Edit /etc/mail/virtusertable
Edit /etc/mail/aliases
Create /etc/init.d/sendmail with queue runner and MIMEDefang support
Link startup script in rc3.d, rc0.d, and rc6.d
Create the /usr/src/sendmail directory and cd to it.
The latest source is available from ftp://ftp.sendmail.org/pub/sendmail/. This example uses version 8.12.9.
Download the source at ftp://ftp.sendmail.org/pub/sendmail/sendmail.8.12.9.tar.gz.
Unpack the source:
tar xvfz sendmail.8.12.9.tar.gz
Remove the source:
rm sendmail.8.12.9.tar.gz
Cd into the sendmail-8.12.9 directory.
Sendmail does not support mail filters by default, and must be compiled with filter support enabled by defining -DMILTER. To do this, create devtools/Site/site.config.m4 with the following lines:
dnl Milter APPENDDEF(`conf_sendmail_ENVDEF', `-DMILTER') APPENDDEF(`conf_libmilter_ENVDEF', `-D_FFR_MILTER_ROOT_UNSAFE')
The first APPENDDEF enables the mail filter interface. The second APPENDDEF keeps libmilter from unlinking a socket when running as root. It is strongly recommended that MIMEDefang is not run as root. MIMEDefang does not need root access to communicate with sendmail.
In the sendmail/ directory, build the source:
sh Build
NOTE: If this is not the first build in this directory tree, and you have changed any of the configuration files in the devtools/Site directory since the last build, use the -c option ("sh Build -c") to clear the previous build configuration. This may be the case if you forgot the site.config.m4 file the first time, or if your distribution preinstalled sendmail without milter support.
Change to the cf/cf/ directory. Copy generic-linux.mc to sendmail.mc. Next, tailor it as explained in cf/README.
Example sendmail.mc file:
divert(-1)dnl # # Copyright (c) 1998, 1999 Sendmail, Inc. and its suppliers. # All rights reserved. # Copyright (c) 1983 Eric P. Allman. All rights reserved. # Copyright (c) 1988, 1993 # The Regents of the University of California. All rights reserved. # # By using this file, you agree to the terms and conditions set # forth in the LICENSE file which can be found at the top level of # the sendmail distribution. # # divert(0)dnl VERSIONID(`$Id: generic-linux.mc,v 8.1 1999/09/24 22:48:05 gshapiro Exp $') OSTYPE(linux)dnl DOMAIN(generic)dnl define(`confBAD_RCPT_THROTTLE', `3')dnl define(`confTO_IDENT',`0s')dnl define(`confMILTER_LOG_LEVEL',`1')dnl FEATURE(`virtusertable',`hash -o /etc/mail/virtusertable')dnl FEATURE(`smrsh',`/usr/sbin/smrsh')dnl FEATURE(`always_add_domain')dnl FEATURE(`use_cw_file')dnl FEATURE(`local_procmail')dnl INPUT_MAIL_FILTER(`mimedefang', `S=unix:/var/spool/MIMEDefang/mimedefang.sock, F=T, T=S:1m;R:1m') MAILER(local)dnl MAILER(smtp)dnl MAILER(procmail)dnlThe important line is INPUT_MAIL_FILTER. This tells sendmail to send all mail through the specified filter.
The sendmail documentation includes this information about INPUT_MAIL_FILTER:
[Y]ou can override the default timeouts used by sendmail when
talking to the filters using the T= equate. There are four fields inside
of the T= equate:
Letter Meaning
C Timeout for connecting to a filter (if 0, use system timeout)
S Timeout for sending information from the MTA to a filter
R Timeout for reading reply from the filter
E Overall timeout between sending end-of-message to filter
and waiting for the final acknowledgment
Note the separator between each is a ';' as a ',' already separates equates
and therefore can't separate timeouts. The default values (if not set in
the config) are:
T=C:5m;S:10s;R:10s;E:5m
where 's' is seconds and 'm' is minutes.
NOTE: Some users of MIMEDefang and SpamAssassin have had troubles with filter
timeouts. This can be somewhat helped by increasing the timeouts:
INPUT_MAIL_FILTER(`mimedefang', `S=unix:/var/spool/MIMEDefang/mimedefang.sock, F=T, T=S:5m;R:5m')NOTE: This configuration is designed so that if the filter (MIMEDefang) fails for any reason while receiving an email, the mail server will return a tempfail code and "Please try again later" message. This is highly recommended if you are scanning for viruses, so that incoming messages are rejected if there is any error.
On the other hand, IF you are only scanning for spam and not for viruses, IF it is more important to you that you get the incoming message no matter what, and IF you are CERTAIN that there is no risk to you or your users if an email is accepted even if there is an error in the filter, you may wish to allow incoming messages to be accepted if the filter fails. To do this, remove the F=T flag.
INPUT_MAIL_FILTER(`mimedefang', `S=unix:/var/spool/MIMEDefang/mimedefang.sock, T=S:5m;R:5m')Build the cf file:
sh Build sendmail.cfIf this is not a new installation, back up your current /etc/mail/sendmail.cf and the sendmail binary in /usr/sbin.
If this is a new installation, create the /etc/mail directory if it does not exist:
mkdir /etc/mail
Install sendmail.cf as /etc/mail/sendmail.cf and submit.cf as /etc/mail/submit.cf:
sh Build install-cfCreate a new user smmsp and a new group smmsp with ID's of 25.
Create a symbolic link for the man pages:
ln -s /usr/share/man /usr/manIn the sendmail/ directory, install the sendmail binary:
sh Build install cd ..
For each of the associated sendmail utilities (makemap, mailstats, etc.), read the README in the utility's directory. When you are ready to install it, back up your installed version and type "sh Build install". At a minimum, makemap and libmilter must be installed. If you use smrsh or other utilities, install them as well.
cd makemap sh Build sh Build install cd .. cd libmilter sh Build sh Build install cd .. cd smrsh sh Build sh Build install cd ..Cd into the /usr/src/sendmail/sendmail-8.12.9 directory.
Install headers and libraries for MIMEDefang:
mkdir -p /usr/local/include/sendmail cp -R include/* /usr/local/include/sendmail cp -R sendmail/*.h /usr/local/include/sendmail mkdir -p /usr/local/lib cp obj.Linux.2.4.7-10.i686/*/*.a /usr/local/libNOTE: On the last "cp" command, replace "obj.Linux.2.4.7-10.i686" with the appropriate "obj.*" directory created by the Sendmail build script.
/etc/mail/local-host-names should have the following lines:
# local-host-names - include all aliases for your machine here. yourcompany.com mail.yourcompany.comCreate /etc/mail/relay-domains with the following lines:
# relay-domains - Hosts for which relaying is permitted yourcompany.comEdit /etc/mail/virtusertable as desired.
Edit /etc/mail/aliases and change the alias for root to a valid address.
# Uncomment and *CHANGE* this! root: insert-human-being-hereCreate or edit startup/shutdown script at /etc/init.d/sendmail to start MIMEDefang. Be sure to chmod 755. The sample script below is the Red Hat default script with these two lines added:
/etc/init.d/mimedefang start /etc/init.d/mimedefang stopOn other systems, add those two lines where appropriate to start MIMEDefang before the sendmail daemon starts, and stop MIMEDefang after the sendmail daemon stops.
The 'mimedefang' init script is included with MIMEDefang and must be manually installed into /etc/init.d (or other appropriate location) during MIMEDefang installation.
In addition, you must run a queue runner to periodically check for administrative messages generated by MIMEDefang. A queue runner is a separate sendmail process that retrieves and sends messages from a queue directory. The default Red Hat script starts a queue runner automatically, although the default interval may be too long. On other systems, a queue runner can be started by adding the following line to your init script, after the main sendmail process is started:
sendmail -Ac -qp5mThe '5m' represents the interval at which the queue runner checks for mail (in this case, 5 minutes). This time can be changed to suit your personal preference. The Red Hat sample script relies on /etc/sysconfig/sendmail, which should contain these lines. Note the change in queue runner time from the default 1h to 5m:
DAEMON=yes QUEUE=5mRed Hat sample init script:
#!/bin/bash
#
# sendmail This shell script takes care of starting and stopping
# sendmail.
#
# description: Sendmail is a Mail Transport Agent, which is the program \
# that moves mail from one machine to another.
# processname: sendmail
# config: /etc/mail/sendmail.cf
# pidfile: /var/run/sendmail.pid
# Source function library.
. /etc/rc.d/init.d/functions
# Source networking configuration.
. /etc/sysconfig/network
# Source sendmail configureation.
if [ -f /etc/sysconfig/sendmail ] ; then
. /etc/sysconfig/sendmail
else
DAEMON=no
QUEUE=1h
fi
# Check that networking is up.
[ ${NETWORKING} = "no" ] && exit 0
[ -f /usr/sbin/sendmail ] || exit 0
RETVAL=0
prog="sendmail"
start() {
# Start daemons.
/etc/init.d/mimedefang start
echo -n $"Starting $prog: "
/usr/bin/newaliases > /dev/null 2>&1
if test -x /usr/bin/make -a -f /etc/mail/Makefile ; then
make -C /etc/mail -s
else
for i in virtusertable access domaintable mailertable ; do
if [ -f /etc/mail/$i ] ; then
makemap hash /etc/mail/$i < /etc/mail/$i
fi
done
fi
daemon /usr/sbin/sendmail -bd \
$([ -n "$QUEUE" ] && echo -q$QUEUE)
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/sendmail
if ! test -f /var/run/sm-client.pid ; then
echo -n $"Starting sm-client: "
touch /var/run/sm-client.pid
chown smmsp:smmsp /var/run/sm-client.pid
daemon --check sm-client /usr/sbin/sendmail -L sm-msp-queue -Ac \
$([ -n "$QUEUE" ] && echo -q$QUEUE)
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/sm-client
fi
return $RETVAL
}
stop() {
# Stop daemons.
echo -n $"Shutting down $prog: "
killproc sendmail
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/sendmail
if test -f /var/run/sm-client.pid ; then
echo -n $"Shutting down sm-client: "
killproc sm-client
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f /var/run/sm-client.pid
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/sm-client
fi
/etc/init.d/mimedefang stop
return $RETVAL
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
stop
start
RETVAL=$?
;;
condrestart)
if [ -f /var/lock/subsys/sendmail ]; then
stop
start
RETVAL=$?
fi
;;
status)
status sendmail
RETVAL=$?
;;
*)
echo $"Usage: $0 {start|stop|restart|condrestart|status}"
exit 1
esac
exit $RETVAL
Create symbolic links:
cd /etc/rc3.d ln -s ../init.d/sendmail S80sendmail cd /etc/rc0.d ln -s ../init.d/sendmail K30sendmail cd /etc/rc6.d ln -s ../init.d/sendmail K30sendmailCreate the mqueue directory:
mkdir /var/spool/mqueue chmod 700 /var/spool/mqueue
NOTE: You do not need Qpopper if you are installing Cyrus IMAP. Cyrus includes its own integrated POP3 server.
In the /usr/local directory, create a symbolic link:
ln -s ../share/man manCd to the /tmp directory.
The latest source is available from ftp://ftp.qualcomm.com/eudora/servers/unix/popper/. This example uses version 4.0.5.
Download the source at ftp://ftp.qualcomm.com/eudora/servers/unix/popper/qpopper4.0.5.tar.gz.
Unpack the source:
tar xvfz qpopper4.0.5.tar.gzRemove the source:
rm qpopper4.0.5.tar.gzCd into the qpopper4.0.5 directory.
Configure, make, and install the source:
./configure --enable-servermode --enable-standalone make make installCd to the /tmp directory.
Remove the source directory:
rm -rf qpopper4.0.5Create startup/shutdown script at /etc/init.d/popper and chmod 755.
#!/bin/sh
# Startup script for popper
#
# description: POP3 server
# Source function library.
. /etc/rc.d/init.d/functions
[ -f /usr/local/sbin/popper ] || exit 0
prog="popper"
start() {
echo -n $"Starting $prog: "
daemon /usr/local/sbin/popper -s
RETVAL=$?
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/popper
echo
return $RETVAL
}
stop() {
if test "x`pidof popper`" != x; then
echo -n $"Stopping $prog: "
killproc popper
echo
fi
RETVAL=$?
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/popper
return $RETVAL
}
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status popper
;;
restart)
stop
start
;;
condrestart)
if test "x`pidof popper`" != x; then
stop
start
fi
;;
*)
echo $"Usage: $0 {start|stop|restart|condrestart|status}"
exit 1
esac
exit 0
Create symbolic links:
cd /etc/rc3.d ln -s ../init.d/popper S85popper cd /etc/rc0.d ln -s ../init.d/popper K15popper cd /etc/rc6.d ln -s ../init.d/popper K15popperCreate user accounts as desired:
/usr/sbin/useradd -M -d / -s /sbin/nologin username passwd usernameStart popper:
/etc/init.d/popper start
Create a user for Cyrus:
/usr/sbin/useradd -g mail -M -r cyrusCreate the /usr/src/cyrus directory and cd to it.
Install the Cyrus SASL library.
The latest source is available from ftp://ftp.andrew.cmu.edu/pub/cyrus-mail/. This example uses version 2.1.13.
Download the source at ftp://ftp.andrew.cmu.edu/pub/cyrus-mail/cyrus-sasl-2.1.13.tar.gz.
Unpack the source:
tar xvfz cyrus-sasl-2.1.13.tar.gzRemove the source:
rm cyrus-sasl-2.1.13.tar.gzCd into the cyrus-sasl-2.1.13 directory.
Configure, make, and install the source:
./configure make make installCreate a symbolic link:
ln -s /usr/local/lib/sasl2 /usr/lib/sasl2Cd back to the /usr/src/cyrus directory.
Next, install the Cyrus IMAP server.
The latest source is available from ftp://ftp.andrew.cmu.edu/pub/cyrus-mail/. This example uses version 2.1.13.
Download the source at ftp://ftp.andrew.cmu.edu/pub/cyrus-mail/cyrus-imapd-2.1.13.tar.gz.
Unpack the source:
tar xvfz cyrus-imapd-2.1.13.tar.gzRemove the source:
rm cyrus-imapd-2.1.13.tar.gzCd into the cyrus-imapd-2.1.13 directory.
Configure, make, and install the source:
./configure --with-auth=unix make make installCd into the perl/imap directory.
Install the source:
perl Makefile.PL make make installNOTE: Don't make test. The tests require a mailbox which we haven't yet created.
If you will be using Sieve, install the perl managesieve module. Cd into the perl/sieve/managesieve directory.
Install the source:
perl Makefile.PL make make install
Enable logging by adding the following lines to /etc/syslog.conf:
local6.debug /var/log/imapd.log auth.debug /var/log/auth.logCreate the log files:
touch /var/log/imapd.log /var/log/auth.logRestart syslogd:
/etc/init.d/syslog restartCreate the SASL state directory, owned by root:
mkdir -p /var/state/saslauthdCreate the file "/etc/imapd.conf". Sample file:
configdirectory: /var/imap partition-default: /var/spool/imap admins: cyrus sasl_pwcheck_method: saslauthdCreate the configuration directory:
cd /var mkdir imap chown cyrus imap chgrp mail imap chmod 750 imapCreate the default partition directory:
cd /var/spool mkdir imap chown cyrus imap chgrp mail imap chmod 750 imapChange to the Cyrus user and use the tool "tools/mkimap" to create the rest of the directories (subdirectories of the directories you just created):
su cyrus /usr/src/cyrus/cyrus-imapd-2.1.13/tools/mkimap exitAdd the following lines to the "/etc/services" file if they aren't already there:
pop3 110/tcp imap 143/tcp imsp 406/tcp acap 674/tcp imaps 993/tcp pop3s 995/tcp kpop 1109/tcp sieve 2000/tcp lmtp 2003/tcp fud 4201/udpRemove "/etc/[x]inetd.conf" entries. Any imap, imaps, pop3, pop3s, kpop, lmtp and sieve lines need to be removed from /etc/[x]inetd.conf and [x]inetd needs to be restarted.
Choose a configuration from the master/conf directory:
small.confbare-bones server supporting IMAP and POP
normal.confserver supporting IMAP, POP, the SSL wrapped versions, and the Sieve script management protocol
prefork.confThe same configuration as above, but with some preforked processes for faster processing. To use normal.conf, do:
cp /usr/src/cyrus/cyrus-imapd-2.1.13/master/conf/normal.conf /etc/cyrus.confOptionally, you can edit /etc/cyrus.conf to disable or enabling certain services, or to tune the number of preforked copies. Be sure not to remove the entries that are labeled required.
Create startup/shutdown script for SASL at /etc/init.d/saslauthd and chmod 755.
#!/bin/sh
# Startup script for saslauthd
# Source function library.
. /etc/rc.d/init.d/functions
[ -f /usr/local/sbin/saslauthd ] || exit 0
prog="saslauthd"
start() {
echo -n $"Starting $prog: "
/usr/local/sbin/saslauthd -a shadow
RETVAL=$?
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/saslauthd
echo
return $RETVAL
}
stop() {
if test "x`pidof saslauthd`" != x; then
echo -n $"Stopping $prog: "
killproc saslauthd
echo
fi
RETVAL=$?
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/saslauthd
return $RETVAL
}
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status saslauthd
;;
restart)
stop
start
;;
condrestart)
if test "x`pidof saslauthd`" != x; then
stop
start
fi
;;
*)
echo $"Usage: $0 {start|stop|restart|condrestart|status}"
exit 1
esac
exit 0
Create symbolic links:
cd /etc/rc3.d ln -s ../init.d/saslauthd S85saslauthd cd /etc/rc0.d ln -s ../init.d/saslauthd K15saslauthd cd /etc/rc6.d ln -s ../init.d/saslauthd K15saslauthd
Create startup/shutdown script for imap at /etc/init.d/imapd and chmod 755.
#!/bin/sh
# Startup script for imapd
# Source function library.
. /etc/rc.d/init.d/functions
[ -f /usr/cyrus/bin/master ] || exit 0
prog="imapd"
start() {
echo -n $"Starting $prog: "
/usr/cyrus/bin/master -d
RETVAL=$?
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/imapd
echo
return $RETVAL
}
stop() {
if test "x`pidof master`" != x; then
echo -n $"Stopping $prog: "
killproc master
echo
fi
RETVAL=$?
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/imapd
return $RETVAL
}
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status master
;;
restart)
stop
start
;;
condrestart)
if test "x`pidof master`" != x; then
stop
start
fi
;;
*)
echo $"Usage: $0 {start|stop|restart|condrestart|status}"
exit 1
esac
exit 0
Create symbolic links:
cd /etc/rc3.d ln -s ../init.d/imapd S86imapd cd /etc/rc0.d ln -s ../init.d/imapd K14imapd cd /etc/rc6.d ln -s ../init.d/imapd K14imapdStart saslauthd and imap:
/etc/init.d/saslauth start /etc/init.d/imapd startAdd an SASL password for cyrus to /etc/sasldb2:
/usr/local/sbin/saslpasswd2 cyrus chown cyrus:mail /etc/sasldb2Create user accounts as desired:
/usr/sbin/useradd -M -d / -s /sbin/nologin username passwd usernameCreate mailboxes to match the user accounts (username1, username2, username3 in this example):
cyradm --user cyrus localhost localhost> cm user.username1 localhost> cm user.username2 localhost> cm user.username3 localhost> exit
LANG="en_US"Several Perl modules are required for proper operation. For those who are familiar with the installation process, the required modules, along with the locations of the latest versions as of this writing, are:
IO-stringy
http://www.cpan.org/authors/id/ERYQ/IO-stringy-2.108.tar.gz
Search CPAN for latest distribution
MIME-Base64
http://www.cpan.org/authors/id/GAAS/MIME-Base64-2.20.tar.gz
Search CPAN for latest distribution
MailTools
http://www.cpan.org/authors/id/M/MA/MARKOV/MailTools-1.58.tar.gz
Search CPAN for latest distribution
MIME-tools
http://www.mimedefang.org/static/MIME-tools-5.411a-RP-Patched-02.tar.gz
NOTE: This is the Roaring Penguin patched version of MIME-Tools. DO NOT use the original from CPAN! It has several security issues and MIMEDefang will refuse to run with it.
Digest-SHA1
http://www.cpan.org/authors/id/GAAS/Digest-SHA1-2.02.tar.gz
Search CPAN for latest distribution
libnet
http://www.cpan.org/authors/id/GBARR/libnet-1.16.tar.gz
Search CPAN for latest distribution
Mail-Audit
http://www.cpan.org/authors/id/S/SI/SIMON/Mail-Audit-2.1.tar.gz
Search CPAN for latest distribution
Time-HiRes
http://www.cpan.org/authors/id/J/JH/JHI/Time-HiRes-1.49.tar.gz
Search CPAN for latest distribution
HTML-Tagset
http://www.cpan.org/authors/id/S/SB/SBURKE/HTML-Tagset-3.03.tar.gz
Search CPAN for latest distribution
HTML-Parser
http://www.cpan.org/authors/id/G/GA/GAAS/HTML-Parser-3.28.tar.gz
Search CPAN for latest distribution
Download the IO-stringy source at http://www.cpan.org/authors/id/ERYQ/IO-stringy-2.108.tar.gz.
Unpack the source:
tar xvfz IO-stringy-2.108.tar.gzRemove the source:
rm IO-stringy-2.108.tar.gzCd into the IO-stringy-2.108 directory.
Install the source:
perl Makefile.PL make make test make installCd to the /tmp directory.
Remove the source directory:
rm -rf IO-stringy-2.108
Unpack the source:
tar xvfz MIME-Base64-2.20.tar.gzRemove the source:
rm MIME-Base64-2.20.tar.gzCd into the MIME-Base64-2.20 directory.
Install the source:
perl Makefile.PL make make test make installCd to the /tmp directory.
Remove the source directory:
rm -rf MIME-Base64-2.20
Unpack the source:
tar xvfz MailTools-1.58.tar.gzRemove the source:
rm MailTools-1.58.tar.gzCd into the MailTools-1.58 directory.
Install the source:
perl Makefile.PL make make test make installCd to the /tmp directory.
Remove the source directory:
rm -rf MailTools-1.58
NOTE: This is the Roaring Penguin patched version of MIME-Tools. DO NOT use the original from CPAN! It has several security issues and MIMEDefang will refuse to run with it.
Unpack the source:
tar xvfz MIME-tools-5.411a-RP-Patched-02.tar.gzRemove the source:
rm MIME-tools-5.411a-RP-Patched-02.tar.gzCd into the MIME-tools-5.411a-RP-Patched-02 directory.
Install the source:
perl Makefile.PL make make test make installCd to the /tmp directory.
Remove the source directory:
rm -rf MIME-tools-5.411a-RP-Patched-02
Unpack the source:
tar xvfz Digest-SHA1-2.02.tar.gzRemove the source:
rm Digest-SHA1-2.02.tar.gzCd into the Digest-SHA1-2.02 directory.
Install the source:
perl Makefile.PL make make test make installCd to the /tmp directory.
Remove the source directory:
rm -rf Digest-SHA1-2.02
Unpack the source:
tar xvfz libnet-1.16.tar.gzRemove the source:
rm libnet-1.16.tar.gzCd into the libnet-1.16 directory.
Install the source:
perl Makefile.PL make make test make installCd to the /tmp directory.
Remove the source directory:
rm -rf libnet-1.16
Unpack the source:
tar xvfz Mail-Audit-2.1.tar.gzRemove the source:
rm Mail-Audit-2.1.tar.gzCd into the Mail-Audit-2.1 directory.
Install the source:
perl Makefile.PL make make test make installCd to the /tmp directory.
Remove the source directory:
rm -rf Mail-Audit-2.1
Unpack the source:
tar xvfz Time-HiRes-1.49.tar.gzRemove the source:
rm Time-HiRes-1.49.tar.gzCd into the Time-HiRes-1.49 directory.
Install the source:
perl Makefile.PL make make test make installCd to the /tmp directory.
Remove the source directory:
rm -rf Time-HiRes-1.49
Unpack the source:
tar xvfz HTML-Tagset-3.03.tar.gzRemove the source:
rm HTML-Tagset-3.03.tar.gzCd into the HTML-Tagset-3.03 directory.
Install the source:
perl Makefile.PL make make test make installCd to the /tmp directory.
Remove the source directory:
rm -rf HTML-Tagset-3.03
Unpack the source:
tar xvfz HTML-Parser-3.28.tar.gzRemove the source:
rm HTML-Parser-3.28.tar.gzCd into the HTML-Parser-3.28 directory.
Install the source:
perl Makefile.PL make make test make installCd to the /tmp directory.
Remove the source directory:
rm -rf HTML-Parser-3.28
Cd to the /tmp directory.
The latest source is available from http://www.spamassassin.org/released/. This example uses version 2.55.
Download the SpamAssassin source at http://www.spamassassin.org/released/Mail-SpamAssassin-2.55.tar.gz.
Unpack the source:
tar xvfz Mail-SpamAssassin-2.55.tar.gzRemove the source:
rm Mail-SpamAssassin-2.55.tar.gzCd into the Mail-SpamAssassin-2.55 directory.
Install the source:
perl Makefile.PL make make installNOTE: Don't "make test" - it does a lot of unnecessary tests we don't need, and can potentially start so many processes that a running sendmail process will reject new connections because of high server utilization.
Instead, use this test procedure:
spamassassin -t < sample-nonspam.txt > nonspam.out spamassassin -t < sample-spam.txt > spam.outVerify (using a text viewer, ie. "less") that nonspam.out has not been tagged as spam, and that spam.out has.
Cd to the /tmp directory.
Remove the source directory:
rm -rf Mail-SpamAssassin-2.55If desired, delete the default white lists, which contain preapproved sender addresses:
rm /usr/share/spamassassin/60_whitelist.cf
cd /tmp mkdir vlnx414l cd vlnx414l
Copy the source package into the temporary directory.
Unpack the source:
tar xvf vlnx414l.tarRemove the source:
rm vlnx414l.tarInstall the source:
./install-uvscanThe install script asks a series of questions. Accept the defaults unless you know you need a non-standard installation.
Cd to the /tmp directory.
Remove the source directory:
rm -rf vlnx414lCheck the installation:
uvscan --versionIf you get this error, you need the compatibility libraries. Download and install the compat-libstdc++ rpm for Red Hat Linux.
uvscan: error while loading shared libraries: libstdc++.so.2.8: cannot load shared object file: No such file or directoryTo update virus definitions automatically, install the following update script. Be sure to change the email addresses and SMTP server name. Install as /usr/local/uvscan/update and add to root's crontab:
0 */6 * * * /usr/local/uvscan/updateSample update script:
#!/usr/bin/perl -w
# uvscan virus DAT file updater written by
# Michael Matsumura (michael+uvscan@limit.org)
# Version 1.0
#
# Slightly modified by Mickey Hill
#
# Net::FTP and MIME::Lite are required for operation
# and 'tar' should be in the PATH
#
use strict;
use Net::FTP;
use MIME::Lite;
#
# Set to the directory uvscan is located/installed in
#
my $uvscan_directory = "/usr/local/uvscan";
#
# Set to the temporary directory to download the dat archive
#
my $tempdir = "/tmp/dat-updates";
#
# Set to email address for anonymous FTP login
#
my $emailaddress = 'postmaster@mycompany.com';
my $mailto = 'me@mycompany.com';
my $smtpserver = 'mail.mycompany.com';
# Define global variables
#
my ($ftp, @dirlist, $arraywalk, $localver, $serverver, $localfile,
@files, $file, $report, $notify);
$report = "Mail server virus definition update\n" . localtime() . "\n\n";
# Get the local uvscan datfile version
#
$localver = &checkuvscanver;
print "Currently installed version: ".$localver."\n";
$report .= "Currently installed version: ".$localver."\n";
# Create FTP connection
#
$ftp = Net::FTP->new("ftp.nai.com", Debug => 0);
# Login
#
$ftp->login("anonymous",$emailaddress);
$ftp->cwd("/pub/antivirus/datfiles/4.x");
$ftp->binary();
@dirlist = $ftp->ls();
foreach $arraywalk (@dirlist) {
if ($arraywalk =~ /dat-([0-9]+)\.tar/i) {
$serverver = $1;
print "Version on ftp.nai.com: ".$serverver."\n";
$report .= "Version on ftp.nai.com: ".$serverver."\n";
if ($serverver > $localver) {
$notify++;
print "Updating virus data files...\n";
$report .= "Updating virus data files...\n";
# Create and then change the working dir to $tempdir
#
if (!(-d $tempdir)) {
mkdir($tempdir, 700) or die("ERROR: Couldn't make temporary directory: $tempdir");
}
chdir $tempdir or die("ERROR: Couldn't change directory to tempdir:
$tempdir");
# Download the dat file!
#
$localfile = $ftp->get($arraywalk);
print "Download complete...updating now\n";
$report .= "Download complete...updating now\n";
# Untar the files, store the names of them into an array
#
@files = `tar -xvf $arraywalk`;
foreach $file (@files) {
# A line break is at the end of each $file...chomp that off
#
chomp($file);
# Move each file to the uvscan_directory; and make sure they
# are lowercase
#
my $movestring = "mv $file ".$uvscan_directory."/".lc($file);
print " ".$movestring."\n";
$report .= " ".$movestring."\n";
system($movestring);
}
# Make sure that the installation worked, by checking if
# the virus scanner reports the same data file version as
# the one we downloaded.
#
if (&checkuvscanver eq $serverver) {
print "Installation successful\n";
$report .= "Installation successful\n";
} else {
print "Error in installation, please install manually\n";
$report .= "Error in installation, please install manually\n";
}
#
# Cleanup...
#
print "Cleaning up\n";
$report .= "Cleaning up\n";
# Remove downloaded dat archive
#
unlink($arraywalk) or die("ERROR: Couldn't delete dat file: $arraywalk");
# Change to filesys root and remove temporary directory
#
chdir("/");
rmdir($tempdir) or die("ERROR: Couldn't remove tempdir: $tempdir");
} else { # if ($serverver > $localver) {
print "DAT files are the same... no need to update\n";
}
# Don't want to continue if there is more than one 'dat-[0-9]+.tar' files
#
last;
}
}
$ftp->quit;
if ($notify) {
my $msg = MIME::Lite->new(
From =>$emailaddress,
To =>$mailto,
Subject =>'Mail server virus definition update',
Type =>'TEXT',
Data =>$report
);
MIME::Lite->send('smtp', $smtpserver, Timeout=>60);
$msg->send;
}
exit 0;
# uvscan --version reports...
# "Virus data file v4119 created Feb 03 2001"
# &checkuvscanver returns the version of the data files
#
sub checkuvscanver {
if (`$uvscan_directory/uvscan --version` =~
/Virus data file v([0-9]+) created/) {
return $1;
}
}
Create a new user and group named defang for MIMEDefang. Create the MIMEDefang spool directory owned by defang:
mkdir /var/spool/MIMEDefang chmod 700 /var/spool/MIMEDefang chown defang /var/spool/MIMEDefang chgrp defang /var/spool/MIMEDefangCd to the /tmp directory.
The latest source is available from http://www.mimedefang.org/. This example uses version 2.35.
Download the MIMEDefang source at http://www.mimedefang.org/static/mimedefang-2.35.tar.gz.
Unpack the source:
tar xvfz mimedefang-2.35.tar.gzRemove the source:
rm mimedefang-2.35.tar.gzCd into the mimedefang-2.35 directory.
Install the source:
./configure make make installCopy the init script from the examples directory to your init directory.
cp examples/init-script /etc/init.d/mimedefangCd to the /tmp directory.
Remove the source directory:
rm -rf mimedefang-2.35
Incoming e-mail is handled as follows:
1) For each incoming message, mimedefang creates a temporary directory, makes it the current directory, and splits the message into parts and saves the parts in various files in the directory.
The directory contains the following files:
INPUTMSG A file containing the complete input e-mail message, including
headers.
HEADERS A file containing just the headers, one per line. Headers which
are continued over several lines in the original message are
collapsed into a single line in this file.
COMMANDS A file containing a list of commands. Each command is a single
letter and may be followed by arguments. Each command is on its
own line.
Additional files containing the message body (text and/or HTML) and any
attachments are also created.
When the end of the message is received, mimedefang executes the following command:
/usr/bin/perl /usr/local/bin/mimedefang.pl dir(If you are using mimedefang-multiplexor, the multiplexor manages a pool of persistent Perl processes, and mimedefang itself does not start a Perl interpreter.)
The single argument dir is the temporary directory in which the message information has been saved.
mimedefang.pl loads mimedefang-filter and begins operations on the files in this directory.
2) If the file /etc/mail/mimedefang-filter.pl defines a Perl function called filter_begin, it is called with no arguments. Any return value is ignored.
3) For each leaf part of the mail message, filter is called with four arguments: entity, a MIME::Entity object; fname, the suggested filename taken from the MIME Content-Disposition header; ext, the file extension, and type, the MIME Content-Type value. For each non-leaf part of the mail message, filter_multipart is called with the same four arguments as filter. A non-leaf part of a message is a part which contains nested parts. Such a part has no useful body, but you should still perform filename checks to check for viruses which use malformed MIME to masquerade as non-leaf parts (like message/rfc822). In general, any action you perform in filter_multipart applies to the part itself and any contained parts.
Note that both filter and filter_multipart are optional. If you do not define them, a default function that simply accepts each part is used.
4) After all parts have been processed, the function filter_end is called if it has been defined. It is passed a single argument consisting of the (possibly modified) MIME::Entity object representing the message about to be delivered.
When filter_begin, filter, filter_multipart, or filter_end decide how to dispose of a message or a part, each can call one or more action_ subroutines. Each action_ subroutine is appropriate for particular filter sections (i.e. an action_ subroutine that only works on individual parts works only in filter, not in filter_begin or filter_end). Some of the action subroutines are:
action_accept() - Accept the part.
action_rebuild() - Rebuild the mail body, even if mimedefang thinks no changes were made. Normally, mimedefang does not alter a message if no changes were made. action_rebuild may be used if you make changes to entities directly (by manipulating the MIME::Head, for example.) Unless you call action_rebuild, mimedefang will be unaware of the changes. Note that all the built-in action... routines which change a message implicitly call action_rebuild.
action_add_header($hdr, $val) - Add a header from the message. This can be used in filter_begin or filter_end. The $hdr component is the header name without the colon, and the $val is the header value. For example, to add the header:
X-MyHeader: A nice piece of text
use:
action_add_header("X-MyHeader", "A nice piece of text");
action_change_header($hdr, $val, $index) - Changes an existing header in the message. This can be used in filter_begin or filter_end. The $hdr parameter is the header name without the colon, and $val is the header value. If the header does not exist, then a header with the given name and value is added. The $index parameter is optional; it defaults to 1. If you supply it, then the $index'th occurrence of the header is changed, if there is more than one header with the same name. (This is common with the Received: header, for example.)
action_delete_header($hdr, $index) - Deletes an existing header in the message. This can be used in filter_begin or filter_end. The $hdr parameter is the header name without the colon.
The $index parameter is optional; it defaults to 1. If you supply it, then the $index'th occurrence of the header is deleted, if there is more than one header with the same name.
action_delete_all_headers($hdr) - Deletes all headers with the specified name. This can be used in filter_begin or filter_end. The $hdr parameter is the header name without the colon.
action_drop() - Drop the part. If called from filter_multipart, drops all contained parts also.
action_drop_with_warning($msg) - Drop the part, but add the warning $msg to the e-mail message. If called from filter_multipart, drops all contained parts also.
action_accept_with_warning($msg) - Accept the part, but add the warning $msg to the e-mail message.
action_replace_with_warning($msg) - Drop the part and replace it with a text part $msg. If called from filter_multipart, drops all contained parts also.
action_replace_with_url($entity, $doc_root, $base_url, $msg, [$cd_data]) - Drop the part, but save it in a unique location under $doc_root. The part is replaced with the text message $msg. The string "_URL_" in $msg is replaced with $base_url/something, which can be used to retrieve the message.
You should not use this function in filter_multipart.
This action is intended for stripping large parts out of the message and replacing them to a link on a Web server. Here's how you would use it in filter():
$size = (stat($entity->bodyhandle->path))[7];
if ($size > 1000000) {
action_replace_with_url($entity,
"/home/httpd/html/mail_parts",
"http://mailserver.company.com/mail_parts",
"The attachment was larger than 1,000,000 bytes.\n" .
"It was removed, but may be accessed at this URL:\n\n" .
"\t_URL_\n");
}
This example moves attachments greater than 1,000,000 bytes into /home/httpd/html/mail_parts and replaces them with a link. The directory should be accessible via a Web server at http://mailserver.company.com/mail_parts.
The generated name is created by performing a SHA1 hash of the part and adding the extension to the ASCII-HEX representation of the hash. If many different e-mails are sent containing an identical large part, only one copy of the part is stored, regardless of the number of senders or recipients.
For privacy reasons, you must turn off Web server indexing in the directory in which you place mail parts, or anyone will be able to read them. If indexing is disabled, an attacker would have to guess the SHA1 hash of a part in order to read it.
Optionally, a fifth argument can supply data to be saved into a hidden dot filename based on the generated name. This data can then be read in on the fly by a CGI script or mod_perl module before serving the file to a web client, and used to add information to the response, such as Content-Disposition data.
action_defang($entity, $name, $fname, $type) - Accept the part, but change its name to $name, its suggested filename to $fname and its MIME type to $type. If $name or $fname are "", then mimedefang.pl generates generic names. Do not use this action in filter_multipart.
If you use action_defang, you must define a subroutine called defang_warning in your filter. This routine takes two arguments: $oldfname (the original name of an attachment) and $fname (the defanged version.) It should return a message telling the user what happened. For example:
sub defang_warning {
my($oldfname, $fname) = @_;
return "The attachment '$oldfname' was renamed to '$fname'\n";
}
action_external_filter($entity, $cmd) - Run an external UNIX command $cmd. This command must read the part from the file ./FILTERINPUT and leave the result in ./FILTEROUTPUT. If the command executes successfully, returns 1, otherwise 0. You can test the return value and call another action_ if the filter failed. Do not use this action in filter_multipart.
action_quarantine($entity, $msg) - Drop and quarantine the part, but add the warning $msg to the e-mail message.
action_quarantine_entire_message() - Quarantines the entire message in a quarantine directory on the mail server, but does not otherwise affect disposition of the message. If "$msg" is non-empty, it is included in any administrator notification.
action_bounce($reply, $code, $dsn) - Bounce the entire e-mail message with the one-line error message $reply. If the optional $code and $dsn arguments are supplied, they specify the numerical SMTP reply code and the extended status code (DSN code). If the codes you supply do not make sense for a bounce, they are replaced with "554" and "5.7.1" respectively.
action_bounce merely makes a note that the message is to be bounced; remaining parts are still processed. If action_bounce is called for more than one part, the mail is bounced with the message in the final call to action_bounce. You can profitably call action_quarantine followed by action_bounce if you want to keep a copy of the offending part. Note that the message is not bounced immediately; rather, remaining parts are processed and the message is bounced after all parts have been processed.
WARNING: action_bounce() may generate spurious bounce messages if the sender address is faked. This is a particular problem with viruses. However, we believe that on balance, it's better to bounce a virus than to silently discard it. It's almost never a good idea to hide a problem.
action_tempfail($msg, $code, $dsn) - Cause an SMTP "temporary failure" code to be returned, so the sending mail relay requeues the message and tries again later. The message $msg is included with the temporary failure code. If the optional $code and $dsn arguments are supplied, they specify the numerical SMTP reply code and the extended status code (DSN code). If the codes you supply do not make sense for a temporary failure, they are replaced with "450" and "4.7.1" respectively.
action_discard() - Silently discard the message, notifying nobody. You can profitably call action_quarantine followed by action_discard if you want to keep a copy of the offending part. Note that the message is not discarded immediately; rather, remaining parts are processed and the message is discarded after all parts have been processed.
action_notify_sender($message) - This action sends an e-mail back to the original sender with the indicated message. You may call another action after this one. If action_notify_sender is called more than once, the messages are accumulated into a single e-mail message -- at most one notification message is sent per incoming message. The message should be terminated with a newline.
action_notify_administrator($message) - This action e-mails the MIMEDefang administrator the supplied message. You may call another action after this one; action_notify_administrator does not affect mail processing. If action_notify_administrator is called more than once, the messages are accumulated into a single e- mail message -- at most one notification message is sent per incoming message. The message should be terminated with a newline.
If using SpamAssassin, edit the following line in spamassassin/sa-mimedefang.cf for a more compact report:
use_terse_report 1MIMEDefang installs a working sample filter to /etc/mail/mimedefang-filter, or to /etc/mail/mimedefang-filter.example if mimedefang-filter already exists. To begin with the new sample filter, copy the sample to mimedefang-filter.
cp mimedefang-filter.example mimedefang-filter
$AdminAddress = 'defang-admin@localhost'; $AdminName = "MIMEDefang Administrator's Full Name"; $DaemonAddress = 'postmaster@yourcompany.com';The following optional lines will modify default values. Add any or all if desired.
$DaemonName = 'Your Company Mail Server'; #*********************************************************************** # Set general warning message here. This message is printed before any # specific warning messages in the warning message text attachment. #*********************************************************************** $GeneralWarning = "WARNING: This email violated Your Company's email security policy and\n" . "has been modified. For more information, contact $Administrator.\n\n";
# Reject message; do NOT allow message to reach recipient(s)
return action_bounce("Message rejected; illegal characters in message (per RFC 2821, 2822)");
sub filter ($$$$) {
my($entity, $fname, $ext, $type) = @_;
return if message_rejected(); # Avoid unnecessary work
# Block message/partial parts
if (lc($type) eq "message/partial") {
md_log('message/partial');
action_bounce("MIME type message/partial not accepted here");
return action_discard();
}
# Virus scan
if ($FoundVirus) {
my($code, $category, $action);
$VirusScannerMessages = "";
($code, $category, $action) = entity_contains_virus($entity);
# If you are more paranoid, change to: if ($action eq "quarantine") {
if ($category eq "virus") {
md_log('virus',$VirusName, $RelayAddr);
# Bounce the mail!
action_bounce("Virus $VirusName found in mail - rejected");
# But quarantine the part for examination later. Comment
# the next line out if you don't want to bother.
action_quarantine($entity, "A known virus was discovered and deleted. Virus-scanner messages follow:\n$VirusScannerMessages\n\n");
return;
}
if ($action eq "tempfail") {
action_tempfail("Problem running virus-scanner");
md_syslog('warning', "Problem running virus scanner: code=$code, category=$category, action=$action");
}
}
if (filter_bad_filename($entity)) {
md_log('bad_filename', $fname, $type);
return action_quarantine($entity, "An attachment named $fname was removed from this document as it\nconstituted a security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n");
}
# eml is bad if it's not multipart
if (re_match($entity, '\.eml')) {
md_log('non_multipart');
return action_quarantine($entity, "A non-multipart attachment named $fname was removed from this document as it\nconstituted a security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n");
}
# Clean up HTML if Anomy::HTMLCleaner is installed.
if ($Features{"HTMLCleaner"}) {
if ($type eq "text/html") {
return anomy_clean_html($entity);
}
}
return action_accept();
}
sub filter_multipart ($$$$) {
my($entity, $fname, $ext, $type) = @_;
if (filter_bad_filename($entity)) {
md_log('bad_filename', $fname, $type);
action_notify_administrator("A MULTIPART attachment of type $type, named $fname was dropped.\n");
return action_drop_with_warning("An attachment of type $type, named $fname was removed from this document as it\nconstituted a security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n");
}
# eml is bad if it's not message/rfc822
if (re_match($entity, '\.eml') and ($type ne "message/rfc822")) {
md_log('non_rfc822',$fname);
return action_drop_with_warning("A non-message/rfc822 attachment named $fname was removed from this document as it\nconstituted a security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n");
}
# Block message/partial parts
if (lc($type) eq "message/partial") {
md_log('message/partial');
action_bounce("MIME type message/partial not accepted here");
return;
}
return action_accept();
}
sub filter_end ($) {
my($entity) = @_;
# If you want quarantine reports, uncomment next line
# send_quarantine_notifications();
# IMPORTANT NOTE: YOU MUST CALL send_quarantine_notifications() AFTER
# ANY PARTS HAVE BEEN QUARANTINED. SO IF YOU MODIFY THIS FILTER TO
# QUARANTINE SPAM, REWORK THE LOGIC TO CALL send_quarantine_notifications()
# AT THE END!!!
# No sense doing any extra work
return if message_rejected();
# Spam checks if SpamAssassin is installed
if ($Features{"SpamAssassin"}) {
if (-s "./INPUTMSG" < 100*1024) {
# Only scan messages smaller than 100kB. Larger messages
# are extremely unlikely to be spam, and SpamAssassin is
# dreadfully slow on very large messages.
my($hits, $req, $names, $report) = spam_assassin_check();
if ($hits >= $req) {
md_log('spam', $hits, $RelayAddr);
my($score);
if ($hits < 40) {
$score = "*" x int($hits);
} else {
$score = "*" x 40;
}
# We add a header which looks like this:
# X-Spam-Score: 6.8 (******) NAME_OF_TEST,NAME_OF_TEST
# The number of asterisks in parens is the integer part
# of the spam score clamped to a maximum of 40.
# MUA filters can easily be written to trigger on a
# minimum number of asterisks...
action_change_header("X-Spam-Score", "$hits ($score) $names");
# If you find the SA report useful, add it, I guess...
action_add_part($entity, "text/plain", "-suggest",
"$report\n",
"SpamAssassinReport.txt", "inline");
} else {
# Delete any existing X-Spam-Score header?
action_delete_header("X-Spam-Score");
}
}
}
# I HATE HTML MAIL! If there's a multipart/alternative with both
# text/plain and text/html parts, nuke the text/html. Thanks for
# wasting our disk space and bandwidth...
# If you don't mind HTML mail, comment out the next line.
remove_redundant_html_parts($entity);
}
Another common use of filter_end is the insertion of boilerplate text into a
message. The append_text_boilerplate and append_html_boilerplate functions
append text to the first text/plain or text/html part found in the message.
These functions would be added inside the existing filter_end:
append_text_boilerplate($entity, "All information contained in " .
"this email is confidential and may be used by the intended " .
"recipient only.", 0);
append_html_boilerplate($entity, "All information contained in " .
"this email is confidential and may be used by the intended " .
"recipient only.", 0);
mimedefang.pl -testYou can also test other filters with the -f option:
mimedefang.pl -f test-filter -testThis is useful (and recommended) when writing a new filter for a production server, so that the working filter can be left in place.
Start sendmail:
/etc/init.d/sendmail start
# -*- Perl -*-
#***********************************************************************
#
# mimedefang-filter
#
# Suggested minimum-protection filter for Microsoft Windows clients, plus
# SpamAssassin checks if SpamAssassin is installed.
#
# Copyright (C) 2002 Roaring Penguin Software Inc.
#
# This program may be distributed under the terms of the GNU General
# Public License, Version 2, or (at your option) any later version.
#
# $Id: suggested-minimum-filter-for-windows-clients,v 1.63 2003/06/17 18:41:13 dfs Exp $
#***********************************************************************
#***********************************************************************
# Set administrator's e-mail address here. The administrator receives
# quarantine messages and is listed as the contact for site-wide
# MIMEDefang policy. A good example would be 'defang-admin@mydomain.com'
#***********************************************************************
$AdminAddress = 'postmaster@localhost';
$AdminName = "MIMEDefang Administrator's Full Name";
#***********************************************************************
# Set the e-mail address from which MIMEDefang quarantine warnings and
# user notifications appear to come. A good example would be
# 'mimedefang@mydomain.com'. Make sure to have an alias for this
# address if you want replies to it to work.
#***********************************************************************
$DaemonAddress = 'mimedefang@localhost';
#***********************************************************************
# If you set $AddWarningsInline to 1, then MIMEDefang tries *very* hard
# to add warnings directly in the message body (text or html) rather
# than adding a separate "WARNING.TXT" MIME part. If the message
# has no text or html part, then a separate MIME part is still used.
#***********************************************************************
$AddWarningsInline = 0;
#***********************************************************************
# To enable syslogging of virus and spam activity, add the following
# to the filter:
# md_graphdefang_log_enable();
# You may optionally provide a syslogging facility by passing an
# argument such as: md_graphdefang_log_enable('local4'); If you do this, be
# sure to setup the new syslog facility (probably in /etc/syslog.conf).
# An optional second argument causes a line of output to be produced
# for each recipient (if it is 1), or only a single summary line
# for all recipients (if it is 0.) The default is 1.
# Comment this line out to disable logging.
#***********************************************************************
md_graphdefang_log_enable('mail', 1);
#***********************************************************************
# Uncomment this to block messages with more than 50 parts. This will
# *NOT* work unless you're using Roaring Penguin's patched version
# of MIME tools, version MIME-tools-5.411a-RP-Patched-02 or later.
#
# WARNING: DO NOT SET THIS VARIABLE unless you're using at least
# MIME-tools-5.411a-RP-Patched-02; otherwise, your filter will fail.
#***********************************************************************
# $MaxMIMEParts = 50;
#***********************************************************************
# Set various stupid things your mail client does below.
#***********************************************************************
# Set the next one if your mail client cannot handle nested multipart
# messages. DO NOT set this lightly; it will cause action_add_part to
# work rather strangely. Leave it at zero, even for MS Outlook, unless
# you have serious problems.
$Stupidity{"flatten"} = 0;
# Set the next one if your mail client cannot handle multiple "inline"
# parts.
$Stupidity{"NoMultipleInlines"} = 0;
# The next 3 lines force SpamAssassin modules to be loaded and rules
# to be compiled immediately. This may improve performance on busy
# mail servers. Comment the lines out if you don't like them.
if ($Features{"SpamAssassin"}) {
spam_assassin_init()->compile_now(1) if defined(spam_assassin_init());
}
# This procedure returns true for entities with bad filenames.
sub filter_bad_filename ($) {
my($entity) = @_;
my($bad_exts, $re);
# Bad extensions
$bad_exts = '(ade|adp|app|asd|asf|asx|bas|bat|chm|cmd|com|cpl|crt|dll|exe|fxp|hlp|hta|hto|inf|ini|ins|isp|jse?|lib|lnk|mdb|mde|msc|msi|msp|mst|ocx|pcd|pif|prg|reg|scr|sct|sh|shb|shs|sys|url|vb|vbe|vbs|vcs|vxd|wmd|wms|wmz|wsc|wsf|wsh|\{)';
# Do not allow:
# - curlies
# - bad extensions (possibly with trailing dots) at end or
# followed by non-alphanum
$re = '\.' . $bad_exts . '\.*([^-A-Za-z0-9_.,]|$)';
return re_match($entity, $re);
}
# Scan for a virus using the first supported virus scanner we find.
sub message_contains_virus () {
return message_contains_virus_avp() if ($Features{'Virus:AVP'});
return message_contains_virus_fprot() if ($Features{'Virus:FPROT'});
return message_contains_virus_fsav() if ($Features{'Virus:FSAV'});
return message_contains_virus_hbedv() if ($Features{'Virus:HBEDV'});
return message_contains_virus_nai() if ($Features{'Virus:NAI'});
return message_contains_virus_nvcc() if ($Features{'Virus:NVCC'});
return message_contains_virus_rav() if ($Features{'Virus:RAV'});
return message_contains_virus_sophie() if ($Features{'Virus:SOPHIE'});
return message_contains_virus_trophie() if ($Features{'Virus:TROPHIE'});
return message_contains_virus_sophos() if ($Features{'Virus:SOPHOS'});
return message_contains_virus_trend() if ($Features{'Virus:TREND'});
return message_contains_virus_filescan() if ($Features{'Virus:FileScan'});
return message_contains_virus_clamd() if ($Features{'Virus:CLAMD'});
return message_contains_virus_clamav() if ($Features{'Virus:CLAMAV'});
return message_contains_virus_carrier_scan() if ($Features{'Virus:SymantecCSS'});
return (wantarray ? (0, 'ok', 'ok') : 0);
}
# Scan for a virus using the first supported virus scanner we find.
sub entity_contains_virus ($) {
my($e) = @_;
return entity_contains_virus_avp($e) if ($Features{'Virus:AVP'});
return entity_contains_virus_fprot($e) if ($Features{'Virus:FPROT'});
return entity_contains_virus_fsav($e) if ($Features{'Virus:FSAV'});
return entity_contains_virus_hbedv($e) if ($Features{'Virus:HBEDV'});
return entity_contains_virus_nai($e) if ($Features{'Virus:NAI'});
return entity_contains_virus_nvcc($e) if ($Features{'Virus:NVCC'});
return entity_contains_virus_rav($e) if ($Features{'Virus:RAV'});
return entity_contains_virus_sophie($e) if ($Features{'Virus:SOPHIE'});
return entity_contains_virus_trophie($e) if ($Features{'Virus:TROPHIE'});
return entity_contains_virus_sophos($e) if ($Features{'Virus:SOPHOS'});
return entity_contains_virus_trend($e) if ($Features{'Virus:TREND'});
return entity_contains_virus_filescan($e) if ($Features{'Virus:FileScan'});
return entity_contains_virus_clamd($e) if ($Features{'Virus:CLAMD'});
return entity_contains_virus_clamav($e) if ($Features{'Virus:CLAMAV'});
return entity_contains_virus_carrier_scan($e) if ($Features{'Virus:SymantecCSS'});
return (wantarray ? (0, 'ok', 'ok') : 0);
}
#***********************************************************************
# %PROCEDURE: filter_begin
# %ARGUMENTS:
# None
# %RETURNS:
# Nothing
# %DESCRIPTION:
# Called just before e-mail parts are processed
#***********************************************************************
sub filter_begin () {
# ALWAYS drop messages with suspicious chars in headers
if ($SuspiciousCharsInHeaders) {
md_graphdefang_log('suspicious_chars');
action_quarantine_entire_message("Message quarantined because of suspicious characters in headers");
# Do NOT allow message to reach recipient(s)
return action_discard();
}
# Scan for viruses if any virus-scanners are installed
my($code, $category, $action) = message_contains_virus();
# Lower level of paranoia - only looks for actual viruses
$FoundVirus = ($category eq "virus");
# Higher level of paranoia - takes care of "suspicious" objects
# $FoundVirus = ($action eq "quarantine");
if ($action eq "tempfail") {
action_tempfail("Problem running virus-scanner");
md_syslog('warning', "Problem running virus scanner: code=$code, category=$category, action=$action");
}
}
#***********************************************************************
# %PROCEDURE: filter
# %ARGUMENTS:
# entity -- a Mime::Entity object (see MIME-tools documentation for details)
# fname -- the suggested filename, taken from the MIME Content-Disposition:
# header. If no filename was suggested, then fname is ""
# ext -- the file extension (everything from the last period in the name
# to the end of the name, including the period.)
# type -- the MIME type, taken from the Content-Type: header.
#
# NOTE: There are two likely and one unlikely place for a filename to
# appear in a MIME message: In Content-Disposition: filename, in
# Content-Type: name, and in Content-Description. If you are paranoid,
# you will use the re_match and re_match_ext functions, which return true
# if ANY of these possibilities match. re_match checks the whole name;
# re_match_ext checks the extension. See the sample filter below for usage.
# %RETURNS:
# Nothing
# %DESCRIPTION:
# This function is called once for each part of a MIME message.
# There are many action_*() routines which can decide the fate
# of each part; see the mimedefang-filter man page.
#***********************************************************************
sub filter ($$$$) {
my($entity, $fname, $ext, $type) = @_;
return if message_rejected(); # Avoid unnecessary work
# Block message/partial parts
if (lc($type) eq "message/partial") {
md_graphdefang_log('message/partial');
action_bounce("MIME type message/partial not accepted here");
return action_discard();
}
# Virus scan
if ($FoundVirus) {
my($code, $category, $action);
$VirusScannerMessages = "";
($code, $category, $action) = entity_contains_virus($entity);
# If you are more paranoid, change to: if ($action eq "quarantine") {
if ($category eq "virus") {
md_graphdefang_log('virus',$VirusName, $RelayAddr);
# Bounce the mail!
action_bounce("Virus $VirusName found in mail - rejected");
# But quarantine the part for examination later. Comment
# the next line out if you don't want to bother.
action_quarantine($entity, "A known virus was discovered and deleted. Virus-scanner messages follow:\n$VirusScannerMessages\n\n");
return;
}
if ($action eq "tempfail") {
action_tempfail("Problem running virus-scanner");
md_syslog('warning', "Problem running virus scanner: code=$code, category=$category, action=$action");
}
}
if (filter_bad_filename($entity)) {
md_graphdefang_log('bad_filename', $fname, $type);
return action_quarantine($entity, "An attachment named $fname was removed from this document as it\nconstituted a
security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n");
}
# eml is bad if it's not multipart
if (re_match($entity, '\.eml')) {
md_graphdefang_log('non_multipart');
return action_quarantine($entity, "A non-multipart attachment named $fname was removed from this document as it\nconstituted a security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n");
}
# Clean up HTML if Anomy::HTMLCleaner is installed.
if ($Features{"HTMLCleaner"}) {
if ($type eq "text/html") {
return anomy_clean_html($entity);
}
}
return action_accept();
}
#***********************************************************************
# %PROCEDURE: filter_multipart
# %ARGUMENTS:
# entity -- a Mime::Entity object (see MIME-tools documentation for details)
# fname -- the suggested filename, taken from the MIME Content-Disposition:
# header. If no filename was suggested, then fname is ""
# ext -- the file extension (everything from the last period in the name
# to the end of the name, including the period.)
# type -- the MIME type, taken from the Content-Type: header.
# %RETURNS:
# Nothing
# %DESCRIPTION:
# This is called for multipart "container" parts such as message/rfc822.
# You cannot replace the body (because multipart parts have no body),
# but you should check for bad filenames.
#***********************************************************************
sub filter_multipart ($$$$) {
my($entity, $fname, $ext, $type) = @_;
if (filter_bad_filename($entity)) {
md_graphdefang_log('bad_filename', $fname, $type);
action_notify_administrator("A MULTIPART attachment of type $type, named $fname was dropped.\n");
return action_drop_with_warning("An attachment of type $type, named $fname was removed from this document as it\nconstituted a security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n");
}
# eml is bad if it's not message/rfc822
if (re_match($entity, '\.eml') and ($type ne "message/rfc822")) {
md_graphdefang_log('non_rfc822',$fname);
return action_drop_with_warning("A non-message/rfc822 attachment named $fname was removed from this document as it\nconstituted a security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n");
}
# Block message/partial parts
if (lc($type) eq "message/partial") {
md_graphdefang_log('message/partial');
action_bounce("MIME type message/partial not accepted here");
return;
}
return action_accept();
}
#***********************************************************************
# %PROCEDURE: defang_warning
# %ARGUMENTS:
# oldfname -- the old file name of an attachment
# fname -- the new "defanged" name
# %RETURNS:
# A warning message
# %DESCRIPTION:
# This function customizes the warning message when an attachment
# is defanged.
#***********************************************************************
sub defang_warning ($$) {
my($oldfname, $fname) = @_;
return
"An attachment named '$oldfname' was converted to '$fname'.\n" .
"To recover the file, right-click on the attachment and Save As\n" .
"'$oldfname'\n";
}
# If SpamAssassin found SPAM, append report. We do it as a separate
# attachment of type text/plain
sub filter_end ($) {
my($entity) = @_;
# If you want quarantine reports, uncomment next line
# send_quarantine_notifications();
# IMPORTANT NOTE: YOU MUST CALL send_quarantine_notifications() AFTER
# ANY PARTS HAVE BEEN QUARANTINED. SO IF YOU MODIFY THIS FILTER TO
# QUARANTINE SPAM, REWORK THE LOGIC TO CALL send_quarantine_notifications()
# AT THE END!!!
# No sense doing any extra work
return if message_rejected();
# Spam checks if SpamAssassin is installed
if ($Features{"SpamAssassin"}) {
if (-s "./INPUTMSG" < 100*1024) {
# Only scan messages smaller than 100kB. Larger messages
# are extremely unlikely to be spam, and SpamAssassin is
# dreadfully slow on very large messages.
my($hits, $req, $names, $report) = spam_assassin_check();
if ($hits >= $req) {
md_graphdefang_log('spam', $hits, $RelayAddr);
my($score);
if ($hits < 40) {
$score = "*" x int($hits);
} else {
$score = "*" x 40;
}
# We add a header which looks like this:
# X-Spam-Score: 6.8 (******) NAME_OF_TEST,NAME_OF_TEST
# The number of asterisks in parens is the integer part
# of the spam score clamped to a maximum of 40.
# MUA filters can easily be written to trigger on a
# minimum number of asterisks...
action_change_header("X-Spam-Score", "$hits ($score) $names");
# If you find the SA report useful, add it, I guess...
action_add_part($entity, "text/plain", "-suggest",
"$report\n",
"SpamAssassinReport.txt", "inline");
} else {
# Delete any existing X-Spam-Score header?
action_delete_header("X-Spam-Score");
}
}
}
# I HATE HTML MAIL! If there's a multipart/alternative with both
# text/plain and text/html parts, nuke the text/html. Thanks for
# wasting our disk space and bandwidth...
# If you don't mind HTML mail, comment out the next line.
remove_redundant_html_parts($entity);
}
# DO NOT delete the next line, or Perl will complain.
1;
/etc/init.d/mimedefang reread
According to Microsoft, "The following list contains attachments that are considered unsafe:"
File Extension Description --------- -------------------------------------------- ADE Microsoft Access project extension ADP Microsoft Access project ASX Windows Media Audio / Video BAS Microsoft Visual Basic class module BAT Batch file CHM Compiled HTML Help file CMD Microsoft Windows NT Command script COM Microsoft MS-DOS program CPL Control Panel extension CRT Security certificate EXE Program HLP Help file HTA HTML program INF Setup Information INS Internet Naming Service ISP Internet Communication settings JS JScript File JSE JScript Encoded Script file LNK Shortcut MDA Microsoft Access add-in program MDB Microsoft Access program MDE Microsoft Access MDE database MDT Microsoft Access workgroup information MDW Microsoft Access workgroup information MDZ Microsoft Access wizard program MSC Microsoft Common Console document MSI Microsoft Windows Installer package MSP Microsoft Windows Installer patch MST Microsoft Windows Installer transform, Microsoft Visual Test source file OPS Office XP settings PCD Photo CD Image, Microsoft Visual compiled script PIF Shortcut to MS-DOS program PRF Microsoft Outlook profile settings REG Registration entries SCF Windows Explorer command SCR Screen saver SCT Windows Script Component SHB Shell Scrap object SHS Shell Scrap object URL Internet shortcut VB VBScript file VBE VBScript Encoded script file VBS VBScript file WSC Windows Script Component WSF Windows Script file WSH Windows Scripting Host Settings file
Subject: VIRUS ALERT
V I R U S A L E R T
Our virus scanner found a VIRUS in your email to <abc@xyz.com>.
We advise you to check your computer for viruses. We recommend the
latest versions of the following anti-virus software:
Norton AntiVirus http://www.symantec.com/avcenter/
McAfee VirusScan http://www.mcafee.com/anti-virus/
BE SURE TO UPDATE YOUR ANTI-VIRUS SOFTWARE WITH THE LATEST VIRUS
DEFINITIONS.
Our virus scanner found the following virus:
...
-----Original Message Headers-----
...
Subject: Rejected email Dear Sirs: Our mail server rejected one or more email messages you attempted to send to a user at this location. Your email message contained illegal characters that can be used to hide computer viruses. This is usually caused by an improperly configured program on your computer system that sent the email message. We ask that you correct this situation before sending your message again. Further illegal messages will continue to be rejected. Specifically, your message contained one or more bare CR or LF characters. These characters are not allowed to appear separately, and may only appear together as CRLF. For more information, see: http://online.securityfocus.com/archive/1/255910 http://online.securityfocus.com/archive/1/256616 All Internet activity is governed by Requests for Comments (RFC's) published by The Internet Society. Rules for email are contained in RFC 2821 and RFC 2822, located at: ftp://ftp.isi.edu/in-notes/rfc2821.txt ftp://ftp.isi.edu/in-notes/rfc2822.txt Thank you.