diff options
author | Marc Haber <zugschlus@debian.org> | 2005-02-07 20:44:40 +0000 |
---|---|---|
committer | Marc Haber <zugschlus@debian.org> | 2005-02-07 20:44:40 +0000 |
commit | a93ce3f118579f2fc36feec5d74db0dd12f9ca3f (patch) | |
tree | a3f80b35812c95cbe443486ae39958cad0700f97 /debian/patches | |
parent | d9da6e37112b0bcbd94593b0e52ca28fc8f7234b (diff) | |
download | exim4-4.32-1.tar.gz |
move exim tags to exim subdir4.32-1
svn path=/tags/debian_version_4_32-1/; revision=858
Diffstat (limited to 'debian/patches')
-rw-r--r-- | debian/patches/00list | 10 | ||||
-rw-r--r-- | debian/patches/10_daemon_close_fds.dpatch | 47 | ||||
-rw-r--r-- | debian/patches/30_dontoverridecflags.dpatch | 34 | ||||
-rw-r--r-- | debian/patches/31_eximmanpage.dpatch | 57 | ||||
-rwxr-xr-x | debian/patches/32_exim4.dpatch | 126 | ||||
-rwxr-xr-x | debian/patches/33_eximon.binary.dpatch | 33 | ||||
-rwxr-xr-x | debian/patches/34_eximstatsmanpage.dpatch | 36 | ||||
-rwxr-xr-x | debian/patches/35_install.dpatch | 37 | ||||
-rwxr-xr-x | debian/patches/36_pcre.dpatch | 91 | ||||
-rw-r--r-- | debian/patches/40_boolean_redefine_protect.dpatch | 47 | ||||
-rw-r--r-- | debian/patches/50_localscan_dlopen.dpatch | 303 | ||||
-rw-r--r-- | debian/patches/exiscan.patch | 10034 |
12 files changed, 10855 insertions, 0 deletions
diff --git a/debian/patches/00list b/debian/patches/00list new file mode 100644 index 0000000..c687e3a --- /dev/null +++ b/debian/patches/00list @@ -0,0 +1,10 @@ +10_daemon_close_fds +30_dontoverridecflags +31_eximmanpage +32_exim4 +33_eximon.binary +34_eximstatsmanpage +35_install +36_pcre +40_boolean_redefine_protect +50_localscan_dlopen diff --git a/debian/patches/10_daemon_close_fds.dpatch b/debian/patches/10_daemon_close_fds.dpatch new file mode 100644 index 0000000..3da77b6 --- /dev/null +++ b/debian/patches/10_daemon_close_fds.dpatch @@ -0,0 +1,47 @@ +#! /bin/sh -e +## 10_daemon_close_fds.dpatch by Steve Haslam <araqnid@debian.org> +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Close FDs 0-254 when going into background + +if [ $# -ne 1 ]; then + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1 +fi +case "$1" in + -patch) patch -f --no-backup-if-mismatch --dry-run -p0 < $0 && patch -f --no-backup-if-mismatch -p0 < $0;; + -unpatch) patch -f --no-backup-if-mismatch -R -p0 < $0;; + *) + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1;; +esac +exit 0 + +@DPATCH@ +--- ../old/src/daemon.c 2002-12-18 10:28:01.000000000 +0000 ++++ src/daemon.c 2003-01-19 06:24:18.000000000 +0000 +@@ -42,6 +42,13 @@ + static smtp_slot *smtp_slots; + + ++static void close_fds(void) ++{ ++ int fd; ++ for (fd = 0; fd < 255; fd++) { ++ close(fd); ++ } ++} + + /************************************************* + * SIGALRM Handler * +@@ -662,9 +669,7 @@ + { + log_close_all(); /* Just in case anything was logged earlier */ + search_tidyup(); /* Just in case any were used in reading the config. */ +- close(0); /* Get rid of stdin/stdout/stderr */ +- close(1); +- close(2); ++ close_fds(); + log_stderr = NULL; /* So no attempt to copy paniclog output */ + + /* If the parent process of this one has pid == 1, we are re-initializing the diff --git a/debian/patches/30_dontoverridecflags.dpatch b/debian/patches/30_dontoverridecflags.dpatch new file mode 100644 index 0000000..bb05d57 --- /dev/null +++ b/debian/patches/30_dontoverridecflags.dpatch @@ -0,0 +1,34 @@ +#! /bin/sh -e +## 30_dontoverridecflags.dpatch by Andreas Metzler +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Don't override CFLAGS in OS/Makefile-Linux, allow to set them +## DP: e.g. in debian/rules + +if [ $# -ne 1 ]; then + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1 +fi +case "$1" in + -patch) patch -f --no-backup-if-mismatch -p1 < $0;; + -unpatch) patch -f --no-backup-if-mismatch -R -p1 < $0;; + *) + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1;; +esac + +exit 0 + +@DPATCH@ +diff -urNad /tmp/exim/exim/OS/Makefile-Linux exim/OS/Makefile-Linux +--- /tmp/exim/exim/OS/Makefile-Linux Wed Mar 31 13:57:42 2004 ++++ exim/OS/Makefile-Linux Wed Mar 31 14:12:37 2004 +@@ -7,7 +7,7 @@ + CHOWN_COMMAND=look_for_it + CHGRP_COMMAND=look_for_it + +-CFLAGS=-O -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE ++CFLAGS ?= -O -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE + + DBMLIB = -ldb + USE_DB = yes diff --git a/debian/patches/31_eximmanpage.dpatch b/debian/patches/31_eximmanpage.dpatch new file mode 100644 index 0000000..ccede32 --- /dev/null +++ b/debian/patches/31_eximmanpage.dpatch @@ -0,0 +1,57 @@ +#! /bin/sh -e +## 31_newpatch.dpatch by <ametzler@logic.univie.ac.at> +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Add info about rmail, mailq, etc. to exim(8). + +if [ $# -ne 1 ]; then + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1 +fi +case "$1" in + -patch) patch -f --no-backup-if-mismatch -p1 < $0;; + -unpatch) patch -f --no-backup-if-mismatch -R -p1 < $0;; + *) + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1;; +esac + +exit 0 + +@DPATCH@ +diff -urNad /tmp/exim4-cvs/doc/exim.8 exim4-cvs/doc/exim.8 +--- /tmp/exim4-cvs/doc/exim.8 Wed Dec 3 16:36:31 2003 ++++ exim4-cvs/doc/exim.8 Wed Dec 3 16:40:36 2003 +@@ -1,6 +1,6 @@ + .TH EXIM 8 + .SH NAME +-exim - a Mail Transfer Agent ++exim \- a Mail Transfer Agent + .SH SYNOPSIS + .B exim [options] arguments ... + .br +@@ -1305,3 +1305,24 @@ + It sets -x when calling the MTA from its "mail" command. Exim ignores this + option. + .TP ++ ++.SH SEE ALSO ++.BR exicyclog (8), ++.BR exigrep (8), ++.BR exim_checkaccess (8), ++.BR exim_convert4r4 (8), ++.BR exim_db (8), ++.BR exim_dbmbuild (8), ++.BR exim_lock (8), ++.BR eximon (8), ++.BR exinext (8), ++.BR exiqgrep (8), ++.BR exiqsumm (8), ++.BR exiwhat (8), ++.BR update\-exim4.conf (8), ++.BR update\-exim4defaults (8), ++/usr/share/doc/exim4\-base/. ++ ++.SH AUTHOR ++This manual page was provided with the upstream Exim source package. ++It was enhanced for the Debian GNU/Linux system. diff --git a/debian/patches/32_exim4.dpatch b/debian/patches/32_exim4.dpatch new file mode 100755 index 0000000..face006 --- /dev/null +++ b/debian/patches/32_exim4.dpatch @@ -0,0 +1,126 @@ +#! /bin/sh -e +## 32_exim4.dpatch by Andreas Metzler +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: The main binary is installed as /usr/sbin/exim4 + +if [ $# -ne 1 ]; then + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1 +fi +case "$1" in + -patch) patch -f --no-backup-if-mismatch -p1 < $0;; + -unpatch) patch -f --no-backup-if-mismatch -R -p1 < $0;; + *) + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1;; +esac + +exit 0 + +@DPATCH@ +diff -urNad /tmp/exim4-4.32/OS/Makefile-Linux exim4-4.32/OS/Makefile-Linux +--- /tmp/exim4-4.32/OS/Makefile-Linux Fri Apr 16 13:16:03 2004 ++++ exim4-4.32/OS/Makefile-Linux Fri Apr 16 13:16:04 2004 +@@ -23,7 +23,7 @@ + EXIWHAT_PS_ARG=ax + EXIWHAT_EGREP_ARG='/exim( |$$)' + EXIWHAT_MULTIKILL_CMD=killall +-EXIWHAT_MULTIKILL_ARG=exim ++EXIWHAT_MULTIKILL_ARG=exim4 + EXIWHAT_KILL_SIGNAL=-USR1 + + # End +diff -urNad /tmp/exim4-4.32/src/exicyclog.src exim4-4.32/src/exicyclog.src +--- /tmp/exim4-4.32/src/exicyclog.src Wed Mar 31 14:44:15 2004 ++++ exim4-4.32/src/exicyclog.src Fri Apr 16 13:16:04 2004 +@@ -115,7 +115,7 @@ + + st=' ' + exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` +-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi ++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi + + spool_directory=`$exim_path -C $config -bP spool_directory | sed 's/.*=[ ]*//'` + log_file_path=`$exim_path -C $config -bP log_file_path | sed 's/.*=[ ]*//'` +diff -urNad /tmp/exim4-4.32/src/exim_checkaccess.src exim4-4.32/src/exim_checkaccess.src +--- /tmp/exim4-4.32/src/exim_checkaccess.src Wed Mar 31 14:44:15 2004 ++++ exim4-4.32/src/exim_checkaccess.src Fri Apr 16 13:16:04 2004 +@@ -53,7 +53,7 @@ + + st=' ' + exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` +-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi ++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi + + + ######################################################################### +diff -urNad /tmp/exim4-4.32/src/eximon.src exim4-4.32/src/eximon.src +--- /tmp/exim4-4.32/src/eximon.src Wed Mar 31 14:44:15 2004 ++++ exim4-4.32/src/eximon.src Fri Apr 16 13:16:04 2004 +@@ -64,7 +64,7 @@ + + st=' ' + EXIM_PATH=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` +-if test "$EXIM_PATH" = ""; then EXIM_PATH=BIN_DIRECTORY/exim; fi ++if test "$EXIM_PATH" = ""; then EXIM_PATH=BIN_DIRECTORY/exim4; fi + + SPOOL_DIRECTORY=`$EXIM_PATH -C $config -bP spool_directory | sed 's/.*=[ ]*//'` + LOG_FILE_PATH=`$EXIM_PATH -C $config -bP log_file_path | sed 's/.*=[ ]*//'` +diff -urNad /tmp/exim4-4.32/src/exinext.src exim4-4.32/src/exinext.src +--- /tmp/exim4-4.32/src/exinext.src Fri Apr 16 12:38:56 2004 ++++ exim4-4.32/src/exinext.src Fri Apr 16 13:16:04 2004 +@@ -90,7 +90,7 @@ + exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` + fi + +-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi ++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi + spool_directory=`$exim_path $eximmacdef -C $config -bP spool_directory | sed 's/.*=[ ]*//'` + qualify_domain=`$exim_path $eximmacdef -C $config -bP qualify_domain | sed 's/.*=[ ]*//'` + +@@ -169,7 +169,7 @@ + + # Run exim_dumpdb to get out the retry data and pick off what we want + +- open(DATA, "${exim}_dumpdb $spool retry |") || ++ open(DATA, "/usr/sbin/exim_dumpdb $spool retry |") || + die "can't run exim_dumpdb"; + + while (<DATA>) +diff -urNad /tmp/exim4-4.32/src/exiqgrep.src exim4-4.32/src/exiqgrep.src +--- /tmp/exim4-4.32/src/exiqgrep.src Wed Mar 31 14:44:15 2004 ++++ exim4-4.32/src/exiqgrep.src Fri Apr 16 13:16:04 2004 +@@ -21,7 +21,7 @@ + use Getopt::Std; + + # Have this variable point to your exim binary. +-my $exim = 'BIN_DIRECTORY/exim'; ++my $exim = 'BIN_DIRECTORY/exim4'; + my $eargs = '-bpu'; + my %id; + my %opt; +diff -urNad /tmp/exim4-4.32/src/exiwhat.src exim4-4.32/src/exiwhat.src +--- /tmp/exim4-4.32/src/exiwhat.src Wed Mar 31 14:44:15 2004 ++++ exim4-4.32/src/exiwhat.src Fri Apr 16 13:16:04 2004 +@@ -82,7 +82,7 @@ + + st=' ' + exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"` +-if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim; fi ++if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi + spool_directory=`$exim_path -C $config -bP spool_directory | sed "s/.*=[ ]*//"` + process_log_path=`$exim_path -C $config -bP process_log_path | sed "s/.*=[ ]*//"` + +diff -urNad /tmp/exim4-4.32/src/globals.c exim4-4.32/src/globals.c +--- /tmp/exim4-4.32/src/globals.c Fri Apr 16 12:38:56 2004 ++++ exim4-4.32/src/globals.c Fri Apr 16 13:16:04 2004 +@@ -436,7 +436,7 @@ + + gid_t exim_gid = EXIM_GID; + BOOL exim_gid_set = TRUE; /* This gid is always set */ +-uschar *exim_path = US BIN_DIRECTORY "/exim" ++uschar *exim_path = US BIN_DIRECTORY "/exim4" + "\0<---------------Space to patch exim_path->"; + uid_t exim_uid = EXIM_UID; + BOOL exim_uid_set = TRUE; /* This uid is always set */ diff --git a/debian/patches/33_eximon.binary.dpatch b/debian/patches/33_eximon.binary.dpatch new file mode 100755 index 0000000..31568d0 --- /dev/null +++ b/debian/patches/33_eximon.binary.dpatch @@ -0,0 +1,33 @@ +#! /bin/sh -e +## 33_eximon.binary.dpatch by Andreas Piesk +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: eximon.bin is installed in /usr/lib/exim4/ and not in path. + +if [ $# -ne 1 ]; then + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1 +fi +case "$1" in + -patch) patch -f --no-backup-if-mismatch -p1 < $0;; + -unpatch) patch -f --no-backup-if-mismatch -R -p1 < $0;; + *) + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1;; +esac + +exit 0 + +@DPATCH@ +diff -urNad 30.tmp/OS/eximon.conf-Default 30/OS/eximon.conf-Default +--- 30.tmp/OS/eximon.conf-Default Mon Dec 30 10:14:03 2002 ++++ 30/OS/eximon.conf-Default Mon Dec 30 10:15:04 2002 +@@ -5,7 +5,7 @@ + # The name of the eximon binary, usually the same as the eximon script, + # with .bin stuck on the end. + +-EXIMON_BINARY=${EXIMON_BINARY-$0.bin} ++EXIMON_BINARY=/usr/lib/exim4/${EXIMON_BINARY-${0##*/}.bin} + + # The remaining parameters are values likely to be changed to suit the + # user's taste. They are documented in the EDITME file. diff --git a/debian/patches/34_eximstatsmanpage.dpatch b/debian/patches/34_eximstatsmanpage.dpatch new file mode 100755 index 0000000..bd7c44a --- /dev/null +++ b/debian/patches/34_eximstatsmanpage.dpatch @@ -0,0 +1,36 @@ +#! /bin/sh -e +## 34_eximstatsmanpage.dpatch by Andreas Metzler <ametzler@downhill.at.eu.org> +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Add note about installing perl-modules on Debian to +## DP: generated manpage + +if [ $# -ne 1 ]; then + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1 +fi +case "$1" in + -patch) patch -f --no-backup-if-mismatch -p1 < $0;; + -unpatch) patch -f --no-backup-if-mismatch -R -p1 < $0;; + *) + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1;; +esac + +exit 0 + +@DPATCH@ +diff -urNad 30.tmp/src/eximstats.src 30/src/eximstats.src +--- 30.tmp/src/eximstats.src Wed Dec 18 11:28:01 2002 ++++ 30/src/eximstats.src Sun Jan 12 14:50:23 2003 +@@ -277,6 +277,10 @@ + make test + make install + ++On B<Debian GNU/Linux> you can use ++C<apt-get install libgd-perl libgd-text-perl libgd-graph-perl> ++instead. ++ + =item B<-chartdir>I <dir> + + Create the charts in the directory <dir> diff --git a/debian/patches/35_install.dpatch b/debian/patches/35_install.dpatch new file mode 100755 index 0000000..161861b --- /dev/null +++ b/debian/patches/35_install.dpatch @@ -0,0 +1,37 @@ +#! /bin/sh -e +## 35_install.dpatch by Andreas Metzler +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Exim's installation scripts install the binary as exim-<version> +## DP: - disable this feature. + +if [ $# -ne 1 ]; then + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1 +fi +case "$1" in + -patch) patch -f --no-backup-if-mismatch -p1 < $0;; + -unpatch) patch -f --no-backup-if-mismatch -R -p1 < $0;; + *) + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1;; +esac + +exit 0 + +@DPATCH@ +diff -urNad 30.tmp/scripts/exim_install 30/scripts/exim_install +--- 30.tmp/scripts/exim_install Mon Dec 30 10:19:59 2002 ++++ 30/scripts/exim_install Mon Dec 30 10:20:21 2002 +@@ -172,8 +172,9 @@ + # The exim binary is handled specially + + if [ $name = exim${EXE} ]; then +- version=exim-`./exim -bV -C /dev/null | \ +- awk '/Exim version/ { OFS=""; print $3,"-",substr($4,2,length($4)-1) }'`${EXE} ++ version=exim ++# version=exim-`./exim -bV -C /dev/null | \ ++# awk '/Exim version/ { OFS=""; print $3,"-",substr($4,2,length($4)-1) }'`${EXE} + + # Do something only if newer than existing file, or no existing file + diff --git a/debian/patches/36_pcre.dpatch b/debian/patches/36_pcre.dpatch new file mode 100755 index 0000000..66bfbb6 --- /dev/null +++ b/debian/patches/36_pcre.dpatch @@ -0,0 +1,91 @@ +#! /bin/sh -e +## 36_pcre.dpatch by Andreas Metzler +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Link exim and eximon dynamically instead of statically +## DP: pcre. + +if [ $# -ne 1 ]; then + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1 +fi +case "$1" in + -patch) patch -f --no-backup-if-mismatch -p1 < $0;; + -unpatch) patch -f --no-backup-if-mismatch -R -p1 < $0;; + *) + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1;; +esac + +exit 0 + +@DPATCH@ +diff -urNad /tmp/exim/exim/OS/Makefile-Base exim/OS/Makefile-Base +--- /tmp/exim/exim/OS/Makefile-Base Wed Mar 31 13:57:42 2004 ++++ exim/OS/Makefile-Base Wed Mar 31 14:20:19 2004 +@@ -94,7 +94,7 @@ + # This is the real default target for all the various exim binaries and + # scripts, once the configuring stuff is done. + +-allexim: config.h buildpcre $(EXIM_MONITOR) exicyclog exinext exiwhat \ ++allexim: config.h $(EXIM_MONITOR) exicyclog exinext exiwhat \ + exigrep eximstats exipick exiqgrep exiqsumm \ + transport-filter.pl convert4r3 convert4r4 \ + exim_checkaccess \ +@@ -295,7 +295,7 @@ + store.o string.o tls.o tod.o transport.o tree.o verify.o \ + local_scan.o $(EXIM_PERL) + +-exim: pcre/libpcre.a lookups/lookups.a auths/auths.a \ ++exim: lookups/lookups.a auths/auths.a \ + routers/routers.a transports/transports.a \ + $(OBJ_EXIM) version.c + @echo " " +@@ -304,7 +304,7 @@ + $(CC) -c $(CFLAGS) $(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE) version.c + rm -f exim + $(PURIFY) $(LNCC) -o exim $(LFLAGS) $(OBJ_EXIM) version.o \ +- pcre/libpcre.a \ ++ -lpcre \ + routers/routers.a transports/transports.a lookups/lookups.a \ + auths/auths.a \ + $(LIBRESOLV) $(LIBS) $(LIBS_EXIM) $(IPV6_LIBS) $(EXTRALIBS) \ +@@ -406,12 +406,12 @@ + + OBJ_MONBIN = util-spool_in.o util-store.o util-string.o tod.o tree.o $(MONBIN) + +-eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) pcre/libpcre.a \ ++eximon.bin: $(EXIMON_EDITME) eximon $(OBJ_MONBIN) \ + ../exim_monitor/em_version.c + $(CC) -o em_version.o -c \ + $(CFLAGS) $(XINCLUDE) -I. ../exim_monitor/em_version.c + $(PURIFY) $(LNCC) -o eximon.bin em_version.o $(LFLAGS) $(XLFLAGS) \ +- $(OBJ_MONBIN) -lXaw -lXmu -lXt -lXext -lX11 pcre/libpcre.a \ ++ $(OBJ_MONBIN) -lXaw -lXmu -lXt -lXext -lX11 -lpcre \ + $(LIBS) $(LIBS_EXIMON) $(EXTRALIBS) $(EXTRALIBS_EXIMON) -lc + @if [ x"$(STRIP_COMMAND)" != x"" ]; then \ + echo $(STRIP_COMMAND) eximon.bin; \ +diff -urNad /tmp/exim/exim/exim_monitor/em_hdr.h exim/exim_monitor/em_hdr.h +--- /tmp/exim/exim/exim_monitor/em_hdr.h Wed Mar 31 13:57:55 2004 ++++ exim/exim_monitor/em_hdr.h Wed Mar 31 14:18:27 2004 +@@ -85,7 +85,7 @@ + + /* Regular expression include */ + +-#include "pcre/pcre.h" ++#include <pcre.h> + + /* Includes from the main source of Exim. We need to have MAXPACKET defined for + the benefit of structs.h. One of these days I should tidy up this interface so +diff -urNad /tmp/exim/exim/src/exim.h exim/src/exim.h +--- /tmp/exim/exim/src/exim.h Wed Mar 31 13:57:59 2004 ++++ exim/src/exim.h Wed Mar 31 14:18:27 2004 +@@ -368,7 +368,7 @@ + + /* The header from the PCRE regex package */ + +-#include "pcre/pcre.h" ++#include <pcre.h> + + /* Exim includes are in several files. Note that local_scan.h #includes + mytypes.h and store.h, so we don't need to mention them explicitly. */ diff --git a/debian/patches/40_boolean_redefine_protect.dpatch b/debian/patches/40_boolean_redefine_protect.dpatch new file mode 100644 index 0000000..8523b2c --- /dev/null +++ b/debian/patches/40_boolean_redefine_protect.dpatch @@ -0,0 +1,47 @@ +#! /bin/sh -e +## 40_boolean_redefine_protect.dpatch by Steve Haslam +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: boolean_redefine_protect +## DP: [src/mytypes.h] +## DP: Surround the definition of TRUE and FALSE macros with #ifndef +## DP: /#endif, in case some other header defines them (from mixing +## DP: Perl and Exim, istr) +## DP: http://www.arise.demon.co.uk/exim-patches/ + +if [ $# -ne 1 ]; then + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1 +fi +case "$1" in + -patch) patch -f --no-backup-if-mismatch -p1 < $0;; + -unpatch) patch -f --no-backup-if-mismatch -R -p1 < $0;; + *) + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1;; +esac + +exit 0 + +@DPATCH@ +diff -urNad 40.tmp/src/mytypes.h 40/src/mytypes.h +--- 40.tmp/src/mytypes.h Sun Dec 29 14:31:27 2002 ++++ 40/src/mytypes.h Sun Dec 29 14:40:41 2002 +@@ -14,9 +14,17 @@ + #define MYTYPES_H + + ++#ifndef FALSE + #define FALSE 0 ++#endif ++ ++#ifndef TRUE + #define TRUE 1 ++#endif ++ ++#ifndef TRUE_UNSET + #define TRUE_UNSET 2 ++#endif + + + /* If gcc is being used to compile Exim, we can use its facility for checking diff --git a/debian/patches/50_localscan_dlopen.dpatch b/debian/patches/50_localscan_dlopen.dpatch new file mode 100644 index 0000000..5e9f9d9 --- /dev/null +++ b/debian/patches/50_localscan_dlopen.dpatch @@ -0,0 +1,303 @@ +#! /bin/sh -e +## 50_localscan_dlopen.dpatch by Marc MERLIN +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Allow to use and switch between different local_scan functions without +## DP: recompiling exim. +## DP: http://marc.merlins.org/linux/exim/files/sa-exim-current/ +## DP: Original patch from David Woodhouse, modified first by Derrick 'dman' +## DP: Hudson and then by Marc MERLIN for SA-Exim and minor/major API version +## DP: tracking + + +if [ $# -ne 1 ]; then + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1 +fi +case "$1" in + -patch) patch -f --no-backup-if-mismatch -p1 < $0;; + -unpatch) patch -f --no-backup-if-mismatch -R -p1 < $0;; + *) + echo >&2 "`basename $0`: script expects -patch|-unpatch as argument" + exit 1;; +esac + +exit 0 + +@DPATCH@ +diff -urN exim-4.14-0/src/EDITME exim-4.14-1/src/EDITME +--- exim-4.14-0/src/EDITME Tue Mar 11 04:20:18 2003 ++++ exim-4.14-1/src/EDITME Sun Mar 23 15:34:15 2003 +@@ -388,6 +388,20 @@ + + + #------------------------------------------------------------------------------ ++# On systems which support dynamic loading of shared libraries, Exim can ++# load a local_scan function specified in its config file instead of having ++# to be recompiled with the desired local_scan function. For a full ++# description of the API to this function, see the Exim specification. ++ ++DLOPEN_LOCAL_SCAN=yes ++ ++# If you set DLOPEN_LOCAL_SCAN, then you need to include -rdynamic in the ++# linker flags. Without it, the loaded .so won't be able to access any ++# functions from exim. ++ ++LFLAGS=-rdynamic ++ ++#------------------------------------------------------------------------------ + # The default distribution of Exim contains only the plain text form of the + # documentation. Other forms are available separately. If you want to install + # the documentation in "info" format, first fetch the Texinfo documentation +diff -urNad 50_localscan_dlopen.tmp/src/config.h.defaults 50_localscan_dlopen/src/config.h.defaults +--- 50_localscan_dlopen.tmp/src/config.h.defaults Sun Dec 29 11:55:42 2002 ++++ 50_localscan_dlopen/src/config.h.defaults Sun Dec 29 11:56:44 2002 +@@ -17,6 +17,8 @@ + #define AUTH_PLAINTEXT + #define AUTH_SPA + ++#define DLOPEN_LOCAL_SCAN ++ + #define BIN_DIRECTORY + + #define CONFIGURE_FILE +diff -urN exim-4.14-0/src/globals.c exim-4.14-1/src/globals.c +--- exim-4.14-0/src/globals.c Tue Mar 11 04:20:20 2003 ++++ exim-4.14-1/src/globals.c Sun Mar 23 15:34:15 2003 +@@ -103,6 +103,9 @@ + uschar *tls_verify_hosts = NULL; + #endif + ++#ifdef DLOPEN_LOCAL_SCAN ++uschar *local_scan_path = NULL; ++#endif + + /* Input-reading functions for messages, so we can use special ones for + incoming TCP/IP. The defaults use stdin. We never need these for any +diff -urN exim-4.14-0/src/globals.h exim-4.14-1/src/globals.h +--- exim-4.14-0/src/globals.h Tue Mar 11 04:20:20 2003 ++++ exim-4.14-1/src/globals.h Sun Mar 23 15:34:15 2003 +@@ -67,6 +67,9 @@ + extern uschar *tls_verify_hosts; /* Mandatory client verification */ + #endif + ++#ifdef DLOPEN_LOCAL_SCAN ++extern uschar *local_scan_path; /* Path to local_scan() library */ ++#endif + + /* Input-reading functions for messages, so we can use special ones for + incoming TCP/IP. */ +diff -urN exim-4.14-0/src/local_scan.c exim-4.14-1/src/local_scan.c +--- exim-4.14-0/src/local_scan.c Tue Mar 11 04:20:20 2003 ++++ exim-4.14-1/src/local_scan.c Sun Mar 23 15:34:15 2003 +@@ -5,60 +5,131 @@ + /* Copyright (c) University of Cambridge 1995 - 2003 */ + /* See the file NOTICE for conditions of use and distribution. */ + ++#include "exim.h" + +-/****************************************************************************** +-This file contains a template local_scan() function that just returns ACCEPT. +-If you want to implement your own version, you should copy this file to, say +-Local/local_scan.c, and edit the copy. To use your version instead of the +-default, you must set +- +-LOCAL_SCAN_SOURCE=Local/local_scan.c +- +-in your Local/Makefile. This makes it easy to copy your version for use with +-subsequent Exim releases. +- +-For a full description of the API to this function, see the Exim specification. +-******************************************************************************/ +- +- +-/* This is the only Exim header that you should include. The effect of +-including any other Exim header is not defined, and may change from release to +-release. Use only the documented interface! */ +- +-#include "local_scan.h" +- +- +-/* This is a "do-nothing" version of a local_scan() function. The arguments +-are: +- +- fd The file descriptor of the open -D file, which contains the +- body of the message. The file is open for reading and +- writing, but modifying it is dangerous and not recommended. +- +- return_text A pointer to an unsigned char* variable which you can set in +- order to return a text string. It is initialized to NULL. +- +-The return values of this function are: +- +- LOCAL_SCAN_ACCEPT +- The message is to be accepted. The return_text argument is +- saved in $local_scan_data. +- +- LOCAL_SCAN_REJECT +- The message is to be rejected. The returned text is used +- in the rejection message. +- +- LOCAL_SCAN_TEMPREJECT +- This specifies a temporary rejection. The returned text +- is used in the rejection message. +-*/ ++#ifdef DLOPEN_LOCAL_SCAN ++#include <dlfcn.h> ++static int (*local_scan_fn)(int fd, uschar **return_text) = NULL; ++static int load_local_scan_library(void); ++#endif + + int + local_scan(int fd, uschar **return_text) + { + fd = fd; /* Keep picky compilers happy */ + return_text = return_text; +-return LOCAL_SCAN_ACCEPT; ++#ifdef DLOPEN_LOCAL_SCAN ++/* local_scan_path is defined AND not the empty string */ ++if (local_scan_path && *local_scan_path) ++ { ++ if (!local_scan_fn) ++ { ++ if (!load_local_scan_library()) ++ { ++ char *base_msg , *error_msg , *final_msg ; ++ int final_length = -1 ; ++ ++ base_msg=US"Local configuration error - local_scan() library failure\n"; ++ error_msg = dlerror() ; ++ ++ final_length = strlen(base_msg) + strlen(error_msg) + 1 ; ++ final_msg = (char*)malloc( final_length*sizeof(char) ) ; ++ *final_msg = '\0' ; ++ ++ strcat( final_msg , base_msg ) ; ++ strcat( final_msg , error_msg ) ; ++ ++ *return_text = final_msg ; ++ return LOCAL_SCAN_TEMPREJECT; ++ } ++ } ++ return local_scan_fn(fd, return_text); ++ } ++else ++#endif ++ return LOCAL_SCAN_ACCEPT; ++} ++ ++#ifdef DLOPEN_LOCAL_SCAN ++ ++static int load_local_scan_library(void) ++{ ++/* No point in keeping local_scan_lib since we'll never dlclose() anyway */ ++void *local_scan_lib = NULL; ++int (*local_scan_version_fn)(void); ++int vers_maj; ++int vers_min; ++ ++local_scan_lib = dlopen(local_scan_path, RTLD_NOW); ++if (!local_scan_lib) ++ { ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library open failed - " ++ "message temporarily rejected"); ++ return FALSE; ++ } ++ ++local_scan_version_fn = dlsym(local_scan_lib, "local_scan_version_major"); ++if (!local_scan_version_fn) ++ { ++ dlclose(local_scan_lib); ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain " ++ "local_scan_version_major() function - message temporarily rejected"); ++ return FALSE; ++ } ++ ++/* The major number is increased when the ABI is changed in a non ++ backward compatible way. */ ++vers_maj = local_scan_version_fn(); ++ ++local_scan_version_fn = dlsym(local_scan_lib, "local_scan_version_minor"); ++if (!local_scan_version_fn) ++ { ++ dlclose(local_scan_lib); ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain " ++ "local_scan_version_minor() function - message temporarily rejected"); ++ return FALSE; ++ } ++ ++/* The minor number is increased each time a new feature is added (in a ++ way that doesn't break backward compatibility) -- Marc */ ++vers_min = local_scan_version_fn(); ++ ++ ++if (vers_maj != LOCAL_SCAN_ABI_VERSION_MAJOR) ++ { ++ dlclose(local_scan_lib); ++ local_scan_lib = NULL; ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible major" ++ "version number, you need to recompile your module for this version" ++ "of exim (The module was compiled for version %d.%d and this exim provides" ++ "ABI version %d.%d)", vers_maj, vers_min, LOCAL_SCAN_ABI_VERSION_MAJOR, ++ LOCAL_SCAN_ABI_VERSION_MINOR); ++ return FALSE; ++ } ++else if (vers_min > LOCAL_SCAN_ABI_VERSION_MINOR) ++ { ++ dlclose(local_scan_lib); ++ local_scan_lib = NULL; ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible minor" ++ "version number, you need to recompile your module for this version" ++ "of exim (The module was compiled for version %d.%d and this exim provides" ++ "ABI version %d.%d)", vers_maj, vers_min, LOCAL_SCAN_ABI_VERSION_MAJOR, ++ LOCAL_SCAN_ABI_VERSION_MINOR); ++ return FALSE; ++ } ++ ++local_scan_fn = dlsym(local_scan_lib, "local_scan"); ++if (!local_scan_fn) ++ { ++ dlclose(local_scan_lib); ++ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain " ++ "local_scan() function - message temporarily rejected"); ++ return FALSE; ++ } ++ ++return TRUE; + } ++ ++#endif /* DLOPEN_LOCAL_SCAN */ + + /* End of local_scan.c */ +diff -urN exim-4.14-0/src/local_scan.h exim-4.14-1/src/local_scan.h +--- exim-4.14-0/src/local_scan.h Tue Mar 11 04:20:20 2003 ++++ exim-4.14-1/src/local_scan.h Sun Mar 23 15:34:57 2003 +@@ -71,6 +71,15 @@ + + #define SPOOL_DATA_START_OFFSET (MESSAGE_ID_LENGTH+3) + ++/* local_scan() ABI version number for dynamic libraries ++ The major number is increased when the ABI is changed in a non ++ backward compatible way. ++ The minor number is increased each time a new feature is added (in a ++ way that doesn't break backward compatibility) -- Marc */ ++#define LOCAL_SCAN_ABI_VERSION_MAJOR 1 ++#define LOCAL_SCAN_ABI_VERSION_MINOR 0 ++#define LOCAL_SCAN_ABI_VERSION \ ++ LOCAL_SCAN_ABI_VERSION_MAJOR.LOCAL_SCAN_ABI_VERSION_MINOR + + /* Structure definitions that are documented as visible in the function. */ + +diff -urN exim-4.14-0/src/readconf.c exim-4.14-1/src/readconf.c +--- exim-4.14-0/src/readconf.c Tue Mar 11 04:20:22 2003 ++++ exim-4.14-1/src/readconf.c Sun Mar 23 15:34:15 2003 +@@ -182,6 +182,9 @@ + { "local_from_prefix", opt_stringptr, &local_from_prefix }, + { "local_from_suffix", opt_stringptr, &local_from_suffix }, + { "local_interfaces", opt_stringptr, &local_interfaces }, ++#ifdef DLOPEN_LOCAL_SCAN ++ { "local_scan_path", opt_stringptr, &local_scan_path }, ++#endif + { "local_scan_timeout", opt_time, &local_scan_timeout }, + { "local_sender_retain", opt_bool, &local_sender_retain }, + { "localhost_number", opt_stringptr, &host_number_string }, diff --git a/debian/patches/exiscan.patch b/debian/patches/exiscan.patch new file mode 100644 index 0000000..600dc09 --- /dev/null +++ b/debian/patches/exiscan.patch @@ -0,0 +1,10034 @@ +diff -urN exim-4.32-orig/OS/Makefile-Base exim-4.32/OS/Makefile-Base +--- exim-4.32-orig/OS/Makefile-Base Thu Apr 15 10:27:01 2004 ++++ exim-4.32/OS/Makefile-Base Thu Apr 15 13:39:38 2004 +@@ -285,14 +285,14 @@ + # Targets for final binaries; the main one has a build number which is + # updated each time. We don't bother with that for the auxiliaries. + +-OBJ_EXIM = acl.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \ ++OBJ_EXIM = acl.o bmi_spam.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o demime.o \ + directory.o dns.o drtables.o enq.o exim.o expand.o filter.o \ + filtertest.o globals.o \ +- header.o host.o ip.o log.o lss.o match.o moan.o \ ++ header.o host.o ip.o log.o lss.o malware.o match.o mime.o moan.o \ + os.o parse.o queue.o \ +- rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \ +- route.o search.o sieve.o smtp_in.o smtp_out.o spool_in.o spool_out.o \ +- store.o string.o tls.o tod.o transport.o tree.o verify.o \ ++ rda.o readconf.o receive.o regex.o retry.o rewrite.o rfc2047.o \ ++ route.o search.o sieve.o smtp_in.o smtp_out.o spam.o spool_in.o spool_mbox.o spool_out.o \ ++ store.o string.o tls.o tnef.o tod.o transport.o tree.o verify.o \ + local_scan.o $(EXIM_PERL) + + exim: pcre/libpcre.a lookups/lookups.a auths/auths.a \ +@@ -498,12 +498,14 @@ + # Dependencies for the "ordinary" exim modules + + acl.o: $(HDRS) acl.c ++bmi_spam.o: $(HDRS) bmi_spam.c + child.o: $(HDRS) child.c + crypt16.o: $(HDRS) crypt16.c + daemon.o: $(HDRS) daemon.c + dbfn.o: $(HDRS) dbfn.c + debug.o: $(HDRS) debug.c + deliver.o: $(HDRS) deliver.c ++demime.o: $(HDRS) demime.c + directory.o: $(HDRS) directory.c + dns.o: $(HDRS) dns.c + enq.o: $(HDRS) enq.c +@@ -517,7 +519,9 @@ + ip.o: $(HDRS) ip.c + log.o: $(HDRS) log.c + lss.o: $(HDRS) lss.c ++malware.o: $(HDRS) malware.c + match.o: $(HDRS) match.c ++mime.o: $(HDRS) mime.c + moan.o: $(HDRS) moan.c + os.o: $(HDRS) os.c + parse.o: $(HDRS) parse.c +@@ -525,6 +529,7 @@ + rda.o: $(HDRS) rda.c + readconf.o: $(HDRS) readconf.c + receive.o: $(HDRS) receive.c ++regex.o: $(HDRS) regex.c + retry.o: $(HDRS) retry.c + rewrite.o: $(HDRS) rewrite.c + rfc2047.o: $(HDRS) rfc2047.c +@@ -533,11 +538,14 @@ + sieve.o: $(HDRS) sieve.c + smtp_in.o: $(HDRS) smtp_in.c + smtp_out.o: $(HDRS) smtp_out.c ++spam.o: $(HDRS) spam.c + spool_in.o: $(HDRS) spool_in.c ++spool_mbox.o: $(HDRS) spool_mbox.c + spool_out.o: $(HDRS) spool_out.c + store.o: $(HDRS) store.c + string.o: $(HDRS) string.c + tls.o: $(HDRS) tls.c tls-gnu.c tls-openssl.c ++tnef.o: $(HDRS) tnef.c + tod.o: $(HDRS) tod.c + transport.o: $(HDRS) transport.c + tree.o: $(HDRS) tree.c +diff -urN exim-4.32-orig/README.EXISCAN exim-4.32/README.EXISCAN +--- exim-4.32-orig/README.EXISCAN Thu Jan 1 01:00:00 1970 ++++ exim-4.32/README.EXISCAN Thu Apr 15 13:39:38 2004 +@@ -0,0 +1 @@ ++Please refer to doc/exiscan-acl-spec.txt +diff -urN exim-4.32-orig/doc/exiscan-acl-examples.txt exim-4.32/doc/exiscan-acl-examples.txt +--- exim-4.32-orig/doc/exiscan-acl-examples.txt Thu Jan 1 01:00:00 1970 ++++ exim-4.32/doc/exiscan-acl-examples.txt Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,440 @@ ++-------------------------------------------------------------- ++exiscan-acl example configurations / FAQ ++-------------------------------------------------------------- ++ ++Author: Tom Kistner <tom@duncanthrax.net> ++ ++The exiscan website is at http://duncanthrax.net/exiscan/. You ++will find the latest patch versions, as well as links to the ++mailing list and its archives there. ++ ++This document shows some example configuration snippets: ++ ++1. Basic sitewide virus and spam filtering by rejecting ++ matching messages after DATA. ++2. Adding a cryptographic "checks done" header that will ++ prevent re-scanning when the message re-visits one of your ++ mail servers, and the body size did not change. ++3. Marking spam-suspicious messages with extra headers and a ++ tag in the subject. ++4. Having more than one spam threshold to act on. ++5. Redirecting matching messages to special accounts while ++ preserving envelope recipient information. ++6. A multi-profile configuration for sites where different ++ "customers" (or users) have different content scanning ++ preferences. ++ ++These examples serve as a guideline and should give you some ++pointers that can help you to create your own configuration. ++Please do not copy these examples verbatim. You really need to ++know what you are doing. The content scanning topic is really ++complex and you can screw up your mail server easily if you do ++not get it "right". ++ ++I recommend to read the exiscan documentation on the above ++mentioned website before trying to make sense of the following ++examples. ++ ++Each example shows part of a DATA ACL definition, unless ++otherwise noted. ++ ++-------------------------------------------------------------- ++1. Basic setup for simple site-wide filtering ++-------------------------------------------------------------- ++The following example only shows the most basic use of the ++exiscan content filtering features. You should see it as a ++base that you can build on. However, it may be all you need ++for smaller systems with only a few users. ++ ++/* ----------------- ++# Do not scan messages submitted from our own hosts ++# and locally submitted messages. Since the DATA ACL ++# is not called for messages not submitted via SMTP ++# protocols, we do not need to check for an empty ++# host field. ++accept hosts = 127.0.0.1:+relay_from_hosts ++ ++# Unpack MIME containers and reject file extensions ++# used by worms. Note that the extension list may be ++# incomplete. ++deny message = $found_extension files are not accepted here ++ demime = com:vbs:bat:pif:scr ++ ++# Reject messages that have serious MIME errors. ++# This calls the demime condition again, but it ++# will return cached results. ++deny message = Serious MIME defect detected ($demime_reason) ++ demime = * ++ condition = ${if >{$demime_errorlevel}{2}{1}{0}} ++ ++# Reject messages containing malware. ++deny message = This message contains malware ($malware_name) ++ malware = * ++ ++# Reject spam messages. Remember to tweak your ++# site-wide SA profile. Do not spam-scan messages ++# larger than eighty kilobytes. ++deny message = Classified as spam (score $spam_score) ++ condition = ${if <{$message_size}{80k}{1}{0}} ++ spam = nobody ++ ++# Finally accept all other messages that have ++# made it to this point ++accept ++------------------ */ ++ ++ ++ ++-------------------------------------------------------------- ++2. Adding a cryptographic "scanning done" header ++-------------------------------------------------------------- ++ ++If you have a mail setup where the same message may pass your ++server twice (redirects from other servers), or you have ++multiple mail servers, you may want to make sure that each ++message is only checked once, to save processing time. Here is ++how to do it: ++ ++At the very beginning of your DATA ACL, put this: ++ ++/* ----------------- ++# Check our crytographic header. If it matches, accept ++# the message. ++accept condition = ${if eq {${hmac{md5}\ ++ {mysecret}\ ++ {$body_linecount}}}\ ++ {$h_X-Scan-Signature:} {1}{0}} ++------------------ */ ++ ++At the end, just before the final "accept" verb, put this: ++ ++/* ----------------- ++# Add the cryptographic header. ++warn message = X-Scan-Signature: ${hmac{md5}{mysecret}\ ++ {$body_linecount}} ++------------------ */ ++ ++Notice the two "mysecret" strings? Replace them with your own ++secret, and don't tell anyone :) The hash also includes the ++number of lines in the message body, to protect against ++message "modifications". ++ ++ ++-------------------------------------------------------------- ++3. Marking Spam messages with extra headers and subject tag ++-------------------------------------------------------------- ++ ++Since the false positive rate with spam scanning is high ++compared to virus scanning, it is wise to implement a scheme ++with two thresholds, where you reject messages with high ++scores and just mark messages with lower scores. End users can ++then set up filters in their Mail User Agents (MUAs). Since ++many MUAs can not filter on custom headers, it can be ++necessary to put a "spam tag" in the subject line. Since it is ++not (yet) possible to remove headers in Exims DATA ACL, we ++must do this in a system filter. Please see the Exim docs on ++how to set up a system filter. ++ ++The following example will unconditionally put two spam ++information headers in each message, if it is smaller than ++eighty kilobytes: ++ ++/* ----------------- ++# Always put X-Spam-Score header in the message. ++# It looks like this: ++# X-Spam-Score: 6.6 (++++++) ++# When a MUA cannot match numbers, it can match for an ++# equivalent number of '+' signs. ++# The 'true' makes sure that the header is always put ++# in, no matter what the score. ++warn message = X-Spam-Score: $spam_score ($spam_bar) ++ condition = ${if <{$message_size}{80k}{1}{0}} ++ spam = nobody:true ++ ++# Always put X-Spam-Report header in the message. ++# This is a multiline header that informs the user ++# which tests a message has "hit", and how much a ++# test has contributed to the score. ++warn message = X-Spam-Report: $spam_report ++ condition = ${if <{$message_size}{80k}{1}{0}} ++ spam = nobody:true ++------------------ */ ++ ++For the subject tag, we prepare a new subject header in the ++ACL, then swap it with the original Subject in the system ++filter. ++ ++In the DATA ACL, put this: ++/* ----------------- ++warn message = X-New-Subject: *SPAM* $h_subject: ++ spam = nobody ++------------------ */ ++ ++In the system filter, put this: ++/* ----------------- ++if "${if def:header_X-New-Subject: {there}}" is there ++then ++ headers remove Subject ++ headers add "Subject: $h_X-New-Subject:" ++ headers remove X-New-Subject ++endif ++------------------ */ ++ ++ ++-------------------------------------------------------------- ++4. Defining multiple spam thresholds with different actions ++-------------------------------------------------------------- ++If you want to mark messages if they exceed your threshold, ++but also have a higher "cutoff" threshold where you reject ++messages, use the example above, plus this part: ++ ++/* ----------------- ++deny message = Spam score too high ($spam_score) ++ condition = ${if <{$message_size}{80k}{1}{0}} ++ spam = nobody:true ++ condition = ${if >{$spam_score_int}{100}{1}{0}} ++------------------ */ ++ ++The last condition is only true if the spam score exceeds 10.0 ++points (Keep in mind that $spam_score_int is the messages ++score multiplied by ten). ++ ++ ++ ++-------------------------------------------------------------- ++5. Redirect infected or spam messages to special accounts ++-------------------------------------------------------------- ++Sometimes it is desirable not to reject messages, but to stop ++them for inspection, and then decide wether to delete, bounce ++or pass them. ++ ++There are multiple ways to achieve this. The simplest way is ++to freeze suspicious messages, and then thaw or bounce them ++after a review. Here is a simple example that will freeze spam ++suspicious messages when they exceed the SA threshold: ++ ++/* ----------------- ++warn log_message = frozen by spam scanner, score $spam_score ++ spam = nobody ++ control = freeze ++------------------ */ ++ ++Another way is to redirect suspicious messages to special ++postmaster accounts, where they can be reviewed. This involves ++setting up a router for these special accounts that acts on a ++header set in the DATA ACL. ++ ++This is the DATA ACL entry: ++ ++/* ----------------- ++warn message = X-Redirect-To: spambox@mycompany.com ++ spam = nobody ++------------------ */ ++ ++This puts the target address in a special header, which can in ++turn be read with this router: ++ ++/* ----------------- ++scan_redirect: ++ driver = redirect ++ condition = ${if def:h_X-Redirect-To: {1}{0}} ++ headers_add = X-Original-Recipient: $local_part@$domain ++ data = $h_X-Redirect-To: ++ headers_remove = X-Redirect-To ++ redirect_router = my_second_router ++------------------ */ ++ ++This router should probably be your very first one, and you ++need to edit the last line (redirect_router = ) to replace ++"my_second_router" with the name of your original first ++router. Note that the original message recipient is saved in ++the "X-Original-Recipient" header, and the X-Redirect-To ++header line is removed. ++ ++ ++-------------------------------------------------------------- ++6. Having multiple content scanning profiles for several ++ users or domains. ++-------------------------------------------------------------- ++This is one of the most often asked questions, and it also has ++the most complicated answer. To understand the difficulties, ++you should first remember that the exiscan facilities are run ++in the DATA ACL. This ACL is called ONCE per message, after ++the sending server has transmitted the end-of-data marker. ++This gives us the very cool possibility to reject unwanted ++messages with a 5xx error code in response. The big drawback ++is that a message can have multiple recipients, and you can ++only reject or accept a message for ALL recipients, not ++individual ones. ++ ++I will first sum up the possible solutions to this dilemma: ++ ++ a. Make sure that each incoming message can have only one ++ envelope recipient. This is brutal, but effective and ++ reliably solves the problem on your end. :) Drawback: ++ Incoming mail to multiple recipients is slowed down. The ++ exact time depends on the retry strategies of the sending ++ hosts. ++ ++ b. Offer a limited number of "profiles" that your customers ++ can subscribe to. Then, similar to a.), only accept ++ recipients with the same profile in a single "batch", and ++ defer the others. This does improve on the drawback of ++ a.) a bit. ++ ++ c. Do scanning as usual, but never reject messages in the ++ DATA ACL. Instead put appropriate information in extra ++ headers and query those in routers or transports later. ++ Drawback: You'll have to send bounces yourself, and your ++ queue will fill up with frozen bounces. Advantage: clean ++ solution, protocol-wise. ++ ++As you see, you can't have your cake and eat it too. Now lets ++get into the details of each possible solution. ++ ++a.) Making sure each incoming message that will be scanned ++ only has one recipient. ++ ++ To use this scheme, you must make sure that you do not use ++ it on your +relay_from_hosts and authenticated senders. ++ Both of these may be MUAs who cannot cope with such a ++ thing. ++ ++ Here is a RCPT ACL that implements the behaviour ++ (shortened, do not copy 1:1!): ++ ++ /* ------------ ++ acl_check_rcpt: ++ ++ # accept local, relay-allowed ++ # and authenticated sources ++ ++ accept hosts = : ++ deny local_parts = ^.*[@%!/|] ++ accept hosts = 127.0.0.1:+relay_from_hosts ++ accept authenticated = * ++ ++ # the following treat non-local, ++ # non-authenticated sources ++ ++ defer message = only one recipient at a time ++ condition = ${if def:acl_m0 {1}{0}} ++ ++ # [ .. ] ++ # put RBLs etc. here ++ # [ .. ] ++ ++ accept domains = +local_domains ++ endpass ++ message = unknown user ++ verify = recipient ++ set acl_m0 = $local_part@$domain ++ ++ accept domains = +relay_to_domains ++ endpass ++ message = unrouteable address ++ verify = recipient ++ set acl_m0 = $domain ++ ++ deny message = relay not permitted ++ ------------ */ ++ ++ The lines which contain acl_m0 are the important ones. The ++ $acl_m0 variable gets set when a remote server ++ successfully sends one RCPT. Subsequent RCPT commands are ++ deferred if this variable is set. The $acl_m0 variable now ++ contains the single recipient domain, which you can use in ++ the DATA ACL to determine the scanning profile. ++ ++ This scheme is only recommended for small servers with a ++ low number of possible recipients, where recipients do not ++ belong to the same organization. An example would be a ++ multiuser shell server. ++ ++ ++b.) Having several scanning profiles that "customers" can ++ choose from. ++ ++ Suppose you want to offer three profiles. Lets call them ++ "reject-aggressive", "reject-conservative", and "warn ++ -only". Customers can select one of the profiles for each ++ of their domains. So you end up with a mapping like this: ++ ++ domain-a.com: reject-aggressive ++ domain-b.org: warn-only ++ domain-c.net: reject-aggressive ++ domain-d.com: reject-conservative ++ [ .. ] ++ ++ Suppose you put that in a file called /etc/exim/scanprefs ++ ++ Now we make a scheme similar to a.), but we do allow more ++ than one recipient if they have the same scanning profile ++ than the first recipient. ++ ++ Here is a RCPT ACL that implements the behaviour ++ (shortened, do not copy 1:1!): ++ ++ /* ------------ ++ acl_check_rcpt: ++ ++ # accept local, relay-allowed and authenticated sources ++ ++ accept hosts = : ++ deny local_parts = ^.*[@%!/|] ++ accept hosts = 127.0.0.1:+relay_from_hosts ++ accept authenticated = * ++ ++ # the following treat non-local, non-authenticated sources ++ ++ defer message = try this address in the next batch ++ condition = ${if eq {${acl_m0}}\ ++ {${lookup{$domain}\ ++ lsearch{/etc/exim/scanprefs}}}\ ++ {0}{1}} ++ ++ # [ .. ] ++ # put RBLs etc. here ++ # [ .. ] ++ ++ accept domains = +local_domains ++ endpass ++ message = unknown user ++ verify = recipient ++ set acl_m0 = $local_part@$domain ++ ++ accept domains = +relay_to_domains ++ endpass ++ message = unrouteable address ++ verify = recipient ++ set acl_m0 = ${lookup{$domain}\ ++ lsearch{/etc/exim/scanprefs}} ++ ++ deny message = relay not permitted ++ ------------ */ ++ ++ Now a recipient address get deferred if its scan profile ++ does not match the current batch profile. The $acl_m0 ++ variable contains the name of the profile, that can be ++ used for processing in the DATA ACL. ++ ++ This scheme works pretty well if you keep the number of ++ possible profiles low, since that will prevent ++ fragmentation of RCPT blocks. ++ ++ ++c.) Classic content scanning without the possibility of ++ rejects after DATA. ++ ++ This emulates the "classic" content scanning in routers ++ and transports. The difference is that we still do the ++ scan in the DATA ACL, but put the outcome of each facility ++ in message headers, that can the be evaluated in special ++ routers, individually for each recipient. ++ ++ A special approach can be taken for spam scanning, since ++ the $spam_score_int variable is also available in routers ++ and transports (it gets written to the spool files), so ++ you do not need to put that information in a header, but ++ rather act on $spam_score_int directly. ++ +diff -urN exim-4.32-orig/doc/exiscan-acl-spec.txt exim-4.32/doc/exiscan-acl-spec.txt +--- exim-4.32-orig/doc/exiscan-acl-spec.txt Thu Jan 1 01:00:00 1970 ++++ exim-4.32/doc/exiscan-acl-spec.txt Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,933 @@ ++-------------------------------------------------------------- ++The exiscan-acl patch for exim4 - Documentation ++-------------------------------------------------------------- ++(c) Tom Kistner <tom@duncanthrax.net> 2003-???? ++License: GPL ++ ++The exiscan-acl patch adds content scanning to the exim4 ACL ++system. It supports the following scanning features: ++ ++ - MIME ACL that is called for all MIME parts in ++ incoming MIME messages. ++ - Antivirus using 3rd party scanners. ++ - Anti-spam using SpamAssassin. ++ - Anti-spam using Brightmail Antispam. ++ - Regular expression match against headers, bodies, raw ++ MIME parts and decoded MIME parts. ++ ++These features are hooked into exim by extending exim's ACL ++system. The patch adds expansion variables and ACL conditions. ++These conditions are designed to be used in the acl_smtp_data ++ACL. It is run when the sending host has completed the DATA ++phase and is waiting for our final response to his end-of-data ++marker. This allows us to reject messages containing ++unwanted content at that stage. ++ ++Support for Brightmail AntiSpam requires special compile-time ++flags. Please refer to chapter 7 for details. ++ ++The default exim configure file contains commented ++configuration examples for some features of exiscan-acl. ++ ++ ++0. Overall concept / Overview ++-------------------------------------------------------------- ++ ++The exiscan-acl patch extends Exims with mechanisms to ++deal with the message body content. Most of these additions ++affect the ACL system. The exiscan patch adds ++ ++- A new ACL, called 'acl_smtp_mime' (Please see detailed ++ chapter on this one below). ++- ACL conditions and modifiers ++ o malware (attach 3rd party virus/malware scanner) ++ o spam (attach SpamAssassin) ++ o regex (match regex against message, linewise) ++ o decode (decode MIME part to disk) ++ o mime_regex (match regex against decoded MIME part) ++ o control = fakereject (reject but really accept a message) ++- expansion variables ++ (see chapters below for names and explanations) ++- configuration options in section 1 of Exim's configure file. ++ o av_scanner (type and options of the AV scanner) ++ o spamd_address (network address / socket of spamd daemon). ++ ++All facilites work on a MBOX copy of the message that is ++temporarily spooled up in a file called: ++ ++ <spool_directory>/scan/<message_id>/<message_id>.eml ++ ++The .eml extension is a friendly hint to virus scanners that ++they can expect an MBOX-like structure inside that file. The ++file is only spooled up once, when the first exiscan facility ++is called. Subsequent calls to exiscan conditions will just ++open the file again. The directory is recursively removed ++when the acl_smtp_data has finished running. When the MIME ++ACL decodes files, they will be put into that same folder by ++default. ++ ++ ++1. The acl_smtp_mime MIME ACL ++-------------------------------------------------------------- ++ ++Note: if you are not familiar with exims ACL system, please go ++read the documentation on it, otherwise this chapter will not ++make much sense to you. ++ ++Here are the facts on acl_smtp_mime: ++ ++ - It is called once for each MIME part of a message, ++ including multipart types, in the sequence of their ++ position in the message. ++ ++ - It is called just before the acl_smtp_data ACL. They share ++ a result code (the one assed to the remote system after ++ DATA). When a call to acl_smtp_mime does not yield ++ "accept", ACL processing is aborted and the respective ++ result code is sent to the remote mailer. This means that ++ the acl_smtp_data is NOT called any more. ++ ++ - It is ONLY called if the message has a MIME-Version header. ++ ++ - MIME parts will NOT be dumped to disk by default, you have ++ to call the "decode" condition to do that (see further ++ below). ++ ++ - For RFC822 attachments (these are messages attached to ++ messages, with a content-type of 'message/rfc822'), ++ the ACL is called again in the same manner as ++ for the "primary" message, only that the $mime_is_rfc822 ++ expansion variable is set (see below). These messages ++ are always decoded to disk before being checked, but ++ the files are unlinked once the check is done. ++ ++To activate acl_smtp_mime, you need to add assign it the name ++of an ACL entry in section 1 of the config file, and then ++write that ACL in the ACL section, like: ++ ++ /* --------------- ++ ++ # -- section 1 ---- ++ [ ... ] ++ acl_smtp_mime = my_mime_acl ++ [ ... ] ++ ++ # -- acl section ---- ++ begin acl ++ ++ [ ... ] ++ ++ my_mime_acl: ++ ++ < ACL logic > ++ ++ [ ... ] ++ ++ ---------------- */ ++ ++The following list describes all expansion variables that are ++available in the MIME ACL: ++ ++ $mime_content_type ++ ------------------ ++ A very important variable. If the MIME part has a "Content ++ -Type:" header, this variable will contain its value, ++ lowercased, and WITHOUT any options (like "name" or ++ "charset", see below for these). Here are some examples of ++ popular MIME types, as they may appear in this variable: ++ ++ text/plain ++ text/html ++ application/octet-stream ++ image/jpeg ++ audio/midi ++ ++ If the MIME part has no "Content-Type:" header, this ++ variable is the empty string. ++ ++ ++ $mime_filename ++ -------------- ++ Another important variable, possibly the most important one. ++ It contains a proposed filename for an attachment, if one ++ was found in either the "Content-Type:" or "Content ++ -Disposition" headers. The filename will be RFC2047 ++ decoded, however NO additional sanity checks are done. See ++ instructions on "decode" further below. If no filename was ++ found, this variable is the empty string. ++ ++ ++ $mime_charset ++ ------------- ++ Contains the charset identifier, if one was found in the ++ "Content-Type:" header. Examples for charset identifiers are ++ ++ us-ascii ++ gb2312 (Chinese) ++ iso-8859-1 ++ ++ Please note that this value will NOT be normalized, so you ++ should do matches case-insensitively. ++ ++ ++ $mime_boundary ++ -------------- ++ If the current part is a multipart (see $mime_is_multipart) ++ below, it SHOULD have a boundary string. It is stored in ++ this variable. If the current part has no boundary parameter ++ in the "Content-Type:" header, this variable contains the ++ empty string. ++ ++ ++ $mime_content_disposition ++ ------------------------- ++ Contains the normalized content of the "Content ++ -Disposition:" header. You can expect strings like ++ "attachment" or "inline" here. ++ ++ ++ $mime_content_transfer_encoding ++ ------------------------------- ++ Contains the normalized content of the "Content ++ -transfer-encoding:" header. This is a symbolic name for ++ an encoding type. Typical values are "base64" and "quoted ++ -printable". ++ ++ ++ $mime_content_id ++ ---------------- ++ Contains the normalized content of the "Content ++ -ID:" header. This is a unique ID that can be used to ++ reference a part from another part. ++ ++ ++ $mime_content_description ++ ------------------------- ++ Contains the normalized content of the "Content ++ -Description:" header. It can contain a human-readable ++ description of the parts content. Some implementations will ++ repeat the filename for attachments here, but they are ++ usually only used for display purposes. ++ ++ ++ $mime_part_count ++ ---------------- ++ This is a counter that is raised for each processed MIME ++ part. It starts at zero for the very first part (which is ++ usually a multipart). The counter is per-message, so it is ++ reset when processing RFC822 attachments (see ++ $mime_is_rfc822). The counter stays set after acl_smtp_mime ++ is complete, so you can use it in the DATA ACL to determine ++ the number of MIME parts of a message. For non-MIME ++ messages, this variable will contain the value -1. ++ ++ ++ $mime_is_multipart ++ ------------------ ++ A "helper" flag that is true (1) when the current ++ part has the main type "multipart", for example ++ "multipart/alternative" or "multipart/mixed". Since ++ multipart entities only serve as containers for other parts, ++ you may not want to carry out specific actions on them. ++ ++ ++ $mime_is_rfc822 ++ --------------- ++ This flag is true (1) if the current part is NOT a part of ++ the checked message itself, but part of an attached message. ++ Attached message decoding is fully recursive. ++ ++ ++ $mime_decoded_filename ++ ---------------------- ++ This variable is only set after the "decode" condition (see ++ below) has been successfully run. It contains the full path ++ and file name of the file containing the decoded data. ++ ++ ++The expansion variables only reflect the content of the MIME ++headers for each part. To actually decode the part to disk, ++you can use the "decode" condition. The general syntax is ++ ++decode = [/<PATH>/]<FILENAME> ++ ++The right hand side is expanded before use. After expansion, ++the value can ++ ++ - be '0' or 'false', in which case no decoding is done. ++ - be the string 'default'. In that case, the file will be ++ put in the temporary "default" directory ++ <spool_directory>/scan/<message_id>/ ++ with a sequential file name, consisting of the message id ++ and a sequence number. The full path and name is available ++ in $mime_decoded_filename after decoding. ++ - start with a slash. If the full name is an existing ++ directory, it will be used as a replacement for the ++ "default" directory. The filename will then also be ++ sequentially assigned. If the name does not exist, it will ++ be used as the full path and file name. ++ - not start with a slash. It will then be used as the ++ filename, and the default path will be used. ++ ++You can easily decode a file with its original, proposed ++filename using "decode = $mime_filename". However, you should ++keep in mind that $mime_filename might contain anything. If ++you place files outside of the default path, they will not be ++automatically unlinked. ++ ++The MIME ACL also supports the regex= and mime_regex= ++conditions. You can use those to match regular expressions ++against raw and decoded MIME parts, respectively. Read the ++next section for more information on these conditions. ++ ++ ++ ++2. Match message or MIME parts against regular expressions ++-------------------------------------------------------------- ++ ++The "regex" condition takes one or more regular expressions as ++arguments and matches them against the full message (when ++called in the DATA ACL) or a raw MIME part (when called in the ++MIME ACL). The "regex" condition matches linewise, with a ++maximum line length of 32k characters. That means you can't ++have multiline matches with the "regex" condition. ++ ++The "mime_regex" can only be called in the MIME ACL. It ++matches up to 32k of decoded content (the whole content at ++once, not linewise). If the part has not been decoded with the ++"decode" condition earlier in the ACL, it is decoded ++automatically when "mime_regex" is executed (using default ++path and filename values). If the decoded data is larger ++than 32k, only the first 32k characters will be ++matched. ++ ++The regular expressions are passed as a colon-separated list. ++To include a literal colon, you must double it. Since the ++whole right-hand side string is expanded before being used, ++you must also escape dollar ($) signs with backslashes. ++ ++Here is a simple example: ++ ++/* ---------------------- ++deny message = contains blacklisted regex ($regex_match_string) ++ regex = [Mm]ortgage : URGENT BUSINESS PROPOSAL ++----------------------- */ ++ ++The conditions returns true if one of the regular ++expressions has matched. The $regex_match_string expansion ++variable is then set up and contains the matching regular ++expression. ++ ++Warning: With large messages, these conditions can be fairly ++CPU-intensive. ++ ++ ++ ++3. Antispam measures with SpamAssassin ++-------------------------------------------------------------- ++ ++The "spam" ACL condition calls SpamAssassin's "spamd" daemon ++to get a spam-score and a report for the message. You must ++first install SpamAssassin. You can get it ++at http://www.spamassassin.org, or, if you have a working ++Perl installation, you can use CPAN by calling ++ ++perl -MCPAN -e 'install Mail::SpamAssassin' ++ ++SpamAssassin has its own set of configuration files. Please ++review its documentation to see how you can tweak it. The ++default installation should work nicely, however. ++ ++After having installed and configured SpamAssassin, start the ++"spamd" daemon. By default, it listens on 127.0.0.1, TCP port ++783. If you use another host or port for spamd, you must set ++the spamd_address option in Section 1 of the exim ++configuration as follows (example): ++ ++spamd_address = 127.0.0.1 783 ++ ++As of version 2.60, spamd also supports communication over UNIX ++sockets. If you want to use these, supply spamd_address with ++an absolute file name instead of a address/port pair, like: ++ ++spamd_address = /var/run/spamd_socket ++ ++If you use the above mentioned default, you do NOT need to set ++this option. ++ ++To use the antispam facility, put the "spam" condition in a ++DATA ACL block. Here is a very simple example: ++ ++/* --------------- ++deny message = This message was classified as SPAM ++ spam = joe ++---------------- */ ++ ++On the right-hand side of the spam condition, you can put the ++username that SpamAssassin should scan for. That allows you to ++use per-domain or per-user antispam profiles. The right-hand ++side is expanded before being used, so you can put lookups or ++conditions there. When the right-hand side evaluates to "0" or ++"false", no scanning will be done and the condition will fail ++immediately. ++ ++If you do not want to scan for a particular user, but rather ++use the SpamAssassin system-wide default profile, you can scan ++for an unknown user, or simply use "nobody". ++ ++The "spam" condition will return true if the threshold ++specified in the user's SpamAssassin profile has been matched ++or exceeded. If you want to use the spam condition for its ++side effects (see the variables below), you can make it always ++return "true" by appending ":true" to the username. ++ ++When the condition is run, it sets up the following expansion ++variables: ++ ++ $spam_score The spam score of the message, for example ++ "3.4" or "30.5". This is useful for ++ inclusion in log or reject messages. ++ ++ $spam_score_int The spam score of the message, multiplied ++ by ten, as an integer value. For example ++ "34" or "305". This is useful for numeric ++ comparisons in conditions. See further ++ below for a more complicated example. This ++ variable is special, since it is written ++ to the spool file, so it can be used ++ during the whole life of the message on ++ your exim system, even in routers ++ or transports. ++ ++ $spam_bar A string consisting of a number of '+' or ++ '-' characters, representing the ++ spam_score value. A spam score of "4.4" ++ would have a spam_bar of '++++'. This is ++ useful for inclusion in warning headers, ++ since MUAs can match on such strings. ++ ++ $spam_report A multiline text table, containing the ++ full SpamAssassin report for the message. ++ Useful for inclusion in headers or reject ++ messages. ++ ++The spam condition caches its results. If you call it again ++with the same user name, it will not really scan again, but ++rather return the same values as before. ++ ++Finally, here is a commented example on how to use the spam ++condition: ++ ++/* ---------------- ++# put headers in all messages (no matter if spam or not) ++warn message = X-Spam-Score: $spam_score ($spam_bar) ++ spam = nobody:true ++warn message = X-Spam-Report: $spam_report ++ spam = nobody:true ++ ++# add second subject line with *SPAM* marker when message ++# is over threshold ++warn message = Subject: *SPAM* $h_Subject ++ spam = nobody ++ ++# reject spam at high scores (> 12) ++deny message = This message scored $spam_score spam points. ++ spam = nobody:true ++ condition = ${if >{$spam_score_int}{120}{1}{0}} ++----------------- */ ++ ++ ++ ++4. The "malware" facility ++ Scan messages for viruses using an external virus scanner ++-------------------------------------------------------------- ++ ++This facility lets you connect virus scanner software to exim. ++It supports a "generic" interface to scanners called via the ++shell, and specialized interfaces for "daemon" type virus ++scanners, who are resident in memory and thus are much faster. ++ ++To use this facility, you MUST set the "av_scanner" option in ++section 1 of the exim config file. It specifies the scanner ++type to use, and any additional options it needs to run. The ++basic syntax is as follows: ++ ++ av_scanner = <scanner-type>:<option1>:<option2>:[...] ++ ++The following scanner-types are supported in this release: ++ ++ sophie Sophie is a daemon that uses Sophos' libsavi ++ library to scan for viruses. You can get Sophie ++ at http://www.vanja.com/tools/sophie/. The only ++ option for this scanner type is the path to the ++ UNIX socket that Sophie uses for client ++ communication. The default path is ++ /var/run/sophie, so if you are using this, you ++ can omit the option. Example: ++ ++ av_scanner = sophie:/tmp/sophie ++ ++ ++ kavdaemon Kapersky's kavdaemon is a daemon-type scanner. ++ You can get a trial version at ++ http://www.kapersky.com. This scanner type takes ++ one option, which is the path to the daemon's ++ UNIX socket. The default is "/var/run/AvpCtl". ++ Example: ++ ++ av_scanner = kavdaemon:/opt/AVP/AvpCtl ++ ++ ++ clamd Another daemon type scanner, this one is GPL and ++ free. Get it at http://clamav.elektrapro.com/. ++ Clamd does not seem to unpack MIME containers, ++ so it is recommended to use the demime facility ++ with it. It takes one option: either the path ++ and name of a UNIX socket file, or a ++ hostname/port pair, separated by space. If ++ unset, the default is "/tmp/clamd". Example: ++ ++ av_scanner = clamd:192.168.2.100 1234 ++ or ++ av_scanner = clamd:/opt/clamd/socket ++ ++ ++ drweb This one is for the DrWeb (http://www.sald.com/) ++ daemon. It takes one argument, either a full ++ path to a UNIX socket, or an IP address and port ++ separated by whitespace. If you omit the ++ argument, the default ++ ++ /usr/local/drweb/run/drwebd.sock ++ ++ is used. Example: ++ ++ av_scanner = drweb:192.168.2.20 31337 ++ or ++ av_scanner = drweb:/var/run/drwebd.sock ++ ++ Thanks to Alex Miller <asm@abbyy.com.ua> for ++ contributing the code for this scanner. ++ ++ ++ mksd Yet another daemon type scanner, aimed mainly at ++ Polish users, though some parts of documentation ++ are now avaliable in English. You can get it at ++ http://linux.mks.com.pl/. The only option for ++ this scanner type is the maximum number of ++ processes used simultaneously to scan the ++ attachments, provided that the demime facility ++ is employed and also mksd has been run with ++ at least the same number of child processes. ++ You can safely omit this option, the default ++ value is 1. Example: ++ ++ av_scanner = mksd:2 ++ ++ ++ cmdline This is the keyword for the generic command line ++ scanner interface. It can be used to attach ++ virus scanners that are invoked on the shell. ++ This scanner type takes 3 mantadory options: ++ ++ - full path and name of the scanner binary, with ++ all command line options and a placeholder ++ (%s) for the directory to scan. ++ ++ - A regular expression to match against the ++ STDOUT and STDERR output of the virus scanner. ++ If the expression matches, a virus was found. ++ You must make absolutely sure that this ++ expression only matches on "virus found". This ++ is called the "trigger" expression. ++ ++ - Another regular expression, containing exactly ++ ONE pair of braces, to match the name of the ++ virus found in the scanners output. This is ++ called the "name" expression. ++ ++ Example: ++ ++ Sophos Sweep reports a virus on a line like ++ this: ++ ++ Virus 'W32/Magistr-B' found in file ./those.bat ++ ++ For the "trigger" expression, we just use the ++ "found" word. For the "name" expression, we want ++ to get the W32/Magistr-B string, so we can match ++ for the single quotes left and right of it, ++ resulting in the regex '(.*)' (WITH the quotes!) ++ ++ Altogether, this makes the configuration ++ setting: ++ ++ av_scanner = cmdline:\ ++ /path/to/sweep -all -rec -archive %s:\ ++ found:'(.+)' ++ ++ ++When av_scanner is correcly set, you can use the "malware" ++condition in the DATA ACL. The condition takes a right-hand ++argument that is expanded before use. It can then be one of ++ ++ - "true", "*", or "1", in which case the message is scanned ++ for viruses. The condition will succeed if a virus was ++ found, or fail otherwise. This is the recommended usage. ++ ++ - "false" or "0", in which case no scanning is done and the ++ condition will fail immediately. ++ ++ - a regular expression, in which case the message is scanned ++ for viruses. The condition will succeed if a virus found ++ found and its name matches the regular expression. This ++ allows you to take special actions on certain types of ++ viruses. ++ ++When a virus was found, the condition sets up an expansion ++variable called $malware_name that contains the name of the ++virus found. You should use it in a "message" modifier that ++contains the error returned to the sender. ++ ++The malware condition caches its results, so when you use it ++multiple times, the actual scanning process is only carried ++out once. ++ ++If your virus scanner cannot unpack MIME and TNEF containers ++itself, you should use the demime condition prior to the ++malware condition. ++ ++Here is a simple example: ++ ++/* ---------------------- ++deny message = This message contains malware ($malware_name) ++ demime = * ++ malware = * ++---------------------- */ ++ ++ ++ ++5. The "demime" facility ++ MIME unpacking, sanity checking and file extension blocking ++-------------------------------------------------------------- ++ ++* This facility provides a simpler interface to MIME decoding ++* than the MIME ACL functionality. It is kept in exiscan for ++* backward compatability. ++ ++The demime facility unpacks MIME containers in the message. It ++detects errors in MIME containers and can match file ++extensions found in the message against a list. Using this ++facility will produce additional files in the temporary scan ++directory that contain the unpacked MIME parts of the message. ++If you do antivirus scanning, it is recommened to use the ++"demime" condition before the antivirus ("malware") condition. ++ ++The condition name of this facility is "demime". On the right ++hand side, you can pass a colon-separated list of file ++extensions that it should match against. If one of the file ++extensions is found, the condition will return "OK" (or ++"true"), otherwise it will return FAIL (or "false"). If there ++was any TEMPORARY error while demimeing (mostly "disk full"), ++the condition will return DEFER, and the message will be ++temporarily rejected. ++ ++The right-hand side gets "expanded" before being treated as a ++list, so you can have conditions and lookups there. If it ++expands to an empty string, "false", or zero ("0"), no ++demimeing is done and the conditions returns FALSE. ++ ++A short example: ++ ++/* ------------ ++deny message = Found blacklisted file attachment ++ demime = vbs:com:bat:pif:prf:lnk ++--------------- */ ++ ++When the condition is run, it sets up the following expansion ++variables: ++ ++ $demime_errorlevel When an error was detected in a MIME ++ container, this variable contains the ++ "severity" of the error, as an integer ++ number. The higher the value, the ++ more severe the error. If this ++ variable is unset or zero, no error has ++ occured. ++ ++ $demime_reason When $demime_errorlevel is greater than ++ zero, this variable contains a human ++ -readable text string describing the ++ MIME error that occured. ++ ++ $found_extension When the "demime" condition returns ++ "true", this variable contains the file ++ extension it has found. ++ ++Both $demime_errorlevel and $demime_reason are set with the ++first call of the "demime" condition, and are not changed on ++subsequent calls. ++ ++If do not want to check for any file extensions, but rather ++use the demime facility for unpacking or error checking ++purposes, just pass "*" as the right-hand side value. ++ ++Here is a more elaborate example on how to use this facility: ++ ++/* ----------------- ++# Reject messages with serious MIME container errors ++deny message = Found MIME error ($demime_reason). ++ demime = * ++ condition = ${if >{$demime_errorlevel}{2}{1}{0}} ++ ++# Reject known virus spreading file extensions. ++# Accepting these is pretty much braindead. ++deny message = contains $found_extension file (blacklisted). ++ demime = com:vbs:bat:pif:scr ++ ++# Freeze .exe and .doc files. Postmaster can ++# examine them and eventually thaw them up. ++deny log_message = Another $found_extension file. ++ demime = exe:doc ++ control = freeze ++--------------------- */ ++ ++ ++ ++6. The "fakereject" control statement ++ Reject a message while really accepting it. ++-------------------------------------------------------------- ++ ++When you put "control = fakereject" in an ACL statement, the ++following will happen: If exim would have accepted the ++message, it will tell the remote host that it did not, with a ++message of: ++ ++550-FAKE_REJECT id=xxxxxx-xxxxxx-xx ++550-Your message has been rejected but is being kept for evaluation. ++550 If it was a legit message, it may still be delivered to the target recipient(s). ++ ++But exim will go on to treat the message as if it had accepted ++it. This should be used with extreme caution, please look into ++the examples document for possible usage. ++ ++ ++ ++7. Brighmail AntiSpam (BMI) suppport ++-------------------------------------------------------------- ++ ++Brightmail AntiSpam is a commercial package. Please see ++http://www.brightmail.com for more information on ++the product. For the sake of clarity, we'll refer to it as ++"BMI" from now on. ++ ++ ++0) BMI concept and implementation overview ++ ++In contrast to how spam-scanning with SpamAssassin is ++implemented in exiscan-acl, BMI is more suited for per ++-recipient scanning of messages. However, each messages is ++scanned only once, but multiple "verdicts" for multiple ++recipients can be returned from the BMI server. The exiscan ++implementation passes the message to the BMI server just ++before accepting it. It then adds the retrieved verdicts to ++the messages header file in the spool. These verdicts can then ++be queried in routers, where operation is per-recipient ++instead of per-message. To use BMI, you need to take the ++following steps: ++ ++ 1) Compile Exim with BMI support ++ 2) Set up main BMI options (top section of exim config file) ++ 3) Set up ACL control statement (ACL section of the config ++ file) ++ 4) Set up your routers to use BMI verdicts (routers section ++ of the config file). ++ ++These four steps are explained in more details below. ++ ++1) Adding support for BMI at compile time ++ ++ To compile with BMI support, you need to link Exim against ++ the Brighmail client SDK, consisting of a library ++ (libbmiclient_single.so) and a header file (bmi_api.h). ++ You'll also need to explicitly set a flag in the Makefile to ++ include BMI support in the Exim binary. Both can be achieved ++ with these 2 lines in Local/Makefile: ++ ++ CFLAGS=-DBRIGHTMAIL -I/path/to/the/dir/with/the/includefile ++ EXTRALIBS_EXIM=-L/path/to/the/dir/with/the/library -lbmiclient_single ++ ++ If you use other CFLAGS or EXTRALIBS_EXIM settings then ++ merge the content of these lines with them. ++ ++ You should also include the location of ++ libbmiclient_single.so in your dynamic linker configuration ++ file (usually /etc/ld.so.conf) and run "ldconfig" ++ afterwards, or else the produced Exim binary will not be ++ able to find the library file. ++ ++ ++2) Setting up BMI support in the exim main configuration ++ ++ To enable BMI support in the main exim configuration, you ++ should set the path to the main BMI configuration file with ++ the "bmi_config_file" option, like this: ++ ++ bmi_config_file = /opt/brightmail/etc/brightmail.cfg ++ ++ This must go into section 1 of exims configuration file (You ++ can put it right on top). If you omit this option, it ++ defaults to /opt/brightmail/etc/brightmail.cfg. ++ ++ ++3) Set up ACL control statement ++ ++ To optimize performance, it makes sense only to process ++ messages coming from remote, untrusted sources with the BMI ++ server. To set up a messages for processing by the BMI ++ server, you MUST set the "bmi_run" control statement in any ++ ACL for an incoming message. You will typically do this in ++ an "accept" block in the "acl_check_rcpt" ACL. You should ++ use the "accept" block(s) that accept messages from remote ++ servers for your own domain(s). Here is an example that uses ++ the "accept" blocks from exims default configuration file: ++ ++ ++ accept domains = +local_domains ++ endpass ++ verify = recipient ++ control = bmi_run ++ ++ accept domains = +relay_to_domains ++ endpass ++ verify = recipient ++ control = bmi_run ++ ++ If bmi_run is not set in any ACL during reception of the ++ message, it will NOT be passed to the BMI server. ++ ++ ++4) Setting up routers to use BMI verdicts ++ ++ When a message has been run through the BMI server, one or ++ more "verdicts" are present. Different recipients can have ++ different verdicts. Each recipient is treated individually ++ during routing, so you can query the verdicts by recipient ++ at that stage. From Exims view, a verdict can have the ++ following outcomes: ++ ++ o deliver the message normally ++ o deliver the message to an alternate location ++ o do not deliver the message ++ ++ To query the verdict for a recipient, the implementation ++ offers the following tools: ++ ++ ++ - Boolean router preconditions. These can be used in any ++ router. For a simple implementation of BMI, these may be ++ all that you need. The following preconditions are ++ available: ++ ++ o bmi_deliver_default ++ ++ This precondition is TRUE if the verdict for the ++ recipient is to deliver the message normally. If the ++ message has not been processed by the BMI server, this ++ variable defaults to TRUE. ++ ++ o bmi_deliver_alternate ++ ++ This precondition is TRUE if the verdict for the ++ recipient is to deliver the message to an alternate ++ location. You can get the location string from the ++ $bmi_alt_location expansion variable if you need it. See ++ further below. If the message has not been processed by ++ the BMI server, this variable defaults to FALSE. ++ ++ o bmi_dont_deliver ++ ++ This precondition is TRUE if the verdict for the ++ recipient is NOT to deliver the message to the ++ recipient. You will typically use this precondition in a ++ top-level blackhole router, like this: ++ ++ # don't deliver messages handled by the BMI server ++ bmi_blackhole: ++ driver = redirect ++ bmi_dont_deliver ++ data = :blackhole: ++ ++ This router should be on top of all others, so messages ++ that should not be delivered do not reach other routers ++ at all. If the message has not been processed by ++ the BMI server, this variable defaults to FALSE. ++ ++ ++ - A list router precondition to query if rules "fired" on ++ the message for the recipient. Its name is "bmi_rule". You ++ use it by passing it a colon-separated list of rule ++ numbers. You can use this condition to route messages that ++ matched specific rules. Here is an example: ++ ++ # special router for BMI rule #5, #8 and #11 ++ bmi_rule_redirect: ++ driver = redirect ++ bmi_rule = 5:8:11 ++ data = postmaster@mydomain.com ++ ++ ++ - Expansion variables. Several expansion variables are set ++ during routing. You can use them in custom router ++ conditions, for example. The following variables are ++ available: ++ ++ o $bmi_base64_verdict ++ ++ This variable will contain the BASE64 encoded verdict ++ for the recipient being routed. You can use it to add a ++ header to messages for tracking purposes, for example: ++ ++ localuser: ++ driver = accept ++ check_local_user ++ headers_add = X-Brightmail-Tracker: $bmi_base64_verdict ++ transport = local_delivery ++ ++ If there is no verdict available for the recipient being ++ routed, this variable contains the empty string. ++ ++ o $bmi_alt_location ++ ++ If the verdict is to redirect the message to an ++ alternate location, this variable will contain the ++ alternate location string returned by the BMI server. In ++ its default configuration, this is a header-like string ++ that can be added to the message with "headers_add". If ++ there is no verdict available for the recipient being ++ routed, or if the message is to be delivered normally, ++ this variable contains the empty string. ++ ++ o $bmi_deliver ++ ++ This is an additional integer variable that can be used ++ to query if the message should be delivered at all. You ++ should use router preconditions instead if possible. ++ ++ $bmi_deliver is '0': the message should NOT be delivered. ++ $bmi_deliver is '1': the message should be delivered. ++ ++ ++ IMPORTANT NOTE: Verdict inheritance. ++ The message is passed to the BMI server during message ++ reception, using the target addresses from the RCPT TO: ++ commands in the SMTP transaction. If recipients get expanded ++ or re-written (for example by aliasing), the new address(es) ++ inherit the verdict from the original address. This means ++ that verdicts also apply to all "child" addresses generated ++ from top-level addresses that were sent to the BMI server. ++ ++ ++-------------------------------------------------------------- ++End of file ++-------------------------------------------------------------- +diff -urN exim-4.32-orig/exim_monitor/em_globals.c exim-4.32/exim_monitor/em_globals.c +--- exim-4.32-orig/exim_monitor/em_globals.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/exim_monitor/em_globals.c Thu Apr 15 13:39:38 2004 +@@ -42,6 +42,10 @@ + uschar *action_required; + uschar *alternate_config = NULL; + ++#ifdef BRIGHTMAIL ++int bmi_run = 0; ++uschar *bmi_verdicts = NULL; ++#endif + int body_max = 20000; + + uschar *exim_path = US BIN_DIRECTORY "/exim" +@@ -126,6 +130,8 @@ + BOOL deliver_manual_thaw = FALSE; + BOOL dont_deliver = FALSE; + ++BOOL fake_reject = FALSE; ++ + header_line *header_last = NULL; + header_line *header_list = NULL; + +@@ -135,6 +141,7 @@ + + BOOL local_error_message = FALSE; + uschar *local_scan_data = NULL; ++uschar *spam_score_int = NULL; + BOOL log_timezone = FALSE; + int message_age = 0; + uschar *message_id; +diff -urN exim-4.32-orig/scripts/MakeLinks exim-4.32/scripts/MakeLinks +--- exim-4.32-orig/scripts/MakeLinks Thu Apr 15 10:27:01 2004 ++++ exim-4.32/scripts/MakeLinks Thu Apr 15 13:39:38 2004 +@@ -170,19 +170,25 @@ + # but local_scan.c does not, because its location is taken from the build-time + # configuration. Likewise for the os.c file, which gets build dynamically. + ++ln -s ../src/bmi_spam.h bmi_spam.h + ln -s ../src/dbfunctions.h dbfunctions.h + ln -s ../src/dbstuff.h dbstuff.h ++ln -s ../src/demime.h demime.h + ln -s ../src/exim.h exim.h + ln -s ../src/functions.h functions.h + ln -s ../src/globals.h globals.h + ln -s ../src/local_scan.h local_scan.h + ln -s ../src/macros.h macros.h ++ln -s ../src/mime.h mime.h + ln -s ../src/mytypes.h mytypes.h + ln -s ../src/osfunctions.h osfunctions.h ++ln -s ../src/spam.h spam.h + ln -s ../src/store.h store.h + ln -s ../src/structs.h structs.h ++ln -s ../src/tnef.h tnef.h + + ln -s ../src/acl.c acl.c ++ln -s ../src/bmi_spam.c bmi_spam.c + ln -s ../src/buildconfig.c buildconfig.c + ln -s ../src/child.c child.c + ln -s ../src/crypt16.c crypt16.c +@@ -190,6 +196,7 @@ + ln -s ../src/dbfn.c dbfn.c + ln -s ../src/debug.c debug.c + ln -s ../src/deliver.c deliver.c ++ln -s ../src/demime.c demime.c + ln -s ../src/directory.c directory.c + ln -s ../src/dns.c dns.c + ln -s ../src/drtables.c drtables.c +@@ -208,7 +215,9 @@ + ln -s ../src/ip.c ip.c + ln -s ../src/log.c log.c + ln -s ../src/lss.c lss.c ++ln -s ../src/malware.c malware.c + ln -s ../src/match.c match.c ++ln -s ../src/mime.c mime.c + ln -s ../src/moan.c moan.c + ln -s ../src/parse.c parse.c + ln -s ../src/perl.c perl.c +@@ -216,6 +225,7 @@ + ln -s ../src/rda.c rda.c + ln -s ../src/readconf.c readconf.c + ln -s ../src/receive.c receive.c ++ln -s ../src/regex.c regex.c + ln -s ../src/retry.c retry.c + ln -s ../src/rewrite.c rewrite.c + ln -s ../src/rfc2047.c rfc2047.c +@@ -224,13 +234,16 @@ + ln -s ../src/sieve.c sieve.c + ln -s ../src/smtp_in.c smtp_in.c + ln -s ../src/smtp_out.c smtp_out.c ++ln -s ../src/spam.c spam.c + ln -s ../src/spool_in.c spool_in.c ++ln -s ../src/spool_mbox.c spool_mbox.c + ln -s ../src/spool_out.c spool_out.c + ln -s ../src/store.c store.c + ln -s ../src/string.c string.c + ln -s ../src/tls.c tls.c + ln -s ../src/tls-gnu.c tls-gnu.c + ln -s ../src/tls-openssl.c tls-openssl.c ++ln -s ../src/tnef.c tnef.c + ln -s ../src/tod.c tod.c + ln -s ../src/transport.c transport.c + ln -s ../src/tree.c tree.c +diff -urN exim-4.32-orig/src/acl.c exim-4.32/src/acl.c +--- exim-4.32-orig/src/acl.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/acl.c Thu Apr 15 13:39:38 2004 +@@ -7,6 +7,8 @@ + + /* Code for handling Access Control Lists (ACLs) */ + ++/* This file has been modified by the exiscan-acl patch. */ ++ + #include "exim.h" + + +@@ -32,19 +34,19 @@ + /* ACL condition and modifier codes - keep in step with the table that + follows. */ + +-enum { ACLC_ACL, ACLC_AUTHENTICATED, ACLC_CONDITION, ACLC_CONTROL, ACLC_DELAY, ++enum { ACLC_ACL, ACLC_AUTHENTICATED, ACLC_CONDITION, ACLC_CONTROL, ACLC_DECODE, ACLC_DELAY, ACLC_DEMIME, + ACLC_DNSLISTS, ACLC_DOMAINS, ACLC_ENCRYPTED, ACLC_ENDPASS, ACLC_HOSTS, +- ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE, ACLC_MESSAGE, +- ACLC_RECIPIENTS, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, ACLC_VERIFY }; ++ ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE, ACLC_MALWARE, ACLC_MESSAGE, ACLC_MIME_REGEX, ++ ACLC_RECIPIENTS, ACLC_REGEX, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, ACLC_SPAM, ACLC_VERIFY }; + + /* ACL conditions/modifiers: "delay", "control", "endpass", "message", + "log_message", "logwrite", and "set" are modifiers that look like conditions + but always return TRUE. They are used for their side effects. */ + + static uschar *conditions[] = { US"acl", US"authenticated", US"condition", +- US"control", US"delay", US"dnslists", US"domains", US"encrypted", +- US"endpass", US"hosts", US"local_parts", US"log_message", US"logwrite", +- US"message", US"recipients", US"sender_domains", US"senders", US"set", ++ US"control", US"decode", US"delay", US"demime", US"dnslists", US"domains", US"encrypted", ++ US"endpass", US"hosts", US"local_parts", US"log_message", US"logwrite", US"malware", ++ US"message", US"mime_regex", US"recipients", US"regex", US"sender_domains", US"senders", US"set", US"spam", + US"verify" }; + + /* Flags to indicate for which conditions /modifiers a string expansion is done +@@ -56,7 +58,9 @@ + FALSE, /* authenticated */ + TRUE, /* condition */ + TRUE, /* control */ ++ TRUE, /* decode */ + TRUE, /* delay */ ++ TRUE, /* demime */ + TRUE, /* dnslists */ + FALSE, /* domains */ + FALSE, /* encrypted */ +@@ -65,11 +69,15 @@ + FALSE, /* local_parts */ + TRUE, /* log_message */ + TRUE, /* logwrite */ ++ TRUE, /* malware */ + TRUE, /* message */ ++ TRUE, /* mime_regex */ + FALSE, /* recipients */ ++ TRUE, /* regex */ + FALSE, /* sender_domains */ + FALSE, /* senders */ + TRUE, /* set */ ++ TRUE, /* spam */ + TRUE /* verify */ + }; + +@@ -80,7 +88,9 @@ + FALSE, /* authenticated */ + FALSE, /* condition */ + TRUE, /* control */ ++ FALSE, /* decode */ + TRUE, /* delay */ ++ FALSE, /* demime */ + FALSE, /* dnslists */ + FALSE, /* domains */ + FALSE, /* encrypted */ +@@ -89,11 +99,15 @@ + FALSE, /* local_parts */ + TRUE, /* log_message */ + TRUE, /* log_write */ ++ FALSE, /* malware */ + TRUE, /* message */ ++ FALSE, /* mime_regex */ + FALSE, /* recipients */ ++ FALSE, /* regex */ + FALSE, /* sender_domains */ + FALSE, /* senders */ + TRUE, /* set */ ++ FALSE, /* spam */ + FALSE /* verify */ + }; + +@@ -102,6 +116,7 @@ + + static unsigned int cond_forbids[] = { + 0, /* acl */ ++ + (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)| /* authenticated */ + (1<<ACL_WHERE_HELO), + 0, /* condition */ +@@ -112,12 +127,29 @@ + (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)| + (1<<ACL_WHERE_STARTTLS)|(ACL_WHERE_VRFY), + ++ (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* decode */ ++ (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)| ++ (1<<ACL_WHERE_DATA)| ++ (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)| ++ (1<<ACL_WHERE_MAILAUTH)| ++ (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)| ++ (1<<ACL_WHERE_VRFY), ++ + 0, /* delay */ ++ ++ (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* demime */ ++ (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)| ++ (1<<ACL_WHERE_MIME)| ++ (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)| ++ (1<<ACL_WHERE_MAILAUTH)| ++ (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)| ++ (1<<ACL_WHERE_VRFY), ++ + (1<<ACL_WHERE_NOTSMTP), /* dnslists */ + + (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* domains */ + (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)| +- (1<<ACL_WHERE_DATA)| ++ (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)| + (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)| + (1<<ACL_WHERE_MAILAUTH)| + (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)| +@@ -125,22 +157,34 @@ + + (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)| /* encrypted */ + (1<<ACL_WHERE_HELO), ++ + 0, /* endpass */ ++ + (1<<ACL_WHERE_NOTSMTP), /* hosts */ + + (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* local_parts */ + (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)| +- (1<<ACL_WHERE_DATA)| ++ (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)| + (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)| + (1<<ACL_WHERE_MAILAUTH)| + (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)| + (1<<ACL_WHERE_VRFY), + + 0, /* log_message */ ++ + 0, /* logwrite */ ++ ++ (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* malware */ ++ (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)| ++ (1<<ACL_WHERE_MIME)| ++ (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)| ++ (1<<ACL_WHERE_MAILAUTH)| ++ (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)| ++ (1<<ACL_WHERE_VRFY), ++ + 0, /* message */ + +- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* recipients */ ++ (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* mime_regex */ + (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)| + (1<<ACL_WHERE_DATA)| + (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)| +@@ -148,6 +192,21 @@ + (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)| + (1<<ACL_WHERE_VRFY), + ++ (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* recipients */ ++ (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)| ++ (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)| ++ (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)| ++ (1<<ACL_WHERE_MAILAUTH)| ++ (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)| ++ (1<<ACL_WHERE_VRFY), ++ ++ (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* regex */ ++ (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)| ++ (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)| ++ (1<<ACL_WHERE_MAILAUTH)| ++ (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)| ++ (1<<ACL_WHERE_VRFY), ++ + (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)| /* sender_domains */ + (1<<ACL_WHERE_HELO)| + (1<<ACL_WHERE_MAILAUTH)| +@@ -162,6 +221,14 @@ + + 0, /* set */ + ++ (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* spam */ ++ (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)| ++ (1<<ACL_WHERE_MIME)| ++ (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)| ++ (1<<ACL_WHERE_MAILAUTH)| ++ (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)| ++ (1<<ACL_WHERE_VRFY), ++ + /* Certain types of verify are always allowed, so we let it through + always and check in the verify function itself */ + +@@ -447,7 +514,7 @@ + /* If this isn't a message ACL, we can't do anything with a user message. + Log an error. */ + +-if (where != ACL_WHERE_MAIL && where != ACL_WHERE_RCPT && ++if (where != ACL_WHERE_MAIL && where != ACL_WHERE_MIME && where != ACL_WHERE_RCPT && + where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP) + { + log_write(0, LOG_MAIN|LOG_PANIC, "ACL \"warn\" with \"message\" setting " +@@ -1136,6 +1203,16 @@ + deliver_freeze = TRUE; + deliver_frozen_at = time(NULL); + } ++ else if (Ustrcmp(arg, "fakereject") == 0) ++ { ++ fake_reject = TRUE; ++ } ++#ifdef BRIGHTMAIL ++ else if (Ustrcmp(arg, "bmi_run") == 0) ++ { ++ bmi_run = 1; ++ } ++#endif + else if (Ustrcmp(arg, "queue_only") == 0) + { + queue_only_policy = TRUE; +@@ -1170,6 +1247,30 @@ + rc = verify_check_dnsbl(&arg); + break; + ++ case ACLC_DECODE: ++ rc = mime_decode(&arg); ++ break; ++ ++ case ACLC_MIME_REGEX: ++ rc = mime_regex(&arg); ++ break; ++ ++ case ACLC_DEMIME: ++ rc = demime(&arg); ++ break; ++ ++ case ACLC_MALWARE: ++ rc = malware(&arg); ++ break; ++ ++ case ACLC_SPAM: ++ rc = spam(&arg); ++ break; ++ ++ case ACLC_REGEX: ++ rc = regex(&arg); ++ break; ++ + case ACLC_DOMAINS: + rc = match_isinlist(addr->domain, &arg, 0, &domainlist_anchor, + addr->domain_cache, MCL_DOMAIN, TRUE, &deliver_domain_data); +@@ -1835,6 +1936,7 @@ + if (where != ACL_WHERE_MAIL && + where != ACL_WHERE_RCPT && + where != ACL_WHERE_DATA && ++ where != ACL_WHERE_MIME && + where != ACL_WHERE_NOTSMTP) + { + log_write(0, LOG_MAIN|LOG_PANIC, "\"discard\" verb not allowed in %s " +diff -urN exim-4.32-orig/src/bmi_spam.c exim-4.32/src/bmi_spam.c +--- exim-4.32-orig/src/bmi_spam.c Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/bmi_spam.c Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,418 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++ patch. It is NOT part of the standard exim distribution. */ ++ ++/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */ ++/* License: GPL */ ++ ++/* Code for calling Brightmail AntiSpam. */ ++ ++#include "exim.h" ++#include "bmi_spam.h" ++ ++#ifdef BRIGHTMAIL ++ ++uschar *bmi_process_message(header_line *header_list, int data_fd) { ++ BmiSystem *system = NULL; ++ BmiMessage *message = NULL; ++ BmiError err; ++ BmiErrorLocation err_loc; ++ BmiErrorType err_type; ++ const BmiVerdict *verdict = NULL; ++ FILE *data_file; ++ uschar data_buffer[4096]; ++ uschar localhost[] = "127.0.0.1"; ++ uschar *host_address; ++ uschar *verdicts = NULL; ++ int i,j; ++ ++ err = bmiInitSystem(BMI_VERSION, (char *)bmi_config_file, &system); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: could not initialize Brightmail system.", (int)err_loc, (int)err_type); ++ return NULL; ++ } ++ ++ err = bmiInitMessage(system, &message); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: could not initialize Brightmail message.", (int)err_loc, (int)err_type); ++ bmiFreeSystem(system); ++ return NULL; ++ } ++ ++ /* Send IP address of sending host */ ++ if (sender_host_address == NULL) ++ host_address = localhost; ++ else ++ host_address = sender_host_address; ++ err = bmiProcessConnection((char *)host_address, message); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiProcessConnection() failed (IP %s).", (int)err_loc, (int)err_type, (char *)host_address); ++ bmiFreeMessage(message); ++ bmiFreeSystem(system); ++ return NULL; ++ }; ++ ++ /* Send envelope sender address */ ++ err = bmiProcessFROM((char *)sender_address, message); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiProcessFROM() failed (address %s).", (int)err_loc, (int)err_type, (char *)sender_address); ++ bmiFreeMessage(message); ++ bmiFreeSystem(system); ++ return NULL; ++ }; ++ ++ /* Send envelope recipients */ ++ for(i=0;i<recipients_count;i++) { ++ recipient_item *r = recipients_list + i; ++ ++ err = bmiAccumulateTO((char *)r->address, NULL, message); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiAccumulateTO() failed (address %s).", (int)err_loc, (int)err_type, (char *)r->address); ++ bmiFreeMessage(message); ++ bmiFreeSystem(system); ++ return NULL; ++ }; ++ }; ++ err = bmiEndTO(message); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiEndTO() failed.", (int)err_loc, (int)err_type); ++ bmiFreeMessage(message); ++ bmiFreeSystem(system); ++ return NULL; ++ }; ++ ++ /* Send message headers */ ++ while (header_list != NULL) { ++ /* skip deleted headers */ ++ if (header_list->type == '*') { ++ header_list = header_list->next; ++ continue; ++ }; ++ err = bmiAccumulateHeaders((const char *)header_list->text, header_list->slen, message); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiAccumulateHeaders() failed.", (int)err_loc, (int)err_type); ++ bmiFreeMessage(message); ++ bmiFreeSystem(system); ++ return NULL; ++ }; ++ header_list = header_list->next; ++ }; ++ err = bmiEndHeaders(message); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiEndHeaders() failed.", (int)err_loc, (int)err_type); ++ bmiFreeMessage(message); ++ bmiFreeSystem(system); ++ return NULL; ++ }; ++ ++ /* Send body */ ++ data_file = fdopen(data_fd,"r"); ++ do { ++ j = fread(data_buffer, 1, sizeof(data_buffer), data_file); ++ if (j > 0) { ++ err = bmiAccumulateBody((const char *)data_buffer, j, message); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiAccumulateBody() failed.", (int)err_loc, (int)err_type); ++ bmiFreeMessage(message); ++ bmiFreeSystem(system); ++ return NULL; ++ }; ++ }; ++ } while (j > 0); ++ err = bmiEndBody(message); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiEndBody() failed.", (int)err_loc, (int)err_type); ++ bmiFreeMessage(message); ++ bmiFreeSystem(system); ++ return NULL; ++ }; ++ ++ ++ /* End message */ ++ err = bmiEndMessage(message); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiEndMessage() failed.", (int)err_loc, (int)err_type); ++ bmiFreeMessage(message); ++ bmiFreeSystem(system); ++ return NULL; ++ }; ++ ++ /* get store for the verdict string */ ++ verdicts = store_get(1); ++ *verdicts = '\0'; ++ ++ for ( err = bmiAccessFirstVerdict(message, &verdict); ++ verdict != NULL; ++ err = bmiAccessNextVerdict(message, verdict, &verdict) ) { ++ char *verdict_str; ++ ++ err = bmiCreateStrFromVerdict(verdict,&verdict_str); ++ if (!store_extend(verdicts, Ustrlen(verdicts)+1, Ustrlen(verdicts)+1+strlen(verdict_str)+1)) { ++ /* can't allocate more store */ ++ return NULL; ++ }; ++ if (*verdicts != '\0') ++ Ustrcat(verdicts, US ":"); ++ Ustrcat(verdicts, US verdict_str); ++ bmiFreeStr(verdict_str); ++ }; ++ ++ DEBUG(D_receive) debug_printf("bmi verdicts: %s\n", verdicts); ++ ++ if (Ustrlen(verdicts) == 0) ++ return NULL; ++ else ++ return verdicts; ++} ++ ++ ++int bmi_get_delivery_status(uschar *base64_verdict) { ++ BmiError err; ++ BmiErrorLocation err_loc; ++ BmiErrorType err_type; ++ BmiVerdict *verdict = NULL; ++ int rc = 1; /* deliver by default */ ++ ++ /* always deliver when there is no verdict */ ++ if (base64_verdict == NULL) ++ return 1; ++ ++ /* create verdict from base64 string */ ++ err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); ++ return 1; ++ }; ++ ++ err = bmiVerdictError(verdict); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ /* deliver normally due to error */ ++ rc = 1; ++ } ++ else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) { ++ /* deliver normally */ ++ rc = 1; ++ } ++ else if (bmiVerdictAccessDestination(verdict) == NULL) { ++ /* do not deliver */ ++ rc = 0; ++ } ++ else { ++ /* deliver to alternate location */ ++ rc = 1; ++ }; ++ ++ bmiFreeVerdict(verdict); ++ return rc; ++} ++ ++ ++uschar *bmi_get_alt_location(uschar *base64_verdict) { ++ BmiError err; ++ BmiErrorLocation err_loc; ++ BmiErrorType err_type; ++ BmiVerdict *verdict = NULL; ++ uschar *rc = NULL; ++ ++ /* always deliver when there is no verdict */ ++ if (base64_verdict == NULL) ++ return NULL; ++ ++ /* create verdict from base64 string */ ++ err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); ++ return NULL; ++ }; ++ ++ err = bmiVerdictError(verdict); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ /* deliver normally due to error */ ++ rc = NULL; ++ } ++ else if (bmiVerdictDestinationIsDefault(verdict) == BMI_TRUE) { ++ /* deliver normally */ ++ rc = NULL; ++ } ++ else if (bmiVerdictAccessDestination(verdict) == NULL) { ++ /* do not deliver */ ++ rc = NULL; ++ } ++ else { ++ /* deliver to alternate location */ ++ rc = store_get(strlen(bmiVerdictAccessDestination(verdict))+1); ++ Ustrcpy(rc, bmiVerdictAccessDestination(verdict)); ++ rc[strlen(bmiVerdictAccessDestination(verdict))] = '\0'; ++ }; ++ ++ bmiFreeVerdict(verdict); ++ return rc; ++} ++ ++uschar *bmi_get_base64_verdict(uschar *bmi_local_part, uschar *bmi_domain) { ++ BmiError err; ++ BmiErrorLocation err_loc; ++ BmiErrorType err_type; ++ BmiVerdict *verdict = NULL; ++ const BmiRecipient *recipient = NULL; ++ const char *verdict_str = NULL; ++ uschar *verdict_ptr; ++ uschar *verdict_buffer = NULL; ++ int sep = 0; ++ ++ /* return nothing if there are no verdicts available */ ++ if (bmi_verdicts == NULL) ++ return NULL; ++ ++ /* allocate room for the b64 verdict string */ ++ verdict_buffer = store_get(Ustrlen(bmi_verdicts)+1); ++ ++ /* loop through verdicts */ ++ verdict_ptr = bmi_verdicts; ++ while ((verdict_str = (const char *)string_nextinlist(&verdict_ptr, &sep, ++ verdict_buffer, ++ Ustrlen(bmi_verdicts)+1)) != NULL) { ++ ++ /* create verdict from base64 string */ ++ err = bmiCreateVerdictFromStr(verdict_str, &verdict); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, verdict_str); ++ return NULL; ++ }; ++ ++ /* loop through rcpts for this verdict */ ++ for ( recipient = bmiVerdictAccessFirstRecipient(verdict); ++ recipient != NULL; ++ recipient = bmiVerdictAccessNextRecipient(verdict, recipient)) { ++ uschar *rcpt_local_part; ++ uschar *rcpt_domain; ++ ++ /* compare address against our subject */ ++ rcpt_local_part = (unsigned char *)bmiRecipientAccessAddress(recipient); ++ rcpt_domain = Ustrchr(rcpt_local_part,'@'); ++ if (rcpt_domain == NULL) { ++ rcpt_domain = US""; ++ } ++ else { ++ *rcpt_domain = '\0'; ++ rcpt_domain++; ++ }; ++ ++ if ( (strcmpic(rcpt_local_part, bmi_local_part) == 0) && ++ (strcmpic(rcpt_domain, bmi_domain) == 0) ) { ++ /* found verdict */ ++ bmiFreeVerdict(verdict); ++ return (uschar *)verdict_str; ++ }; ++ }; ++ ++ bmiFreeVerdict(verdict); ++ }; ++ ++ return NULL; ++} ++ ++ ++int bmi_check_rule(uschar *base64_verdict, uschar *option_list) { ++ BmiError err; ++ BmiErrorLocation err_loc; ++ BmiErrorType err_type; ++ BmiVerdict *verdict = NULL; ++ int rc = 0; ++ uschar *rule_num; ++ uschar *rule_ptr; ++ uschar rule_buffer[32]; ++ int sep = 0; ++ ++ ++ /* no verdict -> no rule fired */ ++ if (base64_verdict == NULL) ++ return 0; ++ ++ /* create verdict from base64 string */ ++ err = bmiCreateVerdictFromStr(CS base64_verdict, &verdict); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ err_loc = bmiErrorGetLocation(err); ++ err_type = bmiErrorGetType(err); ++ log_write(0, LOG_PANIC, ++ "bmi error [loc %d type %d]: bmiCreateVerdictFromStr() failed. [%s]", (int)err_loc, (int)err_type, base64_verdict); ++ return 0; ++ }; ++ ++ err = bmiVerdictError(verdict); ++ if (bmiErrorIsFatal(err) == BMI_TRUE) { ++ /* error -> no rule fired */ ++ bmiFreeVerdict(verdict); ++ return 0; ++ } ++ ++ /* loop through numbers */ ++ rule_ptr = option_list; ++ while ((rule_num = string_nextinlist(&rule_ptr, &sep, ++ rule_buffer, 32)) != NULL) { ++ int rule_int = -1; ++ ++ /* try to translate to int */ ++ sscanf(rule_num, "%d", &rule_int); ++ if (rule_int > 0) { ++ debug_printf("checking rule #%d\n", rule_int); ++ /* check if rule fired on the message */ ++ if (bmiVerdictRuleFired(verdict, rule_int) == BMI_TRUE) { ++ debug_printf("rule #%d fired\n", rule_int); ++ rc = 1; ++ break; ++ }; ++ }; ++ }; ++ ++ ++ bmiFreeVerdict(verdict); ++ return rc; ++}; ++ ++#endif +diff -urN exim-4.32-orig/src/bmi_spam.h exim-4.32/src/bmi_spam.h +--- exim-4.32-orig/src/bmi_spam.h Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/bmi_spam.h Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,23 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++ patch. It is NOT part of the standard exim distribution. */ ++ ++/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */ ++/* License: GPL */ ++ ++/* Code for calling Brightmail AntiSpam. */ ++ ++#ifdef BRIGHTMAIL ++ ++#include <bmi_api.h> ++ ++extern uschar *bmi_process_message(header_line *, int); ++extern uschar *bmi_get_base64_verdict(uschar *, uschar *); ++extern int bmi_get_delivery_status(uschar *); ++extern uschar *bmi_get_alt_location(uschar *); ++extern int bmi_check_rule(uschar *,uschar *); ++ ++#endif +diff -urN exim-4.32-orig/src/configure.default exim-4.32/src/configure.default +--- exim-4.32-orig/src/configure.default Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/configure.default Thu Apr 15 13:39:38 2004 +@@ -108,6 +108,26 @@ + + # You should not change that setting until you understand how ACLs work. + ++# The following ACL entries are used if you want to do content scanning with ++# the exiscan-acl patch. When you uncomment one of these lines, you must also ++# review the respective entries in the ACL section further below. ++ ++# acl_smtp_mime = acl_check_mime ++# acl_smtp_data = acl_check_content ++ ++# This configuration variable defines the virus scanner that is used with ++# the 'malware' ACL condition of the exiscan acl-patch. If you do not use ++# virus scanning, leave it commented. Please read doc/exiscan-acl-readme.txt ++# for a list of supported scanners. ++ ++# av_scanner = sophie:/var/run/sophie ++ ++# The following setting is only needed if you use the 'spam' ACL condition ++# of the exiscan-acl patch. It specifies on which host and port the SpamAssassin ++# "spamd" daemon is listening. If you do not use this condition, or you use ++# the default of "127.0.0.1 783", you can omit this option. ++ ++# spamd_address = 127.0.0.1 783 + + # Specify the domain you want to be added to all unqualified addresses + # here. An unqualified address is one that does not contain an "@" character +@@ -342,6 +362,56 @@ + deny message = relay not permitted + + ++# These access control lists are used for content scanning with the exiscan-acl ++# patch. You must also uncomment the entries for acl_smtp_data and acl_smtp_mime ++# (scroll up), otherwise the ACLs will not be used. IMPORTANT: the default entries here ++# should be treated as EXAMPLES. You MUST read the file doc/exiscan-acl-spec.txt ++# to fully understand what you are doing ... ++ ++acl_check_mime: ++ ++ # Decode MIME parts to disk. This will support virus scanners later. ++ warn decode = default ++ ++ # File extension filtering. ++ deny message = Blacklisted file extension detected ++ condition = ${if match \ ++ {${lc:$mime_filename}} \ ++ {\N(\.exe|\.pif|\.bat|\.scr|\.lnk|\.com)$\N} \ ++ {1}{0}} ++ ++ # Reject messages that carry chinese character sets. ++ # WARNING: This is an EXAMPLE. ++ deny message = Sorry, noone speaks chinese here ++ condition = ${if eq{$mime_charset}{gb2312}{1}{0}} ++ ++ accept ++ ++acl_check_content: ++ ++ # Reject virus infested messages. ++ deny message = This message contains malware ($malware_name) ++ malware = * ++ ++ # Always add X-Spam-Score and X-Spam-Report headers, using SA system-wide settings ++ # (user "nobody"), no matter if over threshold or not. ++ warn message = X-Spam-Score: $spam_score ($spam_bar) ++ spam = nobody:true ++ warn message = X-Spam-Report: $spam_report ++ spam = nobody:true ++ ++ # Add X-Spam-Flag if spam is over system-wide threshold ++ warn message = X-Spam-Flag: YES ++ spam = nobody ++ ++ # Reject spam messages with score over 10, using an extra condition. ++ deny message = This message scored $spam_score points. Congratulations! ++ spam = nobody:true ++ condition = ${if >{$spam_score_int}{100}{1}{0}} ++ ++ # finally accept all the rest ++ accept ++ + + ###################################################################### + # ROUTERS CONFIGURATION # +diff -urN exim-4.32-orig/src/deliver.c exim-4.32/src/deliver.c +--- exim-4.32-orig/src/deliver.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/deliver.c Thu Apr 15 13:39:38 2004 +@@ -10,6 +10,9 @@ + + #include "exim.h" + ++#ifdef BRIGHTMAIL ++#include "bmi_spam.h" ++#endif + + /* Data block for keeping track of subprocesses for parallel remote + delivery. */ +@@ -152,6 +155,12 @@ + deliver_domain = addr->domain; + self_hostname = addr->self_hostname; + ++#ifdef BRIGHTMAIL ++bmi_deliver = 1; /* deliver by default */ ++bmi_alt_location = NULL; ++bmi_base64_verdict = NULL; ++#endif ++ + /* If there's only one address we can set everything. */ + + if (addr->next == NULL) +@@ -201,6 +210,18 @@ + deliver_localpart_suffix = addr->parent->suffix; + } + } ++ ++#ifdef BRIGHTMAIL ++ /* Set expansion variables related to Brightmail AntiSpam */ ++ bmi_base64_verdict = bmi_get_base64_verdict(deliver_localpart_orig, deliver_domain_orig); ++ /* get message delivery status (0 - don't deliver | 1 - deliver) */ ++ bmi_deliver = bmi_get_delivery_status(bmi_base64_verdict); ++ /* if message is to be delivered, get eventual alternate location */ ++ if (bmi_deliver == 1) { ++ bmi_alt_location = bmi_get_alt_location(bmi_base64_verdict); ++ }; ++#endif ++ + } + + /* For multiple addresses, don't set local part, and leave the domain and +diff -urN exim-4.32-orig/src/demime.c exim-4.32/src/demime.c +--- exim-4.32-orig/src/demime.c Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/demime.c Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,1276 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++patch. It is NOT part of the standard exim distribution. */ ++ ++/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */ ++/* License: GPL */ ++ ++/* Code for unpacking MIME containers. Called from acl.c. */ ++ ++#include "exim.h" ++#include "demime.h" ++ ++uschar demime_reason_buffer[1024]; ++struct file_extension *file_extensions = NULL; ++ ++int demime(uschar **listptr) { ++ int sep = 0; ++ uschar *list = *listptr; ++ uschar *option; ++ uschar option_buffer[64]; ++ unsigned long long mbox_size; ++ FILE *mbox_file; ++ uschar defer_error_buffer[1024]; ++ int demime_rc = 0; ++ ++ /* reset found_extension variable */ ++ found_extension = NULL; ++ ++ /* try to find 1st option */ ++ if ((option = string_nextinlist(&list, &sep, ++ option_buffer, ++ sizeof(option_buffer))) != NULL) { ++ ++ /* parse 1st option */ ++ if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) { ++ /* explicitly no demimeing */ ++ return FAIL; ++ }; ++ } ++ else { ++ /* no options -> no demimeing */ ++ return FAIL; ++ }; ++ ++ /* make sure the eml mbox file is spooled up */ ++ mbox_file = spool_mbox(&mbox_size); ++ ++ if (mbox_file == NULL) { ++ /* error while spooling */ ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "demime acl condition: error while creating mbox spool file"); ++ return DEFER; ++ }; ++ ++ /* call demimer if not already done earlier */ ++ if (!demime_ok) ++ demime_rc = mime_demux(mbox_file, defer_error_buffer); ++ ++ fclose(mbox_file); ++ ++ if (demime_rc == DEFER) { ++ /* temporary failure (DEFER => DEFER) */ ++ log_write(0, LOG_MAIN, ++ "demime acl condition: %s", defer_error_buffer); ++ return DEFER; ++ }; ++ ++ /* set demime_ok to avoid unpacking again */ ++ demime_ok = 1; ++ ++ /* check for file extensions, if there */ ++ while (option != NULL) { ++ struct file_extension *this_extension = file_extensions; ++ ++ /* Look for the wildcard. If it is found, we always return true. ++ The user must then use a custom condition to evaluate demime_errorlevel */ ++ if (Ustrcmp(option,"*") == 0) { ++ found_extension = NULL; ++ return OK; ++ }; ++ ++ /* loop thru extension list */ ++ while (this_extension != NULL) { ++ if (strcmpic(option, this_extension->file_extension_string) == 0) { ++ /* found one */ ++ found_extension = this_extension->file_extension_string; ++ return OK; ++ }; ++ this_extension = this_extension->next; ++ }; ++ ++ /* grab next extension from option list */ ++ option = string_nextinlist(&list, &sep, ++ option_buffer, ++ sizeof(option_buffer)); ++ }; ++ ++ /* nothing found */ ++ return FAIL; ++} ++ ++ ++/************************************************* ++* unpack TNEF in given directory * ++*************************************************/ ++ ++int mime_unpack_tnef(uschar *directory) { ++ uschar filepath[1024]; ++ int n; ++ struct dirent *entry; ++ DIR *tempdir; ++ ++ /* open the dir */ ++ tempdir = opendir(CS directory); ++ if (tempdir == NULL) { ++ return -2; ++ }; ++ ++ /* loop thru dir */ ++ n = 0; ++ do { ++ entry = readdir(tempdir); ++ /* break on end of list */ ++ if (entry == NULL) break; ++ snprintf(CS filepath,1024,"%s/%s",directory,entry->d_name); ++ if ( (Ustrcmp(entry->d_name,"..") != 0) && (Ustrcmp(entry->d_name,".") != 0) && (Ustrcmp(entry->d_name,"winmail.dat") == 0) ) { ++ TNEF_set_path(CS directory); ++ n = TNEF_main(CS filepath); ++ }; ++ } while (1); ++ ++ closedir(tempdir); ++ return 0; ++} ++ ++ ++/************************************************* ++* small hex_str -> integer conversion function * ++*************************************************/ ++ ++/* needed for quoted-printable ++*/ ++ ++unsigned int mime_hstr_i(uschar *cptr) { ++ unsigned int i, j = 0; ++ ++ while (cptr && *cptr && isxdigit(*cptr)) { ++ i = *cptr++ - '0'; ++ if (9 < i) i -= 7; ++ j <<= 4; ++ j |= (i & 0x0f); ++ } ++ ++ return(j); ++} ++ ++ ++/************************************************* ++* decode quoted-printable chars * ++*************************************************/ ++ ++/* gets called when we hit a = ++ returns: new pointer position ++ result code in c: ++ -2 - decode error ++ -1 - soft line break, no char ++ 0-255 - char to write ++*/ ++ ++uschar *mime_decode_qp(uschar *qp_p,int *c) { ++ uschar hex[] = {0,0,0}; ++ int nan = 0; ++ uschar *initial_pos = qp_p; ++ ++ /* advance one char */ ++ qp_p++; ++ ++ REPEAT_FIRST: ++ if ( (*qp_p == '\t') || (*qp_p == ' ') || (*qp_p == '\r') ) { ++ /* tab or whitespace may follow ++ just ignore it, but remember ++ that this is not a valid hex ++ encoding any more */ ++ nan = 1; ++ qp_p++; ++ goto REPEAT_FIRST; ++ } ++ else if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { ++ /* this is a valid hex char, if nan is unset */ ++ if (nan) { ++ /* this is illegal */ ++ *c = -2; ++ return initial_pos; ++ } ++ else { ++ hex[0] = *qp_p; ++ qp_p++; ++ }; ++ } ++ else if (*qp_p == '\n') { ++ /* hit soft line break already, continue */ ++ *c = -1; ++ return qp_p; ++ } ++ else { ++ /* illegal char here */ ++ *c = -2; ++ return initial_pos; ++ }; ++ ++ if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { ++ if (hex[0] > 0) { ++ hex[1] = *qp_p; ++ /* do hex conversion */ ++ *c = mime_hstr_i(hex); ++ qp_p++; ++ return qp_p; ++ } ++ else { ++ /* huh ? */ ++ *c = -2; ++ return initial_pos; ++ }; ++ } ++ else { ++ /* illegal char */ ++ *c = -2; ++ return initial_pos; ++ }; ++ ++} ++ ++ ++/************************************************* ++* open new dump file * ++*************************************************/ ++ ++/* open new dump file ++ returns: -2 soft error ++ or file #, FILE * in f ++*/ ++ ++int mime_get_dump_file(uschar *extension, FILE **f, uschar *info) { ++ uschar file_name[1024]; ++ int result; ++ unsigned int file_nr; ++ uschar default_extension[] = ".com"; ++ uschar *p; ++ ++ if (extension == NULL) ++ extension = default_extension; ++ ++ /* scan the proposed extension. ++ if it is longer than 4 chars, or ++ contains exotic chars, use the default extension */ ++ ++/* if (Ustrlen(extension) > 4) { ++ extension = default_extension; ++ }; ++*/ ++ ++ p = extension+1; ++ ++ while (*p != 0) { ++ *p = (uschar)tolower((uschar)*p); ++ if ( (*p < 97) || (*p > 122) ) { ++ extension = default_extension; ++ break; ++ }; ++ p++; ++ }; ++ ++ /* find a new file to write to */ ++ file_nr = 0; ++ do { ++ struct stat mystat; ++ ++ snprintf(CS file_name,1024,"%s/scan/%s/%s-%05u%s",spool_directory,message_id,message_id,file_nr,extension); ++ file_nr++; ++ if (file_nr >= MIME_SANITY_MAX_DUMP_FILES) { ++ /* max parts reached */ ++ mime_trigger_error(MIME_ERRORLEVEL_TOO_MANY_PARTS); ++ break; ++ }; ++ result = stat(CS file_name,&mystat); ++ } ++ while(result != -1); ++ ++ *f = fopen(CS file_name,"w+"); ++ if (*f == NULL) { ++ /* cannot open new dump file, disk full ? -> soft error */ ++ snprintf(CS info, 1024,"unable to open dump file"); ++ return -2; ++ }; ++ ++ return file_nr; ++} ++ ++ ++/************************************************* ++* Find a string in a mime header * ++*************************************************/ ++ ++/* Find a string in a mime header, and optionally fill in ++ the value associated with it into *value ++ ++ returns: 0 - nothing found ++ 1 - found param ++ 2 - found param + value ++*/ ++ ++int mime_header_find(uschar *header, uschar *param, uschar **value) { ++ uschar *needle; ++ ++ needle = strstric(header,param,FALSE); ++ if (needle != NULL) { ++ if (value != NULL) { ++ needle += Ustrlen(param); ++ if (*needle == '=') { ++ uschar *value_start; ++ uschar *value_end; ++ ++ value_start = needle + 1; ++ value_end = strstric(value_start,US";",FALSE); ++ if (value_end != NULL) { ++ /* allocate mem for value */ ++ *value = (uschar *)malloc((value_end - value_start)+1); ++ if (*value == NULL) ++ return 0; ++ ++ Ustrncpy(*value,value_start,(value_end - value_start)); ++ (*value)[(value_end - value_start)] = '\0'; ++ return 2; ++ }; ++ }; ++ }; ++ return 1; ++ }; ++ return 0; ++} ++ ++ ++/************************************************* ++* Read a line of MIME input * ++*************************************************/ ++/* returns status code, one of ++ MIME_READ_LINE_EOF 0 ++ MIME_READ_LINE_OK 1 ++ MIME_READ_LINE_OVERFLOW 2 ++ ++ In header mode, the line will be "cooked". ++*/ ++ ++int mime_read_line(FILE *f, int mime_demux_mode, uschar *buffer, long *num_copied) { ++ int c = EOF; ++ int done = 0; ++ int header_value_mode = 0; ++ int header_open_brackets = 0; ++ ++ *num_copied = 0; ++ ++ while(!done) { ++ ++ c = fgetc(f); ++ if (c == EOF) break; ++ ++ /* --------- header mode -------------- */ ++ if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) { ++ ++ /* always skip CRs */ ++ if (c == '\r') continue; ++ ++ if (c == '\n') { ++ if ((*num_copied) > 0) { ++ /* look if next char is '\t' or ' ' */ ++ c = fgetc(f); ++ if (c == EOF) break; ++ if ( (c == '\t') || (c == ' ') ) continue; ++ ungetc(c,f); ++ }; ++ /* end of the header, terminate with ';' */ ++ c = ';'; ++ done = 1; ++ }; ++ ++ /* skip control characters */ ++ if (c < 32) continue; ++ ++ /* skip whitespace + tabs */ ++ if ( (c == ' ') || (c == '\t') ) ++ continue; ++ ++ if (header_value_mode) { ++ /* --------- value mode ----------- */ ++ /* skip quotes */ ++ if (c == '"') continue; ++ ++ /* leave value mode on ';' */ ++ if (c == ';') { ++ header_value_mode = 0; ++ }; ++ /* -------------------------------- */ ++ } ++ else { ++ /* -------- non-value mode -------- */ ++ if (c == '\\') { ++ /* quote next char. can be used ++ to escape brackets. */ ++ c = fgetc(f); ++ if (c == EOF) break; ++ } ++ else if (c == '(') { ++ header_open_brackets++; ++ continue; ++ } ++ else if ((c == ')') && header_open_brackets) { ++ header_open_brackets--; ++ continue; ++ } ++ else if ( (c == '=') && !header_open_brackets ) { ++ /* enter value mode */ ++ header_value_mode = 1; ++ }; ++ ++ /* skip chars while we are in a comment */ ++ if (header_open_brackets > 0) ++ continue; ++ /* -------------------------------- */ ++ }; ++ } ++ /* ------------------------------------ */ ++ else { ++ /* ----------- non-header mode -------- */ ++ /* break on '\n' */ ++ if (c == '\n') ++ done = 1; ++ /* ------------------------------------ */ ++ }; ++ ++ /* copy the char to the buffer */ ++ buffer[*num_copied] = (uschar)c; ++ /* raise counter */ ++ (*num_copied)++; ++ ++ /* break if buffer is full */ ++ if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1) { ++ done = 1; ++ }; ++ } ++ ++ /* 0-terminate */ ++ buffer[*num_copied] = '\0'; ++ ++ if (*num_copied > MIME_SANITY_MAX_LINE_LENGTH-1) ++ return MIME_READ_LINE_OVERFLOW; ++ else ++ if (c == EOF) ++ return MIME_READ_LINE_EOF; ++ else ++ return MIME_READ_LINE_OK; ++} ++ ++ ++/************************************************* ++* Check for a MIME boundary * ++*************************************************/ ++ ++/* returns: 0 - no boundary found ++ 1 - start boundary found ++ 2 - end boundary found ++*/ ++ ++int mime_check_boundary(uschar *line, struct boundary *boundaries) { ++ struct boundary *thisboundary = boundaries; ++ uschar workbuf[MIME_SANITY_MAX_LINE_LENGTH+1]; ++ unsigned int i,j=0; ++ ++ /* check for '--' first */ ++ if (Ustrncmp(line,"--",2) == 0) { ++ ++ /* strip tab and space */ ++ for (i = 2; i < Ustrlen(line); i++) { ++ if ((line[i] != ' ') && (line[i] != '\t')) { ++ workbuf[j] = line[i]; ++ j++; ++ }; ++ }; ++ workbuf[j+1]='\0'; ++ ++ while(thisboundary != NULL) { ++ if (Ustrncmp(workbuf,thisboundary->boundary_string,Ustrlen(thisboundary->boundary_string)) == 0) { ++ if (Ustrncmp(&workbuf[Ustrlen(thisboundary->boundary_string)],"--",2) == 0) { ++ /* final boundary found */ ++ return 2; ++ }; ++ return 1; ++ }; ++ thisboundary = thisboundary->next; ++ }; ++ }; ++ ++ return 0; ++} ++ ++ ++/************************************************* ++* Check for start of a UUENCODE block * ++*************************************************/ ++ ++/* returns 0 for no hit, ++ >0 for hit ++*/ ++ ++int mime_check_uu_start(uschar *line, uschar *uu_file_extension, int *has_tnef) { ++ ++ if ( (strncmpic(line,US"begin ",6) == 0)) { ++ uschar *uu_filename = &line[6]; ++ ++ /* skip perms, if present */ ++ Ustrtoul(&line[6],&uu_filename,10); ++ ++ /* advance one char */ ++ uu_filename++; ++ ++ /* This should be the filename. ++ Check if winmail.dat is present, ++ which indicates TNEF. */ ++ if (strncmpic(uu_filename,US"winmail.dat",11) == 0) { ++ *has_tnef = 1; ++ }; ++ ++ /* reverse to dot if present, ++ copy up to 4 chars for the extension */ ++ if (Ustrrchr(uu_filename,'.') != NULL) ++ uu_filename = Ustrrchr(uu_filename,'.'); ++ ++ return sscanf(CS uu_filename, "%4[.0-9A-Za-z]",CS uu_file_extension); ++ } ++ else { ++ /* nothing found */ ++ return 0; ++ }; ++} ++ ++ ++/************************************************* ++* Decode a uu line * ++*************************************************/ ++ ++/* returns number of decoded bytes ++ -2 for soft errors ++*/ ++ ++int warned_about_uudec_line_sanity_1 = 0; ++int warned_about_uudec_line_sanity_2 = 0; ++long uu_decode_line(uschar *line, uschar **data, long line_len, uschar *info) { ++ uschar *p; ++ long num_decoded = 0; ++ uschar tmp_c; ++ uschar *work; ++ int uu_decoded_line_len, uu_encoded_line_len; ++ ++ /* allocate memory for data and work buffer */ ++ *data = (uschar *)malloc(line_len); ++ if (*data == NULL) { ++ snprintf(CS info, 1024,"unable to allocate %lu bytes",line_len); ++ return -2; ++ }; ++ ++ work = (uschar *)malloc(line_len); ++ if (work == NULL) { ++ snprintf(CS info, 1024,"unable to allocate %lu bytes",line_len); ++ return -2; ++ }; ++ ++ memcpy(work,line,line_len); ++ ++ /* First char is line length ++ This is microsofts way of getting it. Scary. */ ++ if (work[0] < 32) { ++ /* ignore this line */ ++ return 0; ++ } ++ else { ++ uu_decoded_line_len = uudec[work[0]]; ++ }; ++ ++ p = &work[1]; ++ ++ while (*p > 32) { ++ *p = uudec[*p]; ++ p++; ++ }; ++ ++ uu_encoded_line_len = (p - &work[1]); ++ p = &work[1]; ++ ++ /* check that resulting line length is a multiple of 4 */ ++ if ( ( uu_encoded_line_len % 4 ) != 0) { ++ if (!warned_about_uudec_line_sanity_1) { ++ mime_trigger_error(MIME_ERRORLEVEL_UU_MISALIGNED); ++ warned_about_uudec_line_sanity_1 = 1; ++ }; ++ return -1; ++ }; ++ ++ /* check that the line length matches */ ++ if ( ( (((uu_encoded_line_len/4)*3)-2) > uu_decoded_line_len ) || (((uu_encoded_line_len/4)*3) < uu_decoded_line_len) ) { ++ if (!warned_about_uudec_line_sanity_2) { ++ mime_trigger_error(MIME_ERRORLEVEL_UU_LINE_LENGTH); ++ warned_about_uudec_line_sanity_2 = 1; ++ }; ++ return -1; ++ }; ++ ++ while ( ((p - &work[1]) < uu_encoded_line_len) && (num_decoded < uu_decoded_line_len)) { ++ ++ /* byte 0 ---------------------- */ ++ if ((p - &work[1] + 1) >= uu_encoded_line_len) { ++ return 0; ++ } ++ ++ (*data)[num_decoded] = *p; ++ (*data)[num_decoded] <<= 2; ++ ++ tmp_c = *(p+1); ++ tmp_c >>= 4; ++ (*data)[num_decoded] |= tmp_c; ++ ++ num_decoded++; ++ p++; ++ ++ /* byte 1 ---------------------- */ ++ if ((p - &work[1] + 1) >= uu_encoded_line_len) { ++ return 0; ++ } ++ ++ (*data)[num_decoded] = *p; ++ (*data)[num_decoded] <<= 4; ++ ++ tmp_c = *(p+1); ++ tmp_c >>= 2; ++ (*data)[num_decoded] |= tmp_c; ++ ++ num_decoded++; ++ p++; ++ ++ /* byte 2 ---------------------- */ ++ if ((p - &work[1] + 1) >= uu_encoded_line_len) { ++ return 0; ++ } ++ ++ (*data)[num_decoded] = *p; ++ (*data)[num_decoded] <<= 6; ++ ++ (*data)[num_decoded] |= *(p+1); ++ ++ num_decoded++; ++ p+=2; ++ ++ }; ++ ++ return uu_decoded_line_len; ++} ++ ++ ++/************************************************* ++* Decode a b64 or qp line * ++*************************************************/ ++ ++/* returns number of decoded bytes ++ -1 for hard errors ++ -2 for soft errors ++*/ ++ ++int warned_about_b64_line_length = 0; ++int warned_about_b64_line_sanity = 0; ++int warned_about_b64_illegal_char = 0; ++int warned_about_qp_line_sanity = 0; ++long mime_decode_line(int mime_demux_mode,uschar *line, uschar **data, long max_data_len, uschar *info) { ++ uschar *p; ++ long num_decoded = 0; ++ int offset = 0; ++ uschar tmp_c; ++ ++ /* allocate memory for data */ ++ *data = (uschar *)malloc(max_data_len); ++ if (*data == NULL) { ++ snprintf(CS info, 1024,"unable to allocate %lu bytes",max_data_len); ++ return -2; ++ }; ++ ++ if (mime_demux_mode == MIME_DEMUX_MODE_BASE64) { ++ /* ---------------------------------------------- */ ++ ++ /* NULL out trailing '\r' and '\n' chars */ ++ while (Ustrrchr(line,'\r') != NULL) { ++ *(Ustrrchr(line,'\r')) = '\0'; ++ }; ++ while (Ustrrchr(line,'\n') != NULL) { ++ *(Ustrrchr(line,'\n')) = '\0'; ++ }; ++ ++ /* check maximum base 64 line length */ ++ if (Ustrlen(line) > MIME_SANITY_MAX_B64_LINE_LENGTH ) { ++ if (!warned_about_b64_line_length) { ++ mime_trigger_error(MIME_ERRORLEVEL_B64_LINE_LENGTH); ++ warned_about_b64_line_length = 1; ++ }; ++ }; ++ ++ p = line; ++ offset = 0; ++ while (*(p+offset) != '\0') { ++ /* hit illegal char ? */ ++ if (b64[*(p+offset)] == 128) { ++ if (!warned_about_b64_illegal_char) { ++ mime_trigger_error(MIME_ERRORLEVEL_B64_ILLEGAL_CHAR); ++ warned_about_b64_illegal_char = 1; ++ }; ++ offset++; ++ } ++ else { ++ *p = b64[*(p+offset)]; ++ p++; ++ }; ++ }; ++ *p = 255; ++ ++ /* check that resulting line length is a multiple of 4 */ ++ if ( ( (p - &line[0]) % 4 ) != 0) { ++ if (!warned_about_b64_line_sanity) { ++ mime_trigger_error(MIME_ERRORLEVEL_B64_MISALIGNED); ++ warned_about_b64_line_sanity = 1; ++ }; ++ }; ++ ++ /* line is translated, start bit shifting */ ++ p = line; ++ num_decoded = 0; ++ ++ while(*p != 255) { ++ ++ /* byte 0 ---------------------- */ ++ if (*(p+1) == 255) { ++ break; ++ } ++ ++ (*data)[num_decoded] = *p; ++ (*data)[num_decoded] <<= 2; ++ ++ tmp_c = *(p+1); ++ tmp_c >>= 4; ++ (*data)[num_decoded] |= tmp_c; ++ ++ num_decoded++; ++ p++; ++ ++ /* byte 1 ---------------------- */ ++ if (*(p+1) == 255) { ++ break; ++ } ++ ++ (*data)[num_decoded] = *p; ++ (*data)[num_decoded] <<= 4; ++ ++ tmp_c = *(p+1); ++ tmp_c >>= 2; ++ (*data)[num_decoded] |= tmp_c; ++ ++ num_decoded++; ++ p++; ++ ++ /* byte 2 ---------------------- */ ++ if (*(p+1) == 255) { ++ break; ++ } ++ ++ (*data)[num_decoded] = *p; ++ (*data)[num_decoded] <<= 6; ++ ++ (*data)[num_decoded] |= *(p+1); ++ ++ num_decoded++; ++ p+=2; ++ ++ }; ++ return num_decoded; ++ /* ---------------------------------------------- */ ++ } ++ else if (mime_demux_mode == MIME_DEMUX_MODE_QP) { ++ /* ---------------------------------------------- */ ++ p = line; ++ ++ while (*p != 0) { ++ if (*p == '=') { ++ int decode_qp_result; ++ ++ p = mime_decode_qp(p,&decode_qp_result); ++ ++ if (decode_qp_result == -2) { ++ /* Error from decoder. p is unchanged. */ ++ if (!warned_about_qp_line_sanity) { ++ mime_trigger_error(MIME_ERRORLEVEL_QP_ILLEGAL_CHAR); ++ warned_about_qp_line_sanity = 1; ++ }; ++ (*data)[num_decoded] = '='; ++ num_decoded++; ++ p++; ++ } ++ else if (decode_qp_result == -1) { ++ /* End of the line with soft line break. ++ Bail out. */ ++ goto QP_RETURN; ++ } ++ else if (decode_qp_result >= 0) { ++ (*data)[num_decoded] = decode_qp_result; ++ num_decoded++; ++ }; ++ } ++ else { ++ (*data)[num_decoded] = *p; ++ num_decoded++; ++ p++; ++ }; ++ }; ++ QP_RETURN: ++ return num_decoded; ++ /* ---------------------------------------------- */ ++ }; ++ ++ return 0; ++} ++ ++ ++ ++/************************************************* ++* Log demime errors and set mime error level * ++*************************************************/ ++ ++/* This sets the global demime_reason expansion ++variable and the demime_errorlevel gauge. */ ++ ++void mime_trigger_error(int level, uschar *format, ...) { ++ char *f; ++ va_list ap; ++ ++ if( (f = malloc(16384+23)) != NULL ) { ++ /* first log the incident */ ++ sprintf(f,"demime acl condition: "); ++ f+=22; ++ va_start(ap, format); ++ vsnprintf(f, 16383,(char *)format, ap); ++ va_end(ap); ++ f-=22; ++ log_write(0, LOG_MAIN, f); ++ /* then copy to demime_reason_buffer if new ++ level is greater than old level */ ++ if (level > demime_errorlevel) { ++ demime_errorlevel = level; ++ Ustrcpy(demime_reason_buffer, US f); ++ demime_reason = demime_reason_buffer; ++ }; ++ free(f); ++ }; ++} ++ ++/************************************************* ++* Demultiplex MIME stream. * ++*************************************************/ ++ ++/* We can handle BASE64, QUOTED-PRINTABLE, and UUENCODE. ++ UUENCODE does not need to have a proper ++ transfer-encoding header, we detect it with "begin" ++ ++ This function will report human parsable errors in ++ *info. ++ ++ returns DEFER -> soft error (see *info) ++ OK -> EOF hit, all ok ++*/ ++ ++int mime_demux(FILE *f, uschar *info) { ++ int mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; ++ int uu_mode = MIME_UU_MODE_OFF; ++ FILE *mime_dump_file = NULL; ++ FILE *uu_dump_file = NULL; ++ uschar *line; ++ int mime_read_line_status = MIME_READ_LINE_OK; ++ long line_len; ++ struct boundary *boundaries = NULL; ++ struct mime_part mime_part_p; ++ int has_tnef = 0; ++ int has_rfc822 = 0; ++ ++ /* allocate room for our linebuffer */ ++ line = (uschar *)malloc(MIME_SANITY_MAX_LINE_LENGTH); ++ if (line == NULL) { ++ snprintf(CS info, 1024,"unable to allocate %u bytes",MIME_SANITY_MAX_LINE_LENGTH); ++ return DEFER; ++ }; ++ ++ /* clear MIME header structure */ ++ memset(&mime_part_p,0,sizeof(mime_part)); ++ ++ /* ----------------------- start demux loop --------------------- */ ++ while (mime_read_line_status == MIME_READ_LINE_OK) { ++ ++ /* read a line of input. Depending on the mode we are in, ++ the returned format will differ. */ ++ mime_read_line_status = mime_read_line(f,mime_demux_mode,line,&line_len); ++ ++ if (mime_read_line_status == MIME_READ_LINE_OVERFLOW) { ++ mime_trigger_error(MIME_ERRORLEVEL_LONG_LINE); ++ /* despite the error, continue .. */ ++ mime_read_line_status = MIME_READ_LINE_OK; ++ continue; ++ } ++ else if (mime_read_line_status == MIME_READ_LINE_EOF) { ++ break; ++ }; ++ ++ if (mime_demux_mode == MIME_DEMUX_MODE_MIME_HEADERS) { ++ /* -------------- header mode --------------------- */ ++ ++ /* Check for an empty line, which is the end of the headers. ++ In HEADER mode, the line is returned "cooked", with the ++ final '\n' replaced by a ';' */ ++ if (line_len == 1) { ++ int tmp; ++ ++ /* We have reached the end of the headers. Start decoding ++ with the collected settings. */ ++ if (mime_part_p.seen_content_transfer_encoding > 1) { ++ mime_demux_mode = mime_part_p.seen_content_transfer_encoding; ++ } ++ else { ++ /* default to plain mode if no specific encoding type found */ ++ mime_demux_mode = MIME_DEMUX_MODE_PLAIN; ++ }; ++ ++ /* open new dump file */ ++ tmp = mime_get_dump_file(mime_part_p.extension, &mime_dump_file, info); ++ if (tmp < 0) { ++ return DEFER; ++ }; ++ ++ /* clear out mime_part */ ++ memset(&mime_part_p,0,sizeof(mime_part)); ++ } ++ else { ++ /* Another header to check for file extensions, ++ encoding type and boundaries */ ++ if (strncmpic(US"content-type:",line,Ustrlen("content-type:")) == 0) { ++ /* ---------------------------- Content-Type header ------------------------------- */ ++ uschar *value = line; ++ ++ /* check for message/partial MIME type and reject it */ ++ if (mime_header_find(line,US"message/partial",NULL) > 0) ++ mime_trigger_error(MIME_ERRORLEVEL_MESSAGE_PARTIAL); ++ ++ /* check for TNEF content type, remember to unpack TNEF later. */ ++ if (mime_header_find(line,US"application/ms-tnef",NULL) > 0) ++ has_tnef = 1; ++ ++ /* check for message/rfcxxx attachments */ ++ if (mime_header_find(line,US"message/rfc822",NULL) > 0) ++ has_rfc822 = 1; ++ ++ /* find the file extension, but do not fill it in ++ it is already set, since content-disposition has ++ precedence. */ ++ if (mime_part_p.extension == NULL) { ++ if (mime_header_find(line,US"name",&value) == 2) { ++ if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME) ++ mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH); ++ mime_part_p.extension = value; ++ mime_part_p.extension = Ustrrchr(value,'.'); ++ if (mime_part_p.extension == NULL) { ++ /* file without extension, setting ++ NULL will use the default extension later */ ++ mime_part_p.extension = NULL; ++ } ++ else { ++ struct file_extension *this_extension = ++ (struct file_extension *)malloc(sizeof(file_extension)); ++ ++ this_extension->file_extension_string = ++ (uschar *)malloc(Ustrlen(mime_part_p.extension)+1); ++ Ustrcpy(this_extension->file_extension_string, ++ mime_part_p.extension+1); ++ this_extension->next = file_extensions; ++ file_extensions = this_extension; ++ }; ++ }; ++ }; ++ ++ /* find a boundary and add it to the list, if present */ ++ value = line; ++ if (mime_header_find(line,US"boundary",&value) == 2) { ++ struct boundary *thisboundary; ++ ++ if (Ustrlen(value) > MIME_SANITY_MAX_BOUNDARY_LENGTH) { ++ mime_trigger_error(MIME_ERRORLEVEL_BOUNDARY_LENGTH); ++ } ++ else { ++ thisboundary = (struct boundary*)malloc(sizeof(boundary)); ++ thisboundary->next = boundaries; ++ thisboundary->boundary_string = value; ++ boundaries = thisboundary; ++ }; ++ }; ++ ++ if (mime_part_p.seen_content_type == 0) { ++ mime_part_p.seen_content_type = 1; ++ } ++ else { ++ mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); ++ }; ++ /* ---------------------------------------------------------------------------- */ ++ } ++ else if (strncmpic(US"content-transfer-encoding:",line,Ustrlen("content-transfer-encoding:")) == 0) { ++ /* ---------------------------- Content-Transfer-Encoding header -------------- */ ++ ++ if (mime_part_p.seen_content_transfer_encoding == 0) { ++ if (mime_header_find(line,US"base64",NULL) > 0) { ++ mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_BASE64; ++ } ++ else if (mime_header_find(line,US"quoted-printable",NULL) > 0) { ++ mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_QP; ++ } ++ else { ++ mime_part_p.seen_content_transfer_encoding = MIME_DEMUX_MODE_PLAIN; ++ }; ++ } ++ else { ++ mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); ++ }; ++ /* ---------------------------------------------------------------------------- */ ++ } ++ else if (strncmpic(US"content-disposition:",line,Ustrlen("content-disposition:")) == 0) { ++ /* ---------------------------- Content-Disposition header -------------------- */ ++ uschar *value = line; ++ ++ if (mime_part_p.seen_content_disposition == 0) { ++ mime_part_p.seen_content_disposition = 1; ++ ++ if (mime_header_find(line,US"filename",&value) == 2) { ++ if (Ustrlen(value) > MIME_SANITY_MAX_FILENAME) ++ mime_trigger_error(MIME_ERRORLEVEL_FILENAME_LENGTH); ++ mime_part_p.extension = value; ++ mime_part_p.extension = Ustrrchr(value,'.'); ++ if (mime_part_p.extension == NULL) { ++ /* file without extension, setting ++ NULL will use the default extension later */ ++ mime_part_p.extension = NULL; ++ } ++ else { ++ struct file_extension *this_extension = ++ (struct file_extension *)malloc(sizeof(file_extension)); ++ ++ this_extension->file_extension_string = ++ (uschar *)malloc(Ustrlen(mime_part_p.extension)+1); ++ Ustrcpy(this_extension->file_extension_string, ++ mime_part_p.extension+1); ++ this_extension->next = file_extensions; ++ file_extensions = this_extension; ++ }; ++ }; ++ } ++ else { ++ mime_trigger_error(MIME_ERRORLEVEL_DOUBLE_HEADERS); ++ }; ++ /* ---------------------------------------------------------------------------- */ ++ }; ++ }; /* End of header checks */ ++ /* ------------------------------------------------ */ ++ } ++ else { ++ /* -------------- non-header mode ----------------- */ ++ int tmp; ++ ++ if (uu_mode == MIME_UU_MODE_OFF) { ++ uschar uu_file_extension[5]; ++ /* We are not currently decoding UUENCODE ++ Check for possible UUENCODE start tag. */ ++ if (mime_check_uu_start(line,uu_file_extension,&has_tnef)) { ++ /* possible UUENCODING start detected. ++ Set unconfirmed mode first. */ ++ uu_mode = MIME_UU_MODE_UNCONFIRMED; ++ /* open new uu dump file */ ++ tmp = mime_get_dump_file(uu_file_extension, &uu_dump_file, info); ++ if (tmp < 0) { ++ free(line); ++ return DEFER; ++ }; ++ }; ++ } ++ else { ++ uschar *data; ++ long data_len = 0; ++ ++ if (uu_mode == MIME_UU_MODE_UNCONFIRMED) { ++ /* We are in unconfirmed UUENCODE mode. */ ++ ++ data_len = uu_decode_line(line,&data,line_len,info); ++ ++ if (data_len == -2) { ++ /* temp error, turn off uudecode mode */ ++ if (uu_dump_file != NULL) { ++ fclose(uu_dump_file); uu_dump_file = NULL; ++ }; ++ uu_mode = MIME_UU_MODE_OFF; ++ return DEFER; ++ } ++ else if (data_len == -1) { ++ if (uu_dump_file != NULL) { ++ fclose(uu_dump_file); uu_dump_file = NULL; ++ }; ++ uu_mode = MIME_UU_MODE_OFF; ++ data_len = 0; ++ } ++ else if (data_len > 0) { ++ /* we have at least decoded a valid byte ++ turn on confirmed mode */ ++ uu_mode = MIME_UU_MODE_CONFIRMED; ++ }; ++ } ++ else if (uu_mode == MIME_UU_MODE_CONFIRMED) { ++ /* If we are in confirmed UU mode, ++ check for single "end" tag on line */ ++ if ((strncmpic(line,US"end",3) == 0) && (line[3] < 32)) { ++ if (uu_dump_file != NULL) { ++ fclose(uu_dump_file); uu_dump_file = NULL; ++ }; ++ uu_mode = MIME_UU_MODE_OFF; ++ } ++ else { ++ data_len = uu_decode_line(line,&data,line_len,info); ++ if (data_len == -2) { ++ /* temp error, turn off uudecode mode */ ++ if (uu_dump_file != NULL) { ++ fclose(uu_dump_file); uu_dump_file = NULL; ++ }; ++ uu_mode = MIME_UU_MODE_OFF; ++ return DEFER; ++ } ++ else if (data_len == -1) { ++ /* skip this line */ ++ data_len = 0; ++ }; ++ }; ++ }; ++ ++ /* write data to dump file, if available */ ++ if (data_len > 0) { ++ if (fwrite(data,1,data_len,uu_dump_file) < data_len) { ++ /* short write */ ++ snprintf(CS info, 1024,"short write on uudecode dump file"); ++ free(line); ++ return DEFER; ++ }; ++ }; ++ }; ++ ++ if (mime_demux_mode != MIME_DEMUX_MODE_SCANNING) { ++ /* Non-scanning and Non-header mode. That means ++ we are currently decoding data to the dump ++ file. */ ++ ++ /* Check for a known boundary. */ ++ tmp = mime_check_boundary(line,boundaries); ++ if (tmp == 1) { ++ /* We have hit a known start boundary. ++ That will put us back in header mode. */ ++ mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; ++ if (mime_dump_file != NULL) { ++ /* if the attachment was a RFC822 message, recurse into it */ ++ if (has_rfc822) { ++ has_rfc822 = 0; ++ rewind(mime_dump_file); ++ mime_demux(mime_dump_file,info); ++ }; ++ ++ fclose(mime_dump_file); mime_dump_file = NULL; ++ }; ++ } ++ else if (tmp == 2) { ++ /* We have hit a known end boundary. ++ That puts us into scanning mode, which will end when we hit another known start boundary */ ++ mime_demux_mode = MIME_DEMUX_MODE_SCANNING; ++ if (mime_dump_file != NULL) { ++ /* if the attachment was a RFC822 message, recurse into it */ ++ if (has_rfc822) { ++ has_rfc822 = 0; ++ rewind(mime_dump_file); ++ mime_demux(mime_dump_file,info); ++ }; ++ ++ fclose(mime_dump_file); mime_dump_file = NULL; ++ }; ++ } ++ else { ++ uschar *data; ++ long data_len = 0; ++ ++ /* decode the line with the appropriate method */ ++ if (mime_demux_mode == MIME_DEMUX_MODE_PLAIN) { ++ /* in plain mode, just dump the line */ ++ data = line; ++ data_len = line_len; ++ } ++ else if ( (mime_demux_mode == MIME_DEMUX_MODE_QP) || (mime_demux_mode == MIME_DEMUX_MODE_BASE64) ) { ++ data_len = mime_decode_line(mime_demux_mode,line,&data,line_len,info); ++ if (data_len < 0) { ++ /* Error reported from the line decoder. */ ++ data_len = 0; ++ }; ++ }; ++ ++ /* write data to dump file */ ++ if (data_len > 0) { ++ if (fwrite(data,1,data_len,mime_dump_file) < data_len) { ++ /* short write */ ++ snprintf(CS info, 1024,"short write on dump file"); ++ free(line); ++ return DEFER; ++ }; ++ }; ++ ++ }; ++ } ++ else { ++ /* Scanning mode. We end up here after a end boundary. ++ This will usually be at the end of a message or at ++ the end of a MIME container. ++ We need to look for another start boundary to get ++ back into header mode. */ ++ if (mime_check_boundary(line,boundaries) == 1) { ++ mime_demux_mode = MIME_DEMUX_MODE_MIME_HEADERS; ++ }; ++ ++ }; ++ /* ------------------------------------------------ */ ++ }; ++ }; ++ /* ----------------------- end demux loop ----------------------- */ ++ ++ /* close files, they could still be open */ ++ if (mime_dump_file != NULL) ++ fclose(mime_dump_file); ++ if (uu_dump_file != NULL) ++ fclose(uu_dump_file); ++ ++ /* release line buffer */ ++ free(line); ++ ++ /* FIXME: release boundary buffers. ++ Not too much of a problem since ++ this instance of exim is not resident. */ ++ ++ if (has_tnef) { ++ uschar file_name[1024]; ++ /* at least one file could be TNEF encoded. ++ attempt to send all decoded files thru the TNEF decoder */ ++ ++ snprintf(CS file_name,1024,"%s/scan/%s",spool_directory,message_id); ++ mime_unpack_tnef(file_name); ++ }; ++ ++ return 0; ++} ++ +diff -urN exim-4.32-orig/src/demime.h exim-4.32/src/demime.h +--- exim-4.32-orig/src/demime.h Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/demime.h Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,146 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++patch. It is NOT part of the standard exim distribution. */ ++ ++/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */ ++/* License: GPL */ ++ ++/* demime defines */ ++ ++#define MIME_DEMUX_MODE_SCANNING 0 ++#define MIME_DEMUX_MODE_MIME_HEADERS 1 ++#define MIME_DEMUX_MODE_BASE64 2 ++#define MIME_DEMUX_MODE_QP 3 ++#define MIME_DEMUX_MODE_PLAIN 4 ++ ++#define MIME_UU_MODE_OFF 0 ++#define MIME_UU_MODE_UNCONFIRMED 1 ++#define MIME_UU_MODE_CONFIRMED 2 ++ ++#define MIME_MAX_EXTENSION 128 ++ ++#define MIME_READ_LINE_EOF 0 ++#define MIME_READ_LINE_OK 1 ++#define MIME_READ_LINE_OVERFLOW 2 ++ ++#define MIME_SANITY_MAX_LINE_LENGTH 131071 ++#define MIME_SANITY_MAX_FILENAME 512 ++#define MIME_SANITY_MAX_HEADER_OPTION_VALUE 1024 ++#define MIME_SANITY_MAX_B64_LINE_LENGTH 76 ++#define MIME_SANITY_MAX_BOUNDARY_LENGTH 1024 ++#define MIME_SANITY_MAX_DUMP_FILES 1024 ++ ++ ++ ++/* MIME errorlevel settings */ ++ ++#define MIME_ERRORLEVEL_LONG_LINE 3,US"line length in message or single header size exceeds %u bytes",MIME_SANITY_MAX_LINE_LENGTH ++#define MIME_ERRORLEVEL_TOO_MANY_PARTS 3,US"too many MIME parts (max %u)",MIME_SANITY_MAX_DUMP_FILES ++#define MIME_ERRORLEVEL_MESSAGE_PARTIAL 3,US"'message/partial' MIME type" ++#define MIME_ERRORLEVEL_FILENAME_LENGTH 3,US"proposed filename exceeds %u characters",MIME_SANITY_MAX_FILENAME ++#define MIME_ERRORLEVEL_BOUNDARY_LENGTH 3,US"boundary length exceeds %u characters",MIME_SANITY_MAX_BOUNDARY_LENGTH ++#define MIME_ERRORLEVEL_DOUBLE_HEADERS 2,US"double headers (content-type, content-disposition or content-transfer-encoding)" ++#define MIME_ERRORLEVEL_UU_MISALIGNED 1,US"uuencoded line length is not a multiple of 4 characters" ++#define MIME_ERRORLEVEL_UU_LINE_LENGTH 1,US"uuencoded line length does not match advertised number of bytes" ++#define MIME_ERRORLEVEL_B64_LINE_LENGTH 1,US"base64 line length exceeds %u characters",MIME_SANITY_MAX_B64_LINE_LENGTH ++#define MIME_ERRORLEVEL_B64_ILLEGAL_CHAR 2,US"base64 line contains illegal character" ++#define MIME_ERRORLEVEL_B64_MISALIGNED 1,US"base64 line length is not a multiple of 4 characters" ++#define MIME_ERRORLEVEL_QP_ILLEGAL_CHAR 1,US"quoted-printable encoding contains illegal character" ++ ++ ++/* demime structures */ ++ ++typedef struct mime_part { ++ /* true if there was a content-type header */ ++ int seen_content_type; ++ /* true if there was a content-transfer-encoding header ++ contains the encoding type */ ++ int seen_content_transfer_encoding; ++ /* true if there was a content-disposition header */ ++ int seen_content_disposition; ++ /* pointer to a buffer with the proposed file extension */ ++ uschar *extension; ++} mime_part; ++ ++typedef struct boundary { ++ struct boundary *next; ++ uschar *boundary_string; ++} boundary; ++ ++typedef struct file_extension { ++ struct file_extension *next; ++ uschar *file_extension_string; ++} file_extension; ++ ++/* available functions for the TNEF library (tnef.c & tnef.h) */ ++ ++extern int TNEF_main( char *filename ); ++extern int TNEF_set_verbosity( int level ); ++extern int TNEF_set_debug( int level ); ++extern int TNEF_set_syslogging( int level ); ++extern int TNEF_set_stderrlogging( int level ); ++extern int TNEF_set_path( char *path ); ++ ++ ++ ++/* demime.c prototypes */ ++ ++int mime_unpack_tnef(uschar *); ++unsigned int mime_hstr_i(uschar *); ++uschar *mime_decode_qp(uschar *, int *); ++int mime_get_dump_file(uschar *, FILE **, uschar *); ++int mime_header_find(uschar *, uschar *, uschar **); ++int mime_read_line(FILE *, int, uschar *, long *); ++int mime_check_boundary(uschar *, struct boundary *); ++int mime_check_uu_start(uschar *, uschar *, int *); ++long uu_decode_line(uschar *, uschar **, long, uschar *); ++long mime_decode_line(int ,uschar *, uschar **, long, uschar *); ++void mime_trigger_error(int, uschar *, ...); ++int mime_demux(FILE *, uschar *); ++ ++ ++ ++/* BASE64 decoder matrix */ ++static unsigned char b64[256]={ ++/* 0 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++/* 16 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++/* 32 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 62, 128, 128, 128, 63, ++/* 48 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 128, 128, 128, 255, 128, 128, ++/* 64 */ 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, ++/* 80 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 128, 128, 128, 128, 128, ++/* 96 */ 128, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, ++ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 128, 128, 128, 128, 128, ++ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 ++}; ++ ++ ++/* Microsoft-Style uudecode matrix */ ++static unsigned char uudec[256]={ ++/* 0 */ 0, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, ++/* 16 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, ++/* 32 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ++/* 48 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ++/* 64 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, ++/* 80 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, ++/* 96 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ++/* 112 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ++/* 128 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, ++/* 144 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, ++/* 160 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ++/* 176 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ++/* 192 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, ++/* 208 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, ++/* 224 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ++/* 240 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 ++}; ++ +diff -urN exim-4.32-orig/src/exim.c exim-4.32/src/exim.c +--- exim-4.32-orig/src/exim.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/exim.c Thu Apr 15 13:39:38 2004 +@@ -1530,6 +1530,10 @@ + printf("%s\n", CS version_copyright); + version_printed = TRUE; + show_whats_supported(stdout); ++ printf("Contains exiscan-acl patch revision %s (c) Tom Kistner [http://duncanthrax.net/exiscan/]\n", exiscan_version_string); ++#ifdef BRIGHTMAIL ++ printf("Contains Brightmail AntiSpam support via the exiscan-acl patch.\n"); ++#endif + } + + else badarg = TRUE; +diff -urN exim-4.32-orig/src/expand.c exim-4.32/src/expand.c +--- exim-4.32-orig/src/expand.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/expand.c Thu Apr 15 13:42:14 2004 +@@ -288,6 +288,11 @@ + { "authenticated_id", vtype_stringptr, &authenticated_id }, + { "authenticated_sender",vtype_stringptr, &authenticated_sender }, + { "authentication_failed",vtype_int, &authentication_failed }, ++#ifdef BRIGHTMAIL ++ { "bmi_alt_location", vtype_stringptr, &bmi_alt_location }, ++ { "bmi_base64_verdict", vtype_stringptr, &bmi_base64_verdict }, ++ { "bmi_deliver", vtype_int, &bmi_deliver }, ++#endif + { "body_linecount", vtype_int, &body_linecount }, + { "bounce_recipient", vtype_stringptr, &bounce_recipient }, + { "bounce_return_size_limit", vtype_int, &bounce_return_size_limit }, +@@ -295,6 +300,8 @@ + { "caller_uid", vtype_uid, &real_uid }, + { "compile_date", vtype_stringptr, &version_date }, + { "compile_number", vtype_stringptr, &version_cnumber }, ++ { "demime_errorlevel", vtype_int, &demime_errorlevel }, ++ { "demime_reason", vtype_stringptr, &demime_reason }, + { "dnslist_domain", vtype_stringptr, &dnslist_domain }, + { "dnslist_text", vtype_stringptr, &dnslist_text }, + { "dnslist_value", vtype_stringptr, &dnslist_value }, +@@ -303,6 +310,7 @@ + { "exim_gid", vtype_gid, &exim_gid }, + { "exim_path", vtype_stringptr, &exim_path }, + { "exim_uid", vtype_uid, &exim_uid }, ++ { "found_extension", vtype_stringptr, &found_extension }, + { "home", vtype_stringptr, &deliver_home }, + { "host", vtype_stringptr, &deliver_host }, + { "host_address", vtype_stringptr, &deliver_host_address }, +@@ -324,6 +332,7 @@ + { "local_user_uid", vtype_uid, &local_user_uid }, + { "localhost_number", vtype_int, &host_number }, + { "mailstore_basename", vtype_stringptr, &mailstore_basename }, ++ { "malware_name", vtype_stringptr, &malware_name }, + { "message_age", vtype_int, &message_age }, + { "message_body", vtype_msgbody, &message_body }, + { "message_body_end", vtype_msgbody_end, &message_body_end }, +@@ -331,6 +340,20 @@ + { "message_headers", vtype_msgheaders, NULL }, + { "message_id", vtype_stringptr, &message_id }, + { "message_size", vtype_int, &message_size }, ++ { "mime_anomaly_level", vtype_int, &mime_anomaly_level }, ++ { "mime_anomaly_text", vtype_stringptr, &mime_anomaly_text }, ++ { "mime_boundary", vtype_stringptr, &mime_boundary }, ++ { "mime_charset", vtype_stringptr, &mime_charset }, ++ { "mime_content_description", vtype_stringptr, &mime_content_description }, ++ { "mime_content_disposition", vtype_stringptr, &mime_content_disposition }, ++ { "mime_content_id", vtype_stringptr, &mime_content_id }, ++ { "mime_content_transfer_encoding",vtype_stringptr, &mime_content_transfer_encoding }, ++ { "mime_content_type", vtype_stringptr, &mime_content_type }, ++ { "mime_decoded_filename", vtype_stringptr, &mime_decoded_filename }, ++ { "mime_filename", vtype_stringptr, &mime_filename }, ++ { "mime_is_multipart", vtype_int, &mime_is_multipart }, ++ { "mime_is_rfc822", vtype_int, &mime_is_rfc822 }, ++ { "mime_part_count", vtype_int, &mime_part_count }, + { "n0", vtype_filter_int, &filter_n[0] }, + { "n1", vtype_filter_int, &filter_n[1] }, + { "n2", vtype_filter_int, &filter_n[2] }, +@@ -359,6 +382,7 @@ + { "received_protocol", vtype_stringptr, &received_protocol }, + { "recipients", vtype_recipients, NULL }, + { "recipients_count", vtype_int, &recipients_count }, ++ { "regex_match_string", vtype_stringptr, ®ex_match_string }, + { "reply_address", vtype_reply, NULL }, + { "return_path", vtype_stringptr, &return_path }, + { "return_size_limit", vtype_int, &bounce_return_size_limit }, +@@ -386,6 +410,10 @@ + { "sn7", vtype_filter_int, &filter_sn[7] }, + { "sn8", vtype_filter_int, &filter_sn[8] }, + { "sn9", vtype_filter_int, &filter_sn[9] }, ++ { "spam_bar", vtype_stringptr, &spam_bar }, ++ { "spam_report", vtype_stringptr, &spam_report }, ++ { "spam_score", vtype_stringptr, &spam_score }, ++ { "spam_score_int", vtype_stringptr, &spam_score_int }, + { "spool_directory", vtype_stringptr, &spool_directory }, + { "thisaddress", vtype_stringptr, &filter_thisaddress }, + { "tls_certificate_verified", vtype_int, &tls_certificate_verified }, +diff -urN exim-4.32-orig/src/functions.h exim-4.32/src/functions.h +--- exim-4.32-orig/src/functions.h Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/functions.h Thu Apr 15 13:39:38 2004 +@@ -66,6 +66,7 @@ + extern void deliver_set_expansions(address_item *); + extern int deliver_split_address(address_item *); + extern void deliver_succeeded(address_item *); ++extern int demime(uschar **); + extern BOOL directory_make(uschar *, uschar *, int, BOOL); + extern dns_address *dns_address_from_rr(dns_answer *, dns_record *); + extern void dns_build_reverse(uschar *, uschar *); +@@ -118,6 +119,7 @@ + + extern void log_close_all(void); + ++extern int malware(uschar **); + extern int match_address_list(uschar *, BOOL, uschar **, unsigned int *, + int, int); + extern int match_check_list(uschar **, int, tree_node **, unsigned int **, +@@ -131,6 +133,9 @@ + extern void md5_mid(md5 *, const uschar *); + extern void md5_start(md5 *); + extern void millisleep(int); ++extern int mime_acl_check(FILE *f, uschar *, uschar **, uschar **); ++extern int mime_decode(uschar **); ++extern int mime_regex(uschar **); + extern uschar *moan_check_errorcopy(uschar *); + extern BOOL moan_skipped_syntax_errors(uschar *, error_block *, uschar *, + BOOL, uschar *); +@@ -174,6 +179,7 @@ + extern BOOL receive_check_set_sender(uschar *); + extern BOOL receive_msg(BOOL); + extern void receive_swallow_smtp(void); ++extern int regex(uschar **); + extern BOOL regex_match_and_setup(const pcre *, uschar *, int, int); + extern const pcre *regex_must_compile(uschar *, BOOL, BOOL); + extern void retry_add_item(address_item *, uschar *, int); +@@ -233,6 +239,8 @@ + extern BOOL smtp_start_session(void); + extern int smtp_ungetc(int); + extern int smtp_write_command(smtp_outblock *, BOOL, char *, ...); ++extern int spam(uschar **); ++extern FILE *spool_mbox(unsigned long long *); + extern BOOL spool_move_message(uschar *, uschar *, uschar *, uschar *); + extern BOOL spool_open_datafile(uschar *); + extern int spool_open_temp(uschar *); +@@ -284,6 +292,8 @@ + extern tree_node *tree_search(tree_node *, uschar *); + extern void tree_write(tree_node *, FILE *); + ++extern void unspool_mbox(void); ++ + extern int verify_address(address_item *, FILE *, int, int, BOOL *); + extern int verify_check_dnsbl(uschar **); + extern int verify_check_header_address(uschar **, uschar **, int); +diff -urN exim-4.32-orig/src/globals.c exim-4.32/src/globals.c +--- exim-4.32-orig/src/globals.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/globals.c Thu Apr 15 13:39:38 2004 +@@ -160,6 +160,7 @@ + uschar *acl_smtp_helo = NULL; + uschar *acl_smtp_mail = NULL; + uschar *acl_smtp_mailauth = NULL; ++uschar *acl_smtp_mime = NULL; + uschar *acl_smtp_rcpt = NULL; + uschar *acl_smtp_starttls = NULL; + uschar *acl_smtp_vrfy = NULL; +@@ -180,6 +181,7 @@ + US"EHLO or HELO", + US"MAIL", + US"MAILAUTH", ++ US"MIME", + US"RCPT", + US"STARTTLS", + US"VRFY", +@@ -193,6 +195,7 @@ + 550, /* HELO/EHLO */ + 550, /* MAIL */ + 0, /* MAILAUTH; not relevant */ ++ 550, /* MIME */ + 550, /* RCPT */ + 550, /* STARTTLS */ + 252, /* VRFY */ +@@ -295,6 +298,7 @@ + uschar *auth_defer_msg = US"reason not recorded"; + uschar *auth_defer_user_msg = US""; + int auto_thaw = 0; ++uschar *av_scanner = US"sophie:/var/run/sophie"; + + BOOL background_daemon = TRUE; + uschar *base62_chars= +@@ -302,6 +306,14 @@ + uschar *bi_command = NULL; + uschar *big_buffer = NULL; + int big_buffer_size = BIG_BUFFER_SIZE; ++#ifdef BRIGHTMAIL ++uschar *bmi_alt_location = NULL; ++uschar *bmi_base64_verdict = NULL; ++uschar *bmi_config_file = US"/opt/brightmail/etc/brightmail.cfg"; ++int bmi_deliver = 1; ++int bmi_run = 0; ++uschar *bmi_verdicts = NULL; ++#endif + int body_linecount = 0; + uschar *bounce_message_file = NULL; + uschar *bounce_message_text = NULL; +@@ -411,6 +423,9 @@ + BOOL deliver_selectstring_regex = FALSE; + uschar *deliver_selectstring_sender = NULL; + BOOL deliver_selectstring_sender_regex = FALSE; ++int demime_errorlevel = 0; ++int demime_ok = 0; ++uschar *demime_reason = NULL; + BOOL disable_logging = FALSE; + + uschar *dns_again_means_nonexist = NULL; +@@ -440,6 +455,7 @@ + "\0<---------------Space to patch exim_path->"; + uid_t exim_uid = EXIM_UID; + BOOL exim_uid_set = TRUE; /* This uid is always set */ ++uschar *exiscan_version_string = US"??"; + int expand_forbid = 0; + int expand_nlength[EXPAND_MAXN+1]; + int expand_nmax = -1; +@@ -449,12 +465,14 @@ + BOOL extract_addresses_remove_arguments = TRUE; + uschar *extra_local_interfaces = NULL; + ++BOOL fake_reject = FALSE; + int filter_n[FILTER_VARIABLE_COUNT]; + BOOL filter_running = FALSE; + int filter_sn[FILTER_VARIABLE_COUNT]; + uschar *filter_test = NULL; + uschar *filter_thisaddress = NULL; + int finduser_retries = 0; ++uschar *found_extension = NULL; + uid_t fixed_never_users[] = { FIXED_NEVER_USERS }; + uschar *freeze_tell = NULL; + uschar *fudged_queue_times = US""; +@@ -606,6 +624,7 @@ + + macro_item *macros = NULL; + uschar *mailstore_basename = NULL; ++uschar *malware_name = NULL; + int max_username_length = 0; + int message_age = 0; + uschar *message_body = NULL; +@@ -626,6 +645,21 @@ + uschar *message_size_limit = US"50M"; + uschar message_subdir[2] = { 0, 0 }; + uschar *message_reference = NULL; ++uschar *mime_anomaly_level = NULL; ++uschar *mime_anomaly_text = NULL; ++uschar *mime_boundary = NULL; ++uschar *mime_charset = NULL; ++uschar *mime_content_description = NULL; ++uschar *mime_content_disposition = NULL; ++uschar *mime_content_id = NULL; ++uschar *mime_content_transfer_encoding = NULL; ++uschar *mime_content_type = NULL; ++uschar *mime_decoded_filename = NULL; ++uschar *mime_filename = NULL; ++int mime_is_multipart = 0; ++int mime_is_rfc822 = 0; ++int mime_part_count = -1; ++ + + uid_t *never_users = NULL; + +@@ -725,6 +759,7 @@ + const pcre *regex_PIPELINING = NULL; + const pcre *regex_SIZE = NULL; + const pcre *regex_ismsgid = NULL; ++uschar *regex_match_string = NULL; + int remote_delivery_count = 0; + int remote_max_parallel = 2; + uschar *remote_sort_domains = NULL; +@@ -749,6 +784,9 @@ + NULL, /* driver name */ + + NULL, /* address_data */ ++#ifdef BRIGHTMAIL ++ NULL, /* bmi_rule */ ++#endif + NULL, /* cannot_route_message */ + NULL, /* condition */ + NULL, /* current_directory */ +@@ -777,6 +815,11 @@ + NULL, /* transport_name */ + + TRUE, /* address_test */ ++#ifdef BRIGHTMAIL ++ FALSE, /* bmi_deliver_alternate */ ++ FALSE, /* bmi_deliver_default */ ++ FALSE, /* bmi_dont_deliver */ ++#endif + TRUE, /* expn */ + FALSE, /* caseful_local_part */ + FALSE, /* check_local_user */ +@@ -901,6 +944,11 @@ + int smtp_rlr_threshold = INT_MAX; + BOOL smtp_use_pipelining = FALSE; + BOOL smtp_use_size = FALSE; ++uschar *spamd_address = US"127.0.0.1 783"; ++uschar *spam_bar = NULL; ++uschar *spam_report = NULL; ++uschar *spam_score = NULL; ++uschar *spam_score_int = NULL; + BOOL split_spool_directory = FALSE; + uschar *spool_directory = US SPOOL_DIRECTORY + "\0<--------------Space to patch spool_directory->"; +diff -urN exim-4.32-orig/src/globals.h exim-4.32/src/globals.h +--- exim-4.32-orig/src/globals.h Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/globals.h Thu Apr 15 13:39:38 2004 +@@ -102,6 +102,7 @@ + extern uschar *acl_smtp_helo; /* ACL run after HELO/EHLO */ + extern uschar *acl_smtp_mail; /* ACL run after MAIL */ + extern uschar *acl_smtp_mailauth; /* ACL run after MAIL AUTH */ ++extern uschar *acl_smtp_mime; /* ACL run after DATA, before acl_smtp_data, for each MIME part */ + extern uschar *acl_smtp_rcpt; /* ACL run after RCPT */ + extern uschar *acl_smtp_starttls; /* ACL run after STARTTLS */ + extern uschar *acl_smtp_vrfy; /* ACL run after VRFY */ +@@ -136,12 +137,21 @@ + extern uschar *auth_defer_msg; /* Error message for log */ + extern uschar *auth_defer_user_msg; /* Error message for user */ + extern int auto_thaw; /* Auto-thaw interval */ ++extern uschar *av_scanner; /* AntiVirus scanner to use for the malware condition */ + + extern BOOL background_daemon; /* Set FALSE to keep in foreground */ + extern uschar *base62_chars; /* Table of base-62 characters */ + extern uschar *bi_command; /* Command for -bi option */ + extern uschar *big_buffer; /* Used for various temp things */ + extern int big_buffer_size; /* Current size (can expand) */ ++#ifdef BRIGHTMAIL ++extern uschar *bmi_alt_location; /* expansion variable that contains the alternate location for the rcpt (available during routing) */ ++extern uschar *bmi_base64_verdict; /* expansion variable with base-64 encoded verdict string (available during routing) */ ++extern uschar *bmi_config_file; /* Brightmail config file */ ++extern int bmi_deliver; /* Flag that determines if the message should be delivered to the rcpt (available during routing) */ ++extern int bmi_run; /* Flag that determines if message should be run through Brightmail server */ ++extern uschar *bmi_verdicts; /* BASE64-encoded verdicts with recipient lists */ ++#endif + extern int body_linecount; /* Line count in body */ + extern uschar *bounce_message_file; /* Template file */ + extern uschar *bounce_message_text; /* One-liner */ +@@ -219,6 +229,9 @@ + extern BOOL deliver_selectstring_regex; /* String is regex */ + extern uschar *deliver_selectstring_sender; /* For selecting by sender */ + extern BOOL deliver_selectstring_sender_regex; /* String is regex */ ++extern int demime_errorlevel; /* Severity of MIME error */ ++extern int demime_ok; /* Nonzero if message has been demimed */ ++extern uschar *demime_reason; /* Reason for broken MIME container */ + extern BOOL disable_logging; /* Disables log writing when TRUE */ + + extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */ +@@ -248,6 +261,7 @@ + extern uschar *exim_path; /* Path to exec exim */ + extern uid_t exim_uid; /* Non-root uid for exim */ + extern BOOL exim_uid_set; /* TRUE if exim_uid set */ ++extern uschar *exiscan_version_string; /* Exiscan version string */ + extern int expand_forbid; /* RDO flags for forbidding things */ + extern int expand_nlength[]; /* Lengths of numbered strings */ + extern int expand_nmax; /* Max numerical value */ +@@ -256,6 +270,7 @@ + extern BOOL extract_addresses_remove_arguments; /* Controls -t behaviour */ + extern uschar *extra_local_interfaces; /* Local, non-listen interfaces */ + ++extern BOOL fake_reject; /* TRUE if fake reject is to be given */ + extern int filter_n[FILTER_VARIABLE_COUNT]; /* filter variables */ + extern BOOL filter_running; /* TRUE while running a filter */ + extern int filter_sn[FILTER_VARIABLE_COUNT]; /* variables set by system filter */ +@@ -263,6 +278,7 @@ + extern uschar *filter_thisaddress; /* For address looping */ + extern int finduser_retries; /* Retry count for getpwnam() */ + extern uid_t fixed_never_users[]; /* Can't be overridden */ ++extern uschar *found_extension; /* demime acl condition: file extension found */ + extern uschar *freeze_tell; /* Message on (some) freezings */ + extern uschar *fudged_queue_times; /* For use in test harness */ + +@@ -341,6 +357,7 @@ + + extern macro_item *macros; /* Configuration macros */ + extern uschar *mailstore_basename; /* For mailstore deliveries */ ++extern uschar *malware_name; /* Name of virus or malware ("W32/Klez-H") */ + extern int max_username_length; /* For systems with broken getpwnam() */ + extern int message_age; /* In seconds */ + extern uschar *message_body; /* Start of message body for filter */ +@@ -360,6 +377,21 @@ + extern uschar *message_size_limit; /* As it says */ + extern uschar message_subdir[]; /* Subdirectory for messages */ + extern uschar *message_reference; /* Reference for error messages */ ++extern uschar *mime_anomaly_level; ++extern uschar *mime_anomaly_text; ++extern uschar *mime_boundary; ++extern uschar *mime_charset; ++extern uschar *mime_content_description; ++extern uschar *mime_content_disposition; ++extern uschar *mime_content_id; ++extern uschar *mime_content_transfer_encoding; ++extern uschar *mime_content_type; ++extern uschar *mime_decoded_filename; ++extern uschar *mime_filename; ++extern int mime_is_multipart; ++extern int mime_is_rfc822; ++extern int mime_part_count; ++ + + extern uid_t *never_users; /* List of uids never to be used */ + +@@ -444,6 +476,7 @@ + extern const pcre *regex_PIPELINING; /* For recognizing PIPELINING */ + extern const pcre *regex_SIZE; /* For recognizing SIZE settings */ + extern const pcre *regex_ismsgid; /* Compiled r.e. for message it */ ++extern uschar *regex_match_string; /* regex that matched a line (regex ACL condition) */ + extern int remote_delivery_count; /* Number of remote addresses */ + extern int remote_max_parallel; /* Maximum parallel delivery */ + extern uschar *remote_sort_domains; /* Remote domain sorting order */ +@@ -532,6 +565,11 @@ + extern BOOL smtp_use_pipelining; /* Global for passed connections */ + extern BOOL smtp_use_size; /* Global for passed connections */ + extern BOOL split_spool_directory; /* TRUE to use multiple subdirs */ ++extern uschar *spamd_address; /* address for the spamassassin daemon */ ++extern uschar *spam_bar; /* the spam "bar" (textual representation of spam_score) */ ++extern uschar *spam_report; /* the spamd report (multiline) */ ++extern uschar *spam_score; /* the spam score (float) */ ++extern uschar *spam_score_int; /* spam_score * 10 (int) */ + extern uschar *spool_directory; /* Name of spool directory */ + extern int string_datestamp_offset;/* After insertion by string_format */ + extern BOOL strip_excess_angle_brackets; /* Surrounding route-addrs */ +diff -urN exim-4.32-orig/src/macros.h exim-4.32/src/macros.h +--- exim-4.32-orig/src/macros.h Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/macros.h Thu Apr 15 13:39:38 2004 +@@ -699,7 +699,7 @@ + + enum { ACL_WHERE_AUTH, ACL_WHERE_CONNECT, ACL_WHERE_DATA, ACL_WHERE_ETRN, + ACL_WHERE_EXPN, ACL_WHERE_HELO, ACL_WHERE_MAIL, +- ACL_WHERE_MAILAUTH, ACL_WHERE_RCPT, ++ ACL_WHERE_MAILAUTH, ACL_WHERE_MIME, ACL_WHERE_RCPT, + ACL_WHERE_STARTTLS, ACL_WHERE_VRFY, ACL_WHERE_NOTSMTP }; + + /* Situations for spool_write_header() */ +diff -urN exim-4.32-orig/src/malware.c exim-4.32/src/malware.c +--- exim-4.32-orig/src/malware.c Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/malware.c Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,1181 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++patch. It is NOT part of the standard exim distribution. */ ++ ++/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */ ++/* License: GPL */ ++ ++/* Code for calling virus (malware) scanners. Called from acl.c. */ ++ ++#include "exim.h" ++ ++/* declaration of private routines */ ++int mksd_scan_packed(int sock); ++int mksd_scan_unpacked(int sock, int maxproc); ++ ++/* SHUT_WR seems to be undefined on Unixware ? */ ++#ifndef SHUT_WR ++#define SHUT_WR 1 ++#endif ++ ++#define DRWEBD_SCAN_CMD 0x0001 ++#define DRWEBD_RETURN_VIRUSES 0x0001 ++ ++/* Routine to check whether a system is big- or litte-endian. ++ Ripped from http://www.faqs.org/faqs/graphics/fileformats-faq/part4/section-7.html ++ Needed for proper kavdaemon implementation. Sigh. */ ++#define BIG_MY_ENDIAN 0 ++#define LITTLE_MY_ENDIAN 1 ++int test_byte_order(void); ++int test_byte_order() { ++ short int word = 0x0001; ++ char *byte = (char *) &word; ++ return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN); ++} ++ ++uschar malware_name_buffer[256]; ++int malware_ok = 0; ++ ++int malware(uschar **listptr) { ++ int sep = 0; ++ uschar *list = *listptr; ++ uschar *av_scanner_work = av_scanner; ++ uschar *scanner_name; ++ uschar scanner_name_buffer[16]; ++ uschar *malware_regex; ++ uschar malware_regex_buffer[64]; ++ uschar malware_regex_default[] = ".+"; ++ unsigned long long mbox_size; ++ FILE *mbox_file; ++ int roffset; ++ const pcre *re; ++ const uschar *rerror; ++ ++ /* make sure the eml mbox file is spooled up */ ++ mbox_file = spool_mbox(&mbox_size); ++ if (mbox_file == NULL) { ++ /* error while spooling */ ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: error while creating mbox spool file"); ++ return DEFER; ++ }; ++ /* none of our current scanners need the mbox ++ file as a stream, so we can close it right away */ ++ fclose(mbox_file); ++ ++ /* extract the malware regex to match against from the option list */ ++ if ((malware_regex = string_nextinlist(&list, &sep, ++ malware_regex_buffer, ++ sizeof(malware_regex_buffer))) != NULL) { ++ ++ /* parse 1st option */ ++ if ( (strcmpic(malware_regex,US"false") == 0) || ++ (Ustrcmp(malware_regex,"0") == 0) ) { ++ /* explicitly no matching */ ++ return FAIL; ++ }; ++ ++ /* special cases (match anything except empty) */ ++ if ( (strcmpic(malware_regex,US"true") == 0) || ++ (Ustrcmp(malware_regex,"*") == 0) || ++ (Ustrcmp(malware_regex,"1") == 0) ) { ++ malware_regex = malware_regex_default; ++ }; ++ } ++ else { ++ /* empty means "don't match anything" */ ++ return FAIL; ++ }; ++ ++ /* compile the regex, see if it works */ ++ re = pcre_compile(CS malware_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); ++ if (re == NULL) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: regular expression error in '%s': %s at offset %d", malware_regex, rerror, roffset); ++ return DEFER; ++ }; ++ ++ /* Do not scan twice. */ ++ if (malware_ok == 0) { ++ ++ /* find the scanner type from the av_scanner option */ ++ if ((scanner_name = string_nextinlist(&av_scanner_work, &sep, ++ scanner_name_buffer, ++ sizeof(scanner_name_buffer))) == NULL) { ++ /* no scanner given */ ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: av_scanner configuration variable is empty"); ++ return DEFER; ++ }; ++ ++ /* "drweb" scanner type ----------------------------------------------- */ ++ /* v0.1 - added support for tcp sockets */ ++ /* v0.0 - initial release -- support for unix sockets */ ++ if (strcmpic(scanner_name,US"drweb") == 0) { ++ uschar *drweb_options; ++ uschar drweb_options_buffer[1024]; ++ uschar drweb_options_default[] = "/usr/local/drweb/run/drwebd.sock"; ++ struct sockaddr_un server; ++ int sock, port, result, ovector[30]; ++ unsigned int fsize; ++ uschar tmpbuf[1024], *drweb_fbuf; ++ uschar scanrequest[1024]; ++ uschar drweb_match_string[128]; ++ int drweb_rc, drweb_cmd, drweb_flags = 0x0000, drweb_fd, ++ drweb_vnum, drweb_slen, drweb_fin = 0x0000; ++ unsigned long bread; ++ uschar hostname[256]; ++ struct hostent *he; ++ struct in_addr in; ++ pcre *drweb_re; ++ ++ if ((drweb_options = string_nextinlist(&av_scanner_work, &sep, ++ drweb_options_buffer, sizeof(drweb_options_buffer))) == NULL) { ++ /* no options supplied, use default options */ ++ drweb_options = drweb_options_default; ++ }; ++ ++ if (*drweb_options != '/') { ++ ++ /* extract host and port part */ ++ if( sscanf(CS drweb_options, "%s %u", hostname, &port) != 2 ) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: invalid socket '%s'", drweb_options); ++ return DEFER; ++ } ++ ++ /* Lookup the host */ ++ if((he = gethostbyname(CS hostname)) == 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: failed to lookup host '%s'", hostname); ++ return DEFER; ++ } ++ ++ in = *(struct in_addr *) he->h_addr_list[0]; ++ ++ /* Open the drwebd TCP socket */ ++ if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: unable to acquire socket (%s)", ++ strerror(errno)); ++ return DEFER; ++ } ++ ++ if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: connection to %s, port %u failed (%s)", ++ inet_ntoa(in), port, strerror(errno)); ++ return DEFER; ++ } ++ ++ /* prepare variables */ ++ drweb_cmd = htonl(DRWEBD_SCAN_CMD); ++ drweb_flags = htonl(DRWEBD_RETURN_VIRUSES); ++ snprintf(CS scanrequest, 1024,CS"%s/scan/%s/%s.eml", ++ spool_directory, message_id, message_id); ++ ++ /* calc file size */ ++ drweb_fd = open(CS scanrequest, O_RDONLY); ++ if (drweb_fd == -1) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: can't open spool file %s: %s", ++ scanrequest, strerror(errno)); ++ return DEFER; ++ } ++ fsize = lseek(drweb_fd, 0, SEEK_END); ++ if (fsize == -1) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: can't seek spool file %s: %s", ++ scanrequest, strerror(errno)); ++ return DEFER; ++ } ++ drweb_slen = htonl(fsize); ++ lseek(drweb_fd, 0, SEEK_SET); ++ ++ /* send scan request */ ++ if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || ++ (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || ++ (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) || ++ (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0)) { ++ close(sock); ++ close(drweb_fd); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options); ++ return DEFER; ++ } ++ ++ drweb_fbuf = (uschar *) malloc (fsize); ++ if (!drweb_fbuf) { ++ close(sock); ++ close(drweb_fd); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: unable to allocate memory %u for file (%s)", ++ fsize, scanrequest); ++ return DEFER; ++ } ++ ++ result = read (drweb_fd, drweb_fbuf, fsize); ++ if (result == -1) { ++ close(sock); ++ close(drweb_fd); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: can't read spool file %s: %s", ++ scanrequest, strerror(errno)); ++ return DEFER; ++ } ++ ++ /* send file body to socket */ ++ if (send(sock, drweb_fbuf, fsize, 0) < 0) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: unable to send file body to socket (%s)", drweb_options); ++ return DEFER; ++ } ++ close(drweb_fd); ++ free(drweb_fbuf); ++ } ++ else { ++ /* open the drwebd UNIX socket */ ++ sock = socket(AF_UNIX, SOCK_STREAM, 0); ++ if (sock < 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: can't open UNIX socket"); ++ return DEFER; ++ } ++ server.sun_family = AF_UNIX; ++ Ustrcpy(server.sun_path, drweb_options); ++ if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: unable to connect to socket (%s). errno=%d", drweb_options, errno); ++ return DEFER; ++ } ++ ++ /* prepare variables */ ++ drweb_cmd = htonl(DRWEBD_SCAN_CMD); ++ drweb_flags = htonl(DRWEBD_RETURN_VIRUSES); ++ snprintf(CS scanrequest, 1024,CS"%s/scan/%s/%s.eml", spool_directory, message_id, message_id); ++ drweb_slen = htonl(Ustrlen(scanrequest)); ++ ++ /* send scan request */ ++ if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) || ++ (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) || ++ (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) || ++ (send(sock, scanrequest, Ustrlen(scanrequest), 0) < 0) || ++ (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0)) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: unable to send commands to socket (%s)", drweb_options); ++ return DEFER; ++ } ++ } ++ ++ /* wait for result */ ++ if ((bread = recv(sock, &drweb_rc, sizeof(drweb_rc), 0) != sizeof(drweb_rc))) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: unable to read return code"); ++ return DEFER; ++ } ++ drweb_rc = ntohl(drweb_rc); ++ ++ if ((bread = recv(sock, &drweb_vnum, sizeof(drweb_vnum), 0) != sizeof(drweb_vnum))) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: unable to read the number of viruses"); ++ return DEFER; ++ } ++ drweb_vnum = ntohl(drweb_vnum); ++ ++ /* "virus(es) found" if virus number is > 0 */ ++ if (drweb_vnum) ++ { ++ int i; ++ uschar pre_malware_nb[256]; ++ ++ malware_name = malware_name_buffer; ++ ++ /* setup default virus name */ ++ Ustrcpy(malware_name_buffer,"unknown"); ++ ++ /* read and concatenate virus names into one string */ ++ for (i=0;i<drweb_vnum;i++) ++ { ++ /* read the size of report */ ++ if ((bread = recv(sock, &drweb_slen, sizeof(drweb_slen), 0) != sizeof(drweb_slen))) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: cannot read report size"); ++ return DEFER; ++ }; ++ drweb_slen = ntohl(drweb_slen); ++ ++ /* read report body */ ++ if ((bread = recv(sock, tmpbuf, drweb_slen, 0)) != drweb_slen) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: drweb: cannot read report string"); ++ return DEFER; ++ }; ++ tmpbuf[drweb_slen] = '\0'; ++ ++ /* set up match regex, depends on retcode */ ++ Ustrcpy(drweb_match_string, "infected\\swith\\s*(.+?)$"); ++ ++ drweb_re = pcre_compile( CS drweb_match_string, ++ PCRE_COPT, ++ (const char **)&rerror, ++ &roffset, ++ NULL ); ++ ++ /* try matcher on the line, grab substring */ ++ result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, ovector, 30); ++ if (result >= 2) { ++ pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS pre_malware_nb, 255); ++ } ++ /* the first name we just copy to malware_name */ ++ if (i==0) ++ Ustrcpy(CS malware_name_buffer, CS pre_malware_nb); ++ else { ++ /* concatenate each new virus name to previous */ ++ int slen = Ustrlen(malware_name_buffer); ++ if (slen < (slen+Ustrlen(pre_malware_nb))) { ++ Ustrcat(malware_name_buffer, "/"); ++ Ustrcat(malware_name_buffer, pre_malware_nb); ++ } ++ } ++ } ++ } ++ else { ++ /* no virus found */ ++ malware_name = NULL; ++ }; ++ close(sock); ++ } ++ /* ----------------------------------------------------------------------- */ ++ ++ /* "kavdaemon" scanner type ------------------------------------------------ */ ++ else if (strcmpic(scanner_name,US"kavdaemon") == 0) { ++ uschar *kav_options; ++ uschar kav_options_buffer[1024]; ++ uschar kav_options_default[] = "/var/run/AvpCtl"; ++ struct sockaddr_un server; ++ int sock; ++ time_t t; ++ uschar tmpbuf[1024]; ++ uschar scanrequest[1024]; ++ uschar kav_match_string[128]; ++ int kav_rc; ++ unsigned long kav_reportlen, bread; ++ pcre *kav_re; ++ ++ if ((kav_options = string_nextinlist(&av_scanner_work, &sep, ++ kav_options_buffer, ++ sizeof(kav_options_buffer))) == NULL) { ++ /* no options supplied, use default options */ ++ kav_options = kav_options_default; ++ }; ++ ++ /* open the kavdaemon socket */ ++ sock = socket(AF_UNIX, SOCK_STREAM, 0); ++ if (sock < 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: can't open UNIX socket."); ++ return DEFER; ++ } ++ server.sun_family = AF_UNIX; ++ Ustrcpy(server.sun_path, kav_options); ++ if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: unable to connect to kavdaemon UNIX socket (%s). errno=%d", kav_options, errno); ++ return DEFER; ++ } ++ ++ /* get current date and time, build scan request */ ++ time(&t); ++ strftime(CS tmpbuf, sizeof(tmpbuf), "<0>%d %b %H:%M:%S:%%s/scan/%%s", localtime(&t)); ++ snprintf(CS scanrequest, 1024,CS tmpbuf, spool_directory, message_id); ++ ++ /* send scan request */ ++ if (send(sock, scanrequest, Ustrlen(scanrequest)+1, 0) < 0) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: unable to write to kavdaemon UNIX socket (%s)", kav_options); ++ return DEFER; ++ } ++ ++ /* wait for result */ ++ if ((bread = recv(sock, tmpbuf, 2, 0) != 2)) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: unable to read 2 bytes from kavdaemon socket."); ++ return DEFER; ++ } ++ ++ /* get errorcode from one nibble */ ++ if (test_byte_order() == LITTLE_MY_ENDIAN) { ++ kav_rc = tmpbuf[0] & 0x0F; ++ } ++ else { ++ kav_rc = tmpbuf[1] & 0x0F; ++ }; ++ ++ /* improper kavdaemon configuration */ ++ if ( (kav_rc == 5) || (kav_rc == 6) ) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: please reconfigure kavdaemon to NOT disinfect or remove infected files."); ++ return DEFER; ++ }; ++ ++ if (kav_rc == 1) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: kavdaemon reported 'scanning not completed' (code 1)."); ++ return DEFER; ++ }; ++ ++ if (kav_rc == 7) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: kavdaemon reported 'kavdaemon damaged' (code 7)."); ++ return DEFER; ++ }; ++ ++ /* code 8 is not handled, since it is ambigous. It appears mostly on ++ bounces where part of a file has been cut off */ ++ ++ /* "virus found" return codes (2-4) */ ++ if ((kav_rc > 1) && (kav_rc < 5)) { ++ int report_flag = 0; ++ ++ /* setup default virus name */ ++ Ustrcpy(malware_name_buffer,"unknown"); ++ malware_name = malware_name_buffer; ++ ++ if (test_byte_order() == LITTLE_MY_ENDIAN) { ++ report_flag = tmpbuf[1]; ++ } ++ else { ++ report_flag = tmpbuf[0]; ++ }; ++ ++ /* read the report, if available */ ++ if( report_flag == 1 ) { ++ /* read report size */ ++ if ((bread = recv(sock, &kav_reportlen, 4, 0)) != 4) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: cannot read report size from kavdaemon"); ++ return DEFER; ++ }; ++ ++ /* it's possible that avp returns av_buffer[1] == 1 but the ++ reportsize is 0 (!?) */ ++ if (kav_reportlen > 0) { ++ /* set up match regex, depends on retcode */ ++ if( kav_rc == 3 ) ++ Ustrcpy(kav_match_string, "suspicion:\\s*(.+?)\\s*$"); ++ else ++ Ustrcpy(kav_match_string, "infected:\\s*(.+?)\\s*$"); ++ ++ kav_re = pcre_compile( CS kav_match_string, ++ PCRE_COPT, ++ (const char **)&rerror, ++ &roffset, ++ NULL ); ++ ++ /* read report, linewise */ ++ while (kav_reportlen > 0) { ++ int result = 0; ++ int ovector[30]; ++ ++ bread = 0; ++ while ( recv(sock, &tmpbuf[bread], 1, 0) == 1 ) { ++ kav_reportlen--; ++ if ( (tmpbuf[bread] == '\n') || (bread > 1021) ) break; ++ bread++; ++ }; ++ bread++; ++ tmpbuf[bread] = '\0'; ++ ++ /* try matcher on the line, grab substring */ ++ result = pcre_exec(kav_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0, ovector, 30); ++ if (result >= 2) { ++ pcre_copy_substring(CS tmpbuf, ovector, result, 1, CS malware_name_buffer, 255); ++ break; ++ }; ++ }; ++ }; ++ }; ++ } ++ else { ++ /* no virus found */ ++ malware_name = NULL; ++ }; ++ ++ close(sock); ++ } ++ /* ----------------------------------------------------------------------- */ ++ ++ ++ /* "cmdline" scanner type ------------------------------------------------ */ ++ else if (strcmpic(scanner_name,US"cmdline") == 0) { ++ uschar *cmdline_scanner; ++ uschar cmdline_scanner_buffer[1024]; ++ uschar *cmdline_trigger; ++ uschar cmdline_trigger_buffer[1024]; ++ const pcre *cmdline_trigger_re; ++ uschar *cmdline_regex; ++ uschar cmdline_regex_buffer[1024]; ++ const pcre *cmdline_regex_re; ++ uschar file_name[1024]; ++ uschar commandline[1024]; ++ FILE *scanner_out = NULL; ++ FILE *scanner_record = NULL; ++ uschar linebuffer[32767]; ++ int trigger = 0; ++ int result; ++ int ovector[30]; ++ ++ /* find scanner command line */ ++ if ((cmdline_scanner = string_nextinlist(&av_scanner_work, &sep, ++ cmdline_scanner_buffer, ++ sizeof(cmdline_scanner_buffer))) == NULL) { ++ /* no command line supplied */ ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: missing commandline specification for cmdline scanner type."); ++ return DEFER; ++ }; ++ ++ /* find scanner output trigger */ ++ if ((cmdline_trigger = string_nextinlist(&av_scanner_work, &sep, ++ cmdline_trigger_buffer, ++ sizeof(cmdline_trigger_buffer))) == NULL) { ++ /* no trigger regex supplied */ ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: missing trigger specification for cmdline scanner type."); ++ return DEFER; ++ }; ++ ++ /* precompile trigger regex */ ++ cmdline_trigger_re = pcre_compile(CS cmdline_trigger, PCRE_COPT, (const char **)&rerror, &roffset, NULL); ++ if (cmdline_trigger_re == NULL) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_trigger_re, rerror, roffset); ++ return DEFER; ++ }; ++ ++ /* find scanner name regex */ ++ if ((cmdline_regex = string_nextinlist(&av_scanner_work, &sep, ++ cmdline_regex_buffer, ++ sizeof(cmdline_regex_buffer))) == NULL) { ++ /* no name regex supplied */ ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: missing virus name regex specification for cmdline scanner type."); ++ return DEFER; ++ }; ++ ++ /* precompile name regex */ ++ cmdline_regex_re = pcre_compile(CS cmdline_regex, PCRE_COPT, (const char **)&rerror, &roffset, NULL); ++ if (cmdline_regex_re == NULL) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: regular expression error in '%s': %s at offset %d", cmdline_regex_re, rerror, roffset); ++ return DEFER; ++ }; ++ ++ /* prepare scanner call */ ++ snprintf(CS file_name,1024,"%s/scan/%s", spool_directory, message_id); ++ snprintf(CS commandline,1024, CS cmdline_scanner,file_name); ++ /* redirect STDERR too */ ++ Ustrcat(commandline," 2>&1"); ++ ++ scanner_out = popen(CS commandline,"r"); ++ if (scanner_out == NULL) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: calling cmdline scanner (%s) failed: %s.", commandline, strerror(errno)); ++ return DEFER; ++ }; ++ ++ snprintf(CS file_name,1024,"%s/scan/%s/%s_scanner_output", spool_directory, message_id, message_id); ++ scanner_record = fopen(CS file_name,"w"); ++ ++ if (scanner_record == NULL) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: opening scanner output file (%s) failed: %s.", file_name, strerror(errno)); ++ pclose(scanner_out); ++ return DEFER; ++ }; ++ ++ /* look for trigger while recording output */ ++ while(fgets(CS linebuffer,32767,scanner_out) != NULL) { ++ if ( Ustrlen(linebuffer) > fwrite(linebuffer, 1, Ustrlen(linebuffer), scanner_record) ) { ++ /* short write */ ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: short write on scanner output file (%s).", file_name); ++ pclose(scanner_out); ++ return DEFER; ++ }; ++ /* try trigger match */ ++ if (!trigger && regex_match_and_setup(cmdline_trigger_re, linebuffer, 0, -1)) ++ trigger = 1; ++ }; ++ ++ fclose(scanner_record); ++ pclose(scanner_out); ++ ++ if (trigger) { ++ /* setup default virus name */ ++ Ustrcpy(malware_name_buffer,"unknown"); ++ malware_name = malware_name_buffer; ++ ++ /* re-open the scanner output file, look for name match */ ++ scanner_record = fopen(CS file_name,"r"); ++ while(fgets(CS linebuffer,32767,scanner_record) != NULL) { ++ /* try match */ ++ result = pcre_exec(cmdline_regex_re, NULL, CS linebuffer, Ustrlen(linebuffer), 0, 0, ovector, 30); ++ if (result >= 2) { ++ pcre_copy_substring(CS linebuffer, ovector, result, 1, CS malware_name_buffer, 255); ++ }; ++ }; ++ fclose(scanner_record); ++ } ++ else { ++ /* no virus found */ ++ malware_name = NULL; ++ }; ++ } ++ /* ----------------------------------------------------------------------- */ ++ ++ ++ /* "sophie" scanner type ------------------------------------------------- */ ++ else if (strcmpic(scanner_name,US"sophie") == 0) { ++ uschar *sophie_options; ++ uschar sophie_options_buffer[1024]; ++ uschar sophie_options_default[] = "/var/run/sophie"; ++ int bread = 0; ++ struct sockaddr_un server; ++ int sock; ++ uschar file_name[1024]; ++ uschar av_buffer[1024]; ++ ++ if ((sophie_options = string_nextinlist(&av_scanner_work, &sep, ++ sophie_options_buffer, ++ sizeof(sophie_options_buffer))) == NULL) { ++ /* no options supplied, use default options */ ++ sophie_options = sophie_options_default; ++ }; ++ ++ /* open the sophie socket */ ++ sock = socket(AF_UNIX, SOCK_STREAM, 0); ++ if (sock < 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: can't open UNIX socket."); ++ return DEFER; ++ } ++ server.sun_family = AF_UNIX; ++ Ustrcpy(server.sun_path, sophie_options); ++ if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: unable to connect to sophie UNIX socket (%s). errno=%d", sophie_options, errno); ++ return DEFER; ++ } ++ ++ /* pass the scan directory to sophie */ ++ snprintf(CS file_name,1024,"%s/scan/%s", spool_directory, message_id); ++ if (write(sock, file_name, Ustrlen(file_name)) < 0) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: unable to write to sophie UNIX socket (%s)", sophie_options); ++ return DEFER; ++ }; ++ ++ write(sock, "\n", 1); ++ ++ /* wait for result */ ++ memset(av_buffer, 0, sizeof(av_buffer)); ++ if ((!(bread = read(sock, av_buffer, sizeof(av_buffer))) > 0)) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: unable to read from sophie UNIX socket (%s)", sophie_options); ++ return DEFER; ++ }; ++ ++ close(sock); ++ ++ /* infected ? */ ++ if (av_buffer[0] == '1') { ++ if (Ustrchr(av_buffer, '\n')) *Ustrchr(av_buffer, '\n') = '\0'; ++ Ustrcpy(malware_name_buffer,&av_buffer[2]); ++ malware_name = malware_name_buffer; ++ } ++ else if (!strncmp(CS av_buffer, "-1", 2)) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: malware acl condition: sophie reported error"); ++ return DEFER; ++ } ++ else { ++ /* all ok, no virus */ ++ malware_name = NULL; ++ }; ++ } ++ /* ----------------------------------------------------------------------- */ ++ ++ ++ /* "clamd" scanner type ------------------------------------------------- */ ++ /* This code was contributed by David Saez <david@ols.es> */ ++ else if (strcmpic(scanner_name,US"clamd") == 0) { ++ uschar *clamd_options; ++ uschar clamd_options_buffer[1024]; ++ uschar clamd_options_default[] = "/tmp/clamd"; ++ uschar *p,*vname; ++ struct sockaddr_un server; ++ int sock,port,bread=0; ++ uschar file_name[1024]; ++ uschar av_buffer[1024]; ++ uschar hostname[256]; ++ struct hostent *he; ++ struct in_addr in; ++ ++ if ((clamd_options = string_nextinlist(&av_scanner_work, &sep, ++ clamd_options_buffer, ++ sizeof(clamd_options_buffer))) == NULL) { ++ /* no options supplied, use default options */ ++ clamd_options = clamd_options_default; ++ } ++ ++ /* socket does not start with '/' -> network socket */ ++ if (*clamd_options != '/') { ++ ++ /* extract host and port part */ ++ if( sscanf(CS clamd_options, "%s %u", hostname, &port) != 2 ) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: clamd: invalid socket '%s'", clamd_options); ++ return DEFER; ++ }; ++ ++ /* Lookup the host */ ++ if((he = gethostbyname(CS hostname)) == 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: clamd: failed to lookup host '%s'", hostname); ++ return DEFER; ++ } ++ ++ in = *(struct in_addr *) he->h_addr_list[0]; ++ ++ /* Open the ClamAV Socket */ ++ if ( (sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: clamd: unable to acquire socket (%s)", ++ strerror(errno)); ++ return DEFER; ++ } ++ ++ if (ip_connect(sock, AF_INET, (uschar*)inet_ntoa(in), port, 5) < 0) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: clamd: connection to %s, port %u failed (%s)", ++ inet_ntoa(in), port, strerror(errno)); ++ return DEFER; ++ } ++ } ++ else { ++ /* open the local socket */ ++ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: clamd: unable to acquire socket (%s)", ++ strerror(errno)); ++ return DEFER; ++ } ++ ++ server.sun_family = AF_UNIX; ++ Ustrcpy(server.sun_path, clamd_options); ++ ++ if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: clamd: unable to connect to UNIX socket %s (%s)", ++ clamd_options, strerror(errno) ); ++ return DEFER; ++ } ++ } ++ ++ /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */ ++ ++ snprintf(CS file_name,1024,"SCAN %s/scan/%s\n", spool_directory, message_id); ++ ++ if (send(sock, file_name, Ustrlen(file_name), 0) < 0) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC,"malware acl condition: clamd: unable to write to socket (%s)", ++ strerror(errno)); ++ return DEFER; ++ } ++ ++ /* we're done sending, close socket for writing */ ++ shutdown(sock, SHUT_WR); ++ ++ /* Read the result */ ++ memset(av_buffer, 0, sizeof(av_buffer)); ++ bread = read(sock, av_buffer, sizeof(av_buffer)); ++ close(sock); ++ ++ if (!(bread > 0)) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: clamd: unable to read from socket (%s)", ++ strerror(errno)); ++ return DEFER; ++ } ++ ++ if (bread == sizeof(av_buffer)) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: clamd: buffer too small"); ++ return DEFER; ++ } ++ ++ /* Check the result. ClamAV Returns ++ infected: -> "<filename>: <virusname> FOUND" ++ not-infected: -> "<filename>: OK" ++ error: -> "<filename>: <errcode> ERROR */ ++ ++ if (!(*av_buffer)) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: clamd: ClamAV returned null"); ++ return DEFER; ++ } ++ ++ /* colon in returned output? */ ++ if((p = Ustrrchr(av_buffer,':')) == NULL) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: clamd: ClamAV returned malformed result: %s", ++ av_buffer); ++ return DEFER; ++ } ++ ++ /* strip filename strip CR at the end */ ++ ++p; ++ while (*p == ' ') ++p; ++ vname = p; ++ p = vname + Ustrlen(vname) - 1; ++ if( *p == '\n' ) *p = '\0'; ++ ++ if ((p = Ustrstr(vname, "FOUND"))!=NULL) { ++ *p=0; ++ for (--p;p>vname && *p<=32;p--) *p=0; ++ for (;*vname==32;vname++); ++ Ustrcpy(malware_name_buffer,vname); ++ malware_name = malware_name_buffer; ++ } ++ else { ++ if (Ustrstr(vname, "ERROR")!=NULL) { ++ /* ClamAV reports ERROR ++ Find line start */ ++ for (;*vname!='\n' && vname>av_buffer; vname--); ++ if (*vname=='\n') vname++; ++ ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: clamd: ClamAV returned %s",vname); ++ return DEFER; ++ } ++ else { ++ /* Everything should be OK */ ++ malware_name = NULL; ++ } ++ } ++ } ++ /* ----------------------------------------------------------------------- */ ++ ++ ++ /* "mksd" scanner type --------------------------------------------------- */ ++ else if (strcmpic(scanner_name,US"mksd") == 0) { ++ uschar *mksd_options; ++ char *mksd_options_end; ++ uschar mksd_options_buffer[32]; ++ int mksd_maxproc = 1; /* default, if no option supplied */ ++ struct sockaddr_un server; ++ int sock; ++ int retval; ++ ++ if ((mksd_options = string_nextinlist(&av_scanner_work, &sep, ++ mksd_options_buffer, ++ sizeof(mksd_options_buffer))) != NULL) { ++ mksd_maxproc = (int) strtol(CS mksd_options, &mksd_options_end, 10); ++ if ((*mksd_options == '\0') || (*mksd_options_end != '\0') || ++ (mksd_maxproc < 1) || (mksd_maxproc > 32)) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: mksd: invalid option '%s'", mksd_options); ++ return DEFER; ++ } ++ } ++ ++ /* open the mksd socket */ ++ sock = socket(AF_UNIX, SOCK_STREAM, 0); ++ if (sock < 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: can't open UNIX socket."); ++ return DEFER; ++ } ++ server.sun_family = AF_UNIX; ++ Ustrcpy(server.sun_path, "/var/run/mksd/socket"); ++ if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { ++ close(sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: unable to connect to mksd UNIX socket (/var/run/mksd/socket). errno=%d", errno); ++ return DEFER; ++ } ++ ++ malware_name = NULL; ++ ++ /* choose the appropriate scan routine */ ++ retval = demime_ok ? ++ mksd_scan_unpacked(sock, mksd_maxproc) : ++ mksd_scan_packed(sock); ++ ++ if (retval != OK) ++ return retval; ++ } ++ /* ----------------------------------------------------------------------- */ ++ ++ ++ ++ /* "unknown" scanner type ------------------------------------------------- */ ++ else { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware condition: unknown scanner type '%s'", scanner_name); ++ return DEFER; ++ }; ++ /* ----------------------------------------------------------------------- */ ++ ++ /* set "been here, done that" marker */ ++ malware_ok = 1; ++ }; ++ ++ /* match virus name against pattern (caseless ------->----------v) */ ++ if ( (malware_name != NULL) && ++ (regex_match_and_setup(re, malware_name, 0, -1)) ) { ++ return OK; ++ } ++ else { ++ return FAIL; ++ }; ++} ++ ++/* ============= private routines for the "mksd" scanner type ============== */ ++ ++#include <sys/uio.h> ++ ++int mksd_writev (int sock, struct iovec *iov, int iovcnt) ++{ ++ int i; ++ ++ for (;;) { ++ do ++ i = writev (sock, iov, iovcnt); ++ while ((i < 0) && (errno == EINTR)); ++ if (i <= 0) { ++ close (sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: unable to write to mksd UNIX socket (/var/run/mksd/socket)"); ++ return -1; ++ } ++ ++ for (;;) ++ if (i >= iov->iov_len) { ++ if (--iovcnt == 0) ++ return 0; ++ i -= iov->iov_len; ++ iov++; ++ } else { ++ iov->iov_len -= i; ++ iov->iov_base = CS iov->iov_base + i; ++ break; ++ } ++ } ++ return 0; ++} ++ ++int mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size) ++{ ++ int offset = 0; ++ int i; ++ ++ do { ++ if ((i = recv (sock, av_buffer+offset, av_buffer_size-offset, 0)) <= 0) { ++ close (sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: unable to read from mksd UNIX socket (/var/run/mksd/socket)"); ++ return -1; ++ } ++ ++ offset += i; ++ /* offset == av_buffer_size -> buffer full */ ++ if (offset == av_buffer_size) { ++ close (sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: malformed reply received from mksd"); ++ return -1; ++ } ++ } while (av_buffer[offset-1] != '\n'); ++ ++ av_buffer[offset] = '\0'; ++ return offset; ++} ++ ++int mksd_parse_line (char *line) ++{ ++ char *p; ++ ++ switch (*line) { ++ case 'O': ++ /* OK */ ++ return OK; ++ case 'E': ++ case 'A': ++ /* ERR */ ++ if ((p = strchr (line, '\n')) != NULL) ++ (*p) = '\0'; ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: mksd scanner failed: %s", line); ++ return DEFER; ++ default: ++ /* VIR */ ++ if ((p = strchr (line, '\n')) != NULL) { ++ (*p) = '\0'; ++ if (((p-line) > 5) && ((p-line) < sizeof (malware_name_buffer)) && (line[3] == ' ')) ++ if (((p = strchr (line+4, ' ')) != NULL) && ((p-line) > 4)) { ++ (*p) = '\0'; ++ Ustrcpy (malware_name_buffer, line+4); ++ malware_name = malware_name_buffer; ++ return OK; ++ } ++ } ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: malformed reply received from mksd: %s", line); ++ return DEFER; ++ } ++} ++ ++int mksd_scan_packed (int sock) ++{ ++ struct iovec iov[7]; ++ char *cmd = "MSQ/scan/.eml\n"; ++ uschar av_buffer[1024]; ++ ++ iov[0].iov_base = cmd; ++ iov[0].iov_len = 3; ++ iov[1].iov_base = CS spool_directory; ++ iov[1].iov_len = Ustrlen (spool_directory); ++ iov[2].iov_base = cmd + 3; ++ iov[2].iov_len = 6; ++ iov[3].iov_base = iov[5].iov_base = CS message_id; ++ iov[3].iov_len = iov[5].iov_len = Ustrlen (message_id); ++ iov[4].iov_base = cmd + 3; ++ iov[4].iov_len = 1; ++ iov[6].iov_base = cmd + 9; ++ iov[6].iov_len = 5; ++ ++ if (mksd_writev (sock, iov, 7) < 0) ++ return DEFER; ++ ++ if (mksd_read_lines (sock, av_buffer, sizeof (av_buffer)) < 0) ++ return DEFER; ++ ++ close (sock); ++ ++ return mksd_parse_line (CS av_buffer); ++} ++ ++int mksd_scan_unpacked (int sock, int maxproc) ++{ ++ struct iovec iov[5]; ++ char *cmd = "\nSQ/"; ++ DIR *unpdir; ++ struct dirent *entry; ++ int pending = 0; ++ uschar *line; ++ int i, offset; ++ uschar mbox_name[1024]; ++ uschar unpackdir[1024]; ++ uschar av_buffer[16384]; ++ ++ snprintf (CS mbox_name, sizeof (mbox_name), "%s.eml", CS message_id); ++ snprintf (CS unpackdir, sizeof (unpackdir), "%s/scan/%s", CS spool_directory, CS message_id); ++ ++ if ((unpdir = opendir (CS unpackdir)) == NULL) { ++ close (sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: unable to scan spool directory"); ++ return DEFER; ++ } ++ ++ iov[0].iov_base = cmd; ++ iov[0].iov_len = 3; ++ iov[1].iov_base = CS unpackdir; ++ iov[1].iov_len = Ustrlen (unpackdir); ++ iov[2].iov_base = cmd + 3; ++ iov[2].iov_len = 1; ++ iov[4].iov_base = cmd; ++ iov[4].iov_len = 1; ++ ++ /* main loop */ ++ while ((unpdir != NULL) || (pending > 0)) { ++ ++ /* write loop */ ++ while ((pending < maxproc) && (unpdir != NULL)) { ++ if ((entry = readdir (unpdir)) != NULL) { ++ if ((Ustrcmp (entry->d_name, ".") != 0) && ++ (Ustrcmp (entry->d_name, "..") != 0) && ++ (Ustrcmp (entry->d_name, mbox_name) != 0)) { ++ iov[3].iov_base = entry->d_name; ++ iov[3].iov_len = strlen (entry->d_name); ++ if (mksd_writev (sock, iov, 5) < 0) { ++ closedir (unpdir); ++ return DEFER; ++ } ++ iov[0].iov_base = cmd + 1; ++ iov[0].iov_len = 2; ++ pending++; ++ } ++ } else { ++ closedir (unpdir); ++ unpdir = NULL; ++ } ++ } ++ ++ /* read and parse */ ++ if (pending > 0) { ++ if ((offset = mksd_read_lines (sock, av_buffer, sizeof (av_buffer))) < 0) { ++ if (unpdir != NULL) ++ closedir (unpdir); ++ return DEFER; ++ } ++ line = av_buffer; ++ do { ++ if (((i = mksd_parse_line (CS line)) != OK) || (malware_name != NULL)) { ++ close (sock); ++ if (unpdir != NULL) ++ closedir (unpdir); ++ return i; ++ } ++ pending--; ++ if ((line = Ustrchr (line, '\n')) == NULL) { ++ close (sock); ++ if (unpdir != NULL) ++ closedir (unpdir); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: unterminated line received from mksd"); ++ return DEFER; ++ } ++ } while (++line != (av_buffer + offset)); ++ offset = 0; ++ } ++ } ++ ++ close (sock); ++ return OK; ++} +diff -urN exim-4.32-orig/src/mime.c exim-4.32/src/mime.c +--- exim-4.32-orig/src/mime.c Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/mime.c Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,661 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++patch. It is NOT part of the standard exim distribution. */ ++ ++/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 */ ++/* License: GPL */ ++ ++#include "exim.h" ++#include "mime.h" ++#include <sys/stat.h> ++ ++FILE *mime_stream = NULL; ++uschar *mime_current_boundary = NULL; ++ ++ ++/************************************************* ++* decode quoted-printable chars * ++*************************************************/ ++ ++/* gets called when we hit a = ++ returns: new pointer position ++ result code in c: ++ -2 - decode error ++ -1 - soft line break, no char ++ 0-255 - char to write ++*/ ++ ++unsigned int mime_qp_hstr_i(uschar *cptr) { ++ unsigned int i, j = 0; ++ while (cptr && *cptr && isxdigit(*cptr)) { ++ i = *cptr++ - '0'; ++ if (9 < i) i -= 7; ++ j <<= 4; ++ j |= (i & 0x0f); ++ } ++ return(j); ++} ++ ++uschar *mime_decode_qp_char(uschar *qp_p,int *c) { ++ uschar hex[] = {0,0,0}; ++ int nan = 0; ++ uschar *initial_pos = qp_p; ++ ++ /* advance one char */ ++ qp_p++; ++ ++ REPEAT_FIRST: ++ if ( (*qp_p == '\t') || (*qp_p == ' ') || (*qp_p == '\r') ) { ++ /* tab or whitespace may follow ++ just ignore it, but remember ++ that this is not a valid hex ++ encoding any more */ ++ nan = 1; ++ qp_p++; ++ goto REPEAT_FIRST; ++ } ++ else if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { ++ /* this is a valid hex char, if nan is unset */ ++ if (nan) { ++ /* this is illegal */ ++ *c = -2; ++ return initial_pos; ++ } ++ else { ++ hex[0] = *qp_p; ++ qp_p++; ++ }; ++ } ++ else if (*qp_p == '\n') { ++ /* hit soft line break already, continue */ ++ *c = -1; ++ return qp_p; ++ } ++ else { ++ /* illegal char here */ ++ *c = -2; ++ return initial_pos; ++ }; ++ ++ if ( (('0' <= *qp_p) && (*qp_p <= '9')) || (('A' <= *qp_p) && (*qp_p <= 'F')) || (('a' <= *qp_p) && (*qp_p <= 'f')) ) { ++ if (hex[0] > 0) { ++ hex[1] = *qp_p; ++ /* do hex conversion */ ++ *c = mime_qp_hstr_i(hex); ++ qp_p++; ++ return qp_p; ++ } ++ else { ++ /* huh ? */ ++ *c = -2; ++ return initial_pos; ++ }; ++ } ++ else { ++ /* illegal char */ ++ *c = -2; ++ return initial_pos; ++ }; ++} ++ ++ ++uschar *mime_parse_line(uschar *buffer, uschar *encoding, int *num_decoded) { ++ uschar *data = NULL; ++ ++ data = (uschar *)malloc(Ustrlen(buffer)+2); ++ ++ if (encoding == NULL) { ++ /* no encoding type at all */ ++ NO_DECODING: ++ memcpy(data, buffer, Ustrlen(buffer)); ++ data[(Ustrlen(buffer))] = 0; ++ *num_decoded = Ustrlen(data); ++ return data; ++ } ++ else if (Ustrcmp(encoding,"base64") == 0) { ++ uschar *p = buffer; ++ int offset = 0; ++ ++ /* ----- BASE64 ---------------------------------------------------- */ ++ /* NULL out '\r' and '\n' chars */ ++ while (Ustrrchr(p,'\r') != NULL) { ++ *(Ustrrchr(p,'\r')) = '\0'; ++ }; ++ while (Ustrrchr(p,'\n') != NULL) { ++ *(Ustrrchr(p,'\n')) = '\0'; ++ }; ++ ++ while (*(p+offset) != '\0') { ++ /* hit illegal char ? */ ++ if (mime_b64[*(p+offset)] == 128) { ++ offset++; ++ } ++ else { ++ *p = mime_b64[*(p+offset)]; ++ p++; ++ }; ++ }; ++ *p = 255; ++ ++ /* line is translated, start bit shifting */ ++ p = buffer; ++ *num_decoded = 0; ++ while(*p != 255) { ++ uschar tmp_c; ++ ++ /* byte 0 ---------------------- */ ++ if (*(p+1) == 255) { ++ break; ++ } ++ data[(*num_decoded)] = *p; ++ data[(*num_decoded)] <<= 2; ++ tmp_c = *(p+1); ++ tmp_c >>= 4; ++ data[(*num_decoded)] |= tmp_c; ++ (*num_decoded)++; ++ p++; ++ /* byte 1 ---------------------- */ ++ if (*(p+1) == 255) { ++ break; ++ } ++ data[(*num_decoded)] = *p; ++ data[(*num_decoded)] <<= 4; ++ tmp_c = *(p+1); ++ tmp_c >>= 2; ++ data[(*num_decoded)] |= tmp_c; ++ (*num_decoded)++; ++ p++; ++ /* byte 2 ---------------------- */ ++ if (*(p+1) == 255) { ++ break; ++ } ++ data[(*num_decoded)] = *p; ++ data[(*num_decoded)] <<= 6; ++ data[(*num_decoded)] |= *(p+1); ++ (*num_decoded)++; ++ p+=2; ++ ++ }; ++ return data; ++ /* ----------------------------------------------------------------- */ ++ } ++ else if (Ustrcmp(encoding,"quoted-printable") == 0) { ++ uschar *p = buffer; ++ ++ /* ----- QP -------------------------------------------------------- */ ++ *num_decoded = 0; ++ while (*p != 0) { ++ if (*p == '=') { ++ int decode_qp_result; ++ ++ p = mime_decode_qp_char(p,&decode_qp_result); ++ ++ if (decode_qp_result == -2) { ++ /* Error from decoder. p is unchanged. */ ++ data[(*num_decoded)] = '='; ++ (*num_decoded)++; ++ p++; ++ } ++ else if (decode_qp_result == -1) { ++ break; ++ } ++ else if (decode_qp_result >= 0) { ++ data[(*num_decoded)] = decode_qp_result; ++ (*num_decoded)++; ++ }; ++ } ++ else { ++ data[(*num_decoded)] = *p; ++ (*num_decoded)++; ++ p++; ++ }; ++ }; ++ return data; ++ /* ----------------------------------------------------------------- */ ++ } ++ /* unknown encoding type, just dump as-is */ ++ else goto NO_DECODING; ++} ++ ++ ++FILE *mime_get_decode_file(uschar *pname, uschar *fname) { ++ FILE *f; ++ uschar *filename; ++ ++ filename = (uschar *)malloc(2048); ++ ++ if ((pname != NULL) && (fname != NULL)) { ++ snprintf(CS filename, 2048, "%s/%s", pname, fname); ++ f = fopen(CS filename,"w+"); ++ } ++ else if (pname == NULL) { ++ f = fopen(CS fname,"w+"); ++ } ++ else if (fname == NULL) { ++ int file_nr = 0; ++ int result = 0; ++ ++ /* must find first free sequential filename */ ++ do { ++ struct stat mystat; ++ snprintf(CS filename,2048,"%s/%s-%05u", pname, message_id, file_nr); ++ file_nr++; ++ /* security break */ ++ if (file_nr >= 1024) ++ break; ++ result = stat(CS filename,&mystat); ++ } ++ while(result != -1); ++ f = fopen(CS filename,"w+"); ++ }; ++ ++ /* set expansion variable */ ++ mime_decoded_filename = filename; ++ ++ return f; ++} ++ ++ ++int mime_decode(uschar **listptr) { ++ int sep = 0; ++ uschar *list = *listptr; ++ uschar *option; ++ uschar option_buffer[1024]; ++ uschar decode_path[1024]; ++ FILE *decode_file = NULL; ++ uschar *buffer = NULL; ++ long f_pos = 0; ++ ++ if (mime_stream == NULL) ++ return FAIL; ++ ++ f_pos = ftell(mime_stream); ++ ++ /* build default decode path (will exist since MBOX must be spooled up) */ ++ snprintf(CS decode_path,1024,"%s/scan/%s",spool_directory,message_id); ++ ++ /* reserve a line buffer to work in */ ++ buffer = (uschar *)malloc(MIME_MAX_LINE_LENGTH+1); ++ if (buffer == NULL) { ++ log_write(0, LOG_PANIC, ++ "decode ACL condition: can't allocate %d bytes of memory.", MIME_MAX_LINE_LENGTH+1); ++ return DEFER; ++ }; ++ ++ /* try to find 1st option */ ++ if ((option = string_nextinlist(&list, &sep, ++ option_buffer, ++ sizeof(option_buffer))) != NULL) { ++ ++ /* parse 1st option */ ++ if ( (Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0) ) { ++ /* explicitly no decoding */ ++ return FAIL; ++ }; ++ ++ if (Ustrcmp(option,"default") == 0) { ++ /* explicit default path + file names */ ++ goto DEFAULT_PATH; ++ }; ++ ++ if (option[0] == '/') { ++ struct stat statbuf; ++ ++ memset(&statbuf,0,sizeof(statbuf)); ++ ++ /* assume either path or path+file name */ ++ if ( (stat(CS option, &statbuf) == 0) && S_ISDIR(statbuf.st_mode) ) ++ /* is directory, use it as decode_path */ ++ decode_file = mime_get_decode_file(option, NULL); ++ else ++ /* does not exist or is a file, use as full file name */ ++ decode_file = mime_get_decode_file(NULL, option); ++ } ++ else ++ /* assume file name only, use default path */ ++ decode_file = mime_get_decode_file(decode_path, option); ++ } ++ else ++ /* no option? patch default path */ ++ DEFAULT_PATH: decode_file = mime_get_decode_file(decode_path, NULL); ++ ++ if (decode_file == NULL) ++ return DEFER; ++ ++ /* read data linewise and dump it to the file, ++ while looking for the current boundary */ ++ while(fgets(CS buffer, MIME_MAX_LINE_LENGTH, mime_stream) != NULL) { ++ uschar *decoded_line = NULL; ++ int decoded_line_length = 0; ++ ++ if (mime_current_boundary != NULL) { ++ /* boundary line must start with 2 dashes */ ++ if (Ustrncmp(buffer,"--",2) == 0) { ++ if (Ustrncmp((buffer+2),mime_current_boundary,Ustrlen(mime_current_boundary)) == 0) ++ break; ++ }; ++ }; ++ ++ decoded_line = mime_parse_line(buffer, mime_content_transfer_encoding, &decoded_line_length); ++ /* write line to decode file */ ++ if (fwrite(decoded_line, 1, decoded_line_length, decode_file) < decoded_line_length) { ++ /* error/short write */ ++ clearerr(mime_stream); ++ fseek(mime_stream,f_pos,SEEK_SET); ++ return DEFER; ++ }; ++ free(decoded_line); ++ } ++ ++ fclose(decode_file); ++ ++ clearerr(mime_stream); ++ fseek(mime_stream,f_pos,SEEK_SET); ++ ++ return OK; ++} ++ ++int mime_get_header(FILE *f, uschar *header) { ++ int c = EOF; ++ int done = 0; ++ int header_value_mode = 0; ++ int header_open_brackets = 0; ++ int num_copied = 0; ++ ++ while(!done) { ++ ++ c = fgetc(f); ++ if (c == EOF) break; ++ ++ /* always skip CRs */ ++ if (c == '\r') continue; ++ ++ if (c == '\n') { ++ if (num_copied > 0) { ++ /* look if next char is '\t' or ' ' */ ++ c = fgetc(f); ++ if (c == EOF) break; ++ if ( (c == '\t') || (c == ' ') ) continue; ++ ungetc(c,f); ++ }; ++ /* end of the header, terminate with ';' */ ++ c = ';'; ++ done = 1; ++ }; ++ ++ /* skip control characters */ ++ if (c < 32) continue; ++ ++ if (header_value_mode) { ++ /* --------- value mode ----------- */ ++ /* skip quotes */ ++ if (c == '"') continue; ++ ++ /* leave value mode on ';' */ ++ if (c == ';') { ++ header_value_mode = 0; ++ }; ++ /* -------------------------------- */ ++ } ++ else { ++ /* -------- non-value mode -------- */ ++ /* skip whitespace + tabs */ ++ if ( (c == ' ') || (c == '\t') ) ++ continue; ++ if (c == '\\') { ++ /* quote next char. can be used ++ to escape brackets. */ ++ c = fgetc(f); ++ if (c == EOF) break; ++ } ++ else if (c == '(') { ++ header_open_brackets++; ++ continue; ++ } ++ else if ((c == ')') && header_open_brackets) { ++ header_open_brackets--; ++ continue; ++ } ++ else if ( (c == '=') && !header_open_brackets ) { ++ /* enter value mode */ ++ header_value_mode = 1; ++ }; ++ ++ /* skip chars while we are in a comment */ ++ if (header_open_brackets > 0) ++ continue; ++ /* -------------------------------- */ ++ }; ++ ++ /* copy the char to the buffer */ ++ header[num_copied] = (uschar)c; ++ /* raise counter */ ++ num_copied++; ++ ++ /* break if header buffer is full */ ++ if (num_copied > MIME_MAX_HEADER_SIZE-1) { ++ done = 1; ++ }; ++ }; ++ ++ if (header[num_copied-1] != ';') { ++ header[num_copied-1] = ';'; ++ }; ++ ++ /* 0-terminate */ ++ header[num_copied] = '\0'; ++ ++ /* return 0 for EOF or empty line */ ++ if ((c == EOF) || (num_copied == 1)) ++ return 0; ++ else ++ return 1; ++} ++ ++ ++int mime_acl_check(FILE *f, uschar *boundary, uschar **user_msgptr, uschar **log_msgptr) { ++ int rc = OK; ++ uschar *header = NULL; ++ uschar *this_boundary = NULL; ++ ++ /* reserve a line buffer to work in */ ++ header = (uschar *)malloc(MIME_MAX_HEADER_SIZE+1); ++ if (header == NULL) { ++ log_write(0, LOG_PANIC, ++ "acl_smtp_mime: can't allocate %d bytes of memory.", MIME_MAX_HEADER_SIZE+1); ++ return DEFER; ++ }; ++ ++ /* loop through parts */ ++ while(1) { ++ ++ /* reset all per-part mime variables */ ++ mime_anomaly_level = NULL; ++ mime_anomaly_text = NULL; ++ mime_boundary = NULL; ++ mime_charset = NULL; ++ mime_decoded_filename = NULL; ++ mime_filename = NULL; ++ mime_content_description = NULL; ++ mime_content_disposition = NULL; ++ mime_content_id = NULL; ++ mime_content_transfer_encoding = NULL; ++ mime_content_type = NULL; ++ mime_is_multipart = 0; ++ ++ /* ++ If boundary is null, we assume that *f is positioned on the start of headers (for example, ++ at the very beginning of a message. ++ If a boundary is given, we must first advance to it to reach the start of the next header ++ block. ++ */ ++ ++ if (boundary != NULL) { ++ while(fgets(CS header, MIME_MAX_HEADER_SIZE, f) != NULL) { ++ /* boundary line must start with 2 dashes */ ++ if (Ustrncmp(header,"--",2) == 0) { ++ if (Ustrncmp((header+2),boundary,Ustrlen(boundary)) == 0) { ++ /* found boundary */ ++ if (Ustrncmp((header+2+Ustrlen(boundary)),"--",2) == 0) { ++ /* END boundary found */ ++ debug_printf("End boundary found %s\n", boundary); ++ return rc; ++ } ++ else { ++ debug_printf("Next part with boundary %s\n", boundary); ++ }; ++ /* can't use break here */ ++ goto DECODE_HEADERS; ++ } ++ }; ++ } ++ /* Hit EOF or read error. Ugh. */ ++ debug_printf("Hit EOF ...\n"); ++ return rc; ++ }; ++ ++ DECODE_HEADERS: ++ /* parse headers, set up expansion variables */ ++ while(mime_get_header(f,header)) { ++ int i; ++ /* loop through header list */ ++ for (i = 0; i < mime_header_list_size; i++) { ++ uschar *header_value = NULL; ++ int header_value_len = 0; ++ ++ /* found an interesting header? */ ++ if (strncmpic(mime_header_list[i].name,header,mime_header_list[i].namelen) == 0) { ++ uschar *p = header + mime_header_list[i].namelen; ++ /* yes, grab the value (normalize to lower case) ++ and copy to its corresponding expansion variable */ ++ while(*p != ';') { ++ *p = tolower(*p); ++ p++; ++ }; ++ header_value_len = (p - (header + mime_header_list[i].namelen)); ++ header_value = (uschar *)malloc(header_value_len+1); ++ memset(header_value,0,header_value_len+1); ++ p = header + mime_header_list[i].namelen; ++ Ustrncpy(header_value, p, header_value_len); ++ debug_printf("Found %s MIME header, value is '%s'\n", mime_header_list[i].name, header_value); ++ *((uschar **)(mime_header_list[i].value)) = header_value; ++ ++ /* make p point to the next character after the closing ';' */ ++ p += (header_value_len+1); ++ ++ /* grab all param=value tags on the remaining line, check if they are interesting */ ++ NEXT_PARAM_SEARCH: while (*p != 0) { ++ int j; ++ for (j = 0; j < mime_parameter_list_size; j++) { ++ uschar *param_value = NULL; ++ int param_value_len = 0; ++ ++ /* found an interesting parameter? */ ++ if (strncmpic(mime_parameter_list[j].name,p,mime_parameter_list[j].namelen) == 0) { ++ uschar *q = p + mime_parameter_list[j].namelen; ++ /* yes, grab the value and copy to its corresponding expansion variable */ ++ while(*q != ';') q++; ++ param_value_len = (q - (p + mime_parameter_list[j].namelen)); ++ param_value = (uschar *)malloc(param_value_len+1); ++ memset(param_value,0,param_value_len+1); ++ q = p + mime_parameter_list[j].namelen; ++ Ustrncpy(param_value, q, param_value_len); ++ param_value = rfc2047_decode(param_value, TRUE, NULL, 32, ¶m_value_len, &q); ++ debug_printf("Found %s MIME parameter in %s header, value is '%s'\n", mime_parameter_list[j].name, mime_header_list[i].name, param_value); ++ *((uschar **)(mime_parameter_list[j].value)) = param_value; ++ p += (mime_parameter_list[j].namelen + param_value_len + 1); ++ goto NEXT_PARAM_SEARCH; ++ }; ++ } ++ /* There is something, but not one of our interesting parameters. ++ Advance to the next semicolon */ ++ while(*p != ';') p++; ++ p++; ++ }; ++ }; ++ }; ++ }; ++ ++ /* set additional flag variables (easier access) */ ++ if ( (mime_content_type != NULL) && ++ (Ustrncmp(mime_content_type,"multipart",9) == 0) ) ++ mime_is_multipart = 1; ++ ++ /* Make a copy of the boundary pointer. ++ Required since mime_boundary is global ++ and can be overwritten further down in recursion */ ++ this_boundary = mime_boundary; ++ ++ /* raise global counter */ ++ mime_part_count++; ++ ++ /* copy current file handle to global variable */ ++ mime_stream = f; ++ mime_current_boundary = boundary; ++ ++ /* call ACL handling function */ ++ rc = acl_check(ACL_WHERE_MIME, NULL, acl_smtp_mime, user_msgptr, log_msgptr); ++ ++ mime_stream = NULL; ++ mime_current_boundary = NULL; ++ ++ if (rc != OK) break; ++ ++ /* If we have a multipart entity and a boundary, go recursive */ ++ if ( (mime_content_type != NULL) && ++ (this_boundary != NULL) && ++ (Ustrncmp(mime_content_type,"multipart",9) == 0) ) { ++ debug_printf("Entering multipart recursion, boundary '%s'\n", this_boundary); ++ rc = mime_acl_check(f, this_boundary, user_msgptr, log_msgptr); ++ if (rc != OK) break; ++ } ++ else if ( (mime_content_type != NULL) && ++ (Ustrncmp(mime_content_type,"message/rfc822",14) == 0) ) { ++ uschar *rfc822name = NULL; ++ uschar filename[2048]; ++ int file_nr = 0; ++ int result = 0; ++ ++ /* must find first free sequential filename */ ++ do { ++ struct stat mystat; ++ snprintf(CS filename,2048,"%s/scan/%s/__rfc822_%05u", spool_directory, message_id, file_nr); ++ file_nr++; ++ /* security break */ ++ if (file_nr >= 128) ++ goto NO_RFC822; ++ result = stat(CS filename,&mystat); ++ } ++ while(result != -1); ++ ++ rfc822name = filename; ++ ++ /* decode RFC822 attachment */ ++ mime_decoded_filename = NULL; ++ mime_stream = f; ++ mime_current_boundary = boundary; ++ mime_decode(&rfc822name); ++ mime_stream = NULL; ++ mime_current_boundary = NULL; ++ if (mime_decoded_filename == NULL) { ++ /* decoding failed */ ++ log_write(0, LOG_MAIN, ++ "mime_regex acl condition warning - could not decode RFC822 MIME part to file."); ++ return DEFER; ++ }; ++ mime_decoded_filename = NULL; ++ }; ++ ++ NO_RFC822: ++ /* If the boundary of this instance is NULL, we are finished here */ ++ if (boundary == NULL) break; ++ ++ }; ++ ++ return rc; ++} ++ ++ +diff -urN exim-4.32-orig/src/mime.h exim-4.32/src/mime.h +--- exim-4.32-orig/src/mime.h Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/mime.h Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,66 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++patch. It is NOT part of the standard exim distribution. */ ++ ++/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 */ ++/* License: GPL */ ++ ++ ++#define MIME_MAX_HEADER_SIZE 8192 ++#define MIME_MAX_LINE_LENGTH 32768 ++ ++typedef struct mime_header { ++ uschar *name; ++ int namelen; ++ void *value; ++} mime_header; ++ ++static mime_header mime_header_list[] = { ++ { US"content-type:", 13, &mime_content_type }, ++ { US"content-disposition:", 20, &mime_content_disposition }, ++ { US"content-transfer-encoding:", 26, &mime_content_transfer_encoding }, ++ { US"content-id:", 11, &mime_content_id }, ++ { US"content-description:", 20 , &mime_content_description } ++}; ++ ++static int mime_header_list_size = sizeof(mime_header_list)/sizeof(mime_header); ++ ++ ++ ++typedef struct mime_parameter { ++ uschar *name; ++ int namelen; ++ void *value; ++} mime_parameter; ++ ++static mime_parameter mime_parameter_list[] = { ++ { US"name=", 5, &mime_filename }, ++ { US"filename=", 9, &mime_filename }, ++ { US"charset=", 8, &mime_charset }, ++ { US"boundary=", 9, &mime_boundary } ++}; ++ ++static int mime_parameter_list_size = sizeof(mime_parameter_list)/sizeof(mime_parameter); ++ ++/* BASE64 decoder matrix */ ++static unsigned char mime_b64[256]={ ++/* 0 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++/* 16 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++/* 32 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 62, 128, 128, 128, 63, ++/* 48 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 128, 128, 128, 255, 128, 128, ++/* 64 */ 128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, ++/* 80 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 128, 128, 128, 128, 128, ++/* 96 */ 128, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, ++/* 112 */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 128, 128, 128, 128, 128, ++/* 128 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++/* 144 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++/* 160 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++/* 176 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++/* 192 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++/* 208 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++/* 224 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, ++/* 240 */ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 ++}; +diff -urN exim-4.32-orig/src/readconf.c exim-4.32/src/readconf.c +--- exim-4.32-orig/src/readconf.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/readconf.c Thu Apr 15 13:39:38 2004 +@@ -141,6 +141,7 @@ + { "acl_smtp_helo", opt_stringptr, &acl_smtp_helo }, + { "acl_smtp_mail", opt_stringptr, &acl_smtp_mail }, + { "acl_smtp_mailauth", opt_stringptr, &acl_smtp_mailauth }, ++ { "acl_smtp_mime", opt_stringptr, &acl_smtp_mime }, + { "acl_smtp_rcpt", opt_stringptr, &acl_smtp_rcpt }, + #ifdef SUPPORT_TLS + { "acl_smtp_starttls", opt_stringptr, &acl_smtp_starttls }, +@@ -152,7 +153,11 @@ + { "allow_utf8_domains", opt_bool, &allow_utf8_domains }, + { "auth_advertise_hosts", opt_stringptr, &auth_advertise_hosts }, + { "auto_thaw", opt_time, &auto_thaw }, ++ { "av_scanner", opt_stringptr, &av_scanner }, + { "bi_command", opt_stringptr, &bi_command }, ++#ifdef BRIGHTMAIL ++ { "bmi_config_file", opt_stringptr, &bmi_config_file }, ++#endif + { "bounce_message_file", opt_stringptr, &bounce_message_file }, + { "bounce_message_text", opt_stringptr, &bounce_message_text }, + { "bounce_return_body", opt_bool, &bounce_return_body }, +@@ -311,6 +316,7 @@ + { "smtp_receive_timeout", opt_time, &smtp_receive_timeout }, + { "smtp_reserve_hosts", opt_stringptr, &smtp_reserve_hosts }, + { "smtp_return_error_details",opt_bool, &smtp_return_error_details }, ++ { "spamd_address", opt_stringptr, &spamd_address }, + { "split_spool_directory", opt_bool, &split_spool_directory }, + { "spool_directory", opt_stringptr, &spool_directory }, + { "strip_excess_angle_brackets", opt_bool, &strip_excess_angle_brackets }, +diff -urN exim-4.32-orig/src/receive.c exim-4.32/src/receive.c +--- exim-4.32-orig/src/receive.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/receive.c Thu Apr 15 13:39:38 2004 +@@ -10,7 +10,9 @@ + + #include "exim.h" + +- ++#ifdef BRIGHTMAIL ++#include "bmi_spam.h" ++#endif + + /************************************************* + * Local static variables * +@@ -2483,6 +2485,122 @@ + + if (smtp_input && !smtp_batched_input) + { ++ if (acl_smtp_mime != NULL && recipients_count > 0) ++ { ++ FILE *mbox_file; ++ uschar rfc822_file_path[2048]; ++ unsigned long long mbox_size; ++ header_line *my_headerlist; ++ uschar *user_msg, *log_msg; ++ int mime_part_count_buffer = -1; ++ ++ memset(CS rfc822_file_path,0,2048); ++ ++ /* check if it is a MIME message */ ++ my_headerlist = header_list; ++ while (my_headerlist != NULL) { ++ /* skip deleted headers */ ++ if (my_headerlist->type == '*') { ++ my_headerlist = my_headerlist->next; ++ continue; ++ }; ++ if (strncmpic(my_headerlist->text, US"MIME-Version:", 13) == 0) { ++ DEBUG(D_receive) debug_printf("Found MIME-Version: header - executing acl_smtp_mime.\n"); ++ goto DO_MIME_ACL; ++ }; ++ my_headerlist = my_headerlist->next; ++ }; ++ ++ DEBUG(D_receive) debug_printf("No MIME-Version: header - not a MIME message.\n"); ++ goto NO_MIME_ACL; ++ ++ DO_MIME_ACL: ++ /* make sure the eml mbox file is spooled up */ ++ mbox_file = spool_mbox(&mbox_size); ++ if (mbox_file == NULL) { ++ /* error while spooling */ ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected."); ++ Uunlink(spool_name); ++ unspool_mbox(); ++ smtp_respond(451, TRUE, US"temporary local problem"); ++ message_id[0] = 0; /* Indicate no message accepted */ ++ smtp_reply = US""; /* Indicate reply already sent */ ++ goto TIDYUP; /* Skip to end of function */ ++ }; ++ mime_is_rfc822 = 0; ++ ++ ++ MIME_ACL_CHECK: ++ mime_part_count = -1; ++ rc = mime_acl_check(mbox_file, NULL, &user_msg, &log_msg); ++ fclose(mbox_file); ++ if (Ustrlen(rfc822_file_path) > 0) { ++ mime_part_count = mime_part_count_buffer; ++ if (unlink(CS rfc822_file_path) == -1) { ++ log_write(0, LOG_PANIC, ++ "acl_smtp_mime: can't unlink RFC822 spool file, skipping."); ++ goto END_MIME_ACL; ++ }; ++ }; ++ ++ /* check if we must check any message/rfc822 attachments */ ++ if (rc == OK) { ++ uschar temp_path[1024]; ++ int n; ++ struct dirent *entry; ++ DIR *tempdir; ++ ++ snprintf(CS temp_path, 1024, "%s/scan/%s", spool_directory, message_id); ++ ++ tempdir = opendir(CS temp_path); ++ n = 0; ++ do { ++ entry = readdir(tempdir); ++ if (entry == NULL) break; ++ if (strncmpic(US entry->d_name,US"__rfc822_",9) == 0) { ++ snprintf(CS rfc822_file_path, 2048,"%s/scan/%s/%s", spool_directory, message_id, entry->d_name); ++ debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path); ++ break; ++ }; ++ } while (1); ++ closedir(tempdir); ++ ++ if (entry != NULL) { ++ mbox_file = Ufopen(rfc822_file_path,"r"); ++ if (mbox_file == NULL) { ++ log_write(0, LOG_PANIC, ++ "acl_smtp_mime: can't open RFC822 spool file, skipping."); ++ unlink(CS rfc822_file_path); ++ goto END_MIME_ACL; ++ }; ++ /* set RFC822 expansion variable */ ++ mime_is_rfc822 = 1; ++ mime_part_count_buffer = mime_part_count; ++ goto MIME_ACL_CHECK; ++ }; ++ }; ++ ++ END_MIME_ACL: ++ add_acl_headers(US"MIME"); ++ if (rc == DISCARD) ++ { ++ recipients_count = 0; ++ blackholed_by = US"MIME ACL"; ++ } ++ else if (rc != OK) ++ { ++ Uunlink(spool_name); ++ unspool_mbox(); ++ if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0) ++ smtp_yield = FALSE; /* No more messsages after dropped connection */ ++ smtp_reply = US""; /* Indicate reply already sent */ ++ message_id[0] = 0; /* Indicate no message accepted */ ++ goto TIDYUP; /* Skip to end of function */ ++ }; ++ } ++ ++ NO_MIME_ACL: + if (acl_smtp_data != NULL && recipients_count > 0) + { + uschar *user_msg, *log_msg; +@@ -2496,6 +2614,7 @@ + else if (rc != OK) + { + Uunlink(spool_name); ++ unspool_mbox(); + if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0) + smtp_yield = FALSE; /* No more messsages after dropped connection */ + smtp_reply = US""; /* Indicate reply already sent */ +@@ -2505,6 +2624,7 @@ + } + } + ++ + /* Handle non-SMTP and batch SMTP (i.e. non-interactive) messages. Note that + we cannot take different actions for permanent and temporary rejections. */ + +@@ -2545,6 +2665,8 @@ + enable_dollar_recipients = FALSE; + } + ++unspool_mbox(); ++ + /* The final check on the message is to run the scan_local() function. The + version supplied with Exim always accepts, but this is a hook for sysadmins to + supply their own checking code. The local_scan() function is run even when all +@@ -2756,6 +2878,16 @@ + debug_printf(">>Generated Received: header line\n%c %s", header_list->type, + header_list->text); + ++ ++#ifdef BRIGHTMAIL ++if (bmi_run == 1) { ++ /* rewind data file */ ++ lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET); ++ bmi_verdicts = bmi_process_message(header_list, data_fd); ++}; ++#endif ++ ++ + /* Keep the data file open until we have written the header file, in order to + hold onto the lock. In a -bh run, or if the message is to be blackholed, we + don't write the header file, and we unlink the data file. If writing the header +@@ -2994,6 +3126,7 @@ + if this happens? */ + + TIDYUP: ++ + process_info[process_info_len] = 0; /* Remove message id */ + if (data_file != NULL) fclose(data_file); /* Frees the lock */ + +@@ -3020,12 +3153,31 @@ + { + if (smtp_reply == NULL) + { +- smtp_printf("250 OK id=%s\r\n", message_id); ++ if (fake_reject) ++ { ++ smtp_printf("550-FAKE_REJECT id=%s\r\n", message_id); ++ smtp_printf("550-Your message has been rejected but is being kept for evaluation.\r\n"); ++ smtp_printf("550 If it was a legit message, it may still be delivered to the target recipient(s).\r\n"); ++ } ++ else ++ smtp_printf("250 OK id=%s\r\n", message_id); ++ + if (host_checking) + fprintf(stdout, + "\n**** SMTP testing: that is not a real message id!\n\n"); ++ + } +- else if (smtp_reply[0] != 0) smtp_printf("%.1024s\r\n", smtp_reply); ++ else if (smtp_reply[0] != 0) ++ { ++ if (fake_reject && (smtp_reply[0] == '2')) ++ { ++ smtp_printf("550-FAKE_REJECT id=%s\r\n", message_id); ++ smtp_printf("550-Your message has been rejected but is being kept for evaluation.\r\n"); ++ smtp_printf("550 If it was a legit message, it may still be delivered to the target recipient(s).\r\n"); ++ } ++ else ++ smtp_printf("%.1024s\r\n", smtp_reply); ++ }; + } + + /* For batched SMTP, generate an error message on failure, and do +diff -urN exim-4.32-orig/src/regex.c exim-4.32/src/regex.c +--- exim-4.32-orig/src/regex.c Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/regex.c Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,243 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++patch. It is NOT part of the standard exim distribution. */ ++ ++/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */ ++/* License: GPL */ ++ ++/* Code for matching regular expressions against headers and body. ++ Called from acl.c. */ ++ ++#include "exim.h" ++#include <unistd.h> ++#include <sys/mman.h> ++ ++/* Structure to hold a list of Regular expressions */ ++typedef struct pcre_list { ++ pcre *re; ++ uschar *pcre_text; ++ struct pcre_list *next; ++} pcre_list; ++ ++uschar regex_match_string_buffer[1024]; ++ ++extern FILE *mime_stream; ++extern uschar *mime_current_boundary; ++ ++int regex(uschar **listptr) { ++ int sep = 0; ++ uschar *list = *listptr; ++ uschar *regex_string; ++ uschar regex_string_buffer[1024]; ++ unsigned long long mbox_size; ++ FILE *mbox_file; ++ pcre *re; ++ pcre_list *re_list_head = NULL; ++ pcre_list *re_list_item; ++ const char *pcre_error; ++ int pcre_erroffset; ++ uschar *linebuffer; ++ long f_pos = 0; ++ ++ /* reset expansion variable */ ++ regex_match_string = NULL; ++ ++ if (mime_stream == NULL) { ++ /* We are in the DATA ACL */ ++ mbox_file = spool_mbox(&mbox_size); ++ if (mbox_file == NULL) { ++ /* error while spooling */ ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "regex acl condition: error while creating mbox spool file"); ++ return DEFER; ++ }; ++ } ++ else { ++ f_pos = ftell(mime_stream); ++ mbox_file = mime_stream; ++ }; ++ ++ /* precompile our regexes */ ++ while ((regex_string = string_nextinlist(&list, &sep, ++ regex_string_buffer, ++ sizeof(regex_string_buffer))) != NULL) { ++ ++ /* parse option */ ++ if ( (strcmpic(regex_string,US"false") == 0) || ++ (Ustrcmp(regex_string,"0") == 0) ) { ++ /* explicitly no matching */ ++ continue; ++ }; ++ ++ /* compile our regular expression */ ++ re = pcre_compile( CS regex_string, ++ 0, ++ &pcre_error, ++ &pcre_erroffset, ++ NULL ); ++ ++ if (re == NULL) { ++ log_write(0, LOG_MAIN, ++ "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset); ++ continue; ++ } ++ else { ++ re_list_item = store_get(sizeof(pcre_list)); ++ re_list_item->re = re; ++ re_list_item->pcre_text = string_copy(regex_string); ++ re_list_item->next = re_list_head; ++ re_list_head = re_list_item; ++ }; ++ }; ++ ++ /* no regexes -> nothing to do */ ++ if (re_list_head == NULL) { ++ return FAIL; ++ }; ++ ++ /* match each line against all regexes */ ++ linebuffer = store_get(32767); ++ while (fgets(CS linebuffer, 32767, mbox_file) != NULL) { ++ if ( (mime_stream != NULL) && (mime_current_boundary != NULL) ) { ++ /* check boundary */ ++ if (Ustrncmp(linebuffer,"--",2) == 0) { ++ if (Ustrncmp((linebuffer+2),mime_current_boundary,Ustrlen(mime_current_boundary)) == 0) ++ /* found boundary */ ++ break; ++ }; ++ }; ++ re_list_item = re_list_head; ++ do { ++ /* try matcher on the line */ ++ if (pcre_exec(re_list_item->re, NULL, CS linebuffer, ++ (int)Ustrlen(linebuffer), 0, 0, NULL, 0) >= 0) { ++ Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023); ++ regex_match_string = regex_match_string_buffer; ++ if (mime_stream == NULL) ++ fclose(mbox_file); ++ else { ++ clearerr(mime_stream); ++ fseek(mime_stream,f_pos,SEEK_SET); ++ }; ++ return OK; ++ }; ++ re_list_item = re_list_item->next; ++ } while (re_list_item != NULL); ++ }; ++ ++ if (mime_stream == NULL) ++ fclose(mbox_file); ++ else { ++ clearerr(mime_stream); ++ fseek(mime_stream,f_pos,SEEK_SET); ++ }; ++ ++ /* no matches ... */ ++ return FAIL; ++} ++ ++ ++int mime_regex(uschar **listptr) { ++ int sep = 0; ++ uschar *list = *listptr; ++ uschar *regex_string; ++ uschar regex_string_buffer[1024]; ++ pcre *re; ++ pcre_list *re_list_head = NULL; ++ pcre_list *re_list_item; ++ const char *pcre_error; ++ int pcre_erroffset; ++ FILE *f; ++ uschar *mime_subject = NULL; ++ int mime_subject_len = 0; ++ ++ /* reset expansion variable */ ++ regex_match_string = NULL; ++ ++ /* precompile our regexes */ ++ while ((regex_string = string_nextinlist(&list, &sep, ++ regex_string_buffer, ++ sizeof(regex_string_buffer))) != NULL) { ++ ++ /* parse option */ ++ if ( (strcmpic(regex_string,US"false") == 0) || ++ (Ustrcmp(regex_string,"0") == 0) ) { ++ /* explicitly no matching */ ++ continue; ++ }; ++ ++ /* compile our regular expression */ ++ re = pcre_compile( CS regex_string, ++ 0, ++ &pcre_error, ++ &pcre_erroffset, ++ NULL ); ++ ++ if (re == NULL) { ++ log_write(0, LOG_MAIN, ++ "regex acl condition warning - error in regex '%s': %s at offset %d, skipped.", regex_string, pcre_error, pcre_erroffset); ++ continue; ++ } ++ else { ++ re_list_item = store_get(sizeof(pcre_list)); ++ re_list_item->re = re; ++ re_list_item->pcre_text = string_copy(regex_string); ++ re_list_item->next = re_list_head; ++ re_list_head = re_list_item; ++ }; ++ }; ++ ++ /* no regexes -> nothing to do */ ++ if (re_list_head == NULL) { ++ return FAIL; ++ }; ++ ++ /* check if the file is already decoded */ ++ if (mime_decoded_filename == NULL) { ++ uschar *empty = US""; ++ /* no, decode it first */ ++ mime_decode(&empty); ++ if (mime_decoded_filename == NULL) { ++ /* decoding failed */ ++ log_write(0, LOG_MAIN, ++ "mime_regex acl condition warning - could not decode MIME part to file."); ++ return DEFER; ++ }; ++ }; ++ ++ ++ /* open file */ ++ f = fopen(CS mime_decoded_filename, "r"); ++ if (f == NULL) { ++ /* open failed */ ++ log_write(0, LOG_MAIN, ++ "mime_regex acl condition warning - can't open '%s' for reading.", mime_decoded_filename); ++ return DEFER; ++ }; ++ ++ /* get 32k memory */ ++ mime_subject = (uschar *)store_get(32767); ++ ++ /* read max 32k chars from file */ ++ mime_subject_len = fread(mime_subject, 1, 32766, f); ++ ++ re_list_item = re_list_head; ++ do { ++ /* try matcher on the mmapped file */ ++ debug_printf("Matching '%s'\n", re_list_item->pcre_text); ++ if (pcre_exec(re_list_item->re, NULL, CS mime_subject, ++ mime_subject_len, 0, 0, NULL, 0) >= 0) { ++ Ustrncpy(regex_match_string_buffer, re_list_item->pcre_text, 1023); ++ regex_match_string = regex_match_string_buffer; ++ return OK; ++ }; ++ re_list_item = re_list_item->next; ++ } while (re_list_item != NULL); ++ ++ /* no matches ... */ ++ return FAIL; ++} ++ +diff -urN exim-4.32-orig/src/route.c exim-4.32/src/route.c +--- exim-4.32-orig/src/route.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/route.c Thu Apr 15 13:39:38 2004 +@@ -10,6 +10,9 @@ + + #include "exim.h" + ++#ifdef BRIGHTMAIL ++#include "bmi_spam.h" ++#endif + + + /* Generic options for routers, all of which live inside router_instance +@@ -32,6 +35,16 @@ + (void *)(offsetof(router_instance, address_data)) }, + { "address_test", opt_bool|opt_public, + (void *)(offsetof(router_instance, address_test)) }, ++#ifdef BRIGHTMAIL ++ { "bmi_deliver_alternate", opt_bool | opt_public, ++ (void *)(offsetof(router_instance, bmi_deliver_alternate)) }, ++ { "bmi_deliver_default", opt_bool | opt_public, ++ (void *)(offsetof(router_instance, bmi_deliver_default)) }, ++ { "bmi_dont_deliver", opt_bool | opt_public, ++ (void *)(offsetof(router_instance, bmi_dont_deliver)) }, ++ { "bmi_rule", opt_stringptr|opt_public, ++ (void *)(offsetof(router_instance, bmi_rule)) }, ++#endif + { "cannot_route_message", opt_stringptr | opt_public, + (void *)(offsetof(router_instance, cannot_route_message)) }, + { "caseful_local_part", opt_bool | opt_public, +@@ -978,6 +991,49 @@ + } + } + ++#ifdef BRIGHTMAIL ++ ++/* check if a specific Brightmail AntiSpam rule fired on the message */ ++if (r->bmi_rule != NULL) { ++ DEBUG(D_route) debug_printf("checking bmi_rule\n"); ++ if (bmi_check_rule(bmi_base64_verdict, r->bmi_rule) == 0) { ++ /* none of the rules fired */ ++ DEBUG(D_route) ++ debug_printf("%s router skipped: none of bmi_rule rules fired\n", r->name); ++ return SKIP; ++ }; ++}; ++ ++/* check if message should not be delivered */ ++if (r->bmi_dont_deliver) { ++ if (bmi_deliver == 1) { ++ DEBUG(D_route) ++ debug_printf("%s router skipped: bmi_dont_deliver is FALSE\n", r->name); ++ return SKIP; ++ }; ++}; ++ ++/* check if message should go to an alternate location */ ++if (r->bmi_deliver_alternate) { ++ if ((bmi_deliver == 0) || (bmi_alt_location == NULL)) { ++ DEBUG(D_route) ++ debug_printf("%s router skipped: bmi_deliver_alternate is FALSE\n", r->name); ++ return SKIP; ++ }; ++}; ++ ++/* check if message should go to default location */ ++if (r->bmi_deliver_default) { ++ if ((bmi_deliver == 0) || (bmi_alt_location != NULL)) { ++ DEBUG(D_route) ++ debug_printf("%s router skipped: bmi_deliver_default is FALSE\n", r->name); ++ return SKIP; ++ }; ++}; ++ ++#endif ++ ++ + /* All the checks passed. */ + + return OK; +diff -urN exim-4.32-orig/src/smtp_in.c exim-4.32/src/smtp_in.c +--- exim-4.32-orig/src/smtp_in.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/smtp_in.c Thu Apr 15 13:39:38 2004 +@@ -790,6 +790,7 @@ + acl_warn_headers = NULL; + queue_only_policy = FALSE; + deliver_freeze = FALSE; /* Can be set by ACL */ ++fake_reject = FALSE; /* Can be set by ACL */ + sender_address = NULL; + raw_sender = NULL; /* After SMTP rewrite, before qualifying */ + sender_address_unrewritten = NULL; /* Set only after verify rewrite */ +@@ -797,6 +798,10 @@ + memset(sender_address_cache, 0, sizeof(sender_address_cache)); + memset(sender_domain_cache, 0, sizeof(sender_domain_cache)); + authenticated_sender = NULL; ++#ifdef BRIGHTMAIL ++bmi_run = 0; ++bmi_verdicts = NULL; ++#endif + + for (i = 0; i < ACL_M_MAX; i++) acl_var[ACL_C_MAX + i] = NULL; + +@@ -1746,8 +1751,10 @@ + BOOL drop = rc == FAIL_DROP; + uschar *lognl; + uschar *sender_info = US""; +-uschar *what = (where == ACL_WHERE_DATA)? US"after DATA" : +- string_sprintf("%s %s", acl_wherenames[where], smtp_data); ++uschar *what = string_sprintf("%s %s", acl_wherenames[where], smtp_data); ++ ++if (where == ACL_WHERE_DATA) what = US"after DATA"; ++if (where == ACL_WHERE_MIME) what = US"during MIME ACL checks"; + + if (drop) rc = FAIL; + +@@ -1757,7 +1764,7 @@ + this is what should be logged, so I've changed to logging the unrewritten + address to retain backward compatibility. */ + +-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA) ++if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME) + { + sender_info = string_sprintf("F=<%s> ", (sender_address_unrewritten != NULL)? + sender_address_unrewritten : sender_address); +diff -urN exim-4.32-orig/src/spam.c exim-4.32/src/spam.c +--- exim-4.32-orig/src/spam.c Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/spam.c Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,285 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++patch. It is NOT part of the standard exim distribution. */ ++ ++/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */ ++/* License: GPL */ ++ ++/* Code for calling spamassassin's spamd. Called from acl.c. */ ++ ++#include "exim.h" ++#include "spam.h" ++ ++uschar spam_score_buffer[16]; ++uschar spam_score_int_buffer[16]; ++uschar spam_bar_buffer[128]; ++uschar spam_report_buffer[32600]; ++uschar prev_user_name[128]; ++int spam_ok = 0; ++int spam_rc = 0; ++ ++int spam(uschar **listptr) { ++ int sep = 0; ++ uschar *list = *listptr; ++ uschar *user_name; ++ uschar user_name_buffer[128]; ++ unsigned long long mbox_size; ++ FILE *mbox_file; ++ int spamd_sock; ++ uschar tcp_addr[24]; ++ unsigned int tcp_port; ++ uschar spamd_buffer[32600]; ++ int i, j, offset; ++ uschar spamd_version[8]; ++ uschar spamd_score_char; ++ double spamd_threshold, spamd_score; ++ int spamd_report_offset; ++ uschar *p,*q; ++ int override = 0; ++ struct sockaddr_un server; ++ ++ /* find the username from the option list */ ++ if ((user_name = string_nextinlist(&list, &sep, ++ user_name_buffer, ++ sizeof(user_name_buffer))) == NULL) { ++ /* no username given, this means no scanning should be done */ ++ return FAIL; ++ }; ++ ++ /* if username is "0" or "false", do not scan */ ++ if ( (Ustrcmp(user_name,"0") == 0) || ++ (strcmpic(user_name,US"false") == 0) ) { ++ return FAIL; ++ }; ++ ++ /* if there is an additional option, check if it is "true" */ ++ if (strcmpic(list,US"true") == 0) { ++ /* in that case, always return true later */ ++ override = 1; ++ }; ++ ++ /* if we scanned for this username last time, just return */ ++ if ( spam_ok && ( Ustrcmp(prev_user_name, user_name) == 0 ) ) { ++ if (override) ++ return OK; ++ else ++ return spam_rc; ++ }; ++ ++ /* make sure the eml mbox file is spooled up */ ++ mbox_file = spool_mbox(&mbox_size); ++ ++ if (mbox_file == NULL) { ++ /* error while spooling */ ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "spam acl condition: error while creating mbox spool file"); ++ return DEFER; ++ }; ++ ++ /* socket does not start with '/' -> network socket */ ++ if (*spamd_address != '/') { ++ ++ /* grok spamd address and port */ ++ if( sscanf(CS spamd_address, "%s %u", tcp_addr, &tcp_port) != 2 ) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "spam acl condition: invalid spamd address: '%s'", spamd_address); ++ fclose(mbox_file); ++ return DEFER; ++ }; ++ ++ /* contact spamd */ ++ if ( (spamd_sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "spam acl condition: error creating IP socket for spamd"); ++ fclose(mbox_file); ++ return DEFER; ++ }; ++ ++ if (ip_connect(spamd_sock, AF_INET, tcp_addr, tcp_port, 5) < 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "spam acl condition: spamd connection to %s, port %u failed: %s", tcp_addr, tcp_port, strerror(errno)); ++ fclose(mbox_file); ++ close(spamd_sock); ++ return DEFER; ++ }; ++ ++ } ++ else { ++ /* open the local socket */ ++ ++ if ((spamd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: spamd: unable to acquire socket (%s)", ++ strerror(errno)); ++ fclose(mbox_file); ++ return DEFER; ++ } ++ ++ server.sun_family = AF_UNIX; ++ Ustrcpy(server.sun_path, spamd_address); ++ ++ if (connect(spamd_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "malware acl condition: spamd: unable to connect to UNIX socket %s (%s)", ++ spamd_address, strerror(errno) ); ++ fclose(mbox_file); ++ close(spamd_sock); ++ return DEFER; ++ } ++ ++ } ++ ++ /* now we are connected to spamd on spamd_sock */ ++ snprintf(CS spamd_buffer, ++ sizeof(spamd_buffer), ++ "REPORT SPAMC/1.2\r\nUser: %s\r\nContent-length: %lld\r\n\r\n", ++ user_name, ++ mbox_size); ++ ++ /* send our request */ ++ if (send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) { ++ close(spamd_sock); ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "spam acl condition: spamd send failed: %s", strerror(errno)); ++ fclose(mbox_file); ++ close(spamd_sock); ++ return DEFER; ++ }; ++ ++ /* now send the file */ ++ do { ++ j = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file); ++ if (j > 0) { ++ i = send(spamd_sock,spamd_buffer,j,0); ++ if (i != j) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "spam acl condition: error/short send to spamd"); ++ close(spamd_sock); ++ fclose(mbox_file); ++ return DEFER; ++ }; ++ }; ++ } ++ while (j > 0); ++ ++ fclose(mbox_file); ++ ++ /* we're done sending, close socket for writing */ ++ shutdown(spamd_sock,SHUT_WR); ++ ++ /* read spamd response */ ++ memset(spamd_buffer, 0, sizeof(spamd_buffer)); ++ offset = 0; ++ while((i = ip_recv(spamd_sock, ++ spamd_buffer + offset, ++ sizeof(spamd_buffer) - offset - 1, ++ SPAMD_READ_TIMEOUT)) > 0 ) { ++ offset += i; ++ } ++ ++ /* error handling */ ++ if((i <= 0) && (errno != 0)) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "spam acl condition: error reading from spamd socket: %s", strerror(errno)); ++ close(spamd_sock); ++ return DEFER; ++ } ++ ++ /* reading done */ ++ close(spamd_sock); ++ ++ /* dig in the spamd output and put the report in a multiline header, if requested */ ++ if( sscanf(CS spamd_buffer,"SPAMD/%s 0 EX_OK\r\nContent-length: %*u\r\n\r\n%lf/%lf\r\n%n", ++ spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { ++ ++ /* try to fall back to pre-2.50 spamd output */ ++ if( sscanf(CS spamd_buffer,"SPAMD/%s 0 EX_OK\r\nSpam: %*s ; %lf / %lf\r\n\r\n%n", ++ spamd_version,&spamd_score,&spamd_threshold,&spamd_report_offset) != 3 ) { ++ log_write(0, LOG_MAIN|LOG_PANIC, ++ "spam acl condition: cannot parse spamd output"); ++ return DEFER; ++ }; ++ }; ++ ++ /* Create report. Since this is a multiline string, ++ we must hack it into shape first */ ++ p = &spamd_buffer[spamd_report_offset]; ++ q = spam_report_buffer; ++ while (*p != '\0') { ++ /* skip \r */ ++ if (*p == '\r') { ++ p++; ++ continue; ++ }; ++ *q = *p; ++ q++; ++ if (*p == '\n') { ++ *q = '\t'; ++ q++; ++ /* eat whitespace */ ++ while( (*p <= ' ') && (*p != '\0') ) { ++ p++; ++ }; ++ p--; ++ }; ++ p++; ++ }; ++ /* NULL-terminate */ ++ *q = '\0'; ++ q--; ++ /* cut off trailing leftovers */ ++ while (*q <= ' ') { ++ *q = '\0'; ++ q--; ++ }; ++ spam_report = spam_report_buffer; ++ ++ /* create spam bar */ ++ spamd_score_char = spamd_score > 0 ? '+' : '-'; ++ j = abs((int)(spamd_score)); ++ i = 0; ++ if( j != 0 ) { ++ while((i < j) && (i <= MAX_SPAM_BAR_CHARS)) ++ spam_bar_buffer[i++] = spamd_score_char; ++ } ++ else{ ++ spam_bar_buffer[0] = '/'; ++ i = 1; ++ } ++ spam_bar_buffer[i] = '\0'; ++ spam_bar = spam_bar_buffer; ++ ++ /* create "float" spam score */ ++ snprintf(CS spam_score_buffer, sizeof(spam_score_buffer),"%.1f", spamd_score); ++ spam_score = spam_score_buffer; ++ ++ /* create "int" spam score */ ++ j = (int)(spamd_score*10); ++ snprintf(CS spam_score_int_buffer, sizeof(spam_score_int_buffer), "%d", j); ++ spam_score_int = spam_score_int_buffer; ++ ++ /* compare threshold against score */ ++ if (spamd_score >= spamd_threshold) { ++ /* spam as determined by user's threshold */ ++ spam_rc = OK; ++ } ++ else { ++ /* not spam */ ++ spam_rc = FAIL; ++ }; ++ ++ /* remember user name and "been here" for it */ ++ Ustrcpy(prev_user_name, user_name); ++ spam_ok = 1; ++ ++ if (override) { ++ /* always return OK, no matter what the score */ ++ return OK; ++ } ++ else { ++ return spam_rc; ++ }; ++} +diff -urN exim-4.32-orig/src/spam.h exim-4.32/src/spam.h +--- exim-4.32-orig/src/spam.h Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/spam.h Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,23 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++patch. It is NOT part of the standard exim distribution. */ ++ ++/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */ ++/* License: GPL */ ++ ++/* spam defines */ ++ ++/* timeout for reading from spamd */ ++#define SPAMD_READ_TIMEOUT 3600 ++ ++/* maximum length of the spam bar */ ++#define MAX_SPAM_BAR_CHARS 50 ++ ++/* SHUT_WR seems to be undefined on Unixware ? */ ++#ifndef SHUT_WR ++#define SHUT_WR 1 ++#endif ++ +diff -urN exim-4.32-orig/src/spool_in.c exim-4.32/src/spool_in.c +--- exim-4.32-orig/src/spool_in.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/spool_in.c Thu Apr 15 13:39:38 2004 +@@ -241,6 +241,7 @@ + body_linecount = 0; + deliver_firsttime = FALSE; + deliver_freeze = FALSE; ++fake_reject = FALSE; + deliver_frozen_at = 0; + deliver_manual_thaw = FALSE; + /* dont_deliver must NOT be reset */ +@@ -250,6 +251,7 @@ + interface_port = 0; + local_error_message = FALSE; + local_scan_data = NULL; ++spam_score_int = NULL; + message_linecount = 0; + received_protocol = NULL; + received_count = 0; +@@ -266,6 +268,11 @@ + sender_set_untrusted = FALSE; + tree_nonrecipients = NULL; + ++#ifdef BRIGHTMAIL ++bmi_run = 0; ++bmi_verdicts = NULL; ++#endif ++ + #ifdef SUPPORT_TLS + tls_certificate_verified = FALSE; + tls_cipher = NULL; +@@ -364,6 +371,12 @@ + local_error_message = TRUE; + else if (Ustrncmp(big_buffer, "-local_scan ", 12) == 0) + local_scan_data = string_copy(big_buffer + 12); ++ else if (Ustrncmp(big_buffer, "-spam_score_int ", 16) == 0) ++ spam_score_int = string_copy(big_buffer + 16); ++#ifdef BRIGHTMAIL ++ else if (Ustrncmp(big_buffer, "-bmi_verdicts ", 14) == 0) ++ bmi_verdicts = string_copy(big_buffer + 14); ++#endif + else if (Ustrcmp(big_buffer, "-host_lookup_failed") == 0) + host_lookup_failed = TRUE; + else if (Ustrncmp(big_buffer, "-body_linecount", 15) == 0) +diff -urN exim-4.32-orig/src/spool_mbox.c exim-4.32/src/spool_mbox.c +--- exim-4.32-orig/src/spool_mbox.c Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/spool_mbox.c Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,187 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++patch. It is NOT part of the standard exim distribution. */ ++ ++/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-???? */ ++/* License: GPL */ ++ ++/* Code for setting up a MBOX style spool file inside a /scan/<msgid> ++sub directory of exim's spool directory. */ ++ ++#include "exim.h" ++ ++/* externals, we must reset them on unspooling */ ++extern int demime_ok; ++extern int malware_ok; ++extern int spam_ok; ++extern struct file_extension *file_extensions; ++ ++int spool_mbox_ok = 0; ++uschar spooled_message_id[17]; ++ ++/* returns a pointer to the FILE, and puts the size in bytes into mbox_file_size */ ++ ++FILE *spool_mbox(unsigned long long *mbox_file_size) { ++ uschar mbox_path[1024]; ++ uschar message_subdir[2]; ++ uschar data_buffer[65535]; ++ FILE *mbox_file; ++ FILE *data_file = NULL; ++ header_line *my_headerlist; ++ struct stat statbuf; ++ int i,j; ++ uschar *received; ++ uschar *timestamp; ++ ++ if (!spool_mbox_ok) { ++ /* create scan directory, if not present */ ++ if (!directory_make(spool_directory, US "scan", 0750, FALSE)) { ++ debug_printf("unable to create directory: %s/scan\n", spool_directory); ++ return NULL; ++ }; ++ ++ /* create temp directory inside scan dir */ ++ snprintf(CS mbox_path, 1024, "%s/scan/%s", spool_directory, message_id); ++ if (!directory_make(NULL, mbox_path, 0750, FALSE)) { ++ debug_printf("unable to create directory: %s/scan/%s\n", spool_directory, message_id); ++ return NULL; ++ }; ++ ++ /* open [message_id].eml file for writing */ ++ snprintf(CS mbox_path, 1024, "%s/scan/%s/%s.eml", spool_directory, message_id, message_id); ++ mbox_file = Ufopen(mbox_path,"w"); ++ ++ if (mbox_file == NULL) { ++ debug_printf("unable to open file for writing: %s\n", mbox_path); ++ return NULL; ++ }; ++ ++ /* Generate a preliminary Received: header and put it in the file. ++ We need to do this so SA can do DNS list checks */ ++ timestamp = expand_string(US"${tod_full}"); ++ received = expand_string(received_header_text); ++ if (received != NULL) { ++ uschar *my_received; ++ if (received[0] == 0) { ++ my_received = string_sprintf("Received: ; %s\n", timestamp); ++ } ++ else { ++ my_received = string_sprintf("%s; %s\n", received, timestamp); ++ } ++ i = fwrite(my_received, 1, Ustrlen(my_received), mbox_file); ++ if (i != Ustrlen(my_received)) { ++ debug_printf("error/short write on writing in: %s", mbox_path); ++ fclose(mbox_file); ++ return NULL; ++ }; ++ }; ++ ++ ++ /* write all header lines to mbox file */ ++ my_headerlist = header_list; ++ while (my_headerlist != NULL) { ++ ++ /* skip deleted headers */ ++ if (my_headerlist->type == '*') { ++ my_headerlist = my_headerlist->next; ++ continue; ++ }; ++ ++ i = fwrite(my_headerlist->text, 1, my_headerlist->slen, mbox_file); ++ if (i != my_headerlist->slen) { ++ debug_printf("error/short write on writing in: %s", mbox_path); ++ fclose(mbox_file); ++ return NULL; ++ }; ++ ++ my_headerlist = my_headerlist->next; ++ }; ++ ++ /* copy body file */ ++ message_subdir[1] = '\0'; ++ for (i = 0; i < 2; i++) { ++ message_subdir[0] = (split_spool_directory == (i == 0))? message_id[5] : 0; ++ sprintf(CS mbox_path, "%s/input/%s/%s-D", spool_directory, message_subdir, message_id); ++ data_file = Ufopen(mbox_path,"r"); ++ if (data_file != NULL) ++ break; ++ }; ++ ++ fread(data_buffer, 1, 18, data_file); ++ ++ do { ++ j = fread(data_buffer, 1, sizeof(data_buffer), data_file); ++ if (j > 0) { ++ i = fwrite(data_buffer, 1, j, mbox_file); ++ if (i != j) { ++ debug_printf("error/short write on writing in: %s", mbox_path); ++ fclose(mbox_file); ++ fclose(data_file); ++ return NULL; ++ }; ++ }; ++ } while (j > 0); ++ ++ fclose(data_file); ++ fclose(mbox_file); ++ Ustrcpy(spooled_message_id, message_id); ++ spool_mbox_ok = 1; ++ }; ++ ++ snprintf(CS mbox_path, 1024, "%s/scan/%s/%s.eml", spool_directory, message_id, message_id); ++ ++ /* get the size of the mbox message */ ++ stat(CS mbox_path, &statbuf); ++ *mbox_file_size = statbuf.st_size; ++ ++ /* open [message_id].eml file for reading */ ++ mbox_file = Ufopen(mbox_path,"r"); ++ ++ return mbox_file; ++} ++ ++/* remove mbox spool file, demimed files and temp directory */ ++void unspool_mbox(void) { ++ ++ /* reset all exiscan state variables */ ++ demime_ok = 0; ++ demime_errorlevel = 0; ++ demime_reason = NULL; ++ file_extensions = NULL; ++ spam_ok = 0; ++ malware_ok = 0; ++ ++ ++ if (spool_mbox_ok) { ++ uschar mbox_path[1024]; ++ uschar file_path[1024]; ++ int n; ++ struct dirent *entry; ++ DIR *tempdir; ++ ++ spool_mbox_ok = 0; ++ ++ snprintf(CS mbox_path, 1024, "%s/scan/%s", spool_directory, spooled_message_id); ++ ++ tempdir = opendir(CS mbox_path); ++ /* loop thru dir & delete entries */ ++ n = 0; ++ do { ++ entry = readdir(tempdir); ++ if (entry == NULL) break; ++ snprintf(CS file_path, 1024,"%s/scan/%s/%s", spool_directory, spooled_message_id, entry->d_name); ++ if ( (Ustrcmp(entry->d_name,"..") != 0) && (Ustrcmp(entry->d_name,".") != 0) ) { ++ debug_printf("unspool_mbox(): unlinking '%s'\n", file_path); ++ n = unlink(CS file_path); ++ }; ++ } while (n > -1); ++ ++ closedir(tempdir); ++ ++ /* remove directory */ ++ n = rmdir(CS mbox_path); ++ }; ++} +diff -urN exim-4.32-orig/src/spool_out.c exim-4.32/src/spool_out.c +--- exim-4.32-orig/src/spool_out.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/spool_out.c Thu Apr 15 13:39:38 2004 +@@ -209,9 +209,14 @@ + if (sender_local) fprintf(f, "-local\n"); + if (local_error_message) fprintf(f, "-localerror\n"); + if (local_scan_data != NULL) fprintf(f, "-local_scan %s\n", local_scan_data); ++if (spam_score_int != NULL) fprintf(f,"-spam_score_int %s\n", spam_score_int); + if (deliver_manual_thaw) fprintf(f, "-manual_thaw\n"); + if (sender_set_untrusted) fprintf(f, "-sender_set_untrusted\n"); + ++#ifdef BRIGHTMAIL ++if (bmi_verdicts != NULL) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts); ++#endif ++ + #ifdef SUPPORT_TLS + if (tls_certificate_verified) fprintf(f, "-tls_certificate_verified\n"); + if (tls_cipher != NULL) fprintf(f, "-tls_cipher %s\n", tls_cipher); +diff -urN exim-4.32-orig/src/structs.h exim-4.32/src/structs.h +--- exim-4.32-orig/src/structs.h Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/structs.h Thu Apr 15 13:39:38 2004 +@@ -219,6 +219,9 @@ + uschar *driver_name; /* Must be first */ + + uschar *address_data; /* Arbitrary data */ ++#ifdef BRIGHTMAIL ++ uschar *bmi_rule; /* Brightmail AntiSpam rule checking */ ++#endif + uschar *cannot_route_message; /* Used when routing fails */ + uschar *condition; /* General condition */ + uschar *current_directory; /* For use during delivery */ +@@ -247,6 +250,11 @@ + uschar *transport_name; /* Transport name */ + + BOOL address_test; /* Use this router when testing addresses */ ++#ifdef BRIGHTMAIL ++ BOOL bmi_deliver_alternate; /* TRUE => BMI said that message should be delivered to alternate location */ ++ BOOL bmi_deliver_default; /* TRUE => BMI said that message should be delivered to default location */ ++ BOOL bmi_dont_deliver; /* TRUE => BMI said that message should not be delivered at all */ ++#endif + BOOL expn; /* Use this router when processing EXPN */ + BOOL caseful_local_part; /* TRUE => don't lowercase */ + BOOL check_local_user; /* TRUE => check local user */ +diff -urN exim-4.32-orig/src/tnef.c exim-4.32/src/tnef.c +--- exim-4.32-orig/src/tnef.c Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/tnef.c Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,757 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++patch. It is NOT part of the standard exim distribution. */ ++ ++/* Code for unpacking TNEF containers. Called from demime.c. */ ++ ++/*************************************************************************** ++ * tnef2txt ++* A program to decode application/ms-tnef MIME attachments into text ++* for those fortunate enough not to be running either a Microsoft ++* operating system or mailer. ++* ++ * 18/10/2001 ++* Brutally cropped by Paul L Daniels (pldaniels@pldaniels.com) in order ++* to accommodate the needs of ripMIME/Xamime/Inflex without carrying too ++* much excess baggage. ++* ++ * Brandon Long (blong@uiuc.edu), April 1997 ++* 1.0 Version ++* Supports most types, but doesn't decode properties. Maybe some other ++* time. ++* ++ * 1.1 Version (7/1/97) ++* Supports saving of attAttachData to a file given by attAttachTitle ++* start of property decoding support ++* ++ * 1.2 Version (7/19/97) ++* Some architectures don't like reading 16/32 bit data on unaligned ++* boundaries. Fixed, losing efficiency, but this doesn't really ++* need efficiency anyways. (Still...) ++* Also, the #pragma pack from the MSVC include file wasn't liked ++* by most Unix compilers, replaced with a GCCism. This should work ++* with GCC, but other compilers I don't know. ++* ++ * 1.3 Version (7/22/97) ++* Ok, take out the DTR over the stream, now uses read_16. ++* ++ * NOTE: THIS SOFTWARE IS FOR YOUR PERSONAL GRATIFICATION ONLY. I DON'T ++* IMPLY IN ANY LEGAL SENSE THAT THIS SOFTWARE DOES ANYTHING OR THAT IT WILL ++* BE USEFULL IN ANY WAY. But, you can send me fixes to it, I don't mind. ++***************************************************************************/ ++ ++#include <stdio.h> ++#include <sys/stat.h> ++#include <stdlib.h> ++#include <errno.h> ++#include <string.h> ++#include <netinet/in.h> ++#include "tnef.h" ++ ++ ++#define VERSION "pldtnef/0.0.1" ++ ++int _TNEF_syslogging = 0; ++int _TNEF_stderrlogging = 0; ++int _TNEF_verbose = 0; ++int _TNEF_debug = 0; ++ ++int Verbose = FALSE; ++int SaveData = FALSE; ++ ++char _TNEF_path[1024]=""; ++ ++uint8 *tnef_home; ++uint8 *tnef_limit; ++ ++/*------------------------------------------------------------------------ ++Procedure: TNEF_set_path ID:1 ++Purpose: ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++int TNEF_set_path( char *path ) ++{ ++ snprintf(_TNEF_path,1023,"%s",path); ++ ++ return 0; ++} ++ ++ ++/*------------------------------------------------------------------------ ++Procedure: TNEF_set_verbosity ID:1 ++Purpose: ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++int TNEF_set_verbosity( int level ) ++{ ++ _TNEF_verbose = level; ++ return _TNEF_verbose; ++} ++ ++ ++ ++ ++/*------------------------------------------------------------------------ ++Procedure: TNEF_set_debug ID:1 ++Purpose: ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++int TNEF_set_debug( int level ) ++{ ++ _TNEF_debug = level; ++ TNEF_set_verbosity( level ); ++ return _TNEF_debug; ++} ++ ++ ++ ++/*------------------------------------------------------------------------ ++Procedure: TNEF_set_syslogging ID:1 ++Purpose: Turns on/off the syslog feature for TNEF error messages ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++int TNEF_set_syslogging( int level ) ++{ ++ _TNEF_syslogging = level; ++ return _TNEF_syslogging; ++} ++ ++ ++ ++ ++/*------------------------------------------------------------------------ ++Procedure: TNEF_set_stderrlogging ID:1 ++Purpose: Turns on/off the stderr feature for TNEF error messages ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++int TNEF_set_stderrlogging( int level ) ++{ ++ _TNEF_stderrlogging = level; ++ return _TNEF_stderrlogging; ++} ++ ++ ++/* Some systems don't like to read unaligned data */ ++/*------------------------------------------------------------------------ ++Procedure: read_32 ID:1 ++Purpose: ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++uint32 read_32(uint8 *tsp) ++{ ++ uint8 a,b,c,d; ++ uint32 ret; ++ ++ if (tsp+4 > tnef_limit) ++ { ++ if ((_TNEF_verbose)||(_TNEF_stderrlogging)||(_TNEF_debug)) fprintf(stderr,"TNEF read_32() Attempting to read past end\n"); ++ return -1; ++ } ++ ++ a = *tsp; ++ b = *(tsp+1); ++ c = *(tsp+2); ++ d = *(tsp+3); ++ ++ ret = long_little_endian(a<<24 | b<<16 | c<<8 | d); ++ ++ return ret; ++} ++ ++/*------------------------------------------------------------------------ ++Procedure: read_16 ID:1 ++Purpose: ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++uint16 read_16(uint8 *tsp) ++{ ++ uint8 a,b; ++ uint16 ret; ++ ++ if (tsp+2 > tnef_limit) ++ { ++ if ((_TNEF_verbose)||(_TNEF_stderrlogging)||(_TNEF_debug)) fprintf(stderr,"TNEF read_16() Attempting to read past end\n"); ++ return -1; ++ } ++ ++ ++ a = *tsp; ++ b = *(tsp + 1); ++ ++ ret = little_endian(a<<8 | b); ++ ++ return ret; ++} ++ ++ ++ ++/*------------------------------------------------------------------------ ++Procedure: make_string ID:1 ++Purpose: ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++char *make_string(uint8 *tsp, int size) ++{ ++ static char s[256] = ""; ++ int len = (size>sizeof(s)-1) ? sizeof(s)-1 : size; ++ ++ strncpy(s,(char *)tsp, len); ++ s[len] = '\0'; ++ return s; ++} ++ ++ ++/*------------------------------------------------------------------------ ++Procedure: handle_props ID:1 ++Purpose: ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++ ++int save_attach_data(char *, uint8 *, uint32); ++ ++int handle_props(uint8 *tsp) ++{ ++ int bytes = 0; ++ uint32 num_props = 0; ++ uint32 x = 0; ++ ++ ++ num_props = read_32(tsp); ++ bytes += sizeof(num_props); ++ ++ while (x < num_props) ++ { ++ uint32 prop_tag; ++ uint32 num; ++ char filename[256]; ++ static int file_num = 0; ++ ++ prop_tag = read_32(tsp+bytes); ++ bytes += sizeof(prop_tag); ++ ++ switch (prop_tag & PROP_TYPE_MASK) ++ { ++ case PT_BINARY: ++ num = read_32(tsp+bytes); ++ bytes += sizeof(num); ++ num = read_32(tsp+bytes); ++ bytes += sizeof(num); ++ if (prop_tag == PR_RTF_COMPRESSED) ++ { ++ sprintf (filename, "XAM_%d.rtf", file_num); ++ file_num++; ++ save_attach_data(filename, tsp+bytes, num); ++ } ++ /* num + PAD */ ++ bytes += num + ((num % 4) ? (4 - num%4) : 0); ++ break; ++ case PT_STRING8: ++ num = read_32(tsp+bytes); ++ bytes += sizeof(num); ++ num = read_32(tsp+bytes); ++ bytes += sizeof(num); ++ make_string(tsp+bytes,num); ++ bytes += num + ((num % 4) ? (4 - num%4) : 0); ++ break; ++ case PT_UNICODE: ++ case PT_OBJECT: ++ break; ++ case PT_I2: ++ bytes += 2; ++ break; ++ case PT_LONG: ++ bytes += 4; ++ break; ++ case PT_R4: ++ bytes += 4; ++ break; ++ case PT_DOUBLE: ++ bytes += 8; ++ break; ++ case PT_CURRENCY: ++ case PT_APPTIME: ++ case PT_ERROR: ++ bytes += 4; ++ break; ++ case PT_BOOLEAN: ++ bytes += 4; ++ break; ++ case PT_I8: ++ bytes += 8; ++ case PT_SYSTIME: ++ bytes += 8; ++ break; ++ } ++ x++; ++ } ++ ++ return 0; ++} ++ ++ ++ ++ ++/*------------------------------------------------------------------------ ++Procedure: save_attach_data ID:1 ++Purpose: ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++int save_attach_data(char *title, uint8 *tsp, uint32 size) ++{ ++ FILE *out; ++ char filename[1024]; ++ ++ /* ++ if ((*tsp +size) > _TNEF_size) ++ { ++ return -1; ++ } ++ */ ++ snprintf(filename,1023,"%s/%s",_TNEF_path,title); ++ ++ out = fopen(filename, "w"); ++ if (!out) ++ { ++ if (_TNEF_stderrlogging > 0) fprintf(stderr, "Error openning file %s for writing\n", filename); ++ return -1; ++ } ++ ++ fwrite(tsp, sizeof(uint8), size, out); ++ fclose(out); ++ return 0; ++} ++ ++ ++ ++ ++/*------------------------------------------------------------------------ ++Procedure: default_handler ID:1 ++Purpose: ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++int default_handler(uint32 attribute, uint8 *tsp, uint32 size) ++{ ++ uint16 type = ATT_TYPE(attribute); ++ ++ switch (type) { ++ case atpTriples: ++ break; ++ case atpString: ++ case atpText: ++ break; ++ case atpDate: ++ break; ++ case atpShort: ++ break; ++ case atpLong: ++ break; ++ case atpByte: ++ break; ++ case atpWord: ++ break; ++ case atpDword: ++ break; ++ default: ++ break; ++ } ++ return 0; ++ ++} ++ ++ ++ ++ ++/*------------------------------------------------------------------------ ++Procedure: read_attribute ID:1 ++Purpose: ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++int read_attribute(uint8 *tsp) ++{ ++ ++ int bytes = 0, header = 0; ++ uint32 attribute; ++ uint8 component = 0; ++ uint32 size = 0; ++ uint16 checksum = 0; ++ static char attach_title[256] = { ++ 0 }; ++ static uint32 attach_size = 0; ++ static uint32 attach_loc = 0; ++ ++ /* What component are we look at? */ ++ component = *tsp; ++ ++ bytes += sizeof(uint8); ++ ++ /* Read the attributes of this component */ ++ ++ if (_TNEF_debug) fprintf(stderr,"read_attribute: Reading Attribute...\n"); ++ attribute = read_32(tsp+bytes); ++ if (attribute == -1) return -1; ++ bytes += sizeof(attribute); ++ ++ /* Read the size of the information we have to read */ ++ ++ if (_TNEF_debug) fprintf(stderr,"read_attribute: Reading Size...\n"); ++ size = read_32(tsp+bytes); ++ if (size == -1) return -1; ++ bytes += sizeof(size); ++ ++ /* The header size equals the sum of all the things we've read ++ so far. */ ++ ++ header = bytes; ++ ++ /* The is a bit of a tricky one [if you're being slow ++ it moves the number of bytes ahead by the amount of data of ++ the attribute we're about to read, so that for next ++ "read_attribute()" ++ call starts in the right place. ++ */ ++ ++ bytes += size; ++ ++ /* Read in the checksum for this component ++ ++ AMMENDMENT - 19/07/02 - 17H01 ++ Small code change to deal with strange sitations that occur with non ++ english characters. - Submitted by wtcheuk@netvigator.com @ 19/07/02 ++ */ ++ ++ if ( bytes < 0 ) return -1; ++ ++ /* --END of ammendment. */ ++ ++ if (_TNEF_debug) fprintf(stderr,"read_attribute: Reading Checksum...(offset %d, bytes=%d)\n", tsp -tnef_home, bytes); ++ checksum = read_16(tsp+bytes); ++ bytes += sizeof(checksum); ++ ++ if (_TNEF_debug) fprintf(stderr,"Decoding attribute %d\n",attribute); ++ ++ switch (attribute) { ++ case attNull: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attFrom: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attSubject: ++ break; ++ case attDateSent: ++ break; ++ case attDateRecd: ++ break; ++ case attMessageStatus: ++ break; ++ case attMessageClass: ++ break; ++ case attMessageID: ++ break; ++ case attParentID: ++ break; ++ case attConversationID: ++ break; ++ case attBody: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attPriority: ++ break; ++ case attAttachData: ++ attach_size=size; ++ attach_loc =(int)tsp+header; ++ if (SaveData && strlen(attach_title)>0 && attach_size > 0) { ++ if (!save_attach_data(attach_title, (uint8 *)attach_loc,attach_size)) ++ { ++ if (_TNEF_verbose) fprintf(stdout,"Decoding %s\n", attach_title); ++ } ++ else ++ { ++ if (_TNEF_syslogging > 0) syslog(1,"TNEF: Error saving attachment %s\n",attach_title); ++ } ++ } ++ break; ++ case attAttachTitle: ++ strncpy(attach_title, make_string(tsp+header,size),255); ++ if (SaveData && strlen(attach_title)>0 && attach_size > 0) { ++ if (!save_attach_data(attach_title, (uint8 *)attach_loc,attach_size)) ++ { ++ if (_TNEF_verbose) fprintf(stdout,"Decoding %s\n", attach_title); ++ } ++ else ++ { ++ if (_TNEF_syslogging > 0) syslog(1,"TNEF: Error saving attachment %s\n",attach_title); ++ } ++ } ++ break; ++ case attAttachMetaFile: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attAttachCreateDate: ++ break; ++ case attAttachModifyDate: ++ break; ++ case attDateModified: ++ break; ++ case attAttachTransportFilename: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attAttachRenddata: ++ attach_title[0]=0; ++ attach_size=0; ++ attach_loc=0; ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attMAPIProps: ++ handle_props(tsp+header); ++ break; ++ case attRecipTable: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attAttachment: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attTnefVersion: ++ { ++ uint32 version; ++ version = read_32(tsp+header); ++ if (version == -1) return -1; ++ } ++ break; ++ case attOemCodepage: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attOriginalMessageClass: ++ break; ++ case attOwner: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attSentFor: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attDelegate: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attDateStart: ++ break; ++ case attDateEnd: ++ break; ++ case attAidOwner: ++ default_handler(attribute, tsp+header, size); ++ break; ++ case attRequestRes: ++ default_handler(attribute, tsp+header, size); ++ break; ++ default: ++ default_handler(attribute, tsp+header, size); ++ break; ++ } ++ return bytes; ++ ++} ++ ++ ++ ++ ++/*------------------------------------------------------------------------ ++Procedure: decode_tnef ID:1 ++Purpose: ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++int TNEF_decode_tnef(uint8 *tnef_stream, int size) ++{ ++ ++ int ra_response; ++ uint8 *tsp; ++ ++ if (_TNEF_debug) fprintf(stderr,"TNEF_decode_tnef: Start. Size = %d\n",size); ++ ++ if (size < 4) ++ { ++ if (_TNEF_debug) fprintf(stderr,"TNEF_decode_tnef: Skipping short file\n"); ++ return 0; ++ } ++ ++ /* TSP == TNEF Stream Pointer (well memory block actually!) ++ */ ++ tsp = tnef_stream; ++ ++ /* Read in the signature of this TNEF ++ */ ++ if (TNEF_SIGNATURE == read_32(tsp)) ++ { ++ if (_TNEF_debug) fprintf(stderr,"TNEF signature is good\n"); ++ } ++ else ++ { ++ if (_TNEF_stderrlogging > 0) fprintf(stderr,"TNEF_decode_tnef: Bad TNEF signature, expecting %x got %lx\n",TNEF_SIGNATURE,read_32(tsp)); ++ } ++ ++ /* Move tsp pointer along ++ */ ++ tsp += sizeof(TNEF_SIGNATURE); ++ ++ /* This extra check is here just in case we're running with ++ * _TNEF_debug set and try to calculate TNEF Attach Key ++ * when we shouldn't. ++ */ ++ if (tsp + sizeof(uint16) - tnef_stream > size) ++ { ++ if (_TNEF_debug) fprintf(stderr,"TNEF_decode_tnef: Skipping short file\n"); ++ return 0; ++ } ++ ++ if (_TNEF_debug) fprintf(stderr,"TNEF Attach Key: %x\n",read_16(tsp)); ++ /* Move tsp pointer along ++ */ ++ tsp += sizeof(uint16); ++ ++ /* While we still have more bytes to process, ++ go through entire memory block and extract ++ all the required attributes and files ++ */ ++ if (_TNEF_debug) fprintf(stderr,"TNEF - Commence reading attributes\n"); ++ while ((tsp - tnef_stream) < size) ++ { ++ if (_TNEF_debug) fprintf(stderr,"Offset = %d\n",tsp -tnef_home); ++ ra_response = read_attribute(tsp); ++ if ( ra_response > 0 ) ++ { ++ tsp += ra_response; ++ } else { ++ ++ /* Must find out /WHY/ this happens, and, how to rectify the issue. */ ++ ++ tsp++; ++ if (_TNEF_debug) fprintf(stderr,"TNEF - Attempting to read attribute resulted in a sub-zero response, ending decoding to be safe\n"); ++ break; ++ } ++ } ++ ++ if (_TNEF_debug) fprintf(stderr,"TNEF - DONE.\n"); ++ ++ return 0; ++} ++ ++ ++ ++ ++ ++ ++/*------------------------------------------------------------------------ ++Procedure: TNEF_main ID:1 ++Purpose: Decodes a given TNEF encoded file ++Input: ++Output: ++Errors: ++------------------------------------------------------------------------*/ ++int TNEF_main( char *filename ) ++{ ++ FILE *fp; ++ struct stat sb; ++ uint8 *tnef_stream; ++ int size, nread; ++ ++ if (_TNEF_debug) fprintf(stderr,"TNEF_main: Start, decoding %s\n",filename); ++ ++ SaveData = TRUE; ++ ++ /* Test to see if the file actually exists ++ */ ++ if (stat(filename,&sb) == -1) ++ { ++ if (_TNEF_stderrlogging > 0) fprintf(stderr,"Error stating file %s (%s)\n", filename,strerror(errno)); ++ return -1; ++ } ++ ++ /* Get the filesize */ ++ ++ size = sb.st_size; ++ ++ /* Allocate enough memory to read in the ENTIRE file ++ FIXME - This could be a real consumer if multiple ++ instances of TNEF decoding is going on ++ */ ++ ++ tnef_home = tnef_stream = (uint8 *)malloc(size); ++ tnef_limit = tnef_home +size; ++ ++ /* If we were unable to allocate enough memory, then we ++ should report this */ ++ ++ if (tnef_stream == NULL) ++ { ++ if (_TNEF_stderrlogging > 0) fprintf(stderr,"Error allocating %d bytes for loading file (%s)\n", size,strerror(errno)); ++ return -1; ++ } ++ ++ /* Attempt to open up the TNEF encoded file... if it fails ++ then report the failed condition to syslog */ ++ ++ if ((fp = fopen(filename,"r")) == NULL) ++ { ++ if (_TNEF_stderrlogging > 0) fprintf(stderr,"Error opening file %s for reading (%s)\n", filename,strerror(errno)); ++ return -1; ++ } ++ ++ /* Attempt to read in the entire file */ ++ ++ nread = fread(tnef_stream, sizeof(uint8), size, fp); ++ ++ if (_TNEF_debug) fprintf(stderr,"TNEF: Read %d bytes\n",nread); ++ ++ /* If we did not read in all the bytes, then let syslogs know! */ ++ ++ if (nread < size) ++ { ++ return -1; ++ } ++ ++ /* Close the file */ ++ ++ fclose(fp); ++ ++ /* Proceed to decode the file */ ++ ++ TNEF_decode_tnef(tnef_stream,size); ++ ++ ++ if (_TNEF_debug) fprintf(stderr,"TNEF - finished decoding.\n"); ++ ++ return 0; ++} ++ ++ ++/* --------------------------END. */ ++ ++ ++ +diff -urN exim-4.32-orig/src/tnef.h exim-4.32/src/tnef.h +--- exim-4.32-orig/src/tnef.h Thu Jan 1 01:00:00 1970 ++++ exim-4.32/src/tnef.h Thu Apr 15 13:39:38 2004 +@@ -0,0 +1,1841 @@ ++/************************************************* ++* Exim - an Internet mail transport agent * ++*************************************************/ ++ ++/* This file is part of the exiscan-acl content scanner ++patch. It is NOT part of the standard exim distribution. */ ++ ++/*************************************************************************** ++ * ++ * config.h for tnef decoder by Brandon Long ++ * Based on config.h from S3MOD by Dan Marks and David Jeske ++ * ++ * (C) 1994,1995 By Daniel Marks and David Jeske ++ * ++ * While we retain the copyright to this code, this source code is FREE. ++ * You may use it in any way you wish, in any product you wish. You may ++ * NOT steal the copyright for this code from us. ++ * ++ * We respectfully ask that you email one of us, if possible, if you ++ * produce something significant with this code, or if you have any bug ++ * fixes to contribute. We also request that you give credit where ++ * credit is due if you include part of this code in a program of your own. ++ * ++ *************************************************************************** ++ * ++ * config.h - compile time configuration options and system specific defines ++ * ++ */ ++ ++/* 2003-02-03 Merged all TNEF and MAPI related headers in this file to reduce ++ clutter ++ - Tom Kistner ++*/ ++ ++#include <exim.h> ++ ++#ifndef _CONFIG_H ++#define _CONFIG_H 1 ++ ++/***************************************************************************/ ++/* The following are system specific settings */ ++/***************************************************************************/ ++ ++#if defined(SUN) ++#define BIT_32 ++#define ___TNEF_BYTE_ORDER 4321 ++#undef NEAR_FAR_PTR ++ ++#elif defined (HPUX) ++#define BIT_32 ++#define ___TNEF_BYTE_ORDER 4321 ++#undef NEAR_FAR_PTR ++ ++#elif defined(DEC) ++#undef NEAR_FAR_PTR ++ ++#elif defined(__sgi) ++#define BIT_32 ++#define ___TNEF_BYTE_ORDER 4321 ++#undef NEAR_FAR_PTR ++ ++#elif defined(AIX) ++#undef NEAR_FAR_PTR ++#define ___TNEF_BYTE_ORDER 4321 ++#define BIT_32 ++ ++#elif defined(LINUX) ++#define BIT_32 ++#undef NEAR_FAR_PTR ++ ++#elif defined(MSDOS) ++#define NEAR_FAR_PTR ++#undef BIT_32 ++ ++#else ++#undef NEAR_FAR_PTR ++#define BIT_32 ++ ++ ++#endif /* OS/MACH TYPE */ ++ ++/***************************************************************************/ ++/* 16/32 Bit and Byte Order hacks */ ++/***************************************************************************/ ++ ++#ifdef BIT_32 ++typedef short int int16; ++typedef unsigned short int uint16; ++typedef int int32; ++typedef unsigned int uint32; ++/* typedef char int8; */ ++typedef unsigned char uint8; ++#else ++typedef int int16; ++typedef unsigned int uint16; ++typedef long int int32; ++typedef unsigned long int uint32; ++typedef char int8; ++typedef unsigned char uint8; ++#endif /* BIT_32 */ ++ ++#ifndef WIN32_TYPES ++#define ULONG uint32 ++#define SCODE uint32 ++#define FAR ++#define LPVOID void * ++#define WORD uint16 ++#define DWORD uint32 ++#define LONG int32 ++#define BYTE uint8 ++#endif /* !WIN32_TYPES */ ++ ++#define endian_switch(x) (((((uint16)(x)) & 0xFF00) >> 8) | \ ++ ((((uint16)(x)) & 0xFF) << 8)) ++ ++#define long_endian_switch(x) ( ((((uint32)(x)) & 0xFF00UL) << 8) | \ ++ ((((uint32)(x)) & 0xFFUL) << 24) | \ ++ ((((uint32)(x)) & 0xFF0000UL) >> 8) | \ ++ ((((uint32)(x)) & 0xFF000000UL) >> 24)) ++ ++#if ___TNEF_BYTE_ORDER == 4321 ++#define big_endian(x) (x) ++#define long_big_endian(x) (x) ++#define little_endian(x) (endian_switch(x)) ++#define long_little_endian(x) (long_endian_switch(x)) ++#else ++#define big_endian(x) (endian_switch(x)) ++#define long_big_endian(x) (long_endian_switch(x)) ++#define little_endian(x) (x) ++#define long_little_endian(x) (x) ++#endif /* ___TNEF_BYTE_ORDER */ ++ ++#ifndef TRUE ++#define TRUE 1 ++#endif ++#ifndef FALSE ++#define FALSE 0 ++#endif ++ ++ ++#endif /* _CONFIG_H */ ++/* ++ * Taken from the Win32 SDK or the MSVC4 include files, I'm not sure which. ++ * The document describing the TNEF format alludes to this document for more ++ * information. This file was stripped a bit to allow it to compile with ++ * GCC and without random other Windows header files so it could be used ++ * to decode TNEF bitstreams with tnef2txt. ++ * ++ * T N E F . H ++ * ++ * ++ * This file contains structure and function definitions for the ++ * MAPI implementation of the Transport Neutral Encapsilation Format ++ * used by MAPI providers for the neutral serialization of a MAPI ++ * message. This implementation sits on top of the IStream object as ++ * documented in the OLE 2 Specs. ++ * ++ * Copyright 1986-1996 Microsoft Corporation. All Rights Reserved. ++ */ ++ ++#ifndef TNEF_H ++#define TNEF_H ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++ ++#ifndef BEGIN_INTERFACE ++#define BEGIN_INTERFACE ++#endif ++ ++#ifndef MAPI_DIM ++#define MAPI_DIM 1 ++#endif ++ ++#define TNTNoffsetof(s,m) (unsigned long)&(((s *)0)->m) ++ ++/* ------------------------------------ */ ++/* TNEF Problem and TNEF Problem Arrays */ ++/* ------------------------------------ */ ++ ++typedef struct _STnefProblem ++{ ++ ULONG ulComponent; ++ ULONG ulAttribute; ++ ULONG ulPropTag; ++ SCODE scode; ++} STnefProblem; ++ ++typedef struct _STnefProblemArray ++{ ++ ULONG cProblem; ++ STnefProblem aProblem[MAPI_DIM]; ++} STnefProblemArray, FAR * LPSTnefProblemArray; ++ ++#if 0 ++#define CbNewSTnefProblemArray(_cprob) \ ++ (TNoffsetof(STnefProblemArray,aProblem) + (_cprob)*sizeof(STnefProblem)) ++#define CbSTnefProblemArray(_lparray) \ ++ (TNoffsetof(STnefProblemArray,aProblem) + \ ++ (UINT) ((_lparray)->cProblem*sizeof(STnefProblem))) ++#endif ++ ++/* Pointers to TNEF Interface ---------------------------------------- */ ++ ++#if 0 ++DECLARE_MAPI_INTERFACE_PTR(ITnef, LPITNEF); ++#endif ++ ++/* OpenTNEFStream */ ++ ++#define TNEF_DECODE ((ULONG) 0) ++#define TNEF_ENCODE ((ULONG) 2) ++ ++#define TNEF_PURE ((ULONG) 0x00010000) ++#define TNEF_COMPATIBILITY ((ULONG) 0x00020000) ++#define TNEF_BEST_DATA ((ULONG) 0x00040000) ++#define TNEF_COMPONENT_ENCODING ((ULONG) 0x80000000) ++ ++/* AddProps, ExtractProps */ ++ ++#define TNEF_PROP_INCLUDE ((ULONG) 0x00000001) ++#define TNEF_PROP_EXCLUDE ((ULONG) 0x00000002) ++#define TNEF_PROP_CONTAINED ((ULONG) 0x00000004) ++#define TNEF_PROP_MESSAGE_ONLY ((ULONG) 0x00000008) ++#define TNEF_PROP_ATTACHMENTS_ONLY ((ULONG) 0x00000010) ++#define TNEF_PROP_CONTAINED_TNEF ((ULONG) 0x00000040) ++ ++/* FinishComponent */ ++ ++#define TNEF_COMPONENT_MESSAGE ((ULONG) 0x00001000) ++#define TNEF_COMPONENT_ATTACHMENT ((ULONG) 0x00002000) ++ ++#if 0 ++#define MAPI_ITNEF_METHODS(IPURE) \ ++ MAPIMETHOD(AddProps) \ ++ (THIS_ ULONG ulFlags, \ ++ ULONG ulElemID, \ ++ LPVOID lpvData, \ ++ LPSPropTagArray lpPropList) IPURE; \ ++ MAPIMETHOD(ExtractProps) \ ++ (THIS_ ULONG ulFlags, \ ++ LPSPropTagArray lpPropList, \ ++ LPSTnefProblemArray FAR * lpProblems) IPURE; \ ++ MAPIMETHOD(Finish) \ ++ (THIS_ ULONG ulFlags, \ ++ WORD FAR * lpKey, \ ++ LPSTnefProblemArray FAR * lpProblems) IPURE; \ ++ MAPIMETHOD(OpenTaggedBody) \ ++ (THIS_ LPMESSAGE lpMessage, \ ++ ULONG ulFlags, \ ++ LPSTREAM FAR * lppStream) IPURE; \ ++ MAPIMETHOD(SetProps) \ ++ (THIS_ ULONG ulFlags, \ ++ ULONG ulElemID, \ ++ ULONG cValues, \ ++ LPSPropValue lpProps) IPURE; \ ++ MAPIMETHOD(EncodeRecips) \ ++ (THIS_ ULONG ulFlags, \ ++ LPMAPITABLE lpRecipientTable) IPURE; \ ++ MAPIMETHOD(FinishComponent) \ ++ (THIS_ ULONG ulFlags, \ ++ ULONG ulComponentID, \ ++ LPSPropTagArray lpCustomPropList, \ ++ LPSPropValue lpCustomProps, \ ++ LPSPropTagArray lpPropList, \ ++ LPSTnefProblemArray FAR * lpProblems) IPURE; \ ++ ++#undef INTERFACE ++#define INTERFACE ITnef ++DECLARE_MAPI_INTERFACE_(ITnef, IUnknown) ++{ ++ BEGIN_INTERFACE ++ MAPI_IUNKNOWN_METHODS(PURE) ++ MAPI_ITNEF_METHODS(PURE) ++}; ++ ++STDMETHODIMP OpenTnefStream( ++ LPVOID lpvSupport, ++ LPSTREAM lpStream, ++ LPTSTR lpszStreamName, ++ ULONG ulFlags, ++ LPMESSAGE lpMessage, ++ WORD wKeyVal, ++ LPITNEF FAR * lppTNEF); ++ ++typedef HRESULT (STDMETHODCALLTYPE FAR * LPOPENTNEFSTREAM) ( ++ LPVOID lpvSupport, ++ LPSTREAM lpStream, ++ LPTSTR lpszStreamName, ++ ULONG ulFlags, ++ LPMESSAGE lpMessage, ++ WORD wKeyVal, ++ LPITNEF FAR * lppTNEF); ++ ++STDMETHODIMP OpenTnefStreamEx( ++ LPVOID lpvSupport, ++ LPSTREAM lpStream, ++ LPTSTR lpszStreamName, ++ ULONG ulFlags, ++ LPMESSAGE lpMessage, ++ WORD wKeyVal, ++ LPADRBOOK lpAdressBook, ++ LPITNEF FAR * lppTNEF); ++ ++typedef HRESULT (STDMETHODCALLTYPE FAR * LPOPENTNEFSTREAMEX) ( ++ LPVOID lpvSupport, ++ LPSTREAM lpStream, ++ LPTSTR lpszStreamName, ++ ULONG ulFlags, ++ LPMESSAGE lpMessage, ++ WORD wKeyVal, ++ LPADRBOOK lpAdressBook, ++ LPITNEF FAR * lppTNEF); ++ ++STDMETHODIMP GetTnefStreamCodepage ( ++ LPSTREAM lpStream, ++ ULONG FAR * lpulCodepage, ++ ULONG FAR * lpulSubCodepage); ++ ++typedef HRESULT (STDMETHODCALLTYPE FAR * LPGETTNEFSTREAMCODEPAGE) ( ++ LPSTREAM lpStream, ++ ULONG FAR * lpulCodepage, ++ ULONG FAR * lpulSubCodepage); ++ ++#define OPENTNEFSTREAM "OpenTnefStream" ++#define OPENTNEFSTREAMEX "OpenTnefStreamEx" ++#define GETTNEFSTREAMCODEPAGE "GetTnefStreamCodePage" ++#endif ++ ++/* -------------------------- */ ++/* TNEF Signature and Version */ ++/* -------------------------- */ ++ ++#define MAKE_TNEF_VERSION(_mj,_mn) (((ULONG)(0x0000FFFF & _mj) << 16) | (ULONG)(0x0000FFFF & _mn)) ++#define TNEF_SIGNATURE ((ULONG) 0x223E9F78) ++#define TNEF_VERSION ((ULONG) MAKE_TNEF_VERSION(1,0)) ++ ++ ++/* ------------------------------------------- */ ++/* TNEF Down-level Attachment Types/Structures */ ++/* ------------------------------------------- */ ++ ++typedef WORD ATYP; ++enum { atypNull, atypFile, atypOle, atypPicture, atypMax }; ++ ++#define MAC_BINARY ((DWORD) 0x00000001) ++ ++typedef struct _renddata ++{ ++ ATYP atyp; ++ ULONG ulPosition; ++ WORD dxWidth; ++ WORD dyHeight; ++ DWORD dwFlags; ++ ++} RENDDATA, *PRENDDATA; ++ ++/* ----------------------------------- */ ++/* TNEF Down-level Date/Time Structure */ ++/* ----------------------------------- */ ++ ++typedef struct _dtr ++{ ++ WORD wYear; ++ WORD wMonth; ++ WORD wDay; ++ WORD wHour; ++ WORD wMinute; ++ WORD wSecond; ++ WORD wDayOfWeek; ++ ++} DTR; ++ ++ ++/* ----------------------------- */ ++/* TNEF Down-level Message Flags */ ++/* ----------------------------- */ ++ ++#define fmsNull ((BYTE) 0x00) ++#define fmsModified ((BYTE) 0x01) ++#define fmsLocal ((BYTE) 0x02) ++#define fmsSubmitted ((BYTE) 0x04) ++#define fmsRead ((BYTE) 0x20) ++#define fmsHasAttach ((BYTE) 0x80) ++ ++ ++/* ----------------------------------------- */ ++/* TNEF Down-level Triple Address Structures */ ++/* ----------------------------------------- */ ++ ++#define trpidNull ((WORD) 0x0000) ++#define trpidUnresolved ((WORD) 0x0001) ++#define trpidResolvedNSID ((WORD) 0x0002) ++#define trpidResolvedAddress ((WORD) 0x0003) ++#define trpidOneOff ((WORD) 0x0004) ++#define trpidGroupNSID ((WORD) 0x0005) ++#define trpidOffline ((WORD) 0x0006) ++#define trpidIgnore ((WORD) 0x0007) ++#define trpidClassEntry ((WORD) 0x0008) ++#define trpidResolvedGroupAddress ((WORD) 0x0009) ++typedef struct _trp ++{ ++ WORD trpid; ++ WORD cbgrtrp; ++ WORD cch; ++ WORD cbRgb; ++ ++} TRP, *PTRP, *PGRTRP, FAR * LPTRP; ++#define CbOfTrp(_p) (sizeof(TRP) + (_p)->cch + (_p)->cbRgb) ++#define LpszOfTrp(_p) ((LPSTR)(((LPTRP) (_p)) + 1)) ++#define LpbOfTrp(_p) (((LPBYTE)(((LPTRP)(_p)) + 1)) + (_p)->cch) ++#define LptrpNext(_p) ((LPTRP)((LPBYTE)(_p) + CbOfTrp(_p))) ++ ++typedef DWORD XTYPE; ++#define xtypeUnknown ((XTYPE) 0) ++#define xtypeInternet ((XTYPE) 6) ++ ++#define cbDisplayName 41 ++#define cbEmailName 11 ++#define cbSeverName 12 ++typedef struct _ADDR_ALIAS ++{ ++ char rgchName[cbDisplayName]; ++ char rgchEName[cbEmailName]; ++ char rgchSrvr[cbSeverName]; ++ ULONG dibDetail; ++ WORD type; ++ ++} ADDRALIAS, FAR * LPADDRALIAS; ++#define cbALIAS sizeof(ALIAS) ++ ++#define cbTYPE 16 ++#define cbMaxIdData 200 ++typedef struct _NSID ++{ ++ DWORD dwSize; ++ unsigned char uchType[cbTYPE]; ++ XTYPE xtype; ++ LONG lTime; ++ ++ union ++ { ++ ADDRALIAS alias; ++ char rgchInterNet[1]; ++ ++ } address; ++ ++} NSID, * LPNSID; ++#define cbNSID sizeof(NSID) ++ ++ ++/* -------------------------- */ ++/* TNEF Down-level Priorities */ ++/* -------------------------- */ ++ ++#define prioLow 3 ++#define prioNorm 2 ++#define prioHigh 1 ++ ++ ++/* ------------------------------------- */ ++/* TNEF Down-level Attributes/Properties */ ++/* ------------------------------------- */ ++ ++#define atpTriples ((WORD) 0x0000) ++#define atpString ((WORD) 0x0001) ++#define atpText ((WORD) 0x0002) ++#define atpDate ((WORD) 0x0003) ++#define atpShort ((WORD) 0x0004) ++#define atpLong ((WORD) 0x0005) ++#define atpByte ((WORD) 0x0006) ++#define atpWord ((WORD) 0x0007) ++#define atpDword ((WORD) 0x0008) ++#define atpMax ((WORD) 0x0009) ++ ++#define LVL_MESSAGE ((BYTE) 0x01) ++#define LVL_ATTACHMENT ((BYTE) 0x02) ++ ++#define ATT_ID(_att) ((WORD) ((_att) & 0x0000FFFF)) ++#define ATT_TYPE(_att) ((WORD) (((_att) >> 16) & 0x0000FFFF)) ++#define ATT(_atp, _id) ((((DWORD) (_atp)) << 16) | ((WORD) (_id))) ++ ++#define attNull ATT( 0, 0x0000) ++#define attFrom ATT( atpTriples, 0x8000) /* PR_ORIGINATOR_RETURN_ADDRESS */ ++#define attSubject ATT( atpString, 0x8004) /* PR_SUBJECT */ ++#define attDateSent ATT( atpDate, 0x8005) /* PR_CLIENT_SUBMIT_TIME */ ++#define attDateRecd ATT( atpDate, 0x8006) /* PR_MESSAGE_DELIVERY_TIME */ ++#define attMessageStatus ATT( atpByte, 0x8007) /* PR_MESSAGE_FLAGS */ ++#define attMessageClass ATT( atpWord, 0x8008) /* PR_MESSAGE_CLASS */ ++#define attMessageID ATT( atpString, 0x8009) /* PR_MESSAGE_ID */ ++#define attParentID ATT( atpString, 0x800A) /* PR_PARENT_ID */ ++#define attConversationID ATT( atpString, 0x800B) /* PR_CONVERSATION_ID */ ++#define attBody ATT( atpText, 0x800C) /* PR_BODY */ ++#define attPriority ATT( atpShort, 0x800D) /* PR_IMPORTANCE */ ++#define attAttachData ATT( atpByte, 0x800F) /* PR_ATTACH_DATA_xxx */ ++#define attAttachTitle ATT( atpString, 0x8010) /* PR_ATTACH_FILENAME */ ++#define attAttachMetaFile ATT( atpByte, 0x8011) /* PR_ATTACH_RENDERING */ ++#define attAttachCreateDate ATT( atpDate, 0x8012) /* PR_CREATION_TIME */ ++#define attAttachModifyDate ATT( atpDate, 0x8013) /* PR_LAST_MODIFICATION_TIME */ ++#define attDateModified ATT( atpDate, 0x8020) /* PR_LAST_MODIFICATION_TIME */ ++#define attAttachTransportFilename ATT( atpByte, 0x9001) /* PR_ATTACH_TRANSPORT_NAME */ ++#define attAttachRenddata ATT( atpByte, 0x9002) ++#define attMAPIProps ATT( atpByte, 0x9003) ++#define attRecipTable ATT( atpByte, 0x9004) /* PR_MESSAGE_RECIPIENTS */ ++#define attAttachment ATT( atpByte, 0x9005) ++#define attTnefVersion ATT( atpDword, 0x9006) ++#define attOemCodepage ATT( atpByte, 0x9007) ++#define attOriginalMessageClass ATT( atpWord, 0x0006) /* PR_ORIG_MESSAGE_CLASS */ ++ ++#define attOwner ATT( atpByte, 0x0000) /* PR_RCVD_REPRESENTING_xxx or ++ PR_SENT_REPRESENTING_xxx */ ++#define attSentFor ATT( atpByte, 0x0001) /* PR_SENT_REPRESENTING_xxx */ ++#define attDelegate ATT( atpByte, 0x0002) /* PR_RCVD_REPRESENTING_xxx */ ++#define attDateStart ATT( atpDate, 0x0006) /* PR_DATE_START */ ++#define attDateEnd ATT( atpDate, 0x0007) /* PR_DATE_END */ ++#define attAidOwner ATT( atpLong, 0x0008) /* PR_OWNER_APPT_ID */ ++#define attRequestRes ATT( atpShort, 0x0009) /* PR_RESPONSE_REQUESTED */ ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* defined TNEF_H */ ++/* ++ * M A P I D E F S . H ++ * ++ * Definitions used by MAPI clients and service providers. ++ * ++ * Copyright 1986-1996 Microsoft Corporation. All Rights Reserved. ++ */ ++ ++#ifndef MAPIDEFS_H ++#define MAPIDEFS_H ++ ++ ++/* Array dimension for structures with variable-sized arrays at the end. */ ++ ++/* Simple data types */ ++ ++ ++typedef WORD WCHAR; ++ ++#ifdef UNICODE ++typedef WCHAR TCHAR; ++#else ++typedef char TCHAR; ++#endif ++ ++typedef WCHAR * LPWSTR; ++typedef const WCHAR * LPCWSTR; ++typedef TCHAR * LPTSTR; ++typedef const TCHAR * LPCTSTR; ++typedef BYTE * LPBYTE; ++ ++typedef ULONG * LPULONG; ++ ++#ifndef __LHANDLE ++#define __LHANDLE ++typedef unsigned long LHANDLE, * LPLHANDLE; ++#endif ++ ++#if !defined(_WINBASE_) && !defined(_FILETIME_) ++#define _FILETIME_ ++typedef struct _FILETIME ++{ ++ DWORD dwLowDateTime; ++ DWORD dwHighDateTime; ++} FILETIME, * LPFILETIME; ++#endif ++ ++/* ++ * This flag is used in many different MAPI calls to signify that ++ * the object opened by the call should be modifiable (MAPI_MODIFY). ++ * If the flag MAPI_MAX_ACCESS is set, the object returned should be ++ * returned at the maximum access level allowed. An additional ++ * property available on the object (PR_ACCESS_LEVEL) uses the same ++ * MAPI_MODIFY flag to say just what this new access level is. ++ */ ++ ++#define MAPI_MODIFY ((ULONG) 0x00000001) ++ ++/* ++ * The following flags are used to indicate to the client what access ++ * level is permissible in the object. They appear in PR_ACCESS in ++ * message and folder objects as well as in contents and associated ++ * contents tables ++ */ ++ ++#define MAPI_ACCESS_MODIFY ((ULONG) 0x00000001) ++#define MAPI_ACCESS_READ ((ULONG) 0x00000002) ++#define MAPI_ACCESS_DELETE ((ULONG) 0x00000004) ++#define MAPI_ACCESS_CREATE_HIERARCHY ((ULONG) 0x00000008) ++#define MAPI_ACCESS_CREATE_CONTENTS ((ULONG) 0x00000010) ++#define MAPI_ACCESS_CREATE_ASSOCIATED ((ULONG) 0x00000020) ++ ++/* ++ * The MAPI_UNICODE flag is used in many different MAPI calls to signify ++ * that strings passed through the interface are in Unicode (a 16-bit ++ * character set). The default is an 8-bit character set. ++ * ++ * The value fMapiUnicode can be used as the 'normal' value for ++ * that bit, given the application's default character set. ++ */ ++ ++#define MAPI_UNICODE ((ULONG) 0x80000000) ++ ++#ifdef UNICODE ++#define fMapiUnicode MAPI_UNICODE ++#else ++#define fMapiUnicode 0 ++#endif ++ ++/* successful HRESULT */ ++#define hrSuccess 0 ++ ++ ++ ++/* Recipient types */ ++#ifndef MAPI_ORIG /* also defined in mapi.h */ ++#define MAPI_ORIG 0 /* Recipient is message originator */ ++#define MAPI_TO 1 /* Recipient is a primary recipient */ ++#define MAPI_CC 2 /* Recipient is a copy recipient */ ++#define MAPI_BCC 3 /* Recipient is blind copy recipient */ ++#define MAPI_P1 0x10000000 /* Recipient is a P1 resend recipient */ ++#define MAPI_SUBMITTED 0x80000000 /* Recipient is already processed */ ++/* #define MAPI_AUTHORIZE 4 recipient is a CMC authorizing user */ ++/*#define MAPI_DISCRETE 0x10000000 Recipient is a P1 resend recipient */ ++#endif ++ ++/* Bit definitions for abFlags[0] of ENTRYID */ ++#define MAPI_SHORTTERM 0x80 ++#define MAPI_NOTRECIP 0x40 ++#define MAPI_THISSESSION 0x20 ++#define MAPI_NOW 0x10 ++#define MAPI_NOTRESERVED 0x08 ++ ++/* Bit definitions for abFlags[1] of ENTRYID */ ++#define MAPI_COMPOUND 0x80 ++ ++/* ENTRYID */ ++typedef struct ++{ ++ BYTE abFlags[4]; ++ BYTE ab[MAPI_DIM]; ++} ENTRYID, *LPENTRYID; ++ ++#define CbNewENTRYID(_cb) (offsetof(ENTRYID,ab) + (_cb)) ++#define CbENTRYID(_cb) (offsetof(ENTRYID,ab) + (_cb)) ++ ++/* Byte-order-independent version of GUID (world-unique identifier) */ ++typedef struct _MAPIUID ++{ ++ BYTE ab[16]; ++} MAPIUID, * LPMAPIUID; ++ ++/* Note: need to include C run-times (memory.h) to use this macro */ ++ ++#define IsEqualMAPIUID(lpuid1, lpuid2) (!memcmp(lpuid1, lpuid2, sizeof(MAPIUID))) ++ ++/* ++ * Constants for one-off entry ID: ++ * The MAPIUID that identifies the one-off provider; ++ * the flag that defines whether the embedded strings are Unicode; ++ * the flag that specifies whether the recipient gets TNEF or not. ++ */ ++ ++#define MAPI_ONE_OFF_UID { 0x81, 0x2b, 0x1f, 0xa4, 0xbe, 0xa3, 0x10, 0x19, 0x9d, 0x6e, 0x00, 0xdd, 0x01, 0x0f, 0x54, 0x02 } ++#define MAPI_ONE_OFF_UNICODE 0x8000 ++#define MAPI_ONE_OFF_NO_RICH_INFO 0x0001 ++ ++/* Object type */ ++ ++#define MAPI_STORE ((ULONG) 0x00000001) /* Message Store */ ++#define MAPI_ADDRBOOK ((ULONG) 0x00000002) /* Address Book */ ++#define MAPI_FOLDER ((ULONG) 0x00000003) /* Folder */ ++#define MAPI_ABCONT ((ULONG) 0x00000004) /* Address Book Container */ ++#define MAPI_MESSAGE ((ULONG) 0x00000005) /* Message */ ++#define MAPI_MAILUSER ((ULONG) 0x00000006) /* Individual Recipient */ ++#define MAPI_ATTACH ((ULONG) 0x00000007) /* Attachment */ ++#define MAPI_DISTLIST ((ULONG) 0x00000008) /* Distribution List Recipient */ ++#define MAPI_PROFSECT ((ULONG) 0x00000009) /* Profile Section */ ++#define MAPI_STATUS ((ULONG) 0x0000000A) /* Status Object */ ++#define MAPI_SESSION ((ULONG) 0x0000000B) /* Session */ ++#define MAPI_FORMINFO ((ULONG) 0x0000000C) /* Form Information */ ++ ++ ++/* ++ * Maximum length of profile names and passwords, not including ++ * the null termination character. ++ */ ++#ifndef cchProfileNameMax ++#define cchProfileNameMax 64 ++#define cchProfilePassMax 64 ++#endif ++ ++ ++/* Property Types */ ++ ++#define MV_FLAG 0x1000 /* Multi-value flag */ ++ ++#define PT_UNSPECIFIED ((ULONG) 0) /* (Reserved for interface use) type doesn't matter to caller */ ++#define PT_NULL ((ULONG) 1) /* NULL property value */ ++#define PT_I2 ((ULONG) 2) /* Signed 16-bit value */ ++#define PT_LONG ((ULONG) 3) /* Signed 32-bit value */ ++#define PT_R4 ((ULONG) 4) /* 4-byte floating point */ ++#define PT_DOUBLE ((ULONG) 5) /* Floating point double */ ++#define PT_CURRENCY ((ULONG) 6) /* Signed 64-bit int (decimal w/ 4 digits right of decimal pt) */ ++#define PT_APPTIME ((ULONG) 7) /* Application time */ ++#define PT_ERROR ((ULONG) 10) /* 32-bit error value */ ++#define PT_BOOLEAN ((ULONG) 11) /* 16-bit boolean (non-zero true) */ ++#define PT_OBJECT ((ULONG) 13) /* Embedded object in a property */ ++#define PT_I8 ((ULONG) 20) /* 8-byte signed integer */ ++#define PT_STRING8 ((ULONG) 30) /* Null terminated 8-bit character string */ ++#define PT_UNICODE ((ULONG) 31) /* Null terminated Unicode string */ ++#define PT_SYSTIME ((ULONG) 64) /* FILETIME 64-bit int w/ number of 100ns periods since Jan 1,1601 */ ++#define PT_CLSID ((ULONG) 72) /* OLE GUID */ ++#define PT_BINARY ((ULONG) 258) /* Uninterpreted (counted byte array) */ ++/* Changes are likely to these numbers, and to their structures. */ ++ ++/* Alternate property type names for ease of use */ ++#define PT_SHORT PT_I2 ++#define PT_I4 PT_LONG ++#define PT_FLOAT PT_R4 ++#define PT_R8 PT_DOUBLE ++#define PT_LONGLONG PT_I8 ++ ++/* ++ * The type of a MAPI-defined string property is indirected, so ++ * that it defaults to Unicode string on a Unicode platform and to ++ * String8 on an ANSI or DBCS platform. ++ * ++ * Macros are defined here both for the property type, and for the ++ * field of the property value structure which should be ++ * dereferenced to obtain the string pointer. ++ */ ++ ++#ifdef UNICODE ++#define PT_TSTRING PT_UNICODE ++#define PT_MV_TSTRING (MV_FLAG|PT_UNICODE) ++#define LPSZ lpszW ++#define LPPSZ lppszW ++#define MVSZ MVszW ++#else ++#define PT_TSTRING PT_STRING8 ++#define PT_MV_TSTRING (MV_FLAG|PT_STRING8) ++#define LPSZ lpszA ++#define LPPSZ lppszA ++#define MVSZ MVszA ++#endif ++ ++ ++/* Property Tags ++ * ++ * By convention, MAPI never uses 0 or FFFF as a property ID. ++ * Use as null values, initializers, sentinels, or what have you. ++ */ ++ ++#define PROP_TYPE_MASK ((ULONG)0x0000FFFF) /* Mask for Property type */ ++#define PROP_TYPE(ulPropTag) (((ULONG)(ulPropTag))&PROP_TYPE_MASK) ++#define PROP_ID(ulPropTag) (((ULONG)(ulPropTag))>>16) ++#define PROP_TAG(ulPropType,ulPropID) ((((ULONG)(ulPropID))<<16)|((ULONG)(ulPropType))) ++#define PROP_ID_NULL 0 ++#define PROP_ID_INVALID 0xFFFF ++#define PR_NULL PROP_TAG( PT_NULL, PROP_ID_NULL) ++#if 0 ++#define CHANGE_PROP_TYPE(ulPropTag, ulPropType) \ ++ (((ULONG)0xFFFF0000 & ulPropTag) | ulPropType) ++#endif ++ ++ ++/* Multi-valued Property Types */ ++ ++#define PT_MV_I2 (MV_FLAG|PT_I2) ++#define PT_MV_LONG (MV_FLAG|PT_LONG) ++#define PT_MV_R4 (MV_FLAG|PT_R4) ++#define PT_MV_DOUBLE (MV_FLAG|PT_DOUBLE) ++#define PT_MV_CURRENCY (MV_FLAG|PT_CURRENCY) ++#define PT_MV_APPTIME (MV_FLAG|PT_APPTIME) ++#define PT_MV_SYSTIME (MV_FLAG|PT_SYSTIME) ++#define PT_MV_STRING8 (MV_FLAG|PT_STRING8) ++#define PT_MV_BINARY (MV_FLAG|PT_BINARY) ++#define PT_MV_UNICODE (MV_FLAG|PT_UNICODE) ++#define PT_MV_CLSID (MV_FLAG|PT_CLSID) ++#define PT_MV_I8 (MV_FLAG|PT_I8) ++ ++/* Alternate property type names for ease of use */ ++#define PT_MV_SHORT PT_MV_I2 ++#define PT_MV_I4 PT_MV_LONG ++#define PT_MV_FLOAT PT_MV_R4 ++#define PT_MV_R8 PT_MV_DOUBLE ++#define PT_MV_LONGLONG PT_MV_I8 ++ ++/* ++ * Property type reserved bits ++ * ++ * MV_INSTANCE is used as a flag in table operations to request ++ * that a multi-valued property be presented as a single-valued ++ * property appearing in multiple rows. ++ */ ++ ++#define MV_INSTANCE 0x2000 ++#define MVI_FLAG (MV_FLAG | MV_INSTANCE) ++#define MVI_PROP(tag) ((tag) | MVI_FLAG) ++ ++ ++ ++#endif /* MAPIDEFS_H */ ++/* ++ * M A P I T A G S . H ++ * ++ * Property tag definitions for standard properties of MAPI ++ * objects. ++ * ++ * The following ranges should be used for all property IDs. Note that ++ * property IDs for objects other than messages and recipients should ++ * all fall in the range 0x3000 to 0x3FFF: ++ * ++ * From To Kind of property ++ * -------------------------------- ++ * 0001 0BFF MAPI_defined envelope property ++ * 0C00 0DFF MAPI_defined per-recipient property ++ * 0E00 0FFF MAPI_defined non-transmittable property ++ * 1000 2FFF MAPI_defined message content property ++ * ++ * 3000 3FFF MAPI_defined property (usually not message or recipient) ++ * ++ * 4000 57FF Transport-defined envelope property ++ * 5800 5FFF Transport-defined per-recipient property ++ * 6000 65FF User-defined non-transmittable property ++ * 6600 67FF Provider-defined internal non-transmittable property ++ * 6800 7BFF Message class-defined content property ++ * 7C00 7FFF Message class-defined non-transmittable ++ * property ++ * ++ * 8000 FFFE User-defined Name-to-id mapped property ++ * ++ * The 3000-3FFF range is further subdivided as follows: ++ * ++ * From To Kind of property ++ * -------------------------------- ++ * 3000 33FF Common property such as display name, entry ID ++ * 3400 35FF Message store object ++ * 3600 36FF Folder or AB container ++ * 3700 38FF Attachment ++ * 3900 39FF Address book object ++ * 3A00 3BFF Mail user ++ * 3C00 3CFF Distribution list ++ * 3D00 3DFF Profile section ++ * 3E00 3FFF Status object ++ * ++ * Copyright 1986-1996 Microsoft Corporation. All Rights Reserved. ++ */ ++ ++#ifndef MAPITAGS_H ++#define MAPITAGS_H ++ ++/* Determine if a property is transmittable. */ ++ ++#define FIsTransmittable(ulPropTag) \ ++ ((PROP_ID (ulPropTag) < (ULONG)0x0E00) || \ ++ (PROP_ID (ulPropTag) >= (ULONG)0x8000) || \ ++ ((PROP_ID (ulPropTag) >= (ULONG)0x1000) && (PROP_ID (ulPropTag) < (ULONG)0x6000)) || \ ++ ((PROP_ID (ulPropTag) >= (ULONG)0x6800) && (PROP_ID (ulPropTag) < (ULONG)0x7C00))) ++ ++/* ++ * Message envelope properties ++ */ ++ ++#define PR_ACKNOWLEDGEMENT_MODE PROP_TAG( PT_LONG, 0x0001) ++#define PR_ALTERNATE_RECIPIENT_ALLOWED PROP_TAG( PT_BOOLEAN, 0x0002) ++#define PR_AUTHORIZING_USERS PROP_TAG( PT_BINARY, 0x0003) ++#define PR_AUTO_FORWARD_COMMENT PROP_TAG( PT_TSTRING, 0x0004) ++#define PR_AUTO_FORWARD_COMMENT_W PROP_TAG( PT_UNICODE, 0x0004) ++#define PR_AUTO_FORWARD_COMMENT_A PROP_TAG( PT_STRING8, 0x0004) ++#define PR_AUTO_FORWARDED PROP_TAG( PT_BOOLEAN, 0x0005) ++#define PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID PROP_TAG( PT_BINARY, 0x0006) ++#define PR_CONTENT_CORRELATOR PROP_TAG( PT_BINARY, 0x0007) ++#define PR_CONTENT_IDENTIFIER PROP_TAG( PT_TSTRING, 0x0008) ++#define PR_CONTENT_IDENTIFIER_W PROP_TAG( PT_UNICODE, 0x0008) ++#define PR_CONTENT_IDENTIFIER_A PROP_TAG( PT_STRING8, 0x0008) ++#define PR_CONTENT_LENGTH PROP_TAG( PT_LONG, 0x0009) ++#define PR_CONTENT_RETURN_REQUESTED PROP_TAG( PT_BOOLEAN, 0x000A) ++ ++ ++ ++#define PR_CONVERSATION_KEY PROP_TAG( PT_BINARY, 0x000B) ++ ++#define PR_CONVERSION_EITS PROP_TAG( PT_BINARY, 0x000C) ++#define PR_CONVERSION_WITH_LOSS_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x000D) ++#define PR_CONVERTED_EITS PROP_TAG( PT_BINARY, 0x000E) ++#define PR_DEFERRED_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x000F) ++#define PR_DELIVER_TIME PROP_TAG( PT_SYSTIME, 0x0010) ++#define PR_DISCARD_REASON PROP_TAG( PT_LONG, 0x0011) ++#define PR_DISCLOSURE_OF_RECIPIENTS PROP_TAG( PT_BOOLEAN, 0x0012) ++#define PR_DL_EXPANSION_HISTORY PROP_TAG( PT_BINARY, 0x0013) ++#define PR_DL_EXPANSION_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x0014) ++#define PR_EXPIRY_TIME PROP_TAG( PT_SYSTIME, 0x0015) ++#define PR_IMPLICIT_CONVERSION_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x0016) ++#define PR_IMPORTANCE PROP_TAG( PT_LONG, 0x0017) ++#define PR_IPM_ID PROP_TAG( PT_BINARY, 0x0018) ++#define PR_LATEST_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x0019) ++#define PR_MESSAGE_CLASS PROP_TAG( PT_TSTRING, 0x001A) ++#define PR_MESSAGE_CLASS_W PROP_TAG( PT_UNICODE, 0x001A) ++#define PR_MESSAGE_CLASS_A PROP_TAG( PT_STRING8, 0x001A) ++#define PR_MESSAGE_DELIVERY_ID PROP_TAG( PT_BINARY, 0x001B) ++ ++ ++ ++ ++ ++#define PR_MESSAGE_SECURITY_LABEL PROP_TAG( PT_BINARY, 0x001E) ++#define PR_OBSOLETED_IPMS PROP_TAG( PT_BINARY, 0x001F) ++#define PR_ORIGINALLY_INTENDED_RECIPIENT_NAME PROP_TAG( PT_BINARY, 0x0020) ++#define PR_ORIGINAL_EITS PROP_TAG( PT_BINARY, 0x0021) ++#define PR_ORIGINATOR_CERTIFICATE PROP_TAG( PT_BINARY, 0x0022) ++#define PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0023) ++#define PR_ORIGINATOR_RETURN_ADDRESS PROP_TAG( PT_BINARY, 0x0024) ++ ++ ++ ++#define PR_PARENT_KEY PROP_TAG( PT_BINARY, 0x0025) ++#define PR_PRIORITY PROP_TAG( PT_LONG, 0x0026) ++ ++ ++ ++#define PR_ORIGIN_CHECK PROP_TAG( PT_BINARY, 0x0027) ++#define PR_PROOF_OF_SUBMISSION_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0028) ++#define PR_READ_RECEIPT_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0029) ++#define PR_RECEIPT_TIME PROP_TAG( PT_SYSTIME, 0x002A) ++#define PR_RECIPIENT_REASSIGNMENT_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x002B) ++#define PR_REDIRECTION_HISTORY PROP_TAG( PT_BINARY, 0x002C) ++#define PR_RELATED_IPMS PROP_TAG( PT_BINARY, 0x002D) ++#define PR_ORIGINAL_SENSITIVITY PROP_TAG( PT_LONG, 0x002E) ++#define PR_LANGUAGES PROP_TAG( PT_TSTRING, 0x002F) ++#define PR_LANGUAGES_W PROP_TAG( PT_UNICODE, 0x002F) ++#define PR_LANGUAGES_A PROP_TAG( PT_STRING8, 0x002F) ++#define PR_REPLY_TIME PROP_TAG( PT_SYSTIME, 0x0030) ++#define PR_REPORT_TAG PROP_TAG( PT_BINARY, 0x0031) ++#define PR_REPORT_TIME PROP_TAG( PT_SYSTIME, 0x0032) ++#define PR_RETURNED_IPM PROP_TAG( PT_BOOLEAN, 0x0033) ++#define PR_SECURITY PROP_TAG( PT_LONG, 0x0034) ++#define PR_INCOMPLETE_COPY PROP_TAG( PT_BOOLEAN, 0x0035) ++#define PR_SENSITIVITY PROP_TAG( PT_LONG, 0x0036) ++#define PR_SUBJECT PROP_TAG( PT_TSTRING, 0x0037) ++#define PR_SUBJECT_W PROP_TAG( PT_UNICODE, 0x0037) ++#define PR_SUBJECT_A PROP_TAG( PT_STRING8, 0x0037) ++#define PR_SUBJECT_IPM PROP_TAG( PT_BINARY, 0x0038) ++#define PR_CLIENT_SUBMIT_TIME PROP_TAG( PT_SYSTIME, 0x0039) ++#define PR_REPORT_NAME PROP_TAG( PT_TSTRING, 0x003A) ++#define PR_REPORT_NAME_W PROP_TAG( PT_UNICODE, 0x003A) ++#define PR_REPORT_NAME_A PROP_TAG( PT_STRING8, 0x003A) ++#define PR_SENT_REPRESENTING_SEARCH_KEY PROP_TAG( PT_BINARY, 0x003B) ++#define PR_X400_CONTENT_TYPE PROP_TAG( PT_BINARY, 0x003C) ++#define PR_SUBJECT_PREFIX PROP_TAG( PT_TSTRING, 0x003D) ++#define PR_SUBJECT_PREFIX_W PROP_TAG( PT_UNICODE, 0x003D) ++#define PR_SUBJECT_PREFIX_A PROP_TAG( PT_STRING8, 0x003D) ++#define PR_NON_RECEIPT_REASON PROP_TAG( PT_LONG, 0x003E) ++#define PR_RECEIVED_BY_ENTRYID PROP_TAG( PT_BINARY, 0x003F) ++#define PR_RECEIVED_BY_NAME PROP_TAG( PT_TSTRING, 0x0040) ++#define PR_RECEIVED_BY_NAME_W PROP_TAG( PT_UNICODE, 0x0040) ++#define PR_RECEIVED_BY_NAME_A PROP_TAG( PT_STRING8, 0x0040) ++#define PR_SENT_REPRESENTING_ENTRYID PROP_TAG( PT_BINARY, 0x0041) ++#define PR_SENT_REPRESENTING_NAME PROP_TAG( PT_TSTRING, 0x0042) ++#define PR_SENT_REPRESENTING_NAME_W PROP_TAG( PT_UNICODE, 0x0042) ++#define PR_SENT_REPRESENTING_NAME_A PROP_TAG( PT_STRING8, 0x0042) ++#define PR_RCVD_REPRESENTING_ENTRYID PROP_TAG( PT_BINARY, 0x0043) ++#define PR_RCVD_REPRESENTING_NAME PROP_TAG( PT_TSTRING, 0x0044) ++#define PR_RCVD_REPRESENTING_NAME_W PROP_TAG( PT_UNICODE, 0x0044) ++#define PR_RCVD_REPRESENTING_NAME_A PROP_TAG( PT_STRING8, 0x0044) ++#define PR_REPORT_ENTRYID PROP_TAG( PT_BINARY, 0x0045) ++#define PR_READ_RECEIPT_ENTRYID PROP_TAG( PT_BINARY, 0x0046) ++#define PR_MESSAGE_SUBMISSION_ID PROP_TAG( PT_BINARY, 0x0047) ++#define PR_PROVIDER_SUBMIT_TIME PROP_TAG( PT_SYSTIME, 0x0048) ++#define PR_ORIGINAL_SUBJECT PROP_TAG( PT_TSTRING, 0x0049) ++#define PR_ORIGINAL_SUBJECT_W PROP_TAG( PT_UNICODE, 0x0049) ++#define PR_ORIGINAL_SUBJECT_A PROP_TAG( PT_STRING8, 0x0049) ++#define PR_DISC_VAL PROP_TAG( PT_BOOLEAN, 0x004A) ++#define PR_ORIG_MESSAGE_CLASS PROP_TAG( PT_TSTRING, 0x004B) ++#define PR_ORIG_MESSAGE_CLASS_W PROP_TAG( PT_UNICODE, 0x004B) ++#define PR_ORIG_MESSAGE_CLASS_A PROP_TAG( PT_STRING8, 0x004B) ++#define PR_ORIGINAL_AUTHOR_ENTRYID PROP_TAG( PT_BINARY, 0x004C) ++#define PR_ORIGINAL_AUTHOR_NAME PROP_TAG( PT_TSTRING, 0x004D) ++#define PR_ORIGINAL_AUTHOR_NAME_W PROP_TAG( PT_UNICODE, 0x004D) ++#define PR_ORIGINAL_AUTHOR_NAME_A PROP_TAG( PT_STRING8, 0x004D) ++#define PR_ORIGINAL_SUBMIT_TIME PROP_TAG( PT_SYSTIME, 0x004E) ++#define PR_REPLY_RECIPIENT_ENTRIES PROP_TAG( PT_BINARY, 0x004F) ++#define PR_REPLY_RECIPIENT_NAMES PROP_TAG( PT_TSTRING, 0x0050) ++#define PR_REPLY_RECIPIENT_NAMES_W PROP_TAG( PT_UNICODE, 0x0050) ++#define PR_REPLY_RECIPIENT_NAMES_A PROP_TAG( PT_STRING8, 0x0050) ++ ++#define PR_RECEIVED_BY_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0051) ++#define PR_RCVD_REPRESENTING_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0052) ++#define PR_READ_RECEIPT_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0053) ++#define PR_REPORT_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0054) ++#define PR_ORIGINAL_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x0055) ++#define PR_ORIGINAL_AUTHOR_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0056) ++ ++#define PR_MESSAGE_TO_ME PROP_TAG( PT_BOOLEAN, 0x0057) ++#define PR_MESSAGE_CC_ME PROP_TAG( PT_BOOLEAN, 0x0058) ++#define PR_MESSAGE_RECIP_ME PROP_TAG( PT_BOOLEAN, 0x0059) ++ ++#define PR_ORIGINAL_SENDER_NAME PROP_TAG( PT_TSTRING, 0x005A) ++#define PR_ORIGINAL_SENDER_NAME_W PROP_TAG( PT_UNICODE, 0x005A) ++#define PR_ORIGINAL_SENDER_NAME_A PROP_TAG( PT_STRING8, 0x005A) ++#define PR_ORIGINAL_SENDER_ENTRYID PROP_TAG( PT_BINARY, 0x005B) ++#define PR_ORIGINAL_SENDER_SEARCH_KEY PROP_TAG( PT_BINARY, 0x005C) ++#define PR_ORIGINAL_SENT_REPRESENTING_NAME PROP_TAG( PT_TSTRING, 0x005D) ++#define PR_ORIGINAL_SENT_REPRESENTING_NAME_W PROP_TAG( PT_UNICODE, 0x005D) ++#define PR_ORIGINAL_SENT_REPRESENTING_NAME_A PROP_TAG( PT_STRING8, 0x005D) ++#define PR_ORIGINAL_SENT_REPRESENTING_ENTRYID PROP_TAG( PT_BINARY, 0x005E) ++#define PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY PROP_TAG( PT_BINARY, 0x005F) ++ ++#define PR_START_DATE PROP_TAG( PT_SYSTIME, 0x0060) ++#define PR_END_DATE PROP_TAG( PT_SYSTIME, 0x0061) ++#define PR_OWNER_APPT_ID PROP_TAG( PT_LONG, 0x0062) ++#define PR_RESPONSE_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0063) ++ ++#define PR_SENT_REPRESENTING_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0064) ++#define PR_SENT_REPRESENTING_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0064) ++#define PR_SENT_REPRESENTING_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0064) ++#define PR_SENT_REPRESENTING_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0065) ++#define PR_SENT_REPRESENTING_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0065) ++#define PR_SENT_REPRESENTING_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0065) ++ ++#define PR_ORIGINAL_SENDER_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0066) ++#define PR_ORIGINAL_SENDER_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0066) ++#define PR_ORIGINAL_SENDER_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0066) ++#define PR_ORIGINAL_SENDER_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0067) ++#define PR_ORIGINAL_SENDER_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0067) ++#define PR_ORIGINAL_SENDER_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0067) ++ ++#define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0068) ++#define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0068) ++#define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0068) ++#define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0069) ++#define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0069) ++#define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0069) ++ ++#define PR_CONVERSATION_TOPIC PROP_TAG( PT_TSTRING, 0x0070) ++#define PR_CONVERSATION_TOPIC_W PROP_TAG( PT_UNICODE, 0x0070) ++#define PR_CONVERSATION_TOPIC_A PROP_TAG( PT_STRING8, 0x0070) ++#define PR_CONVERSATION_INDEX PROP_TAG( PT_BINARY, 0x0071) ++ ++#define PR_ORIGINAL_DISPLAY_BCC PROP_TAG( PT_TSTRING, 0x0072) ++#define PR_ORIGINAL_DISPLAY_BCC_W PROP_TAG( PT_UNICODE, 0x0072) ++#define PR_ORIGINAL_DISPLAY_BCC_A PROP_TAG( PT_STRING8, 0x0072) ++#define PR_ORIGINAL_DISPLAY_CC PROP_TAG( PT_TSTRING, 0x0073) ++#define PR_ORIGINAL_DISPLAY_CC_W PROP_TAG( PT_UNICODE, 0x0073) ++#define PR_ORIGINAL_DISPLAY_CC_A PROP_TAG( PT_STRING8, 0x0073) ++#define PR_ORIGINAL_DISPLAY_TO PROP_TAG( PT_TSTRING, 0x0074) ++#define PR_ORIGINAL_DISPLAY_TO_W PROP_TAG( PT_UNICODE, 0x0074) ++#define PR_ORIGINAL_DISPLAY_TO_A PROP_TAG( PT_STRING8, 0x0074) ++ ++#define PR_RECEIVED_BY_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0075) ++#define PR_RECEIVED_BY_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0075) ++#define PR_RECEIVED_BY_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0075) ++#define PR_RECEIVED_BY_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0076) ++#define PR_RECEIVED_BY_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0076) ++#define PR_RECEIVED_BY_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0076) ++ ++#define PR_RCVD_REPRESENTING_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0077) ++#define PR_RCVD_REPRESENTING_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0077) ++#define PR_RCVD_REPRESENTING_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0077) ++#define PR_RCVD_REPRESENTING_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0078) ++#define PR_RCVD_REPRESENTING_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0078) ++#define PR_RCVD_REPRESENTING_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0078) ++ ++#define PR_ORIGINAL_AUTHOR_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0079) ++#define PR_ORIGINAL_AUTHOR_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0079) ++#define PR_ORIGINAL_AUTHOR_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0079) ++#define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x007A) ++#define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x007A) ++#define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x007A) ++ ++#define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE PROP_TAG( PT_TSTRING, 0x007B) ++#define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x007B) ++#define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x007B) ++#define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x007C) ++#define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x007C) ++#define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x007C) ++ ++#define PR_TRANSPORT_MESSAGE_HEADERS PROP_TAG(PT_TSTRING, 0x007D) ++#define PR_TRANSPORT_MESSAGE_HEADERS_W PROP_TAG(PT_UNICODE, 0x007D) ++#define PR_TRANSPORT_MESSAGE_HEADERS_A PROP_TAG(PT_STRING8, 0x007D) ++ ++#define PR_DELEGATION PROP_TAG(PT_BINARY, 0x007E) ++ ++#define PR_TNEF_CORRELATION_KEY PROP_TAG(PT_BINARY, 0x007F) ++ ++ ++ ++/* ++ * Message content properties ++ */ ++ ++#define PR_BODY PROP_TAG( PT_TSTRING, 0x1000) ++#define PR_BODY_W PROP_TAG( PT_UNICODE, 0x1000) ++#define PR_BODY_A PROP_TAG( PT_STRING8, 0x1000) ++#define PR_REPORT_TEXT PROP_TAG( PT_TSTRING, 0x1001) ++#define PR_REPORT_TEXT_W PROP_TAG( PT_UNICODE, 0x1001) ++#define PR_REPORT_TEXT_A PROP_TAG( PT_STRING8, 0x1001) ++#define PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY PROP_TAG( PT_BINARY, 0x1002) ++#define PR_REPORTING_DL_NAME PROP_TAG( PT_BINARY, 0x1003) ++#define PR_REPORTING_MTA_CERTIFICATE PROP_TAG( PT_BINARY, 0x1004) ++ ++/* Removed PR_REPORT_ORIGIN_AUTHENTICATION_CHECK with DCR 3865, use PR_ORIGIN_CHECK */ ++ ++#define PR_RTF_SYNC_BODY_CRC PROP_TAG( PT_LONG, 0x1006) ++#define PR_RTF_SYNC_BODY_COUNT PROP_TAG( PT_LONG, 0x1007) ++#define PR_RTF_SYNC_BODY_TAG PROP_TAG( PT_TSTRING, 0x1008) ++#define PR_RTF_SYNC_BODY_TAG_W PROP_TAG( PT_UNICODE, 0x1008) ++#define PR_RTF_SYNC_BODY_TAG_A PROP_TAG( PT_STRING8, 0x1008) ++#define PR_RTF_COMPRESSED PROP_TAG( PT_BINARY, 0x1009) ++#define PR_RTF_SYNC_PREFIX_COUNT PROP_TAG( PT_LONG, 0x1010) ++#define PR_RTF_SYNC_TRAILING_COUNT PROP_TAG( PT_LONG, 0x1011) ++#define PR_ORIGINALLY_INTENDED_RECIP_ENTRYID PROP_TAG( PT_BINARY, 0x1012) ++ ++/* ++ * Reserved 0x1100-0x1200 ++ */ ++ ++ ++/* ++ * Message recipient properties ++ */ ++ ++#define PR_CONTENT_INTEGRITY_CHECK PROP_TAG( PT_BINARY, 0x0C00) ++#define PR_EXPLICIT_CONVERSION PROP_TAG( PT_LONG, 0x0C01) ++#define PR_IPM_RETURN_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C02) ++#define PR_MESSAGE_TOKEN PROP_TAG( PT_BINARY, 0x0C03) ++#define PR_NDR_REASON_CODE PROP_TAG( PT_LONG, 0x0C04) ++#define PR_NDR_DIAG_CODE PROP_TAG( PT_LONG, 0x0C05) ++#define PR_NON_RECEIPT_NOTIFICATION_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C06) ++#define PR_DELIVERY_POINT PROP_TAG( PT_LONG, 0x0C07) ++ ++#define PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C08) ++#define PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT PROP_TAG( PT_BINARY, 0x0C09) ++#define PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY PROP_TAG( PT_BOOLEAN, 0x0C0A) ++#define PR_PHYSICAL_DELIVERY_MODE PROP_TAG( PT_LONG, 0x0C0B) ++#define PR_PHYSICAL_DELIVERY_REPORT_REQUEST PROP_TAG( PT_LONG, 0x0C0C) ++#define PR_PHYSICAL_FORWARDING_ADDRESS PROP_TAG( PT_BINARY, 0x0C0D) ++#define PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C0E) ++#define PR_PHYSICAL_FORWARDING_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x0C0F) ++#define PR_PHYSICAL_RENDITION_ATTRIBUTES PROP_TAG( PT_BINARY, 0x0C10) ++#define PR_PROOF_OF_DELIVERY PROP_TAG( PT_BINARY, 0x0C11) ++#define PR_PROOF_OF_DELIVERY_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C12) ++#define PR_RECIPIENT_CERTIFICATE PROP_TAG( PT_BINARY, 0x0C13) ++#define PR_RECIPIENT_NUMBER_FOR_ADVICE PROP_TAG( PT_TSTRING, 0x0C14) ++#define PR_RECIPIENT_NUMBER_FOR_ADVICE_W PROP_TAG( PT_UNICODE, 0x0C14) ++#define PR_RECIPIENT_NUMBER_FOR_ADVICE_A PROP_TAG( PT_STRING8, 0x0C14) ++#define PR_RECIPIENT_TYPE PROP_TAG( PT_LONG, 0x0C15) ++#define PR_REGISTERED_MAIL_TYPE PROP_TAG( PT_LONG, 0x0C16) ++#define PR_REPLY_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C17) ++#define PR_REQUESTED_DELIVERY_METHOD PROP_TAG( PT_LONG, 0x0C18) ++#define PR_SENDER_ENTRYID PROP_TAG( PT_BINARY, 0x0C19) ++#define PR_SENDER_NAME PROP_TAG( PT_TSTRING, 0x0C1A) ++#define PR_SENDER_NAME_W PROP_TAG( PT_UNICODE, 0x0C1A) ++#define PR_SENDER_NAME_A PROP_TAG( PT_STRING8, 0x0C1A) ++#define PR_SUPPLEMENTARY_INFO PROP_TAG( PT_TSTRING, 0x0C1B) ++#define PR_SUPPLEMENTARY_INFO_W PROP_TAG( PT_UNICODE, 0x0C1B) ++#define PR_SUPPLEMENTARY_INFO_A PROP_TAG( PT_STRING8, 0x0C1B) ++#define PR_TYPE_OF_MTS_USER PROP_TAG( PT_LONG, 0x0C1C) ++#define PR_SENDER_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0C1D) ++#define PR_SENDER_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0C1E) ++#define PR_SENDER_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0C1E) ++#define PR_SENDER_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0C1E) ++#define PR_SENDER_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0C1F) ++#define PR_SENDER_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0C1F) ++#define PR_SENDER_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0C1F) ++ ++/* ++ * Message non-transmittable properties ++ */ ++ ++/* ++ * The two tags, PR_MESSAGE_RECIPIENTS and PR_MESSAGE_ATTACHMENTS, ++ * are to be used in the exclude list passed to ++ * IMessage::CopyTo when the caller wants either the recipients or attachments ++ * of the message to not get copied. It is also used in the ProblemArray ++ * return from IMessage::CopyTo when an error is encountered copying them ++ */ ++ ++#define PR_CURRENT_VERSION PROP_TAG( PT_I8, 0x0E00) ++#define PR_DELETE_AFTER_SUBMIT PROP_TAG( PT_BOOLEAN, 0x0E01) ++#define PR_DISPLAY_BCC PROP_TAG( PT_TSTRING, 0x0E02) ++#define PR_DISPLAY_BCC_W PROP_TAG( PT_UNICODE, 0x0E02) ++#define PR_DISPLAY_BCC_A PROP_TAG( PT_STRING8, 0x0E02) ++#define PR_DISPLAY_CC PROP_TAG( PT_TSTRING, 0x0E03) ++#define PR_DISPLAY_CC_W PROP_TAG( PT_UNICODE, 0x0E03) ++#define PR_DISPLAY_CC_A PROP_TAG( PT_STRING8, 0x0E03) ++#define PR_DISPLAY_TO PROP_TAG( PT_TSTRING, 0x0E04) ++#define PR_DISPLAY_TO_W PROP_TAG( PT_UNICODE, 0x0E04) ++#define PR_DISPLAY_TO_A PROP_TAG( PT_STRING8, 0x0E04) ++#define PR_PARENT_DISPLAY PROP_TAG( PT_TSTRING, 0x0E05) ++#define PR_PARENT_DISPLAY_W PROP_TAG( PT_UNICODE, 0x0E05) ++#define PR_PARENT_DISPLAY_A PROP_TAG( PT_STRING8, 0x0E05) ++#define PR_MESSAGE_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x0E06) ++#define PR_MESSAGE_FLAGS PROP_TAG( PT_LONG, 0x0E07) ++#define PR_MESSAGE_SIZE PROP_TAG( PT_LONG, 0x0E08) ++#define PR_PARENT_ENTRYID PROP_TAG( PT_BINARY, 0x0E09) ++#define PR_SENTMAIL_ENTRYID PROP_TAG( PT_BINARY, 0x0E0A) ++#define PR_CORRELATE PROP_TAG( PT_BOOLEAN, 0x0E0C) ++#define PR_CORRELATE_MTSID PROP_TAG( PT_BINARY, 0x0E0D) ++#define PR_DISCRETE_VALUES PROP_TAG( PT_BOOLEAN, 0x0E0E) ++#define PR_RESPONSIBILITY PROP_TAG( PT_BOOLEAN, 0x0E0F) ++#define PR_SPOOLER_STATUS PROP_TAG( PT_LONG, 0x0E10) ++#define PR_TRANSPORT_STATUS PROP_TAG( PT_LONG, 0x0E11) ++#define PR_MESSAGE_RECIPIENTS PROP_TAG( PT_OBJECT, 0x0E12) ++#define PR_MESSAGE_ATTACHMENTS PROP_TAG( PT_OBJECT, 0x0E13) ++#define PR_SUBMIT_FLAGS PROP_TAG( PT_LONG, 0x0E14) ++#define PR_RECIPIENT_STATUS PROP_TAG( PT_LONG, 0x0E15) ++#define PR_TRANSPORT_KEY PROP_TAG( PT_LONG, 0x0E16) ++#define PR_MSG_STATUS PROP_TAG( PT_LONG, 0x0E17) ++#define PR_MESSAGE_DOWNLOAD_TIME PROP_TAG( PT_LONG, 0x0E18) ++#define PR_CREATION_VERSION PROP_TAG( PT_I8, 0x0E19) ++#define PR_MODIFY_VERSION PROP_TAG( PT_I8, 0x0E1A) ++#define PR_HASATTACH PROP_TAG( PT_BOOLEAN, 0x0E1B) ++#define PR_BODY_CRC PROP_TAG( PT_LONG, 0x0E1C) ++#define PR_NORMALIZED_SUBJECT PROP_TAG( PT_TSTRING, 0x0E1D) ++#define PR_NORMALIZED_SUBJECT_W PROP_TAG( PT_UNICODE, 0x0E1D) ++#define PR_NORMALIZED_SUBJECT_A PROP_TAG( PT_STRING8, 0x0E1D) ++#define PR_RTF_IN_SYNC PROP_TAG( PT_BOOLEAN, 0x0E1F) ++#define PR_ATTACH_SIZE PROP_TAG( PT_LONG, 0x0E20) ++#define PR_ATTACH_NUM PROP_TAG( PT_LONG, 0x0E21) ++#define PR_PREPROCESS PROP_TAG( PT_BOOLEAN, 0x0E22) ++ ++/* PR_ORIGINAL_DISPLAY_TO, _CC, and _BCC moved to transmittible range 03/09/95 */ ++ ++#define PR_ORIGINATING_MTA_CERTIFICATE PROP_TAG( PT_BINARY, 0x0E25) ++#define PR_PROOF_OF_SUBMISSION PROP_TAG( PT_BINARY, 0x0E26) ++ ++ ++/* ++ * The range of non-message and non-recipient property IDs (0x3000 - 0x3FFF) is ++ * further broken down into ranges to make assigning new property IDs easier. ++ * ++ * From To Kind of property ++ * -------------------------------- ++ * 3000 32FF MAPI_defined common property ++ * 3200 33FF MAPI_defined form property ++ * 3400 35FF MAPI_defined message store property ++ * 3600 36FF MAPI_defined Folder or AB Container property ++ * 3700 38FF MAPI_defined attachment property ++ * 3900 39FF MAPI_defined address book property ++ * 3A00 3BFF MAPI_defined mailuser property ++ * 3C00 3CFF MAPI_defined DistList property ++ * 3D00 3DFF MAPI_defined Profile Section property ++ * 3E00 3EFF MAPI_defined Status property ++ * 3F00 3FFF MAPI_defined display table property ++ */ ++ ++/* ++ * Properties common to numerous MAPI objects. ++ * ++ * Those properties that can appear on messages are in the ++ * non-transmittable range for messages. They start at the high ++ * end of that range and work down. ++ * ++ * Properties that never appear on messages are defined in the common ++ * property range (see above). ++ */ ++ ++/* ++ * properties that are common to multiple objects (including message objects) ++ * -- these ids are in the non-transmittable range ++ */ ++ ++#define PR_ENTRYID PROP_TAG( PT_BINARY, 0x0FFF) ++#define PR_OBJECT_TYPE PROP_TAG( PT_LONG, 0x0FFE) ++#define PR_ICON PROP_TAG( PT_BINARY, 0x0FFD) ++#define PR_MINI_ICON PROP_TAG( PT_BINARY, 0x0FFC) ++#define PR_STORE_ENTRYID PROP_TAG( PT_BINARY, 0x0FFB) ++#define PR_STORE_RECORD_KEY PROP_TAG( PT_BINARY, 0x0FFA) ++#define PR_RECORD_KEY PROP_TAG( PT_BINARY, 0x0FF9) ++#define PR_MAPPING_SIGNATURE PROP_TAG( PT_BINARY, 0x0FF8) ++#define PR_ACCESS_LEVEL PROP_TAG( PT_LONG, 0x0FF7) ++#define PR_INSTANCE_KEY PROP_TAG( PT_BINARY, 0x0FF6) ++#define PR_ROW_TYPE PROP_TAG( PT_LONG, 0x0FF5) ++#define PR_ACCESS PROP_TAG( PT_LONG, 0x0FF4) ++ ++/* ++ * properties that are common to multiple objects (usually not including message objects) ++ * -- these ids are in the transmittable range ++ */ ++ ++#define PR_ROWID PROP_TAG( PT_LONG, 0x3000) ++#define PR_DISPLAY_NAME PROP_TAG( PT_TSTRING, 0x3001) ++#define PR_DISPLAY_NAME_W PROP_TAG( PT_UNICODE, 0x3001) ++#define PR_DISPLAY_NAME_A PROP_TAG( PT_STRING8, 0x3001) ++#define PR_ADDRTYPE PROP_TAG( PT_TSTRING, 0x3002) ++#define PR_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x3002) ++#define PR_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x3002) ++#define PR_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x3003) ++#define PR_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x3003) ++#define PR_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x3003) ++#define PR_COMMENT PROP_TAG( PT_TSTRING, 0x3004) ++#define PR_COMMENT_W PROP_TAG( PT_UNICODE, 0x3004) ++#define PR_COMMENT_A PROP_TAG( PT_STRING8, 0x3004) ++#define PR_DEPTH PROP_TAG( PT_LONG, 0x3005) ++#define PR_PROVIDER_DISPLAY PROP_TAG( PT_TSTRING, 0x3006) ++#define PR_PROVIDER_DISPLAY_W PROP_TAG( PT_UNICODE, 0x3006) ++#define PR_PROVIDER_DISPLAY_A PROP_TAG( PT_STRING8, 0x3006) ++#define PR_CREATION_TIME PROP_TAG( PT_SYSTIME, 0x3007) ++#define PR_LAST_MODIFICATION_TIME PROP_TAG( PT_SYSTIME, 0x3008) ++#define PR_RESOURCE_FLAGS PROP_TAG( PT_LONG, 0x3009) ++#define PR_PROVIDER_DLL_NAME PROP_TAG( PT_TSTRING, 0x300A) ++#define PR_PROVIDER_DLL_NAME_W PROP_TAG( PT_UNICODE, 0x300A) ++#define PR_PROVIDER_DLL_NAME_A PROP_TAG( PT_STRING8, 0x300A) ++#define PR_SEARCH_KEY PROP_TAG( PT_BINARY, 0x300B) ++#define PR_PROVIDER_UID PROP_TAG( PT_BINARY, 0x300C) ++#define PR_PROVIDER_ORDINAL PROP_TAG( PT_LONG, 0x300D) ++ ++/* ++ * MAPI Form properties ++ */ ++#define PR_FORM_VERSION PROP_TAG(PT_TSTRING, 0x3301) ++#define PR_FORM_VERSION_W PROP_TAG(PT_UNICODE, 0x3301) ++#define PR_FORM_VERSION_A PROP_TAG(PT_STRING8, 0x3301) ++#define PR_FORM_CLSID PROP_TAG(PT_CLSID, 0x3302) ++#define PR_FORM_CONTACT_NAME PROP_TAG(PT_TSTRING, 0x3303) ++#define PR_FORM_CONTACT_NAME_W PROP_TAG(PT_UNICODE, 0x3303) ++#define PR_FORM_CONTACT_NAME_A PROP_TAG(PT_STRING8, 0x3303) ++#define PR_FORM_CATEGORY PROP_TAG(PT_TSTRING, 0x3304) ++#define PR_FORM_CATEGORY_W PROP_TAG(PT_UNICODE, 0x3304) ++#define PR_FORM_CATEGORY_A PROP_TAG(PT_STRING8, 0x3304) ++#define PR_FORM_CATEGORY_SUB PROP_TAG(PT_TSTRING, 0x3305) ++#define PR_FORM_CATEGORY_SUB_W PROP_TAG(PT_UNICODE, 0x3305) ++#define PR_FORM_CATEGORY_SUB_A PROP_TAG(PT_STRING8, 0x3305) ++#define PR_FORM_HOST_MAP PROP_TAG(PT_MV_LONG, 0x3306) ++#define PR_FORM_HIDDEN PROP_TAG(PT_BOOLEAN, 0x3307) ++#define PR_FORM_DESIGNER_NAME PROP_TAG(PT_TSTRING, 0x3308) ++#define PR_FORM_DESIGNER_NAME_W PROP_TAG(PT_UNICODE, 0x3308) ++#define PR_FORM_DESIGNER_NAME_A PROP_TAG(PT_STRING8, 0x3308) ++#define PR_FORM_DESIGNER_GUID PROP_TAG(PT_CLSID, 0x3309) ++#define PR_FORM_MESSAGE_BEHAVIOR PROP_TAG(PT_LONG, 0x330A) ++ ++/* ++ * Message store properties ++ */ ++ ++#define PR_DEFAULT_STORE PROP_TAG( PT_BOOLEAN, 0x3400) ++#define PR_STORE_SUPPORT_MASK PROP_TAG( PT_LONG, 0x340D) ++#define PR_STORE_STATE PROP_TAG( PT_LONG, 0x340E) ++ ++#define PR_IPM_SUBTREE_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3410) ++#define PR_IPM_OUTBOX_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3411) ++#define PR_IPM_WASTEBASKET_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3412) ++#define PR_IPM_SENTMAIL_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3413) ++#define PR_MDB_PROVIDER PROP_TAG( PT_BINARY, 0x3414) ++#define PR_RECEIVE_FOLDER_SETTINGS PROP_TAG( PT_OBJECT, 0x3415) ++ ++#define PR_VALID_FOLDER_MASK PROP_TAG( PT_LONG, 0x35DF) ++#define PR_IPM_SUBTREE_ENTRYID PROP_TAG( PT_BINARY, 0x35E0) ++ ++#define PR_IPM_OUTBOX_ENTRYID PROP_TAG( PT_BINARY, 0x35E2) ++#define PR_IPM_WASTEBASKET_ENTRYID PROP_TAG( PT_BINARY, 0x35E3) ++#define PR_IPM_SENTMAIL_ENTRYID PROP_TAG( PT_BINARY, 0x35E4) ++#define PR_VIEWS_ENTRYID PROP_TAG( PT_BINARY, 0x35E5) ++#define PR_COMMON_VIEWS_ENTRYID PROP_TAG( PT_BINARY, 0x35E6) ++#define PR_FINDER_ENTRYID PROP_TAG( PT_BINARY, 0x35E7) ++ ++/* Proptags 0x35E8-0x35FF reserved for folders "guaranteed" by PR_VALID_FOLDER_MASK */ ++ ++ ++/* ++ * Folder and AB Container properties ++ */ ++ ++#define PR_CONTAINER_FLAGS PROP_TAG( PT_LONG, 0x3600) ++#define PR_FOLDER_TYPE PROP_TAG( PT_LONG, 0x3601) ++#define PR_CONTENT_COUNT PROP_TAG( PT_LONG, 0x3602) ++#define PR_CONTENT_UNREAD PROP_TAG( PT_LONG, 0x3603) ++#define PR_CREATE_TEMPLATES PROP_TAG( PT_OBJECT, 0x3604) ++#define PR_DETAILS_TABLE PROP_TAG( PT_OBJECT, 0x3605) ++#define PR_SEARCH PROP_TAG( PT_OBJECT, 0x3607) ++#define PR_SELECTABLE PROP_TAG( PT_BOOLEAN, 0x3609) ++#define PR_SUBFOLDERS PROP_TAG( PT_BOOLEAN, 0x360A) ++#define PR_STATUS PROP_TAG( PT_LONG, 0x360B) ++#define PR_ANR PROP_TAG( PT_TSTRING, 0x360C) ++#define PR_ANR_W PROP_TAG( PT_UNICODE, 0x360C) ++#define PR_ANR_A PROP_TAG( PT_STRING8, 0x360C) ++#define PR_CONTENTS_SORT_ORDER PROP_TAG( PT_MV_LONG, 0x360D) ++#define PR_CONTAINER_HIERARCHY PROP_TAG( PT_OBJECT, 0x360E) ++#define PR_CONTAINER_CONTENTS PROP_TAG( PT_OBJECT, 0x360F) ++#define PR_FOLDER_ASSOCIATED_CONTENTS PROP_TAG( PT_OBJECT, 0x3610) ++#define PR_DEF_CREATE_DL PROP_TAG( PT_BINARY, 0x3611) ++#define PR_DEF_CREATE_MAILUSER PROP_TAG( PT_BINARY, 0x3612) ++#define PR_CONTAINER_CLASS PROP_TAG( PT_TSTRING, 0x3613) ++#define PR_CONTAINER_CLASS_W PROP_TAG( PT_UNICODE, 0x3613) ++#define PR_CONTAINER_CLASS_A PROP_TAG( PT_STRING8, 0x3613) ++#define PR_CONTAINER_MODIFY_VERSION PROP_TAG( PT_I8, 0x3614) ++#define PR_AB_PROVIDER_ID PROP_TAG( PT_BINARY, 0x3615) ++#define PR_DEFAULT_VIEW_ENTRYID PROP_TAG( PT_BINARY, 0x3616) ++#define PR_ASSOC_CONTENT_COUNT PROP_TAG( PT_LONG, 0x3617) ++ ++/* Reserved 0x36C0-0x36FF */ ++ ++/* ++ * Attachment properties ++ */ ++ ++#define PR_ATTACHMENT_X400_PARAMETERS PROP_TAG( PT_BINARY, 0x3700) ++#define PR_ATTACH_DATA_OBJ PROP_TAG( PT_OBJECT, 0x3701) ++#define PR_ATTACH_DATA_BIN PROP_TAG( PT_BINARY, 0x3701) ++#define PR_ATTACH_ENCODING PROP_TAG( PT_BINARY, 0x3702) ++#define PR_ATTACH_EXTENSION PROP_TAG( PT_TSTRING, 0x3703) ++#define PR_ATTACH_EXTENSION_W PROP_TAG( PT_UNICODE, 0x3703) ++#define PR_ATTACH_EXTENSION_A PROP_TAG( PT_STRING8, 0x3703) ++#define PR_ATTACH_FILENAME PROP_TAG( PT_TSTRING, 0x3704) ++#define PR_ATTACH_FILENAME_W PROP_TAG( PT_UNICODE, 0x3704) ++#define PR_ATTACH_FILENAME_A PROP_TAG( PT_STRING8, 0x3704) ++#define PR_ATTACH_METHOD PROP_TAG( PT_LONG, 0x3705) ++#define PR_ATTACH_LONG_FILENAME PROP_TAG( PT_TSTRING, 0x3707) ++#define PR_ATTACH_LONG_FILENAME_W PROP_TAG( PT_UNICODE, 0x3707) ++#define PR_ATTACH_LONG_FILENAME_A PROP_TAG( PT_STRING8, 0x3707) ++#define PR_ATTACH_PATHNAME PROP_TAG( PT_TSTRING, 0x3708) ++#define PR_ATTACH_PATHNAME_W PROP_TAG( PT_UNICODE, 0x3708) ++#define PR_ATTACH_PATHNAME_A PROP_TAG( PT_STRING8, 0x3708) ++#define PR_ATTACH_RENDERING PROP_TAG( PT_BINARY, 0x3709) ++#define PR_ATTACH_TAG PROP_TAG( PT_BINARY, 0x370A) ++#define PR_RENDERING_POSITION PROP_TAG( PT_LONG, 0x370B) ++#define PR_ATTACH_TRANSPORT_NAME PROP_TAG( PT_TSTRING, 0x370C) ++#define PR_ATTACH_TRANSPORT_NAME_W PROP_TAG( PT_UNICODE, 0x370C) ++#define PR_ATTACH_TRANSPORT_NAME_A PROP_TAG( PT_STRING8, 0x370C) ++#define PR_ATTACH_LONG_PATHNAME PROP_TAG( PT_TSTRING, 0x370D) ++#define PR_ATTACH_LONG_PATHNAME_W PROP_TAG( PT_UNICODE, 0x370D) ++#define PR_ATTACH_LONG_PATHNAME_A PROP_TAG( PT_STRING8, 0x370D) ++#define PR_ATTACH_MIME_TAG PROP_TAG( PT_TSTRING, 0x370E) ++#define PR_ATTACH_MIME_TAG_W PROP_TAG( PT_UNICODE, 0x370E) ++#define PR_ATTACH_MIME_TAG_A PROP_TAG( PT_STRING8, 0x370E) ++#define PR_ATTACH_ADDITIONAL_INFO PROP_TAG( PT_BINARY, 0x370F) ++ ++/* ++ * AB Object properties ++ */ ++ ++#define PR_DISPLAY_TYPE PROP_TAG( PT_LONG, 0x3900) ++#define PR_TEMPLATEID PROP_TAG( PT_BINARY, 0x3902) ++#define PR_PRIMARY_CAPABILITY PROP_TAG( PT_BINARY, 0x3904) ++ ++ ++/* ++ * Mail user properties ++ */ ++#define PR_7BIT_DISPLAY_NAME PROP_TAG( PT_STRING8, 0x39FF) ++#define PR_ACCOUNT PROP_TAG( PT_TSTRING, 0x3A00) ++#define PR_ACCOUNT_W PROP_TAG( PT_UNICODE, 0x3A00) ++#define PR_ACCOUNT_A PROP_TAG( PT_STRING8, 0x3A00) ++#define PR_ALTERNATE_RECIPIENT PROP_TAG( PT_BINARY, 0x3A01) ++#define PR_CALLBACK_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A02) ++#define PR_CALLBACK_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A02) ++#define PR_CALLBACK_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A02) ++#define PR_CONVERSION_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x3A03) ++#define PR_DISCLOSE_RECIPIENTS PROP_TAG( PT_BOOLEAN, 0x3A04) ++#define PR_GENERATION PROP_TAG( PT_TSTRING, 0x3A05) ++#define PR_GENERATION_W PROP_TAG( PT_UNICODE, 0x3A05) ++#define PR_GENERATION_A PROP_TAG( PT_STRING8, 0x3A05) ++#define PR_GIVEN_NAME PROP_TAG( PT_TSTRING, 0x3A06) ++#define PR_GIVEN_NAME_W PROP_TAG( PT_UNICODE, 0x3A06) ++#define PR_GIVEN_NAME_A PROP_TAG( PT_STRING8, 0x3A06) ++#define PR_GOVERNMENT_ID_NUMBER PROP_TAG( PT_TSTRING, 0x3A07) ++#define PR_GOVERNMENT_ID_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A07) ++#define PR_GOVERNMENT_ID_NUMBER_A PROP_TAG( PT_STRING8, 0x3A07) ++#define PR_BUSINESS_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A08) ++#define PR_BUSINESS_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A08) ++#define PR_BUSINESS_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A08) ++#define PR_OFFICE_TELEPHONE_NUMBER PR_BUSINESS_TELEPHONE_NUMBER ++#define PR_OFFICE_TELEPHONE_NUMBER_W PR_BUSINESS_TELEPHONE_NUMBER_W ++#define PR_OFFICE_TELEPHONE_NUMBER_A PR_BUSINESS_TELEPHONE_NUMBER_A ++#define PR_HOME_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A09) ++#define PR_HOME_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A09) ++#define PR_HOME_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A09) ++#define PR_INITIALS PROP_TAG( PT_TSTRING, 0x3A0A) ++#define PR_INITIALS_W PROP_TAG( PT_UNICODE, 0x3A0A) ++#define PR_INITIALS_A PROP_TAG( PT_STRING8, 0x3A0A) ++#define PR_KEYWORD PROP_TAG( PT_TSTRING, 0x3A0B) ++#define PR_KEYWORD_W PROP_TAG( PT_UNICODE, 0x3A0B) ++#define PR_KEYWORD_A PROP_TAG( PT_STRING8, 0x3A0B) ++#define PR_LANGUAGE PROP_TAG( PT_TSTRING, 0x3A0C) ++#define PR_LANGUAGE_W PROP_TAG( PT_UNICODE, 0x3A0C) ++#define PR_LANGUAGE_A PROP_TAG( PT_STRING8, 0x3A0C) ++#define PR_LOCATION PROP_TAG( PT_TSTRING, 0x3A0D) ++#define PR_LOCATION_W PROP_TAG( PT_UNICODE, 0x3A0D) ++#define PR_LOCATION_A PROP_TAG( PT_STRING8, 0x3A0D) ++#define PR_MAIL_PERMISSION PROP_TAG( PT_BOOLEAN, 0x3A0E) ++#define PR_MHS_COMMON_NAME PROP_TAG( PT_TSTRING, 0x3A0F) ++#define PR_MHS_COMMON_NAME_W PROP_TAG( PT_UNICODE, 0x3A0F) ++#define PR_MHS_COMMON_NAME_A PROP_TAG( PT_STRING8, 0x3A0F) ++#define PR_ORGANIZATIONAL_ID_NUMBER PROP_TAG( PT_TSTRING, 0x3A10) ++#define PR_ORGANIZATIONAL_ID_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A10) ++#define PR_ORGANIZATIONAL_ID_NUMBER_A PROP_TAG( PT_STRING8, 0x3A10) ++#define PR_SURNAME PROP_TAG( PT_TSTRING, 0x3A11) ++#define PR_SURNAME_W PROP_TAG( PT_UNICODE, 0x3A11) ++#define PR_SURNAME_A PROP_TAG( PT_STRING8, 0x3A11) ++#define PR_ORIGINAL_ENTRYID PROP_TAG( PT_BINARY, 0x3A12) ++#define PR_ORIGINAL_DISPLAY_NAME PROP_TAG( PT_TSTRING, 0x3A13) ++#define PR_ORIGINAL_DISPLAY_NAME_W PROP_TAG( PT_UNICODE, 0x3A13) ++#define PR_ORIGINAL_DISPLAY_NAME_A PROP_TAG( PT_STRING8, 0x3A13) ++#define PR_ORIGINAL_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3A14) ++#define PR_POSTAL_ADDRESS PROP_TAG( PT_TSTRING, 0x3A15) ++#define PR_POSTAL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x3A15) ++#define PR_POSTAL_ADDRESS_A PROP_TAG( PT_STRING8, 0x3A15) ++#define PR_COMPANY_NAME PROP_TAG( PT_TSTRING, 0x3A16) ++#define PR_COMPANY_NAME_W PROP_TAG( PT_UNICODE, 0x3A16) ++#define PR_COMPANY_NAME_A PROP_TAG( PT_STRING8, 0x3A16) ++#define PR_TITLE PROP_TAG( PT_TSTRING, 0x3A17) ++#define PR_TITLE_W PROP_TAG( PT_UNICODE, 0x3A17) ++#define PR_TITLE_A PROP_TAG( PT_STRING8, 0x3A17) ++#define PR_DEPARTMENT_NAME PROP_TAG( PT_TSTRING, 0x3A18) ++#define PR_DEPARTMENT_NAME_W PROP_TAG( PT_UNICODE, 0x3A18) ++#define PR_DEPARTMENT_NAME_A PROP_TAG( PT_STRING8, 0x3A18) ++#define PR_OFFICE_LOCATION PROP_TAG( PT_TSTRING, 0x3A19) ++#define PR_OFFICE_LOCATION_W PROP_TAG( PT_UNICODE, 0x3A19) ++#define PR_OFFICE_LOCATION_A PROP_TAG( PT_STRING8, 0x3A19) ++#define PR_PRIMARY_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1A) ++#define PR_PRIMARY_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1A) ++#define PR_PRIMARY_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1A) ++#define PR_BUSINESS2_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1B) ++#define PR_BUSINESS2_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1B) ++#define PR_BUSINESS2_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1B) ++#define PR_OFFICE2_TELEPHONE_NUMBER PR_BUSINESS2_TELEPHONE_NUMBER ++#define PR_OFFICE2_TELEPHONE_NUMBER_W PR_BUSINESS2_TELEPHONE_NUMBER_W ++#define PR_OFFICE2_TELEPHONE_NUMBER_A PR_BUSINESS2_TELEPHONE_NUMBER_A ++#define PR_MOBILE_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1C) ++#define PR_MOBILE_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1C) ++#define PR_MOBILE_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1C) ++#define PR_CELLULAR_TELEPHONE_NUMBER PR_MOBILE_TELEPHONE_NUMBER ++#define PR_CELLULAR_TELEPHONE_NUMBER_W PR_MOBILE_TELEPHONE_NUMBER_W ++#define PR_CELLULAR_TELEPHONE_NUMBER_A PR_MOBILE_TELEPHONE_NUMBER_A ++#define PR_RADIO_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1D) ++#define PR_RADIO_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1D) ++#define PR_RADIO_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1D) ++#define PR_CAR_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1E) ++#define PR_CAR_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1E) ++#define PR_CAR_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1E) ++#define PR_OTHER_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1F) ++#define PR_OTHER_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1F) ++#define PR_OTHER_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1F) ++#define PR_TRANSMITABLE_DISPLAY_NAME PROP_TAG( PT_TSTRING, 0x3A20) ++#define PR_TRANSMITABLE_DISPLAY_NAME_W PROP_TAG( PT_UNICODE, 0x3A20) ++#define PR_TRANSMITABLE_DISPLAY_NAME_A PROP_TAG( PT_STRING8, 0x3A20) ++#define PR_PAGER_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A21) ++#define PR_PAGER_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A21) ++#define PR_PAGER_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A21) ++#define PR_BEEPER_TELEPHONE_NUMBER PR_PAGER_TELEPHONE_NUMBER ++#define PR_BEEPER_TELEPHONE_NUMBER_W PR_PAGER_TELEPHONE_NUMBER_W ++#define PR_BEEPER_TELEPHONE_NUMBER_A PR_PAGER_TELEPHONE_NUMBER_A ++#define PR_USER_CERTIFICATE PROP_TAG( PT_BINARY, 0x3A22) ++#define PR_PRIMARY_FAX_NUMBER PROP_TAG( PT_TSTRING, 0x3A23) ++#define PR_PRIMARY_FAX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A23) ++#define PR_PRIMARY_FAX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A23) ++#define PR_BUSINESS_FAX_NUMBER PROP_TAG( PT_TSTRING, 0x3A24) ++#define PR_BUSINESS_FAX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A24) ++#define PR_BUSINESS_FAX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A24) ++#define PR_HOME_FAX_NUMBER PROP_TAG( PT_TSTRING, 0x3A25) ++#define PR_HOME_FAX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A25) ++#define PR_HOME_FAX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A25) ++#define PR_COUNTRY PROP_TAG( PT_TSTRING, 0x3A26) ++#define PR_COUNTRY_W PROP_TAG( PT_UNICODE, 0x3A26) ++#define PR_COUNTRY_A PROP_TAG( PT_STRING8, 0x3A26) ++#define PR_BUSINESS_ADDRESS_COUNTRY PR_COUNTRY ++#define PR_BUSINESS_ADDRESS_COUNTRY_W PR_COUNTRY_W ++#define PR_BUSINESS_ADDRESS_COUNTRY_A PR_COUNTRY_A ++ ++#define PR_LOCALITY PROP_TAG( PT_TSTRING, 0x3A27) ++#define PR_LOCALITY_W PROP_TAG( PT_UNICODE, 0x3A27) ++#define PR_LOCALITY_A PROP_TAG( PT_STRING8, 0x3A27) ++#define PR_BUSINESS_ADDRESS_CITY PR_LOCALITY ++#define PR_BUSINESS_ADDRESS_CITY_W PR_LOCALITY_W ++#define PR_BUSINESS_ADDRESS_CITY_A PR_LOCALITY_A ++ ++#define PR_STATE_OR_PROVINCE PROP_TAG( PT_TSTRING, 0x3A28) ++#define PR_STATE_OR_PROVINCE_W PROP_TAG( PT_UNICODE, 0x3A28) ++#define PR_STATE_OR_PROVINCE_A PROP_TAG( PT_STRING8, 0x3A28) ++#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE PR_STATE_OR_PROVINCE ++#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_W PR_STATE_OR_PROVINCE_W ++#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_A PR_STATE_OR_PROVINCE_A ++ ++#define PR_STREET_ADDRESS PROP_TAG( PT_TSTRING, 0x3A29) ++#define PR_STREET_ADDRESS_W PROP_TAG( PT_UNICODE, 0x3A29) ++#define PR_STREET_ADDRESS_A PROP_TAG( PT_STRING8, 0x3A29) ++#define PR_BUSINESS_ADDRESS_STREET PR_STREET_ADDRESS ++#define PR_BUSINESS_ADDRESS_STREET_W PR_STREET_ADDRESS_W ++#define PR_BUSINESS_ADDRESS_STREET_A PR_STREET_ADDRESS_A ++ ++#define PR_POSTAL_CODE PROP_TAG( PT_TSTRING, 0x3A2A) ++#define PR_POSTAL_CODE_W PROP_TAG( PT_UNICODE, 0x3A2A) ++#define PR_POSTAL_CODE_A PROP_TAG( PT_STRING8, 0x3A2A) ++#define PR_BUSINESS_ADDRESS_POSTAL_CODE PR_POSTAL_CODE ++#define PR_BUSINESS_ADDRESS_POSTAL_CODE_W PR_POSTAL_CODE_W ++#define PR_BUSINESS_ADDRESS_POSTAL_CODE_A PR_POSTAL_CODE_A ++ ++ ++#define PR_POST_OFFICE_BOX PROP_TAG( PT_TSTRING, 0x3A2B) ++#define PR_POST_OFFICE_BOX_W PROP_TAG( PT_UNICODE, 0x3A2B) ++#define PR_POST_OFFICE_BOX_A PROP_TAG( PT_STRING8, 0x3A2B) ++#define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX PR_POST_OFFICE_BOX ++#define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX_W PR_POST_OFFICE_BOX_W ++#define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX_A PR_POST_OFFICE_BOX_A ++ ++ ++#define PR_TELEX_NUMBER PROP_TAG( PT_TSTRING, 0x3A2C) ++#define PR_TELEX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2C) ++#define PR_TELEX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2C) ++#define PR_ISDN_NUMBER PROP_TAG( PT_TSTRING, 0x3A2D) ++#define PR_ISDN_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2D) ++#define PR_ISDN_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2D) ++#define PR_ASSISTANT_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A2E) ++#define PR_ASSISTANT_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2E) ++#define PR_ASSISTANT_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2E) ++#define PR_HOME2_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A2F) ++#define PR_HOME2_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2F) ++#define PR_HOME2_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2F) ++#define PR_ASSISTANT PROP_TAG( PT_TSTRING, 0x3A30) ++#define PR_ASSISTANT_W PROP_TAG( PT_UNICODE, 0x3A30) ++#define PR_ASSISTANT_A PROP_TAG( PT_STRING8, 0x3A30) ++#define PR_SEND_RICH_INFO PROP_TAG( PT_BOOLEAN, 0x3A40) ++ ++#define PR_WEDDING_ANNIVERSARY PROP_TAG( PT_SYSTIME, 0x3A41) ++#define PR_BIRTHDAY PROP_TAG( PT_SYSTIME, 0x3A42) ++ ++ ++#define PR_HOBBIES PROP_TAG( PT_TSTRING, 0x3A43) ++#define PR_HOBBIES_W PROP_TAG( PT_UNICODE, 0x3A43) ++#define PR_HOBBIES_A PROP_TAG( PT_STRING8, 0x3A43) ++ ++#define PR_MIDDLE_NAME PROP_TAG( PT_TSTRING, 0x3A44) ++#define PR_MIDDLE_NAME_W PROP_TAG( PT_UNICODE, 0x3A44) ++#define PR_MIDDLE_NAME_A PROP_TAG( PT_STRING8, 0x3A44) ++ ++#define PR_DISPLAY_NAME_PREFIX PROP_TAG( PT_TSTRING, 0x3A45) ++#define PR_DISPLAY_NAME_PREFIX_W PROP_TAG( PT_UNICODE, 0x3A45) ++#define PR_DISPLAY_NAME_PREFIX_A PROP_TAG( PT_STRING8, 0x3A45) ++ ++#define PR_PROFESSION PROP_TAG( PT_TSTRING, 0x3A46) ++#define PR_PROFESSION_W PROP_TAG( PT_UNICODE, 0x3A46) ++#define PR_PROFESSION_A PROP_TAG( PT_STRING8, 0x3A46) ++ ++#define PR_PREFERRED_BY_NAME PROP_TAG( PT_TSTRING, 0x3A47) ++#define PR_PREFERRED_BY_NAME_W PROP_TAG( PT_UNICODE, 0x3A47) ++#define PR_PREFERRED_BY_NAME_A PROP_TAG( PT_STRING8, 0x3A47) ++ ++#define PR_SPOUSE_NAME PROP_TAG( PT_TSTRING, 0x3A48) ++#define PR_SPOUSE_NAME_W PROP_TAG( PT_UNICODE, 0x3A48) ++#define PR_SPOUSE_NAME_A PROP_TAG( PT_STRING8, 0x3A48) ++ ++#define PR_COMPUTER_NETWORK_NAME PROP_TAG( PT_TSTRING, 0x3A49) ++#define PR_COMPUTER_NETWORK_NAME_W PROP_TAG( PT_UNICODE, 0x3A49) ++#define PR_COMPUTER_NETWORK_NAME_A PROP_TAG( PT_STRING8, 0x3A49) ++ ++#define PR_CUSTOMER_ID PROP_TAG( PT_TSTRING, 0x3A4A) ++#define PR_CUSTOMER_ID_W PROP_TAG( PT_UNICODE, 0x3A4A) ++#define PR_CUSTOMER_ID_A PROP_TAG( PT_STRING8, 0x3A4A) ++ ++#define PR_TTYTDD_PHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A4B) ++#define PR_TTYTDD_PHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A4B) ++#define PR_TTYTDD_PHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A4B) ++ ++#define PR_FTP_SITE PROP_TAG( PT_TSTRING, 0x3A4C) ++#define PR_FTP_SITE_W PROP_TAG( PT_UNICODE, 0x3A4C) ++#define PR_FTP_SITE_A PROP_TAG( PT_STRING8, 0x3A4C) ++ ++#define PR_GENDER PROP_TAG( PT_SHORT, 0x3A4D) ++ ++#define PR_MANAGER_NAME PROP_TAG( PT_TSTRING, 0x3A4E) ++#define PR_MANAGER_NAME_W PROP_TAG( PT_UNICODE, 0x3A4E) ++#define PR_MANAGER_NAME_A PROP_TAG( PT_STRING8, 0x3A4E) ++ ++#define PR_NICKNAME PROP_TAG( PT_TSTRING, 0x3A4F) ++#define PR_NICKNAME_W PROP_TAG( PT_UNICODE, 0x3A4F) ++#define PR_NICKNAME_A PROP_TAG( PT_STRING8, 0x3A4F) ++ ++#define PR_PERSONAL_HOME_PAGE PROP_TAG( PT_TSTRING, 0x3A50) ++#define PR_PERSONAL_HOME_PAGE_W PROP_TAG( PT_UNICODE, 0x3A50) ++#define PR_PERSONAL_HOME_PAGE_A PROP_TAG( PT_STRING8, 0x3A50) ++ ++ ++#define PR_BUSINESS_HOME_PAGE PROP_TAG( PT_TSTRING, 0x3A51) ++#define PR_BUSINESS_HOME_PAGE_W PROP_TAG( PT_UNICODE, 0x3A51) ++#define PR_BUSINESS_HOME_PAGE_A PROP_TAG( PT_STRING8, 0x3A51) ++ ++#define PR_CONTACT_VERSION PROP_TAG( PT_CLSID, 0x3A52) ++#define PR_CONTACT_ENTRYIDS PROP_TAG( PT_MV_BINARY, 0x3A53) ++ ++#define PR_CONTACT_ADDRTYPES PROP_TAG( PT_MV_TSTRING, 0x3A54) ++#define PR_CONTACT_ADDRTYPES_W PROP_TAG( PT_MV_UNICODE, 0x3A54) ++#define PR_CONTACT_ADDRTYPES_A PROP_TAG( PT_MV_STRING8, 0x3A54) ++ ++#define PR_CONTACT_DEFAULT_ADDRESS_INDEX PROP_TAG( PT_LONG, 0x3A55) ++ ++#define PR_CONTACT_EMAIL_ADDRESSES PROP_TAG( PT_MV_TSTRING, 0x3A56) ++#define PR_CONTACT_EMAIL_ADDRESSES_W PROP_TAG( PT_MV_UNICODE, 0x3A56) ++#define PR_CONTACT_EMAIL_ADDRESSES_A PROP_TAG( PT_MV_STRING8, 0x3A56) ++ ++ ++#define PR_COMPANY_MAIN_PHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A57) ++#define PR_COMPANY_MAIN_PHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A57) ++#define PR_COMPANY_MAIN_PHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A57) ++ ++#define PR_CHILDRENS_NAMES PROP_TAG( PT_MV_TSTRING, 0x3A58) ++#define PR_CHILDRENS_NAMES_W PROP_TAG( PT_MV_UNICODE, 0x3A58) ++#define PR_CHILDRENS_NAMES_A PROP_TAG( PT_MV_STRING8, 0x3A58) ++ ++ ++ ++#define PR_HOME_ADDRESS_CITY PROP_TAG( PT_TSTRING, 0x3A59) ++#define PR_HOME_ADDRESS_CITY_W PROP_TAG( PT_UNICODE, 0x3A59) ++#define PR_HOME_ADDRESS_CITY_A PROP_TAG( PT_STRING8, 0x3A59) ++ ++#define PR_HOME_ADDRESS_COUNTRY PROP_TAG( PT_TSTRING, 0x3A5A) ++#define PR_HOME_ADDRESS_COUNTRY_W PROP_TAG( PT_UNICODE, 0x3A5A) ++#define PR_HOME_ADDRESS_COUNTRY_A PROP_TAG( PT_STRING8, 0x3A5A) ++ ++#define PR_HOME_ADDRESS_POSTAL_CODE PROP_TAG( PT_TSTRING, 0x3A5B) ++#define PR_HOME_ADDRESS_POSTAL_CODE_W PROP_TAG( PT_UNICODE, 0x3A5B) ++#define PR_HOME_ADDRESS_POSTAL_CODE_A PROP_TAG( PT_STRING8, 0x3A5B) ++ ++#define PR_HOME_ADDRESS_STATE_OR_PROVINCE PROP_TAG( PT_TSTRING, 0x3A5C) ++#define PR_HOME_ADDRESS_STATE_OR_PROVINCE_W PROP_TAG( PT_UNICODE, 0x3A5C) ++#define PR_HOME_ADDRESS_STATE_OR_PROVINCE_A PROP_TAG( PT_STRING8, 0x3A5C) ++ ++#define PR_HOME_ADDRESS_STREET PROP_TAG( PT_TSTRING, 0x3A5D) ++#define PR_HOME_ADDRESS_STREET_W PROP_TAG( PT_UNICODE, 0x3A5D) ++#define PR_HOME_ADDRESS_STREET_A PROP_TAG( PT_STRING8, 0x3A5D) ++ ++#define PR_HOME_ADDRESS_POST_OFFICE_BOX PROP_TAG( PT_TSTRING, 0x3A5E) ++#define PR_HOME_ADDRESS_POST_OFFICE_BOX_W PROP_TAG( PT_UNICODE, 0x3A5E) ++#define PR_HOME_ADDRESS_POST_OFFICE_BOX_A PROP_TAG( PT_STRING8, 0x3A5E) ++ ++#define PR_OTHER_ADDRESS_CITY PROP_TAG( PT_TSTRING, 0x3A5F) ++#define PR_OTHER_ADDRESS_CITY_W PROP_TAG( PT_UNICODE, 0x3A5F) ++#define PR_OTHER_ADDRESS_CITY_A PROP_TAG( PT_STRING8, 0x3A5F) ++ ++#define PR_OTHER_ADDRESS_COUNTRY PROP_TAG( PT_TSTRING, 0x3A60) ++#define PR_OTHER_ADDRESS_COUNTRY_W PROP_TAG( PT_UNICODE, 0x3A60) ++#define PR_OTHER_ADDRESS_COUNTRY_A PROP_TAG( PT_STRING8, 0x3A60) ++ ++#define PR_OTHER_ADDRESS_POSTAL_CODE PROP_TAG( PT_TSTRING, 0x3A61) ++#define PR_OTHER_ADDRESS_POSTAL_CODE_W PROP_TAG( PT_UNICODE, 0x3A61) ++#define PR_OTHER_ADDRESS_POSTAL_CODE_A PROP_TAG( PT_STRING8, 0x3A61) ++ ++#define PR_OTHER_ADDRESS_STATE_OR_PROVINCE PROP_TAG( PT_TSTRING, 0x3A62) ++#define PR_OTHER_ADDRESS_STATE_OR_PROVINCE_W PROP_TAG( PT_UNICODE, 0x3A62) ++#define PR_OTHER_ADDRESS_STATE_OR_PROVINCE_A PROP_TAG( PT_STRING8, 0x3A62) ++ ++#define PR_OTHER_ADDRESS_STREET PROP_TAG( PT_TSTRING, 0x3A63) ++#define PR_OTHER_ADDRESS_STREET_W PROP_TAG( PT_UNICODE, 0x3A63) ++#define PR_OTHER_ADDRESS_STREET_A PROP_TAG( PT_STRING8, 0x3A63) ++ ++#define PR_OTHER_ADDRESS_POST_OFFICE_BOX PROP_TAG( PT_TSTRING, 0x3A64) ++#define PR_OTHER_ADDRESS_POST_OFFICE_BOX_W PROP_TAG( PT_UNICODE, 0x3A64) ++#define PR_OTHER_ADDRESS_POST_OFFICE_BOX_A PROP_TAG( PT_STRING8, 0x3A64) ++ ++ ++/* ++ * Profile section properties ++ */ ++ ++#define PR_STORE_PROVIDERS PROP_TAG( PT_BINARY, 0x3D00) ++#define PR_AB_PROVIDERS PROP_TAG( PT_BINARY, 0x3D01) ++#define PR_TRANSPORT_PROVIDERS PROP_TAG( PT_BINARY, 0x3D02) ++ ++#define PR_DEFAULT_PROFILE PROP_TAG( PT_BOOLEAN, 0x3D04) ++#define PR_AB_SEARCH_PATH PROP_TAG( PT_MV_BINARY, 0x3D05) ++#define PR_AB_DEFAULT_DIR PROP_TAG( PT_BINARY, 0x3D06) ++#define PR_AB_DEFAULT_PAB PROP_TAG( PT_BINARY, 0x3D07) ++ ++#define PR_FILTERING_HOOKS PROP_TAG( PT_BINARY, 0x3D08) ++#define PR_SERVICE_NAME PROP_TAG( PT_TSTRING, 0x3D09) ++#define PR_SERVICE_NAME_W PROP_TAG( PT_UNICODE, 0x3D09) ++#define PR_SERVICE_NAME_A PROP_TAG( PT_STRING8, 0x3D09) ++#define PR_SERVICE_DLL_NAME PROP_TAG( PT_TSTRING, 0x3D0A) ++#define PR_SERVICE_DLL_NAME_W PROP_TAG( PT_UNICODE, 0x3D0A) ++#define PR_SERVICE_DLL_NAME_A PROP_TAG( PT_STRING8, 0x3D0A) ++#define PR_SERVICE_ENTRY_NAME PROP_TAG( PT_STRING8, 0x3D0B) ++#define PR_SERVICE_UID PROP_TAG( PT_BINARY, 0x3D0C) ++#define PR_SERVICE_EXTRA_UIDS PROP_TAG( PT_BINARY, 0x3D0D) ++#define PR_SERVICES PROP_TAG( PT_BINARY, 0x3D0E) ++#define PR_SERVICE_SUPPORT_FILES PROP_TAG( PT_MV_TSTRING, 0x3D0F) ++#define PR_SERVICE_SUPPORT_FILES_W PROP_TAG( PT_MV_UNICODE, 0x3D0F) ++#define PR_SERVICE_SUPPORT_FILES_A PROP_TAG( PT_MV_STRING8, 0x3D0F) ++#define PR_SERVICE_DELETE_FILES PROP_TAG( PT_MV_TSTRING, 0x3D10) ++#define PR_SERVICE_DELETE_FILES_W PROP_TAG( PT_MV_UNICODE, 0x3D10) ++#define PR_SERVICE_DELETE_FILES_A PROP_TAG( PT_MV_STRING8, 0x3D10) ++#define PR_AB_SEARCH_PATH_UPDATE PROP_TAG( PT_BINARY, 0x3D11) ++#define PR_PROFILE_NAME PROP_TAG( PT_TSTRING, 0x3D12) ++#define PR_PROFILE_NAME_A PROP_TAG( PT_STRING8, 0x3D12) ++#define PR_PROFILE_NAME_W PROP_TAG( PT_UNICODE, 0x3D12) ++ ++/* ++ * Status object properties ++ */ ++ ++#define PR_IDENTITY_DISPLAY PROP_TAG( PT_TSTRING, 0x3E00) ++#define PR_IDENTITY_DISPLAY_W PROP_TAG( PT_UNICODE, 0x3E00) ++#define PR_IDENTITY_DISPLAY_A PROP_TAG( PT_STRING8, 0x3E00) ++#define PR_IDENTITY_ENTRYID PROP_TAG( PT_BINARY, 0x3E01) ++#define PR_RESOURCE_METHODS PROP_TAG( PT_LONG, 0x3E02) ++#define PR_RESOURCE_TYPE PROP_TAG( PT_LONG, 0x3E03) ++#define PR_STATUS_CODE PROP_TAG( PT_LONG, 0x3E04) ++#define PR_IDENTITY_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3E05) ++#define PR_OWN_STORE_ENTRYID PROP_TAG( PT_BINARY, 0x3E06) ++#define PR_RESOURCE_PATH PROP_TAG( PT_TSTRING, 0x3E07) ++#define PR_RESOURCE_PATH_W PROP_TAG( PT_UNICODE, 0x3E07) ++#define PR_RESOURCE_PATH_A PROP_TAG( PT_STRING8, 0x3E07) ++#define PR_STATUS_STRING PROP_TAG( PT_TSTRING, 0x3E08) ++#define PR_STATUS_STRING_W PROP_TAG( PT_UNICODE, 0x3E08) ++#define PR_STATUS_STRING_A PROP_TAG( PT_STRING8, 0x3E08) ++#define PR_X400_DEFERRED_DELIVERY_CANCEL PROP_TAG( PT_BOOLEAN, 0x3E09) ++#define PR_HEADER_FOLDER_ENTRYID PROP_TAG( PT_BINARY, 0x3E0A) ++#define PR_REMOTE_PROGRESS PROP_TAG( PT_LONG, 0x3E0B) ++#define PR_REMOTE_PROGRESS_TEXT PROP_TAG( PT_TSTRING, 0x3E0C) ++#define PR_REMOTE_PROGRESS_TEXT_W PROP_TAG( PT_UNICODE, 0x3E0C) ++#define PR_REMOTE_PROGRESS_TEXT_A PROP_TAG( PT_STRING8, 0x3E0C) ++#define PR_REMOTE_VALIDATE_OK PROP_TAG( PT_BOOLEAN, 0x3E0D) ++ ++/* ++ * Display table properties ++ */ ++ ++#define PR_CONTROL_FLAGS PROP_TAG( PT_LONG, 0x3F00) ++#define PR_CONTROL_STRUCTURE PROP_TAG( PT_BINARY, 0x3F01) ++#define PR_CONTROL_TYPE PROP_TAG( PT_LONG, 0x3F02) ++#define PR_DELTAX PROP_TAG( PT_LONG, 0x3F03) ++#define PR_DELTAY PROP_TAG( PT_LONG, 0x3F04) ++#define PR_XPOS PROP_TAG( PT_LONG, 0x3F05) ++#define PR_YPOS PROP_TAG( PT_LONG, 0x3F06) ++#define PR_CONTROL_ID PROP_TAG( PT_BINARY, 0x3F07) ++#define PR_INITIAL_DETAILS_PANE PROP_TAG( PT_LONG, 0x3F08) ++ ++/* ++ * Secure property id range ++ */ ++ ++#define PROP_ID_SECURE_MIN 0x67F0 ++#define PROP_ID_SECURE_MAX 0x67FF ++ ++ ++#endif /* MAPITAGS_H */ +diff -urN exim-4.32-orig/src/version.c exim-4.32/src/version.c +--- exim-4.32-orig/src/version.c Thu Apr 15 10:27:01 2004 ++++ exim-4.32/src/version.c Thu Apr 15 13:40:33 2004 +@@ -11,6 +11,7 @@ + + + #define THIS_VERSION "4.32" ++#define EXISCAN_VERSION "17" + + + /* The header file cnumber.h contains a single line containing the +@@ -40,6 +41,7 @@ + version_cnumber_format = US"%d\0<<eximcnumber>>"; + sprintf(CS version_cnumber, CS version_cnumber_format, cnumber); + version_string = US THIS_VERSION "\0<<eximversion>>"; ++exiscan_version_string = US EXISCAN_VERSION; + + Ustrcpy(today, __DATE__); + if (today[4] == ' ') today[4] = '0'; |