summaryrefslogtreecommitdiff
path: root/debian/patches
diff options
context:
space:
mode:
authorMarc Haber <zugschlus@debian.org>2005-02-07 20:44:40 +0000
committerMarc Haber <zugschlus@debian.org>2005-02-07 20:44:40 +0000
commita93ce3f118579f2fc36feec5d74db0dd12f9ca3f (patch)
treea3f80b35812c95cbe443486ae39958cad0700f97 /debian/patches
parentd9da6e37112b0bcbd94593b0e52ca28fc8f7234b (diff)
downloadexim4-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/00list10
-rw-r--r--debian/patches/10_daemon_close_fds.dpatch47
-rw-r--r--debian/patches/30_dontoverridecflags.dpatch34
-rw-r--r--debian/patches/31_eximmanpage.dpatch57
-rwxr-xr-xdebian/patches/32_exim4.dpatch126
-rwxr-xr-xdebian/patches/33_eximon.binary.dpatch33
-rwxr-xr-xdebian/patches/34_eximstatsmanpage.dpatch36
-rwxr-xr-xdebian/patches/35_install.dpatch37
-rwxr-xr-xdebian/patches/36_pcre.dpatch91
-rw-r--r--debian/patches/40_boolean_redefine_protect.dpatch47
-rw-r--r--debian/patches/50_localscan_dlopen.dpatch303
-rw-r--r--debian/patches/exiscan.patch10034
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, &regex_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, &param_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';