[Pkg-clamav-commits] [SCM] Debian repository for ClamAV branch, debian/unstable, updated. debian/0.94.dfsg.2-1-424-g6e7bde7

Michael Tautschnig mt at debian.org
Wed Mar 18 17:23:23 UTC 2009


The following commit has been merged in the debian/unstable branch:
commit dbd8d5b49ed202855e4a1795da31618b3ad4284f
Author: Michael Tautschnig <mt at debian.org>
Date:   Wed Mar 18 18:10:31 2009 +0100

    Merged changes from upstream tarball
    
    - Most notably, contrib/ is shipped again
    
    Signed-off-by: Michael Tautschnig <mt at debian.org>

diff --git a/Makefile.am b/Makefile.am
index ba36f0c..ab66d5d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -19,7 +19,7 @@
 ACLOCAL_AMFLAGS=-I m4
 
 SUBDIRS = libltdl libclamav clamscan clamd clamdscan freshclam sigtool clamconf database docs etc clamav-milter test unit_tests clamdtop
-EXTRA_DIST = FAQ examples BUGS shared libclamav.pc.in UPGRADE COPYING.bzip2 COPYING.lzma COPYING.unrar COPYING.LGPL COPYING.file COPYING.zlib COPYING.getopt COPYING.regex COPYING.sha256
+EXTRA_DIST = FAQ examples BUGS shared libclamav.pc.in UPGRADE COPYING.bzip2 COPYING.lzma COPYING.unrar COPYING.LGPL COPYING.file COPYING.zlib COPYING.getopt COPYING.regex COPYING.sha256 contrib
 
 bin_SCRIPTS=clamav-config
 
diff --git a/Makefile.in b/Makefile.in
index 841ba55..7324bb8 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -282,7 +282,7 @@ top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
 ACLOCAL_AMFLAGS = -I m4
 SUBDIRS = libltdl libclamav clamscan clamd clamdscan freshclam sigtool clamconf database docs etc clamav-milter test unit_tests clamdtop
-EXTRA_DIST = FAQ examples BUGS shared libclamav.pc.in UPGRADE COPYING.bzip2 COPYING.lzma COPYING.unrar COPYING.LGPL COPYING.file COPYING.zlib COPYING.getopt COPYING.regex COPYING.sha256
+EXTRA_DIST = FAQ examples BUGS shared libclamav.pc.in UPGRADE COPYING.bzip2 COPYING.lzma COPYING.unrar COPYING.LGPL COPYING.file COPYING.zlib COPYING.getopt COPYING.regex COPYING.sha256 contrib
 bin_SCRIPTS = clamav-config
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = libclamav.pc
diff --git a/configure b/configure
index facbc78..73ffdcf 100755
--- a/configure
+++ b/configure
@@ -2989,7 +2989,7 @@ cat >>confdefs.h <<\_ACEOF
 _ACEOF
 
 
-VERSION="0.95rc1"
+VERSION="0.95rc2"
 
 cat >>confdefs.h <<_ACEOF
 #define VERSION "$VERSION"
diff --git a/configure.in b/configure.in
index 8aaaf6d..1ed1180 100644
--- a/configure.in
+++ b/configure.in
@@ -39,7 +39,7 @@ dnl the date in the version
 AC_DEFINE([PACKAGE], PACKAGE_NAME, [Name of package])
 
 dnl change this on a release
-VERSION="0.95rc1"
+VERSION="0.95rc2"
 AC_DEFINE_UNQUOTED([VERSION],"$VERSION",[Version number of package])
 
 LC_CURRENT=6
diff --git a/contrib/clamdmon/clamdmon-1.0.tar.gz b/contrib/clamdmon/clamdmon-1.0.tar.gz
new file mode 100644
index 0000000..a80950f
Binary files /dev/null and b/contrib/clamdmon/clamdmon-1.0.tar.gz differ
diff --git a/contrib/clamdwatch/clamdwatch.tar.gz b/contrib/clamdwatch/clamdwatch.tar.gz
new file mode 100644
index 0000000..b327726
Binary files /dev/null and b/contrib/clamdwatch/clamdwatch.tar.gz differ
diff --git a/contrib/cleanup-partial.pl b/contrib/cleanup-partial.pl
new file mode 100755
index 0000000..1346f71
--- /dev/null
+++ b/contrib/cleanup-partial.pl
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+# ---- Settings ----
+# TemporaryDirectory in clamd.conf
+my $TMPDIR='/tmp';
+# How long to wait for next part of RFC1341 message (seconds)
+my $cleanup_interval=3600;
+
+# ---- End of Settings ----
+
+my $partial_dir = "$TMPDIR/clamav-partial";
+#  if there is no partial directory, nothing to clean up
+opendir(DIR, $partial_dir) || exit 0;
+
+my $cleanup_threshold = time - $cleanup_interval;
+while(my $file = readdir(DIR)) {
+	next unless $file =~ m/^clamav-partial-([0-9]+)_[0-9a-f]{32}-[0-9]+$/;
+	my $filetime = $1;
+	unlink "$partial_dir/$file" unless $filetime > $cleanup_threshold;
+}
+closedir DIR;
diff --git a/contrib/entitynorm/AUTHORS b/contrib/entitynorm/AUTHORS
new file mode 100644
index 0000000..08811f2
--- /dev/null
+++ b/contrib/entitynorm/AUTHORS
@@ -0,0 +1 @@
+edwin at clamav.net
\ No newline at end of file
diff --git a/contrib/entitynorm/COPYING b/contrib/entitynorm/COPYING
new file mode 100644
index 0000000..623b625
--- /dev/null
+++ b/contrib/entitynorm/COPYING
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/contrib/entitynorm/ChangeLog b/contrib/entitynorm/ChangeLog
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/entitynorm/Makefile b/contrib/entitynorm/Makefile
new file mode 100644
index 0000000..f4e619f
--- /dev/null
+++ b/contrib/entitynorm/Makefile
@@ -0,0 +1,30 @@
+PERL=perl
+CC=cc
+
+all: entitylist.h encoding_aliases.h gentbl encname_chars.h generate_hash
+
+entities_parsed: entities entities/* entity_decl_parse.pl
+	$(PERL) entity_decl_parse.pl $</* | sort -u >$@
+
+generate_entitylist: generate_entitylist.c ../../libclamav/hashtab.h ../../libclamav/hashtab.c ../../libclamav/others.c
+	$(CC) -I. -DHAVE_CONFIG_H -DCLI_MEMFUNSONLY -DPROFILE_HASHTABLE $< ../../libclamav/hashtab.c ../../libclamav/others.c -o $@
+
+generate_hash: generate_hash.c ../../libclamav/hashtab.h ../../libclamav/hashtab.c ../../libclamav/others.c
+	$(CC) -I. -DHAVE_CONFIG_H -DCLI_MEMFUNSONLY -DPROFILE_HASHTABLE $< ../../libclamav/hashtab.c ../../libclamav/others.c -o $@
+
+generate_encoding_aliases: generate_encoding_aliases.c ../../libclamav/hashtab.c ../../libclamav/others.c ../../libclamav/htmlnorm.h ../../libclamav/entconv.h ../../libclamav/cltypes.h ../../libclamav/hashtab.h ../../libclamav/hashtab.h
+	$(CC) -I. -DHAVE_CONFIG_H -DCLI_MEMFUNSONLY -DPROFILE_HASHTABLE $< ../../libclamav/hashtab.c ../../libclamav/others.c -o $@
+
+entitylist.h: generate_entitylist entities_parsed
+	./$< <entities_parsed >$@
+
+encoding_aliases.h: generate_encoding_aliases
+	./$< >$@
+
+gentbl: gentbl.c
+	$(CC) $< -o $@
+encname_chars.h: gentbl
+	./gentbl encname_chars 0-9 a-z A-Z _ . / \( \) - : >$@
+
+clean: 
+	rm -f entitylist.h encoding_aliases.h entities_parsed generate_entitylist generate_encoding_aliases gentbl encname_chars.h
diff --git a/contrib/entitynorm/NEWS b/contrib/entitynorm/NEWS
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/entitynorm/README b/contrib/entitynorm/README
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/entitynorm/clamav-config.h b/contrib/entitynorm/clamav-config.h
new file mode 100644
index 0000000..54f9429
--- /dev/null
+++ b/contrib/entitynorm/clamav-config.h
@@ -0,0 +1,398 @@
+/* clamav-config.h.  Generated from clamav-config.h.in by configure.  */
+/* clamav-config.h.in.  Generated from configure.in by autoheader.  */
+
+/* enable bind8 compatibility */
+/* #undef BIND_8_COMPAT */
+
+/* Define if your snprintf is busted */
+/* #undef BROKEN_SNPRINTF */
+
+/* "build clamd" */
+/* #undef BUILD_CLAMD */
+
+/* name of the clamav group */
+#define CLAMAVGROUP "clamav"
+
+/* name of the clamav user */
+#define CLAMAVUSER "clamav"
+
+/* enable clamuko */
+/* #undef CLAMUKO */
+
+/* enable debugging */
+/* #undef CL_DEBUG */
+
+/* enable experimental code */
+/* #undef CL_EXPERIMENTAL */
+
+/* thread safe */
+/* #undef CL_THREAD_SAFE */
+
+/* where to look for the config file */
+#define CONFDIR "/usr/local/etc"
+
+/* os is aix */
+/* #undef C_AIX */
+
+/* os is beos */
+/* #undef C_BEOS */
+
+/* Increase thread stack size. */
+/* #undef C_BIGSTACK */
+
+/* os is bsd flavor */
+/* #undef C_BSD */
+
+/* os is cygwin */
+/* #undef C_CYGWIN */
+
+/* os is darwin */
+/* #undef C_DARWIN */
+
+/* target is gnu-hurd */
+/* #undef C_GNU_HURD */
+
+/* os is hpux */
+/* #undef C_HPUX */
+
+/* os is interix */
+/* #undef C_INTERIX */
+
+/* os is irix */
+/* #undef C_IRIX */
+
+/* target is kfreebsd-gnu */
+/* #undef C_KFREEBSD_GNU */
+
+/* target is linux */
+/* #define C_LINUX 1 */
+
+/* os is OS/2 */
+/* #undef C_OS2 */
+
+/* os is osf/tru64 */
+/* #undef C_OSF */
+
+/* os is QNX 6.x.x */
+/* #undef C_QNX6 */
+
+/* os is solaris */
+/* #undef C_SOLARIS */
+
+/* Path to virus database directory. */
+#define DATADIR "/usr/local/share/clamav"
+
+/* "default FD_SETSIZE value" */
+#define DEFAULT_FD_SETSIZE 1024
+
+/* "build unrar code" */
+/* #undef ENABLE_UNRAR */
+
+/* file i/o buffer size */
+#define FILEBUFF 8192
+
+/* FPU byte ordering is little endian */
+#define FPU_WORDS_BIGENDIAN 0
+
+/* enable workaround for broken DNS servers */
+/* #undef FRESHCLAM_DNS_FIX */
+
+/* use "Cache-Control: no-cache" in freshclam */
+/* #undef FRESHCLAM_NO_CACHE */
+
+/* access rights in msghdr */
+/* #undef HAVE_ACCRIGHTS_IN_MSGHDR */
+
+/* attrib aligned */
+#define HAVE_ATTRIB_ALIGNED 1
+
+/* attrib packed */
+#define HAVE_ATTRIB_PACKED 1
+
+/* have bzip2 */
+/* #define HAVE_BZLIB_H 1 */
+
+/* ancillary data style fd pass */
+/* #define HAVE_CONTROL_IN_MSGHDR 1 */
+
+/* Define to 1 if you have the `ctime_r' function. */
+/* #define HAVE_CTIME_R 1 */
+
+/* ctime_r takes 2 arguments */
+/* #define HAVE_CTIME_R_2 1 */
+
+/* ctime_r takes 3 arguments */
+/* #undef HAVE_CTIME_R_3 */
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+/* #define HAVE_DLFCN_H 1 */
+
+/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */
+#define HAVE_FSEEKO 1
+
+/* gethostbyname_r takes 3 arguments */
+/* #undef HAVE_GETHOSTBYNAME_R_3 */
+
+/* gethostbyname_r takes 5 arguments */
+/* #undef HAVE_GETHOSTBYNAME_R_5 */
+
+/* gethostbyname_r takes 6 arguments */
+/* #undef HAVE_GETHOSTBYNAME_R_6 */
+
+/* Define to 1 if you have the `getpagesize' function. */
+/* #define HAVE_GETPAGESIZE 1 */
+
+/* have gmp installed */
+/* #undef HAVE_GMP */
+
+/* Define to 1 if you have the <grp.h> header file. */
+ #define HAVE_GRP_H 1 
+
+/* Define to 1 if you have the <iconv.h> header file. */
+/* #define HAVE_ICONV_H 1 */
+
+/* Define to 1 if you have the `inet_ntop' function. */
+/* #define HAVE_INET_NTOP 1 */
+
+/* Define to 1 if you have the `initgroups' function. */
+/* #define HAVE_INITGROUPS 1 */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+/* #define HAVE_INTTYPES_H 1 */
+
+/* in_addr_t is defined */
+/* #define HAVE_IN_ADDR_T 1 */
+
+/* in_port_t is defined */
+/* #define HAVE_IN_PORT_T 1 */
+
+/* Define to 1 if you have the <libmilter/mfapi.h> header file. */
+/* #undef HAVE_LIBMILTER_MFAPI_H */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <malloc.h> header file. */
+/* #define HAVE_MALLOC_H 1 */
+
+/* Define to 1 if you have the `memcpy' function. */
+#define HAVE_MEMCPY 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+/* #define HAVE_MEMORY_H 1 */
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have a working `mmap' system call. */
+#define HAVE_MMAP 1
+
+/* Define to 1 if you have the <ndir.h> header file. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the `poll' function. */
+/* #define HAVE_POLL 1 */
+
+/* Define to 1 if you have the <poll.h> header file. */
+/* #define HAVE_POLL_H 1 */
+
+/* "pragma pack" */
+/* #undef HAVE_PRAGMA_PACK */
+
+/* "pragma pack hppa/hp-ux style" */
+/* #undef HAVE_PRAGMA_PACK_HPPA */
+
+/* Define to 1 if you have the <pwd.h> header file. */
+#define HAVE_PWD_H 1 
+
+/* readdir_r takes 2 arguments */
+/* #undef HAVE_READDIR_R_2 */
+
+/* readdir_r takes 3 arguments */
+/* #undef HAVE_READDIR_R_3 */
+
+/* Define to 1 if you have the `recvmsg' function. */
+/* #define HAVE_RECVMSG 1 */
+
+/* have resolv.h */
+/* #undef HAVE_RESOLV_H */
+
+/* Define signed right shift implementation */
+#define HAVE_SAR 1
+
+/* Define to 1 if you have the `sendmsg' function. */
+/* #define HAVE_SENDMSG 1 */
+
+/* Define to 1 if you have the `setgroups' function. */
+/* #define HAVE_SETGROUPS 1 */
+
+/* Define to 1 if you have the `setsid' function. */
+/* #define HAVE_SETSID 1 */
+
+/* Define to 1 if you have the `snprintf' function. */
+#define HAVE_SNPRINTF 1
+
+/* Define to 1 if you have the <stdbool.h> header file. */
+#define HAVE_STDBOOL_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strerror_r' function. */
+/* #define HAVE_STRERROR_R 1 */
+
+/* Define to 1 if you have the <strings.h> header file. */
+/* #define HAVE_STRINGS_H 1 */
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strlcat' function. */
+/* #undef HAVE_STRLCAT */
+
+/* Define to 1 if you have the `strlcpy' function. */
+/* #undef HAVE_STRLCPY */
+
+/* Define to 1 if you have the <sys/filio.h> header file. */
+/* #undef HAVE_SYS_FILIO_H */
+
+/* Define to 1 if you have the <sys/inttypes.h> header file. */
+/* #undef HAVE_SYS_INTTYPES_H */
+
+/* Define to 1 if you have the <sys/int_types.h> header file. */
+/* #undef HAVE_SYS_INT_TYPES_H */
+
+/* Define to 1 if you have the <sys/mman.h> header file. */
+#define HAVE_SYS_MMAN_H 1
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* "have <sys/select.h>" */
+/* #undef HAVE_SYS_SELECT_H */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/uio.h> header file. */
+/* #define HAVE_SYS_UIO_H 1 */
+
+/* Define to 1 if you have the <tcpd.h> header file. */
+/* #undef HAVE_TCPD_H */
+
+/* Define to 1 if you have the <termios.h> header file. */
+/* #define HAVE_TERMIOS_H 1 */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the `vsnprintf' function. */
+#define HAVE_VSNPRINTF 1
+
+/* zlib installed */
+#define HAVE_ZLIB_H 1
+
+/* Early Linux doesn't set cmsg fields */
+/* #undef INCOMPLETE_CMSG */
+
+/* bzip funtions do not have bz2 prefix */
+/* #undef NOBZ2PREFIX */
+
+/* "no fd_set" */
+/* #undef NO_FD_SET */
+
+/* Name of package */
+#define PACKAGE "clamav"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* scan buffer size */
+#define SCANBUFF 131072
+
+/* location of Sendmail binary */
+/* #undef SENDMAIL_BIN */
+
+/* major version of Sendmail */
+/* #undef SENDMAIL_VERSION_A */
+
+/* minor version of Sendmail */
+/* #undef SENDMAIL_VERSION_B */
+
+/* subversion of Sendmail */
+/* #undef SENDMAIL_VERSION_C */
+
+/* Define to 1 if the `setpgrp' function takes no argument. */
+#define SETPGRP_VOID 1
+
+#if 0
+/* lets assume system has proper stdint that defines uintX_t. */
+/* The number of bytes in type int */
+/* #define SIZEOF_INT 4 */
+
+/* The number of bytes in type long */
+#define SIZEOF_LONG 8
+
+/* The number of bytes in type long long */
+#define SIZEOF_LONG_LONG 8
+
+/* The number of bytes in type short */
+#define SIZEOF_SHORT 2
+#endif
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* use syslog */
+/* #define USE_SYSLOG 1 */
+
+/* Version number of package */
+#define VERSION "devel-20071218"
+
+/* tcpwrappers support */
+/* #undef WITH_TCPWRAP */
+
+/* endianess */
+/* #define WORDS_BIGENDIAN 0 */
+
+/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
+/* #undef _LARGEFILE_SOURCE */
+
+/* POSIX compatibility */
+/* #undef _POSIX_PII_SOCKET */
+
+/* thread safe */
+/* #undef _REENTRANT */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+   calls it, or to nothing if 'inline' is not supported under any name.  */
+#ifndef __cplusplus
+/* #undef inline */
+#endif
+
+/* Define to `long int' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to "int" if <sys/socket.h> does not define. */
+/* #undef socklen_t */
diff --git a/contrib/entitynorm/entities/isoamsa.ent b/contrib/entitynorm/entities/isoamsa.ent
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/entitynorm/entities/isoamsb.ent b/contrib/entitynorm/entities/isoamsb.ent
new file mode 100644
index 0000000..39ce606
--- /dev/null
+++ b/contrib/entitynorm/entities/isoamsb.ent
@@ -0,0 +1,83 @@
+
+<!--
+     File isoamsb.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isoamsb.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Added Math Symbols: Binary Operators//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isoamsb.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isoamsb PUBLIC
+         "ISO 8879:1986//ENTITIES Added Math Symbols: Binary Operators//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isoamsb.ent"
+       >
+       %isoamsb;
+
+-->
+
+<!ENTITY amalg            "&#x02A3F;" ><!--AMALGAMATION OR COPRODUCT -->
+<!ENTITY Barwed           "&#x02306;" ><!--PERSPECTIVE -->
+<!ENTITY barwed           "&#x02305;" ><!--PROJECTIVE -->
+<!ENTITY Cap              "&#x022D2;" ><!--DOUBLE INTERSECTION -->
+<!ENTITY coprod           "&#x02210;" ><!--N-ARY COPRODUCT -->
+<!ENTITY Cup              "&#x022D3;" ><!--DOUBLE UNION -->
+<!ENTITY cuvee            "&#x022CE;" ><!--CURLY LOGICAL OR -->
+<!ENTITY cuwed            "&#x022CF;" ><!--CURLY LOGICAL AND -->
+<!ENTITY diam             "&#x022C4;" ><!--DIAMOND OPERATOR -->
+<!ENTITY divonx           "&#x022C7;" ><!--DIVISION TIMES -->
+<!ENTITY intcal           "&#x022BA;" ><!--INTERCALATE -->
+<!ENTITY lthree           "&#x022CB;" ><!--LEFT SEMIDIRECT PRODUCT -->
+<!ENTITY ltimes           "&#x022C9;" ><!--LEFT NORMAL FACTOR SEMIDIRECT PRODUCT -->
+<!ENTITY minusb           "&#x0229F;" ><!--SQUARED MINUS -->
+<!ENTITY oast             "&#x0229B;" ><!--CIRCLED ASTERISK OPERATOR -->
+<!ENTITY ocir             "&#x0229A;" ><!--CIRCLED RING OPERATOR -->
+<!ENTITY odash            "&#x0229D;" ><!--CIRCLED DASH -->
+<!ENTITY odot             "&#x02299;" ><!--CIRCLED DOT OPERATOR -->
+<!ENTITY ominus           "&#x02296;" ><!--CIRCLED MINUS -->
+<!ENTITY oplus            "&#x02295;" ><!--CIRCLED PLUS -->
+<!ENTITY osol             "&#x02298;" ><!--CIRCLED DIVISION SLASH -->
+<!ENTITY otimes           "&#x02297;" ><!--CIRCLED TIMES -->
+<!ENTITY plusb            "&#x0229E;" ><!--SQUARED PLUS -->
+<!ENTITY plusdo           "&#x02214;" ><!--DOT PLUS -->
+<!ENTITY prod             "&#x0220F;" ><!--N-ARY PRODUCT -->
+<!ENTITY rthree           "&#x022CC;" ><!--RIGHT SEMIDIRECT PRODUCT -->
+<!ENTITY rtimes           "&#x022CA;" ><!--RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT -->
+<!ENTITY sdot             "&#x022C5;" ><!--DOT OPERATOR -->
+<!ENTITY sdotb            "&#x022A1;" ><!--SQUARED DOT OPERATOR -->
+<!ENTITY setmn            "&#x02216;" ><!--SET MINUS -->
+<!ENTITY sqcap            "&#x02293;" ><!--SQUARE CAP -->
+<!ENTITY sqcup            "&#x02294;" ><!--SQUARE CUP -->
+<!ENTITY ssetmn           "&#x02216;" ><!--SET MINUS -->
+<!ENTITY sstarf           "&#x022C6;" ><!--STAR OPERATOR -->
+<!ENTITY sum              "&#x02211;" ><!--N-ARY SUMMATION -->
+<!ENTITY timesb           "&#x022A0;" ><!--SQUARED TIMES -->
+<!ENTITY top              "&#x022A4;" ><!--DOWN TACK -->
+<!ENTITY uplus            "&#x0228E;" ><!--MULTISET UNION -->
+<!ENTITY wreath           "&#x02240;" ><!--WREATH PRODUCT -->
+<!ENTITY xcirc            "&#x025EF;" ><!--LARGE CIRCLE -->
+<!ENTITY xdtri            "&#x025BD;" ><!--WHITE DOWN-POINTING TRIANGLE -->
+<!ENTITY xutri            "&#x025B3;" ><!--WHITE UP-POINTING TRIANGLE -->
diff --git a/contrib/entitynorm/entities/isoamsc.ent b/contrib/entitynorm/entities/isoamsc.ent
new file mode 100644
index 0000000..f74c051
--- /dev/null
+++ b/contrib/entitynorm/entities/isoamsc.ent
@@ -0,0 +1,51 @@
+
+<!--
+     File isoamsc.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isoamsc.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Added Math Symbols: Delimiters//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isoamsc.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isoamsc PUBLIC
+         "ISO 8879:1986//ENTITIES Added Math Symbols: Delimiters//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isoamsc.ent"
+       >
+       %isoamsc;
+
+-->
+
+<!ENTITY dlcorn           "&#x0231E;" ><!--BOTTOM LEFT CORNER -->
+<!ENTITY drcorn           "&#x0231F;" ><!--BOTTOM RIGHT CORNER -->
+<!ENTITY lceil            "&#x02308;" ><!--LEFT CEILING -->
+<!ENTITY lfloor           "&#x0230A;" ><!--LEFT FLOOR -->
+<!ENTITY lpargt           "&#x029A0;" ><!--SPHERICAL ANGLE OPENING LEFT -->
+<!ENTITY rceil            "&#x02309;" ><!--RIGHT CEILING -->
+<!ENTITY rfloor           "&#x0230B;" ><!--RIGHT FLOOR -->
+<!ENTITY rpargt           "&#x02994;" ><!--RIGHT ARC GREATER-THAN BRACKET -->
+<!ENTITY ulcorn           "&#x0231C;" ><!--TOP LEFT CORNER -->
+<!ENTITY urcorn           "&#x0231D;" ><!--TOP RIGHT CORNER -->
diff --git a/contrib/entitynorm/entities/isoamso.ent b/contrib/entitynorm/entities/isoamso.ent
new file mode 100644
index 0000000..8869859
--- /dev/null
+++ b/contrib/entitynorm/entities/isoamso.ent
@@ -0,0 +1,59 @@
+
+<!--
+     File isoamso.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isoamso.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Added Math Symbols: Ordinary//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isoamso.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isoamso PUBLIC
+         "ISO 8879:1986//ENTITIES Added Math Symbols: Ordinary//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isoamso.ent"
+       >
+       %isoamso;
+
+-->
+
+<!ENTITY ang              "&#x02220;" ><!--ANGLE -->
+<!ENTITY angmsd           "&#x02221;" ><!--MEASURED ANGLE -->
+<!ENTITY beth             "&#x02136;" ><!--BET SYMBOL -->
+<!ENTITY bprime           "&#x02035;" ><!--REVERSED PRIME -->
+<!ENTITY comp             "&#x02201;" ><!--COMPLEMENT -->
+<!ENTITY daleth           "&#x02138;" ><!--DALET SYMBOL -->
+<!ENTITY ell              "&#x02113;" ><!--SCRIPT SMALL L -->
+<!ENTITY empty            "&#x02205;" ><!--EMPTY SET -->
+<!ENTITY gimel            "&#x02137;" ><!--GIMEL SYMBOL -->
+<!ENTITY inodot           "&#x00131;" ><!--LATIN SMALL LETTER DOTLESS I -->
+<!ENTITY jnodot           "&#x0006A;" ><!--LATIN SMALL LETTER J -->
+<!ENTITY nexist           "&#x02204;" ><!--THERE DOES NOT EXIST -->
+<!ENTITY oS               "&#x024C8;" ><!--CIRCLED LATIN CAPITAL LETTER S -->
+<!ENTITY planck           "&#x0210F;" ><!--PLANCK CONSTANT OVER TWO PI -->
+<!ENTITY real             "&#x0211C;" ><!--BLACK-LETTER CAPITAL R -->
+<!ENTITY sbsol            "&#x0FE68;" ><!--SMALL REVERSE SOLIDUS -->
+<!ENTITY vprime           "&#x02032;" ><!--PRIME -->
+<!ENTITY weierp           "&#x02118;" ><!--SCRIPT CAPITAL P -->
diff --git a/contrib/entitynorm/entities/isoamsr.ent b/contrib/entitynorm/entities/isoamsr.ent
new file mode 100644
index 0000000..e087b0f
--- /dev/null
+++ b/contrib/entitynorm/entities/isoamsr.ent
@@ -0,0 +1,125 @@
+
+<!--
+     File isoamsr.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isoamsr.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Added Math Symbols: Relations//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isoamsr.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isoamsr PUBLIC
+         "ISO 8879:1986//ENTITIES Added Math Symbols: Relations//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isoamsr.ent"
+       >
+       %isoamsr;
+
+-->
+
+<!ENTITY ape              "&#x0224A;" ><!--ALMOST EQUAL OR EQUAL TO -->
+<!ENTITY asymp            "&#x02248;" ><!--ALMOST EQUAL TO -->
+<!ENTITY bcong            "&#x0224C;" ><!--ALL EQUAL TO -->
+<!ENTITY bepsi            "&#x003F6;" ><!--GREEK REVERSED LUNATE EPSILON SYMBOL -->
+<!ENTITY bowtie           "&#x022C8;" ><!--BOWTIE -->
+<!ENTITY bsim             "&#x0223D;" ><!--REVERSED TILDE -->
+<!ENTITY bsime            "&#x022CD;" ><!--REVERSED TILDE EQUALS -->
+<!ENTITY bump             "&#x0224E;" ><!--GEOMETRICALLY EQUIVALENT TO -->
+<!ENTITY bumpe            "&#x0224F;" ><!--DIFFERENCE BETWEEN -->
+<!ENTITY cire             "&#x02257;" ><!--RING EQUAL TO -->
+<!ENTITY colone           "&#x02254;" ><!--COLON EQUALS -->
+<!ENTITY cuepr            "&#x022DE;" ><!--EQUAL TO OR PRECEDES -->
+<!ENTITY cuesc            "&#x022DF;" ><!--EQUAL TO OR SUCCEEDS -->
+<!ENTITY cupre            "&#x0227C;" ><!--PRECEDES OR EQUAL TO -->
+<!ENTITY dashv            "&#x022A3;" ><!--LEFT TACK -->
+<!ENTITY ecir             "&#x02256;" ><!--RING IN EQUAL TO -->
+<!ENTITY ecolon           "&#x02255;" ><!--EQUALS COLON -->
+<!ENTITY eDot             "&#x02251;" ><!--GEOMETRICALLY EQUAL TO -->
+<!ENTITY efDot            "&#x02252;" ><!--APPROXIMATELY EQUAL TO OR THE IMAGE OF -->
+<!ENTITY egs              "&#x02A96;" ><!--SLANTED EQUAL TO OR GREATER-THAN -->
+<!ENTITY els              "&#x02A95;" ><!--SLANTED EQUAL TO OR LESS-THAN -->
+<!ENTITY erDot            "&#x02253;" ><!--IMAGE OF OR APPROXIMATELY EQUAL TO -->
+<!ENTITY esdot            "&#x02250;" ><!--APPROACHES THE LIMIT -->
+<!ENTITY fork             "&#x022D4;" ><!--PITCHFORK -->
+<!ENTITY frown            "&#x02322;" ><!--FROWN -->
+<!ENTITY gap              "&#x02A86;" ><!--GREATER-THAN OR APPROXIMATE -->
+<!ENTITY gE               "&#x02267;" ><!--GREATER-THAN OVER EQUAL TO -->
+<!ENTITY gEl              "&#x02A8C;" ><!--GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN -->
+<!ENTITY gel              "&#x022DB;" ><!--GREATER-THAN EQUAL TO OR LESS-THAN -->
+<!ENTITY ges              "&#x02A7E;" ><!--GREATER-THAN OR SLANTED EQUAL TO -->
+<!ENTITY Gg               "&#x022D9;" ><!--VERY MUCH GREATER-THAN -->
+<!ENTITY gl               "&#x02277;" ><!--GREATER-THAN OR LESS-THAN -->
+<!ENTITY gsdot            "&#x022D7;" ><!--GREATER-THAN WITH DOT -->
+<!ENTITY gsim             "&#x02273;" ><!--GREATER-THAN OR EQUIVALENT TO -->
+<!ENTITY Gt               "&#x0226B;" ><!--MUCH GREATER-THAN -->
+<!ENTITY lap              "&#x02A85;" ><!--LESS-THAN OR APPROXIMATE -->
+<!ENTITY ldot             "&#x022D6;" ><!--LESS-THAN WITH DOT -->
+<!ENTITY lE               "&#x02266;" ><!--LESS-THAN OVER EQUAL TO -->
+<!ENTITY lEg              "&#x02A8B;" ><!--LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN -->
+<!ENTITY leg              "&#x022DA;" ><!--LESS-THAN EQUAL TO OR GREATER-THAN -->
+<!ENTITY les              "&#x02A7D;" ><!--LESS-THAN OR SLANTED EQUAL TO -->
+<!ENTITY lg               "&#x02276;" ><!--LESS-THAN OR GREATER-THAN -->
+<!ENTITY Ll               "&#x022D8;" ><!--VERY MUCH LESS-THAN -->
+<!ENTITY lsim             "&#x02272;" ><!--LESS-THAN OR EQUIVALENT TO -->
+<!ENTITY Lt               "&#x0226A;" ><!--MUCH LESS-THAN -->
+<!ENTITY ltrie            "&#x022B4;" ><!--NORMAL SUBGROUP OF OR EQUAL TO -->
+<!ENTITY mid              "&#x02223;" ><!--DIVIDES -->
+<!ENTITY models           "&#x022A7;" ><!--MODELS -->
+<!ENTITY pr               "&#x0227A;" ><!--PRECEDES -->
+<!ENTITY prap             "&#x02AB7;" ><!--PRECEDES ABOVE ALMOST EQUAL TO -->
+<!ENTITY pre              "&#x02AAF;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN -->
+<!ENTITY prsim            "&#x0227E;" ><!--PRECEDES OR EQUIVALENT TO -->
+<!ENTITY rtrie            "&#x022B5;" ><!--CONTAINS AS NORMAL SUBGROUP OR EQUAL TO -->
+<!ENTITY samalg           "&#x02210;" ><!--N-ARY COPRODUCT -->
+<!ENTITY sc               "&#x0227B;" ><!--SUCCEEDS -->
+<!ENTITY scap             "&#x02AB8;" ><!--SUCCEEDS ABOVE ALMOST EQUAL TO -->
+<!ENTITY sccue            "&#x0227D;" ><!--SUCCEEDS OR EQUAL TO -->
+<!ENTITY sce              "&#x02AB0;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN -->
+<!ENTITY scsim            "&#x0227F;" ><!--SUCCEEDS OR EQUIVALENT TO -->
+<!ENTITY sfrown           "&#x02322;" ><!--FROWN -->
+<!ENTITY smid             "&#x02223;" ><!--DIVIDES -->
+<!ENTITY smile            "&#x02323;" ><!--SMILE -->
+<!ENTITY spar             "&#x02225;" ><!--PARALLEL TO -->
+<!ENTITY sqsub            "&#x0228F;" ><!--SQUARE IMAGE OF -->
+<!ENTITY sqsube           "&#x02291;" ><!--SQUARE IMAGE OF OR EQUAL TO -->
+<!ENTITY sqsup            "&#x02290;" ><!--SQUARE ORIGINAL OF -->
+<!ENTITY sqsupe           "&#x02292;" ><!--SQUARE ORIGINAL OF OR EQUAL TO -->
+<!ENTITY ssmile           "&#x02323;" ><!--SMILE -->
+<!ENTITY Sub              "&#x022D0;" ><!--DOUBLE SUBSET -->
+<!ENTITY subE             "&#x02AC5;" ><!--SUBSET OF ABOVE EQUALS SIGN -->
+<!ENTITY Sup              "&#x022D1;" ><!--DOUBLE SUPERSET -->
+<!ENTITY supE             "&#x02AC6;" ><!--SUPERSET OF ABOVE EQUALS SIGN -->
+<!ENTITY thkap            "&#x02248;" ><!--ALMOST EQUAL TO -->
+<!ENTITY thksim           "&#x0223C;" ><!--TILDE OPERATOR -->
+<!ENTITY trie             "&#x0225C;" ><!--DELTA EQUAL TO -->
+<!ENTITY twixt            "&#x0226C;" ><!--BETWEEN -->
+<!ENTITY Vdash            "&#x022A9;" ><!--FORCES -->
+<!ENTITY vDash            "&#x022A8;" ><!--TRUE -->
+<!ENTITY vdash            "&#x022A2;" ><!--RIGHT TACK -->
+<!ENTITY veebar           "&#x022BB;" ><!--XOR -->
+<!ENTITY vltri            "&#x022B2;" ><!--NORMAL SUBGROUP OF -->
+<!ENTITY vprop            "&#x0221D;" ><!--PROPORTIONAL TO -->
+<!ENTITY vrtri            "&#x022B3;" ><!--CONTAINS AS NORMAL SUBGROUP -->
+<!ENTITY Vvdash           "&#x022AA;" ><!--TRIPLE VERTICAL BAR RIGHT TURNSTILE -->
diff --git a/contrib/entitynorm/entities/isobox.ent b/contrib/entitynorm/entities/isobox.ent
new file mode 100644
index 0000000..7731223
--- /dev/null
+++ b/contrib/entitynorm/entities/isobox.ent
@@ -0,0 +1,81 @@
+
+<!--
+     File isobox.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isobox.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Box and Line Drawing//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isobox.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isobox PUBLIC
+         "ISO 8879:1986//ENTITIES Box and Line Drawing//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isobox.ent"
+       >
+       %isobox;
+
+-->
+
+<!ENTITY boxDL            "&#x02557;" ><!--BOX DRAWINGS DOUBLE DOWN AND LEFT -->
+<!ENTITY boxDl            "&#x02556;" ><!--BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE -->
+<!ENTITY boxdL            "&#x02555;" ><!--BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE -->
+<!ENTITY boxdl            "&#x02510;" ><!--BOX DRAWINGS LIGHT DOWN AND LEFT -->
+<!ENTITY boxDR            "&#x02554;" ><!--BOX DRAWINGS DOUBLE DOWN AND RIGHT -->
+<!ENTITY boxDr            "&#x02553;" ><!--BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE -->
+<!ENTITY boxdR            "&#x02552;" ><!--BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE -->
+<!ENTITY boxdr            "&#x0250C;" ><!--BOX DRAWINGS LIGHT DOWN AND RIGHT -->
+<!ENTITY boxH             "&#x02550;" ><!--BOX DRAWINGS DOUBLE HORIZONTAL -->
+<!ENTITY boxh             "&#x02500;" ><!--BOX DRAWINGS LIGHT HORIZONTAL -->
+<!ENTITY boxHD            "&#x02566;" ><!--BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL -->
+<!ENTITY boxHd            "&#x02564;" ><!--BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE -->
+<!ENTITY boxhD            "&#x02565;" ><!--BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE -->
+<!ENTITY boxhd            "&#x0252C;" ><!--BOX DRAWINGS LIGHT DOWN AND HORIZONTAL -->
+<!ENTITY boxHU            "&#x02569;" ><!--BOX DRAWINGS DOUBLE UP AND HORIZONTAL -->
+<!ENTITY boxHu            "&#x02567;" ><!--BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE -->
+<!ENTITY boxhU            "&#x02568;" ><!--BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE -->
+<!ENTITY boxhu            "&#x02534;" ><!--BOX DRAWINGS LIGHT UP AND HORIZONTAL -->
+<!ENTITY boxUL            "&#x0255D;" ><!--BOX DRAWINGS DOUBLE UP AND LEFT -->
+<!ENTITY boxUl            "&#x0255C;" ><!--BOX DRAWINGS UP DOUBLE AND LEFT SINGLE -->
+<!ENTITY boxuL            "&#x0255B;" ><!--BOX DRAWINGS UP SINGLE AND LEFT DOUBLE -->
+<!ENTITY boxul            "&#x02518;" ><!--BOX DRAWINGS LIGHT UP AND LEFT -->
+<!ENTITY boxUR            "&#x0255A;" ><!--BOX DRAWINGS DOUBLE UP AND RIGHT -->
+<!ENTITY boxUr            "&#x02559;" ><!--BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE -->
+<!ENTITY boxuR            "&#x02558;" ><!--BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE -->
+<!ENTITY boxur            "&#x02514;" ><!--BOX DRAWINGS LIGHT UP AND RIGHT -->
+<!ENTITY boxV             "&#x02551;" ><!--BOX DRAWINGS DOUBLE VERTICAL -->
+<!ENTITY boxv             "&#x02502;" ><!--BOX DRAWINGS LIGHT VERTICAL -->
+<!ENTITY boxVH            "&#x0256C;" ><!--BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL -->
+<!ENTITY boxVh            "&#x0256B;" ><!--BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE -->
+<!ENTITY boxvH            "&#x0256A;" ><!--BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE -->
+<!ENTITY boxvh            "&#x0253C;" ><!--BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL -->
+<!ENTITY boxVL            "&#x02563;" ><!--BOX DRAWINGS DOUBLE VERTICAL AND LEFT -->
+<!ENTITY boxVl            "&#x02562;" ><!--BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE -->
+<!ENTITY boxvL            "&#x02561;" ><!--BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE -->
+<!ENTITY boxvl            "&#x02524;" ><!--BOX DRAWINGS LIGHT VERTICAL AND LEFT -->
+<!ENTITY boxVR            "&#x02560;" ><!--BOX DRAWINGS DOUBLE VERTICAL AND RIGHT -->
+<!ENTITY boxVr            "&#x0255F;" ><!--BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE -->
+<!ENTITY boxvR            "&#x0255E;" ><!--BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE -->
+<!ENTITY boxvr            "&#x0251C;" ><!--BOX DRAWINGS LIGHT VERTICAL AND RIGHT -->
diff --git a/contrib/entitynorm/entities/isocyr1.ent b/contrib/entitynorm/entities/isocyr1.ent
new file mode 100644
index 0000000..2e5c784
--- /dev/null
+++ b/contrib/entitynorm/entities/isocyr1.ent
@@ -0,0 +1,108 @@
+
+<!--
+     File isocyr1.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isocyr1.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Russian Cyrillic//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isocyr1.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isocyr1 PUBLIC
+         "ISO 8879:1986//ENTITIES Russian Cyrillic//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isocyr1.ent"
+       >
+       %isocyr1;
+
+-->
+
+<!ENTITY Acy              "&#x00410;" ><!--CYRILLIC CAPITAL LETTER A -->
+<!ENTITY acy              "&#x00430;" ><!--CYRILLIC SMALL LETTER A -->
+<!ENTITY Bcy              "&#x00411;" ><!--CYRILLIC CAPITAL LETTER BE -->
+<!ENTITY bcy              "&#x00431;" ><!--CYRILLIC SMALL LETTER BE -->
+<!ENTITY CHcy             "&#x00427;" ><!--CYRILLIC CAPITAL LETTER CHE -->
+<!ENTITY chcy             "&#x00447;" ><!--CYRILLIC SMALL LETTER CHE -->
+<!ENTITY Dcy              "&#x00414;" ><!--CYRILLIC CAPITAL LETTER DE -->
+<!ENTITY dcy              "&#x00434;" ><!--CYRILLIC SMALL LETTER DE -->
+<!ENTITY Ecy              "&#x0042D;" ><!--CYRILLIC CAPITAL LETTER E -->
+<!ENTITY ecy              "&#x0044D;" ><!--CYRILLIC SMALL LETTER E -->
+<!ENTITY Fcy              "&#x00424;" ><!--CYRILLIC CAPITAL LETTER EF -->
+<!ENTITY fcy              "&#x00444;" ><!--CYRILLIC SMALL LETTER EF -->
+<!ENTITY Gcy              "&#x00413;" ><!--CYRILLIC CAPITAL LETTER GHE -->
+<!ENTITY gcy              "&#x00433;" ><!--CYRILLIC SMALL LETTER GHE -->
+<!ENTITY HARDcy           "&#x0042A;" ><!--CYRILLIC CAPITAL LETTER HARD SIGN -->
+<!ENTITY hardcy           "&#x0044A;" ><!--CYRILLIC SMALL LETTER HARD SIGN -->
+<!ENTITY Icy              "&#x00418;" ><!--CYRILLIC CAPITAL LETTER I -->
+<!ENTITY icy              "&#x00438;" ><!--CYRILLIC SMALL LETTER I -->
+<!ENTITY IEcy             "&#x00415;" ><!--CYRILLIC CAPITAL LETTER IE -->
+<!ENTITY iecy             "&#x00435;" ><!--CYRILLIC SMALL LETTER IE -->
+<!ENTITY IOcy             "&#x00401;" ><!--CYRILLIC CAPITAL LETTER IO -->
+<!ENTITY iocy             "&#x00451;" ><!--CYRILLIC SMALL LETTER IO -->
+<!ENTITY Jcy              "&#x00419;" ><!--CYRILLIC CAPITAL LETTER SHORT I -->
+<!ENTITY jcy              "&#x00439;" ><!--CYRILLIC SMALL LETTER SHORT I -->
+<!ENTITY Kcy              "&#x0041A;" ><!--CYRILLIC CAPITAL LETTER KA -->
+<!ENTITY kcy              "&#x0043A;" ><!--CYRILLIC SMALL LETTER KA -->
+<!ENTITY KHcy             "&#x00425;" ><!--CYRILLIC CAPITAL LETTER HA -->
+<!ENTITY khcy             "&#x00445;" ><!--CYRILLIC SMALL LETTER HA -->
+<!ENTITY Lcy              "&#x0041B;" ><!--CYRILLIC CAPITAL LETTER EL -->
+<!ENTITY lcy              "&#x0043B;" ><!--CYRILLIC SMALL LETTER EL -->
+<!ENTITY Mcy              "&#x0041C;" ><!--CYRILLIC CAPITAL LETTER EM -->
+<!ENTITY mcy              "&#x0043C;" ><!--CYRILLIC SMALL LETTER EM -->
+<!ENTITY Ncy              "&#x0041D;" ><!--CYRILLIC CAPITAL LETTER EN -->
+<!ENTITY ncy              "&#x0043D;" ><!--CYRILLIC SMALL LETTER EN -->
+<!ENTITY numero           "&#x02116;" ><!--NUMERO SIGN -->
+<!ENTITY Ocy              "&#x0041E;" ><!--CYRILLIC CAPITAL LETTER O -->
+<!ENTITY ocy              "&#x0043E;" ><!--CYRILLIC SMALL LETTER O -->
+<!ENTITY Pcy              "&#x0041F;" ><!--CYRILLIC CAPITAL LETTER PE -->
+<!ENTITY pcy              "&#x0043F;" ><!--CYRILLIC SMALL LETTER PE -->
+<!ENTITY Rcy              "&#x00420;" ><!--CYRILLIC CAPITAL LETTER ER -->
+<!ENTITY rcy              "&#x00440;" ><!--CYRILLIC SMALL LETTER ER -->
+<!ENTITY Scy              "&#x00421;" ><!--CYRILLIC CAPITAL LETTER ES -->
+<!ENTITY scy              "&#x00441;" ><!--CYRILLIC SMALL LETTER ES -->
+<!ENTITY SHCHcy           "&#x00429;" ><!--CYRILLIC CAPITAL LETTER SHCHA -->
+<!ENTITY shchcy           "&#x00449;" ><!--CYRILLIC SMALL LETTER SHCHA -->
+<!ENTITY SHcy             "&#x00428;" ><!--CYRILLIC CAPITAL LETTER SHA -->
+<!ENTITY shcy             "&#x00448;" ><!--CYRILLIC SMALL LETTER SHA -->
+<!ENTITY SOFTcy           "&#x0042C;" ><!--CYRILLIC CAPITAL LETTER SOFT SIGN -->
+<!ENTITY softcy           "&#x0044C;" ><!--CYRILLIC SMALL LETTER SOFT SIGN -->
+<!ENTITY Tcy              "&#x00422;" ><!--CYRILLIC CAPITAL LETTER TE -->
+<!ENTITY tcy              "&#x00442;" ><!--CYRILLIC SMALL LETTER TE -->
+<!ENTITY TScy             "&#x00426;" ><!--CYRILLIC CAPITAL LETTER TSE -->
+<!ENTITY tscy             "&#x00446;" ><!--CYRILLIC SMALL LETTER TSE -->
+<!ENTITY Ucy              "&#x00423;" ><!--CYRILLIC CAPITAL LETTER U -->
+<!ENTITY ucy              "&#x00443;" ><!--CYRILLIC SMALL LETTER U -->
+<!ENTITY Vcy              "&#x00412;" ><!--CYRILLIC CAPITAL LETTER VE -->
+<!ENTITY vcy              "&#x00432;" ><!--CYRILLIC SMALL LETTER VE -->
+<!ENTITY YAcy             "&#x0042F;" ><!--CYRILLIC CAPITAL LETTER YA -->
+<!ENTITY yacy             "&#x0044F;" ><!--CYRILLIC SMALL LETTER YA -->
+<!ENTITY Ycy              "&#x0042B;" ><!--CYRILLIC CAPITAL LETTER YERU -->
+<!ENTITY ycy              "&#x0044B;" ><!--CYRILLIC SMALL LETTER YERU -->
+<!ENTITY YUcy             "&#x0042E;" ><!--CYRILLIC CAPITAL LETTER YU -->
+<!ENTITY yucy             "&#x0044E;" ><!--CYRILLIC SMALL LETTER YU -->
+<!ENTITY Zcy              "&#x00417;" ><!--CYRILLIC CAPITAL LETTER ZE -->
+<!ENTITY zcy              "&#x00437;" ><!--CYRILLIC SMALL LETTER ZE -->
+<!ENTITY ZHcy             "&#x00416;" ><!--CYRILLIC CAPITAL LETTER ZHE -->
+<!ENTITY zhcy             "&#x00436;" ><!--CYRILLIC SMALL LETTER ZHE -->
diff --git a/contrib/entitynorm/entities/isocyr2.ent b/contrib/entitynorm/entities/isocyr2.ent
new file mode 100644
index 0000000..3676caf
--- /dev/null
+++ b/contrib/entitynorm/entities/isocyr2.ent
@@ -0,0 +1,67 @@
+
+<!--
+     File isocyr2.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isocyr2.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Non-Russian Cyrillic//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isocyr2.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isocyr2 PUBLIC
+         "ISO 8879:1986//ENTITIES Non-Russian Cyrillic//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isocyr2.ent"
+       >
+       %isocyr2;
+
+-->
+
+<!ENTITY DJcy             "&#x00402;" ><!--CYRILLIC CAPITAL LETTER DJE -->
+<!ENTITY djcy             "&#x00452;" ><!--CYRILLIC SMALL LETTER DJE -->
+<!ENTITY DScy             "&#x00405;" ><!--CYRILLIC CAPITAL LETTER DZE -->
+<!ENTITY dscy             "&#x00455;" ><!--CYRILLIC SMALL LETTER DZE -->
+<!ENTITY DZcy             "&#x0040F;" ><!--CYRILLIC CAPITAL LETTER DZHE -->
+<!ENTITY dzcy             "&#x0045F;" ><!--CYRILLIC SMALL LETTER DZHE -->
+<!ENTITY GJcy             "&#x00403;" ><!--CYRILLIC CAPITAL LETTER GJE -->
+<!ENTITY gjcy             "&#x00453;" ><!--CYRILLIC SMALL LETTER GJE -->
+<!ENTITY Iukcy            "&#x00406;" ><!--CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I -->
+<!ENTITY iukcy            "&#x00456;" ><!--CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
+<!ENTITY Jsercy           "&#x00408;" ><!--CYRILLIC CAPITAL LETTER JE -->
+<!ENTITY jsercy           "&#x00458;" ><!--CYRILLIC SMALL LETTER JE -->
+<!ENTITY Jukcy            "&#x00404;" ><!--CYRILLIC CAPITAL LETTER UKRAINIAN IE -->
+<!ENTITY jukcy            "&#x00454;" ><!--CYRILLIC SMALL LETTER UKRAINIAN IE -->
+<!ENTITY KJcy             "&#x0040C;" ><!--CYRILLIC CAPITAL LETTER KJE -->
+<!ENTITY kjcy             "&#x0045C;" ><!--CYRILLIC SMALL LETTER KJE -->
+<!ENTITY LJcy             "&#x00409;" ><!--CYRILLIC CAPITAL LETTER LJE -->
+<!ENTITY ljcy             "&#x00459;" ><!--CYRILLIC SMALL LETTER LJE -->
+<!ENTITY NJcy             "&#x0040A;" ><!--CYRILLIC CAPITAL LETTER NJE -->
+<!ENTITY njcy             "&#x0045A;" ><!--CYRILLIC SMALL LETTER NJE -->
+<!ENTITY TSHcy            "&#x0040B;" ><!--CYRILLIC CAPITAL LETTER TSHE -->
+<!ENTITY tshcy            "&#x0045B;" ><!--CYRILLIC SMALL LETTER TSHE -->
+<!ENTITY Ubrcy            "&#x0040E;" ><!--CYRILLIC CAPITAL LETTER SHORT U -->
+<!ENTITY ubrcy            "&#x0045E;" ><!--CYRILLIC SMALL LETTER SHORT U -->
+<!ENTITY YIcy             "&#x00407;" ><!--CYRILLIC CAPITAL LETTER YI -->
+<!ENTITY yicy             "&#x00457;" ><!--CYRILLIC SMALL LETTER YI -->
diff --git a/contrib/entitynorm/entities/isodia.ent b/contrib/entitynorm/entities/isodia.ent
new file mode 100644
index 0000000..7e9a9c6
--- /dev/null
+++ b/contrib/entitynorm/entities/isodia.ent
@@ -0,0 +1,55 @@
+
+<!--
+     File isodia.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isodia.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Diacritical Marks//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isodia.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isodia PUBLIC
+         "ISO 8879:1986//ENTITIES Diacritical Marks//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isodia.ent"
+       >
+       %isodia;
+
+-->
+
+<!ENTITY acute            "&#x000B4;" ><!--ACUTE ACCENT -->
+<!ENTITY breve            "&#x002D8;" ><!--BREVE -->
+<!ENTITY caron            "&#x002C7;" ><!--CARON -->
+<!ENTITY cedil            "&#x000B8;" ><!--CEDILLA -->
+<!ENTITY circ             "&#x002C6;" ><!--MODIFIER LETTER CIRCUMFLEX ACCENT -->
+<!ENTITY dblac            "&#x002DD;" ><!--DOUBLE ACUTE ACCENT -->
+<!ENTITY die              "&#x000A8;" ><!--DIAERESIS -->
+<!ENTITY dot              "&#x002D9;" ><!--DOT ABOVE -->
+<!ENTITY grave            "&#x00060;" ><!--GRAVE ACCENT -->
+<!ENTITY macr             "&#x000AF;" ><!--MACRON -->
+<!ENTITY ogon             "&#x002DB;" ><!--OGONEK -->
+<!ENTITY ring             "&#x002DA;" ><!--RING ABOVE -->
+<!ENTITY tilde            "&#x002DC;" ><!--SMALL TILDE -->
+<!ENTITY uml              "&#x000A8;" ><!--DIAERESIS -->
diff --git a/contrib/entitynorm/entities/isogrk1.ent b/contrib/entitynorm/entities/isogrk1.ent
new file mode 100644
index 0000000..4755910
--- /dev/null
+++ b/contrib/entitynorm/entities/isogrk1.ent
@@ -0,0 +1,90 @@
+
+<!--
+     File isogrk1.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isogrk1.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Greek Letters//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isogrk1.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isogrk1 PUBLIC
+         "ISO 8879:1986//ENTITIES Greek Letters//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isogrk1.ent"
+       >
+       %isogrk1;
+
+-->
+
+<!ENTITY Agr              "&#x00391;" ><!--GREEK CAPITAL LETTER ALPHA -->
+<!ENTITY agr              "&#x003B1;" ><!--GREEK SMALL LETTER ALPHA -->
+<!ENTITY Bgr              "&#x00392;" ><!--GREEK CAPITAL LETTER BETA -->
+<!ENTITY bgr              "&#x003B2;" ><!--GREEK SMALL LETTER BETA -->
+<!ENTITY Dgr              "&#x00394;" ><!--GREEK CAPITAL LETTER DELTA -->
+<!ENTITY dgr              "&#x003B4;" ><!--GREEK SMALL LETTER DELTA -->
+<!ENTITY EEgr             "&#x00397;" ><!--GREEK CAPITAL LETTER ETA -->
+<!ENTITY eegr             "&#x003B7;" ><!--GREEK SMALL LETTER ETA -->
+<!ENTITY Egr              "&#x00395;" ><!--GREEK CAPITAL LETTER EPSILON -->
+<!ENTITY egr              "&#x003B5;" ><!--GREEK SMALL LETTER EPSILON -->
+<!ENTITY Ggr              "&#x00393;" ><!--GREEK CAPITAL LETTER GAMMA -->
+<!ENTITY ggr              "&#x003B3;" ><!--GREEK SMALL LETTER GAMMA -->
+<!ENTITY Igr              "&#x00399;" ><!--GREEK CAPITAL LETTER IOTA -->
+<!ENTITY igr              "&#x003B9;" ><!--GREEK SMALL LETTER IOTA -->
+<!ENTITY Kgr              "&#x0039A;" ><!--GREEK CAPITAL LETTER KAPPA -->
+<!ENTITY kgr              "&#x003BA;" ><!--GREEK SMALL LETTER KAPPA -->
+<!ENTITY KHgr             "&#x003A7;" ><!--GREEK CAPITAL LETTER CHI -->
+<!ENTITY khgr             "&#x003C7;" ><!--GREEK SMALL LETTER CHI -->
+<!ENTITY Lgr              "&#x0039B;" ><!--GREEK CAPITAL LETTER LAMDA -->
+<!ENTITY lgr              "&#x003BB;" ><!--GREEK SMALL LETTER LAMDA -->
+<!ENTITY Mgr              "&#x0039C;" ><!--GREEK CAPITAL LETTER MU -->
+<!ENTITY mgr              "&#x003BC;" ><!--GREEK SMALL LETTER MU -->
+<!ENTITY Ngr              "&#x0039D;" ><!--GREEK CAPITAL LETTER NU -->
+<!ENTITY ngr              "&#x003BD;" ><!--GREEK SMALL LETTER NU -->
+<!ENTITY Ogr              "&#x0039F;" ><!--GREEK CAPITAL LETTER OMICRON -->
+<!ENTITY ogr              "&#x003BF;" ><!--GREEK SMALL LETTER OMICRON -->
+<!ENTITY OHgr             "&#x003A9;" ><!--GREEK CAPITAL LETTER OMEGA -->
+<!ENTITY ohgr             "&#x003C9;" ><!--GREEK SMALL LETTER OMEGA -->
+<!ENTITY Pgr              "&#x003A0;" ><!--GREEK CAPITAL LETTER PI -->
+<!ENTITY pgr              "&#x003C0;" ><!--GREEK SMALL LETTER PI -->
+<!ENTITY PHgr             "&#x003A6;" ><!--GREEK CAPITAL LETTER PHI -->
+<!ENTITY phgr             "&#x003C6;" ><!--GREEK SMALL LETTER PHI -->
+<!ENTITY PSgr             "&#x003A8;" ><!--GREEK CAPITAL LETTER PSI -->
+<!ENTITY psgr             "&#x003C8;" ><!--GREEK SMALL LETTER PSI -->
+<!ENTITY Rgr              "&#x003A1;" ><!--GREEK CAPITAL LETTER RHO -->
+<!ENTITY rgr              "&#x003C1;" ><!--GREEK SMALL LETTER RHO -->
+<!ENTITY sfgr             "&#x003C2;" ><!--GREEK SMALL LETTER FINAL SIGMA -->
+<!ENTITY Sgr              "&#x003A3;" ><!--GREEK CAPITAL LETTER SIGMA -->
+<!ENTITY sgr              "&#x003C3;" ><!--GREEK SMALL LETTER SIGMA -->
+<!ENTITY Tgr              "&#x003A4;" ><!--GREEK CAPITAL LETTER TAU -->
+<!ENTITY tgr              "&#x003C4;" ><!--GREEK SMALL LETTER TAU -->
+<!ENTITY THgr             "&#x00398;" ><!--GREEK CAPITAL LETTER THETA -->
+<!ENTITY thgr             "&#x003B8;" ><!--GREEK SMALL LETTER THETA -->
+<!ENTITY Ugr              "&#x003A5;" ><!--GREEK CAPITAL LETTER UPSILON -->
+<!ENTITY ugr              "&#x003C5;" ><!--GREEK SMALL LETTER UPSILON -->
+<!ENTITY Xgr              "&#x0039E;" ><!--GREEK CAPITAL LETTER XI -->
+<!ENTITY xgr              "&#x003BE;" ><!--GREEK SMALL LETTER XI -->
+<!ENTITY Zgr              "&#x00396;" ><!--GREEK CAPITAL LETTER ZETA -->
+<!ENTITY zgr              "&#x003B6;" ><!--GREEK SMALL LETTER ZETA -->
diff --git a/contrib/entitynorm/entities/isogrk2.ent b/contrib/entitynorm/entities/isogrk2.ent
new file mode 100644
index 0000000..f922d83
--- /dev/null
+++ b/contrib/entitynorm/entities/isogrk2.ent
@@ -0,0 +1,61 @@
+
+<!--
+     File isogrk2.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isogrk2.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Monotoniko Greek//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isogrk2.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isogrk2 PUBLIC
+         "ISO 8879:1986//ENTITIES Monotoniko Greek//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isogrk2.ent"
+       >
+       %isogrk2;
+
+-->
+
+<!ENTITY Aacgr            "&#x00386;" ><!--GREEK CAPITAL LETTER ALPHA WITH TONOS -->
+<!ENTITY aacgr            "&#x003AC;" ><!--GREEK SMALL LETTER ALPHA WITH TONOS -->
+<!ENTITY Eacgr            "&#x00388;" ><!--GREEK CAPITAL LETTER EPSILON WITH TONOS -->
+<!ENTITY eacgr            "&#x003AD;" ><!--GREEK SMALL LETTER EPSILON WITH TONOS -->
+<!ENTITY EEacgr           "&#x00389;" ><!--GREEK CAPITAL LETTER ETA WITH TONOS -->
+<!ENTITY eeacgr           "&#x003AE;" ><!--GREEK SMALL LETTER ETA WITH TONOS -->
+<!ENTITY Iacgr            "&#x0038A;" ><!--GREEK CAPITAL LETTER IOTA WITH TONOS -->
+<!ENTITY iacgr            "&#x003AF;" ><!--GREEK SMALL LETTER IOTA WITH TONOS -->
+<!ENTITY idiagr           "&#x00390;" ><!--GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS -->
+<!ENTITY Idigr            "&#x003AA;" ><!--GREEK CAPITAL LETTER IOTA WITH DIALYTIKA -->
+<!ENTITY idigr            "&#x003CA;" ><!--GREEK SMALL LETTER IOTA WITH DIALYTIKA -->
+<!ENTITY Oacgr            "&#x0038C;" ><!--GREEK CAPITAL LETTER OMICRON WITH TONOS -->
+<!ENTITY oacgr            "&#x003CC;" ><!--GREEK SMALL LETTER OMICRON WITH TONOS -->
+<!ENTITY OHacgr           "&#x0038F;" ><!--GREEK CAPITAL LETTER OMEGA WITH TONOS -->
+<!ENTITY ohacgr           "&#x003CE;" ><!--GREEK SMALL LETTER OMEGA WITH TONOS -->
+<!ENTITY Uacgr            "&#x0038E;" ><!--GREEK CAPITAL LETTER UPSILON WITH TONOS -->
+<!ENTITY uacgr            "&#x003CD;" ><!--GREEK SMALL LETTER UPSILON WITH TONOS -->
+<!ENTITY udiagr           "&#x003B0;" ><!--GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS -->
+<!ENTITY Udigr            "&#x003AB;" ><!--GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA -->
+<!ENTITY udigr            "&#x003CB;" ><!--GREEK SMALL LETTER UPSILON WITH DIALYTIKA -->
diff --git a/contrib/entitynorm/entities/isogrk3.ent b/contrib/entitynorm/entities/isogrk3.ent
new file mode 100644
index 0000000..caebefb
--- /dev/null
+++ b/contrib/entitynorm/entities/isogrk3.ent
@@ -0,0 +1,84 @@
+
+<!--
+     File isogrk3.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isogrk3.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Greek Symbols//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isogrk3.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isogrk3 PUBLIC
+         "ISO 8879:1986//ENTITIES Greek Symbols//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isogrk3.ent"
+       >
+       %isogrk3;
+
+-->
+
+<!ENTITY alpha            "&#x003B1;" ><!--GREEK SMALL LETTER ALPHA -->
+<!ENTITY beta             "&#x003B2;" ><!--GREEK SMALL LETTER BETA -->
+<!ENTITY chi              "&#x003C7;" ><!--GREEK SMALL LETTER CHI -->
+<!ENTITY Delta            "&#x00394;" ><!--GREEK CAPITAL LETTER DELTA -->
+<!ENTITY delta            "&#x003B4;" ><!--GREEK SMALL LETTER DELTA -->
+<!ENTITY epsi             "&#x003F5;" ><!--GREEK LUNATE EPSILON SYMBOL -->
+<!ENTITY epsis            "&#x003F5;" ><!--GREEK LUNATE EPSILON SYMBOL -->
+<!ENTITY epsiv            "&#x003B5;" ><!--GREEK SMALL LETTER EPSILON -->
+<!ENTITY eta              "&#x003B7;" ><!--GREEK SMALL LETTER ETA -->
+<!ENTITY Gamma            "&#x00393;" ><!--GREEK CAPITAL LETTER GAMMA -->
+<!ENTITY gamma            "&#x003B3;" ><!--GREEK SMALL LETTER GAMMA -->
+<!ENTITY gammad           "&#x003DD;" ><!--GREEK SMALL LETTER DIGAMMA -->
+<!ENTITY iota             "&#x003B9;" ><!--GREEK SMALL LETTER IOTA -->
+<!ENTITY kappa            "&#x003BA;" ><!--GREEK SMALL LETTER KAPPA -->
+<!ENTITY kappav           "&#x003F0;" ><!--GREEK KAPPA SYMBOL -->
+<!ENTITY Lambda           "&#x0039B;" ><!--GREEK CAPITAL LETTER LAMDA -->
+<!ENTITY lambda           "&#x003BB;" ><!--GREEK SMALL LETTER LAMDA -->
+<!ENTITY mu               "&#x003BC;" ><!--GREEK SMALL LETTER MU -->
+<!ENTITY nu               "&#x003BD;" ><!--GREEK SMALL LETTER NU -->
+<!ENTITY Omega            "&#x003A9;" ><!--GREEK CAPITAL LETTER OMEGA -->
+<!ENTITY omega            "&#x003C9;" ><!--GREEK SMALL LETTER OMEGA -->
+<!ENTITY Phi              "&#x003A6;" ><!--GREEK CAPITAL LETTER PHI -->
+<!ENTITY phis             "&#x003D5;" ><!--GREEK PHI SYMBOL -->
+<!ENTITY phiv             "&#x003C6;" ><!--GREEK SMALL LETTER PHI -->
+<!ENTITY Pi               "&#x003A0;" ><!--GREEK CAPITAL LETTER PI -->
+<!ENTITY pi               "&#x003C0;" ><!--GREEK SMALL LETTER PI -->
+<!ENTITY piv              "&#x003D6;" ><!--GREEK PI SYMBOL -->
+<!ENTITY Psi              "&#x003A8;" ><!--GREEK CAPITAL LETTER PSI -->
+<!ENTITY psi              "&#x003C8;" ><!--GREEK SMALL LETTER PSI -->
+<!ENTITY rho              "&#x003C1;" ><!--GREEK SMALL LETTER RHO -->
+<!ENTITY rhov             "&#x003F1;" ><!--GREEK RHO SYMBOL -->
+<!ENTITY Sigma            "&#x003A3;" ><!--GREEK CAPITAL LETTER SIGMA -->
+<!ENTITY sigma            "&#x003C3;" ><!--GREEK SMALL LETTER SIGMA -->
+<!ENTITY sigmav           "&#x003C2;" ><!--GREEK SMALL LETTER FINAL SIGMA -->
+<!ENTITY tau              "&#x003C4;" ><!--GREEK SMALL LETTER TAU -->
+<!ENTITY Theta            "&#x00398;" ><!--GREEK CAPITAL LETTER THETA -->
+<!ENTITY thetas           "&#x003B8;" ><!--GREEK SMALL LETTER THETA -->
+<!ENTITY thetav           "&#x003D1;" ><!--GREEK THETA SYMBOL -->
+<!ENTITY Upsi             "&#x003D2;" ><!--GREEK UPSILON WITH HOOK SYMBOL -->
+<!ENTITY upsi             "&#x003C5;" ><!--GREEK SMALL LETTER UPSILON -->
+<!ENTITY Xi               "&#x0039E;" ><!--GREEK CAPITAL LETTER XI -->
+<!ENTITY xi               "&#x003BE;" ><!--GREEK SMALL LETTER XI -->
+<!ENTITY zeta             "&#x003B6;" ><!--GREEK SMALL LETTER ZETA -->
diff --git a/contrib/entitynorm/entities/isolat1.ent b/contrib/entitynorm/entities/isolat1.ent
new file mode 100644
index 0000000..d4a4e86
--- /dev/null
+++ b/contrib/entitynorm/entities/isolat1.ent
@@ -0,0 +1,103 @@
+
+<!--
+     File isolat1.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isolat1.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Added Latin 1//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isolat1.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isolat1 PUBLIC
+         "ISO 8879:1986//ENTITIES Added Latin 1//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isolat1.ent"
+       >
+       %isolat1;
+
+-->
+
+<!ENTITY Aacute           "&#x000C1;" ><!--LATIN CAPITAL LETTER A WITH ACUTE -->
+<!ENTITY aacute           "&#x000E1;" ><!--LATIN SMALL LETTER A WITH ACUTE -->
+<!ENTITY Acirc            "&#x000C2;" ><!--LATIN CAPITAL LETTER A WITH CIRCUMFLEX -->
+<!ENTITY acirc            "&#x000E2;" ><!--LATIN SMALL LETTER A WITH CIRCUMFLEX -->
+<!ENTITY AElig            "&#x000C6;" ><!--LATIN CAPITAL LETTER AE -->
+<!ENTITY aelig            "&#x000E6;" ><!--LATIN SMALL LETTER AE -->
+<!ENTITY Agrave           "&#x000C0;" ><!--LATIN CAPITAL LETTER A WITH GRAVE -->
+<!ENTITY agrave           "&#x000E0;" ><!--LATIN SMALL LETTER A WITH GRAVE -->
+<!ENTITY Aring            "&#x000C5;" ><!--LATIN CAPITAL LETTER A WITH RING ABOVE -->
+<!ENTITY aring            "&#x000E5;" ><!--LATIN SMALL LETTER A WITH RING ABOVE -->
+<!ENTITY Atilde           "&#x000C3;" ><!--LATIN CAPITAL LETTER A WITH TILDE -->
+<!ENTITY atilde           "&#x000E3;" ><!--LATIN SMALL LETTER A WITH TILDE -->
+<!ENTITY Auml             "&#x000C4;" ><!--LATIN CAPITAL LETTER A WITH DIAERESIS -->
+<!ENTITY auml             "&#x000E4;" ><!--LATIN SMALL LETTER A WITH DIAERESIS -->
+<!ENTITY Ccedil           "&#x000C7;" ><!--LATIN CAPITAL LETTER C WITH CEDILLA -->
+<!ENTITY ccedil           "&#x000E7;" ><!--LATIN SMALL LETTER C WITH CEDILLA -->
+<!ENTITY Eacute           "&#x000C9;" ><!--LATIN CAPITAL LETTER E WITH ACUTE -->
+<!ENTITY eacute           "&#x000E9;" ><!--LATIN SMALL LETTER E WITH ACUTE -->
+<!ENTITY Ecirc            "&#x000CA;" ><!--LATIN CAPITAL LETTER E WITH CIRCUMFLEX -->
+<!ENTITY ecirc            "&#x000EA;" ><!--LATIN SMALL LETTER E WITH CIRCUMFLEX -->
+<!ENTITY Egrave           "&#x000C8;" ><!--LATIN CAPITAL LETTER E WITH GRAVE -->
+<!ENTITY egrave           "&#x000E8;" ><!--LATIN SMALL LETTER E WITH GRAVE -->
+<!ENTITY ETH              "&#x000D0;" ><!--LATIN CAPITAL LETTER ETH -->
+<!ENTITY eth              "&#x000F0;" ><!--LATIN SMALL LETTER ETH -->
+<!ENTITY Euml             "&#x000CB;" ><!--LATIN CAPITAL LETTER E WITH DIAERESIS -->
+<!ENTITY euml             "&#x000EB;" ><!--LATIN SMALL LETTER E WITH DIAERESIS -->
+<!ENTITY Iacute           "&#x000CD;" ><!--LATIN CAPITAL LETTER I WITH ACUTE -->
+<!ENTITY iacute           "&#x000ED;" ><!--LATIN SMALL LETTER I WITH ACUTE -->
+<!ENTITY Icirc            "&#x000CE;" ><!--LATIN CAPITAL LETTER I WITH CIRCUMFLEX -->
+<!ENTITY icirc            "&#x000EE;" ><!--LATIN SMALL LETTER I WITH CIRCUMFLEX -->
+<!ENTITY Igrave           "&#x000CC;" ><!--LATIN CAPITAL LETTER I WITH GRAVE -->
+<!ENTITY igrave           "&#x000EC;" ><!--LATIN SMALL LETTER I WITH GRAVE -->
+<!ENTITY Iuml             "&#x000CF;" ><!--LATIN CAPITAL LETTER I WITH DIAERESIS -->
+<!ENTITY iuml             "&#x000EF;" ><!--LATIN SMALL LETTER I WITH DIAERESIS -->
+<!ENTITY Ntilde           "&#x000D1;" ><!--LATIN CAPITAL LETTER N WITH TILDE -->
+<!ENTITY ntilde           "&#x000F1;" ><!--LATIN SMALL LETTER N WITH TILDE -->
+<!ENTITY Oacute           "&#x000D3;" ><!--LATIN CAPITAL LETTER O WITH ACUTE -->
+<!ENTITY oacute           "&#x000F3;" ><!--LATIN SMALL LETTER O WITH ACUTE -->
+<!ENTITY Ocirc            "&#x000D4;" ><!--LATIN CAPITAL LETTER O WITH CIRCUMFLEX -->
+<!ENTITY ocirc            "&#x000F4;" ><!--LATIN SMALL LETTER O WITH CIRCUMFLEX -->
+<!ENTITY Ograve           "&#x000D2;" ><!--LATIN CAPITAL LETTER O WITH GRAVE -->
+<!ENTITY ograve           "&#x000F2;" ><!--LATIN SMALL LETTER O WITH GRAVE -->
+<!ENTITY Oslash           "&#x000D8;" ><!--LATIN CAPITAL LETTER O WITH STROKE -->
+<!ENTITY oslash           "&#x000F8;" ><!--LATIN SMALL LETTER O WITH STROKE -->
+<!ENTITY Otilde           "&#x000D5;" ><!--LATIN CAPITAL LETTER O WITH TILDE -->
+<!ENTITY otilde           "&#x000F5;" ><!--LATIN SMALL LETTER O WITH TILDE -->
+<!ENTITY Ouml             "&#x000D6;" ><!--LATIN CAPITAL LETTER O WITH DIAERESIS -->
+<!ENTITY ouml             "&#x000F6;" ><!--LATIN SMALL LETTER O WITH DIAERESIS -->
+<!ENTITY szlig            "&#x000DF;" ><!--LATIN SMALL LETTER SHARP S -->
+<!ENTITY THORN            "&#x000DE;" ><!--LATIN CAPITAL LETTER THORN -->
+<!ENTITY thorn            "&#x000FE;" ><!--LATIN SMALL LETTER THORN -->
+<!ENTITY Uacute           "&#x000DA;" ><!--LATIN CAPITAL LETTER U WITH ACUTE -->
+<!ENTITY uacute           "&#x000FA;" ><!--LATIN SMALL LETTER U WITH ACUTE -->
+<!ENTITY Ucirc            "&#x000DB;" ><!--LATIN CAPITAL LETTER U WITH CIRCUMFLEX -->
+<!ENTITY ucirc            "&#x000FB;" ><!--LATIN SMALL LETTER U WITH CIRCUMFLEX -->
+<!ENTITY Ugrave           "&#x000D9;" ><!--LATIN CAPITAL LETTER U WITH GRAVE -->
+<!ENTITY ugrave           "&#x000F9;" ><!--LATIN SMALL LETTER U WITH GRAVE -->
+<!ENTITY Uuml             "&#x000DC;" ><!--LATIN CAPITAL LETTER U WITH DIAERESIS -->
+<!ENTITY uuml             "&#x000FC;" ><!--LATIN SMALL LETTER U WITH DIAERESIS -->
+<!ENTITY Yacute           "&#x000DD;" ><!--LATIN CAPITAL LETTER Y WITH ACUTE -->
+<!ENTITY yacute           "&#x000FD;" ><!--LATIN SMALL LETTER Y WITH ACUTE -->
+<!ENTITY yuml             "&#x000FF;" ><!--LATIN SMALL LETTER Y WITH DIAERESIS -->
diff --git a/contrib/entitynorm/entities/isonum.ent b/contrib/entitynorm/entities/isonum.ent
new file mode 100644
index 0000000..f20e9c7
--- /dev/null
+++ b/contrib/entitynorm/entities/isonum.ent
@@ -0,0 +1,117 @@
+
+<!--
+     File isonum.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isonum.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Numeric and Special Graphic//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isonum.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isonum PUBLIC
+         "ISO 8879:1986//ENTITIES Numeric and Special Graphic//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isonum.ent"
+       >
+       %isonum;
+
+-->
+
+<!ENTITY amp              "&#38;#38;" ><!--AMPERSAND -->
+<!ENTITY apos             "&#x00027;" ><!--APOSTROPHE -->
+<!ENTITY ast              "&#x0002A;" ><!--ASTERISK -->
+<!ENTITY brvbar           "&#x000A6;" ><!--BROKEN BAR -->
+<!ENTITY bsol             "&#x0005C;" ><!--REVERSE SOLIDUS -->
+<!ENTITY cent             "&#x000A2;" ><!--CENT SIGN -->
+<!ENTITY colon            "&#x0003A;" ><!--COLON -->
+<!ENTITY comma            "&#x0002C;" ><!--COMMA -->
+<!ENTITY commat           "&#x00040;" ><!--COMMERCIAL AT -->
+<!ENTITY copy             "&#x000A9;" ><!--COPYRIGHT SIGN -->
+<!ENTITY curren           "&#x000A4;" ><!--CURRENCY SIGN -->
+<!ENTITY darr             "&#x02193;" ><!--DOWNWARDS ARROW -->
+<!ENTITY deg              "&#x000B0;" ><!--DEGREE SIGN -->
+<!ENTITY divide           "&#x000F7;" ><!--DIVISION SIGN -->
+<!ENTITY dollar           "&#x00024;" ><!--DOLLAR SIGN -->
+<!ENTITY equals           "&#x0003D;" ><!--EQUALS SIGN -->
+<!ENTITY excl             "&#x00021;" ><!--EXCLAMATION MARK -->
+<!ENTITY frac12           "&#x000BD;" ><!--VULGAR FRACTION ONE HALF -->
+<!ENTITY frac14           "&#x000BC;" ><!--VULGAR FRACTION ONE QUARTER -->
+<!ENTITY frac18           "&#x0215B;" ><!--VULGAR FRACTION ONE EIGHTH -->
+<!ENTITY frac34           "&#x000BE;" ><!--VULGAR FRACTION THREE QUARTERS -->
+<!ENTITY frac38           "&#x0215C;" ><!--VULGAR FRACTION THREE EIGHTHS -->
+<!ENTITY frac58           "&#x0215D;" ><!--VULGAR FRACTION FIVE EIGHTHS -->
+<!ENTITY frac78           "&#x0215E;" ><!--VULGAR FRACTION SEVEN EIGHTHS -->
+<!ENTITY gt               "&#x0003E;" ><!--GREATER-THAN SIGN -->
+<!ENTITY half             "&#x000BD;" ><!--VULGAR FRACTION ONE HALF -->
+<!ENTITY horbar           "&#x02015;" ><!--HORIZONTAL BAR -->
+<!ENTITY hyphen           "&#x02010;" ><!--HYPHEN -->
+<!ENTITY iexcl            "&#x000A1;" ><!--INVERTED EXCLAMATION MARK -->
+<!ENTITY iquest           "&#x000BF;" ><!--INVERTED QUESTION MARK -->
+<!ENTITY laquo            "&#x000AB;" ><!--LEFT-POINTING DOUBLE ANGLE QUOTATION MARK -->
+<!ENTITY larr             "&#x02190;" ><!--LEFTWARDS ARROW -->
+<!ENTITY lcub             "&#x0007B;" ><!--LEFT CURLY BRACKET -->
+<!ENTITY ldquo            "&#x0201C;" ><!--LEFT DOUBLE QUOTATION MARK -->
+<!ENTITY lowbar           "&#x0005F;" ><!--LOW LINE -->
+<!ENTITY lpar             "&#x00028;" ><!--LEFT PARENTHESIS -->
+<!ENTITY lsqb             "&#x0005B;" ><!--LEFT SQUARE BRACKET -->
+<!ENTITY lsquo            "&#x02018;" ><!--LEFT SINGLE QUOTATION MARK -->
+<!ENTITY lt               "&#38;#60;" ><!--LESS-THAN SIGN -->
+<!ENTITY micro            "&#x000B5;" ><!--MICRO SIGN -->
+<!ENTITY middot           "&#x000B7;" ><!--MIDDLE DOT -->
+<!ENTITY nbsp             "&#x000A0;" ><!--NO-BREAK SPACE -->
+<!ENTITY not              "&#x000AC;" ><!--NOT SIGN -->
+<!ENTITY num              "&#x00023;" ><!--NUMBER SIGN -->
+<!ENTITY ohm              "&#x02126;" ><!--OHM SIGN -->
+<!ENTITY ordf             "&#x000AA;" ><!--FEMININE ORDINAL INDICATOR -->
+<!ENTITY ordm             "&#x000BA;" ><!--MASCULINE ORDINAL INDICATOR -->
+<!ENTITY para             "&#x000B6;" ><!--PILCROW SIGN -->
+<!ENTITY percnt           "&#x00025;" ><!--PERCENT SIGN -->
+<!ENTITY period           "&#x0002E;" ><!--FULL STOP -->
+<!ENTITY plus             "&#x0002B;" ><!--PLUS SIGN -->
+<!ENTITY plusmn           "&#x000B1;" ><!--PLUS-MINUS SIGN -->
+<!ENTITY pound            "&#x000A3;" ><!--POUND SIGN -->
+<!ENTITY quest            "&#x0003F;" ><!--QUESTION MARK -->
+<!ENTITY quot             "&#x00022;" ><!--QUOTATION MARK -->
+<!ENTITY raquo            "&#x000BB;" ><!--RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
+<!ENTITY rarr             "&#x02192;" ><!--RIGHTWARDS ARROW -->
+<!ENTITY rcub             "&#x0007D;" ><!--RIGHT CURLY BRACKET -->
+<!ENTITY rdquo            "&#x0201D;" ><!--RIGHT DOUBLE QUOTATION MARK -->
+<!ENTITY reg              "&#x000AE;" ><!--REGISTERED SIGN -->
+<!ENTITY rpar             "&#x00029;" ><!--RIGHT PARENTHESIS -->
+<!ENTITY rsqb             "&#x0005D;" ><!--RIGHT SQUARE BRACKET -->
+<!ENTITY rsquo            "&#x02019;" ><!--RIGHT SINGLE QUOTATION MARK -->
+<!ENTITY sect             "&#x000A7;" ><!--SECTION SIGN -->
+<!ENTITY semi             "&#x0003B;" ><!--SEMICOLON -->
+<!ENTITY shy              "&#x000AD;" ><!--SOFT HYPHEN -->
+<!ENTITY sol              "&#x0002F;" ><!--SOLIDUS -->
+<!ENTITY sung             "&#x0266A;" ><!--EIGHTH NOTE -->
+<!ENTITY sup1             "&#x000B9;" ><!--SUPERSCRIPT ONE -->
+<!ENTITY sup2             "&#x000B2;" ><!--SUPERSCRIPT TWO -->
+<!ENTITY sup3             "&#x000B3;" ><!--SUPERSCRIPT THREE -->
+<!ENTITY times            "&#x000D7;" ><!--MULTIPLICATION SIGN -->
+<!ENTITY trade            "&#x02122;" ><!--TRADE MARK SIGN -->
+<!ENTITY uarr             "&#x02191;" ><!--UPWARDS ARROW -->
+<!ENTITY verbar           "&#x0007C;" ><!--VERTICAL LINE -->
+<!ENTITY yen              "&#x000A5;" ><!--YEN SIGN -->
diff --git a/contrib/entitynorm/entities/isopub.ent b/contrib/entitynorm/entities/isopub.ent
new file mode 100644
index 0000000..10ad9d5
--- /dev/null
+++ b/contrib/entitynorm/entities/isopub.ent
@@ -0,0 +1,125 @@
+
+<!--
+     File isopub.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isopub.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES Publishing//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isopub.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isopub PUBLIC
+         "ISO 8879:1986//ENTITIES Publishing//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isopub.ent"
+       >
+       %isopub;
+
+-->
+
+<!ENTITY blank            "&#x02423;" ><!--OPEN BOX -->
+<!ENTITY blk12            "&#x02592;" ><!--MEDIUM SHADE -->
+<!ENTITY blk14            "&#x02591;" ><!--LIGHT SHADE -->
+<!ENTITY blk34            "&#x02593;" ><!--DARK SHADE -->
+<!ENTITY block            "&#x02588;" ><!--FULL BLOCK -->
+<!ENTITY bull             "&#x02022;" ><!--BULLET -->
+<!ENTITY caret            "&#x02041;" ><!--CARET INSERTION POINT -->
+<!ENTITY check            "&#x02713;" ><!--CHECK MARK -->
+<!ENTITY cir              "&#x025CB;" ><!--WHITE CIRCLE -->
+<!ENTITY clubs            "&#x02663;" ><!--BLACK CLUB SUIT -->
+<!ENTITY copysr           "&#x02117;" ><!--SOUND RECORDING COPYRIGHT -->
+<!ENTITY cross            "&#x02717;" ><!--BALLOT X -->
+<!ENTITY Dagger           "&#x02021;" ><!--DOUBLE DAGGER -->
+<!ENTITY dagger           "&#x02020;" ><!--DAGGER -->
+<!ENTITY dash             "&#x02010;" ><!--HYPHEN -->
+<!ENTITY diams            "&#x02666;" ><!--BLACK DIAMOND SUIT -->
+<!ENTITY dlcrop           "&#x0230D;" ><!--BOTTOM LEFT CROP -->
+<!ENTITY drcrop           "&#x0230C;" ><!--BOTTOM RIGHT CROP -->
+<!ENTITY dtri             "&#x025BF;" ><!--WHITE DOWN-POINTING SMALL TRIANGLE -->
+<!ENTITY dtrif            "&#x025BE;" ><!--BLACK DOWN-POINTING SMALL TRIANGLE -->
+<!ENTITY emsp             "&#x02003;" ><!--EM SPACE -->
+<!ENTITY emsp13           "&#x02004;" ><!--THREE-PER-EM SPACE -->
+<!ENTITY emsp14           "&#x02005;" ><!--FOUR-PER-EM SPACE -->
+<!ENTITY ensp             "&#x02002;" ><!--EN SPACE -->
+<!ENTITY female           "&#x02640;" ><!--FEMALE SIGN -->
+<!ENTITY ffilig           "&#x0FB03;" ><!--LATIN SMALL LIGATURE FFI -->
+<!ENTITY fflig            "&#x0FB00;" ><!--LATIN SMALL LIGATURE FF -->
+<!ENTITY ffllig           "&#x0FB04;" ><!--LATIN SMALL LIGATURE FFL -->
+<!ENTITY filig            "&#x0FB01;" ><!--LATIN SMALL LIGATURE FI -->
+<!ENTITY flat             "&#x0266D;" ><!--MUSIC FLAT SIGN -->
+<!ENTITY fllig            "&#x0FB02;" ><!--LATIN SMALL LIGATURE FL -->
+<!ENTITY frac13           "&#x02153;" ><!--VULGAR FRACTION ONE THIRD -->
+<!ENTITY frac15           "&#x02155;" ><!--VULGAR FRACTION ONE FIFTH -->
+<!ENTITY frac16           "&#x02159;" ><!--VULGAR FRACTION ONE SIXTH -->
+<!ENTITY frac23           "&#x02154;" ><!--VULGAR FRACTION TWO THIRDS -->
+<!ENTITY frac25           "&#x02156;" ><!--VULGAR FRACTION TWO FIFTHS -->
+<!ENTITY frac35           "&#x02157;" ><!--VULGAR FRACTION THREE FIFTHS -->
+<!ENTITY frac45           "&#x02158;" ><!--VULGAR FRACTION FOUR FIFTHS -->
+<!ENTITY frac56           "&#x0215A;" ><!--VULGAR FRACTION FIVE SIXTHS -->
+<!ENTITY hairsp           "&#x0200A;" ><!--HAIR SPACE -->
+<!ENTITY hearts           "&#x02665;" ><!--BLACK HEART SUIT -->
+<!ENTITY hellip           "&#x02026;" ><!--HORIZONTAL ELLIPSIS -->
+<!ENTITY hybull           "&#x02043;" ><!--HYPHEN BULLET -->
+<!ENTITY incare           "&#x02105;" ><!--CARE OF -->
+<!ENTITY ldquor           "&#x0201E;" ><!--DOUBLE LOW-9 QUOTATION MARK -->
+<!ENTITY lhblk            "&#x02584;" ><!--LOWER HALF BLOCK -->
+<!ENTITY loz              "&#x025CA;" ><!--LOZENGE -->
+<!ENTITY lozf             "&#x029EB;" ><!--BLACK LOZENGE -->
+<!ENTITY lsquor           "&#x0201A;" ><!--SINGLE LOW-9 QUOTATION MARK -->
+<!ENTITY ltri             "&#x025C3;" ><!--WHITE LEFT-POINTING SMALL TRIANGLE -->
+<!ENTITY ltrif            "&#x025C2;" ><!--BLACK LEFT-POINTING SMALL TRIANGLE -->
+<!ENTITY male             "&#x02642;" ><!--MALE SIGN -->
+<!ENTITY malt             "&#x02720;" ><!--MALTESE CROSS -->
+<!ENTITY marker           "&#x025AE;" ><!--BLACK VERTICAL RECTANGLE -->
+<!ENTITY mdash            "&#x02014;" ><!--EM DASH -->
+<!ENTITY mldr             "&#x02026;" ><!--HORIZONTAL ELLIPSIS -->
+<!ENTITY natur            "&#x0266E;" ><!--MUSIC NATURAL SIGN -->
+<!ENTITY ndash            "&#x02013;" ><!--EN DASH -->
+<!ENTITY nldr             "&#x02025;" ><!--TWO DOT LEADER -->
+<!ENTITY numsp            "&#x02007;" ><!--FIGURE SPACE -->
+<!ENTITY phone            "&#x0260E;" ><!--BLACK TELEPHONE -->
+<!ENTITY puncsp           "&#x02008;" ><!--PUNCTUATION SPACE -->
+<!ENTITY rdquor           "&#x0201D;" ><!--RIGHT DOUBLE QUOTATION MARK -->
+<!ENTITY rect             "&#x025AD;" ><!--WHITE RECTANGLE -->
+<!ENTITY rsquor           "&#x02019;" ><!--RIGHT SINGLE QUOTATION MARK -->
+<!ENTITY rtri             "&#x025B9;" ><!--WHITE RIGHT-POINTING SMALL TRIANGLE -->
+<!ENTITY rtrif            "&#x025B8;" ><!--BLACK RIGHT-POINTING SMALL TRIANGLE -->
+<!ENTITY rx               "&#x0211E;" ><!--PRESCRIPTION TAKE -->
+<!ENTITY sext             "&#x02736;" ><!--SIX POINTED BLACK STAR -->
+<!ENTITY sharp            "&#x0266F;" ><!--MUSIC SHARP SIGN -->
+<!ENTITY spades           "&#x02660;" ><!--BLACK SPADE SUIT -->
+<!ENTITY squ              "&#x025A1;" ><!--WHITE SQUARE -->
+<!ENTITY squf             "&#x025AA;" ><!--BLACK SMALL SQUARE -->
+<!ENTITY star             "&#x02606;" ><!--WHITE STAR -->
+<!ENTITY starf            "&#x02605;" ><!--BLACK STAR -->
+<!ENTITY target           "&#x02316;" ><!--POSITION INDICATOR -->
+<!ENTITY telrec           "&#x02315;" ><!--TELEPHONE RECORDER -->
+<!ENTITY thinsp           "&#x02009;" ><!--THIN SPACE -->
+<!ENTITY uhblk            "&#x02580;" ><!--UPPER HALF BLOCK -->
+<!ENTITY ulcrop           "&#x0230F;" ><!--TOP LEFT CROP -->
+<!ENTITY urcrop           "&#x0230E;" ><!--TOP RIGHT CROP -->
+<!ENTITY utri             "&#x025B5;" ><!--WHITE UP-POINTING SMALL TRIANGLE -->
+<!ENTITY utrif            "&#x025B4;" ><!--BLACK UP-POINTING SMALL TRIANGLE -->
+<!ENTITY vellip           "&#x022EE;" ><!--VERTICAL ELLIPSIS -->
diff --git a/contrib/entitynorm/entities/isotech.ent b/contrib/entitynorm/entities/isotech.ent
new file mode 100644
index 0000000..3184553
--- /dev/null
+++ b/contrib/entitynorm/entities/isotech.ent
@@ -0,0 +1,103 @@
+
+<!--
+     File isotech.ent produced by the XSL script entities.xsl
+     from input data in unicode.xml.
+
+     Please report any errors to David Carlisle
+     via the public W3C list www-math at w3.org.
+
+     The numeric character values assigned to each entity
+     (should) match the Unicode assignments in Unicode 4.0.
+
+     Entity names in this file are derived from files carrying the
+     following notice:
+
+     (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+
+-->
+
+
+<!-- 
+     Version: $Id: isotech.ent,v 1.1 2006/12/26 16:17:01 tkojm Exp $
+
+       Public identifier: ISO 8879:1986//ENTITIES General Technical//EN//XML
+       System identifier: http://www.w3.org/2003/entities/iso8879/isotech.ent
+
+     The public identifier should always be used verbatim.
+     The system identifier may be changed to suit local requirements.
+
+     Typical invocation:
+
+       <!ENTITY % isotech PUBLIC
+         "ISO 8879:1986//ENTITIES General Technical//EN//XML"
+         "http://www.w3.org/2003/entities/iso8879/isotech.ent"
+       >
+       %isotech;
+
+-->
+
+<!ENTITY aleph            "&#x02135;" ><!--ALEF SYMBOL -->
+<!ENTITY and              "&#x02227;" ><!--LOGICAL AND -->
+<!ENTITY ang90            "&#x0221F;" ><!--RIGHT ANGLE -->
+<!ENTITY angsph           "&#x02222;" ><!--SPHERICAL ANGLE -->
+<!ENTITY angst            "&#x0212B;" ><!--ANGSTROM SIGN -->
+<!ENTITY ap               "&#x02248;" ><!--ALMOST EQUAL TO -->
+<!ENTITY becaus           "&#x02235;" ><!--BECAUSE -->
+<!ENTITY bernou           "&#x0212C;" ><!--SCRIPT CAPITAL B -->
+<!ENTITY bottom           "&#x022A5;" ><!--UP TACK -->
+<!ENTITY cap              "&#x02229;" ><!--INTERSECTION -->
+<!ENTITY compfn           "&#x02218;" ><!--RING OPERATOR -->
+<!ENTITY cong             "&#x02245;" ><!--APPROXIMATELY EQUAL TO -->
+<!ENTITY conint           "&#x0222E;" ><!--CONTOUR INTEGRAL -->
+<!ENTITY cup              "&#x0222A;" ><!--UNION -->
+<!ENTITY Dot              "&#x000A8;" ><!--DIAERESIS -->
+<!ENTITY DotDot           " &#x020DC;" ><!--COMBINING FOUR DOTS ABOVE -->
+<!ENTITY equiv            "&#x02261;" ><!--IDENTICAL TO -->
+<!ENTITY exist            "&#x02203;" ><!--THERE EXISTS -->
+<!ENTITY fnof             "&#x00192;" ><!--LATIN SMALL LETTER F WITH HOOK -->
+<!ENTITY forall           "&#x02200;" ><!--FOR ALL -->
+<!ENTITY ge               "&#x02265;" ><!--GREATER-THAN OR EQUAL TO -->
+<!ENTITY hamilt           "&#x0210B;" ><!--SCRIPT CAPITAL H -->
+<!ENTITY iff              "&#x021D4;" ><!--LEFT RIGHT DOUBLE ARROW -->
+<!ENTITY infin            "&#x0221E;" ><!--INFINITY -->
+<!ENTITY int              "&#x0222B;" ><!--INTEGRAL -->
+<!ENTITY isin             "&#x02208;" ><!--ELEMENT OF -->
+<!ENTITY lagran           "&#x02112;" ><!--SCRIPT CAPITAL L -->
+<!ENTITY lang             "&#x02329;" ><!--LEFT-POINTING ANGLE BRACKET -->
+<!ENTITY lArr             "&#x021D0;" ><!--LEFTWARDS DOUBLE ARROW -->
+<!ENTITY le               "&#x02264;" ><!--LESS-THAN OR EQUAL TO -->
+<!ENTITY lowast           "&#x02217;" ><!--ASTERISK OPERATOR -->
+<!ENTITY minus            "&#x02212;" ><!--MINUS SIGN -->
+<!ENTITY mnplus           "&#x02213;" ><!--MINUS-OR-PLUS SIGN -->
+<!ENTITY nabla            "&#x02207;" ><!--NABLA -->
+<!ENTITY ne               "&#x02260;" ><!--NOT EQUAL TO -->
+<!ENTITY ni               "&#x0220B;" ><!--CONTAINS AS MEMBER -->
+<!ENTITY notin            "&#x02209;" ><!--NOT AN ELEMENT OF -->
+<!ENTITY or               "&#x02228;" ><!--LOGICAL OR -->
+<!ENTITY order            "&#x02134;" ><!--SCRIPT SMALL O -->
+<!ENTITY par              "&#x02225;" ><!--PARALLEL TO -->
+<!ENTITY part             "&#x02202;" ><!--PARTIAL DIFFERENTIAL -->
+<!ENTITY permil           "&#x02030;" ><!--PER MILLE SIGN -->
+<!ENTITY perp             "&#x022A5;" ><!--UP TACK -->
+<!ENTITY phmmat           "&#x02133;" ><!--SCRIPT CAPITAL M -->
+<!ENTITY Prime            "&#x02033;" ><!--DOUBLE PRIME -->
+<!ENTITY prime            "&#x02032;" ><!--PRIME -->
+<!ENTITY prop             "&#x0221D;" ><!--PROPORTIONAL TO -->
+<!ENTITY radic            "&#x0221A;" ><!--SQUARE ROOT -->
+<!ENTITY rang             "&#x0232A;" ><!--RIGHT-POINTING ANGLE BRACKET -->
+<!ENTITY rArr             "&#x021D2;" ><!--RIGHTWARDS DOUBLE ARROW -->
+<!ENTITY sim              "&#x0223C;" ><!--TILDE OPERATOR -->
+<!ENTITY sime             "&#x02243;" ><!--ASYMPTOTICALLY EQUAL TO -->
+<!ENTITY square           "&#x025A1;" ><!--WHITE SQUARE -->
+<!ENTITY sub              "&#x02282;" ><!--SUBSET OF -->
+<!ENTITY sube             "&#x02286;" ><!--SUBSET OF OR EQUAL TO -->
+<!ENTITY sup              "&#x02283;" ><!--SUPERSET OF -->
+<!ENTITY supe             "&#x02287;" ><!--SUPERSET OF OR EQUAL TO -->
+<!ENTITY tdot             " &#x020DB;" ><!--COMBINING THREE DOTS ABOVE -->
+<!ENTITY there4           "&#x02234;" ><!--THEREFORE -->
+<!ENTITY tprime           "&#x02034;" ><!--TRIPLE PRIME -->
+<!ENTITY Verbar           "&#x02016;" ><!--DOUBLE VERTICAL LINE -->
+<!ENTITY wedgeq           "&#x02259;" ><!--ESTIMATES -->
diff --git a/contrib/entitynorm/entities/xhtml-lat1.ent b/contrib/entitynorm/entities/xhtml-lat1.ent
new file mode 100644
index 0000000..aaae738
--- /dev/null
+++ b/contrib/entitynorm/entities/xhtml-lat1.ent
@@ -0,0 +1,196 @@
+<!-- Portions (C) International Organization for Standardization 1986
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+<!-- Character entity set. Typical invocation:
+    <!ENTITY % HTMLlat1 PUBLIC
+       "-//W3C//ENTITIES Latin 1 for XHTML//EN"
+       "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent">
+    %HTMLlat1;
+-->
+
+<!ENTITY nbsp   "&#160;"> <!-- no-break space = non-breaking space,
+                                  U+00A0 ISOnum -->
+<!ENTITY iexcl  "&#161;"> <!-- inverted exclamation mark, U+00A1 ISOnum -->
+<!ENTITY cent   "&#162;"> <!-- cent sign, U+00A2 ISOnum -->
+<!ENTITY pound  "&#163;"> <!-- pound sign, U+00A3 ISOnum -->
+<!ENTITY curren "&#164;"> <!-- currency sign, U+00A4 ISOnum -->
+<!ENTITY yen    "&#165;"> <!-- yen sign = yuan sign, U+00A5 ISOnum -->
+<!ENTITY brvbar "&#166;"> <!-- broken bar = broken vertical bar,
+                                  U+00A6 ISOnum -->
+<!ENTITY sect   "&#167;"> <!-- section sign, U+00A7 ISOnum -->
+<!ENTITY uml    "&#168;"> <!-- diaeresis = spacing diaeresis,
+                                  U+00A8 ISOdia -->
+<!ENTITY copy   "&#169;"> <!-- copyright sign, U+00A9 ISOnum -->
+<!ENTITY ordf   "&#170;"> <!-- feminine ordinal indicator, U+00AA ISOnum -->
+<!ENTITY laquo  "&#171;"> <!-- left-pointing double angle quotation mark
+                                  = left pointing guillemet, U+00AB ISOnum -->
+<!ENTITY not    "&#172;"> <!-- not sign = discretionary hyphen,
+                                  U+00AC ISOnum -->
+<!ENTITY shy    "&#173;"> <!-- soft hyphen = discretionary hyphen,
+                                  U+00AD ISOnum -->
+<!ENTITY reg    "&#174;"> <!-- registered sign = registered trade mark sign,
+                                  U+00AE ISOnum -->
+<!ENTITY macr   "&#175;"> <!-- macron = spacing macron = overline
+                                  = APL overbar, U+00AF ISOdia -->
+<!ENTITY deg    "&#176;"> <!-- degree sign, U+00B0 ISOnum -->
+<!ENTITY plusmn "&#177;"> <!-- plus-minus sign = plus-or-minus sign,
+                                  U+00B1 ISOnum -->
+<!ENTITY sup2   "&#178;"> <!-- superscript two = superscript digit two
+                                  = squared, U+00B2 ISOnum -->
+<!ENTITY sup3   "&#179;"> <!-- superscript three = superscript digit three
+                                  = cubed, U+00B3 ISOnum -->
+<!ENTITY acute  "&#180;"> <!-- acute accent = spacing acute,
+                                  U+00B4 ISOdia -->
+<!ENTITY micro  "&#181;"> <!-- micro sign, U+00B5 ISOnum -->
+<!ENTITY para   "&#182;"> <!-- pilcrow sign = paragraph sign,
+                                  U+00B6 ISOnum -->
+<!ENTITY middot "&#183;"> <!-- middle dot = Georgian comma
+                                  = Greek middle dot, U+00B7 ISOnum -->
+<!ENTITY cedil  "&#184;"> <!-- cedilla = spacing cedilla, U+00B8 ISOdia -->
+<!ENTITY sup1   "&#185;"> <!-- superscript one = superscript digit one,
+                                  U+00B9 ISOnum -->
+<!ENTITY ordm   "&#186;"> <!-- masculine ordinal indicator,
+                                  U+00BA ISOnum -->
+<!ENTITY raquo  "&#187;"> <!-- right-pointing double angle quotation mark
+                                  = right pointing guillemet, U+00BB ISOnum -->
+<!ENTITY frac14 "&#188;"> <!-- vulgar fraction one quarter
+                                  = fraction one quarter, U+00BC ISOnum -->
+<!ENTITY frac12 "&#189;"> <!-- vulgar fraction one half
+                                  = fraction one half, U+00BD ISOnum -->
+<!ENTITY frac34 "&#190;"> <!-- vulgar fraction three quarters
+                                  = fraction three quarters, U+00BE ISOnum -->
+<!ENTITY iquest "&#191;"> <!-- inverted question mark
+                                  = turned question mark, U+00BF ISOnum -->
+<!ENTITY Agrave "&#192;"> <!-- latin capital letter A with grave
+                                  = latin capital letter A grave,
+                                  U+00C0 ISOlat1 -->
+<!ENTITY Aacute "&#193;"> <!-- latin capital letter A with acute,
+                                  U+00C1 ISOlat1 -->
+<!ENTITY Acirc  "&#194;"> <!-- latin capital letter A with circumflex,
+                                  U+00C2 ISOlat1 -->
+<!ENTITY Atilde "&#195;"> <!-- latin capital letter A with tilde,
+                                  U+00C3 ISOlat1 -->
+<!ENTITY Auml   "&#196;"> <!-- latin capital letter A with diaeresis,
+                                  U+00C4 ISOlat1 -->
+<!ENTITY Aring  "&#197;"> <!-- latin capital letter A with ring above
+                                  = latin capital letter A ring,
+                                  U+00C5 ISOlat1 -->
+<!ENTITY AElig  "&#198;"> <!-- latin capital letter AE
+                                  = latin capital ligature AE,
+                                  U+00C6 ISOlat1 -->
+<!ENTITY Ccedil "&#199;"> <!-- latin capital letter C with cedilla,
+                                  U+00C7 ISOlat1 -->
+<!ENTITY Egrave "&#200;"> <!-- latin capital letter E with grave,
+                                  U+00C8 ISOlat1 -->
+<!ENTITY Eacute "&#201;"> <!-- latin capital letter E with acute,
+                                  U+00C9 ISOlat1 -->
+<!ENTITY Ecirc  "&#202;"> <!-- latin capital letter E with circumflex,
+                                  U+00CA ISOlat1 -->
+<!ENTITY Euml   "&#203;"> <!-- latin capital letter E with diaeresis,
+                                  U+00CB ISOlat1 -->
+<!ENTITY Igrave "&#204;"> <!-- latin capital letter I with grave,
+                                  U+00CC ISOlat1 -->
+<!ENTITY Iacute "&#205;"> <!-- latin capital letter I with acute,
+                                  U+00CD ISOlat1 -->
+<!ENTITY Icirc  "&#206;"> <!-- latin capital letter I with circumflex,
+                                  U+00CE ISOlat1 -->
+<!ENTITY Iuml   "&#207;"> <!-- latin capital letter I with diaeresis,
+                                  U+00CF ISOlat1 -->
+<!ENTITY ETH    "&#208;"> <!-- latin capital letter ETH, U+00D0 ISOlat1 -->
+<!ENTITY Ntilde "&#209;"> <!-- latin capital letter N with tilde,
+                                  U+00D1 ISOlat1 -->
+<!ENTITY Ograve "&#210;"> <!-- latin capital letter O with grave,
+                                  U+00D2 ISOlat1 -->
+<!ENTITY Oacute "&#211;"> <!-- latin capital letter O with acute,
+                                  U+00D3 ISOlat1 -->
+<!ENTITY Ocirc  "&#212;"> <!-- latin capital letter O with circumflex,
+                                  U+00D4 ISOlat1 -->
+<!ENTITY Otilde "&#213;"> <!-- latin capital letter O with tilde,
+                                  U+00D5 ISOlat1 -->
+<!ENTITY Ouml   "&#214;"> <!-- latin capital letter O with diaeresis,
+                                  U+00D6 ISOlat1 -->
+<!ENTITY times  "&#215;"> <!-- multiplication sign, U+00D7 ISOnum -->
+<!ENTITY Oslash "&#216;"> <!-- latin capital letter O with stroke
+                                  = latin capital letter O slash,
+                                  U+00D8 ISOlat1 -->
+<!ENTITY Ugrave "&#217;"> <!-- latin capital letter U with grave,
+                                  U+00D9 ISOlat1 -->
+<!ENTITY Uacute "&#218;"> <!-- latin capital letter U with acute,
+                                  U+00DA ISOlat1 -->
+<!ENTITY Ucirc  "&#219;"> <!-- latin capital letter U with circumflex,
+                                  U+00DB ISOlat1 -->
+<!ENTITY Uuml   "&#220;"> <!-- latin capital letter U with diaeresis,
+                                  U+00DC ISOlat1 -->
+<!ENTITY Yacute "&#221;"> <!-- latin capital letter Y with acute,
+                                  U+00DD ISOlat1 -->
+<!ENTITY THORN  "&#222;"> <!-- latin capital letter THORN,
+                                  U+00DE ISOlat1 -->
+<!ENTITY szlig  "&#223;"> <!-- latin small letter sharp s = ess-zed,
+                                  U+00DF ISOlat1 -->
+<!ENTITY agrave "&#224;"> <!-- latin small letter a with grave
+                                  = latin small letter a grave,
+                                  U+00E0 ISOlat1 -->
+<!ENTITY aacute "&#225;"> <!-- latin small letter a with acute,
+                                  U+00E1 ISOlat1 -->
+<!ENTITY acirc  "&#226;"> <!-- latin small letter a with circumflex,
+                                  U+00E2 ISOlat1 -->
+<!ENTITY atilde "&#227;"> <!-- latin small letter a with tilde,
+                                  U+00E3 ISOlat1 -->
+<!ENTITY auml   "&#228;"> <!-- latin small letter a with diaeresis,
+                                  U+00E4 ISOlat1 -->
+<!ENTITY aring  "&#229;"> <!-- latin small letter a with ring above
+                                  = latin small letter a ring,
+                                  U+00E5 ISOlat1 -->
+<!ENTITY aelig  "&#230;"> <!-- latin small letter ae
+                                  = latin small ligature ae, U+00E6 ISOlat1 -->
+<!ENTITY ccedil "&#231;"> <!-- latin small letter c with cedilla,
+                                  U+00E7 ISOlat1 -->
+<!ENTITY egrave "&#232;"> <!-- latin small letter e with grave,
+                                  U+00E8 ISOlat1 -->
+<!ENTITY eacute "&#233;"> <!-- latin small letter e with acute,
+                                  U+00E9 ISOlat1 -->
+<!ENTITY ecirc  "&#234;"> <!-- latin small letter e with circumflex,
+                                  U+00EA ISOlat1 -->
+<!ENTITY euml   "&#235;"> <!-- latin small letter e with diaeresis,
+                                  U+00EB ISOlat1 -->
+<!ENTITY igrave "&#236;"> <!-- latin small letter i with grave,
+                                  U+00EC ISOlat1 -->
+<!ENTITY iacute "&#237;"> <!-- latin small letter i with acute,
+                                  U+00ED ISOlat1 -->
+<!ENTITY icirc  "&#238;"> <!-- latin small letter i with circumflex,
+                                  U+00EE ISOlat1 -->
+<!ENTITY iuml   "&#239;"> <!-- latin small letter i with diaeresis,
+                                  U+00EF ISOlat1 -->
+<!ENTITY eth    "&#240;"> <!-- latin small letter eth, U+00F0 ISOlat1 -->
+<!ENTITY ntilde "&#241;"> <!-- latin small letter n with tilde,
+                                  U+00F1 ISOlat1 -->
+<!ENTITY ograve "&#242;"> <!-- latin small letter o with grave,
+                                  U+00F2 ISOlat1 -->
+<!ENTITY oacute "&#243;"> <!-- latin small letter o with acute,
+                                  U+00F3 ISOlat1 -->
+<!ENTITY ocirc  "&#244;"> <!-- latin small letter o with circumflex,
+                                  U+00F4 ISOlat1 -->
+<!ENTITY otilde "&#245;"> <!-- latin small letter o with tilde,
+                                  U+00F5 ISOlat1 -->
+<!ENTITY ouml   "&#246;"> <!-- latin small letter o with diaeresis,
+                                  U+00F6 ISOlat1 -->
+<!ENTITY divide "&#247;"> <!-- division sign, U+00F7 ISOnum -->
+<!ENTITY oslash "&#248;"> <!-- latin small letter o with stroke,
+                                  = latin small letter o slash,
+                                  U+00F8 ISOlat1 -->
+<!ENTITY ugrave "&#249;"> <!-- latin small letter u with grave,
+                                  U+00F9 ISOlat1 -->
+<!ENTITY uacute "&#250;"> <!-- latin small letter u with acute,
+                                  U+00FA ISOlat1 -->
+<!ENTITY ucirc  "&#251;"> <!-- latin small letter u with circumflex,
+                                  U+00FB ISOlat1 -->
+<!ENTITY uuml   "&#252;"> <!-- latin small letter u with diaeresis,
+                                  U+00FC ISOlat1 -->
+<!ENTITY yacute "&#253;"> <!-- latin small letter y with acute,
+                                  U+00FD ISOlat1 -->
+<!ENTITY thorn  "&#254;"> <!-- latin small letter thorn with,
+                                  U+00FE ISOlat1 -->
+<!ENTITY yuml   "&#255;"> <!-- latin small letter y with diaeresis,
+                                  U+00FF ISOlat1 -->
diff --git a/contrib/entitynorm/entities/xhtml-special.ent b/contrib/entitynorm/entities/xhtml-special.ent
new file mode 100644
index 0000000..cf709d1
--- /dev/null
+++ b/contrib/entitynorm/entities/xhtml-special.ent
@@ -0,0 +1,79 @@
+<!-- Special characters for HTML -->
+
+<!-- Character entity set. Typical invocation:
+     <!ENTITY % HTMLspecial PUBLIC
+        "-//W3C//ENTITIES Special for XHTML//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent">
+     %HTMLspecial;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+     New names (i.e., not in ISO 8879 list) do not clash with any
+     existing ISO 8879 entity names. ISO 10646 character numbers
+     are given for each character, in hex. values are decimal
+     conversions of the ISO 10646 values and refer to the document
+     character set. Names are Unicode names. 
+-->
+
+<!-- C0 Controls and Basic Latin -->
+<!ENTITY quot    "&#34;"> <!--  quotation mark = APL quote,
+                                    U+0022 ISOnum -->
+<!ENTITY amp     "&#38;#38;"> <!--  ampersand, U+0026 ISOnum -->
+<!ENTITY lt      "&#38;#60;"> <!--  less-than sign, U+003C ISOnum -->
+<!ENTITY gt      "&#62;"> <!--  greater-than sign, U+003E ISOnum -->
+<!ENTITY apos	 "&#39;"> <!--  apostrophe mark, U+0027 ISOnum -->
+
+<!-- Latin Extended-A -->
+<!ENTITY OElig   "&#338;"> <!--  latin capital ligature OE,
+                                    U+0152 ISOlat2 -->
+<!ENTITY oelig   "&#339;"> <!--  latin small ligature oe, U+0153 ISOlat2 -->
+<!-- ligature is a misnomer, this is a separate character in some languages -->
+<!ENTITY Scaron  "&#352;"> <!--  latin capital letter S with caron,
+                                    U+0160 ISOlat2 -->
+<!ENTITY scaron  "&#353;"> <!--  latin small letter s with caron,
+                                    U+0161 ISOlat2 -->
+<!ENTITY Yuml    "&#376;"> <!--  latin capital letter Y with diaeresis,
+                                    U+0178 ISOlat2 -->
+
+<!-- Spacing Modifier Letters -->
+<!ENTITY circ    "&#710;"> <!--  modifier letter circumflex accent,
+                                    U+02C6 ISOpub -->
+<!ENTITY tilde   "&#732;"> <!--  small tilde, U+02DC ISOdia -->
+
+<!-- General Punctuation -->
+<!ENTITY ensp    "&#8194;"> <!-- en space, U+2002 ISOpub -->
+<!ENTITY emsp    "&#8195;"> <!-- em space, U+2003 ISOpub -->
+<!ENTITY thinsp  "&#8201;"> <!-- thin space, U+2009 ISOpub -->
+<!ENTITY zwnj    "&#8204;"> <!-- zero width non-joiner,
+                                    U+200C NEW RFC 2070 -->
+<!ENTITY zwj     "&#8205;"> <!-- zero width joiner, U+200D NEW RFC 2070 -->
+<!ENTITY lrm     "&#8206;"> <!-- left-to-right mark, U+200E NEW RFC 2070 -->
+<!ENTITY rlm     "&#8207;"> <!-- right-to-left mark, U+200F NEW RFC 2070 -->
+<!ENTITY ndash   "&#8211;"> <!-- en dash, U+2013 ISOpub -->
+<!ENTITY mdash   "&#8212;"> <!-- em dash, U+2014 ISOpub -->
+<!ENTITY lsquo   "&#8216;"> <!-- left single quotation mark,
+                                    U+2018 ISOnum -->
+<!ENTITY rsquo   "&#8217;"> <!-- right single quotation mark,
+                                    U+2019 ISOnum -->
+<!ENTITY sbquo   "&#8218;"> <!-- single low-9 quotation mark, U+201A NEW -->
+<!ENTITY ldquo   "&#8220;"> <!-- left double quotation mark,
+                                    U+201C ISOnum -->
+<!ENTITY rdquo   "&#8221;"> <!-- right double quotation mark,
+                                    U+201D ISOnum -->
+<!ENTITY bdquo   "&#8222;"> <!-- double low-9 quotation mark, U+201E NEW -->
+<!ENTITY dagger  "&#8224;"> <!-- dagger, U+2020 ISOpub -->
+<!ENTITY Dagger  "&#8225;"> <!-- double dagger, U+2021 ISOpub -->
+<!ENTITY permil  "&#8240;"> <!-- per mille sign, U+2030 ISOtech -->
+<!ENTITY lsaquo  "&#8249;"> <!-- single left-pointing angle quotation mark,
+                                    U+2039 ISO proposed -->
+<!-- lsaquo is proposed but not yet ISO standardized -->
+<!ENTITY rsaquo  "&#8250;"> <!-- single right-pointing angle quotation mark,
+                                    U+203A ISO proposed -->
+<!-- rsaquo is proposed but not yet ISO standardized -->
+<!ENTITY euro   "&#8364;"> <!--  euro sign, U+20AC NEW -->
diff --git a/contrib/entitynorm/entities/xhtml-symbol.ent b/contrib/entitynorm/entities/xhtml-symbol.ent
new file mode 100644
index 0000000..16f876b
--- /dev/null
+++ b/contrib/entitynorm/entities/xhtml-symbol.ent
@@ -0,0 +1,242 @@
+<!-- Mathematical, Greek and Symbolic characters for HTML -->
+
+<!-- Character entity set. Typical invocation:
+     <!ENTITY % HTMLsymbol PUBLIC
+        "-//W3C//ENTITIES Symbols for XHTML//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent">
+     %HTMLsymbol;
+-->
+
+<!-- Portions (C) International Organization for Standardization 1986:
+     Permission to copy in any form is granted for use with
+     conforming SGML systems and applications as defined in
+     ISO 8879, provided this notice is included in all copies.
+-->
+
+<!-- Relevant ISO entity set is given unless names are newly introduced.
+     New names (i.e., not in ISO 8879 list) do not clash with any
+     existing ISO 8879 entity names. ISO 10646 character numbers
+     are given for each character, in hex. values are decimal
+     conversions of the ISO 10646 values and refer to the document
+     character set. Names are Unicode names. 
+-->
+
+<!-- Latin Extended-B -->
+<!ENTITY fnof     "&#402;"> <!-- latin small f with hook = function
+                                    = florin, U+0192 ISOtech -->
+
+<!-- Greek -->
+<!ENTITY Alpha    "&#913;"> <!-- greek capital letter alpha, U+0391 -->
+<!ENTITY Beta     "&#914;"> <!-- greek capital letter beta, U+0392 -->
+<!ENTITY Gamma    "&#915;"> <!-- greek capital letter gamma,
+                                    U+0393 ISOgrk3 -->
+<!ENTITY Delta    "&#916;"> <!-- greek capital letter delta,
+                                    U+0394 ISOgrk3 -->
+<!ENTITY Epsilon  "&#917;"> <!-- greek capital letter epsilon, U+0395 -->
+<!ENTITY Zeta     "&#918;"> <!-- greek capital letter zeta, U+0396 -->
+<!ENTITY Eta      "&#919;"> <!-- greek capital letter eta, U+0397 -->
+<!ENTITY Theta    "&#920;"> <!-- greek capital letter theta,
+                                    U+0398 ISOgrk3 -->
+<!ENTITY Iota     "&#921;"> <!-- greek capital letter iota, U+0399 -->
+<!ENTITY Kappa    "&#922;"> <!-- greek capital letter kappa, U+039A -->
+<!ENTITY Lambda   "&#923;"> <!-- greek capital letter lambda,
+                                    U+039B ISOgrk3 -->
+<!ENTITY Mu       "&#924;"> <!-- greek capital letter mu, U+039C -->
+<!ENTITY Nu       "&#925;"> <!-- greek capital letter nu, U+039D -->
+<!ENTITY Xi       "&#926;"> <!-- greek capital letter xi, U+039E ISOgrk3 -->
+<!ENTITY Omicron  "&#927;"> <!-- greek capital letter omicron, U+039F -->
+<!ENTITY Pi       "&#928;"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->
+<!ENTITY Rho      "&#929;"> <!-- greek capital letter rho, U+03A1 -->
+<!-- there is no Sigmaf, and no U+03A2 character either -->
+<!ENTITY Sigma    "&#931;"> <!-- greek capital letter sigma,
+                                    U+03A3 ISOgrk3 -->
+<!ENTITY Tau      "&#932;"> <!-- greek capital letter tau, U+03A4 -->
+<!ENTITY Upsilon  "&#933;"> <!-- greek capital letter upsilon,
+                                    U+03A5 ISOgrk3 -->
+<!ENTITY Phi      "&#934;"> <!-- greek capital letter phi,
+                                    U+03A6 ISOgrk3 -->
+<!ENTITY Chi      "&#935;"> <!-- greek capital letter chi, U+03A7 -->
+<!ENTITY Psi      "&#936;"> <!-- greek capital letter psi,
+                                    U+03A8 ISOgrk3 -->
+<!ENTITY Omega    "&#937;"> <!-- greek capital letter omega,
+                                    U+03A9 ISOgrk3 -->
+
+<!ENTITY alpha    "&#945;"> <!-- greek small letter alpha,
+                                    U+03B1 ISOgrk3 -->
+<!ENTITY beta     "&#946;"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->
+<!ENTITY gamma    "&#947;"> <!-- greek small letter gamma,
+                                    U+03B3 ISOgrk3 -->
+<!ENTITY delta    "&#948;"> <!-- greek small letter delta,
+                                    U+03B4 ISOgrk3 -->
+<!ENTITY epsilon  "&#949;"> <!-- greek small letter epsilon,
+                                    U+03B5 ISOgrk3 -->
+<!ENTITY zeta     "&#950;"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->
+<!ENTITY eta      "&#951;"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->
+<!ENTITY theta    "&#952;"> <!-- greek small letter theta,
+                                    U+03B8 ISOgrk3 -->
+<!ENTITY iota     "&#953;"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->
+<!ENTITY kappa    "&#954;"> <!-- greek small letter kappa,
+                                    U+03BA ISOgrk3 -->
+<!ENTITY lambda   "&#955;"> <!-- greek small letter lambda,
+                                    U+03BB ISOgrk3 -->
+<!ENTITY mu       "&#956;"> <!-- greek small letter mu, U+03BC ISOgrk3 -->
+<!ENTITY nu       "&#957;"> <!-- greek small letter nu, U+03BD ISOgrk3 -->
+<!ENTITY xi       "&#958;"> <!-- greek small letter xi, U+03BE ISOgrk3 -->
+<!ENTITY omicron  "&#959;"> <!-- greek small letter omicron, U+03BF NEW -->
+<!ENTITY pi       "&#960;"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->
+<!ENTITY rho      "&#961;"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->
+<!ENTITY sigmaf   "&#962;"> <!-- greek small letter final sigma,
+                                    U+03C2 ISOgrk3 -->
+<!ENTITY sigma    "&#963;"> <!-- greek small letter sigma,
+                                    U+03C3 ISOgrk3 -->
+<!ENTITY tau      "&#964;"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->
+<!ENTITY upsilon  "&#965;"> <!-- greek small letter upsilon,
+                                    U+03C5 ISOgrk3 -->
+<!ENTITY phi      "&#966;"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->
+<!ENTITY chi      "&#967;"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->
+<!ENTITY psi      "&#968;"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->
+<!ENTITY omega    "&#969;"> <!-- greek small letter omega,
+                                    U+03C9 ISOgrk3 -->
+<!ENTITY thetasym "&#977;"> <!-- greek small letter theta symbol,
+                                    U+03D1 NEW -->
+<!ENTITY upsih    "&#978;"> <!-- greek upsilon with hook symbol,
+                                    U+03D2 NEW -->
+<!ENTITY piv      "&#982;"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->
+
+<!-- General Punctuation -->
+<!ENTITY bull     "&#8226;"> <!-- bullet = black small circle,
+                                     U+2022 ISOpub  -->
+<!-- bullet is NOT the same as bullet operator, U+2219 -->
+<!ENTITY hellip   "&#8230;"> <!-- horizontal ellipsis = three dot leader,
+                                     U+2026 ISOpub  -->
+<!ENTITY prime    "&#8242;"> <!-- prime = minutes = feet, U+2032 ISOtech -->
+<!ENTITY Prime    "&#8243;"> <!-- double prime = seconds = inches,
+                                     U+2033 ISOtech -->
+<!ENTITY oline    "&#8254;"> <!-- overline = spacing overscore,
+                                     U+203E NEW -->
+<!ENTITY frasl    "&#8260;"> <!-- fraction slash, U+2044 NEW -->
+
+<!-- Letterlike Symbols -->
+<!ENTITY weierp   "&#8472;"> <!-- script capital P = power set
+                                     = Weierstrass p, U+2118 ISOamso -->
+<!ENTITY image    "&#8465;"> <!-- blackletter capital I = imaginary part,
+                                     U+2111 ISOamso -->
+<!ENTITY real     "&#8476;"> <!-- blackletter capital R = real part symbol,
+                                     U+211C ISOamso -->
+<!ENTITY trade    "&#8482;"> <!-- trade mark sign, U+2122 ISOnum -->
+<!ENTITY alefsym  "&#8501;"> <!-- alef symbol = first transfinite cardinal,
+                                     U+2135 NEW -->
+<!-- alef symbol is NOT the same as hebrew letter alef,
+     U+05D0 although the same glyph could be used to depict both characters -->
+
+<!-- Arrows -->
+<!ENTITY larr     "&#8592;"> <!-- leftwards arrow, U+2190 ISOnum -->
+<!ENTITY uarr     "&#8593;"> <!-- upwards arrow, U+2191 ISOnum-->
+<!ENTITY rarr     "&#8594;"> <!-- rightwards arrow, U+2192 ISOnum -->
+<!ENTITY darr     "&#8595;"> <!-- downwards arrow, U+2193 ISOnum -->
+<!ENTITY harr     "&#8596;"> <!-- left right arrow, U+2194 ISOamsa -->
+<!ENTITY crarr    "&#8629;"> <!-- downwards arrow with corner leftwards
+                                     = carriage return, U+21B5 NEW -->
+<!ENTITY lArr     "&#8656;"> <!-- leftwards double arrow, U+21D0 ISOtech -->
+<!-- Unicode does not say that lArr is the same as the 'is implied by' arrow
+    but also does not have any other character for that function. So ? lArr can
+    be used for 'is implied by' as ISOtech suggests -->
+<!ENTITY uArr     "&#8657;"> <!-- upwards double arrow, U+21D1 ISOamsa -->
+<!ENTITY rArr     "&#8658;"> <!-- rightwards double arrow,
+                                     U+21D2 ISOtech -->
+<!-- Unicode does not say this is the 'implies' character but does not have 
+     another character with this function so ?
+     rArr can be used for 'implies' as ISOtech suggests -->
+<!ENTITY dArr     "&#8659;"> <!-- downwards double arrow, U+21D3 ISOamsa -->
+<!ENTITY hArr     "&#8660;"> <!-- left right double arrow,
+                                     U+21D4 ISOamsa -->
+
+<!-- Mathematical Operators -->
+<!ENTITY forall   "&#8704;"> <!-- for all, U+2200 ISOtech -->
+<!ENTITY part     "&#8706;"> <!-- partial differential, U+2202 ISOtech  -->
+<!ENTITY exist    "&#8707;"> <!-- there exists, U+2203 ISOtech -->
+<!ENTITY empty    "&#8709;"> <!-- empty set = null set = diameter,
+                                     U+2205 ISOamso -->
+<!ENTITY nabla    "&#8711;"> <!-- nabla = backward difference,
+                                     U+2207 ISOtech -->
+<!ENTITY isin     "&#8712;"> <!-- element of, U+2208 ISOtech -->
+<!ENTITY notin    "&#8713;"> <!-- not an element of, U+2209 ISOtech -->
+<!ENTITY ni       "&#8715;"> <!-- contains as member, U+220B ISOtech -->
+<!-- should there be a more memorable name than 'ni'? -->
+<!ENTITY prod     "&#8719;"> <!-- n-ary product = product sign,
+                                     U+220F ISOamsb -->
+<!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though
+     the same glyph might be used for both -->
+<!ENTITY sum      "&#8721;"> <!-- n-ary sumation, U+2211 ISOamsb -->
+<!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'
+     though the same glyph might be used for both -->
+<!ENTITY minus    "&#8722;"> <!-- minus sign, U+2212 ISOtech -->
+<!ENTITY lowast   "&#8727;"> <!-- asterisk operator, U+2217 ISOtech -->
+<!ENTITY radic    "&#8730;"> <!-- square root = radical sign,
+                                     U+221A ISOtech -->
+<!ENTITY prop     "&#8733;"> <!-- proportional to, U+221D ISOtech -->
+<!ENTITY infin    "&#8734;"> <!-- infinity, U+221E ISOtech -->
+<!ENTITY ang      "&#8736;"> <!-- angle, U+2220 ISOamso -->
+<!ENTITY and      "&#8743;"> <!-- logical and = wedge, U+2227 ISOtech -->
+<!ENTITY or       "&#8744;"> <!-- logical or = vee, U+2228 ISOtech -->
+<!ENTITY cap      "&#8745;"> <!-- intersection = cap, U+2229 ISOtech -->
+<!ENTITY cup      "&#8746;"> <!-- union = cup, U+222A ISOtech -->
+<!ENTITY int      "&#8747;"> <!-- integral, U+222B ISOtech -->
+<!ENTITY there4   "&#8756;"> <!-- therefore, U+2234 ISOtech -->
+<!ENTITY sim      "&#8764;"> <!-- tilde operator = varies with = similar to,
+                                     U+223C ISOtech -->
+<!-- tilde operator is NOT the same character as the tilde, U+007E,
+     although the same glyph might be used to represent both  -->
+<!ENTITY cong     "&#8773;"> <!-- approximately equal to, U+2245 ISOtech -->
+<!ENTITY asymp    "&#8776;"> <!-- almost equal to = asymptotic to,
+                                     U+2248 ISOamsr -->
+<!ENTITY ne       "&#8800;"> <!-- not equal to, U+2260 ISOtech -->
+<!ENTITY equiv    "&#8801;"> <!-- identical to, U+2261 ISOtech -->
+<!ENTITY le       "&#8804;"> <!-- less-than or equal to, U+2264 ISOtech -->
+<!ENTITY ge       "&#8805;"> <!-- greater-than or equal to,
+                                     U+2265 ISOtech -->
+<!ENTITY sub      "&#8834;"> <!-- subset of, U+2282 ISOtech -->
+<!ENTITY sup      "&#8835;"> <!-- superset of, U+2283 ISOtech -->
+<!-- note that nsup, 'not a superset of, U+2283' is not covered by the Symbol 
+     font encoding and is not included. Should it be, for symmetry?
+     It is in ISOamsn  --> 
+<!ENTITY nsub     "&#8836;"> <!-- not a subset of, U+2284 ISOamsn -->
+<!ENTITY sube     "&#8838;"> <!-- subset of or equal to, U+2286 ISOtech -->
+<!ENTITY supe     "&#8839;"> <!-- superset of or equal to,
+                                     U+2287 ISOtech -->
+<!ENTITY oplus    "&#8853;"> <!-- circled plus = direct sum,
+                                     U+2295 ISOamsb -->
+<!ENTITY otimes   "&#8855;"> <!-- circled times = vector product,
+                                     U+2297 ISOamsb -->
+<!ENTITY perp     "&#8869;"> <!-- up tack = orthogonal to = perpendicular,
+                                     U+22A5 ISOtech -->
+<!ENTITY sdot     "&#8901;"> <!-- dot operator, U+22C5 ISOamsb -->
+<!-- dot operator is NOT the same character as U+00B7 middle dot -->
+
+<!-- Miscellaneous Technical -->
+<!ENTITY lceil    "&#8968;"> <!-- left ceiling = apl upstile,
+                                     U+2308 ISOamsc  -->
+<!ENTITY rceil    "&#8969;"> <!-- right ceiling, U+2309 ISOamsc  -->
+<!ENTITY lfloor   "&#8970;"> <!-- left floor = apl downstile,
+                                     U+230A ISOamsc  -->
+<!ENTITY rfloor   "&#8971;"> <!-- right floor, U+230B ISOamsc  -->
+<!ENTITY lang     "&#9001;"> <!-- left-pointing angle bracket = bra,
+                                     U+2329 ISOtech -->
+<!-- lang is NOT the same character as U+003C 'less than' 
+     or U+2039 'single left-pointing angle quotation mark' -->
+<!ENTITY rang     "&#9002;"> <!-- right-pointing angle bracket = ket,
+                                     U+232A ISOtech -->
+<!-- rang is NOT the same character as U+003E 'greater than' 
+     or U+203A 'single right-pointing angle quotation mark' -->
+
+<!-- Geometric Shapes -->
+<!ENTITY loz      "&#9674;"> <!-- lozenge, U+25CA ISOpub -->
+
+<!-- Miscellaneous Symbols -->
+<!ENTITY spades   "&#9824;"> <!-- black spade suit, U+2660 ISOpub -->
+<!-- black here seems to mean filled as opposed to hollow -->
+<!ENTITY clubs    "&#9827;"> <!-- black club suit = shamrock,
+                                     U+2663 ISOpub -->
+<!ENTITY hearts   "&#9829;"> <!-- black heart suit = valentine,
+                                     U+2665 ISOpub -->
+<!ENTITY diams    "&#9830;"> <!-- black diamond suit, U+2666 ISOpub -->
diff --git a/contrib/entitynorm/entity_decl_parse.pl b/contrib/entitynorm/entity_decl_parse.pl
new file mode 100644
index 0000000..c69b929
--- /dev/null
+++ b/contrib/entitynorm/entity_decl_parse.pl
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+# (C) 2008  Török Edwin <edwin at clamav.net>
+# parse <!ENTITY declarations and output them in the format
+# used by generate_entitylist.c
+# Format is EntityName,EntityValue.
+# Only accepts entity values 0 < V < 0xffff, and doesn't accept entities that have multiple values assigned.
+while(<>) {
+	chomp;
+	if(/<!ENTITY +([^ \t]+)[ \t]+\" *([^ \"]+) *\" *>/) {
+		$name = $1;
+		$v = $2;
+		if($v =~ /^&(#38;)?#([^;]+);$/) {
+			$valx = $2;
+			my $value;
+			if($valx =~ /^x([0-9a-fA-F]+)$/) {
+				$value = hex($valx);
+				if($value > 0xffff) {
+					printf STDERR "TOOBIG $_\n"
+				} else {
+					printf "$name,%d\n", $value
+				}
+			} elsif($valx =~ /^[0-9]+$/) {
+				if($valx > 0xffff) {
+					print STDERR "TOOBIG $_\n";
+				} else {
+					printf "$name,%d\n", $valx
+				}
+			} else {
+				print "unknown1: $_\n";
+			}
+		} elsif($v =~ /^(&#x[0-9a-fA-F]+;)+$/) {
+			print STDERR "MULTIPLECHARS $name $1\n";
+		} else {
+			print "unknown2: $_\n";
+		}
+	} elsif(/.*<!ENTITY.*/) {
+		if($_ !~ /.*(PUBLIC|SYSTEM).*/) {
+			print "unknown3: $_\n";
+		}
+	}
+}
diff --git a/contrib/entitynorm/fix_dbs.sh b/contrib/entitynorm/fix_dbs.sh
new file mode 100644
index 0000000..284a9ec
--- /dev/null
+++ b/contrib/entitynorm/fix_dbs.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+export DBLOCATION=/usr/local/share/clamav
+export OUTDIR=db_temp
+
+mkdir -p /tmp/$OUTDIR
+(
+	cd /tmp/$OUTDIR
+	sigtool --unpack $DBLOCATION/main.cvd 2>/tmp/$OUTDIR/errlog
+	sigtool --unpack $DBLOCATIOn/daily.cvd 2>>/tmp/$OUTDIR/errlog
+	cp $DBLOCATION/daily.inc/* . 2>>/tmp/$OUTDIR/errlog
+	cp $DBLOCATION/main.inc/* . 2>>/tmp/$OUTDIR/errlog
+)
+
+./fixdb </tmp/$OUTDIR/main.ndb >/tmp/$OUTDIR/fixed_db 2>/tmp/$OUTDIR/errlog
+./fixdb </tmp/$OUTDIR/daily.ndb >>/tmp/$OUTDIR/fixed_db 2>>/tmp/$OUTDIR/errlog
+cat /tmp/$OUTDIR/fixed_db |./postprocessdb 1 > /tmp/$OUTDIR/fixed_db_p
+cat /tmp/$OUTDIR/fixed_db_p|./postprocessdb nocolor >/tmp/$OUTDIR/fixed.ndb
+
+echo /tmp/$OUTDIR/fixed.ndb "created"
diff --git a/contrib/entitynorm/fixdb.c b/contrib/entitynorm/fixdb.c
new file mode 100644
index 0000000..6641c52
--- /dev/null
+++ b/contrib/entitynorm/fixdb.c
@@ -0,0 +1,428 @@
+/*
+ *  Copyright (C) 2006 Török Edvin <edwin at clamav.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA 02110-1301, USA.
+ *
+ */
+
+#include <clamav-config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <clamav.h>
+#include <str.h>
+#include <ctype.h>
+#include <string.h>
+#include <others.h>
+#include <htmlnorm.h>
+
+static int dehex(int c)
+{
+	int l;
+
+    if(!isascii(c))
+    	return -1;
+
+    if(isdigit(c))
+	return c - '0';
+
+    l = tolower(c);
+    if((l >= 'a') && (l <= 'f'))
+	return l + 10 - 'a';
+
+    cli_errmsg("hex2int() translation problem (%d)\n", l);
+    return -1;
+}
+
+static const char* red = "\033[1;31m";
+static const char* blue = "\033[1;34m";
+static const char* green = "\033[1;32m";
+static const char* magenta = "\033[1;35m";
+static const char* yellow = "\033[1;33m";
+static const char* color_off = "\033[0;0m";
+
+/* TODO: for each uppercase letter add a lowercase alternative */
+static const unsigned char* normalize_sig(unsigned char* sig,size_t len)
+{
+	const unsigned char* ret = NULL;
+	const unsigned char* dir = cli_gentempdir("/tmp"); 
+	unsigned char* filenam;
+	FILE* f;
+
+	html_normalise_mem(sig, len, dir , NULL);
+
+	filenam = cli_malloc(strlen(dir)+20);
+	strcpy(filenam, dir);
+	strcat(filenam,"/");
+	strcat(filenam,"comment.html");
+
+	f = fopen(filenam,"rb");
+	if(f) {
+		long siz;
+		unsigned char* buff;
+		size_t iread;
+
+		fseek(f,0,SEEK_END);
+		siz = ftell(f);
+		buff = cli_malloc(siz);
+
+		fseek(f,0, SEEK_SET);
+
+		iread = fread(buff, 1, siz, f);
+
+		if(ferror(f))
+			perror("Error while reading file!");
+		fclose(f);
+
+		ret = cli_str2hex(buff,iread);	
+		free(buff);
+
+	}
+	else
+		cli_dbgmsg("Unable to open:%s",filenam);
+
+	free(filenam);		
+	cli_rmdirs(dir);
+
+	return ret;
+}
+
+
+static int cleanup_sig(const char* newsig, const char* sig)
+{
+	int up = 0;
+	size_t i,j;
+	cli_chomp(newsig);
+	cli_chomp(sig);
+	for(i=0, j=0;j < strlen(sig);) {
+		int new_val;
+		int old_val;
+		if(!isxdigit(newsig[i]) && !isxdigit(sig[j]) && newsig[i] == sig[j]) {
+			switch(sig[j]) {
+				case '{':
+					while (sig[j] != '}') {
+						putc(sig[j++],stdout);
+						i++;
+					}
+					putc(sig[j++],stdout);
+					i++;
+					break;
+				case '(':
+					while(sig[j] != ')') {
+						putc(sig[j++],stdout);
+						i++;
+					}
+					putc(sig[j++],stdout);
+					i++;
+					break;
+				default:
+					putc(sig[j++],stdout);
+					i++;
+					break;
+			}
+			continue;
+		}
+
+		if(isxdigit(newsig[i]) && isxdigit(newsig[i+1]) && !isxdigit(sig[j])) {
+			printf("%s%c%c%s",blue,newsig[i],newsig[i+1],color_off);
+			up = 1;
+			i += 2;
+			continue;
+		}
+
+		if(isxdigit(sig[j]) && isxdigit(sig[j+1]) && !isxdigit(newsig[i])) {
+			if( (sig[j] == '2' && sig[j+1] == '0') || (sig[j]=='2' && sig[j+1] == '6'))
+				printf("%c%c",sig[j],sig[j+1]);/* space, and ampersand is normal to be stripped before {,(... */
+			else {
+				printf("%s{-1}%s",red,color_off);
+				up = 1;
+			}
+			j += 2;
+			continue;
+		}
+
+		new_val= (dehex(newsig[i])<<4) + dehex(newsig[i+1]);
+		old_val = (dehex(sig[j])<<4) + dehex(sig[j+1]);
+
+		if(old_val != new_val || old_val==0x26 ) {/* 0x26 needs resync always*/
+			int resync_needed = 0;
+
+			if(new_val - old_val == 'a'-'A') {
+				printf("%s(%02x|%02x)%s",green,old_val,new_val,color_off);
+				up = 1;
+				i += 2;
+				j += 2;
+				continue;
+			}
+
+			switch(old_val) {
+				case 0x09:
+				case 0x0a:
+				case 0x0d:
+					printf("%s{-1}%s",blue,color_off);
+					/* TODO: check why this got stripped */
+					j += 2;
+					up = 1;
+					break;
+				case 0x20:
+					/*strip extra space*/
+					j += 2;
+					break;
+				case 0x26:
+					resync_needed = 1;					
+					break;
+				default:
+					switch(new_val) {
+						case 0x20:
+							printf("%s{-1}%s",blue,color_off);
+							i += 2;
+							/*TODO:implement*/
+							up = 1;
+							break;
+						default:
+							resync_needed = 1;
+					}
+			}/*switch old_val */
+			
+			if(resync_needed) {
+				if(old_val >= 0x80 && new_val == 0x26) {
+					int cnt = 2;
+					i += 2;
+					up = 1;
+					j += 2;
+
+					if(i < strlen(newsig)) {
+						old_val = (dehex(sig[j])<<4) + dehex(sig[j+1]);
+						new_val = (dehex(newsig[i])<<4) + dehex(newsig[i+1]);
+						if(old_val >=0x80) old_val = 0x26;
+						while(i < strlen(newsig) && new_val != 0x3b )  {
+							i += 2;
+							cnt++;
+							if(i<strlen(newsig))
+								new_val = (dehex(newsig[i])<<4) + dehex(newsig[i+1]);
+						}
+						i += 2;
+						printf("%s{1-%d}%s",red, cnt, color_off);
+					}
+				}
+				else if(old_val == '&' && new_val == '&') {
+					int cnt=0;
+					printf("26");
+					i += 2;
+					j += 2;
+					while(i < strlen(newsig) && j < strlen(sig) && old_val != ';' && new_val != ';') {						
+						old_val = (dehex(sig[j])<<4) + dehex(sig[j+1]);
+						new_val = (dehex(newsig[i])<<4) + dehex(newsig[i+1]);
+						if(old_val == new_val) {
+							printf("%02x",old_val);
+						}
+						else  {
+							printf("%s(%02x|%02x)%s",red,old_val,new_val,color_off);
+							up = 1;
+						}
+						i += 2;
+						j += 2;
+						
+					}
+					while(old_val != 0x3b && j < strlen(sig)) {
+						old_val = (dehex(sig[j])<<4) + dehex(sig[j+1]);
+						j += 2;
+						cnt++;
+					}
+					if(cnt) {
+						printf("%s{0-%d}%s",red,cnt,color_off);
+						up = 1;
+					}
+					else {
+						while(new_val != 0x3b && i < strlen(newsig)) {
+							new_val = (dehex(newsig[i])<<4)+ dehex(newsig[i+1]);
+							i += 2;
+							cnt++;
+						}
+						if(cnt) {
+							printf("%s{0-%d}%s",red,cnt+1,color_off);
+							up = 1;
+						}
+/*						else if(old_val == new_val) {
+ *						no operation needed
+						}*/
+					}
+				}
+				else if(old_val == '&') {
+					const size_t sig_len = strlen(sig);
+					int cnt = 2;
+					/*printf("%s(%02x|%02x)%s", red, old_val, new_val, color_off);
+					i += 2;*/
+					up = 1;
+					j += 2;
+					while(j < sig_len && old_val != 0x3b ) {
+							j += 2;
+							if(j < sig_len) 
+								old_val = (dehex(sig[j])<<4) + dehex(sig[j+1]);
+							cnt++; 
+					}
+					j += 2;
+					printf("%s{-%d}%s",red,cnt,color_off);
+				}
+				else if (new_val == '&') {
+					const size_t sig_len = strlen(sig);
+					int cnt = 2;
+					i += 2;
+					up = 1;
+					j += 2;
+					while(j < sig_len && old_val != 0x3b ) {
+							j += 2;
+							if(j < sig_len) 
+								old_val = (dehex(sig[j])<<4) + dehex(sig[j+1]);
+							cnt++; 
+					}
+					j += 2;
+					printf("%s{1-%d}%s",red,cnt,color_off);
+				}
+				else if(new_val - old_val == 'a' - 'A') {
+					printf("%s(%02x|%02x)%s",green,old_val,new_val,color_off);
+					i += 2;
+					up = 1;
+					j += 2;
+				}
+				else	{
+					printf("%s(%02x|%02x)%s",red, old_val,new_val,color_off);
+					i += 2;
+					up = 1;
+					j += 2;
+				}
+			}
+		}
+		else {
+			printf("%02x",old_val);
+			i += 2;
+			j += 2;
+		}
+	}
+	if(newsig[i]) {
+		printf("%s",red);
+		while(newsig[i]) {
+			putc(newsig[i++],stdout);
+			up = 1;
+		}
+		printf("%s\n",color_off);
+	}
+	return up;
+}
+
+int main(int argc,char* argv[])
+{
+	char* line=NULL;
+	size_t n;
+	size_t i;
+	cl_debug();
+	while(getline(&line,&n,stdin)!=-1) {
+
+		const char* signame = cli_strtok(line, 0, ":");
+		const char* sigtype = cli_strtok(line,1,":");
+		const char* x = cli_strtok(line,2,":");
+		const char* sig = cli_strtok(line,3,":");
+		if(sigtype[0] == '3') {
+			const size_t len = strlen(sig);
+			size_t real_len = 0;
+			size_t up_len = 0;
+			unsigned char* outbuff = cli_malloc(len);
+			unsigned char* upgraded_sig = cli_malloc(20*len);
+
+			cli_dbgmsg("Verifying signature:%s\n",signame); 
+
+			for(i=0; i < len ; i++) {
+				if(isxdigit(sig[i])) {
+					unsigned char val = (dehex(sig[i])<<4) + dehex(sig[i+1]);
+					i++;
+					outbuff[real_len++] = val;
+				}
+				else {
+					const unsigned char* up = normalize_sig(outbuff, real_len);
+					strncpy(upgraded_sig+up_len, up, strlen(up));
+					up_len += strlen(up);
+					real_len = 0;
+
+					if(sig[i] == '{') {
+						while(sig[i] != '}') {
+							upgraded_sig[up_len++] = sig[i++];
+						}
+						upgraded_sig[up_len++] = sig[i];
+					}
+					else
+						upgraded_sig[up_len++] = sig[i];
+				}
+			}
+
+			if(real_len) {
+					const unsigned char* up = normalize_sig(outbuff, real_len);
+					strncpy(upgraded_sig+up_len, up, strlen(up));
+					up_len += strlen(up);
+					real_len = 0;
+			}
+
+			upgraded_sig[up_len] = '\0';
+			printf("%s:%s:%s:",signame, sigtype, x);
+			if(cleanup_sig(upgraded_sig, sig)) {
+				printf("\n");
+				printf("%s%s:%s:%s:%s%s\n",magenta, signame, sigtype, x, sig, color_off);
+				printf("%s%s:%s:%s:%s%s\n",yellow, signame, sigtype, x, upgraded_sig, color_off);
+			}
+			else
+				printf("\n");
+			printf("\n");
+#if 0			
+			start =0 ;
+			for(i=0, j=0;j < strlen(sig);j++) {
+				if(!isxdigit(upgraded_sig[i]) && !isxdigit(sig[j])) {
+					i++;
+					continue;
+				}
+/*				cli_dbgmsg("%c%c==%c%c(%d,%d)\n",upgraded_sig[i],upgraded_sig[i+1],sig[j],sig[j+1],i,j);*/
+				if(upgraded_sig[i] != sig[j] || (isxdigit(upgraded_sig[i+1]) && isxdigit(sig[j+1]) && upgraded_sig[i+1] != sig[j+1])) {
+					if(((sig[j]=='2' && sig[j+1]=='0') || (sig[j] == '0' && sig[j+1] == 'a') || (sig[j] == '0' && sig[j+1]=='d') || (sig[j]=='0' && sig[j+1]=='9')|| 
+								((!isxdigit(upgraded_sig[i]) &&  (sig[j]=='2' && sig[j+1]=='6'))))) 
+						j++;
+					else if(upgraded_sig[i]=='2' && upgraded_sig[i+1]=='0') {
+						i+=2;
+						j--;
+					}
+					else {
+						cli_dbgmsg("Upgrade is needed for this signature, difference at:%ld: %c%c!=%c%c\n",i,upgraded_sig[i],upgraded_sig[i+1],sig[j],sig[j+1]);
+/*						printf("%s:%s:%s:%s",signame, sigtype, x, sig);*/
+
+						printf("%s:%s:%s:%s",signame, sigtype, x, cleanup_sig(upgraded_sig,sig) );
+						break;
+					}
+					start = 0;
+				}
+				else {
+					if(isxdigit(upgraded_sig[i+1]) && isxdigit(sig[j+1]))
+						i++,j++;
+					i++;
+				}
+
+			}
+#endif			
+			free(upgraded_sig);
+		}
+		free(signame);
+		free(sig);
+		free(x);
+		free(line);
+		line=NULL;
+	}
+	return 0;
+}
diff --git a/contrib/entitynorm/generate_encoding_aliases.c b/contrib/entitynorm/generate_encoding_aliases.c
new file mode 100644
index 0000000..f7df968
--- /dev/null
+++ b/contrib/entitynorm/generate_encoding_aliases.c
@@ -0,0 +1,77 @@
+/*
+ *  Copyright (C) 2006 Török Edvin <edwin at clamav.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA 02110-1301, USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include "../../libclamav/htmlnorm.h"
+#include "../../libclamav/entconv.h"
+#include "../../libclamav/hashtab.h"
+#include <string.h>
+
+static const struct {
+	const char* alias;
+	int   encoding;
+} aliases [] = {
+	{"UTF8",E_UTF8},
+	{"UTF-8",E_UTF8},
+	{"ISO-10646/UTF8",E_UTF8},
+	{"ISO-10646/UTF-8",E_UTF8},
+	{"ISO-10646",E_UCS4},
+	{"10646-1:1993",E_UCS4},
+	{"UCS4",E_UCS4},
+	{"UCS-4",E_UCS4},
+	{"UCS-4BE",E_UCS4_4321},
+	{"UCS-4LE",E_UCS4_1234},
+	{"ISO-10646/UCS4",E_UCS4},
+	{"10646-1:1993/UCS4",E_UCS4},
+	{"UCS2",E_UTF16},
+	{"ISO-10646/UCS2",E_UTF16},
+	{"UTF-16",E_UTF16},
+	{"UTF-16BE",E_UTF16_BE},
+	{"UTF-16LE",E_UTF16_LE},
+	{"UTF16BE",E_UTF16_BE},
+	{"UTF16LE",E_UTF16_LE},
+	{"UTF32",E_UCS4},
+	{"UTF32BE",E_UCS4_4321},
+	{"UTF32LE",E_UCS4_1234},
+	{"UTF-32",E_UCS4},
+	{"UTF-32BE",E_UCS4_4321},
+	{"UTF-32LE",E_UCS4_1234}
+};
+
+static const size_t aliases_cnt = sizeof(aliases)/sizeof(aliases[0]);
+extern short cli_debug_flag;
+
+int main(int argc, char* argv[])
+{
+	struct hashtable ht;
+	size_t i;
+
+	cli_debug_flag=1;
+	hashtab_init(&ht,aliases_cnt);
+
+	for(i=0;i < aliases_cnt;i++) {
+		hashtab_insert(&ht,(const unsigned char*)aliases[i].alias,strlen(aliases[i].alias),aliases[i].encoding);
+	}
+
+	hashtab_generate_c(&ht,"aliases_htable");
+	return 0;
+}
diff --git a/contrib/entitynorm/generate_entitylist.c b/contrib/entitynorm/generate_entitylist.c
new file mode 100644
index 0000000..e720ae3
--- /dev/null
+++ b/contrib/entitynorm/generate_entitylist.c
@@ -0,0 +1,78 @@
+/*
+ *  Copyright (C) 2006 Török Edvin <edwin at clamav.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA 02110-1301, USA.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include "../../libclamav/hashtab.h"
+#include <sys/types.h>
+#include <dirent.h>
+
+#define MAX_LINE 1024
+/* ------------ generating entity tables from .ent files ---------------- */
+static uint16_t* map_data = NULL;
+static size_t map_data_n = 0;
+
+static void loadEntities(struct hashtable* s)
+{
+	char line[MAX_LINE];
+
+	while( fgets(line, MAX_LINE, stdin)) {
+		const char* name = line;
+		char* mapto;
+		size_t val;
+		struct element* elem;
+		uint16_t converted;
+		int found=0, i;
+
+		mapto = strchr(line,',');
+		if(!mapto) {
+			fprintf(stderr,"Invalid line:%s\n",line);
+			abort();
+		}
+		*mapto++ = '\0';
+
+		mapto[strlen(mapto)-1] = '\0';
+		if(elem = hashtab_find(s,name,strlen(name))) {
+			if(strlen(elem->key) == strlen(name)) {
+				fprintf(stderr, "Duplicate entity:%s\n", name);
+			}
+			continue;
+		}
+		converted = atoi(mapto);
+		hashtab_insert(s,name,strlen(name), converted);
+	}
+}
+extern short cli_debug_flag;
+
+int main(int argc, char* argv[])
+{
+	struct hashtable ht;
+	int i;
+	cli_debug_flag=1;
+	hashtab_init(&ht,2048);
+
+	loadEntities(&ht);
+	hashtab_generate_c(&ht,"entities_htable");
+	return 0;
+}
+
diff --git a/contrib/entitynorm/gentbl.c b/contrib/entitynorm/gentbl.c
new file mode 100644
index 0000000..007863d
--- /dev/null
+++ b/contrib/entitynorm/gentbl.c
@@ -0,0 +1,48 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+int main(int argc, char* argv[])
+{
+	int i;
+	uint8_t tbl[256];
+	if(argc < 3) {
+		fprintf(stderr, "Usage: %s <variable-name> <character-range|single-char> ...\n", argv[0]);
+		return 1;
+	}
+	memset(tbl, 0, sizeof(tbl));
+	for(i=2;i<argc;i++) {
+		const char* v = argv[i];
+		tbl[*v] = 1;
+		if(v[1] == '-') {
+			int j;
+			for(j=v[0]+1;j<=v[2];j++) {
+				tbl[j]=1;
+			}
+		} else if(v[1]){
+			fprintf(stderr,"Invalid char range spec:%s\n",v);
+			return 2;
+		}
+	}
+	printf("/*");
+	for(i=0;i<sizeof(tbl);i++) {
+		if(tbl[i]) putc(i, stdout);
+	}
+	printf("*/\n");
+	printf("static const uint8_t %s[256] = {\n\t", argv[1]);
+	for(i=0;i<sizeof(tbl);i++) {
+		printf("%d",tbl[i]);
+		if(i!=sizeof(tbl)-1) {
+			putc(',', stdout);
+			if(i%16==15)
+				fputs("\n\t",stdout);
+			else
+				putc(' ', stdout);
+		} else {
+			putc('\n',stdout);
+		}
+	}
+	printf("};\n");
+
+	return 0;
+}
diff --git a/contrib/entitynorm/postprocess.c b/contrib/entitynorm/postprocess.c
new file mode 100644
index 0000000..3199337
--- /dev/null
+++ b/contrib/entitynorm/postprocess.c
@@ -0,0 +1,77 @@
+/*
+ *  Copyright (C) 2006 Török Edvin <edwin at clamav.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA 02110-1301, USA.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <clamav.h>
+
+int main(int argc,char* argv[])
+{
+	char* line = NULL;
+	size_t n = 0;
+	int linecnt = 0;
+	int line_wanted;
+	int strip_color = 0;
+
+	if(argc<2) {
+		fprintf(stderr,"Usage: %s <line_wanted>|nocolor\n",argv[0]);
+	}
+	if(strcmp(argv[1],"nocolor")==0) 
+		strip_color = 1;
+	else
+		line_wanted = atoi(argv[1]);
+
+	cl_debug();
+	while(getline(&line,&n,stdin)!=-1) {
+		if(strchr(line,'\033')) {
+			linecnt++;
+			if(linecnt == line_wanted || strip_color) {
+				if(strip_color) {
+					do {
+						char* col = strchr(line,'\033');
+						if(col) {
+							*col++ = '\0';
+							printf("%s",line);
+								line = strchr(col,'m');
+								if(line) line++;
+						}
+						else {
+							printf("%s",line);
+							line = NULL;
+						}
+					} while(line);
+				}
+				else
+					printf("%s",line);
+			}
+		}
+		else {
+			if(strip_color)
+				printf("%s",line);
+			linecnt = 0;
+		}
+		line=NULL;
+	}
+
+	return 0;
+}
+
diff --git a/contrib/entitynorm/target.h b/contrib/entitynorm/target.h
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/hwaccel/hwaccel.patch b/contrib/hwaccel/hwaccel.patch
new file mode 100644
index 0000000..bcf0fa0
--- /dev/null
+++ b/contrib/hwaccel/hwaccel.patch
@@ -0,0 +1,1073 @@
+diff -Nura clamav-devel/clamd/clamd.c clamav-devel.hwaccel/clamd/clamd.c
+--- clamav-devel/clamd/clamd.c	2007-09-07 14:04:34.000000000 +0200
++++ clamav-devel.hwaccel/clamd/clamd.c	2007-08-13 18:44:10.000000000 +0200
+@@ -312,6 +312,15 @@
+     else
+ 	logg("Disabling URL based phishing detection.\n");
+ 
++    if(cfgopt(copt, "NodalCoreAcceleration")->enabled) {
++#ifdef HAVE_NCORE
++	dboptions |= CL_DB_NCORE;
++	logg("Enabling support for hardware acceleration.\n");
++#else
++	logg("^Support for hardware acceleration not compiled in.\n");
++#endif
++    }
++
+     if((ret = cl_load(dbdir, &engine, &sigs, dboptions))) {
+ 	logg("!%s\n", cl_strerror(ret));
+ 	logg_close();
+diff -Nura clamav-devel/clamd/server-th.c clamav-devel.hwaccel/clamd/server-th.c
+--- clamav-devel/clamd/server-th.c	2007-09-07 14:04:16.000000000 +0200
++++ clamav-devel.hwaccel/clamd/server-th.c	2007-05-28 17:26:59.000000000 +0200
+@@ -610,6 +610,11 @@
+ 	}
+ 
+ 	pthread_mutex_lock(&reload_mutex);
++	if(reload && cfgopt(copt, "NodalCoreAcceleration")->enabled) {
++	    logg("^RELOAD is not available in hardware accelerated mode (yet).\n");
++	    logg("^Please restart the daemon manually.\n");
++	    reload = 0;
++	}
+ 	if(reload) {
+ 	    pthread_mutex_unlock(&reload_mutex);
+ 	    engine = reload_db(engine, dboptions, copt, FALSE, &ret);
+diff -Nura clamav-devel/clamdscan/client.c clamav-devel.hwaccel/clamdscan/client.c
+--- clamav-devel/clamdscan/client.c	2007-09-07 14:05:26.000000000 +0200
++++ clamav-devel.hwaccel/clamdscan/client.c	2007-03-31 20:59:16.000000000 +0200
+@@ -56,6 +56,7 @@
+ 
+ void move_infected(const char *filename, const struct optstruct *opt);
+ int notremoved = 0, notmoved = 0;
++static int ncore = 0;
+ 
+ static int dsresult(int sockd, const struct optstruct *opt)
+ {
+@@ -388,6 +389,11 @@
+ 	return -1;
+     }
+ 
++#ifdef HAVE_NCORE
++    if(cfgopt(copt, "NodalCoreAcceleration")->enabled)
++	ncore = 1;
++#endif
++
+     freecfg(copt);
+ 
+     return sockd;
+@@ -403,7 +409,7 @@
+ 
+     *infected = 0;
+ 
+-    if(opt_check(opt, "multiscan"))
++    if(ncore || opt_check(opt, "multiscan"))
+ 	scantype = "MULTISCAN";
+ 
+     /* parse argument list */
+diff -Nura clamav-devel/clamscan/clamscan.c clamav-devel.hwaccel/clamscan/clamscan.c
+--- clamav-devel/clamscan/clamscan.c	2007-09-07 14:06:16.000000000 +0200
++++ clamav-devel.hwaccel/clamscan/clamscan.c	2007-08-13 18:23:19.000000000 +0200
+@@ -224,7 +224,10 @@
+ 	dms += (dms < 0) ? (1000000):(0);
+ 	logg("\n----------- SCAN SUMMARY -----------\n");
+ 	logg("Known viruses: %u\n", info.sigs);
+-	logg("Engine version: %s\n", cl_retver());
++	if(opt_check(opt, "ncore"))
++	    logg("Engine version: %s [ncore]\n", cl_retver());
++	else
++	    logg("Engine version: %s\n", cl_retver());
+ 	logg("Scanned directories: %u\n", info.dirs);
+ 	logg("Scanned files: %u\n", info.files);
+ 	logg("Infected files: %u\n", info.ifiles);
+@@ -291,6 +294,9 @@
+     mprintf("    --include=PATT                       Only scan file names containing PATT\n");
+     mprintf("    --include-dir=PATT                   Only scan directories containing PATT\n");
+ #endif
++#ifdef HAVE_NCORE
++    mprintf("\n    --ncore                            Use hardware acceleration\n");
++#endif
+     mprintf("\n");
+     mprintf("    --detect-pua                         Detect Possibly Unwanted Applications\n");
+     mprintf("    --no-mail                            Disable mail file support\n");
+diff -Nura clamav-devel/clamscan/clamscan_opt.h clamav-devel.hwaccel/clamscan/clamscan_opt.h
+--- clamav-devel/clamscan/clamscan_opt.h	2007-09-07 14:06:51.000000000 +0200
++++ clamav-devel.hwaccel/clamscan/clamscan_opt.h	2007-08-13 18:20:41.000000000 +0200
+@@ -59,6 +59,9 @@
+     {"max-recursion", 1, 0, 0},
+     {"max-dir-recursion", 1, 0, 0},
+     {"max-mail-recursion", 1, 0, 0},
++#ifdef HAVE_NCORE
++    {"ncore", 0, 0, 0},
++#endif
+     {"detect-pua", 0, 0, 0},
+     {"disable-archive", 0, 0, 0},
+     {"no-archive", 0, 0, 0},
+diff -Nura clamav-devel/clamscan/manager.c clamav-devel.hwaccel/clamscan/manager.c
+--- clamav-devel/clamscan/manager.c	2007-09-07 14:06:38.000000000 +0200
++++ clamav-devel.hwaccel/clamscan/manager.c	2007-08-13 18:18:26.000000000 +0200
+@@ -164,6 +164,9 @@
+     }
+ #endif
+ 
++    if(opt_check(opt, "ncore"))
++	dboptions |= CL_DB_NCORE;
++
+     if(!opt_check(opt, "no-phishing-sigs"))
+ 	dboptions |= CL_DB_PHISHING;
+ 
+diff -Nura clamav-devel/configure.in clamav-devel.hwaccel/configure.in
+--- clamav-devel/configure.in	2007-09-07 14:14:41.000000000 +0200
++++ clamav-devel.hwaccel/configure.in	2007-07-11 00:20:10.000000000 +0200
+@@ -151,6 +151,23 @@
+     AC_CHECK_HEADER(bzlib.h,[LIBCLAMAV_LIBS="$LIBCLAMAV_LIBS -lbz2"; AC_DEFINE(HAVE_BZLIB_H,1,have bzip2)], AC_MSG_WARN([****** bzip2 support disabled]))
+ fi
+ 
++AC_ARG_ENABLE(ncore,
++[  --disable-ncore	  disable support for NodalCore acceleration (default=auto)],
++want_ncore=$enableval, want_ncore="auto")
++
++if test "$want_ncore" = "auto"
++then
++    AC_CHECK_LIB(sn_sigscan, sn_sigscan_initdb, have_sigscan=yes,)
++    if test "$have_sigscan" = "yes"; then
++	want_ncore="yes"
++    fi
++fi
++
++if test "$want_ncore" = "yes"
++then
++    AC_CHECK_HEADER(dlfcn.h,[LIBCLAMAV_LIBS="$LIBCLAMAV_LIBS -ldl" ; AC_DEFINE(HAVE_NCORE,1,Support for NodalCore acceleration)], AC_MSG_WARN([****** NodalCore support disabled (no support for dlopen)]))
++fi
++
+ AC_ARG_ENABLE(dns,
+     AC_HELP_STRING([--disable-dns], [disable support for database verification through DNS]),
+     [want_dns=$enableval], [want_dns=yes]
+diff -Nura clamav-devel/docs/clamdoc.tex clamav-devel.hwaccel/docs/clamdoc.tex
+--- clamav-devel/docs/clamdoc.tex	2007-09-07 14:07:40.000000000 +0200
++++ clamav-devel.hwaccel/docs/clamdoc.tex	2007-07-11 00:05:45.000000000 +0200
+@@ -659,6 +659,19 @@
+ 	\item CryptFF
+     \end{itemize}
+ 
++    \subsection{Hardware acceleration}
++    ClamAV 0.90 comes with support for Sensory Networks' NodalCore
++    acceleration technology. Thanks to specialized Security Processing Unit
++    built into NodalCore C-Series accelerators it is possible to achieve more
++    performance than is possible by just adding general purpose CPUs.
++    Additionally, Sensory Networks' CorePAKT technology ensures that compiled
++    signature databases occupy a memory footprint smaller than any other
++    competing technology on the market - in many cases by up to 90\%. The
++    ability to store multiple databases in compressed format whilst still
++    achieving high throughputs makes NodalCore ideal for applications
++    demanding high performance on large signature databases such as ClamAV.\\
++    For more information please visit \url{http://www.clamav.net/nodalcore/}
++
+     \subsection{API}
+ 
+     \subsubsection{Header file}
+@@ -687,6 +700,8 @@
+     \begin{itemize}
+ 	\item \textbf{CL\_DB\_STDOPT}\\
+ 	This is an alias for a recommended set of scan options.
++	\item \textbf{CL\_DB\_NCORE}\\
++	Initialize NodalCore and load the hardware database (if applicable).
+ 	\item \textbf{CL\_DB\_PHISHING}\\
+ 	Load phishing signatures.
+ 	\item \textbf{CL\_DB\_PHISHING\_URLS}\\
+diff -Nura clamav-devel/docs/man/clamd.conf.5.in clamav-devel.hwaccel/docs/man/clamd.conf.5.in
+--- clamav-devel/docs/man/clamd.conf.5.in	2007-09-07 15:04:51.000000000 +0200
++++ clamav-devel.hwaccel/docs/man/clamd.conf.5.in	2007-08-13 18:40:08.000000000 +0200
+@@ -310,6 +310,11 @@
+ .br 
+ Default: no
+ .TP 
++\fBNodalCoreAcceleration BOOL\fR
++Enable support for Sensory Networks' NodalCore hardware accelerator.
++.br
++Default: no
++.TP 
+ \fBClamukoScanOnAccess BOOL\fR
+ Enable Clamuko. Dazuko (/dev/dazuko) must be configured and running.
+ .br 
+diff -Nura clamav-devel/etc/clamd.conf clamav-devel.hwaccel/etc/clamd.conf
+--- clamav-devel/etc/clamd.conf	2007-09-07 14:07:56.000000000 +0200
++++ clamav-devel.hwaccel/etc/clamd.conf	2007-08-20 19:03:08.000000000 +0200
+@@ -314,6 +314,10 @@
+ # Default: no
+ #ArchiveBlockMax no
+ 
++# Enable support for Sensory Networks' NodalCore hardware accelerator.
++# Default: no
++#NodalCoreAcceleration yes
++
+ 
+ ##
+ ## Clamuko settings
+diff -Nura clamav-devel/libclamav/Makefile.am clamav-devel.hwaccel/libclamav/Makefile.am
+--- clamav-devel/libclamav/Makefile.am	2007-09-07 14:16:27.000000000 +0200
++++ clamav-devel.hwaccel/libclamav/Makefile.am	2007-09-05 15:37:12.000000000 +0200
+@@ -31,6 +31,8 @@
+         matcher-ac.h \
+         matcher-bm.c \
+         matcher-bm.h \
++	matcher-ncore.c \
++	matcher-ncore.h \
+         matcher.c \
+         matcher.h \
+         md5.c \
+diff -Nura clamav-devel/libclamav/clamav.h clamav-devel.hwaccel/libclamav/clamav.h
+--- clamav-devel/libclamav/clamav.h	2007-09-07 14:38:43.000000000 +0200
++++ clamav-devel.hwaccel/libclamav/clamav.h	2007-08-28 22:36:01.000000000 +0200
+@@ -64,7 +64,13 @@
+ #define CL_ELOCKDB	-126 /* can't lock DB directory */
+ #define CL_EARJ         -127 /* ARJ handler error */
+ 
++/* NodalCore */
++#define CL_ENCINIT	-200 /* NodalCore initialization failed */
++#define	CL_ENCLOAD	-201 /* error loading NodalCore database */
++#define CL_ENCIO	-202 /* general NodalCore I/O error */
++
+ /* db options */
++#define CL_DB_NCORE	    0x1
+ #define CL_DB_PHISHING	    0x2
+ #define CL_DB_ACONLY	    0x4 /* WARNING: only for developers */
+ #define CL_DB_PHISHING_URLS 0x8
+@@ -105,6 +111,7 @@
+ 
+ struct cl_engine {
+     unsigned int refcount; /* reference counter */
++    unsigned short ncore;
+     unsigned short sdb;
+     unsigned int dboptions;
+ 
+@@ -123,6 +130,9 @@
+     /* RAR metadata */
+     void *rar_mlist;
+ 
++    /* NodalCore database handle */
++    void *ncdb;
++
+     /* Phishing .pdb and .wdb databases*/
+     void *whitelist_matcher;
+     void *domainlist_matcher;
+diff -Nura clamav-devel/libclamav/matcher-ncore.c clamav-devel.hwaccel/libclamav/matcher-ncore.c
+--- clamav-devel/libclamav/matcher-ncore.c	1970-01-01 01:00:00.000000000 +0100
++++ clamav-devel.hwaccel/libclamav/matcher-ncore.c	2007-03-31 21:17:45.000000000 +0200
+@@ -0,0 +1,606 @@
++/*
++ *  Copyright (C) 2006 Sensory Networks, Inc.
++ *	      Written by Tomasz Kojm, dlopen() support by Peter Duthie
++ *
++ *  This program is free software; you can redistribute it and/or modify
++ *  it under the terms of the GNU General Public License version 2 as
++ *  published by the Free Software Foundation.
++ *
++ *  This program is distributed in the hope that it will be useful,
++ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
++ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ *  GNU General Public License for more details.
++ *
++ *  You should have received a copy of the GNU General Public License
++ *  along with this program; if not, write to the Free Software
++ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
++ *  MA 02110-1301, USA.
++ */
++
++#if HAVE_CONFIG_H
++#include "clamav-config.h"
++#endif
++
++#ifdef HAVE_NCORE
++
++#include <stdio.h>
++#include <stdlib.h>
++#ifdef	HAVE_UNISTD_H
++#include <unistd.h>
++#endif
++#include <string.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <fcntl.h>
++#include <ctype.h>
++#ifdef HAVE_NCORE
++#include <dlfcn.h>
++#endif
++
++#include "clamav.h"
++#include "matcher.h"
++#include "cltypes.h"
++#include "md5.h"
++#include "readdb.h"
++#include "str.h"
++#include "matcher-ncore.h"
++
++#define HWBUFFSIZE 32768
++
++/* Globals */
++static void *g_ncore_dllhandle = 0;
++static const char *g_ncore_dllpath = "/usr/lib/libsn_sigscan.so";
++
++/* Function pointer types */
++typedef int (*sn_sigscan_initdb_t)(void **);
++typedef int (*sn_sigscan_loaddb_t)(void *dbhandle, const char *filename,
++        int devicenum, unsigned int *count);
++typedef int (*sn_sigscan_load2dbs_t)(void *dbhandle, const char *baseFilename,
++        const char *incrFilename, int devicenum, unsigned int *count);
++typedef int (*sn_sigscan_closedb_t)(void *dbhandle);
++typedef int (*sn_sigscan_createstream_t)(void *dbhandle,
++        const uint32_t *dbMaskData, unsigned int dbMaskWords,
++        void **streamhandle);
++typedef int (*sn_sigscan_writestream_t)(void *streamhandle, const char *buffer,
++        unsigned int len);
++typedef int (*sn_sigscan_closestream_t)(void *streamhandle,
++        void **resulthandle);
++typedef int (*sn_sigscan_resultcount_t)(void *resulthandle);
++typedef int (*sn_sigscan_resultget_name_t)(void *resulthandle,
++        unsigned int index, const char **matchname);
++typedef int (*sn_sigscan_resultget_startoffset_t)(void *resulthandle,
++        unsigned int index, unsigned long long *startoffset);
++typedef int (*sn_sigscan_resultget_endoffset_t)(void *resulthandle,
++        unsigned int index, unsigned long long *endoffset);
++typedef int (*sn_sigscan_resultget_targettype_t)(void *resulthandle,
++        unsigned int index, int *targettype);
++typedef int (*sn_sigscan_resultget_offsetstring_t)(void *resulthandle,
++        unsigned int index, const char **offsetstring);
++typedef int (*sn_sigscan_resultget_extradata_t)(void *resulthandle,
++        unsigned int index, const char **optionalsigdata);
++typedef int (*sn_sigscan_resultfree_t)(void *resulthandle);
++typedef void (*sn_sigscan_error_function_t)(const char *msg);
++typedef int (*sn_sigscan_seterrorlogger_t)(sn_sigscan_error_function_t errfn);
++
++/* Function pointer values */
++sn_sigscan_initdb_t sn_sigscan_initdb_f = 0;
++sn_sigscan_loaddb_t sn_sigscan_loaddb_f = 0;
++sn_sigscan_load2dbs_t sn_sigscan_load2dbs_f = 0;
++sn_sigscan_closedb_t sn_sigscan_closedb_f = 0;
++sn_sigscan_createstream_t sn_sigscan_createstream_f = 0;
++sn_sigscan_writestream_t sn_sigscan_writestream_f = 0;
++sn_sigscan_closestream_t sn_sigscan_closestream_f = 0;
++sn_sigscan_resultcount_t sn_sigscan_resultcount_f = 0;
++sn_sigscan_resultget_name_t sn_sigscan_resultget_name_f = 0;
++sn_sigscan_resultget_startoffset_t sn_sigscan_resultget_startoffset_f = 0;
++sn_sigscan_resultget_endoffset_t sn_sigscan_resultget_endoffset_f = 0;
++sn_sigscan_resultget_targettype_t sn_sigscan_resultget_targettype_f = 0;
++sn_sigscan_resultget_offsetstring_t sn_sigscan_resultget_offsetstring_f = 0;
++sn_sigscan_resultget_extradata_t sn_sigscan_resultget_extradata_f = 0;
++sn_sigscan_resultfree_t sn_sigscan_resultfree_f = 0;
++sn_sigscan_seterrorlogger_t sn_sigscan_seterrorlogger_f = 0;
++
++static int cli_ncore_dlinit()
++{
++    if(access(g_ncore_dllpath, R_OK) == -1) {
++	cli_dbgmsg("cli_ncore_dlinit: Can't access %s\n", g_ncore_dllpath);
++	return CL_ENCINIT;
++    }
++
++    g_ncore_dllhandle = dlopen(g_ncore_dllpath, RTLD_NOW | RTLD_LOCAL);
++    if(!g_ncore_dllhandle) {
++	cli_dbgmsg("cli_ncore_dlinit: dlopen() failed for %s\n", g_ncore_dllpath);
++	return CL_ENCINIT;
++    }
++
++    /* get the symbols */
++    sn_sigscan_initdb_f = (sn_sigscan_initdb_t)dlsym(g_ncore_dllhandle, "sn_sigscan_initdb");
++    sn_sigscan_loaddb_f = (sn_sigscan_loaddb_t)dlsym(g_ncore_dllhandle, "sn_sigscan_loaddb");
++    sn_sigscan_load2dbs_f = (sn_sigscan_load2dbs_t)dlsym(g_ncore_dllhandle, "sn_sigscan_load2dbs");
++    sn_sigscan_closedb_f = (sn_sigscan_closedb_t)dlsym(g_ncore_dllhandle, "sn_sigscan_closedb");
++    sn_sigscan_createstream_f = (sn_sigscan_createstream_t)dlsym(g_ncore_dllhandle, "sn_sigscan_createstream");
++    sn_sigscan_writestream_f = (sn_sigscan_writestream_t)dlsym(g_ncore_dllhandle, "sn_sigscan_writestream");
++    sn_sigscan_closestream_f = (sn_sigscan_closestream_t)dlsym(g_ncore_dllhandle, "sn_sigscan_closestream");
++    sn_sigscan_resultcount_f = (sn_sigscan_resultcount_t)dlsym(g_ncore_dllhandle, "sn_sigscan_resultcount");
++    sn_sigscan_resultget_name_f = (sn_sigscan_resultget_name_t)dlsym(g_ncore_dllhandle, "sn_sigscan_resultget_name");
++    sn_sigscan_resultget_startoffset_f = (sn_sigscan_resultget_startoffset_t)dlsym(g_ncore_dllhandle, "sn_sigscan_resultget_startoffset");
++    sn_sigscan_resultget_endoffset_f = (sn_sigscan_resultget_endoffset_t)dlsym(g_ncore_dllhandle, "sn_sigscan_resultget_endoffset");
++    sn_sigscan_resultget_targettype_f = (sn_sigscan_resultget_targettype_t)dlsym(g_ncore_dllhandle, "sn_sigscan_resultget_targettype");
++    sn_sigscan_resultget_offsetstring_f = (sn_sigscan_resultget_offsetstring_t)dlsym(g_ncore_dllhandle, "sn_sigscan_resultget_offsetstring");
++    sn_sigscan_resultget_extradata_f = (sn_sigscan_resultget_extradata_t)dlsym(g_ncore_dllhandle, "sn_sigscan_resultget_extradata");
++    sn_sigscan_resultfree_f = (sn_sigscan_resultfree_t)dlsym(g_ncore_dllhandle, "sn_sigscan_resultfree");
++    sn_sigscan_seterrorlogger_f = (sn_sigscan_seterrorlogger_t)dlsym(g_ncore_dllhandle, "sn_sigscan_seterrorlogger");
++
++    /* Check that we got all the symbols */
++    if(sn_sigscan_initdb_f && sn_sigscan_loaddb_f && sn_sigscan_load2dbs_f &&
++            sn_sigscan_closedb_f && sn_sigscan_createstream_f &&
++            sn_sigscan_writestream_f && sn_sigscan_closestream_f &&
++            sn_sigscan_resultcount_f && sn_sigscan_resultget_name_f &&
++            sn_sigscan_resultget_startoffset_f &&
++            sn_sigscan_resultget_endoffset_f &&
++            sn_sigscan_resultget_targettype_f &&
++            sn_sigscan_resultget_offsetstring_f &&
++            sn_sigscan_resultget_extradata_f && sn_sigscan_resultfree_f &&
++            sn_sigscan_seterrorlogger_f)
++    {
++        return CL_SUCCESS;
++    }
++
++    dlclose(g_ncore_dllhandle);
++    g_ncore_dllhandle = 0;
++    return CL_ENCINIT;
++}
++
++int cli_ncore_scanbuff(const char *buffer, unsigned int length, const char **virname, const struct cl_engine *engine, unsigned short ftype, unsigned int *targettab)
++{
++	void *streamhandle;
++	void *resulthandle;
++	static const uint32_t datamask[2] = { 0xffffffff, 0xffffffff };
++	int count, hret, i;
++	char *pt;
++	int ret = CL_CLEAN;
++
++
++    /* TODO: Setup proper data bitmask (need specs) */
++    /* Create the hardware scanning stream */
++    hret = (*sn_sigscan_createstream_f)(engine->ncdb, datamask, 2, &streamhandle);
++    if(hret) {
++        cli_errmsg("cli_ncore_scanbuff: can't create new hardware stream: %d\n", hret);
++        return CL_ENCIO;
++    }
++
++    /* Write data to the hardware scanning stream */
++    hret = (*sn_sigscan_writestream_f)(streamhandle, buffer, length);
++    if(hret) {
++        cli_errmsg("cli_ncore_scanbuff: can't write %u bytes to hardware stream: %d\n", length, hret);
++        (*sn_sigscan_closestream_f)(streamhandle, &resulthandle);
++        (*sn_sigscan_resultfree_f)(resulthandle);
++        return CL_ENCIO;
++    }
++
++    /* Close the hardware scanning stream and collect the result */
++    hret = (*sn_sigscan_closestream_f)(streamhandle, &resulthandle);
++    if(hret) {
++        cli_errmsg("cli_ncore_scanbuff: can't close hardware stream: %d\n", hret);
++        return CL_ENCIO;
++    }
++
++    /* Iterate through the results */
++    count = (*sn_sigscan_resultcount_f)(resulthandle);
++    for(i = 0; i < count; i++) {
++        const char *matchname = NULL, *offsetstring = NULL, *optionalsigdata = NULL;
++        unsigned int targettype = 0;
++
++        /* Acquire the name of the result */
++        hret = (*sn_sigscan_resultget_name_f)(resulthandle, i, &matchname);
++        if(hret) {
++            cli_errmsg("cli_ncore_scanbuff: sn_sigscan_resultget_name failed for result %u: %d\n", i, hret);
++            (*sn_sigscan_resultfree_f)(resulthandle);
++            return CL_ENCIO;
++        }
++        if(!matchname) {
++            cli_errmsg("cli_ncore_scanbuff: HW Result[%u]: Signature without name\n", i);
++            (*sn_sigscan_resultfree_f)(resulthandle);
++            return CL_EMALFDB;
++        }
++
++        /* Acquire the result file type and check that it is correct */
++        hret = (*sn_sigscan_resultget_targettype_f)(resulthandle, i, &targettype);
++        if(hret) {
++            cli_errmsg("cli_ncore_scanbuff: sn_sigscan_resultget_targettype failed for result %u, signature %s: %d\n", i, matchname, hret);
++            (*sn_sigscan_resultfree_f)(resulthandle);
++            return CL_ENCIO;
++        }
++
++        if(targettype && targettab[targettype] != ftype) {
++            cli_dbgmsg("cli_ncore_scanbuff: HW Result[%u]: %s: Target type: %u, expected: %u\n", i, matchname, targettab[targettype], ftype);
++            continue;
++        }
++
++        hret = (*sn_sigscan_resultget_offsetstring_f)(resulthandle, i, &offsetstring);
++        if(hret) {
++            cli_errmsg("cli_ncore_scanbuff: sn_sigscan_resultget_offsetstring failed for result %u, signature %s: %d\n", i, matchname, hret);
++            (*sn_sigscan_resultfree_f)(resulthandle);
++            return CL_ENCIO;
++        }
++        if(offsetstring) {
++            cli_dbgmsg("cli_ncore_scanbuff: HW Result[%u]: %s: Offset based signature not supported in buffer mode\n", i, matchname);
++            continue;
++        }
++
++        hret = (*sn_sigscan_resultget_extradata_f)(resulthandle, i, &optionalsigdata);
++        if(hret) {
++            cli_errmsg("cli_ncore_scanbuff: sn_sigscan_resultget_extradata failed for result %u, signature %s: %d\n", i, matchname, hret);
++            (*sn_sigscan_resultfree_f)(resulthandle);
++            return CL_ENCIO;
++        }
++        if(optionalsigdata && strlen(optionalsigdata)) {
++            pt = cli_strtok(optionalsigdata, 1, ":");
++            if(pt) {
++                if(!isdigit(*pt)) {
++                    free(pt);
++                    cli_errmsg("cli_ncore_scanbuff: HW Result[%u]: %s: Incorrect optional signature data: %s\n", i, matchname, optionalsigdata);
++                    (*sn_sigscan_resultfree_f)(resulthandle);
++                    return CL_EMALFDB;
++                }
++
++                if((unsigned int) atoi(pt) < cl_retflevel()) {
++                    cli_dbgmsg("cli_ncore_scanbuff: HW Result[%u]: %s: Signature max flevel: %d, current: %d\n", i, matchname, atoi(pt), cl_retflevel());
++                    free(pt);
++                    continue;
++                }
++
++                free(pt);
++                pt = cli_strtok(optionalsigdata, 0, ":");
++                if(pt) {
++                    if(!isdigit(*pt)) {
++                        free(pt);
++                        cli_errmsg("cli_ncore_scanbuff: HW Result[%u]: %s: Incorrect optional signature data: %s\n", i, matchname, optionalsigdata);
++                        (*sn_sigscan_resultfree_f)(resulthandle);
++                        return CL_EMALFDB;
++                    }
++
++                    if((unsigned int) atoi(pt) > cl_retflevel()) {
++                        cli_dbgmsg("cli_ncore_scanbuff: HW Result[%u]: %s: Signature required flevel: %u, current: %u\n", i, matchname, atoi(pt), cl_retflevel());
++                        free(pt);
++                        continue;
++                    }
++                    free(pt);
++                }
++
++            } else {
++                if(!isdigit(*optionalsigdata)) {
++                    cli_errmsg("cli_ncore_scanbuff: HW Result[%u]: %s: Incorrect optional signature data: %s\n", i, matchname, optionalsigdata);
++                    (*sn_sigscan_resultfree_f)(resulthandle);
++                    return CL_EMALFDB;
++                }
++
++                if((unsigned int) atoi(optionalsigdata) > cl_retflevel()) {
++                    cli_dbgmsg("cli_ncore_scandesc: HW Result[%u]: %s: Signature required flevel: %u, current: %u\n", i, matchname, atoi(optionalsigdata), cl_retflevel());
++                    continue;
++                }
++            }
++        }
++
++        /* Store the name of the match */
++        *virname = matchname;
++        ret = CL_VIRUS;
++        break;
++    }
++
++    /* Clean up the result structure */
++    hret = (*sn_sigscan_resultfree_f)(resulthandle);
++    if(hret) {
++        cli_errmsg("cli_ncore_scanbuff: can't free results: %d\n", ret);
++        return CL_ENCIO;
++    }
++
++    return ret;
++}
++
++int cli_ncore_scandesc(int desc, cli_ctx *ctx, unsigned short ftype, int *cont, unsigned int *targettab, cli_md5_ctx *md5ctx)
++{
++	void *streamhandle;
++	void *resulthandle;
++	uint32_t datamask[2] = { 0xffffffff, 0xffffffff };
++	struct cli_target_info info;
++	int i, count, hret, bytes, ret = CL_CLEAN;
++	off_t origoff;
++	*cont = 0;
++	char *buffer;
++
++
++    /* TODO: Setup proper data bitmask (need specs) */
++    /* Create the hardware scanning stream */
++    hret = (*sn_sigscan_createstream_f)(ctx->engine->ncdb, datamask, 2, &streamhandle);
++    if(hret) {
++        cli_errmsg("cli_ncore_scandesc: can't create new hardware stream: %d\n", hret);
++        return CL_ENCIO;
++    }
++
++    /* Obtain the initial offset */
++    origoff = lseek(desc, 0, SEEK_CUR);
++    if(origoff == -1) {
++        cli_errmsg("cli_ncore_scandesc: lseek() failed for descriptor %d\n", desc);
++	(*sn_sigscan_closestream_f)(streamhandle, &resulthandle);
++        (*sn_sigscan_resultfree_f)(resulthandle);
++        return CL_EIO;
++    }
++
++    buffer = (char *) cli_calloc(HWBUFFSIZE, sizeof(char));
++    if(!buffer) {
++        cli_dbgmsg("cli_ncore_scandesc: unable to cli_calloc(%u)\n", HWBUFFSIZE);
++	(*sn_sigscan_closestream_f)(streamhandle, &resulthandle);
++        (*sn_sigscan_resultfree_f)(resulthandle);
++        return CL_EMEM;
++    }
++
++    /* Initialize the MD5 hasher */
++    if(ctx->engine->md5_hlist)
++        MD5_Init(md5ctx);
++
++    /* Read and scan the data */
++    while ((bytes = cli_readn(desc, buffer, HWBUFFSIZE)) > 0) {
++        hret = (*sn_sigscan_writestream_f)(streamhandle, buffer, bytes);
++        if(hret) {
++            cli_errmsg("cli_ncore_scandesc: can't write to hardware stream: %d\n", hret);
++            ret = CL_ENCIO;
++            break;
++        } else {
++            if(ctx->scanned)
++                *ctx->scanned += bytes / CL_COUNT_PRECISION;
++ 
++    	    if(ctx->engine->md5_hlist)
++                MD5_Update(md5ctx, buffer, bytes);
++        }
++    }
++
++    free(buffer);
++
++    /* Close the stream and get the result */
++    hret = (*sn_sigscan_closestream_f)(streamhandle, &resulthandle);
++    if(hret) {
++        cli_errmsg("cli_ncore_scandesc: can't close hardware stream: %d\n", hret);
++        return CL_ENCIO;
++    }
++
++    memset(&info, 0, sizeof(info));
++
++    /* Iterate over the list of results */
++    count = (*sn_sigscan_resultcount_f)(resulthandle);
++    for(i = 0; i < count; i++) {
++    	const char *matchname = NULL, *offsetstring = NULL, *optionalsigdata = NULL;
++    	unsigned long long startoffset = 0;
++    	unsigned int targettype = 0, maxshift = 0;
++        char *pt;
++
++        /* Get the description of the match */
++        hret = (*sn_sigscan_resultget_name_f)(resulthandle, i, &matchname);
++        if(hret) {
++            cli_errmsg("cli_ncore_scandesc: sn_sigscan_resultget_name failed for result %u: %d\n", i, hret);
++            (*sn_sigscan_resultfree_f)(resulthandle);
++	    if(info.exeinfo.section)
++		free(info.exeinfo.section);
++            return CL_ENCIO;
++        }
++
++        if(!matchname) {
++            cli_errmsg("cli_ncore_scandesc: HW Result[%u]: Signature without name\n", i);
++            (*sn_sigscan_resultfree_f)(resulthandle);
++	    if(info.exeinfo.section)
++		free(info.exeinfo.section);
++            return CL_EMALFDB;
++        }
++
++        hret = (*sn_sigscan_resultget_targettype_f)(resulthandle, i, &targettype);
++        if(hret) {
++    	    cli_errmsg("cli_ncore_scandesc: sn_sigscan_resultget_targettype failed for result %d, signature %s: %d\n", i, matchname, hret);
++    	    (*sn_sigscan_resultfree_f)(resulthandle);
++	    if(info.exeinfo.section)
++		free(info.exeinfo.section);
++    	    return CL_ENCIO;
++        }
++        if(targettype && targettab[targettype] != ftype) {
++    	    cli_dbgmsg("cli_ncore_scandesc: HW Result[%u]: %s: Target type: %u, expected: %d\n", i, matchname, targettab[targettype], ftype);
++            continue;
++        }
++
++        hret = (*sn_sigscan_resultget_offsetstring_f)(resulthandle, i, &offsetstring);
++        if(hret) {
++            cli_errmsg("cli_ncore_scandesc: sn_sigscan_resultget_offsetstring failed for result %u, signature %s: %d\n", i, matchname, hret);
++            (*sn_sigscan_resultfree_f)(resulthandle);
++	    if(info.exeinfo.section)
++		free(info.exeinfo.section);
++            return CL_ENCIO;
++        }
++
++        hret = (*sn_sigscan_resultget_startoffset_f)(resulthandle, i, &startoffset);
++        if(hret) {
++    	    cli_errmsg("cli_ncore_scandesc: sn_sigscan_resultget_startoffset failed for result %u, signature %s: %d\n", i, matchname, hret);
++    	    (*sn_sigscan_resultfree_f)(resulthandle);
++	    if(info.exeinfo.section)
++		free(info.exeinfo.section);
++    	    return CL_ENCIO;
++        }
++        if(offsetstring && strcmp(offsetstring, "*")) {
++	    off_t off = cli_caloff(offsetstring, &info, desc, ftype, &hret, &maxshift);
++
++    	    if(hret == -1) {
++                cli_dbgmsg("cli_ncore_scandesc: HW Result[%u]: %s: Bad offset in signature\n", i, matchname);
++                (*sn_sigscan_resultfree_f)(resulthandle);
++		if(info.exeinfo.section)
++		    free(info.exeinfo.section);
++                return CL_EMALFDB;
++            }
++	    if(maxshift) {
++		if((startoffset < (unsigned long long) off) || (startoffset > (unsigned long long) off + maxshift)) {
++		    cli_dbgmsg("cli_ncore_scandesc: HW Result[%u]: %s: Virus offset: %Lu, expected: [%Lu..%Lu]\n", i, matchname, startoffset, off, off + maxshift);
++		    continue;
++		}
++	    } else if(startoffset != (unsigned long long) off) {
++                cli_dbgmsg("cli_ncore_scandesc: HW Result[%u]: %s: Virus offset: %Lu, expected: %Lu\n", i, matchname, startoffset, off);
++                continue;
++            }
++        }
++
++        hret = (*sn_sigscan_resultget_extradata_f)(resulthandle, i, &optionalsigdata);
++        if(hret) {
++            cli_errmsg("cli_ncore_scandesc: sn_sigscan_resultget_extradata failed for result %d, signature %s: %d\n", i, matchname, hret);
++            (*sn_sigscan_resultfree_f)(resulthandle);
++	    if(info.exeinfo.section)
++		free(info.exeinfo.section);
++            return CL_ENCIO;
++        }
++
++        if(optionalsigdata && strlen(optionalsigdata)) {
++    	    pt = cli_strtok(optionalsigdata, 1, ":");
++    	    if(pt) {
++    	        if(!isdigit(*pt)) {
++    		    free(pt);
++                    cli_errmsg("cli_ncore_scandesc: HW Result[%u]: %s: Incorrect optional signature data: %s\n", i, matchname, optionalsigdata);
++		    (*sn_sigscan_resultfree_f)(resulthandle);
++		    if(info.exeinfo.section)
++			free(info.exeinfo.section);
++                    return CL_EMALFDB;
++                }
++
++                if((unsigned int) atoi(pt) < cl_retflevel()) {
++                    cli_dbgmsg("cli_ncore_scandesc: HW Result[%u]: %s: Signature max flevel: %d, current: %d\n", i, matchname, atoi(pt), cl_retflevel());
++                    free(pt);
++                    continue;
++                }
++
++                free(pt);
++
++    	        pt = cli_strtok(optionalsigdata, 0, ":");
++    	        if(pt) {
++                    if(!isdigit(*pt)) {
++                        free(pt);
++                        cli_errmsg("cli_ncore_scandesc: HW Result[%u]: %s: Incorrect optional signature data: %s\n", i, matchname, optionalsigdata);
++			(*sn_sigscan_resultfree_f)(resulthandle);
++			if(info.exeinfo.section)
++			    free(info.exeinfo.section);
++                        return CL_EMALFDB;
++                    }
++
++                    if((unsigned int) atoi(pt) > cl_retflevel()) {
++                        cli_dbgmsg("cli_ncore_scandesc: HW Result[%u]: %s: Signature required flevel: %d, current: %d\n", i, matchname, atoi(pt), cl_retflevel());
++                        free(pt);
++                        continue;
++                    }
++                    free(pt);
++                }
++            } else {
++                if(!isdigit(*optionalsigdata)) {
++                    cli_errmsg("cli_ncore_scandesc: HW Result[%u]: %s: Incorrect optional signature data: %s\n", i, matchname, optionalsigdata);
++                    (*sn_sigscan_resultfree_f)(resulthandle);
++		    if(info.exeinfo.section)
++			free(info.exeinfo.section);
++                    return CL_EMALFDB;
++                }
++
++                if((unsigned int) atoi(optionalsigdata) > cl_retflevel()) {
++                    cli_dbgmsg("cli_ncore_scandesc: HW Result[%u]: %s: Signature required flevel: %d, current: %d\n", i, matchname, atoi(optionalsigdata), cl_retflevel());
++                    continue;
++                }
++            }
++        }
++
++        *ctx->virname = matchname;
++        ret = CL_VIRUS;
++        break;
++    }
++
++    if(info.exeinfo.section)
++	free(info.exeinfo.section);
++
++    hret = (*sn_sigscan_resultfree_f)(resulthandle);
++    if(hret) {
++        cli_errmsg("cli_ncore_scandesc: can't free results: %d\n", ret);
++        return CL_ENCIO;
++    }
++
++    if(ctx->engine->md5_hlist) {
++        unsigned char digest[16];
++        struct cli_md5_node *md5_node;
++        MD5_Final(digest, md5ctx);
++
++        md5_node = cli_vermd5(digest, ctx->engine);
++        if(md5_node) {
++            struct stat sb;
++            if(fstat(desc, &sb) == -1)
++                return CL_EIO;
++
++    	    if((unsigned int) sb.st_size != md5_node->size) {
++                cli_warnmsg("Detected false positive MD5 match. Please report.\n");
++            } else {
++                if(md5_node->fp) {
++                    cli_dbgmsg("Eliminated false positive match (fp sig: %s)\n", md5_node->virname);
++                    ret = CL_CLEAN;
++                } else {
++                    if(ctx->virname)
++                        *ctx->virname = md5_node->virname;
++
++                    ret = CL_VIRUS;
++                }
++            }
++        }
++    }
++
++    if(ret == CL_VIRUS || (ftype != CL_TYPE_UNKNOWN_TEXT && ftype != CL_TYPE_UNKNOWN_DATA))
++        return ret;
++
++    if(lseek(desc, origoff, SEEK_SET) == -1) {
++        cli_errmsg("cli_ncore_scandesc: lseek() failed for descriptor %d\n", desc);
++        return CL_EIO;
++    }
++
++    *cont = 1;
++    return ret;
++}
++
++int cli_ncore_load(const char *filename, struct cl_engine **engine, unsigned int *signo, unsigned int options)
++{
++	int ret = 0;
++	unsigned int newsigs = 0;
++
++
++    if((ret = cli_initengine(engine, options))) {
++	cl_free(*engine);
++	return ret;
++    }
++
++    if((ret = cli_ncore_dlinit())) {
++	cl_free(*engine);
++	return ret;
++    }
++
++    ret = (*sn_sigscan_initdb_f)(&(*engine)->ncdb);
++    if(ret) {
++        cli_errmsg("cli_ncore_load: error initializing the matcher: %d\n", ret);
++        cl_free(*engine);
++        return CL_ENCINIT;
++    }
++
++    (*engine)->ncore = 1;
++
++    ret = (*sn_sigscan_loaddb_f)((*engine)->ncdb, filename, 0, &newsigs);
++    if(ret) {
++        cli_errmsg("cli_ncore_load: can't load hardware database: %d\n", ret);
++        cl_free(*engine);
++        return CL_ENCLOAD;
++    }
++
++    *signo += newsigs;
++    return CL_SUCCESS;
++}
++
++void cli_ncore_unload(struct cl_engine *engine)
++{
++	int ret;
++
++    ret = (*sn_sigscan_closedb_f)(engine->ncdb);
++    if(ret)
++        cli_errmsg("cl_free: can't close hardware database: %d\n", ret);
++}
++#endif
+diff -Nura clamav-devel/libclamav/matcher-ncore.h clamav-devel.hwaccel/libclamav/matcher-ncore.h
+--- clamav-devel/libclamav/matcher-ncore.h	1970-01-01 01:00:00.000000000 +0100
++++ clamav-devel.hwaccel/libclamav/matcher-ncore.h	2007-03-31 21:18:08.000000000 +0200
+@@ -0,0 +1,34 @@
++/*
++ *  Copyright (C) 2006 Sensory Networks, Inc.
++ *	      Written by Tomasz Kojm, dlopen() support by Peter Duthie
++ *
++ *  This program is free software; you can redistribute it and/or modify
++ *  it under the terms of the GNU General Public License version 2 as
++ *  published by the Free Software Foundation.
++ *
++ *  This program is distributed in the hope that it will be useful,
++ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
++ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ *  GNU General Public License for more details.
++ *
++ *  You should have received a copy of the GNU General Public License
++ *  along with this program; if not, write to the Free Software
++ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
++ *  MA 02110-1301, USA.
++ */
++
++#ifndef __MATCHER_NCORE_H
++#define __MATCHER_NCORE_H
++
++#include "clamav.h"
++#include "md5.h"
++
++int cli_ncore_scanbuff(const char *buffer, unsigned int length, const char **virname, const struct cl_engine *engine, unsigned short ftype, unsigned int *targettab);
++
++int cli_ncore_scandesc(int desc, cli_ctx *ctx, unsigned short ftype, int *cont, unsigned int *targettab, cli_md5_ctx *md5ctx);
++
++int cli_ncore_load(const char *filename, struct cl_engine **engine, unsigned int *signo, unsigned int options);
++
++void cli_ncore_unload(struct cl_engine *engine);
++
++#endif
+diff -Nura clamav-devel/libclamav/matcher.c clamav-devel.hwaccel/libclamav/matcher.c
+--- clamav-devel/libclamav/matcher.c	2007-09-07 14:09:42.000000000 +0200
++++ clamav-devel.hwaccel/libclamav/matcher.c	2007-08-31 20:36:14.000000000 +0200
+@@ -42,6 +42,10 @@
+ #include "str.h"
+ #include "cltypes.h"
+ 
++#ifdef HAVE_NCORE
++#include "matcher-ncore.h"
++#endif
++
+ static cli_file_t targettab[CL_TARGET_TABLE_SIZE] = { 0, CL_TYPE_MSEXE, CL_TYPE_MSOLE2, CL_TYPE_HTML, CL_TYPE_MAIL, CL_TYPE_GRAPHICS, CL_TYPE_ELF };
+ 
+ int cli_scanbuff(const unsigned char *buffer, uint32_t length, const char **virname, const struct cl_engine *engine, cli_file_t ftype)
+@@ -57,6 +61,11 @@
+ 	return CL_ENULLARG;
+     }
+ 
++#ifdef HAVE_NCORE
++    if(engine->ncore)
++	return cli_ncore_scanbuff(buffer, length, virname, engine, ftype, targettab);
++#endif
++
+     groot = engine->root[0]; /* generic signatures */
+ 
+     if(ftype) {
+@@ -292,6 +301,16 @@
+ 	return CL_ENULLARG;
+     }
+ 
++#ifdef HAVE_NCORE
++    if(ctx->engine->ncore) {
++	    int cont;
++
++	ret = cli_ncore_scandesc(desc, ctx, ftype, &cont, targettab, &md5ctx);
++	if(!cont)
++	    return ret;
++    }
++#endif
++
+     if(!ftonly)
+ 	groot = ctx->engine->root[0]; /* generic signatures */
+ 
+diff -Nura clamav-devel/libclamav/others.c clamav-devel.hwaccel/libclamav/others.c
+--- clamav-devel/libclamav/others.c	2007-09-07 14:13:47.000000000 +0200
++++ clamav-devel.hwaccel/libclamav/others.c	2007-08-31 20:33:10.000000000 +0200
+@@ -186,6 +186,12 @@
+ 	    return "Bad format or broken data";
+ 	case CL_ESUPPORT:
+ 	    return "Not supported data format";
++	case CL_ENCINIT:
++	    return "NodalCore initialization failure";
++	case CL_ENCLOAD:
++	    return "Error loading NodalCore database";
++	case CL_ENCIO:
++	    return "NodalCore accelerator Input/Output error";
+ 	case CL_ELOCKDB:
+ 	    return "Unable to lock database directory";
+ 	case CL_EARJ:
+diff -Nura clamav-devel/libclamav/readdb.c clamav-devel.hwaccel/libclamav/readdb.c
+--- clamav-devel/libclamav/readdb.c	2007-09-07 14:12:44.000000000 +0200
++++ clamav-devel.hwaccel/libclamav/readdb.c	2007-08-28 23:03:27.000000000 +0200
+@@ -70,6 +70,10 @@
+ static pthread_mutex_t cli_ref_mutex = PTHREAD_MUTEX_INITIALIZER;
+ #endif
+ 
++#ifdef HAVE_NCORE
++#include "matcher-ncore.h"
++#endif
++
+ /* Prototypes for old public functions just to shut up some gcc warnings;
+  * to be removed in 1.0
+  */
+@@ -960,7 +964,10 @@
+     }
+ 
+     if(cli_strbcasestr(filename, ".db")) {
+-	ret = cli_loaddb(fd, engine, signo, options);
++	if(options & CL_DB_NCORE)
++	    skipped = 1;
++	else
++	    ret = cli_loaddb(fd, engine, signo, options);
+ 
+     } else if(cli_strbcasestr(filename, ".cvd")) {
+ 	    int warn = 0;
+@@ -992,16 +999,23 @@
+ 	    skipped = 1;
+ 
+     } else if(cli_strbcasestr(filename, ".ndb")) {
+-	ret = cli_loadndb(fd, engine, signo, 0, options);
++	if(options & CL_DB_NCORE)
++	    skipped = 1;
++	else
++	    ret = cli_loadndb(fd, engine, signo, 0, options);
+ 
+     } else if(cli_strbcasestr(filename, ".ndu")) {
+-	if(!(options & CL_DB_PUA))
++	if(!(options & CL_DB_PUA) || (options & CL_DB_NCORE))
+ 	    skipped = 1;
+ 	else
+ 	    ret = cli_loadndb(fd, engine, signo, 0, options);
+ 
+     } else if(cli_strbcasestr(filename, ".sdb")) {
+-	ret = cli_loadndb(fd, engine, signo, 1, options);
++	/* FIXME: Add support in ncore mode */
++	if(options & CL_DB_NCORE)
++	    skipped = 1;
++	else
++	    ret = cli_loadndb(fd, engine, signo, 1, options);
+ 
+     } else if(cli_strbcasestr(filename, ".zmd")) {
+ 	ret = cli_loadmd(fd, engine, signo, 1, options);
+@@ -1012,6 +1026,13 @@
+     } else if(cli_strbcasestr(filename, ".cfg")) {
+ 	ret = cli_dconf_load(fd, engine, options);
+ 
++    } else if(cli_strbcasestr(filename, ".ncdb")) {
++#ifdef HAVE_NCORE
++	if(options & CL_DB_NCORE)
++	    ret = cli_ncore_load(filename, engine, signo, options);
++	else
++#endif
++	    skipped = 1;
+     } else if(cli_strbcasestr(filename, ".wdb")) {
+ 	if(options & CL_DB_PHISHING_URLS)
+ 	    ret = cli_loadwdb(fd, engine, options);
+@@ -1106,6 +1127,7 @@
+ 	     cli_strbcasestr(dent->d_name, ".rmd")  ||
+ 	     cli_strbcasestr(dent->d_name, ".pdb")  ||
+ 	     cli_strbcasestr(dent->d_name, ".wdb")  ||
++	     cli_strbcasestr(dent->d_name, ".ncdb") ||
+ 	     cli_strbcasestr(dent->d_name, ".inc")  ||
+ 	     cli_strbcasestr(dent->d_name, ".cvd"))) {
+ 
+@@ -1263,6 +1285,7 @@
+ 	    cli_strbcasestr(dent->d_name, ".cfg")  ||
+ 	    cli_strbcasestr(dent->d_name, ".pdb")  ||
+ 	    cli_strbcasestr(dent->d_name, ".wdb")  ||
++	    cli_strbcasestr(dent->d_name, ".ncdb")  ||
+ 	    cli_strbcasestr(dent->d_name, ".inc")   ||
+ 	    cli_strbcasestr(dent->d_name, ".cvd"))) {
+ 
+@@ -1373,6 +1396,7 @@
+ 	    cli_strbcasestr(dent->d_name, ".cfg")  ||
+ 	    cli_strbcasestr(dent->d_name, ".pdb")  ||
+ 	    cli_strbcasestr(dent->d_name, ".wdb")  ||
++	    cli_strbcasestr(dent->d_name, ".ncdb")  ||
+ 	    cli_strbcasestr(dent->d_name, ".inc")   ||
+ 	    cli_strbcasestr(dent->d_name, ".cvd"))) {
+ 
+@@ -1485,6 +1509,11 @@
+     pthread_mutex_unlock(&cli_ref_mutex);
+ #endif
+ 
++#ifdef HAVE_NCORE
++    if(engine->ncore)
++	cli_ncore_unload(engine);
++#endif
++
+     if(engine->root) {
+ 	for(i = 0; i < CL_TARGET_TABLE_SIZE; i++) {
+ 	    if((root = engine->root[i])) {
+diff -Nura clamav-devel/shared/cfgparser.c clamav-devel.hwaccel/shared/cfgparser.c
+--- clamav-devel/shared/cfgparser.c	2007-09-07 14:57:04.000000000 +0200
++++ clamav-devel.hwaccel/shared/cfgparser.c	2007-08-13 18:45:55.000000000 +0200
+@@ -91,6 +91,7 @@
+     {"AllowSupplementaryGroups", OPT_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_FRESHCLAM},
+     {"SelfCheck", OPT_NUM, 1800, NULL, 0, OPT_CLAMD},
+     {"VirusEvent", OPT_FULLSTR, -1, NULL, 0, OPT_CLAMD},
++    {"NodalCoreAcceleration", OPT_BOOL, 0, NULL, 0, OPT_CLAMD},
+     {"ClamukoScanOnAccess", OPT_BOOL, -1, NULL, 0, OPT_CLAMD},
+     {"ClamukoScanOnOpen", OPT_BOOL, -1, NULL, 0, OPT_CLAMD},
+     {"ClamukoScanOnClose", OPT_BOOL, -1, NULL, 0, OPT_CLAMD},
diff --git a/contrib/init/FreeBSD5.2/clamav b/contrib/init/FreeBSD5.2/clamav
new file mode 100755
index 0000000..d138678
--- /dev/null
+++ b/contrib/init/FreeBSD5.2/clamav
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+# Copyright (C) 2004 Nigel Horne <njh at bandsman.co.uk>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+#
+# Install into /usr/local/etc/rc.d as /usr/local/etc/rc.d/clamav.sh
+#	chmod 755 /usr/local/etc/rc.d/clamav.sh
+#
+# Add lines such as this to /etc/rc.conf:
+#	clamd_enable="YES"
+#	clamav_milter_enable="YES"
+#	clamav_milter_flags="--max-children=2 --dont-wait --timeout=0 -P local:/var/run/clamav/clamav.sock --pidfile=/var/run/clamav/clamav-milter.pid --quarantine-dir=/var/run/clamav/quarantine"
+#
+# Tested with FreeBSD 5.2 and 5.3
+
+# PROVIDE: clamav
+# REQUIRE: NETWORKING
+# KEYWORD: FreeBSD
+
+. /etc/rc.subr
+
+# Don't allow files larger than 20M to be created, to limit DoS
+# Needs to be large enough to extract the signature files
+ulimit -f 20000
+
+name="clamd"
+rcvar="`set_rcvar`"
+command="/usr/local/sbin/${name}"
+
+load_rc_config $name
+run_rc_command "$1"
+
+name="clamav_milter"
+rcvar="`set_rcvar`"
+command="/usr/local/sbin/clamav-milter"
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/contrib/init/NetBSD2.0/clamav b/contrib/init/NetBSD2.0/clamav
new file mode 100755
index 0000000..a933458
--- /dev/null
+++ b/contrib/init/NetBSD2.0/clamav
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+# Copyright (C) 2004 Nigel Horne <njh at bandsman.co.uk>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+#
+# Install into /etc/rc.d as /etc/rc.d/clamav
+#	chmod 755 /etc/rc.d/clamav
+#
+# Add lines such as this to /etc/rc.conf:
+#	clamd="YES"
+#	clamav_milter="YES"
+#	clamav_milter_flags="--max-children=2 --dont-wait --timeout=0 -P local:/var/run/clamav/clamav.sock --pidfile=/var/run/clamav/clamav-milter.pid --quarantine-dir=/var/run/clamav/quarantine"
+#
+# Tested with NetBSD 2.0
+
+# PROVIDE: clamav
+# REQUIRE: NETWORKING
+# KEYWORD: NetBSD
+
+. /etc/rc.subr
+
+# Don't allow files larger than 20M to be created, to limit DoS
+# Needs to be large enough to extract the signature files
+ulimit -f 20000
+
+name="clamd"
+command="/usr/local/sbin/${name}"
+
+load_rc_config $name
+run_rc_command "$1"
+
+name="clamav_milter"
+command="/usr/local/sbin/clamav-milter"
+
+load_rc_config $name
+if [ $clamav_milter = YES ]; then
+	run_rc_command "$1"
+fi
diff --git a/contrib/init/OpenBSD3.6/README b/contrib/init/OpenBSD3.6/README
new file mode 100644
index 0000000..617a520
--- /dev/null
+++ b/contrib/init/OpenBSD3.6/README
@@ -0,0 +1,9 @@
+Edit /etc/rc.local adding the following before the "echo '.'" line, you
+should also call freshclam.
+
+if [ -x /usr/local/sbin/clamd ]; then
+	echo -n ' clamd'
+	# Don't allow files larger than 20M to be created, to limit DoS
+	# Needs to be large enough to extract the signature files
+	(ulimit -f 20000 && /usr/local/sbin/clamd)
+fi
diff --git a/contrib/init/RedHat/clamav-milter b/contrib/init/RedHat/clamav-milter
new file mode 100755
index 0000000..18bf5bf
--- /dev/null
+++ b/contrib/init/RedHat/clamav-milter
@@ -0,0 +1,104 @@
+#!/bin/sh
+#
+# clamav-milter This script starts and stops the clamav-milter daemon
+#
+# chkconfig: 2345 79 40
+#
+# description: clamav-milter is a daemon which hooks into sendmail and routes \
+#              email messages for virus scanning with ClamAV
+# processname: clamav-milter
+# pidfile: /var/lock/subsys/clamav-milter
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+# Source networking configuration.
+. /etc/sysconfig/network
+
+# Local clamav-milter config
+CLAMAV_FLAGS=
+test -f /etc/sysconfig/clamav-milter && . /etc/sysconfig/clamav-milter
+
+# Check that networking is up.
+[ ${NETWORKING} = "no" ] && exit 0
+
+[ -x /usr/local/sbin/clamav-milter ] || exit 0
+PATH=$PATH:/usr/bin:/usr/local/sbin:/usr/local/bin
+
+RETVAL=0
+
+# Clamav-milter must have write access to the pid file, /var/run is not suitable
+default_pidfile=
+[ -d /var/run/clamav-milter ] && default_pidfile=/var/run/clamav-milter/clamav-milter.pid
+[ -d /var/clamav ] && default_pidfile=/var/clamav/clamav-milter.pid
+pidfile=${PIDFILE:-$default_pidfile}
+
+lockfile=/var/lock/subsys/clamav-milter
+
+start() {
+        echo -n "Starting clamav-milter: "
+	# Don't allow files larger than 25M to be created, to limit DoS
+	# Needs to be large enough to extract the signature files
+	ulimit -f 25600
+	if [ ! -z $pidfile ]; then
+		CLAMAV_PID=--pidfile=${pidfile}
+		PID=`pidofproc -p ${pidfile} clamav-milter`
+	else
+		CLAMAV_PID=
+		PID=`pidofproc clamav-milter`
+	fi
+	[ -n "$PID" ] && echo " already running!" && return 1
+        LANG= daemon clamav-milter $CLAMAV_PID ${CLAMAV_FLAGS}
+        RETVAL=$?
+	[ ! -z $pidfile -a -f $pidfile ] && sed -i -e 's/-//' $pidfile
+        echo
+	test $RETVAL -eq 0 && touch ${lockfile}
+	return $RETVAL
+}
+
+stop() {
+        echo -n "Shutting down clamav-milter: "
+	if [ ! -z $pidfile ]; then
+	        killproc -p ${pidfile} clamav-milter
+	else
+		killproc clamav-milter
+	fi
+        RETVAL=$?
+        echo
+	test $RETVAL -eq 0 && rm -f ${lockfile} ${pidfile}
+}
+
+restart() {
+	stop
+	start
+}
+
+# See how we were called.
+case "$1" in
+  start)
+        # Start daemon.
+	start
+        ;;
+  stop)
+        # Stop daemon.
+	stop
+        ;;
+  restart|reload)
+	restart
+        ;;
+  condrestart)
+	test -f ${lockfile} && $0 restart || :
+        ;;
+  status)
+	if [ ! -z $pidfile ]; then
+	        status -p ${pidfile} clamav-milter
+	else
+		status clamav-milter
+	fi
+        ;;
+  *)
+        echo "Usage: $0 {start|stop|reload|restart|condrestart|status}"
+        exit 1
+esac
+
+exit $?
diff --git a/contrib/init/RedHat/clamd b/contrib/init/RedHat/clamd
new file mode 100755
index 0000000..3e9a214
--- /dev/null
+++ b/contrib/init/RedHat/clamd
@@ -0,0 +1,89 @@
+#! /bin/bash
+#
+# crond   Start/Stop the clam antivirus daemon.
+#
+# chkconfig: 2345 70 41
+# description: clamd is a standard Linux/UNIX program that scans for Viruses.
+# processname: clamd
+# config: /usr/local/etc/clamd.conf
+# pidfile: /var/lock/subsys/clamd
+
+# Source function library.
+. /etc/init.d/functions
+
+RETVAL=0
+
+# See how we were called.
+
+prog="clamd"
+progdir="/usr/local/sbin"
+
+# Source configuration
+if [ -f /etc/sysconfig/$prog ] ; then
+	. /etc/sysconfig/$prog
+fi
+
+start() {
+	echo -n $"Starting $prog: "
+	# Don't allow files larger than 20M to be created, to limit DoS
+	# Needs to be large enough to extract the signature files
+	ulimit -f 20000
+        LANG= daemon $progdir/$prog
+	RETVAL=$?
+	echo
+	[ $RETVAL -eq 0 ] && touch /var/lock/subsys/clamd
+	return $RETVAL
+}
+
+stop() {
+	echo -n $"Stopping $prog: "
+	# Would be better to send QUIT first, then killproc if that fails
+	killproc $prog
+	RETVAL=$?
+	echo
+	[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/clamd
+	return $RETVAL
+}
+
+rhstatus() {
+	status clamd
+}
+
+restart() {
+	stop
+	start
+}
+
+reload() {
+	echo -n $"Reloading clam daemon configuration: "
+	killproc clamd -HUP
+	retval=$?
+	echo
+	return $RETVAL
+}
+
+case "$1" in
+  start)
+	start
+	;;
+  stop)
+	stop
+	;;
+  restart)
+	restart
+	;;
+  reload)
+	reload
+	;;
+  status)
+	rhstatus
+	;;
+  condrestart)
+	[ -f /var/lock/subsys/clamd ] && restart || :
+	;;
+  *)
+	echo $"Usage: $0 {start|stop|status|reload|restart|condrestart}"
+	exit 1
+esac
+
+exit $?
diff --git a/contrib/init/Solaris10/clamav-milter b/contrib/init/Solaris10/clamav-milter
new file mode 100644
index 0000000..74c8380
--- /dev/null
+++ b/contrib/init/Solaris10/clamav-milter
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+CONF_FILE=/usr/local/etc/clamd.conf
+RUNDIR=/var/run/clamav
+CLAMAV_MILTER_FLAGS="-l --max-children=2 local:$RUNDIR/clmilter.sock"
+
+if [ ! -f $CONF_FILE ]; then
+	exit 0
+fi
+
+if [ ! -d $RUNDIR ]; then
+	/usr/bin/mkdir -p -m 700 $RUNDIR
+	USER=`fgrep User ${CONF_FILE} | awk '{ print $2 }'`
+	if [ x$USER != x ]; then
+		chown $USER $RUNDIR
+	fi
+fi
+
+case "$1" in
+	start)
+		/usr/local/sbin/clamav-milter $CLAMAV_MILTER_FLAGS
+		;;
+	stop)
+		kill `ps -ef | awk '$NF ~ /clamav-milter/ { print $2 }'` > /dev/null 2>&1
+		;;
+	*)
+		echo "usage: $0 {start|stop}"
+esac
diff --git a/contrib/init/Solaris10/clamd b/contrib/init/Solaris10/clamd
new file mode 100755
index 0000000..9afa9f2
--- /dev/null
+++ b/contrib/init/Solaris10/clamd
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+CONF_FILE=/usr/local/etc/clamd.conf
+
+if [ ! -f $CONF_FILE ]; then
+	exit 0
+fi
+
+case "$1" in
+	start)
+		/usr/local/sbin/clamd
+		;;
+	stop)
+		kill `ps -ef | awk '$NF ~ /clamd/ { print $2 }'` > /dev/null 2>&1
+		;;
+	*)
+		echo "usage: $0 {start|stop}"
+esac
diff --git a/contrib/init/SuSE/clamd b/contrib/init/SuSE/clamd
new file mode 100755
index 0000000..2e483ff
--- /dev/null
+++ b/contrib/init/SuSE/clamd
@@ -0,0 +1,101 @@
+#! /bin/sh
+# v1.2 05-2004, martin fuxa, yeti at email.cz
+#
+### BEGIN INIT INFO
+# Provides:       clamd
+# Required-Start: 
+# Required-Stop:  
+# Default-Start:  2 3 5
+# Default-Stop:   0 1 2 6
+# Description:    Control clamav daemon.
+### END INIT INFO
+#
+### HISTORY
+# 2004-05-27 ADD - FreshClam code
+
+# Variables
+PID="/var/run/clamd.pid"
+SBIN="/usr/local/sbin/clamd"
+CONF="/etc/clamav.conf"
+WHAT="Clam AntiVirus"
+
+# START_FRESHCLAM value: 1=true, 0 false
+START_FRESHCLAM=1
+FRESHCLAM_SBIN="/usr/local/bin/freshclam"
+FRESHCLAM_CONF="/etc/freshclam.conf"
+FRESHCLAM_WHAT="FreshClam"
+
+# Source SuSE config
+. /etc/rc.status
+
+test -x $SBIN || exit 5
+test -e $CONF || exit 5
+
+if [ $START_FRESHCLAM = 1 ]
+then
+    test -x $FRESHCLAM_SBIN || exit 5
+    test -e $FRESHCLAM_CONF || exit 5
+fi
+
+# First reset status of this service
+rc_reset
+
+# Process request
+case "$1" in
+    start)
+        if [ $START_FRESHCLAM = 1 ]
+        then
+            echo -n "Starting ${FRESHCLAM_WHAT} ${FRESHCLAM_CONF}"
+            startproc $FRESHCLAM_SBIN --daemon --config-file=${FRESHCLAM_CONF}
+            rc_status -v
+        fi
+        echo -n "Starting ${WHAT} ${CONF} "
+        ## Start daemon with startproc(8). If this fails
+        ## the echo return value is set appropriate.
+        startproc $SBIN $CONF
+        # Remember status and be verbose
+        rc_status -v
+        ## start freshclam
+        
+    ;;
+    stop)
+        echo -n "Shutting down ${WHAT}"
+        ## Stop daemon with killproc(8) and if this fails
+        ## set echo the echo return value.
+        killproc -TERM $SBIN
+        # Remember status and be verbose
+        rc_status -v
+        if [ $START_FRESHCLAM = 1 ]
+        then
+            echo -n "Shutting down ${FRESHCLAM_WHAT}"
+            killproc -TERM $FRESHCLAM_SBIN
+            rc_status -v
+        fi
+    ;;
+    restart)
+        ## Stop the service and regardless of whether it was
+        ## running or not, start it again.
+        $0 stop
+        $0 start
+        # Remember status and be quiet
+        rc_status
+    ;;
+    status)
+        echo -n "Checking for ${WHAT} "
+        checkproc $SBIN
+        rc_status -v
+        if [ $START_FRESHCLAM = 1 ]
+        then
+            echo -n "Checking for ${FRESHCLAM_WHAT} "
+            checkproc $FRESHCLAM_SBIN
+            rc_status -v
+        fi
+    ;;
+
+    *)
+        echo "Usage: $0 {start|stop|status|restart}"
+        exit 1
+    ;;
+esac
+rc_exit
+### END
diff --git a/contrib/make-clamav-milter-conf.pl b/contrib/make-clamav-milter-conf.pl
new file mode 100644
index 0000000..9d6e34c
--- /dev/null
+++ b/contrib/make-clamav-milter-conf.pl
@@ -0,0 +1,429 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Getopt::Long qw(:config gnu_getopt);
+
+sub wwarn {
+	my $w = shift;
+	warn "WARNING: $w\n";
+}
+
+sub tosconf {
+	my ($cfg, $v) = @_;
+	if($v) {
+		my $sep = $v=~/ / ? '"' : '';
+		$v = "\n$cfg $sep$v$sep";
+	}
+	return $v;
+}
+
+my $notify = 0;
+my $black = 0;
+my $report = 0;
+my $debug = 0;
+my $sign = 0;
+my $broad = 0;
+my $forge = 0;
+my $sanity = 1;
+my $blackhole = 0;
+my $quarantine = 0;
+my $rate = 0;
+my $monitor = 0;
+my $oninfected = 'Reject';
+my $onfail = 'Defer';
+my @localnets = ();
+my $whitelist = '';
+my $config = '';
+my $chroot = '';
+my $pidfile = '';
+my $addheader = 1;
+my $tcpclamds = '';
+my $localclamd;
+
+GetOptions (
+	"from|a:s" => \$notify,
+	"bounce|b" => \$notify,
+	"headers|H" => \$notify,
+	"postmaster|p=s" => \$notify,
+	"postmaster-only|P" => \$notify,
+	"template-file|t=s" => \$notify,
+	"template-headers|1=s" => \$notify,
+	"quiet|q" => sub { $notify = 0 },
+	"dont-blacklist|K=s" => \$black,
+	"blacklist-time|k=i" => \$black,
+	"report-phish|r=s" => \$report,
+	"report-phish-false-positives|R=s" => \$report,
+	"debug-level|x=i" => \$debug,
+	"debug|D" => \$debug,
+	"sign|S" => \$sign,
+	"signature-file|F=s" => \$sign,
+	"broadcast|B=s" => \$broad,
+	"detect-forged-local-address|L" => \$forge,
+	"dont-sanitise|z" => sub { $sanity = 0 },
+	"black-hole-mode|2" => \$blackhole,
+	"quarantine|Q=s" => \$quarantine,
+	"quarantine-dir|U" => \$quarantine,
+	"max-children|m=i" => \$rate,
+	"dont-wait|w" => \$rate,
+	"timeout|T=i" => \$rate,
+	"freshclam-monitor|M=i" => \$monitor,
+	"external|e" => sub { },
+	"no-check-cf" => sub { },
+	"sendmail-cf|0=s" => sub { },
+	"advisory|A" => sub { $oninfected='Accept'; },
+	"noreject|N" => sub { $oninfected='Blackhole'; },
+	"dont-scan-on-error|d" => sub { $onfail = 'Accept'; },
+	"ignore|I=s" => \@localnets,
+	"local|l" => sub { @localnets = (); },
+	"force-scan|f" => sub { @localnets = (); },
+	"whitelist-file|W=s" => \$whitelist,
+	"config-file|c=s" => \$config,
+	"chroot|C=s" => \$chroot,
+	"pidfile|i=s" => \$pidfile,
+	"noxheader|n" => sub { $addheader = 0},
+	"outgoing|o" => sub { push(@localnets, 'localhost'); },
+	"server|s=s" => \$tcpclamds,
+) or die "huh?!";
+
+my %clamds = ();
+foreach (split(/:/, $tcpclamds)) {
+	$clamds{"tcp:$_:3310"}++;
+}
+
+my $user = '';
+my $supgrp = '';
+my $syslog = '';
+my $facility = '';
+my $tempdir = '';
+my $maxsize = '';
+
+if ($config) {
+	my $port = 0;
+	my $ip = '';
+	my $lsock = '';
+	open CFG, "<$chroot/$config" or die "failed to open clamd config file $config";
+	while (<CFG>) {
+		chomp;
+		$port = $1 if /^TCPSocket\s+(.*)$/;
+		$ip = $1 if /^TCPAddr\s+(.*)$/;
+		$lsock = $1 if /^LocalSocket\s+(.*)$/;
+		$user = $1 if /^User\s+(.*)$/;
+		$supgrp = $1 if /^AllowSupplementaryGroups\s+(.*)$/;
+		$syslog = $1 if /^LogSyslog\s+(.*)$/;
+		$facility = $1 if /^LogFacility\s+(.*)$/;
+		$tempdir = $1 if /^TemporaryDirectory\s+(.*)$/;
+		$maxsize = $1 if /^MaxFileSize\s+(.*)$/;
+	}
+	close(CFG);
+	if ($lsock) {
+		$clamds{"unix:$lsock"}++;
+	} elsif ($port) {
+		if($ip) {
+			$clamds{"tcp:$ip:$port"}++;
+		} else {
+			$clamds{"tcp:localhost:$port"}++;
+		}
+	}
+}
+
+die "FAIL: No socket provided" unless $ARGV[0];
+die "FAIL: Unable to determine clamd socket\n" unless scalar keys %clamds;
+
+wwarn "Notifications and bounces are no longer supported.
+As a result the following command line options cannot be converted into new config options:
+ --from (-a)
+ --bounce (-b)
+ --headers (-H)
+ --postmaster (-p)
+ --postmaster-only (-P)
+ --template-file (-t)
+ --template-headers (-1)
+" if $notify;
+
+wwarn "Temporary blacklisting of ip addresses is no longer supported.
+As a result the following command line options cannot be converted into new config options:
+ --dont-blacklist (-K)
+ --blacklist-time (-k)
+" if $black;
+
+wwarn "Phising reports are no longer supported.
+As a result the following command line options cannot be converted into new config options:
+ --report-phish (-r)
+ --report-phish-false-positives (-R)
+" if $report; 
+
+wwarn "The options --debug (-D) and --debug-level (-x) are no longer supported.
+Please set LogVerbose to yes instead
+" if $debug;
+
+wwarn "Message scan signatures are no longer supported.
+As a result the following command line options cannot be converted into new config options:
+ --sign (-S)
+ --signature-file (-F)
+" if $sign;
+
+wwarn "Broadcasting is no longer supported\n" if $broad;
+
+wwarn "Forgery detection is no longer supported\n" if $forge;
+
+wwarn "Please be aware that email addresses are no longer checked for weird characters like '|' and ';'\n" if $sanity;
+
+wwarn "Blackhole mode is no longer available\nIf you have a lot users aliased to /dev/null you may want to whitelist them instead\n" if $blackhole;
+
+wwarn "Quarantine now achieved via native milter support\nPlease read more about it in the example config file\n" if $quarantine;
+
+wwarn "Rate limiting in the milter is no longer supported.
+As a result the following command line options cannot be converted into new config options:
+ --max-children (-m)
+ --dont-wait (-w)
+ --timeout (-T)
+Please make use of the native Sendmail / Postfix rate limiting facilities
+" if $rate;
+
+wwarn "The option --freshclam-monitor (-M) only made sense in internal mode\nPlease configure freshclam to notify clamd about updates instead\n" if $monitor;
+
+wwarn "Your whitelist file path has been preserved, however please be aware that its syntax is changed\nInstead of a full email address you are now allowed to use regexes. See the example clamav-milter.conf file for more info.\n" if $whitelist;
+
+wwarn "Here is the auto generated config file. Please review:\n";
+
+my $mysock = tosconf('MilterSocket', $ARGV[0]);
+$chroot = tosconf('Chroot', $chroot);
+$pidfile = tosconf('PidFile', $pidfile);
+$oninfected = tosconf('OnInfected', $oninfected);
+$onfail = tosconf('OnFail', $onfail);
+$whitelist = tosconf('Whitelist', $whitelist);
+$addheader = $addheader ? "\nAddHeader Yes" : '';
+$user = tosconf('User', $user);
+$supgrp = $supgrp ? "\nAllowSupplementaryGroups Yes" : '';
+if ($syslog =~ /yes/i) {
+	$syslog = "LogSyslog yes";
+	$facility = tosconf('LogFacility', $facility);
+} else {
+	$syslog = '';
+	$facility = '';
+}
+$tempdir = tosconf('TemporaryDirectory', $tempdir);
+$maxsize = tosconf('MaxFileSize', $maxsize);
+
+print <<BLOCK1;
+##
+## Example config file for clamav-milter
+## (automatically generated by make-clamav-milter-conf.pl)
+##
+
+# Comment or remove the line below.
+Example
+
+
+##
+## Main options
+##
+
+# Define the interface through which we communicate with sendmail
+# This option is mandatory! Possible formats are:
+# [[unix|local]:]/path/to/file - to specify a unix domain socket
+# inet:port@[hostname|ip-address] - to specify an ipv4 socket
+# inet6:port@[hostname|ip-address] - to specify an ipv6 socket
+#
+# Default: no default
+#MilterSocket /tmp/clamav-milter.socket
+#MilterSocket inet:7357$mysock
+
+# Remove stale socket after unclean shutdown.
+#
+# Default: yes
+#FixStaleSocket yes
+
+# Run as another user (clamav-milter must be started by root for this option to work)
+#
+# Default: unset (don't drop privileges)
+#User clamav$user
+
+# Initialize supplementary group access (clamd must be started by root).
+#
+# Default: no
+#AllowSupplementaryGroups no$supgrp
+
+# Waiting for data from clamd will timeout after this time (seconds).
+# Value of 0 disables the timeout.
+#
+# Default: 120
+#ReadTimeout 300
+
+# Don't fork into background.
+#
+# Default: no
+#Foreground yes
+
+# Chroot to the specified directory.
+# Chrooting is performed just after reading the config file and before dropping privileges.
+#
+# Default: unset (don't chroot)
+#Chroot /newroot$chroot
+
+# This option allows you to save a process identifier of the listening
+# daemon (main thread).
+#
+# Default: disabled
+#PidFile /var/run/clamd.pid$pidfile
+
+# Optional path to the global temporary directory.
+# Default: system specific (usually /tmp or /var/tmp).
+#
+#TemporaryDirectory /var/tmp$tempdir
+
+##
+## Clamd options
+##
+
+# Define the clamd socket to connect to for scanning.
+# If not set (the default), clamav-milter uses internal mode.
+# This option is mandatory! Syntax:
+# ClamdSocket unix:path
+# ClamdSocket tcp:host:port
+# The first syntax specifies a local unix socket (needs an bsolute path) e.g.:
+#     ClamdSocket unix:/var/run/clamd/clamd.socket
+# The second syntax specifies a tcp local or remote tcp socket: the
+# host can be a hostname or an ip address; the ":port" field is only required
+# for IPv6 addresses, otherwise it defaults to 3310
+#     ClamdSocket tcp:192.168.0.1
+#
+# This option can be repeated several times with different sockets or even
+# with the same socket: clamd servers will be selected in a round-robin fashion.
+#
+# Default: no default
+#ClamdSocket tcp:scanner.mydomain:7357
+BLOCK1
+
+print "ClamdSocket \"$_\"\n" foreach (keys %clamds);
+print <<BLOCK2;
+
+
+##
+## Exclusions
+##
+
+# Messages originating from these hosts/networks will not be scanned
+# This option takes a host(name)/mask pair in CIRD notation and can be
+# repeated several times. If "/mask" is omitted, a host is assumed.
+# To specify a locally orignated, non-smtp, email use the keyword "local"
+#
+# Default: unset (scan everything regardless of the origin)
+#LocalNet local
+#LocalNet 192.168.0.0/24
+#LocalNet 1111:2222:3333::/48
+
+# This option specifies a file which contains a list of POSIX regular
+# expressions. Addresses (sent to or from - see below) matching these regexes
+# will not be scanned.  Optionally each line can start with the string "From:"
+# or "To:" (note: no whitespace after the colon) indicating if it is, 
+# respectively, the sender or recipient that is to be whitelisted.
+# If the field is missing, "To:" is assumed.
+# Lines starting with #, : or ! are ignored.
+#
+# Default unset (no exclusion applied)
+#Whitelist /etc/whitelisted_addresses$whitelist
+
+
+##
+## Actions
+##
+
+# The following group of options controls the delievery process under
+# different circumstances.
+# The following actions are available:
+# - Accept
+#   The message is accepted for delievery
+# - Reject
+#   Immediately refuse delievery (a 5xx error is returned to the peer)
+# - Defer
+#   Return a temporary failure message (4xx) to the peer
+# - Blackhole (not available for OnFail)
+#   Like accept but the message is sent to oblivion
+# - Quarantine (not available for OnFail)
+#   Like accept but message is quarantined instead of being deilievered
+#   In sendmail the quarantine queue can be examined via mailq -qQ
+#   For Postfix this causes the message to be accepted but placed on hold
+# 
+# Action to be performed on clean messages (mostly useful for testing)
+# Default Accept
+#OnClean Accept
+
+# Action to be performed on infected messages
+# Default: Quarantine
+#OnInfected Quarantine$oninfected
+
+# Action to be performed on error conditions (this includes failure to
+# allocate data structures, no scanners available, network timeouts,
+# unknown scanner replies and the like)
+# Default Defer
+#OnFail Defer$onfail
+
+# If this option is set to Yes, an "X-Virus-Scanned" and an "X-Virus-Status"
+# headers will be attached to each processed message, possibly replacing
+# existing headers. 
+# Default: No
+#AddHeader Yes$addheader
+
+
+##
+## Logging options
+##
+
+# Uncomment this option to enable logging.
+# LogFile must be writable for the user running daemon.
+# A full path is required.
+#
+# Default: disabled
+#LogFile /tmp/clamav-milter.log
+
+# By default the log file is locked for writing - the lock protects against
+# running clamav-milter multiple times.
+# This option disables log file locking.
+#
+# Default: no
+#LogFileUnlock yes
+
+# Maximum size of the log file.
+# Value of 0 disables the limit.
+# You may use 'M' or 'm' for megabytes (1M = 1m = 1048576 bytes)
+# and 'K' or 'k' for kilobytes (1K = 1k = 1024 bytes). To specify the size
+# in bytes just don't use modifiers.
+#
+# Default: 1M
+#LogFileMaxSize 2M
+
+# Log time with each message.
+#
+# Default: no
+#LogTime yes
+
+# Use system logger (can work together with LogFile).
+#
+# Default: no
+#LogSyslog yes$syslog
+
+# Specify the type of syslog messages - please refer to 'man syslog'
+# for facility names.
+#
+# Default: LOG_LOCAL6
+#LogFacility LOG_MAIL$facility
+
+# Enable verbose logging.
+#
+# Default: no
+#LogVerbose yes
+
+
+##
+## Limits
+##
+
+# Messages larger than this value won't be scanned.
+# Default: 25M
+#MaxFileSize 150M$maxsize
+BLOCK2
+
+
diff --git a/contrib/mpoolparse/mpoolparse.pl b/contrib/mpoolparse/mpoolparse.pl
new file mode 100644
index 0000000..70b1ad8
--- /dev/null
+++ b/contrib/mpoolparse/mpoolparse.pl
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+my %frags = ();
+
+while (<>) {
+	chomp;
+	next unless /^LibClamAV Warning: [mc]alloc .* size (\d+) .*$/;
+	$frags{$1}++;
+}
+
+foreach (sort {$a<=>$b} (keys(%frags))) {
+	print "$_, /* ($frags{$_}) */\n";
+}
+
diff --git a/contrib/old-clamav-milter/INSTALL b/contrib/old-clamav-milter/INSTALL
new file mode 100644
index 0000000..0f3ead0
--- /dev/null
+++ b/contrib/old-clamav-milter/INSTALL
@@ -0,0 +1,439 @@
+1. BUILD INSTRUCTIONS
+
+A makefile was supplied with this which should have built the program. If it
+fails please let us know, and here are some hints for building on different
+platforms. You will need to set --enable-milter when running configure for
+the automatic build to work.
+
+Tested OK on Linux/x86 with gcc3.2.
+	cc -O3 -pedantic -Wuninitialized -Wall -pipe -mcpu=pentium -march=pentium -fomit-frame-pointer -ffast-math -finline-functions -funroll-loops clamav-milter.c -pthread -lmilter ../libclamav/.libs/libclamav.a ../clamd/cfgfile.o ../clamd/others.o
+
+Compiles OK on Linux/x86 with tcc 0.9.16, but fails to link errors with 'atexit'
+	tcc -g -b -lmilter -lpthread clamav-milter.c...
+
+Fails to compile on Linux/x86 with icc6.0 (complains about stdio.h...)
+	icc -O3 -tpp7 -xiMKW -ipo -parallel -i_dynamic -w2 clamav-milter.c...
+Fails to build on Linux/x86 with icc7.1 with -ipo (fails on libclamav.a - keeps saying run ranlib). Otherwise it builds and runs OK.
+	icc -O2 -tpp7 -xiMKW -parallel -i_dynamic -w2 -march=pentium4 -mcpu=pentium4 clamav-milter.c...
+
+Tested with Electric Fence 2.2.2, and the bounds checking C compiler from
+	http://sourceforge.net/projects/boundschecking/
+
+Compiles OK on Linux/ppc (YDL2.3) with gcc2.95.4. Needs -lsmutil to link.
+	cc -O3 -pedantic -Wuninitialized -Wall -pipe -fomit-frame-pointer -ffast-math -finline-functions -funroll-loop -pthread -lmilter ../libclamav/.libs/libclamav.a ../clamd/cfgfile.o ../clamd/others.o -lsmutil
+I haven't tested it further on this platform yet.
+YDL3.0 should compile out of the box
+
+Linux/sparc (Gentoo 2004.2) comes with a sendmail that doesn't support MILTER,
+so *before* running "configure --enable-milter", download from
+http://www.sendmail.org/ftp, then:
+	cd .../sendmail-source-directory
+	sh Build
+	make install
+	cd libmilter
+	make install
+
+Sendmail on MacOS/X (10.1) is provided without a development package so this
+can't be run "out of the box"
+
+Solaris 8 doesn't have milter support so clamav-milter won't work unless you
+rebuild sendmail from source.
+
+FreeBSD4.7 use /usr/local/bin/gcc30. GCC3.0 is an optional extra on
+FreeBSD. It comes with getopt.h which is handy. To link you need
+-lgnugetopt
+	gcc30 -O3 -DCONFDIR=\"/usr/local/etc\" -I. -I.. -I../clamd -I../libclamav -pedantic -Wuninitialized -Wall -pipe -mcpu=pentium -march=pentium -fomit-frame-pointer -ffast-math -finline-functions -funroll-loops clamav-milter.c -pthread -lmilter ../libclamav/.libs/libclamav.a ../clamd/cfgfile.o ../clamd/others.o -lgnugetopt
+
+FreeBSD4.8: compiles out of the box with either gcc2.95 or gcc3
+
+NetBSD2.0: compiles out of the box
+
+OpenBSD3.4: the supplied sendmail does not come with Milter support.
+Do this *before* running configure (thanks for Per-Olov Sjöhol
+<peo_s at incedo.org> for these instructions).
+
+	echo WANT_LIBMILTER=1 > /etc/mk.conf
+	cd /usr/src/gnu/usr.sbin/sendmail
+	make depend
+	make
+	make install
+	kill -HUP `sed q /var/run/sendmail.pid`
+
+Then do this to make the milter headers available to clamav...
+(the libmilter.a file is already in the right place after the sendmail
+recompiles above)
+
+	cd /usr/include
+	ln -s ../src/gnu/usr.sbin/sendmail/include/libmilter libmilter
+
+Solaris 9 and FreeBSD5 have milter support in the supplied sendmail, but
+doesn't include libmilter so you can't develop milter applications on it.
+Go to sendmail.org, download the latest sendmail, cd to libmilter and
+"make install" there.
+
+Needs -lresolv on Solaris, for res_close().
+
+If, when building clamav-milter, you see the error
+	"undefined reference to smfi_opensocket",
+it means that your sendmail installation is broken. More specifically it means
+that your installed version of libmilter does not agree with your installed
+version of Sendmail. Naturally they must be the same. Check to see if you have
+more than one mfapi.h on your system; if you installed sendmail from source,
+did you remember to install libmilter at the same time? You can ensure that
+your Sendmail is correctly installed if you follow these instructions:
+	cd .../sendmail-source-directory
+	sh Build
+	make install
+	cd libmilter
+	make install
+
+2. INSTALLATION
+
+Install into /usr/local/sbin/clamav-milter.
+
+Ensure that your sendmail supports milters by running
+	/usr/lib/sendmail -d0 < /dev/null | fgrep MILTER
+or
+	/usr/sbin/sendmail -d0 < /dev/null | fgrep MILTER
+
+You should see something like:
+	MATCHGECOS MILTER MIME7TO8 MIME8TO7 NAMED_BIND NETINET NETINET6
+It doesn't matter exactly what you see, as long as the word MILTER is printed.
+
+If you see no output you MUST upgrade your sendmail.
+
+See http://www.nmt.edu/~wcolburn/sendmail-8.12.5/libmilter/docs/sample.html
+
+2.1 LINUX (RedHat, Fedora, YellowDog etc)
+
+Installations for RedHat Linux and it's derivatives such as YellowDog:
+	Ensure that you have the sendmail-devel RPM installed
+	Add to /etc/mail/sendmail.mc before the MAILER statement:
+	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clmilter.sock, F=, T=S:4m;R:4m;C:30s;E:10m')dnl
+	define(`confINPUT_MAIL_FILTERS', `clamav')
+
+	Note that the INPUT_MAIL_FILTER line must come before the
+		confINPUT_MAIL_FILTERS line.
+
+	Don't worry that the file /var/run/clamav/clmilter.sock doesn't exist,
+		clamav-milter will create it for you. However you will need
+		to create the directory /var/run/clamav (usually owned
+		by user clamav, mode 700).
+
+	Check entry in /usr/local/etc/clamd.conf of the form:
+	LocalSocket /var/run/clamav/clamd.sock
+
+	If you already have a filter (such as spamassassin-milter from
+	http://savannah.nongnu.org/projects/spamass-milt) add it thus:
+	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clmilter.sock, F=, T=S:4m;R:4m')dnl
+	INPUT_MAIL_FILTER(`spamassassin', `S=local:/var/run/spamass.sock, F=, T=C:15m;S:4m;R:4m;E:10m')
+	define(`confINPUT_MAIL_FILTERS', `spamassassin,clamav')dnl
+
+	mkdir /var/run/clamav
+	chown clamav /var/run/clamav	(if you use User clamav in clamd.conf)
+	chmod 700 /var/run/clamav
+
+	Where /var/run/spamass.sock is the location of the spamass-milt
+	socket file (on some systems it is in /var/run/sendmail/spamass.sock).
+
+2.2 LINUX (Debian)
+
+Installations for Debian Linux:
+	As above for RedHat, except that you need the libmilter-dev package:
+		apt-get install libmilter-dev
+	To use TCPwrappers you need to:
+		apt-get install libwrap0-dev
+
+2.3 FreeBSD
+
+Installations for FreeBSD5 (may be true for other BSDs)
+	Add to /etc/mail/freebsd.mc:
+	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clmilter.sock, F=, T=S:4m;R:4m')dnl
+	define(`confINPUT_MAIL_FILTERS', `clamav')
+
+	Check entry in /usr/local/etc/clamd.conf of the form:
+	LocalSocket /var/run/clamav/clamd.sock
+
+	If you already have a filter (such as spamassassin-milter from
+	http://savannah.nongnu.org/projects/spamass-milt) add it thus:
+	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clmilter.sock, F=, T=S:4m;R:4m')dnl
+	INPUT_MAIL_FILTER(`spamassassin', `S=local:/var/run/spamass.sock, F=, T=C:15m;S:4m;R:4m;E:10m')
+	define(`confINPUT_MAIL_FILTERS', `spamassassin,clamav')dnl
+
+	mkdir /var/run/clamav
+	chown clamav /var/run/clamav	(if you use User clamav in clamd.conf)
+	chmod 700 /var/run/clamav
+
+	Where /var/run/spamass.sock is the location of the spamass-milt
+	socket file (on some systems it is in /var/run/sendmail/spamass.sock).
+
+FreeBSD5.3 sendmail comes without libmilter support. You can upgrade by
+	cd /usr/ports/mail/sendmail
+	make install
+
+This may overwrite your existing sendmail configuration, so ensure
+that you back up first.
+
+You should have received a script to install into /etc/rc.d as /etc/rc.d/clamav
+with this software. Add to /etc/rc.conf:
+	clamd_enable="YES"
+	clamav_milter_enable="YES"
+	clamav_milter_flags="--max-children=2 --dont-wait --timeout=0 -P local:/var/run/clamav/clmilter.sock --pidfile=/var/run/clamav/clamav-milter.pid --quarantine-dir=/var/run/clamav/quarantine"
+
+2.4 Solaris 10
+
+Solaris 10 should install out of the box. Edit /etc/mail/cf/cf/main.mc adding
+the line:
+	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clamav-milter, F=, T=S:4m;R:4m')dnl
+Then:
+	cp /etc/mail/cf/cf/main.cf /etc/mail/main.cf
+	/usr/local/sbin/clamav-milter local:/var/run/clamav/clamav-milter
+	mkdir /var/run/clamav
+	chown clamav /var/run/clamav	(if you use User clamav in clamd.conf)
+	chmod 700 /var/run/clamav
+
+You should have received a script to install into /etc/init.d as
+/etc/init.d/clamav-milter. Then:
+
+	chmod 755 /etc/init.d/clamav-milter
+	cd /etc
+	ln init.d/clamav-milter rc2.d/S90clamav-milter
+	ln init.d/clamav-milter rc0.d/K90clamav-milter
+	/etc/init.d/clamav-milter start
+	/etc/init.d/sendmail restart
+
+2.5 OpenBSD4.1:
+
+OpenBSD4.1 should install out of the box.
+Edit <your .mc file>, or if you have none: cd into /usr/share/sendmail/cf,
+copy openbsd-proto.mc custom.mc, edit custom.mc adding:
+	INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav/clamav-milter, F=, T=S:4m;R:4m')dnl
+Then run
+	m4 ../m4/cf.m4 custom.mc >/etc/mail/localhost.cf
+and finally restart sendmail by sending it a SIGHUP
+
+2.6 General Installation Issues
+
+You may find INPUT_MAIL_FILTERS is not needed on your machine, however it
+is recommended by the Sendmail documentation and I recommend going along
+with that.
+
+If you see an unsafe socket error from sendmail, it means that the permissions
+of the /var/run/clamav directory are too open; check you have correctly run
+chown and chmod. It may also mean that clamav-milter hasn't started, run
+ps and check your logs.
+
+The above example shows clamav-milter, clamd and sendmail all on the
+same machine, however using TCP they may reside on different machines,
+indeed clamav-milter is capable of talking to multiple clamds for redundancy
+and load balancing. An alternative load balancer is PEN (http://siag.nu/pen/).
+
+I suggest putting SpamAssassin first since you're more likely to get spam
+than a virus/worm sent to you.
+
+Add to /etc/sysconfig/clamav-milter
+	CLAMAV_FLAGS="local:/var/run/clamav/clmilter.sock"
+or if clamd is on a different machine
+	CLAMAV_FLAGS="--server=192.168.1.9 local:/var/run/clamav/clmilter.sock"
+
+If you want clamav-milter to listen on TCP for communication with sendmail,
+for example if they are on different machines use inet:<port>.
+On machine A (running sendmail) you would have in sendmail.mc:
+	INPUT_MAIL_FILTER(`clamav', `S=inet:3311 at machineb, F=T, T=S:4m;R:4m')dnl
+On machine B (running clamav-milter) you would start up clamav-milter thus:
+	clamav-milter inet:3311
+
+You should have received a script to put into /etc/init.d with this software.
+
+You should always start clamd before clamav-milter.
+
+You may also think about the F= entry in sendmail.mc, since it tells sendmail
+what to do with emails if clamav-milter is not running. Setting F=T will tell
+the remote end to resend later (temporary failure), setting F=R will reject
+the email (permanent failure) and setting F= will pass the email through as
+though clamav-milter were not installed, in this case you should warn your
+users that emails are not being scanned. We recommend setting F=T.
+
+You may wish to experiment with the T= entry which governs timeout options. You
+MUST set some type of timeout or a malicious client could cause a Denial of
+Service attack by keeping your clamav-milter threads alive. The types of
+timeout are C (time for clamav-milter to acknowledge to sendmail that it
+has accepted a new connection), S (timeout for sending information from sendmail
+to clamav-milter), R (timeout for sendmail reading a reply from clamav-milter
+when it has been sent some information) and E (timeout for clamav-milter to
+handle the end-of-message request, this needs to be high enough to scan the
+largest file that you will receive since it is at this stage that the file is
+scanned, but short enough to ensure that a DoS can't occur when lots of scans
+are requested). The important entries for clamav-milter are C and E (both
+default to 5 minutes).
+
+WARNING: When running on internal mode (--external is NOT used), clamav-milter
+will need to wait for all connections to stop before it can reload the database
+after running freshclam. It is therefore important that NO timeouts in
+sendmail.cf are set too high or worse still turned off, otherwise clamav-milter
+can wait a long time, perhaps indefinately, while waiting for the system to
+quieten down. The same goes for disabling StreamMaxLength, since receiving a
+very large email to be scanned may take a long time. We advise setting
+StreamMaxLength to 1M.
+
+Don't forget to rebuild sendmail.cf after modifying sendmail.mc. You will
+need to restart sendmail after rebuilding sendmail.cf and starting clamd and
+clamav-milter.
+
+As with all software it is wise to ensure that clamav-milter has the least
+privileges it needs to run. So don't run it as root and don't store the sockets in a directory that can be written by everyone. For example ensure that /var/run
+is owned and writeable only by root and add entries for 'User' and
+'FixStaleSocket' in clamd.conf.
+
+When using UNIX domain sockets via the LocalSocket option of clamd.conf,
+we recommend that you use the --quarantine-dir option since that may improve
+performance.
+
+If you wish to send a warning when a message is blocked, clamav-milter MUST be
+able to call sendmail, for example on a Fedora Linux system:
+
+	# ls -lL /usr/lib/sendmail
+	-rwxr-sr-x  1 root smmsp 732356 Sep  1 11:16 /usr/lib/sendmail
+
+To test that your clamAV system is now intercepting viruses, visit
+http://www.testvirus.org
+
+If, under heavy strain on Linux, you see the message
+	thread_create() failed: 12, abort
+appearing in a log file, you will need to increase the number of threads on
+your system (/proc/sys/kernel/threads-max), or decrease the value of
+--max-children.
+
+Clamav-milter performs DNS look ups, if you wish to tweak its timeouts
+see resolv.conf(5).
+
+2.7 Postfix
+
+Clamav-milter has only been designed to work with Sendmail. I understand that
+modern versions of Postfix have milter support, and I've heard that
+Clamav-milter runs with these versions of Postfix, however it is not supported
+with that software and I do not know how much functionality works.
+
+To start clamav-milter:
+
+	# clamav-milter --sendmail-cf= --max-children=2 \
+		--timeout=0 --pidfile=/var/run/clamav/clamav-milter \
+		local:/var/spool/postfix/clamav/clamav-milter
+	# chown clamav:postfix /var/spool/postfix/clamav/clamav-milter
+	# chmod g+w /var/spool/postfix/clamav/clmilter
+
+In /etc/postfix/main.cf set:
+	smtpd_milters = unix:clamav/clamav-milter
+	non_smtpd_milters = unix:clamav/clamav-milter
+
+3. CHANGE HISTORY
+
+See ../ChangeLog
+
+4. INTERNATIONALISATION
+
+The .po file was created with the command
+	xgettext --msgid-bugs-address=bugs at clamav.net --copyright-holder=njh at bandsman.co.uk -L c -d clamav-milter -k_ clamav-milter.c
+
+If you're interested in helping to translate this program please drop the
+author an e-mail.
+
+5. BUG REPORTS
+
+Please send bug reports and/or comments to Nigel Horne <njh at clamav.net> or
+bugs at clamav.net.
+
+Various tips will go here, for example
+	define(`confMILTER_LOG_LEVEL',`22')
+Running in the foreground, valgrind, LogSyslog, LogVerbose, LogFile etc.
+
+5.1. Patches
+
+Patches are welcome, but they must be against the latest CVS version and adhere
+to the coding style of clamav-milter. Coding style is religious, everyone
+believes theirs is great and all others are rubbish.
+
+This is my coding style, live with it. You don't want me in a bad mood because
+I can't read your code when I'm deciding if your code should be incorporated.
+
+Most of this style is based on K&R.
+
+Use the tab key, not space key, to indent.
+
+Except for functions, braces always go on the same line as the condition.
+
+Don't leave to chance, or your knowledge of precedence, use brackets to
+highten the readability.
+
+Choose variable names sensibly, don't use Hungarian style.
+
+The code is ANSI C, not C++, remember that when thinking of comment formats,
+location of declarations, etc.
+
+Patches which use 'goto' will never, ever, be accepted.
+
+Use the design of your code as comments.
+
+Test your patches and document the tests when submitting, e.g. different
+hardware, operating systems, test tools such as valgrind, compilers (gcc, icc,
+Sun's cc).
+
+Function names appear at the start of lines (I use ctags).
+
+Document your changes. If you add, remove, or change functionality you will
+need to update the manual page and possibly the usage message as well.
+
+6. CHROOT JAIL
+
+The instructions will differ for you, but these will give you an idea.
+You will have to do a lot of fiddling if you want notifications to work,
+since clamav-milter calls sendmail to handle the notifications and sendmail
+will run of out the same jail. I've not disabled the notifications, but I
+may in the future - for the moment handling notifications in the jail is an
+excercise for the reader. I've put in a symbolic link to sendmail, but I
+suspect it should be a real copy.
+
+	mkdir /var/run/clamav-root
+	chown clamav:clamav /var/run/clamav-root
+	chmod 750 /var/run/clamav-root
+	cd /var/run/clamav-root
+	mkdir var
+	mkdir var/tmp
+	ln -s var/tmp .
+	mkdir var/log
+	cd var/log
+	ln -s ../../../../../var/log/clamav .
+	cd ..
+	mkdir run
+	mkdir run/clamav
+	chown clamav:clamav run/clamav
+	cd ..
+	mkdir usr
+	mkdir usr/local
+	mkdir usr/local/share
+	ln -s ../../../../../../usr/local/share/clamav .
+	mkdir usr/lib
+	cd usr/lib
+	ln -s ../../../../../usr/lib/sendmail .
+	cd ../..
+	mkdir dev
+	cd dev
+	mknod null c 1 3
+	chown clamav:clamav null
+
+In sendmail.mc:
+INPUT_MAIL_FILTER(`clamav', `S=local:/var/run/clamav-root/var/run/clamav/clmilter.sock, F=T, T=S:4m;R:4m;C:30s;E:10m')dnl
+
+When starting clamav-milter use options such as (notice that the location
+of clmilter.sock is different in sendmail.mc than the location clamav-milter
+expects to see it)
+	--chroot=/var/run/clamav-root --max-children=3 -P --pidfile=/var/run/clamav/clamav-milter.pid --blacklist=60 --black-hole-mode local:/var/run/clamav/clmilter.sock
+
+You may need to modify your shutdown script to look for clamav-milter.pid
+in /var/run/clamav-root/var/run/clamav/clamav-milter.pid
+
+7. TODO
+
+There are several ideas marked as TODO in the source code. If anyone has
+any other suggestions please feel free to contact me. To avoid disappointment
+always contact me before undertaking any work.
diff --git a/contrib/old-clamav-milter/Makefile.am b/contrib/old-clamav-milter/Makefile.am
new file mode 100644
index 0000000..4a9eda3
--- /dev/null
+++ b/contrib/old-clamav-milter/Makefile.am
@@ -0,0 +1,48 @@
+#
+#  Copyright (C) 2003 - 2005 Tomasz Kojm <tkojm at clamav.net>
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+#  MA 02110-1301, USA.
+
+# FIXME: check automake for 'and' (&&)
+if BUILD_CLAMD
+if HAVE_MILTER
+
+sbin_PROGRAMS = clamav-milter
+
+clamav_milter_SOURCES = \
+    $(top_srcdir)/shared/cfgparser.c \
+    $(top_srcdir)/shared/cfgparser.h \
+    $(top_srcdir)/shared/output.c \
+    $(top_srcdir)/shared/output.h \
+    $(top_srcdir)/shared/getopt.c \
+    $(top_srcdir)/shared/getopt.h \
+    $(top_srcdir)/shared/misc.c \
+    $(top_srcdir)/shared/misc.h \
+    $(top_srcdir)/shared/network.c \
+    $(top_srcdir)/shared/network.h \
+    clamav-milter.c
+man_MANS = $(top_builddir)/docs/man/clamav-milter.8
+
+endif
+endif
+
+LIBS = $(top_builddir)/libclamav/libclamav.la @CLAMAV_MILTER_LIBS@ @THREAD_LIBS@
+AM_CPPFLAGS = -I$(top_srcdir)/clamd -I$(top_srcdir)/libclamav -I$(top_srcdir)/shared -I$(top_srcdir)
+EXTRA_DIST = clamav-milter.c INSTALL
+CLEANFILES=*.gcda *.gcno
+CFLAGS=`echo "@CFLAGS@" | sed -e 's/-Werror[^-]//'`
+
+
diff --git a/contrib/old-clamav-milter/Makefile.in b/contrib/old-clamav-milter/Makefile.in
new file mode 100644
index 0000000..ca7c717
--- /dev/null
+++ b/contrib/old-clamav-milter/Makefile.in
@@ -0,0 +1,705 @@
+# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008  Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+ at SET_MAKE@
+
+#
+#  Copyright (C) 2003 - 2005 Tomasz Kojm <tkojm at clamav.net>
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+#  MA 02110-1301, USA.
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE at sbin_PROGRAMS =  \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@	clamav-milter$(EXEEXT)
+subdir = clamav-milter
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in INSTALL
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/acinclude.m4 \
+	$(top_srcdir)/m4/argz.m4 $(top_srcdir)/m4/fdpassing.m4 \
+	$(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+	$(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltdl.m4 \
+	$(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+	$(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+	$(top_srcdir)/m4/mmap_private.m4 $(top_srcdir)/m4/resolv.m4 \
+	$(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/clamav-config.h
+CONFIG_CLEAN_FILES =
+am__installdirs = "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)"
+sbinPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
+PROGRAMS = $(sbin_PROGRAMS)
+am__clamav_milter_SOURCES_DIST = $(top_srcdir)/shared/cfgparser.c \
+	$(top_srcdir)/shared/cfgparser.h $(top_srcdir)/shared/output.c \
+	$(top_srcdir)/shared/output.h $(top_srcdir)/shared/getopt.c \
+	$(top_srcdir)/shared/getopt.h $(top_srcdir)/shared/misc.c \
+	$(top_srcdir)/shared/misc.h $(top_srcdir)/shared/network.c \
+	$(top_srcdir)/shared/network.h clamav-milter.c
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE at am_clamav_milter_OBJECTS =  \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@	cfgparser.$(OBJEXT) \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@	output.$(OBJEXT) \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@	getopt.$(OBJEXT) \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@	misc.$(OBJEXT) \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@	network.$(OBJEXT) \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@	clamav-milter.$(OBJEXT)
+clamav_milter_OBJECTS = $(am_clamav_milter_OBJECTS)
+clamav_milter_LDADD = $(LDADD)
+DEFAULT_INCLUDES = -I. at am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__depfiles_maybe = depfiles
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+	--mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+	$(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
+	--mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
+	$(LDFLAGS) -o $@
+SOURCES = $(clamav_milter_SOURCES)
+DIST_SOURCES = $(am__clamav_milter_SOURCES_DIST)
+man8dir = $(mandir)/man8
+NROFF = nroff
+MANS = $(man_MANS)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AR = @AR@
+ARGZ_H = @ARGZ_H@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFGDIR = @CFGDIR@
+CFLAGS = `echo "@CFLAGS@" | sed -e 's/-Werror[^-]//'`
+CHECK_CPPFLAGS = @CHECK_CPPFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+CLAMAVGROUP = @CLAMAVGROUP@
+CLAMAVUSER = @CLAMAVUSER@
+CLAMAV_MILTER_LIBS = @CLAMAV_MILTER_LIBS@
+CLAMD_LIBS = @CLAMD_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DBDIR = @DBDIR@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FRESHCLAM_LIBS = @FRESHCLAM_LIBS@
+GCOV = @GCOV@
+GENHTML = @GENHTML@
+GETENT = @GETENT@
+GPERF = @GPERF@
+GREP = @GREP@
+HAVE_LIBGMP = @HAVE_LIBGMP@
+INCLTDL = @INCLTDL@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBADD_DL = @LIBADD_DL@
+LIBADD_DLD_LINK = @LIBADD_DLD_LINK@
+LIBADD_DLOPEN = @LIBADD_DLOPEN@
+LIBADD_SHL_LOAD = @LIBADD_SHL_LOAD@
+LIBBZ2 = @LIBBZ2@
+LIBBZ2_PREFIX = @LIBBZ2_PREFIX@
+LIBCLAMAV_LIBS = @LIBCLAMAV_LIBS@
+LIBCLAMAV_VERSION = @LIBCLAMAV_VERSION@
+LIBGMP = @LIBGMP@
+LIBGMP_PREFIX = @LIBGMP_PREFIX@
+LIBLTDL = @LIBLTDL@
+LIBOBJS = @LIBOBJS@
+LIBS = $(top_builddir)/libclamav/libclamav.la @CLAMAV_MILTER_LIBS@ @THREAD_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTDLDEPS = @LTDLDEPS@
+LTDLINCL = @LTDLINCL@
+LTDLOPEN = @LTDLOPEN@
+LTLIBBZ2 = @LTLIBBZ2@
+LTLIBGMP = @LTLIBGMP@
+LTLIBOBJS = @LTLIBOBJS@
+LT_CONFIG_H = @LT_CONFIG_H@
+LT_DLLOADERS = @LT_DLLOADERS@
+LT_DLPREOPEN = @LT_DLPREOPEN@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+THREAD_LIBS = @THREAD_LIBS@
+TH_SAFE = @TH_SAFE@
+VERSION = @VERSION@
+VERSIONSCRIPTFLAG = @VERSIONSCRIPTFLAG@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_ECHO = @lt_ECHO@
+ltdl_LIBOBJS = @ltdl_LIBOBJS@
+ltdl_LTLIBOBJS = @ltdl_LTLIBOBJS@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sendmailprog = @sendmailprog@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sys_symbol_underscore = @sys_symbol_underscore@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE at clamav_milter_SOURCES = \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@    $(top_srcdir)/shared/cfgparser.c \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@    $(top_srcdir)/shared/cfgparser.h \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@    $(top_srcdir)/shared/output.c \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@    $(top_srcdir)/shared/output.h \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@    $(top_srcdir)/shared/getopt.c \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@    $(top_srcdir)/shared/getopt.h \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@    $(top_srcdir)/shared/misc.c \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@    $(top_srcdir)/shared/misc.h \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@    $(top_srcdir)/shared/network.c \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@    $(top_srcdir)/shared/network.h \
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE@    clamav-milter.c
+
+ at BUILD_CLAMD_TRUE@@HAVE_MILTER_TRUE at man_MANS = $(top_builddir)/docs/man/clamav-milter.8
+AM_CPPFLAGS = -I$(top_srcdir)/clamd -I$(top_srcdir)/libclamav -I$(top_srcdir)/shared -I$(top_srcdir)
+EXTRA_DIST = clamav-milter.c INSTALL
+CLEANFILES = *.gcda *.gcno
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+		&& exit 0; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign  clamav-milter/Makefile'; \
+	cd $(top_srcdir) && \
+	  $(AUTOMAKE) --foreign  clamav-milter/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+install-sbinPROGRAMS: $(sbin_PROGRAMS)
+	@$(NORMAL_INSTALL)
+	test -z "$(sbindir)" || $(MKDIR_P) "$(DESTDIR)$(sbindir)"
+	@list='$(sbin_PROGRAMS)'; for p in $$list; do \
+	  p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+	  if test -f $$p \
+	     || test -f $$p1 \
+	  ; then \
+	    f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \
+	   echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(sbinPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(sbindir)/$$f'"; \
+	   $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(sbinPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(sbindir)/$$f" || exit 1; \
+	  else :; fi; \
+	done
+
+uninstall-sbinPROGRAMS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(sbin_PROGRAMS)'; for p in $$list; do \
+	  f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+	  echo " rm -f '$(DESTDIR)$(sbindir)/$$f'"; \
+	  rm -f "$(DESTDIR)$(sbindir)/$$f"; \
+	done
+
+clean-sbinPROGRAMS:
+	@list='$(sbin_PROGRAMS)'; for p in $$list; do \
+	  f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+	  echo " rm -f $$p $$f"; \
+	  rm -f $$p $$f ; \
+	done
+
+installcheck-sbinPROGRAMS: $(sbin_PROGRAMS)
+	bad=0; pid=$$$$; list="$(sbin_PROGRAMS)"; for p in $$list; do \
+	  case ' $(AM_INSTALLCHECK_STD_OPTIONS_EXEMPT) ' in \
+	   *" $$p "* | *" $(srcdir)/$$p "*) continue;; \
+	  esac; \
+	  f=`echo "$$p" | \
+	     sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+	  for opt in --help --version; do \
+	    if "$(DESTDIR)$(sbindir)/$$f" $$opt >c$${pid}_.out \
+	         2>c$${pid}_.err </dev/null \
+		 && test -n "`cat c$${pid}_.out`" \
+		 && test -z "`cat c$${pid}_.err`"; then :; \
+	    else echo "$$f does not support $$opt" 1>&2; bad=1; fi; \
+	  done; \
+	done; rm -f c$${pid}_.???; exit $$bad
+clamav-milter$(EXEEXT): $(clamav_milter_OBJECTS) $(clamav_milter_DEPENDENCIES) 
+	@rm -f clamav-milter$(EXEEXT)
+	$(LINK) $(clamav_milter_OBJECTS) $(clamav_milter_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/cfgparser.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/clamav-milter.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/getopt.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/misc.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/network.Po at am__quote@
+ at AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/output.Po at am__quote@
+
+.c.o:
+ at am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+ at am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+ at am__fastdepCC_TRUE@	$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(LTCOMPILE) -c -o $@ $<
+
+cfgparser.o: $(top_srcdir)/shared/cfgparser.c
+ at am__fastdepCC_TRUE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT cfgparser.o -MD -MP -MF $(DEPDIR)/cfgparser.Tpo -c -o cfgparser.o `test -f '$(top_srcdir)/shared/cfgparser.c' || echo '$(srcdir)/'`$(top_srcdir)/shared/cfgparser.c
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/cfgparser.Tpo $(DEPDIR)/cfgparser.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/shared/cfgparser.c' object='cfgparser.o' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o cfgparser.o `test -f '$(top_srcdir)/shared/cfgparser.c' || echo '$(srcdir)/'`$(top_srcdir)/shared/cfgparser.c
+
+cfgparser.obj: $(top_srcdir)/shared/cfgparser.c
+ at am__fastdepCC_TRUE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT cfgparser.obj -MD -MP -MF $(DEPDIR)/cfgparser.Tpo -c -o cfgparser.obj `if test -f '$(top_srcdir)/shared/cfgparser.c'; then $(CYGPATH_W) '$(top_srcdir)/shared/cfgparser.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/shared/cfgparser.c'; fi`
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/cfgparser.Tpo $(DEPDIR)/cfgparser.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/shared/cfgparser.c' object='cfgparser.obj' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o cfgparser.obj `if test -f '$(top_srcdir)/shared/cfgparser.c'; then $(CYGPATH_W) '$(top_srcdir)/shared/cfgparser.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/shared/cfgparser.c'; fi`
+
+output.o: $(top_srcdir)/shared/output.c
+ at am__fastdepCC_TRUE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT output.o -MD -MP -MF $(DEPDIR)/output.Tpo -c -o output.o `test -f '$(top_srcdir)/shared/output.c' || echo '$(srcdir)/'`$(top_srcdir)/shared/output.c
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/output.Tpo $(DEPDIR)/output.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/shared/output.c' object='output.o' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o output.o `test -f '$(top_srcdir)/shared/output.c' || echo '$(srcdir)/'`$(top_srcdir)/shared/output.c
+
+output.obj: $(top_srcdir)/shared/output.c
+ at am__fastdepCC_TRUE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT output.obj -MD -MP -MF $(DEPDIR)/output.Tpo -c -o output.obj `if test -f '$(top_srcdir)/shared/output.c'; then $(CYGPATH_W) '$(top_srcdir)/shared/output.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/shared/output.c'; fi`
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/output.Tpo $(DEPDIR)/output.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/shared/output.c' object='output.obj' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o output.obj `if test -f '$(top_srcdir)/shared/output.c'; then $(CYGPATH_W) '$(top_srcdir)/shared/output.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/shared/output.c'; fi`
+
+getopt.o: $(top_srcdir)/shared/getopt.c
+ at am__fastdepCC_TRUE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT getopt.o -MD -MP -MF $(DEPDIR)/getopt.Tpo -c -o getopt.o `test -f '$(top_srcdir)/shared/getopt.c' || echo '$(srcdir)/'`$(top_srcdir)/shared/getopt.c
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/getopt.Tpo $(DEPDIR)/getopt.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/shared/getopt.c' object='getopt.o' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o getopt.o `test -f '$(top_srcdir)/shared/getopt.c' || echo '$(srcdir)/'`$(top_srcdir)/shared/getopt.c
+
+getopt.obj: $(top_srcdir)/shared/getopt.c
+ at am__fastdepCC_TRUE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT getopt.obj -MD -MP -MF $(DEPDIR)/getopt.Tpo -c -o getopt.obj `if test -f '$(top_srcdir)/shared/getopt.c'; then $(CYGPATH_W) '$(top_srcdir)/shared/getopt.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/shared/getopt.c'; fi`
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/getopt.Tpo $(DEPDIR)/getopt.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/shared/getopt.c' object='getopt.obj' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o getopt.obj `if test -f '$(top_srcdir)/shared/getopt.c'; then $(CYGPATH_W) '$(top_srcdir)/shared/getopt.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/shared/getopt.c'; fi`
+
+misc.o: $(top_srcdir)/shared/misc.c
+ at am__fastdepCC_TRUE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT misc.o -MD -MP -MF $(DEPDIR)/misc.Tpo -c -o misc.o `test -f '$(top_srcdir)/shared/misc.c' || echo '$(srcdir)/'`$(top_srcdir)/shared/misc.c
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/misc.Tpo $(DEPDIR)/misc.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/shared/misc.c' object='misc.o' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o misc.o `test -f '$(top_srcdir)/shared/misc.c' || echo '$(srcdir)/'`$(top_srcdir)/shared/misc.c
+
+misc.obj: $(top_srcdir)/shared/misc.c
+ at am__fastdepCC_TRUE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT misc.obj -MD -MP -MF $(DEPDIR)/misc.Tpo -c -o misc.obj `if test -f '$(top_srcdir)/shared/misc.c'; then $(CYGPATH_W) '$(top_srcdir)/shared/misc.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/shared/misc.c'; fi`
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/misc.Tpo $(DEPDIR)/misc.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/shared/misc.c' object='misc.obj' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o misc.obj `if test -f '$(top_srcdir)/shared/misc.c'; then $(CYGPATH_W) '$(top_srcdir)/shared/misc.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/shared/misc.c'; fi`
+
+network.o: $(top_srcdir)/shared/network.c
+ at am__fastdepCC_TRUE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT network.o -MD -MP -MF $(DEPDIR)/network.Tpo -c -o network.o `test -f '$(top_srcdir)/shared/network.c' || echo '$(srcdir)/'`$(top_srcdir)/shared/network.c
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/network.Tpo $(DEPDIR)/network.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/shared/network.c' object='network.o' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o network.o `test -f '$(top_srcdir)/shared/network.c' || echo '$(srcdir)/'`$(top_srcdir)/shared/network.c
+
+network.obj: $(top_srcdir)/shared/network.c
+ at am__fastdepCC_TRUE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT network.obj -MD -MP -MF $(DEPDIR)/network.Tpo -c -o network.obj `if test -f '$(top_srcdir)/shared/network.c'; then $(CYGPATH_W) '$(top_srcdir)/shared/network.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/shared/network.c'; fi`
+ at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/network.Tpo $(DEPDIR)/network.Po
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$(top_srcdir)/shared/network.c' object='network.obj' libtool=no @AMDEPBACKSLASH@
+ at AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+ at am__fastdepCC_FALSE@	$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o network.obj `if test -f '$(top_srcdir)/shared/network.c'; then $(CYGPATH_W) '$(top_srcdir)/shared/network.c'; else $(CYGPATH_W) '$(srcdir)/$(top_srcdir)/shared/network.c'; fi`
+
+mostlyclean-libtool:
+	-rm -f *.lo
+
+clean-libtool:
+	-rm -rf .libs _libs
+install-man8: $(man8_MANS) $(man_MANS)
+	@$(NORMAL_INSTALL)
+	test -z "$(man8dir)" || $(MKDIR_P) "$(DESTDIR)$(man8dir)"
+	@list='$(man8_MANS) $(dist_man8_MANS) $(nodist_man8_MANS)'; \
+	l2='$(man_MANS) $(dist_man_MANS) $(nodist_man_MANS)'; \
+	for i in $$l2; do \
+	  case "$$i" in \
+	    *.8*) list="$$list $$i" ;; \
+	  esac; \
+	done; \
+	for i in $$list; do \
+	  if test -f $(srcdir)/$$i; then file=$(srcdir)/$$i; \
+	  else file=$$i; fi; \
+	  ext=`echo $$i | sed -e 's/^.*\\.//'`; \
+	  case "$$ext" in \
+	    8*) ;; \
+	    *) ext='8' ;; \
+	  esac; \
+	  inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \
+	  inst=`echo $$inst | sed -e 's/^.*\///'`; \
+	  inst=`echo $$inst | sed '$(transform)'`.$$ext; \
+	  echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \
+	  $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst"; \
+	done
+uninstall-man8:
+	@$(NORMAL_UNINSTALL)
+	@list='$(man8_MANS) $(dist_man8_MANS) $(nodist_man8_MANS)'; \
+	l2='$(man_MANS) $(dist_man_MANS) $(nodist_man_MANS)'; \
+	for i in $$l2; do \
+	  case "$$i" in \
+	    *.8*) list="$$list $$i" ;; \
+	  esac; \
+	done; \
+	for i in $$list; do \
+	  ext=`echo $$i | sed -e 's/^.*\\.//'`; \
+	  case "$$ext" in \
+	    8*) ;; \
+	    *) ext='8' ;; \
+	  esac; \
+	  inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \
+	  inst=`echo $$inst | sed -e 's/^.*\///'`; \
+	  inst=`echo $$inst | sed '$(transform)'`.$$ext; \
+	  echo " rm -f '$(DESTDIR)$(man8dir)/$$inst'"; \
+	  rm -f "$(DESTDIR)$(man8dir)/$$inst"; \
+	done
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	tags=; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	    $$tags $$unique; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	tags=; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$tags$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$tags $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && cd $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) $$here
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+	    fi; \
+	    cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+	  else \
+	    test -f $(distdir)/$$file \
+	    || cp -p $$d/$$file $(distdir)/$$file \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(MANS)
+installdirs:
+	for dir in "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+	-test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-sbinPROGRAMS \
+	mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+info: info-am
+
+info-am:
+
+install-data-am: install-man
+
+install-dvi: install-dvi-am
+
+install-exec-am: install-sbinPROGRAMS
+
+install-html: install-html-am
+
+install-info: install-info-am
+
+install-man: install-man8
+
+install-pdf: install-pdf-am
+
+install-ps: install-ps-am
+
+installcheck-am: installcheck-sbinPROGRAMS
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+	mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-man uninstall-sbinPROGRAMS
+
+uninstall-man: uninstall-man8
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-libtool clean-sbinPROGRAMS ctags distclean \
+	distclean-compile distclean-generic distclean-libtool \
+	distclean-tags distdir dvi dvi-am html html-am info info-am \
+	install install-am install-data install-data-am install-dvi \
+	install-dvi-am install-exec install-exec-am install-html \
+	install-html-am install-info install-info-am install-man \
+	install-man8 install-pdf install-pdf-am install-ps \
+	install-ps-am install-sbinPROGRAMS install-strip installcheck \
+	installcheck-am installcheck-sbinPROGRAMS installdirs \
+	maintainer-clean maintainer-clean-generic mostlyclean \
+	mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+	pdf pdf-am ps ps-am tags uninstall uninstall-am uninstall-man \
+	uninstall-man8 uninstall-sbinPROGRAMS
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/contrib/old-clamav-milter/clamav-milter.c b/contrib/old-clamav-milter/clamav-milter.c
new file mode 100644
index 0000000..dc69995
--- /dev/null
+++ b/contrib/old-clamav-milter/clamav-milter.c
@@ -0,0 +1,7039 @@
+/*
+ * clamav-milter.c
+ *	.../clamav-milter/clamav-milter.c
+ *
+ *  Copyright (C) 2003-2007 Nigel Horne <njh at bandsman.co.uk>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA 02110-1301, USA.
+ *
+ * Install into /usr/local/sbin/clamav-milter
+ * See http://www.elandsys.com/resources/sendmail/libmilter/overview.html
+ *
+ * For installation instructions see the file INSTALL that came with this file
+ *
+ * NOTE: first character of strings to logg():
+ *	! Error
+ *	^ Warning
+ *	* Verbose
+ *	# Info, but not logged in foreground
+ *	Default Info
+ */
+static	char	const	rcsid[] = "$Id: clamav-milter.c,v 1.312 2007/02/12 22:24:21 njh Exp $";
+
+#if HAVE_CONFIG_H
+#include "clamav-config.h"
+#endif
+
+#include "cfgparser.h"
+#include "target.h"
+#include "str.h"
+#include "../libclamav/others.h"
+#include "output.h"
+#include "clamav.h"
+#include "table.h"
+#include "network.h"
+#include "misc.h"
+
+#include <stdio.h>
+#include <sysexits.h>
+#ifdef	HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#if	HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#if	HAVE_MEMORY_H
+#include <memory.h>
+#endif
+#if	HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include <sys/wait.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <stdarg.h>
+#include <errno.h>
+#if	HAVE_LIBMILTER_MFAPI_H
+#include <libmilter/mfapi.h>
+#endif
+#include <pthread.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <grp.h>
+#if	HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#if	HAVE_RESOLV_H
+#include <arpa/nameser.h>	/* for HEADER */
+#include <resolv.h>
+#endif
+#ifdef	HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <ctype.h>
+
+#if HAVE_MMAP
+#if HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#else /* HAVE_SYS_MMAN_H */
+#undef HAVE_MMAP
+#endif
+#endif
+
+#define NONBLOCK_SELECT_MAX_FAILURES	3
+#define NONBLOCK_MAX_ATTEMPTS	10
+#define	CONNECT_TIMEOUT	5	/* Allow 5 seconds to connect */
+
+#ifdef	C_LINUX
+#include <sys/sendfile.h>	/* FIXME: use sendfile on BSD not Linux */
+#include <libintl.h>
+#include <locale.h>
+
+#define	gettext_noop(s)	s
+#define	_(s)	gettext(s)
+#define	N_(s)	gettext_noop(s)
+
+#else
+
+#define	_(s)	s
+#define	N_(s)	s
+
+#endif
+
+#ifdef	USE_SYSLOG
+#include <syslog.h>
+#endif
+
+#ifdef	WITH_TCPWRAP
+#if	HAVE_TCPD_H
+#include <tcpd.h>
+#endif
+
+int	allow_severity = LOG_DEBUG;
+int	deny_severity = LOG_NOTICE;
+#endif
+
+#ifdef	CL_DEBUG
+static	char	console[] = "/dev/console";
+#endif
+
+#if defined(CL_DEBUG) && defined(C_LINUX)
+#include <sys/resource.h>
+#endif
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#ifndef	SENDMAIL_BIN
+#define	SENDMAIL_BIN	"/usr/lib/sendmail"
+#endif
+
+#ifndef HAVE_IN_PORT_T
+typedef	unsigned short	in_port_t;
+#endif
+
+#ifndef	HAVE_IN_ADDR_T
+typedef	unsigned int	in_addr_t;
+#endif
+
+#ifndef	INET6_ADDRSTRLEN
+#ifdef	AF_INET6
+#define	INET6_ADDRSTRLEN	40
+#else
+#define	INET6_ADDRSTRLEN	16
+#endif
+#endif
+
+#ifndef	EX_CONFIG	/* HP-UX */
+#define	EX_CONFIG	78
+#endif
+
+#define	VERSION_LENGTH	128
+#define	DEFAULT_TIMEOUT	120
+
+#define	NTRIES	5	/* How many times we try to connect to a clamd */
+
+/*#define	SESSION*/
+		/* Keep one command connexion open to clamd, otherwise a new
+		 * command connexion is created for each new email
+		 *
+		 * FIXME: When SESSIONS are open, freshclam can hang when
+		 *	notfying clamd of an update. This is most likely to be a
+		 *	problem with the implementation of SESSIONS on clamd.
+		 *	The problem seems worst on BSD.
+		 *
+		 * Note that clamd is buggy and can hang or even crash if you
+		 *	send SESSION command so be aware
+		 */
+
+/*
+ * TODO: optional: xmessage on console when virus stopped (SNMP would be real nice!)
+ *	Having said that, with LogSysLog you can (on Linux) configure the system
+ *	to get messages on the system console, see syslog.conf(5), also you
+ *	can use wall(1) in the VirusEvent entry in clamd.conf
+ * TODO: Decide action (bounce, discard, reject etc.) based on the virus
+ *	found. Those with faked addresses, such as SCO.A want discarding,
+ *	others could be bounced properly.
+ * TODO: Encrypt mails sent to clamd to stop sniffers. Sending by UNIX domain
+ *	sockets is better
+ * TODO: Load balancing, allow local machine to talk via UNIX domain socket.
+ * TODO: allow each To: line in the whitelist file to specify a quarantine email
+ *	address
+ * TODO: optionally use zlib to compress data sent to remote hosts
+ * TODO: Finish IPv6 support (serverIPs array and SPF are IPv4 only)
+ * TODO: Check domainkeys as well as SPF for phish false positives
+ */
+
+struct header_node_t {
+	char	*header;
+	struct	header_node_t *next;
+};
+
+struct header_list_struct {
+	struct	header_node_t *first;
+	struct	header_node_t *last;
+};
+
+typedef struct header_list_struct *header_list_t;
+
+/*
+ * Local addresses are those not scanned if --local is not set
+ * 127.0.0.0 is not in this table since that's goverend by --outgoing
+ * Andy Fiddaman <clam at fiddaman.net> added 169.254.0.0/16
+ *	(Microsoft default DHCP)
+ * TODO: compare this with RFC1918/RFC3330
+ */
+#define PACKADDR(a, b, c, d) (((uint32_t)(a) << 24) | ((b) << 16) | ((c) << 8) | (d))
+#define MAKEMASK(bits)	((uint32_t)(0xffffffff << (32 - bits)))
+
+static struct cidr_net {	/* don't make this const because of -I flag */
+	uint32_t	base;
+	uint32_t	mask;
+} localNets[] = {
+	/*{ PACKADDR(127,   0,   0,   0), MAKEMASK(8) },	*   127.0.0.0/8 */
+	{ PACKADDR(192, 168,   0,   0), MAKEMASK(16) },	/* 192.168.0.0/16 - RFC3330 */
+	/*{ PACKADDR(192, 18,   0,   0), MAKEMASK(15) },	* 192.18.0.0/15 - RFC2544 */
+	/*{ PACKADDR(192, 0,   2,   0), MAKEMASK(24) },	* 192.0.2.0/24 - RFC3330 */
+	{ PACKADDR( 10,   0,   0,   0), MAKEMASK(8) },	/*    10.0.0.0/8 */
+	{ PACKADDR(172,  16,   0,   0), MAKEMASK(12) },	/*  172.16.0.0/12 */
+	{ PACKADDR(169, 254,   0,   0), MAKEMASK(16) },	/* 169.254.0.0/16 */
+	{ 0, 0 },	/* space to put eight more via -I addr */
+	{ 0, 0 },
+	{ 0, 0 },
+	{ 0, 0 },
+	{ 0, 0 },
+	{ 0, 0 },
+	{ 0, 0 },
+	{ 0, 0 },
+	{ 0, 0 }
+};
+#define IFLAG_MAX 8
+
+#ifdef	AF_INET6
+typedef struct cidr_net6 {
+	struct in6_addr	base;
+	int preflen;
+} cidr_net6;
+static	cidr_net6	localNets6[IFLAG_MAX];
+static	int	localNets6_cnt;
+#endif
+
+/*
+ * Each libmilter thread has one of these
+ */
+struct	privdata {
+	char	*from;	/* Who sent the message */
+	char	*subject;	/* Original subject */
+	char	*sender;	/* Secretary - often used in mailing lists */
+	char	**to;	/* Who is the message going to */
+	char	ip[INET6_ADDRSTRLEN];	/* IP address of the other end */
+	int	numTo;	/* Number of people the message is going to */
+#ifndef	SESSION
+	int	cmdSocket;	/*
+				 * Socket to send/get commands e.g. PORT for
+				 * dataSocket
+				 */
+#endif
+	int	dataSocket;	/* Socket to send data to clamd */
+	char	*filename;	/* Where to store the message in quarantine */
+	u_char	*body;		/* body of the message if Sflag is set */
+	size_t	bodyLen;	/* number of bytes in body */
+	header_list_t headers;	/* Message headers */
+	long	numBytes;	/* Number of bytes sent so far */
+	char	*received;	/* keep track of received from */
+	const	char	*rejectCode;	/* 550 or 554? */
+	unsigned	int	discard:1;	/*
+				 * looks like the remote end is playing ping
+				 * pong with us
+				 */
+#ifdef	HAVE_RESOLV_H
+	unsigned	int	spf_ok:1;
+#endif
+	int	statusCount;	/* number of X-Virus-Status headers */
+	int	serverNumber;	/* Index into serverIPs */
+};
+
+#ifdef	SESSION
+static	int		createSession(unsigned int s);
+#else
+static	int		pingServer(int serverNumber);
+static	void		*try_server(void *var);
+static	int		active_servers(int *active);
+struct	try_server_struct {
+	int	sock;
+	int	rc;
+	struct	sockaddr_in *server;
+	int	server_index;
+};
+#endif
+static	int		findServer(void);
+static	sfsistat	clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr);
+#ifdef	CL_DEBUG
+static	sfsistat	clamfi_helo(SMFICTX *ctx, char *helostring);
+#endif
+static	sfsistat	clamfi_envfrom(SMFICTX *ctx, char **argv);
+static	sfsistat	clamfi_envrcpt(SMFICTX *ctx, char **argv);
+static	sfsistat	clamfi_header(SMFICTX *ctx, char *headerf, char *headerv);
+static	sfsistat	clamfi_eoh(SMFICTX *ctx);
+static	sfsistat	clamfi_body(SMFICTX *ctx, u_char *bodyp, size_t len);
+static	sfsistat	clamfi_eom(SMFICTX *ctx);
+static	sfsistat	clamfi_abort(SMFICTX *ctx);
+static	sfsistat	clamfi_close(SMFICTX *ctx);
+static	void		clamfi_cleanup(SMFICTX *ctx);
+static	void		clamfi_free(struct privdata *privdata, int keep);
+#ifdef __GNUC__
+static	int		clamfi_send(struct privdata *privdata, size_t len, const char *format, ...) __attribute__((format(printf, 3,4)));
+#else
+static	int		clamfi_send(struct privdata *privdata, size_t len, const char *format, ...);
+#endif
+static	long		clamd_recv(int sock, char *buf, size_t len);
+static	off_t		updateSigFile(void);
+static	header_list_t	header_list_new(void);
+static	void	header_list_free(header_list_t list);
+static	void	header_list_add(header_list_t list, const char *headerf, const char *headerv);
+static	void	header_list_print(header_list_t list, FILE *fp);
+static	int	connect2clamd(struct privdata *privdata);
+static	int	sendToFrom(struct privdata *privdata);
+static	int	checkClamd(int log_result);
+static	int	sendtemplate(SMFICTX *ctx, const char *filename, FILE *sendmail, const char *virusname);
+static	int	qfile(struct privdata *privdata, const char *sendmailId, const char *virusname);
+static	int	move(const char *oldfile, const char *newfile);
+static	void	setsubject(SMFICTX *ctx, const char *virusname);
+/*static	int	clamfi_gethostbyname(const char *hostname, struct hostent *hp, char *buf, size_t len);*/
+static	int	add_local_ip(char *address);
+static	int	isLocalAddr(in_addr_t addr);
+static	int	isLocal(const char *addr);
+static	void	clamdIsDown(void);
+static	void	*watchdog(void *a);
+static	int	check_and_reload_database(void);
+static	void	timeoutBlacklist(char *ip_address, int time_of_blacklist, void *v);
+static	void	quit(void);
+static	void	broadcast(const char *mess);
+static	int	loadDatabase(void);
+static	int	increment_connexions(void);
+static	void	decrement_connexions(void);
+static	void	dump_blacklist(char *key, int value, void *v);
+static	int	nonblock_connect(int sock, const struct sockaddr_in *sin, const char *hostname);
+static	int	connect_error(int sock, const char *hostname);
+
+#ifdef	SESSION
+static	pthread_mutex_t	version_mutex = PTHREAD_MUTEX_INITIALIZER;
+static	char	**clamav_versions;	/* max_children elements in the array */
+#define	clamav_version	(clamav_versions[0])
+#else
+static	char	clamav_version[VERSION_LENGTH + 1];
+#endif
+static	int	fflag = 0;	/* force a scan, whatever */
+static	int	oflag = 0;	/* scan messages from our machine? */
+static	int	lflag = 0;	/* scan messages from our site? */
+static	int	Iflag = 0;	/* Added an IP addr to localNets? */
+static	const	char	*progname;	/* our name - usually clamav-milter */
+
+/* Variables for --external */
+static	int	external = 0;	/* scan messages ourself or use clamd? */
+static	pthread_mutex_t	engine_mutex = PTHREAD_MUTEX_INITIALIZER;
+struct  cl_engine *engine = NULL;
+uint64_t maxscansize;
+uint64_t maxfilesize;
+uint32_t maxreclevel;
+uint32_t maxfiles;
+
+static	struct	cl_stat	dbstat;
+static	int	options = CL_SCAN_STDOPT;
+
+#ifdef	BOUNCE
+static	int	bflag = 0;	/*
+				 * send a failure (bounce) message to the
+				 * sender. This probably isn't a good idea
+				 * since most reply addresses will be fake
+				 *
+				 * TODO: Perhaps we can have an option to
+				 * bounce outgoing mail, but not incoming?
+				 */
+#endif
+static	const	char	*iface;	/*
+				 * Broadcast a message when a virus is found,
+				 * this allows remote network management
+				 */
+static	int	broadcastSock = -1;
+static	int	pflag = 0;	/*
+				 * Send a warning to the postmaster only,
+				 * this means user's won't be told when someone
+				 * sent them a virus
+				 */
+static	int	qflag = 0;	/*
+				 * Send no warnings when a virus is found,
+				 * this means that the only log of viruses
+				 * found is the syslog, so it's best to
+				 * enable LogSyslog in clamd.conf
+				 */
+static	int	Sflag = 0;	/*
+				 * Add a signature to each message that
+				 * has been scanned
+				 */
+static	const	char	*sigFilename;	/*
+				 * File where the scanned message signature
+				 * can be found
+				 */
+static	char	*quarantine;	/*
+				 * If a virus is found in an email redirect
+				 * it to this account
+				 */
+static	char	*quarantine_dir; /*
+				 * Path to store messages before scanning.
+				 * Infected ones will be left there.
+				 */
+static	int	nflag = 0;	/*
+				 * Don't add X-Virus-Scanned to header. Patch
+				 * from Dirk Meyer <dirk.meyer at dinoex.sub.org>
+				 */
+static	int	rejectmail = 1;	/*
+				 * Send a 550 rejection when a virus is
+				 * found
+				 */
+static	int	hflag = 0;	/*
+				 * Include original message headers in
+				 * report
+				 */
+static	int	cl_error = SMFIS_TEMPFAIL; /*
+				 * If an error occurs, return
+				 * this status. Allows messages
+				 * to be passed through
+				 * unscanned in the event of
+				 * an error. Patch from
+				 * Joe Talbott <josepht at cstone.net>
+				 */
+static	int	readTimeout = DEFAULT_TIMEOUT; /*
+				 * number of seconds to wait for clamd to
+				 * respond, see ReadTimeout in clamd.conf
+				 */
+static	long	streamMaxLength = -1;	/* StreamMaxLength from clamd.conf */
+static	int	logok = 0;	/*
+				 * Add clean items to the log file
+				 */
+static	const char	*signature = N_("-- \nScanned by ClamAv - http://www.clamav.net\n");
+static	time_t	signatureStamp;
+static	char	*templateFile;	/* e-mail to be sent when virus detected */
+static	char	*templateHeaders;	/* headers to be added to the above */
+static	const char	*tmpdir;
+
+#ifdef	CL_DEBUG
+static	int	debug_level = 0;
+#endif
+
+static	pthread_mutex_t	n_children_mutex = PTHREAD_MUTEX_INITIALIZER;
+static	pthread_cond_t	n_children_cond = PTHREAD_COND_INITIALIZER;
+static	int	n_children = 0;
+static	int	max_children = 0;
+static	unsigned	int	freshclam_monitor = 10;	/*
+							 * how often, in
+							 * seconds, to scan for
+							 * database updates
+							 */
+static	int	child_timeout = 300;	/* number of seconds to wait for
+					 * a child to die. Set to 0 to
+					 * wait forever
+					 */
+static	int	dont_wait = 0;	/*
+				 * If 1 send retry later to the remote end
+				 * if max_chilren is exceeded, otherwise we
+				 * wait for the number to go down
+				 */
+static	int	dont_sanitise = 0; /*
+				 * Don't check for ";" and "|" chars in 
+				 * email addresses.
+				 */
+static	int	advisory = 0;	/*
+				 * Run clamav-milter in advisory mode - viruses
+				 * are flagged rather than deleted. Incompatible
+				 * with quarantine options
+				 */
+static	int	detect_forged_local_address;	/*
+				 * for incoming only mail servers, drop emails
+				 * claiming to be from us that must be false
+				 * Requires that -o, -l or -f are NOT given
+				 */
+static	const	char	*pidFile;
+static	struct	cfgstruct	*copt;
+static	const	char	*localSocket;	/* milter->clamd comms */
+static	in_port_t	tcpSocket;	/* milter->clamd comms */
+static	char	*port = NULL;	/* sendmail->milter comms */
+
+static	const	char	*serverHostNames = "127.0.0.1";
+#if	HAVE_IN_ADDR_T
+static	in_addr_t	*serverIPs;	/* IPv4 only, in network byte order */
+#else
+static	long	*serverIPs;	/* IPv4 only, in network byte order */
+#endif
+static	int	numServers;	/* number of elements in serverIPs array */
+#ifndef	SESSION
+#define	RETRY_SECS	300	/* How often to retry a server that's down */
+static	time_t	*last_failed_pings;	/* For servers that are down. NB: not mutexed */
+#endif
+static	char	*rootdir;	/* for chroot */
+
+#ifdef	SESSION
+static	struct	session {
+	int	sock;	/* fd */
+	enum	{ CMDSOCKET_FREE, CMDSOCKET_INUSE, CMDSOCKET_DOWN }	status;
+} *sessions;	/* max_children elements in the array */
+static	pthread_mutex_t sstatus_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif	/*SESSION*/
+
+static	pthread_cond_t	watchdog_cond = PTHREAD_COND_INITIALIZER;
+
+#ifndef	SHUT_RD
+#define	SHUT_RD		0
+#endif
+#ifndef	SHUT_WR
+#define	SHUT_WR		1
+#endif
+
+static	const	char	*postmaster = "postmaster";
+static	const	char	*from = "MAILER-DAEMON";
+static	int	quitting;
+static	int	reload;	/* reload database when SIGUSR2 is received */
+static	const	char	*report;	/* Report Phishing to this address */
+static	const	char	*report_fps;	/* Report Phish FPs to this address */
+
+static	const	char	*whitelistFile;	/*
+					 * file containing destination email
+					 * addresses that we don't scan
+					 */
+static	const	char	*sendmailCF;	/* location of sendmail.cf to verify */
+static		int	checkCF = 1;
+static	const	char	*pidfile;
+static	int	black_hole_mode; /*
+				 * Since sendmail calls its milters before it
+				 * looks in /etc/aliases we can spend time
+				 * looking for malware that's going to be
+				 * thrown away even if the message is clean.
+				 * Enable this to not scan these messages.
+				 * Sadly, because these days sendmail -bv
+				 * only works as root, you can't use this with
+				 * the User directive, which some won't like
+				 * which also may contain the real target name
+				 *
+				 * smfi_getsymval(ctx, "{rcpt_addr}") only
+				 * handles virtuser, it doesn't also deref
+				 * the alias table, so it isn't any help
+				 */
+
+static	table_t	*blacklist;	/* never freed */
+static	int	blacklist_time;	/* How long to blacklist an IP */
+static	pthread_mutex_t	blacklist_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+#ifdef	CL_DEBUG
+#if __GLIBC__ == 2 && __GLIBC_MINOR__ >= 1
+#define HAVE_BACKTRACE
+#endif
+#endif
+
+static	void	sigsegv(int sig);
+static	void	sigusr1(int sig);
+static	void	sigusr2(int sig);
+
+#ifdef HAVE_BACKTRACE
+#include <execinfo.h>
+
+static	void	print_trace(void);
+
+#define	BACKTRACE_SIZE	200
+
+#endif
+
+static	int	verifyIncomingSocketName(const char *sockName);
+static	int	isWhitelisted(const char *emailaddress, int to);
+static	int	isBlacklisted(const char *ip_address);
+static	table_t	*mx(const char *host, table_t *t);
+static	sfsistat	black_hole(const struct privdata *privdata);
+static	int	useful_header(const char *cmd);
+
+extern	short	logg_foreground;
+
+#ifdef HAVE_RESOLV_H
+static	table_t	*resolve(const char *host, table_t *t);
+static	int	spf(struct privdata *privdata, table_t *prevhosts);
+static	void	spf_ip(char *ip, int zero, void *v);
+
+pthread_mutex_t	res_pool_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+#ifdef HAVE_LRESOLV_R
+res_state res_pool;
+uint8_t *res_pool_state;
+pthread_cond_t res_pool_cond = PTHREAD_COND_INITIALIZER;
+
+static int safe_res_query(const char *d, int c, int t, u_char *a, int l) {
+	int i = -1, ret;
+
+	pthread_mutex_lock(&res_pool_mutex);
+	while(i==-1) {
+		int j;
+		for(j=0; j<max_children+1; j++) {
+			if(!res_pool_state[j]) continue;
+			i = j;
+			break;
+		}
+		if(i!=-1) break;
+		pthread_cond_wait(&res_pool_cond, &res_pool_mutex);
+	}
+	res_pool_state[i]=0;
+	pthread_mutex_unlock(&res_pool_mutex);
+
+	ret = res_nquery(&res_pool[i], d, c, t, a, l);
+  
+	pthread_mutex_lock(&res_pool_mutex);
+	res_pool_state[i]=1;
+	pthread_cond_signal(&res_pool_cond);
+	pthread_mutex_unlock(&res_pool_mutex);
+	return ret;
+}
+
+#else /* !HAVE_LRESOLV_R - non thread safe resolver (old bsd's) */
+
+static int safe_res_query(const char *d, int c, int t, u_char *a, int l) {
+	int ret;
+	pthread_mutex_lock(&res_pool_mutex);
+	ret = res_query(d, c, t, a, l);
+	pthread_mutex_unlock(&res_pool_mutex);
+	return ret;
+}
+
+#endif /* HAVE_LRESOLV_R */
+
+#endif /* HAVE_RESOLV_H */
+
+static void
+help(void)
+{
+	printf("\n\tclamav-milter version %s\n", get_version());
+	puts("\tCopyright (C) 2007 Nigel Horne <njh at clamav.net>\n");
+
+	puts(_("\t--advisory\t\t-A\tFlag viruses rather than deleting them."));
+	puts(_("\t--blacklist-time=SECS\t-k\tTime (in seconds) to blacklist an IP."));
+	puts(_("\t--black-hole-mode\t\tDon't scan messages aliased to /dev/null."));
+#ifdef	BOUNCE
+	puts(_("\t--bounce\t\t-b\tSend a failure message to the sender."));
+#endif
+	puts(_("\t--broadcast\t\t-B [IFACE]\tBroadcast to a network manager when a virus is found."));
+	puts(_("\t--chroot=DIR\t\t-C DIR\tChroot to dir when starting."));
+	puts(_("\t--config-file=FILE\t-c FILE\tRead configuration from FILE."));
+	puts(_("\t--debug\t\t\t-D\tPrint debug messages."));
+	puts(_("\t--detect-forged-local-address\t-L\tReject mails that claim to be from us."));
+	puts(_("\t--dont-blacklist\t-K\tDon't blacklist a given IP."));
+	puts(_("\t--dont-scan-on-error\t-d\tPass e-mails through unscanned if a system error occurs."));
+	puts(_("\t--dont-wait\t\t\tAsk remote end to resend if max-children exceeded."));
+	puts(_("\t--dont-sanitise\t\t\tAllow semicolon and pipe characters in email addresses."));
+	puts(_("\t--external\t\t-e\tUse an external scanner (usually clamd)."));
+	puts(_("\t--freshclam-monitor=SECS\t-M SECS\tHow often to check for database update."));
+	puts(_("\t--from=EMAIL\t\t-a EMAIL\tError messages come from here."));
+	puts(_("\t--force-scan\t\t-f\tForce scan all messages (overrides (-o and -l)."));
+	puts(_("\t--help\t\t\t-h\tThis message."));
+	puts(_("\t--headers\t\t-H\tInclude original message headers in the report."));
+	puts(_("\t--ignore IPaddr\t\t-I IPaddr\tAdd IPaddr to LAN IP list (see --local)."));
+	puts(_("\t--local\t\t\t-l\tScan messages sent from machines on our LAN."));
+	puts(_("\t--max-childen\t\t-m\tMaximum number of concurrent scans."));
+	puts(_("\t--outgoing\t\t-o\tScan outgoing messages from this machine."));
+	puts(_("\t--noreject\t\t-N\tDon't reject viruses, silently throw them away."));
+	puts(_("\t--noxheader\t\t-n\tSuppress X-Virus-Scanned/X-Virus-Status headers."));
+	puts(_("\t--pidfile=FILE\t\t-i FILE\tLocation of pidfile."));
+	puts(_("\t--postmaster\t\t-p EMAIL\tPostmaster address [default=postmaster]."));
+	puts(_("\t--postmaster-only\t-P\tSend notifications only to the postmaster."));
+	puts(_("\t--quiet\t\t\t-q\tDon't send e-mail notifications of interceptions."));
+	puts(_("\t--quarantine=USER\t-Q EMAIL\tQuarantine e-mail account."));
+	puts(_("\t--report-phish=EMAIL\t-r EMAIL\tReport phish to this email address."));
+	puts(_("\t--report-phish-false-positives=EMAIL\t-R EMAIL\tReport phish false positves to this email address."));
+	puts(_("\t--quarantine-dir=DIR\t-U DIR\tDirectory to store infected emails."));
+	puts(_("\t--server=SERVER\t\t-s SERVER\tHostname/IP address of server(s) running clamd (when using TCPsocket)."));
+	puts(_("\t--sendmail-cf=FILE\t\tLocation of the sendmail.cf file to verify"));
+	puts(_("\t--no-check-cf\t\tSkip verification of sendmail.cf"));
+	puts(_("\t--sign\t\t\t-S\tAdd a hard-coded signature to each scanned message."));
+	puts(_("\t--signature-file=FILE\t-F FILE\tLocation of signature file."));
+	puts(_("\t--template-file=FILE\t-t FILE\tLocation of e-mail template file."));
+	puts(_("\t--template-headers=FILE\t\tLocation of e-mail headers for template file."));
+	puts(_("\t--timeout=SECS\t\t-T SECS\tTimeout waiting to childen to die."));
+	puts(_("\t--whitelist-file=FILE\t-W FILE\tLocation of the file of whitelisted addresses"));
+	puts(_("\t--version\t\t-V\tPrint the version number of this software."));
+#ifdef	CL_DEBUG
+	puts(_("\t--debug-level=n\t\t-x n\tSets the debug level to 'n'."));
+#endif
+	puts(_("\nFor more information type \"man clamav-milter\"."));
+	puts(_("For bug reports, please refer to http://www.clamav.net/bugs"));
+}
+
+extern char *optarg;
+int
+main(int argc, char **argv)
+{
+	int i, Bflag = 0, server = 0;
+	char *cfgfile = NULL;
+	const char *wont_blacklist = NULL;
+	const struct cfgstruct *cpt;
+	char version[VERSION_LENGTH + 1];
+	pthread_t tid;
+	struct rlimit rlim;
+#ifdef	CL_DEBUG
+	int consolefd;
+#endif
+
+	/*
+	 * The SMFI_VERSION checks are for Sendmail 8.14, which I don't have
+	 * yet, so I can't verify them
+	 * Patch from Andy Fiddaman <clam at fiddaman.net>
+	 */
+	struct smfiDesc smfilter = {
+		"ClamAv", /* filter name */
+		SMFI_VERSION,	/* version code -- leave untouched */
+		SMFIF_ADDHDRS|SMFIF_CHGHDRS,	/* flags - we add and delete headers */
+		clamfi_connect, /* connexion callback */
+#ifdef	CL_DEBUG
+		clamfi_helo,	/* HELO filter callback */
+#else
+		NULL,
+#endif
+		clamfi_envfrom, /* envelope sender filter callback */
+		clamfi_envrcpt, /* envelope recipient filter callback */
+		clamfi_header,	/* header filter callback */
+		clamfi_eoh,	/* end of header callback */
+		clamfi_body,	/* body filter callback */
+		clamfi_eom,	/* end of message callback */
+		clamfi_abort,	/* message aborted callback */
+		clamfi_close,	/* connexion cleanup callback */
+#if	SMFI_VERSION > 2
+		NULL,		/* Unrecognised command */
+#endif
+#if	SMFI_VERSION > 3
+		NULL,		/* DATA command callback */
+#endif
+#if	SMFI_VERSION >= 0x01000000
+		NULL,		/* Negotiation callback */
+#endif
+	};
+
+#if defined(CL_DEBUG) && defined(C_LINUX)
+	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
+	if(setrlimit(RLIMIT_CORE, &rlim) < 0)
+		perror("setrlimit");
+#endif
+
+	/*
+	 * Temporarily enter guessed value into version, will
+	 * be overwritten later by the value returned by clamd
+	 */
+	snprintf(version, sizeof(version) - 1,
+		"ClamAV version %s, clamav-milter version %s",
+		cl_retver(), get_version());
+
+	progname = strrchr(argv[0], '/');
+	if(progname)
+		progname++;
+	else
+		progname = "clamav-milter";
+
+#ifdef	C_LINUX
+	setlocale(LC_ALL, "");
+	bindtextdomain(progname, DATADIR"/clamav-milter/locale");
+	textdomain(progname);
+#endif
+
+	for(;;) {
+		int opt_index = 0;
+#ifdef	BOUNCE
+#ifdef	CL_DEBUG
+		const char *args = "a:AbB:c:C:dDefF:I:i:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:x:z0:1:2";
+#else
+		const char *args = "a:AbB:c:C:dDefF:I:i:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:z0:1:2";
+#endif
+#else	/*!BOUNCE*/
+#ifdef	CL_DEBUG
+		const char *args = "a:AB:c:C:dDefF:I:i:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:x:z0:1:2";
+#else
+		const char *args = "a:AB:c:C:dDefF:I:i:k:K:lLm:M:nNop:PqQ:r:R:hHs:St:T:U:VwW:z0:1:2";
+#endif
+#endif	/*BOUNCE*/
+
+		static struct option long_options[] = {
+			{
+				"from", 2, NULL, 'a'
+			},
+			{
+				"advisory", 0, NULL, 'A'
+			},
+#ifdef	BOUNCE
+			{
+				"bounce", 0, NULL, 'b'
+			},
+#endif
+			{
+				"broadcast", 2, NULL, 'B'
+			},
+			{
+				"config-file", 1, NULL, 'c'
+			},
+			{
+				"chroot", 1, NULL, 'C'
+			},
+			{
+				"detect-forged-local-address", 0, NULL, 'L'
+			},
+			{
+				"dont-blacklist", 1, NULL, 'K'
+			},
+			{
+				"dont-scan-on-error", 0, NULL, 'd'
+			},
+			{
+				"dont-wait", 0, NULL, 'w'
+			},
+			{
+				"dont-sanitise", 0, NULL, 'z'
+			},
+			{
+				"debug", 0, NULL, 'D'
+			},
+			{
+				"external", 0, NULL, 'e'
+			},
+			{
+				"force-scan", 0, NULL, 'f'
+			},
+			{
+				"headers", 0, NULL, 'H'
+			},
+			{
+				"help", 0, NULL, 'h'
+			},
+			{
+				"ignore", 1, NULL, 'I'
+			},
+			{
+				"pidfile", 1, NULL, 'i'
+			},
+			{
+				"blacklist-time", 1, NULL, 'k'
+			},
+			{
+				"local", 0, NULL, 'l'
+			},
+			{
+				"noreject", 0, NULL, 'N'
+			},
+			{
+				"noxheader", 0, NULL, 'n'
+			},
+			{
+				"outgoing", 0, NULL, 'o'
+			},
+			{
+				"postmaster", 1, NULL, 'p'
+			},
+			{
+				"postmaster-only", 0, NULL, 'P',
+			},
+			{
+				"quiet", 0, NULL, 'q'
+			},
+			{
+				"quarantine", 1, NULL, 'Q',
+			},
+			{
+				"report-phish", 1, NULL, 'r'
+			},
+			{
+				"report-phish-false-positives", 1, NULL, 'R'
+			},
+			{
+				"quarantine-dir", 1, NULL, 'U',
+			},
+			{
+				"max-children", 1, NULL, 'm'
+			},
+			{
+				"freshclam-monitor", 1, NULL, 'M'
+			},
+			{
+				"sendmail-cf", 1, NULL, '0'
+			},
+			{
+				"no-check-cf", 0, &checkCF, 0
+			},
+			{
+				"server", 1, NULL, 's'
+			},
+			{
+				"sign", 0, NULL, 'S'
+			},
+			{
+				"signature-file", 1, NULL, 'F'
+			},
+			{
+				"template-file", 1, NULL, 't'
+			},
+			{
+				"template-headers", 1, NULL, '1'
+			},
+			{
+				"timeout", 1, NULL, 'T'
+			},
+			{
+				"whitelist-file", 1, NULL, 'W'
+			},
+			{
+				"version", 0, NULL, 'V'
+			},
+			{
+				"black-hole-mode", 0, NULL, '2'
+			},
+#ifdef	CL_DEBUG
+			{
+				"debug-level", 1, NULL, 'x'
+			},
+#endif
+			{
+				NULL, 0, NULL, '\0'
+			}
+		};
+
+		int ret = getopt_long(argc, argv, args, long_options, &opt_index);
+
+		if(ret == -1)
+			break;
+  		else if(ret == 0)
+  			continue;
+
+		switch(ret) {
+			case 'a':	/* e-mail errors from here */
+				/*
+				 * optarg is optional - if you give --from
+				 * then the --from is set to the orginal,
+				 * probably forged, email address
+				 */
+				from = optarg;
+				break;
+			case 'A':
+				advisory++;
+				break;
+#ifdef	BOUNCE
+			case 'b':	/* bounce worms/viruses */
+				bflag++;
+				break;
+#endif
+			case 'B':	/* broadcast */
+				Bflag++;
+				if(optarg)
+					iface = optarg;
+				break;
+			case 'c':	/* where is clamd.conf? */
+				cfgfile = optarg;
+				break;
+			case 'C':	/* chroot */
+				rootdir = optarg;
+				break;
+			case 'd':	/* don't scan on error */
+				cl_error = SMFIS_ACCEPT;
+				break;
+			case 'D':	/* enable debug messages */
+				cl_debug();
+				break;
+			case 'e':	/* use clamd */
+				external++;
+				break;
+			case 'f':	/* force the scan */
+				fflag++;
+				break;
+			case 'h':
+				help();
+				return EX_OK;
+			case 'H':
+				hflag++;
+				break;
+			case 'i':	/* pidfile */
+				pidfile = optarg;
+				break;
+			case 'k':	/* blacklist time */
+				blacklist_time = atoi(optarg);
+				break;
+			case 'K':	/* don't black list given IP */
+				wont_blacklist = optarg;
+				break;
+			case 'I':	/* --ignore, -I hostname */
+				/*
+				 * Based on patch by jpd at louisiana.edu
+				 */
+				if(Iflag == IFLAG_MAX) {
+					fprintf(stderr,
+						_("%s: %s, -I may only be given %d times\n"),
+							argv[0], optarg, IFLAG_MAX);
+					return EX_USAGE;
+				}
+				if(!add_local_ip(optarg)) {
+					fprintf(stderr,
+						_("%s: Cannot convert -I%s to IPaddr\n"),
+							argv[0], optarg);
+					return EX_USAGE;
+				}
+				Iflag++;
+				break;
+			case 'l':	/* scan mail from the lan */
+				lflag++;
+				break;
+			case 'L':	/* detect forged local addresses */
+				detect_forged_local_address++;
+				break;
+			case 'm':	/* maximum number of children */
+				max_children = atoi(optarg);
+				break;
+			case 'M':	/* how often to monitor for freshclam */
+				freshclam_monitor = atoi(optarg);
+				break;
+			case 'n':	/* don't add X-Virus-Scanned */
+				nflag++;
+				smfilter.xxfi_flags &= ~(SMFIF_ADDHDRS|SMFIF_CHGHDRS);
+				break;
+			case 'N':	/* Do we reject mail or silently drop it */
+				rejectmail = 0;
+				break;
+			case 'o':	/* scan outgoing mail */
+				oflag++;
+				break;
+			case 'p':	/* postmaster e-mail address */
+				postmaster = optarg;
+				break;
+			case 'P':	/* postmaster only */
+				pflag++;
+				break;
+			case 'q':	/* send NO notification email */
+				qflag++;
+				break;
+			case 'Q':	/* quarantine e-mail address */
+				quarantine = optarg;
+				smfilter.xxfi_flags |= SMFIF_CHGHDRS|SMFIF_ADDRCPT|SMFIF_DELRCPT;
+				break;
+			case 'r':	/* report phishing here */
+				/* e.g. reportphishing at antiphishing.org */
+				report = optarg;
+				break;
+			case 'R':	/* report phishing false positives here */
+				report_fps = optarg;
+				break;
+			case 's':	/* server running clamd */
+				server++;
+				serverHostNames = optarg;
+				break;
+			case 'F':	/* signature file */
+				sigFilename = optarg;
+				signature = NULL;
+				/* fall through */
+			case 'S':	/* sign */
+				smfilter.xxfi_flags |= SMFIF_CHGBODY;
+				Sflag++;
+				break;
+			case 't':	/* e-mail template file */
+				templateFile = optarg;
+				break;
+			case '1':	/* headers for the template file */
+				templateHeaders = optarg;
+				break;
+			case '2':
+				black_hole_mode++;
+				break;
+			case 'T':	/* time to wait for child to die */
+				child_timeout = atoi(optarg);
+				break;
+			case 'U':	/* quarantine path */
+				quarantine_dir = optarg;
+				break;
+			case 'V':
+				puts(version);
+				return EX_OK;
+			case 'w':
+				dont_wait++;
+				break;
+			case 'W':
+				whitelistFile = optarg;
+				break;
+			case 'z':
+				dont_sanitise=1;
+				break;
+			case '0':
+				sendmailCF = optarg;
+				break;
+#ifdef	CL_DEBUG
+			case 'x':
+				debug_level = atoi(optarg);
+				break;
+#endif
+			default:
+#ifdef	CL_DEBUG
+				fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-e] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-s SERVER] [-S] [-x#] [-U PATH] [-M#] socket-addr\n", argv[0]);
+#else
+				fprintf(stderr, "Usage: %s [-b] [-c FILE] [-F FILE] [--max-children=num] [-e] [-l] [-o] [-p address] [-P] [-q] [-Q USER] [-s SERVER] [-S] [-U PATH] [-M#] socket-addr\n", argv[0]);
+#endif
+				return EX_USAGE;
+		}
+	}
+
+	/*
+	 * Check sanity of --external and --server arguments
+	 */
+	if(server && !external) {
+		fprintf(stderr,
+			"%s: --server can only be used with --external\n",
+			argv[0]);
+		return EX_USAGE;
+	}
+#ifdef	SESSION
+	if(!external) {
+		fprintf(stderr,
+			_("%s: SESSIONS mode requires --external\n"), argv[0]);
+		return EX_USAGE;
+	}
+#endif
+
+	/* TODO: support freshclam's daemon notify if --external is not given */
+
+	if(optind == argc) {
+		fprintf(stderr, _("%s: No socket-addr given\n"), argv[0]);
+		return EX_USAGE;
+	}
+	port = argv[optind];
+
+	if(rootdir == NULL)	/* FIXME: Handle CHROOT */
+		if(checkCF && verifyIncomingSocketName(port) < 0) {
+			fprintf(stderr, _("%s: socket-addr (%s) doesn't agree with sendmail.cf\n"), argv[0], port);
+			return EX_CONFIG;
+		}
+
+	if(strncasecmp(port, "inet:", 5) == 0)
+		if(!lflag) {
+			/*
+			 * Barmy but true. It seems that clamfi_connect will,
+			 * in this case, get the IP address of the machine
+			 * running sendmail, not of the machine sending the
+			 * mail, so the remote end will be a local address so
+			 * we must scan by enabling --local
+			 *
+			 * TODO: this is probably not needed if the remote
+			 * machine is localhost, need to check though
+			 */
+			fprintf(stderr, _("%s: when using inet: connexion to sendmail you must enable --local\n"), argv[0]);
+			return EX_USAGE;
+		}
+
+	/*
+	 * Sanity checks on the clamav configuration file
+	 */
+	if(cfgfile == NULL) {
+		cfgfile = cli_malloc(strlen(CONFDIR) + 12);	/* leak */
+		sprintf(cfgfile, "%s/clamd.conf", CONFDIR);
+	}
+	if((copt = getcfg(cfgfile, 1, OPT_CLAMD)) == NULL) {
+		fprintf(stderr, _("%s: Can't parse the config file %s\n"),
+			argv[0], cfgfile);
+		return EX_CONFIG;
+	}
+
+	if(detect_forged_local_address) {
+		if(oflag) {
+			fprintf(stderr, _("%s: --detect-forged-local-addresses is not compatible with --outgoing\n"), argv[0]);
+			return EX_CONFIG;
+		}
+		if(lflag) {
+			fprintf(stderr, _("%s: --detect-forged-local-addresses is not compatible with --local\n"), argv[0]);
+			return EX_CONFIG;
+		}
+		if(fflag) {
+			fprintf(stderr, _("%s: --detect-forged-local-addresses is not compatible with --force\n"), argv[0]);
+			return EX_CONFIG;
+		}
+	}
+
+	if(Bflag) {
+		int on;
+
+		broadcastSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+		/*
+		 * SO_BROADCAST doesn't sent to all NICs on Linux, it only
+		 * broadcasts on eth0, which is why there's an optional argument
+		 * to --broadcast to say which NIC to broadcast on. You can use
+		 * SO_BINDTODEVICE to get around that, but you need to have
+		 * uid == 0 for that
+		 */
+		on = 1;
+		if(setsockopt(broadcastSock, SOL_SOCKET, SO_BROADCAST, (int *)&on, sizeof(on)) < 0) {
+			perror("setsockopt");
+			return EX_UNAVAILABLE;
+		}
+		shutdown(broadcastSock, SHUT_RD);
+	}
+
+	/*
+	 * Drop privileges
+	 */
+#ifdef	CL_DEBUG
+	/* Save the fd for later, open while we can */
+	consolefd = open(console, O_WRONLY);
+#endif
+
+	if(getuid() == 0) {
+		if(iface) {
+#ifdef	SO_BINDTODEVICE
+			struct ifreq ifr;
+
+			memset(&ifr, '\0', sizeof(struct ifreq));
+			strncpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name) - 1);
+			ifr.ifr_name[sizeof(ifr.ifr_name)-1]='\0';
+			if(setsockopt(broadcastSock, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0) {
+				perror(iface);
+				return EX_CONFIG;
+			}
+#else
+			fprintf(stderr, _("%s: The iface option to --broadcast is not supported on your operating system\n"), argv[0]);
+			return EX_CONFIG;
+#endif
+		}
+
+		if(((cpt = cfgopt(copt, "User")) != NULL) && cpt->enabled) {
+			const struct passwd *user;
+
+			if((user = getpwnam(cpt->strarg)) == NULL) {
+				fprintf(stderr, _("%s: Can't get information about user %s\n"), argv[0], cpt->strarg);
+				return EX_CONFIG;
+			}
+
+			if(cfgopt(copt, "AllowSupplementaryGroups")->enabled) {
+#ifdef HAVE_INITGROUPS
+				if(initgroups(cpt->strarg, user->pw_gid) < 0) {
+					perror(cpt->strarg);
+					return EX_CONFIG;
+				}
+#else
+				fprintf(stderr, _("%s: AllowSupplementaryGroups: initgroups not supported.\n"),
+					argv[0]);
+				return EX_CONFIG;
+#endif
+			} else {
+#ifdef	HAVE_SETGROUPS
+				if(setgroups(1, &user->pw_gid) < 0) {
+					perror(cpt->strarg);
+					return EX_CONFIG;
+				}
+#endif
+			}
+
+			setgid(user->pw_gid);
+
+			if(setuid(user->pw_uid) < 0)
+				perror(cpt->strarg);
+#ifdef	CL_DEBUG
+			else
+				printf(_("Running as user %s (UID %d, GID %d)\n"),
+					cpt->strarg, (int)user->pw_uid,
+					(int)user->pw_gid);
+#endif
+
+			/*
+			 * Note, some O/Ss (e.g. OpenBSD/Fedora Linux) FORCE
+			 * you to run as root in black-hole-mode because
+			 * /var/spool/mqueue is mode 700 owned by root!
+			 * Flames to them, not to me, please.
+			 */
+			if(black_hole_mode && (user->pw_uid != 0)) {
+				int are_trusted;
+				FILE *sendmail;
+				char cmd[128];
+
+				/*
+				 * Determine if we're a "trusted user"
+				 */
+				snprintf(cmd, sizeof(cmd) - 1, "%s -bv root</dev/null 2>&1",
+					SENDMAIL_BIN);
+
+				sendmail = popen(cmd, "r");
+
+				if(sendmail == NULL) {
+					perror(SENDMAIL_BIN);
+					are_trusted = 0;
+				} else {
+					int status;
+					char buf[BUFSIZ];
+
+					while(fgets(buf, sizeof(buf), sendmail) != NULL)
+						;
+					/*
+					 * Can't do
+					 * switch(WEXITSTATUS(pclose(sendmail)))
+					 * because that fails to compile on
+					 * NetBSD2.0
+					 */
+					status = pclose(sendmail);
+					switch(WEXITSTATUS(status)) {
+						case EX_NOUSER:
+							/*
+							 * No root? But at least
+							 * we're trusted enough
+							 * to find out!
+							 */
+							are_trusted = 1;
+							break;
+						default:
+							are_trusted = 0;
+							break;
+						case EX_OK:
+							are_trusted = 1;
+					}
+				}
+				if(!are_trusted) {
+					fprintf(stderr, _("%s: You cannot use black hole mode unless %s is a TrustedUser\n"),
+						argv[0], cpt->strarg);
+					return EX_CONFIG;
+				}
+			}
+		} else
+			printf(_("^%s: running as root is not recommended (check \"User\" in %s)\n"), argv[0], cfgfile);
+	} else if(iface) {
+		fprintf(stderr, _("%s: Only root can set an interface for --broadcast\n"), argv[0]);
+		return EX_USAGE;
+	}
+
+	if(advisory && quarantine) {
+		fprintf(stderr, _("%s: Advisory mode doesn't work with quarantine mode\n"), argv[0]);
+		return EX_USAGE;
+	}
+	if(quarantine_dir) {
+		struct stat statb;
+
+		if(advisory) {
+			fprintf(stderr,
+				_("%s: Advisory mode doesn't work with quarantine directories\n"),
+				argv[0]);
+			return EX_USAGE;
+		}
+		if(strstr(quarantine_dir, "ERROR") != NULL) {
+			fprintf(stderr,
+				_("%s: the quarantine directory must not contain the string 'ERROR'\n"),
+				argv[0]);
+			return EX_USAGE;
+		}
+		if(strstr(quarantine_dir, "FOUND") != NULL) {
+			fprintf(stderr,
+				_("%s: the quarantine directory must not contain the string 'FOUND'\n"),
+				argv[0]);
+			return EX_USAGE;
+		}
+		if(strstr(quarantine_dir, "OK") != NULL) {
+			fprintf(stderr,
+				_("%s: the quarantine directory must not contain the string 'OK'\n"),
+				argv[0]);
+			return EX_USAGE;
+		}
+		if(access(quarantine_dir, W_OK) < 0) {
+			perror(quarantine_dir);
+			return EX_USAGE;
+		}
+		if(stat(quarantine_dir, &statb) < 0) {
+			perror(quarantine_dir);
+			return EX_USAGE;
+		}
+		/*
+		 * Quit if the quarantine directory is publically readable
+		 * or writeable
+		 */
+		if(statb.st_mode & 077) {
+			fprintf(stderr, _("%s: insecure quarantine directory %s (mode 0%o)\n"),
+				argv[0], quarantine_dir, (int)statb.st_mode & 0777);
+			return EX_CONFIG;
+		}
+	}
+
+	if(sigFilename && !updateSigFile())
+		return EX_USAGE;
+
+	if(templateFile && (access(templateFile, R_OK) < 0)) {
+		perror(templateFile);
+		return EX_CONFIG;
+	}
+	if(templateHeaders) {
+		if(templateFile == NULL) {
+			fputs(("%s: --template-headers requires --template-file\n"),
+				stderr);
+			return EX_CONFIG;
+		}
+		if(access(templateHeaders, R_OK) < 0) {
+			perror(templateHeaders);
+			return EX_CONFIG;
+		}
+	}
+	if(whitelistFile && (access(whitelistFile, R_OK) < 0)) {
+		perror(whitelistFile);
+		return EX_CONFIG;
+	}
+
+	/*
+	 * If the --max-children flag isn't set, see if MaxThreads
+	 * is set in the config file. Based on an idea by "Richard G. Roberto"
+	 * <rgr at dedlegend.com>
+	 */
+	if((max_children == 0) && ((cpt = cfgopt(copt, "MaxThreads")) != NULL))
+		max_children = cfgopt(copt, "MaxThreads")->numarg;
+
+#ifdef HAVE_LRESOLV_R
+	/* allocate a pool of resolvers */
+	if(!(res_pool=cli_calloc(max_children+1, sizeof(*res_pool))))
+		return EX_OSERR;
+	if(!(res_pool_state=cli_malloc(max_children+1)))
+		return EX_OSERR;
+	memset(res_pool_state, 1, max_children+1);
+	for(i = 0; i < max_children+1; i++)
+		res_ninit(&res_pool[i]);
+#endif
+
+	if((cpt = cfgopt(copt, "ReadTimeout")) != NULL) {
+		readTimeout = cpt->numarg;
+
+		if(readTimeout < 0) {
+			fprintf(stderr, _("%s: ReadTimeout must not be negative in %s\n"),
+				argv[0], cfgfile);
+			return EX_CONFIG;
+		}
+	}
+
+	if((cpt = cfgopt(copt, "StreamMaxLength")) != NULL) {
+		streamMaxLength = (long)cpt->numarg;
+		if(streamMaxLength < 0L) {
+			fprintf(stderr, _("%s: StreamMaxLength must not be negative in %s\n"),
+			argv[0], cfgfile);
+			return EX_CONFIG;
+		}
+	}
+
+	if(((cpt = cfgopt(copt, "LogSyslog")) != NULL) && cpt->enabled) {
+#if defined(USE_SYSLOG) && !defined(C_AIX)
+		int fac = LOG_LOCAL6;
+#endif
+
+		if(cfgopt(copt, "LogVerbose")->enabled) {
+			logg_verbose = 1;
+#ifdef	CL_DEBUG
+#if	((SENDMAIL_VERSION_A > 8) || ((SENDMAIL_VERSION_A == 8) && (SENDMAIL_VERSION_B >= 13)))
+			if(debug_level >= 15)
+				smfi_setdbg(6);
+#endif
+#endif
+		}
+#if defined(USE_SYSLOG) && !defined(C_AIX)
+		logg_syslog = 1;
+
+		if(((cpt = cfgopt(copt, "LogFacility")) != NULL) && cpt->enabled)
+			if((fac = logg_facility(cpt->strarg)) == -1) {
+				fprintf(stderr, "%s: LogFacility: %s: No such facility\n",
+					argv[0], cpt->strarg);
+				return EX_CONFIG;
+			}
+		openlog(progname, LOG_CONS|LOG_PID, fac);
+#endif
+	} else {
+		if(qflag)
+			fprintf(stderr, _("%s: (-q && !LogSyslog): warning - all interception message methods are off\n"),
+				argv[0]);
+#if defined(USE_SYSLOG) && !defined(C_AIX)
+		logg_syslog = 0;
+#endif
+	}
+	/*
+	 * Get the outgoing socket details - the way to talk to clamd, unless
+	 * we're doing the scanning internally
+	 */
+	if(!external) {
+#ifdef	C_LINUX
+		const char *lang;
+#endif
+
+		if(max_children == 0) {
+			fprintf(stderr, _("%s: --max-children must be given if --external is not given\n"), argv[0]);
+			return EX_CONFIG;
+		}
+		if(freshclam_monitor <= 0) {
+			fprintf(stderr, _("%s: --freshclam_monitor must be at least one second\n"), argv[0]);
+			return EX_CONFIG;
+		}
+#ifdef	C_LINUX
+		lang = getenv("LANG");
+
+		if(lang && (strstr(lang, "UTF-8") != NULL)) {
+			fprintf(stderr, "Your LANG environment variable is set to '%s'\n", lang);
+			fprintf(stderr, "This is known to cause problems for some %s installations.\n", argv[0]);
+			fputs("If you get failures with temporary files, please try again with LANG unset.\n", stderr);
+		}
+#endif
+#if	0
+		if(child_timeout) {
+			fprintf(stderr, _("%s: --timeout must not be given if --external is not given\n"), argv[0]);
+			return EX_CONFIG;
+		}
+#endif
+		if (cl_init(CL_INIT_DEFAULT)!=CL_SUCCESS) {
+			fprintf(stderr, "%s: Failed to initialize libclamav, bailing out.\n", argv[0]);
+			return EX_UNAVAILABLE;
+		}
+		if(loadDatabase() != 0) {
+			/*
+			 * Handle the dont-scan-on-error option, which says
+			 * that we pass on emails, unscanned, if an error has
+			 * occurred
+			 */
+			if(cl_error != SMFIS_ACCEPT)
+				return EX_CONFIG;
+
+			fprintf(stderr, _("%s: No emails will be scanned"),
+				argv[0]);
+		}
+		numServers = 1;
+	} else if(((cpt = cfgopt(copt, "LocalSocket")) != NULL) && cpt->enabled) {
+#ifdef	SESSION
+		struct sockaddr_un sockun;
+#endif
+		char *sockname = NULL;
+
+		if(cfgopt(copt, "TCPSocket")->enabled) {
+			fprintf(stderr, _("%s: You can select one server type only (local/TCP) in %s\n"),
+				argv[0], cfgfile);
+			return EX_CONFIG;
+		}
+		if(server) {
+			fprintf(stderr, _("%s: You cannot use the --server option when using LocalSocket in %s\n"),
+				argv[0], cfgfile);
+			return EX_USAGE;
+		}
+		if(strncasecmp(port, "unix:", 5) == 0)
+			sockname = &port[5];
+		else if(strncasecmp(port, "local:", 6) == 0)
+			sockname = &port[6];
+
+		if(sockname && (strcmp(sockname, cpt->strarg) == 0)) {
+			fprintf(stderr, _("The connexion from sendmail to %s (%s) must not\n"),
+				argv[0], sockname);
+			fprintf(stderr, _("be the same as the connexion to clamd (%s) in %s\n"),
+				cpt->strarg, cfgfile);
+			return EX_CONFIG;
+		}
+		/*
+		 * TODO: check --server hasn't been set
+		 */
+		localSocket = cpt->strarg;
+#ifndef	SESSION
+		if(!pingServer(-1)) {
+			fprintf(stderr, _("Can't talk to clamd server via %s\n"),
+				localSocket);
+			fprintf(stderr, _("Check your entry for LocalSocket in %s\n"),
+				cfgfile);
+			return EX_CONFIG;
+		}
+#endif
+		/*if(quarantine_dir == NULL)
+			fprintf(stderr, _("When using Localsocket in %s\nyou may improve performance if you use the --quarantine-dir option\n"), cfgfile);*/
+
+		umask(077);
+
+		serverIPs = (in_addr_t *)cli_malloc(sizeof(in_addr_t));
+#ifdef	INADDR_LOOPBACK
+		serverIPs[0] = htonl(INADDR_LOOPBACK);
+#else
+		serverIPs[0] = inet_addr("127.0.0.1");
+#endif
+
+#ifdef	SESSION
+		memset((char *)&sockun, 0, sizeof(struct sockaddr_un));
+		sockun.sun_family = AF_UNIX;
+		strncpy(sockun.sun_path, localSocket, sizeof(sockun.sun_path));
+		sockun.sun_path[sizeof(sockun.sun_path)-1]='\0';
+
+		sessions = (struct session *)cli_malloc(sizeof(struct session));
+		if((sessions[0].sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+			perror(localSocket);
+			fprintf(stderr, _("Can't talk to clamd server via %s\n"),
+				localSocket);
+			fprintf(stderr, _("Check your entry for LocalSocket in %s\n"),
+				cfgfile);
+			return EX_CONFIG;
+		}
+		if(connect(sessions[0].sock, (struct sockaddr *)&sockun, sizeof(struct sockaddr_un)) < 0) {
+			perror(localSocket);
+			return EX_UNAVAILABLE;
+		}
+		if(send(sessions[0].sock, "SESSION\n", 8, 0) < 8) {
+			perror("send");
+			fputs(_("!Can't create a clamd session"), stderr);
+			return EX_UNAVAILABLE;
+		}
+		sessions[0].status = CMDSOCKET_FREE;
+#endif
+		/*
+		 * FIXME: Allow connexion to remote servers by TCP/IP whilst
+		 * connecting to the localserver via a UNIX domain socket
+		 */
+		numServers = 1;
+	} else if(((cpt = cfgopt(copt, "TCPSocket")) != NULL) && cpt->enabled) {
+		int activeServers;
+
+		/*
+		 * TCPSocket is in fact a port number not a full socket
+		 */
+		if(quarantine_dir) {
+			fprintf(stderr, _("%s: --quarantine-dir not supported for TCPSocket - use --quarantine\n"), argv[0]);
+			return EX_CONFIG;
+		}
+
+		tcpSocket = (in_port_t)cpt->numarg;
+
+		/*
+		 * cli_strtok's fieldno counts from 0
+		 */
+		for(;;) {
+			char *hostname = cli_strtok(serverHostNames, numServers, ":");
+			if(hostname == NULL)
+				break;
+#ifdef	MAXHOSTNAMELEN
+			if(strlen(hostname) > MAXHOSTNAMELEN) {
+				fprintf(stderr, _("%s: hostname %s is longer than %d characters\n"),
+					argv[0], hostname, MAXHOSTNAMELEN);
+				return EX_CONFIG;
+			}
+#endif
+			numServers++;
+			free(hostname);
+		}
+
+#ifdef	CL_DEBUG
+		printf("numServers: %d\n", numServers);
+#endif
+
+		serverIPs = (in_addr_t *)cli_malloc(numServers * sizeof(in_addr_t));
+		if(serverIPs == NULL)
+			return EX_OSERR;
+		activeServers = 0;
+
+#ifdef	SESSION
+		/*
+		 * We need to know how many connexion to establish to clamd
+		 */
+		if(max_children == 0) {
+			fprintf(stderr, _("%s: --max-children must be given in sessions mode\n"), argv[0]);
+			return EX_CONFIG;
+		}
+#endif
+
+		if(numServers > max_children) {
+			fprintf(stderr, _("%1$s: --max-children (%2$d) is lower than the number of servers you have (%3$d)\n"),
+				argv[0], max_children, numServers);
+			return EX_CONFIG;
+		}
+
+		for(i = 0; i < numServers; i++) {
+#ifdef	MAXHOSTNAMELEN
+			char hostname[MAXHOSTNAMELEN + 1];
+
+			if(cli_strtokbuf(serverHostNames, i, ":", hostname) == NULL)
+				break;
+#else
+			char *hostname = cli_strtok(serverHostNames, i, ":");
+#endif
+
+			/*
+			 * Translate server's name to IP address
+			 */
+			serverIPs[i] = inet_addr(hostname);
+#ifdef	INADDR_NONE
+			if(serverIPs[i] == INADDR_NONE) {
+#else
+			if(serverIPs[i] == (in_addr_t)-1) {
+#endif
+				const struct hostent *h = gethostbyname(hostname);
+
+				if(h == NULL) {
+					fprintf(stderr, _("%s: Unknown host %s\n"),
+						argv[0], hostname);
+					return EX_USAGE;
+				}
+
+				memcpy((char *)&serverIPs[i], h->h_addr, sizeof(serverIPs[i]));
+			}
+
+#if	defined(NTRIES) && ((NTRIES > 1))
+#ifndef	SESSION
+#ifdef	INADDR_LOOPBACK
+			if(serverIPs[i] == htonl(INADDR_LOOPBACK)) {
+#else
+#if	HAVE_IN_ADDR_T
+			if(serverIPs[i] == (in_addr_t)inet_addr("127.0.0.1")) {
+#else
+			if(serverIPs[i] == (long)inet_addr("127.0.0.1")) {
+#endif
+#endif
+				int tries;
+
+				/*
+				 * Fudge to allow clamd to come up on
+				 * our local machine
+				 */
+				for(tries = 0; tries < NTRIES - 1; tries++) {
+					if(pingServer(i))
+						break;
+					if(checkClamd(1))	/* will try all servers */
+						break;
+					puts(_("Waiting for clamd to come up"));
+					/*
+					 * something to do as the system starts
+					 */
+					sync();
+					sleep(1);
+				}
+				/* Will try one more time */
+			}
+#endif	/* NTRIES > 1 */
+
+			if(pingServer(i))
+				activeServers++;
+			else {
+				printf(_("Can't talk to clamd server %s on port %d\n"),
+					hostname, tcpSocket);
+				if(serverIPs[i] == htonl(INADDR_LOOPBACK)) {
+					if(cfgopt(copt, "TCPAddr")->enabled)
+						printf(_("Check the value for TCPAddr in %s\n"), cfgfile);
+				} else
+					printf(_("Check the value for TCPAddr in clamd.conf on %s\n"), hostname);
+			}
+#endif
+
+#ifndef	MAXHOSTNAMELEN
+			free(hostname);
+#endif
+		}
+#ifdef	SESSION
+		activeServers = numServers;
+
+		sessions = (struct session *)cli_calloc(max_children, sizeof(struct session));
+		for(i = 0; i < (int)max_children; i++)
+			if(createSession(i) < 0)
+				return EX_UNAVAILABLE;
+		if(activeServers == 0) {
+			fprintf(stderr, _("Check your entry for TCPSocket in %s\n"),
+				cfgfile);
+		}
+#else
+		if(activeServers == 0) {
+			fprintf(stderr, _("Check your entry for TCPSocket in %s\n"),
+				cfgfile);
+			fputs(_("Can't find any clamd server\n"), stderr);
+			return EX_CONFIG;
+		}
+		last_failed_pings = (time_t *)cli_calloc(numServers, sizeof(time_t));
+#endif
+	} else {
+		fprintf(stderr, _("%s: You must select server type (local/TCP) in %s\n"),
+			argv[0], cfgfile);
+		return EX_CONFIG;
+	}
+
+#ifdef	SESSION
+	if(!external) {
+		if(clamav_versions == NULL) {
+			clamav_versions = (char **)cli_malloc(sizeof(char *));
+			if(clamav_versions == NULL)
+				return EX_TEMPFAIL;
+			clamav_version = cli_strdup(version);
+		}
+	} else {
+		unsigned int session;
+
+		/*
+		 * We need to know how many connexions to establish to clamd
+		 */
+		if(max_children == 0) {
+			fprintf(stderr, _("%s: --max-children must be given in sessions mode\n"), argv[0]);
+			return EX_CONFIG;
+		}
+
+		clamav_versions = (char **)cli_malloc(max_children * sizeof(char *));
+		if(clamav_versions == NULL)
+			return EX_TEMPFAIL;
+
+		for(session = 0; session < max_children; session++) {
+			clamav_versions[session] = cli_strdup(version);
+			if(clamav_versions[session] == NULL)
+				return EX_TEMPFAIL;
+		}
+	}
+#else
+	strcpy(clamav_version, version);
+#endif
+
+	if(((quarantine_dir == NULL) && localSocket) || !external) {
+		/* set the temporary dir */
+		if((cpt = cfgopt(copt, "TemporaryDirectory")) && cpt->enabled) {
+			tmpdir = cpt->strarg;
+			/*
+			 * FIXME: replace this:
+			  cl_settempdir(tmpdir, (short)(cfgopt(copt, "LeaveTemporaryFiles")->enabled)); *
+			 * with:
+			 * cl_engine_set(engine, CL_ENGINE_TMPDIR, cpt->strarg);
+			 * somewhere...
+			 */
+		} else if((tmpdir = getenv("TMPDIR")) == (char *)NULL)
+			if((tmpdir = getenv("TMP")) == (char *)NULL)
+				if((tmpdir = getenv("TEMP")) == (char *)NULL)
+#ifdef	P_tmpdir
+					tmpdir = P_tmpdir;
+#else
+					tmpdir = "/tmp";
+#endif
+
+		/*
+		 * TODO: investigate mkdtemp on LINUX and possibly others
+		 */
+		tmpdir = cli_gentemp(NULL);
+
+		cli_dbgmsg("Making %s\n", tmpdir);
+
+		if(mkdir(tmpdir, 0700)) {
+			perror(tmpdir);
+			return EX_CANTCREAT;
+		}
+	} else
+		tmpdir = NULL;
+
+	if(report) {
+		if(!cfgopt(copt, "PhishingSignatures")->enabled) {
+			fprintf(stderr, "%s: You have chosen --report-phish, but PhishingSignatures is off in %s\n",
+				argv[0], cfgfile);
+			return EX_USAGE;
+		}
+		if((quarantine_dir == NULL) && (tmpdir == NULL)) {
+			/*
+			 * Limitation: doesn't store message in a temporary
+			 * file, so we won't be able to use mail < file
+			 */
+			fprintf(stderr, "%s: when using --external, --report-phish cannot be used without either LocalSocket or --quarantine-dir\n",
+				argv[0]);
+			return EX_USAGE;
+		}
+		if(lflag) {
+			/*
+			 * Naturally, if you attempt to scan the phish you've
+			 * just reported, it'll be blocked!
+			 */
+			fprintf(stderr, "%s: --report-phish cannot be used with --local\n",
+				argv[0]);
+			return EX_USAGE;
+		}
+	}
+	if(report_fps)
+		if(!cfgopt(copt, "PhishingSignatures")->enabled) {
+			fprintf(stderr, "%s: You have chosen --report-phish-false-positives, but PhishingSignatures is off in %s\n",
+				argv[0], cfgfile);
+			return EX_USAGE;
+		}
+
+	if(cfgopt(copt, "Foreground")->enabled)
+		logg_foreground = 1;
+	else {
+		logg_foreground = 0;
+#ifdef	CL_DEBUG
+		printf(_("When debugging it is recommended that you use Foreground mode in %s\n"), cfgfile);
+		puts(_("\tso that you can see all of the messages"));
+#endif
+
+		switch(fork()) {
+			case -1:
+				perror("fork");
+				return EX_OSERR;
+			case 0:	/* child */
+				break;
+			default:	/* parent */
+				return EX_OK;
+		}
+		close(0);
+		open("/dev/null", O_RDONLY);
+
+		/* initialize logger */
+		logg_lock = cfgopt(copt, "LogFileUnlock")->enabled;
+		logg_time = cfgopt(copt, "LogTime")->enabled;
+		logok = cfgopt(copt, "LogClean")->enabled;
+		logg_size = cfgopt(copt, "LogFileMaxSize")->numarg;
+		logg_verbose = mprintf_verbose = cfgopt(copt, "LogVerbose")->enabled;
+
+		if(cfgopt(copt, "Debug")->enabled) /* enable debug messages in libclamav */
+			cl_debug();
+
+		if((cpt = cfgopt(copt, "LogFile"))->enabled) {
+			time_t currtime;
+
+			logg_file = cpt->strarg;
+			if((strlen(logg_file) < 2) ||
+			   ((logg_file[0] != '/') && (logg_file[0] != '\\') && (logg_file[1] != ':'))) {
+				fprintf(stderr, "ERROR: LogFile requires full path.\n");
+				logg_close();
+				freecfg(copt);
+				return 1;
+			}
+			time(&currtime);
+			close(1);
+			if(logg("#ClamAV-milter started at %s", ctime(&currtime))) {
+				fprintf(stderr, "ERROR: Problem with internal logger. Please check the permissions on the %s file.\n", logg_file);
+				logg_close();
+				freecfg(copt);
+				return 1;
+			}
+		} else {
+#ifdef	CL_DEBUG
+			close(1);
+			logg_file = console;
+			if(consolefd < 0) {
+				perror(console);
+				return EX_OSFILE;
+			}
+			dup(consolefd);
+#else
+			int fds[3];
+			logg_file = NULL;
+			if(chdir("/") < 0)
+				perror("/");
+			fds[0] = open("/dev/null", O_RDONLY);
+			fds[1] = open("/dev/null", O_WRONLY);
+			fds[2] = open("/dev/null", O_WRONLY);
+			for(i = 0; i <= 2; i++) {
+				if(fds[i] == -1 || dup2(fds[i], i) == -1) {
+					fprintf(stderr, "ERROR: failed to daemonize.\n");
+					logg_close();
+					freecfg(copt);
+					return 1;
+				}
+			}
+#endif
+		}
+
+		dup2(1, 2);
+
+#ifdef	CL_DEBUG
+		if(consolefd >= 0)
+			close(consolefd);
+#endif
+
+#ifdef HAVE_SETPGRP
+#ifdef SETPGRP_VOID
+		setpgrp();
+#else
+		setpgrp(0,0);
+#endif
+#else
+#ifdef HAVE_SETSID
+		setsid();
+#endif
+#endif
+	}
+
+	if(cfgopt(copt, "Debug")->enabled)
+		/*
+		 * enable debug messages in libclamav, --debug also does this
+		 */
+		cl_debug();
+
+	atexit(quit);
+
+	if(!external) {
+		if(!cfgopt(copt, "ScanMail")->enabled)
+			printf(_("%s: ScanMail not defined in %s (needed without --external), enabling\n"),
+				argv[0], cfgfile);
+
+		options |= CL_SCAN_MAIL;	/* no choice */
+		/*if(!cfgopt(copt, "ScanRAR")->enabled)
+			options |= CL_SCAN_DISABLERAR;*/
+		if(cfgopt(copt, "ArchiveBlockEncrypted")->enabled)
+			options |= CL_SCAN_BLOCKENCRYPTED;
+		if(cfgopt(copt, "ScanPE")->enabled)
+			options |= CL_SCAN_PE;
+		if(cfgopt(copt, "DetectBrokenExecutables")->enabled)
+			options |= CL_SCAN_BLOCKBROKEN;
+		if(cfgopt(copt, "MailFollowURLs")->enabled)
+			options |= CL_SCAN_MAILURL;
+		if(cfgopt(copt, "ScanOLE2")->enabled)
+			options |= CL_SCAN_OLE2;
+		if(cfgopt(copt, "ScanHTML")->enabled)
+			options |= CL_SCAN_HTML;
+
+		if(((cpt = cfgopt(copt, "MaxScanSize")) != NULL) && cpt->enabled)
+			maxscansize = cpt->numarg;
+		else
+			maxscansize = 104857600;
+		if(((cpt = cfgopt(copt, "MaxFileSize")) != NULL) && cpt->enabled)
+			maxfilesize = cpt->numarg;
+		else
+			maxfilesize = 10485760;
+
+		if(getrlimit(RLIMIT_FSIZE, &rlim) == 0) {
+			if((rlim.rlim_max < maxfilesize) || (rlim.rlim_max < maxscansize))
+				logg("^System limit for file size is lower than maxfilesize or maxscansize\n");
+		} else {
+			logg("^Cannot obtain resource limits for file size\n");
+		}
+
+		if(((cpt = cfgopt(copt, "MaxRecursion")) != NULL) && cpt->enabled)
+			maxreclevel = cpt->numarg;
+		else
+			maxreclevel = 8;
+
+		if(((cpt = cfgopt(copt, "MaxFiles")) != NULL) && cpt->enabled)
+			maxfiles = cpt->numarg;
+		else
+			maxfiles = 1000;
+
+		if(cfgopt(copt, "ScanArchive")->enabled)
+			options |= CL_SCAN_ARCHIVE;
+	}
+
+	pthread_create(&tid, NULL, watchdog, NULL);
+
+	if(((cpt = cfgopt(copt, "PidFile")) != NULL) && cpt->enabled)
+		pidFile = cpt->strarg;
+
+	broadcast(_("Starting clamav-milter"));
+
+	if(rootdir) {
+		if(getuid() == 0) {
+			if(chdir(rootdir) < 0) {
+				perror(rootdir);
+				logg("!chdir %s failed\n", rootdir);
+				return EX_CONFIG;
+			}
+			if(chroot(rootdir) < 0) {
+				perror(rootdir);
+				logg("!chroot %s failed\n", rootdir);
+				return EX_CONFIG;
+			}
+			logg("Chrooted to %s\n", rootdir);
+		} else {
+			logg("!chroot option needs root\n");
+			return EX_CONFIG;
+		}
+	}
+
+	if(pidfile) {
+		/* save the PID */
+		char *p, *q;
+		FILE *fd;
+		const mode_t old_umask = umask(0006);
+
+		if(pidfile[0] != '/') {
+			logg(_("!pidfile: '%s' must be a full pathname"),
+				pidfile);
+
+			return EX_CONFIG;
+		}
+		p = cli_strdup(pidfile);
+		q = strrchr(p, '/');
+		*q = '\0';
+
+		if(rootdir == NULL)
+			if(chdir(p) < 0)	/* safety */
+				perror(p);
+
+		free(p);
+
+		if((fd = fopen(pidfile, "w")) == NULL) {
+			logg(_("!Can't save PID in file %s\n"), pidfile);
+			return EX_CONFIG;
+		}
+#ifdef	C_LINUX
+		/* Ensure that all threads are kill()ed */
+		fprintf(fd, "-%d\n", (int)getpgrp());
+#else
+		fprintf(fd, "%d\n", (int)getpid());
+#endif
+		fclose(fd);
+		umask(old_umask);
+	} else if(tmpdir) {
+		if(rootdir == NULL)
+			if(chdir(tmpdir) < 0) {	/* safety */
+				perror(tmpdir);
+				logg("!chdir %s failed\n", tmpdir);
+			}
+	} else
+		if(rootdir == NULL)
+#ifdef	P_tmpdir
+			if(chdir(P_tmpdir) < 0) {
+				perror(P_tmpdir);
+				logg("!chdir %s failed\n", P_tmpdir);
+			}
+#else
+			if(chdir("/tmp") < 0) {
+				perror("/tmp");
+				logg("!chdir /tmp failed\n", P_tmpdir);
+			}
+#endif
+
+	if(cfgopt(copt, "FixStaleSocket")->enabled) {
+		/*
+		 * Get the incoming socket details - the way sendmail talks to
+		 * us
+		 *
+		 * TODO: There's a security problem here that'll need fixing if
+		 * the User entry of clamd.conf is not used
+		 */
+		if(strncasecmp(port, "unix:", 5) == 0) {
+			if(unlink(&port[5]) < 0)
+				if(errno != ENOENT)
+					perror(&port[5]);
+		} else if(strncasecmp(port, "local:", 6) == 0) {
+			if(unlink(&port[6]) < 0)
+				if(errno != ENOENT)
+					perror(&port[6]);
+		} else if(port[0] == '/') {
+			if(unlink(port) < 0)
+				if(errno != ENOENT)
+					perror(port);
+		}
+	}
+
+	if(smfi_setconn(port) == MI_FAILURE) {
+		cli_errmsg("smfi_setconn failure\n");
+		return EX_SOFTWARE;
+	}
+
+	if(smfi_register(smfilter) == MI_FAILURE) {
+		fprintf(stderr, "smfi_register failure, ensure that you have linked against the correct version of sendmail\n");
+		return EX_UNAVAILABLE;
+	}
+
+#if	((SENDMAIL_VERSION_A > 8) || ((SENDMAIL_VERSION_A == 8) && (SENDMAIL_VERSION_B >= 13)))
+	if(smfi_opensocket(1) == MI_FAILURE) {
+		perror(port);
+		fprintf(stderr, "Can't open/create %s\n", port);
+		return EX_CONFIG;
+	}
+#endif
+
+	signal(SIGPIPE, SIG_IGN);	/* libmilter probably does this as well */
+	signal(SIGXFSZ, SIG_IGN); /* TODO: check if it's safe to call signal() here */
+
+#ifdef	SESSION
+	pthread_mutex_lock(&version_mutex);
+#endif
+	logg(_("Starting %s\n"), clamav_version);
+	logg(_("*Debugging is on\n"));
+
+#ifdef HAVE_RESOLV_H
+#if ! defined(HAVE_LRESOLV_R)
+	if(!(_res.options&RES_INIT))
+		if(res_init() < 0) {
+			fprintf(stderr, "%s: Can't initialise the resolver\n",
+				argv[0]);
+			return EX_UNAVAILABLE;
+		}
+#endif
+	if(blacklist_time) {
+		char name[MAXHOSTNAMELEN + 1];
+
+		if(gethostname(name, sizeof(name)) < 0) {
+			perror("gethostname");
+			return EX_UNAVAILABLE;
+		}
+
+		blacklist = mx(name, NULL);
+		if(blacklist)
+			/* We must never blacklist ourself */
+			tableInsert(blacklist, "127.0.0.1", 0);
+
+		if(wont_blacklist) {
+			char *w;
+
+			i = 0;
+			while((w = cli_strtok(wont_blacklist, i++, ",")) != NULL) {
+				(void)tableInsert(blacklist, w, 0);
+				free(w);
+			}
+		}
+		tableIterate(blacklist, dump_blacklist, NULL);
+	}
+#endif /* HAVE_RESOLV_H */
+
+#ifdef	SESSION
+	pthread_mutex_unlock(&version_mutex);
+#endif
+
+	(void)signal(SIGSEGV, sigsegv);
+	if(!logg_foreground)
+		(void)signal(SIGUSR1, sigusr1);
+	if(!external)
+		(void)signal(SIGUSR2, sigusr2);
+
+	return smfi_main();
+}
+
+#ifdef	SESSION
+/*
+ * Use the SESSION command of clamd.
+ * Returns -1 for terminal failure, 0 for OK, 1 for nonterminal failure
+ * The caller must take care of locking the sessions array
+ */
+static int
+createSession(unsigned int s)
+{
+	int ret = 0, fd;
+	const int serverNumber = s % numServers;
+	struct session *session = &sessions[s];
+	const struct protoent *proto;
+	struct sockaddr_in server;
+
+	cli_dbgmsg("createSession session %d, server %d\n", s, serverNumber);
+	assert(s < max_children);
+
+	memset((char *)&server, 0, sizeof(struct sockaddr_in));
+	server.sin_family = AF_INET;
+	server.sin_port = (in_port_t)htons(tcpSocket);
+
+	server.sin_addr.s_addr = serverIPs[serverNumber];
+
+	session->sock = -1;
+	proto = getprotobyname("tcp");
+	if(proto == NULL) {
+		fputs("Unknown prototol tcp, check /etc/protocols\n", stderr);
+		fd = ret = -1;
+	} else if((fd = socket(AF_INET, SOCK_STREAM, proto->p_proto)) < 0) {
+		perror("socket");
+		ret = -1;
+	} else if(connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
+		perror("connect");
+		ret = 1;
+	} else if(send(fd, "SESSION\n", 8, 0) < 8) {
+		perror("send");
+		ret = 1;
+	}
+
+	if(ret != 0) {
+#ifdef	MAXHOSTNAMELEN
+		char hostname[MAXHOSTNAMELEN + 1];
+
+		cli_strtokbuf(serverHostNames, serverNumber, ":", hostname);
+		if(strcmp(hostname, "127.0.0.1") == 0)
+			gethostname(hostname, sizeof(hostname));
+#else
+		char *hostname = cli_strtok(serverHostNames, serverNumber, ":");
+#endif
+
+		session->status = CMDSOCKET_DOWN;
+
+		if(fd >= 0)
+			close(fd);
+
+		cli_warnmsg(_("Check clamd server %s - it may be down\n"), hostname);
+#ifndef	MAXHOSTNAMELEN
+		free(hostname);
+#endif
+
+		broadcast(_("Check clamd server - it may be down"));
+	} else
+		session->sock = fd;
+
+	return ret;
+}
+
+#else
+
+/*
+ * Verify that the server is where we think it is
+ * Returns true or false
+ *
+ * serverNumber counts from 0, but is only used for TCPSocket
+ */
+static int
+pingServer(int serverNumber)
+{
+	char *ptr;
+	int sock;
+	long nbytes;
+	char buf[128];
+
+	if(localSocket) {
+		struct sockaddr_un server;
+
+		memset((char *)&server, 0, sizeof(struct sockaddr_un));
+		server.sun_family = AF_UNIX;
+		strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
+		server.sun_path[sizeof(server.sun_path)-1]='\0';
+
+		if((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+			perror(localSocket);
+			return 0;
+		}
+		checkClamd(1);
+		if(connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
+			perror(localSocket);
+			close(sock);
+			return 0;
+		}
+	} else {
+		struct sockaddr_in server;
+		char *hostname;
+
+		memset((char *)&server, 0, sizeof(struct sockaddr_in));
+		server.sin_family = AF_INET;
+		server.sin_port = (in_port_t)htons(tcpSocket);
+
+		assert(serverIPs != NULL);
+#ifdef	INADDR_NONE
+		assert(serverIPs[0] != INADDR_NONE);
+#else
+		assert(serverIPs[0] != (in_addr_t)-1);
+#endif
+
+		server.sin_addr.s_addr = serverIPs[serverNumber];
+
+		if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+			perror("socket");
+			return 0;
+		}
+		hostname = cli_strtok(serverHostNames, serverNumber, ":");
+		/*
+		 * FIXME: use non-blocking connect, once the code is
+		 * amalgomated
+		 */
+		if(nonblock_connect(sock, &server, hostname) < 0) {
+			int is_connected = 0;
+
+#if	(!defined(NTRIES)) || ((NTRIES <= 1))
+			if(errno == ECONNREFUSED) {
+				/*
+				 * During startup there is a race condition:
+				 * clamd can start and fork, then rc will start
+				 * clamav-milter before clamd has run accept(2),
+				 * so we fail to connect.
+				 * In case this is the situation here, we wait
+				 * for a couple of seconds and try again. The
+				 * sync() is because during startup the machine
+				 * won't be doing much for most of the time, so
+				 * we may as well do something constructive!
+				 */
+				sync();
+				sleep(2);
+				if(nonblock_connect(sock, &server, hostname) >= 0)
+					is_connected = 1;
+			}
+#endif
+			if(!is_connected) {
+				if(errno != EINPROGRESS)
+					perror(hostname ? hostname : "connect");
+				close(sock);
+				if(hostname)
+					free(hostname);
+				return 0;
+			}
+		}
+		if(hostname)
+			free(hostname);
+	}
+
+	/*
+	 * It would be better to use PING, check for PONG then issue the
+	 * VERSION command, since that would better validate that we're
+	 * talking to clamd, however clamd closes the session after
+	 * sending PONG :-(
+	 * So this code does not really validate that we're talking to clamd
+	 * Needs a fix to clamd
+	 * Also version command is verbose: says "clamd / ClamAV version"
+	 * instead of "clamAV version"
+	 */
+	cli_dbgmsg("pingServer%d: sending VERSION\n", serverNumber);
+	if(send(sock, "VERSION\n", 8, 0) < 8) {
+		perror("send");
+		return close(sock);
+	}
+
+	shutdown(sock, SHUT_WR);
+
+	nbytes = clamd_recv(sock, buf, sizeof(buf) - 1);
+
+	close(sock);
+
+	if(nbytes < 0) {
+		perror("recv");
+		return 0;
+	}
+	if(nbytes == 0)
+		return 0;
+
+	buf[nbytes] = '\0';
+
+	/* Remove the trailing new line from the reply */
+	if((ptr = strchr(buf, '\n')) != NULL)
+		*ptr = '\0';
+
+	/*
+	 * No real validation is done here
+	 *
+	 * TODO: When connecting to more than one server, give a warning
+	 *	if they're running different versions, or if the virus DBs
+	 *	are out of date (say more than a month old)
+	 */
+	snprintf(clamav_version, sizeof(clamav_version) - 1,
+		"%s\n\tclamav-milter version %s",
+		buf, get_version());
+
+	return 1;
+}
+#endif
+
+/*
+ * Find the best server to connect to. No intelligence to this.
+ * It is best to weight the order of the servers from most wanted to least
+ * wanted
+ *
+ * Return value is from 0 - index into sessions array
+ *
+ * If the load balancing fails return the first server in the list, not
+ * an error, to be on the safe side
+ */
+#ifdef	SESSION
+static int
+findServer(void)
+{
+	unsigned int i, j;
+	struct session *session;
+
+	/*
+	 * FIXME: Sessions code isn't flexible at handling servers
+	 *	appearing and disappearing, e.g. sessions[n_children].sock == -1
+	 */
+	i = 0;
+	pthread_mutex_lock(&n_children_mutex);
+	assert(n_children > 0);
+	assert(n_children <= max_children);
+	j = n_children - 1;
+	pthread_mutex_unlock(&n_children_mutex);
+
+	pthread_mutex_lock(&sstatus_mutex);
+	for(; i < max_children; i++) {
+		const int sess = (j + i) % max_children;
+
+		session = &sessions[sess];
+		cli_dbgmsg("findServer: try server %d\n", sess);
+		if(session->status == CMDSOCKET_FREE) {
+			session->status = CMDSOCKET_INUSE;
+			pthread_mutex_unlock(&sstatus_mutex);
+			return sess;
+		}
+	}
+	pthread_mutex_unlock(&sstatus_mutex);
+
+	/*
+	 * No session free - wait until one comes available. Only
+	 * retries once.
+	 */
+	if(pthread_cond_broadcast(&watchdog_cond) < 0)
+		perror("pthread_cond_broadcast");
+
+	i = 0;
+	session = sessions;
+	pthread_mutex_lock(&sstatus_mutex);
+	for(; i < max_children; i++, session++) {
+		cli_dbgmsg("findServer: try server %d\n", i);
+		if(session->status == CMDSOCKET_FREE) {
+			session->status = CMDSOCKET_INUSE;
+			pthread_mutex_unlock(&sstatus_mutex);
+			return i;
+		}
+	}
+	pthread_mutex_unlock(&sstatus_mutex);
+
+	cli_warnmsg(_("No free clamd sessions\n"));
+
+	return -1;	/* none available - must fail */
+}
+#else
+/*
+ * Return value is from 0 - index into serverIPs
+ */
+static int
+findServer(void)
+{
+	struct sockaddr_in *servers, *server;
+	int maxsock, i, j, active;
+	int retval;
+	pthread_t *tids;
+	struct try_server_struct *socks;
+	fd_set rfds;
+
+	assert(tcpSocket != 0);
+	assert(numServers > 0);
+
+	if(numServers == 1)
+		return 0;
+
+	if(active_servers(&active) <= 1)
+		return active;
+
+	servers = (struct sockaddr_in *)cli_calloc(numServers, sizeof(struct sockaddr_in));
+	if(servers == NULL)
+		return 0;
+	socks = (struct try_server_struct *)cli_malloc(numServers * sizeof(struct try_server_struct));
+
+	if(max_children > 0) {
+		assert(n_children > 0);
+		assert(n_children <= max_children);
+
+		/*
+		 * Don't worry about no lock - it's doesn't matter if it's
+		 * not really accurate
+		 */
+		j = n_children - 1;	/* look at the next free one */
+		if(j < 0)
+			j = 0;
+	} else
+		/*
+		 * cli_rndnum returns 0..max
+		 */
+		j = cli_rndnum(numServers - 1);
+
+	for(i = 0; i < numServers; i++)
+		socks[i].sock = -1;
+
+	tids = cli_malloc(numServers * sizeof(pthread_t));
+
+	for(i = 0, server = servers; i < numServers; i++, server++) {
+		int sock;
+		int server_index = (i + j) % numServers;
+
+		server->sin_family = AF_INET;
+		server->sin_port = (in_port_t)htons(tcpSocket);
+		server->sin_addr.s_addr = serverIPs[server_index];
+
+		logg("*findServer: try server %d\n", server_index);
+
+		sock = socks[i].sock = socket(AF_INET, SOCK_STREAM, 0);
+
+		if(sock < 0) {
+			perror("socket");
+			while(i--) {
+				pthread_join(tids[i], NULL);
+				if(socks[i].sock >= 0)
+					close(socks[i].sock);
+			}
+			free(socks);
+			free(servers);
+			free(tids);
+			return 0;	/* Use the first server on failure */
+		}
+
+		socks[i].server = server;
+		socks[i].server_index = server_index;
+
+		if(pthread_create(&tids[i], NULL, try_server, &socks[i]) != 0) {
+			perror("pthread_create");
+			j = i;
+			do {
+				if (j!=i) pthread_join(tids[i], NULL);
+				if(socks[i].sock >= 0)
+					close(socks[i].sock);
+			} while(--i >= 0);
+			free(socks);
+			free(servers);
+			free(tids);
+			return 0;	/* Use the first server on failure */
+		}
+	}
+
+	maxsock = -1;
+	FD_ZERO(&rfds);
+
+	for(i = 0; i < numServers; i++) {
+		struct try_server_struct *rc;
+
+		pthread_join(tids[i], (void **)&rc);
+		assert(rc->sock == socks[i].sock);
+		if(rc->rc == 0) {
+			close(rc->sock);
+			socks[i].sock = -1;
+		} else {
+			shutdown(rc->sock, SHUT_WR);
+			FD_SET(rc->sock, &rfds);
+			if(rc->sock > maxsock)
+				maxsock = rc->sock;
+		}
+	}
+
+	free(servers);
+	free(tids);
+
+	if(maxsock == -1) {
+		logg(_("^Couldn't establish a connexion to any clamd server\n"));
+		retval = 0;
+	} else {
+		struct timeval tv;
+
+		tv.tv_sec = readTimeout ? readTimeout : DEFAULT_TIMEOUT;
+		tv.tv_usec = 0;
+
+		retval = select(maxsock + 1, &rfds, NULL, NULL, &tv);
+	}
+
+	if(retval < 0)
+		perror("select");
+
+	for(i = 0; i < numServers; i++)
+		if(socks[i].sock >= 0)
+			close(socks[i].sock);
+
+	if(retval == 0) {
+		free(socks);
+		clamdIsDown();
+		return 0;
+	} else if(retval < 0) {
+		free(socks);
+		logg(_("^findServer: select failed (maxsock = %d)\n"), maxsock);
+		return 0;
+	}
+
+	for(i = 0; i < numServers; i++)
+		if((socks[i].sock >= 0) && (FD_ISSET(socks[i].sock, &rfds))) {
+			const int s = (i + j) % numServers;
+
+			free(socks);
+			logg("*findServer: use server %d\n", s);
+			return s;
+		}
+
+	free(socks);
+	logg(_("^findServer: No response from any server\n"));
+	return 0;
+}
+
+/*
+ * How many servers are up at the moment? If a server is marked as down,
+ *	don't keep on flooding it with requests to see if it's now back up
+ * If only one server is active, let the caller know, which server is the
+ *	active one
+ */
+static int
+active_servers(int *active)
+{
+	int server, count;
+	time_t now = (time_t)0;
+
+	for(count = server = 0; server < numServers; server++)
+		if(last_failed_pings[server] == (time_t)0) {
+			*active = server;
+			count++;
+		} else {
+			if(now == (time_t)0)
+				time(&now);
+			if(now - last_failed_pings[server] >= RETRY_SECS)
+				/* Try this server again next time */
+				last_failed_pings[server] = (time_t)0;
+		}
+
+	if(count != 1)
+		*active = 0;
+	return count;
+}
+
+/*
+ * Connecting to remote servers can take some time, so let's connect to
+ *	them in parallel. This routine is started as a thread
+ */
+static void *
+try_server(void *var)
+{
+	struct try_server_struct *s = (struct try_server_struct *)var;
+	int sock = s->sock;
+	struct sockaddr *server = (struct sockaddr *)s->server;
+	int server_index = s->server_index;
+
+	if(last_failed_pings[server_index]) {
+		s->rc = 0;
+		return var;
+	}
+
+	logg("*try_server: sock %d\n", sock);
+
+	if((connect(sock, server, sizeof(struct sockaddr)) < 0) ||
+	   (send(sock, "PING\n", 5, 0) < 5)) {
+		time(&last_failed_pings[server_index]);
+		s->rc = 0;
+	} else
+		s->rc = 1;
+
+	if(s->rc == 0) {
+#ifdef	MAXHOSTNAMELEN
+		char hostname[MAXHOSTNAMELEN + 1];
+
+		cli_strtokbuf(serverHostNames, server_index, ":", hostname);
+		if(strcmp(hostname, "127.0.0.1") == 0)
+			gethostname(hostname, sizeof(hostname));
+#else
+		char *hostname = cli_strtok(serverHostNames, server_index, ":");
+#endif
+		perror(hostname);
+		logg(_("^Check clamd server %s - it may be down\n"), hostname);
+#ifndef	MAXHOSTNAMELEN
+		free(hostname);
+#endif
+		broadcast(_("Check clamd server - it may be down\n"));
+	}
+
+	return var;
+}
+#endif
+
+/*
+ * Sendmail wants to establish a connexion to us
+ * TODO: is it possible (desirable?) to determine if the remote machine has been
+ *	compromised?
+ */
+static sfsistat
+clamfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr)
+{
+#if	defined(HAVE_INET_NTOP) || defined(WITH_TCPWRAP)
+	char ip[INET6_ADDRSTRLEN];
+#endif
+	int t;
+	const char *remoteIP;
+	struct privdata *privdata;
+
+	if(quitting)
+		return cl_error;
+
+	if(ctx == NULL) {
+		logg(_("!clamfi_connect: ctx is null"));
+		return cl_error;
+	}
+	if(hostname == NULL) {
+		logg(_("!clamfi_connect: hostname is null"));
+		return cl_error;
+	}
+	if(smfi_getpriv(ctx) != NULL) {
+		/* More than one connexion command, "can't happen" */
+		cli_warnmsg("clamfi_connect: called more than once\n");
+		clamfi_cleanup(ctx);
+		return cl_error;
+	}
+#ifdef AF_INET6
+	if((hostaddr == NULL) ||
+	   ((hostaddr->sa_family == AF_INET) && (&(((struct sockaddr_in *)(hostaddr))->sin_addr) == NULL)) ||
+	   ((hostaddr->sa_family == AF_INET6) && (&(((struct sockaddr_in6 *)(hostaddr))->sin6_addr) == NULL)))
+#else
+	if((hostaddr == NULL) || (&(((struct sockaddr_in *)(hostaddr))->sin_addr) == NULL))
+#endif
+		/*
+		 * According to the sendmail API hostaddr is NULL if
+		 * "the type is not supported in the current version". What
+		 * the documentation doesn't say is the type of what.
+		 *
+		 * Possibly the input is not a TCP/IP socket e.g. stdin?
+		 */
+		remoteIP = "127.0.0.1";
+	else {
+#ifdef HAVE_INET_NTOP
+		switch(hostaddr->sa_family) {
+			case AF_INET:
+				remoteIP = (const char *)inet_ntop(AF_INET, &((struct sockaddr_in *)(hostaddr))->sin_addr, ip, sizeof(ip));
+				break;
+#ifdef AF_INET6
+			case AF_INET6:
+				remoteIP = (const char *)inet_ntop(AF_INET6, &((struct sockaddr_in6 *)(hostaddr))->sin6_addr, ip, sizeof(ip));
+				break;
+#endif
+			default:
+				logg(_("clamfi_connect: Unexpected sa_family %d\n"),
+					hostaddr->sa_family);
+				return cl_error;
+		}
+
+#else
+		remoteIP = inet_ntoa(((struct sockaddr_in *)(hostaddr))->sin_addr);
+#endif
+
+		if(remoteIP == NULL) {
+			logg(_("clamfi_connect: remoteIP is null"));
+			return cl_error;
+		}
+	}
+
+#ifdef	CL_DEBUG
+	if(debug_level >= 4) {
+		if(hostname[0] == '[')
+			logg(_("clamfi_connect: connexion from %s"), remoteIP);
+		else
+			logg(_("clamfi_connect: connexion from %s [%s]"), hostname, remoteIP);
+	}
+#endif
+
+#ifdef	WITH_TCPWRAP
+	/*
+	 * Support /etc/hosts.allow and /etc/hosts.deny
+	 */
+	if(strncasecmp(port, "inet:", 5) == 0) {
+		const char *hostmail;
+		struct hostent hostent;
+		char buf[BUFSIZ];
+		static pthread_mutex_t wrap_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+		/*
+		 * Using TCP/IP for the sendmail->clamav-milter connexion
+		 */
+		if(((hostmail = smfi_getsymval(ctx, "{if_name}")) == NULL) &&
+		   ((hostmail = smfi_getsymval(ctx, "j")) == NULL)) {
+			logg(_("Can't get sendmail hostname"));
+			return cl_error;
+		}
+		/*
+		 * Use hostmail for error statements, not hostname, suggestion
+		 * by Yar Tikhiy <yar at comp.chem.msu.su>
+		 */
+		if(r_gethostbyname(hostmail, &hostent, buf, sizeof(buf)) != 0) {
+			logg(_("^Access Denied: Host Unknown (%s)"), hostmail);
+			if(hostmail[0] == '[')
+				/*
+				 * A case could be made that it's not clamAV's
+				 * job to check a system's DNS configuration
+				 * and let this message through. However I am
+				 * just too worried about any knock on effects
+				 * to do that...
+				 */
+				cli_warnmsg(_("Can't find entry for IP address %s in DNS - check your DNS setting\n"),
+					hostmail);
+			return cl_error;
+		}
+
+#ifdef HAVE_INET_NTOP
+		if(hostent.h_addr &&
+		   (inet_ntop(AF_INET, (struct in_addr *)hostent.h_addr, ip, sizeof(ip)) == NULL)) {
+			perror(hostent.h_name);
+			/*strcpy(ip, (char *)inet_ntoa(*(struct in_addr *)hostent.h_addr));*/
+			logg(_("^Access Denied: Can't get IP address for (%s)"), hostent.h_name);
+			return cl_error;
+		}
+#else
+		strncpy(ip, (char *)inet_ntoa(*(struct in_addr *)hostent.h_addr), sizeof(ip));
+		ip[sizeof(ip)-1]='\0';
+#endif
+
+		/*
+		 * Ask is this is a allowed name or IP number
+		 *
+		 * hosts_ctl uses strtok so it is not thread safe, see
+		 * hosts_access(3)
+		 */
+		pthread_mutex_lock(&wrap_mutex);
+		if(!hosts_ctl(progname, hostent.h_name, ip, STRING_UNKNOWN)) {
+			pthread_mutex_unlock(&wrap_mutex);
+			logg(_("^Access Denied for %s[%s]"), hostent.h_name, ip);
+			return SMFIS_TEMPFAIL;
+		}
+		pthread_mutex_unlock(&wrap_mutex);
+	}
+#endif	/*WITH_TCPWRAP*/
+
+	if(fflag)
+		/*
+		 * Patch from "Richard G. Roberto" <rgr at dedlegend.com>
+		 * Always scan whereever the message is from
+		 */
+		return SMFIS_CONTINUE;
+
+	if(!oflag)
+		if(strcmp(remoteIP, "127.0.0.1") == 0) {
+			logg(_("*clamfi_connect: not scanning outgoing messages"));
+			return SMFIS_ACCEPT;
+		}
+
+	if((!lflag) && isLocal(remoteIP)) {
+#ifdef	CL_DEBUG
+		logg(_("*clamfi_connect: not scanning local messages\n"));
+#endif
+		return SMFIS_ACCEPT;
+	}
+
+#if	defined(HAVE_INET_NTOP) || defined(WITH_TCPWRAP)
+	if(detect_forged_local_address && !isLocal(ip)) {
+#else
+	if(detect_forged_local_address && !isLocal(remoteIP)) {
+#endif
+		char me[MAXHOSTNAMELEN + 1];
+
+		if(gethostname(me, sizeof(me) - 1) < 0) {
+			logg(_("^clamfi_connect: gethostname failed"));
+			return SMFIS_CONTINUE;
+		}
+		logg("*me '%s' hostname '%s'\n", me, hostname);
+		if(strcasecmp(hostname, me) == 0) {
+			logg(_("Rejected connexion falsely claiming to be from here\n"));
+			smfi_setreply(ctx, "550", "5.7.1", _("You have claimed to be me, but you are not"));
+			broadcast(_("Forged local address detected"));
+			return SMFIS_REJECT;
+		}
+	}
+	if(isBlacklisted(remoteIP)) {
+		char mess[128];
+
+		/*
+		 * TODO: Option to greylist rather than blacklist, by sending
+		 *	a try again code
+		 * TODO: state *which* virus
+		 * TODO: add optional list of IP addresses that won't be
+		 *	blacklisted
+		 */
+		logg("Rejected connexion from blacklisted IP %s\n", remoteIP);
+
+		snprintf(mess, sizeof(mess), _("%s is blacklisted because your machine is infected with a virus"), remoteIP);
+		smfi_setreply(ctx, "550", "5.7.1", mess);
+		broadcast(_("Blacklisted IP detected"));
+
+		/*
+		 * Keep them blacklisted
+		 */
+		pthread_mutex_lock(&blacklist_mutex);
+		(void)tableUpdate(blacklist, remoteIP, (int)time((time_t *)0));
+		pthread_mutex_unlock(&blacklist_mutex);
+
+		return SMFIS_REJECT;
+	}
+
+	if(blacklist_time == 0)
+		return SMFIS_CONTINUE;	/* allocate privdata per message */
+
+	pthread_mutex_lock(&blacklist_mutex);
+	t = tableFind(blacklist, remoteIP);
+	pthread_mutex_unlock(&blacklist_mutex);
+
+	if(t == 0)
+		return SMFIS_CONTINUE;	/* this IP will never be blacklisted */
+
+	privdata = (struct privdata *)cli_calloc(1, sizeof(struct privdata));
+	if(privdata == NULL)
+		return cl_error;
+
+#ifdef	SESSION
+	privdata->dataSocket = -1;
+#else
+	privdata->dataSocket = privdata->cmdSocket = -1;
+#endif
+
+	if(smfi_setpriv(ctx, privdata) == MI_SUCCESS) {
+		strcpy(privdata->ip, remoteIP);
+		return SMFIS_CONTINUE;
+	}
+
+	free(privdata);
+
+	return cl_error;
+}
+
+/*
+ * Since sendmail requires that MAIL FROM is called before RCPT TO, it is
+ *	safe to assume that this routine is called first, so the n_children
+ *	handler is put here
+ */
+static sfsistat
+clamfi_envfrom(SMFICTX *ctx, char **argv)
+{
+	struct privdata *privdata;
+	const char *mailaddr = argv[0];
+
+	logg("*clamfi_envfrom: %s\n", argv[0]);
+
+	if(isWhitelisted(argv[0], 0)) {
+		logg(_("*clamfi_envfrom: ignoring whitelisted message"));
+		return SMFIS_ACCEPT;
+	}
+
+	if(strcmp(argv[0], "<>") == 0) {
+		mailaddr = smfi_getsymval(ctx, "{mail_addr}");
+		if(mailaddr == NULL)
+			mailaddr = smfi_getsymval(ctx, "_");
+
+		if(mailaddr && *mailaddr)
+			cli_dbgmsg("Message from \"%s\" has no from field\n", mailaddr);
+		else {
+#if	0
+			if(use_syslog)
+				syslog(LOG_NOTICE, _("Rejected email with empty from field"));
+			smfi_setreply(ctx, "554", "5.7.1", _("You have not said who the email is from"));
+			broadcast(_("Reject email with empty from field"));
+			clamfi_cleanup(ctx);
+			return SMFIS_REJECT;
+#endif
+			mailaddr = "<>";
+		}
+	}
+	privdata = smfi_getpriv(ctx);
+
+	if(privdata == NULL) {
+		privdata = (struct privdata *)cli_calloc(1, sizeof(struct privdata));
+		if(privdata == NULL)
+			return cl_error;
+		if(smfi_setpriv(ctx, privdata) != MI_SUCCESS) {
+			free(privdata);
+			return cl_error;
+		}
+		if(!increment_connexions()) {
+			smfi_setreply(ctx, "451", "4.3.2", _("AV system temporarily overloaded - please try later"));
+			free(privdata);
+			smfi_setpriv(ctx, NULL);
+			return SMFIS_TEMPFAIL;
+		}
+	} else {
+		/* More than one message on this connexion */
+		char ip[INET6_ADDRSTRLEN];
+
+		strcpy(ip, privdata->ip);
+		if(isBlacklisted(ip)) {
+			char mess[80 + INET6_ADDRSTRLEN];
+
+			logg("Rejected email from blacklisted IP %s\n", ip);
+
+			/*
+			 * TODO: Option to greylist rather than blacklist, by
+			 *	sending	a try again code
+			 * TODO: state *which* virus
+			 */
+			sprintf(mess, "Your IP (%s) is blacklisted because your machine is infected with a virus", ip);
+			smfi_setreply(ctx, "550", "5.7.1", mess);
+			broadcast(_("Blacklisted IP detected"));
+
+			/*
+			 * Keep them blacklisted
+			 */
+			pthread_mutex_lock(&blacklist_mutex);
+			(void)tableUpdate(blacklist, ip, (int)time((time_t *)0));
+			pthread_mutex_unlock(&blacklist_mutex);
+
+			return SMFIS_REJECT;
+		}
+		clamfi_free(privdata, 1);
+		strcpy(privdata->ip, ip);
+	}
+
+#ifdef	SESSION
+	privdata->dataSocket = -1;
+#else
+	privdata->dataSocket = privdata->cmdSocket = -1;
+#endif
+
+	/*
+	 * Rejection is via 550 until DATA is received. We know that
+	 * DATA has been sent when either we get a header or the end of
+	 * header statement
+	 */
+	privdata->rejectCode = "550";
+
+	privdata->from = cli_strdup(mailaddr);
+
+	if(hflag) {
+		privdata->headers = header_list_new();
+
+		if(privdata->headers == NULL) {
+			clamfi_free(privdata, 1);
+			return cl_error;
+		}
+	}
+
+	return SMFIS_CONTINUE;
+}
+
+#ifdef	CL_DEBUG
+static sfsistat
+clamfi_helo(SMFICTX *ctx, char *helostring)
+{
+	logg("HELO '%s'\n", helostring);
+
+	return SMFIS_CONTINUE;
+}
+#endif
+
+static sfsistat
+clamfi_envrcpt(SMFICTX *ctx, char **argv)
+{
+	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
+	const char *to, *ptr;
+
+	logg("*clamfi_envrcpt: %s\n", argv[0]);
+
+	if(privdata == NULL)	/* sanity check */
+		return cl_error;
+
+	if(privdata->to == NULL) {
+		privdata->to = cli_malloc(sizeof(char *) * 2);
+
+		assert(privdata->numTo == 0);
+	} else
+		privdata->to = cli_realloc(privdata->to, sizeof(char *) * (privdata->numTo + 2));
+
+	if(privdata->to == NULL)
+		return cl_error;
+
+	to = smfi_getsymval(ctx, "{rcpt_addr}");
+	if(to == NULL)
+		to = argv[0];
+
+	for(ptr = to; !dont_sanitise && *ptr; ptr++)
+		if(strchr("|;", *ptr) != NULL) {
+			smfi_setreply(ctx, "554", "5.7.1", _("Suspicious recipient address blocked"));
+			logg("^Suspicious recipient address blocked: '%s'\n", to);
+			privdata->to[privdata->numTo] = NULL;
+			if(blacklist_time && privdata->ip[0]) {
+				logg(_("Will blacklist %s for %d seconds because of cracking attempt\n"),
+					privdata->ip, blacklist_time);
+				pthread_mutex_lock(&blacklist_mutex);
+				(void)tableUpdate(blacklist, privdata->ip,
+					(int)time((time_t *)0));
+				pthread_mutex_unlock(&blacklist_mutex);
+			}
+			/*
+			 * REJECT rejects this recipient, not the entire email
+			 */
+			return SMFIS_REJECT;
+		}
+
+	privdata->to[privdata->numTo] = cli_strdup(to);
+	privdata->to[++privdata->numTo] = NULL;
+
+	return SMFIS_CONTINUE;
+}
+
+static sfsistat
+clamfi_header(SMFICTX *ctx, char *headerf, char *headerv)
+{
+	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
+
+#ifdef	CL_DEBUG
+	if(debug_level >= 9)
+		logg("*clamfi_header: %s: %s\n", headerf, headerv);
+	else
+		logg("*clamfi_header: %s\n", headerf);
+#else
+	logg("*clamfi_header: %s\n", headerf);
+#endif
+
+	/*
+	 * The DATA instruction from SMTP (RFC2821) must have been sent
+	 */
+	privdata->rejectCode = "554";
+
+	if(hflag)
+		header_list_add(privdata->headers, headerf, headerv);
+	else if((strcasecmp(headerf, "Received") == 0) &&
+		(strncasecmp(headerv, "from ", 5) == 0) &&
+		(strstr(headerv, "localhost") != 0)) {
+		if(privdata->received)
+			free(privdata->received);
+		privdata->received = cli_strdup(headerv);
+	}
+
+	if((strcasecmp(headerf, "Message-ID") == 0) &&
+	   (strncasecmp(headerv, "<MDAEMON", 8) == 0))
+		privdata->discard = 1;
+	else if((strcasecmp(headerf, "Subject") == 0) && headerv) {
+		if(privdata->subject)
+			free(privdata->subject);
+		if(headerv)
+			privdata->subject = cli_strdup(headerv);
+	} else if(strcasecmp(headerf, "X-Virus-Status") == 0)
+		privdata->statusCount++;
+	else if((strcasecmp(headerf, "Sender") == 0) && headerv) {
+		if(privdata->sender)
+			free(privdata->sender);
+		privdata->sender = cli_strdup(headerv);
+	}
+#ifdef	HAVE_RESOLV_H
+	else if((strcasecmp(headerf, "From") == 0) && headerv) {
+		/*
+		 * SPF check against the from header, since the SMTP header
+		 * may be valid. This is not what the SPF spec says, but I
+		 * have seen SPF matches on what are clearly phishes, so by
+		 * checking against the from: header we're less likely to
+		 * FP a real phish
+		 */
+		if(privdata->from)
+			free(privdata->from);
+		privdata->from = cli_strdup(headerv);
+	}
+#endif
+
+	if(!useful_header(headerf)) {
+		logg("*Discarded the header\n");
+		return SMFIS_CONTINUE;
+	}
+
+	if(privdata->dataSocket == -1)
+		/*
+		 * First header - make connexion with clamd
+		 */
+		if(!connect2clamd(privdata)) {
+			clamfi_cleanup(ctx);
+			return cl_error;
+		}
+
+	if(clamfi_send(privdata, 0, "%s: %s\n", headerf, headerv) <= 0) {
+		clamfi_cleanup(ctx);
+		return cl_error;
+	}
+
+	return SMFIS_CONTINUE;
+}
+
+/*
+ * At this point DATA will have been received, so we really ought to
+ * send 554 back not 550
+ */
+static sfsistat
+clamfi_eoh(SMFICTX *ctx)
+{
+	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
+	char **to;
+
+	logg(_("*clamfi_eoh\n"));
+
+	/*
+	 * The DATA instruction from SMTP (RFC2821) must have been sent
+	 */
+	privdata->rejectCode = "554";
+
+	if(privdata->dataSocket == -1)
+		/*
+		 * No headers - make connexion with clamd
+		 */
+		if(!connect2clamd(privdata)) {
+			clamfi_cleanup(ctx);
+			return cl_error;
+		}
+
+#if	0
+	/* Mailing lists often say our own posts are from us */
+	if(detect_forged_local_address && privdata->from &&
+	   (!privdata->sender) && !isWhitelisted(privdata->from, 1)) {
+		char me[MAXHOSTNAMELEN + 1];
+		const char *ptr;
+
+		if(gethostname(me, sizeof(me) - 1) < 0) {
+			if(use_syslog)
+				syslog(LOG_WARNING, _("clamfi_eoh: gethostname failed"));
+			return SMFIS_CONTINUE;
+		}
+		ptr = strstr(privdata->from, me);
+		if(ptr && (ptr != privdata->from) && (*--ptr == '@')) {
+			if(use_syslog)
+				syslog(LOG_NOTICE, _("Rejected email falsely claiming to be from %s"), privdata->from);
+			smfi_setreply(ctx, "554", "5.7.1", _("You have claimed to be from me, but you are not"));
+			broadcast(_("Forged local address detected"));
+			clamfi_cleanup(ctx);
+			return SMFIS_REJECT;
+		}
+	}
+#endif
+
+	if(clamfi_send(privdata, 1, "\n") != 1) {
+		clamfi_cleanup(ctx);
+		return cl_error;
+	}
+
+	if(black_hole_mode) {
+		sfsistat rc = black_hole(privdata);
+
+		if(rc != SMFIS_CONTINUE) {
+			clamfi_cleanup(ctx);
+			return rc;
+		}
+	}
+
+	/*
+	 * See if the e-mail is only going to members of the list
+	 * of users we don't scan for. If it is, don't scan, otherwise
+	 * scan
+	 *
+	 * scan = false
+	 * FORALL recipients
+	 *	IF receipient NOT MEMBER OF white address list
+	 *	THEN
+	 *		scan = true
+	 *	FI
+	 * ENDFOR
+	 */
+	for(to = privdata->to; *to; to++)
+		if(!isWhitelisted(*to, 1))
+			/*
+			 * This recipient is not on the whitelist,
+			 * no need to check any further
+			 */
+			return SMFIS_CONTINUE;
+
+	/*
+	 * Didn't find a recipient who is not on the white list, so all
+	 * must be on the white list, so just accept the e-mail
+	 */
+	logg(_("*clamfi_enveoh: ignoring whitelisted message"));
+	clamfi_cleanup(ctx);
+
+	return SMFIS_ACCEPT;
+}
+
+static sfsistat
+clamfi_body(SMFICTX *ctx, u_char *bodyp, size_t len)
+{
+	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
+	int nbytes;
+
+	logg(_("*clamfi_envbody: %lu bytes"), (unsigned long)len);
+
+	if(len == 0)	/* unlikely */
+		return SMFIS_CONTINUE;
+
+	if(privdata == NULL)	/* sanity check */
+		return cl_error;
+
+	/*
+	 * TODO:
+	 *	If not in external mode, call cli_scanbuff here, at least for
+	 * the first block
+	 */
+	/*
+	 * Lines starting with From are changed to >From, to
+	 *	avoid FP matches in the scanning code, which will speed it up
+	 */
+	if((len >= 6) && cli_memstr((char *)bodyp, len, "\nFrom ", 6)) {
+		const char *ptr = (const char *)bodyp;
+		int left = len;
+
+		nbytes = 0;
+
+		/*
+		 * FIXME: sending one byte at a time down a socket is
+		 *	inefficient
+		 */
+		do {
+			if(*ptr == '\n') {
+				/*
+				 * FIXME: doesn't work if the \nFrom straddles
+				 * multiple calls to clamfi_body
+				 */
+				if(strncmp(ptr, "\nFrom ", 6) == 0) {
+					nbytes += clamfi_send(privdata, 7, "\n>From ");
+					ptr += 6;
+					left -= 6;
+				} else {
+					nbytes += clamfi_send(privdata, 1, "\n");
+					ptr++;
+					left--;
+				}
+			} else {
+				nbytes += clamfi_send(privdata, 1, ptr++);
+				left--;
+			}
+			if(left < 6 && left > 0) {
+				nbytes += clamfi_send(privdata, left, ptr);
+				break;
+			}
+		} while(left > 0);
+	} else
+		nbytes = clamfi_send(privdata, len, (char *)bodyp);
+
+	if(streamMaxLength > 0L) {
+		if(privdata->numBytes > streamMaxLength) {
+			const char *sendmailId = smfi_getsymval(ctx, "i");
+
+			if(sendmailId == NULL)
+				sendmailId = "Unknown";
+			logg(_("%s: Message more than StreamMaxLength (%ld) bytes - not scanned\n"),
+				sendmailId, streamMaxLength);
+			if(!nflag)
+				smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned - StreamMaxLength exceeded"));
+
+			return SMFIS_ACCEPT;	/* clamfi_close will be called */
+		}
+	}
+	if(nbytes < (int)len) {
+		clamfi_cleanup(ctx);	/* not needed, but just to be safe */
+		return cl_error;
+	}
+	if(Sflag) {
+		if(privdata->body) {
+			assert(privdata->bodyLen > 0);
+			privdata->body = cli_realloc(privdata->body, privdata->bodyLen + len);
+			memcpy(&privdata->body[privdata->bodyLen], bodyp, len);
+			privdata->bodyLen += len;
+		} else {
+			assert(privdata->bodyLen == 0);
+			privdata->body = cli_malloc(len);
+			memcpy(privdata->body, bodyp, len);
+			privdata->bodyLen = len;
+		}
+	}
+	return SMFIS_CONTINUE;
+}
+
+static sfsistat
+clamfi_eom(SMFICTX *ctx)
+{
+	int rc = SMFIS_CONTINUE;
+	char *ptr;
+	const char *sendmailId;
+	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
+	char mess[128];
+#ifdef	SESSION
+	struct session *session;
+#endif
+
+	logg("*clamfi_eom\n");
+
+#ifdef	CL_DEBUG
+	assert(privdata != NULL);
+#ifndef	SESSION
+	assert((privdata->cmdSocket >= 0) || (privdata->filename != NULL));
+	assert(!((privdata->cmdSocket >= 0) && (privdata->filename != NULL)));
+#endif
+#endif
+
+	if(external) {
+		shutdown(privdata->dataSocket, SHUT_WR);	/* bug 487 */
+		close(privdata->dataSocket);
+		privdata->dataSocket = -1;
+	}
+
+	if(!nflag) {
+		/*
+		 * remove any existing claims that it's virus free so that
+		 * downstream checkers aren't fooled by a carefully crafted
+		 * virus.
+		 */
+		int i;
+
+		for(i = privdata->statusCount; i > 0; --i)
+			if(smfi_chgheader(ctx, "X-Virus-Status", i, NULL) == MI_FAILURE)
+				logg(_("^Failed to delete X-Virus-Status header %d\n"), i);
+	}
+
+	if(!external) {
+		const char *virname;
+		int ret;
+		struct  cl_engine *cur_engine;
+
+		pthread_mutex_lock(&engine_mutex);
+		ret = cl_engine_addref(engine);
+		cur_engine = engine; /* avoid races */
+		pthread_mutex_unlock(&engine_mutex);
+		if(ret != CL_SUCCESS) {
+			logg("!cl_engine_addref failed\n");
+			clamfi_cleanup(ctx);
+			return cl_error;
+		}
+		switch(cl_scanfile(privdata->filename, &virname, NULL, cur_engine, options)) {
+			case CL_CLEAN:
+				if(logok)
+					logg("#%s: OK\n", privdata->filename);
+				strcpy(mess, "OK");
+				break;
+			case CL_VIRUS:
+				snprintf(mess, sizeof(mess), "%s: %s FOUND", privdata->filename, virname);
+				logg("#%s\n", mess);
+				break;
+			default:
+				snprintf(mess, sizeof(mess), "%s: ERROR", privdata->filename);
+				logg("!%s\n", mess);
+				break;
+		}
+		cl_engine_free(cur_engine); /* drop reference or free */
+
+#ifdef	SESSION
+		session = NULL;
+#endif
+	} else if(privdata->filename) {
+		char cmdbuf[1024];
+		/*
+		 * Create socket to talk to clamd.
+		 */
+#ifndef	SESSION
+		struct sockaddr_un server;
+#endif
+		long nbytes;
+
+		snprintf(cmdbuf, sizeof(cmdbuf) - 1, "SCAN %s", privdata->filename);
+		cli_dbgmsg("clamfi_eom: SCAN %s\n", privdata->filename);
+
+		nbytes = (int)strlen(cmdbuf);
+
+#ifdef	SESSION
+		session = sessions;
+		if(send(session->sock, cmdbuf, nbytes, 0) < nbytes) {
+			perror("send");
+			clamfi_cleanup(ctx);
+			logg(_("failed to send SCAN %s command to clamd\n"), privdata->filename);
+			return cl_error;
+		}
+#else
+		if((privdata->cmdSocket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+			perror("socket");
+			clamfi_cleanup(ctx);
+			return cl_error;
+		}
+		memset((char *)&server, 0, sizeof(struct sockaddr_un));
+		server.sun_family = AF_UNIX;
+		strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
+		server.sun_path[sizeof(server.sun_path)-1]='\0';
+
+		if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
+			perror(localSocket);
+			clamfi_cleanup(ctx);
+			return cl_error;
+		}
+		if(send(privdata->cmdSocket, cmdbuf, nbytes, 0) < nbytes) {
+			perror("send");
+			clamfi_cleanup(ctx);
+			logg(_("failed to send SCAN command to clamd\n"));
+			return cl_error;
+		}
+
+		shutdown(privdata->cmdSocket, SHUT_WR);
+#endif
+	}
+#ifdef	SESSION
+	else
+		session = &sessions[privdata->serverNumber];
+#endif
+
+	sendmailId = smfi_getsymval(ctx, "i");
+	if(sendmailId == NULL)
+		sendmailId = "Unknown";
+
+	if(external) {
+		int nbytes;
+#ifdef	SESSION
+#ifdef	CL_DEBUG
+		if(debug_level >= 4)
+			cli_dbgmsg(_("Waiting to read status from fd %d\n"),
+				session->sock);
+#endif
+		nbytes = clamd_recv(session->sock, mess, sizeof(mess) - 1);
+#else
+		nbytes = clamd_recv(privdata->cmdSocket, mess, sizeof(mess) - 1);
+#endif
+		if(nbytes > 0) {
+			mess[nbytes] = '\0';
+			if((ptr = strchr(mess, '\n')) != NULL)
+				*ptr = '\0';
+
+			logg(_("*clamfi_eom: read %s\n"), mess);
+		} else {
+#ifdef	MAXHOSTNAMELEN
+			char hostname[MAXHOSTNAMELEN + 1];
+
+			cli_strtokbuf(serverHostNames, privdata->serverNumber, ":", hostname);
+			if(strcmp(hostname, "127.0.0.1") == 0)
+				gethostname(hostname, sizeof(hostname));
+#else
+			char *hostname = cli_strtok(serverHostNames, privdata->serverNumber, ":");
+#endif
+			if(privdata->subject)
+				logg(_("^%s: clamfi_eom: read nothing from clamd on %s, from %s (%s)\n"),
+					sendmailId, hostname, privdata->from,
+					privdata->subject);
+			else
+				logg(_("^%s: clamfi_eom: read nothing from clamd on %s, from %s\n"),
+					sendmailId, hostname, privdata->from);
+
+			if((!nflag) && (cl_error == SMFIS_ACCEPT))
+				smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned - Read timeout exceeded"));
+#ifndef	MAXHOSTNAMELEN
+			free(hostname);
+#endif
+
+#ifdef	CL_DEBUG
+			/*
+			 * Save the file which caused the timeout, for
+			 * debugging
+			 */
+			if(quarantine_dir) {
+				logg(_("Quarantining failed email\n"));
+				qfile(privdata, sendmailId, "scanning-timeout");
+			}
+#endif
+
+			/*
+			 * TODO: if more than one host has been specified, try
+			 * another one - setting cl_error to SMFIS_TEMPFAIL
+			 * helps by forcing a retry
+			 */
+			clamfi_cleanup(ctx);
+
+#ifdef	SESSION
+			pthread_mutex_lock(&sstatus_mutex);
+			session->status = CMDSOCKET_DOWN;
+			pthread_mutex_unlock(&sstatus_mutex);
+#endif
+			return cl_error;
+		}
+
+#ifdef	SESSION
+		pthread_mutex_lock(&sstatus_mutex);
+		if(session->status == CMDSOCKET_INUSE)
+			session->status = CMDSOCKET_FREE;
+		pthread_mutex_unlock(&sstatus_mutex);
+#else
+		close(privdata->cmdSocket);
+		privdata->cmdSocket = -1;
+#endif
+	}
+
+	if(!nflag) {
+		char buf[1024];
+
+		/*
+		 * Include the hostname where the scan took place
+		 */
+		if(localSocket || !external) {
+#ifdef	MAXHOSTNAMELEN
+			char hostname[MAXHOSTNAMELEN + 1];
+#else
+			char hostname[65];
+#endif
+
+			if(gethostname(hostname, sizeof(hostname)) < 0) {
+				const char *j = smfi_getsymval(ctx, "{j}");
+
+				if(j)
+					strncpy(hostname, j, sizeof(hostname) - 1);
+				else
+					strcpy(hostname, _("Error determining host"));
+				hostname[sizeof(hostname)-1]='\0';
+			} else if(strchr(hostname, '.') == NULL) {
+				/*
+				 * Determine fully qualified name
+				 */
+				struct hostent hostent;
+
+				if((r_gethostbyname(hostname, &hostent, buf, sizeof(buf)) == 0) && hostent.h_name) {
+					strncpy(hostname, hostent.h_name, sizeof(hostname));
+					hostname[sizeof(hostname)-1]='\0';
+				}
+			}
+
+#ifdef	SESSION
+			pthread_mutex_lock(&version_mutex);
+			snprintf(buf, sizeof(buf) - 1, "%s on %s",
+				clamav_versions[privdata->serverNumber], hostname);
+			pthread_mutex_unlock(&version_mutex);
+#else
+			snprintf(buf, sizeof(buf) - 1, "%s on %s",
+				clamav_version, hostname);
+#endif
+		} else {
+#ifdef	MAXHOSTNAMELEN
+			char hostname[MAXHOSTNAMELEN + 1];
+
+			if(cli_strtokbuf(serverHostNames, privdata->serverNumber, ":", hostname)) {
+				if(strcmp(hostname, "127.0.0.1") == 0)
+					gethostname(hostname, sizeof(hostname));
+#else
+			char *hostname = cli_strtok(serverHostNames, privdata->serverNumber, ":");
+			if(hostname) {
+#endif
+
+#ifdef	SESSION
+				pthread_mutex_lock(&version_mutex);
+				snprintf(buf, sizeof(buf) - 1, "%s on %s",
+					clamav_versions[privdata->serverNumber], hostname);
+				pthread_mutex_unlock(&version_mutex);
+#else
+				snprintf(buf, sizeof(buf) - 1, "%s on %s",
+					clamav_version, hostname);
+#endif
+#ifndef	MAXHOSTNAMELEN
+				free(hostname);
+#endif
+			} else
+				/* sanity check failed - should issue warning */
+				strcpy(buf, _("Error determining host"));
+		}
+		smfi_addheader(ctx, "X-Virus-Scanned", buf);
+	}
+
+	/*
+	 * TODO: it would be useful to add a header if mbox.c/FOLLOWURLS was
+	 * exceeded
+	 */
+#ifdef	HAVE_RESOLV_H
+	if((strstr(mess, "FOUND") != NULL) && (strstr(mess, "Phishing") != NULL)) {
+		table_t *prevhosts = tableCreate();
+
+		if(spf(privdata, prevhosts)) {
+			logg(_("%s: Ignoring %s false positive from %s received from %s\n"),
+				sendmailId, mess, privdata->from, privdata->ip);
+			strcpy(mess, "OK");
+			/*
+			 * Report false positive to ClamAV, works best when
+			 * clamav-milter has had to create a local copy of
+			 * the email, e.g. when --quarantine-dir is on
+			 */
+			if(report_fps &&
+			   (smfi_addrcpt(ctx, report_fps) == MI_FAILURE)) {
+				if(privdata->filename) {
+					char cmd[1024];
+
+					snprintf(cmd, sizeof(cmd) - 1,
+						"mail -s \"False Positive: %s\" %s < %s",
+						mess, report_fps,
+						privdata->filename);
+					if(system(cmd) == 0)
+						logg(_("#Reported phishing false positive to %s"), report_fps);
+					else
+						logg(_("^Couldn't report false positive to %s\n"), report_fps);
+				} else
+					/*
+					 * Most likely this is because we're
+					 * attempting to add a recipient on
+					 * another host
+					 */
+					logg(_("^Can't set phish FP header\n"));
+			}
+		}
+		tableDestroy(prevhosts);
+	}
+#endif
+	if(strstr(mess, "ERROR") != NULL) {
+		if(strstr(mess, "Size limit reached") != NULL) {
+			/*
+			 * Clamd has stopped on StreamMaxLength before us
+			 */
+			logg(_("%s: Message more than StreamMaxLength (%ld) bytes - not scanned"),
+				sendmailId, streamMaxLength);
+			if(!nflag)
+				smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned - StreamMaxLength exceeded"));
+			clamfi_cleanup(ctx);	/* not needed, but just to be safe */
+			return SMFIS_ACCEPT;
+		}
+		if(!nflag)
+			smfi_addheader(ctx, "X-Virus-Status", _("Not Scanned"));
+
+		logg("!%s: %s\n", sendmailId, mess);
+		rc = cl_error;
+	} else if((ptr = strstr(mess, "FOUND")) != NULL) {
+		/*
+		 * FIXME: This will give false positives if the
+		 *	word "FOUND" is in the email, e.g. the
+		 *	quarantine directory is /tmp/VIRUSES-FOUND
+		 */
+		int i;
+		char **to, *virusname, *err;
+		char reject[1024];
+
+		/*
+		 * Remove the "FOUND" word, and the space before it
+		 */
+		*--ptr = '\0';
+
+		/* skip over 'stream/filename: ' at the start */
+		if((virusname = strchr(mess, ':')) != NULL)
+			virusname = &virusname[2];
+		else
+			virusname = mess;
+
+		if(!nflag) {
+			char buf[129];
+
+			snprintf(buf, sizeof(buf) - 1, "%s %s", _("Infected with"), virusname);
+			smfi_addheader(ctx, "X-Virus-Status", buf);
+		}
+
+		if(quarantine_dir)
+			qfile(privdata, sendmailId, virusname);
+
+		/*
+		 * Setup err as a list of recipients
+		 */
+		err = (char *)cli_malloc(1024);
+
+		if(err == NULL) {
+			clamfi_cleanup(ctx);
+			return cl_error;
+		}
+
+		/*
+		 * Use snprintf rather than printf since we don't know
+		 * the length of privdata->from and may get a buffer
+		 * overrun
+		 */
+		snprintf(err, 1023, _("Intercepted virus from %s to"),
+			privdata->from);
+
+		ptr = strchr(err, '\0');
+
+		i = 1024;
+
+		for(to = privdata->to; *to; to++) {
+			/*
+			 * Re-alloc if we are about run out of buffer
+			 * space
+			 *
+			 * TODO: Only append *to if it's a valid, local
+			 *	email address
+			 */
+			if(&ptr[strlen(*to) + 2] >= &err[i]) {
+				i += 1024;
+				err = cli_realloc(err, i);
+				if(err == NULL) {
+					clamfi_cleanup(ctx);
+					return cl_error;
+				}
+				ptr = strchr(err, '\0');
+			}
+			ptr = cli_strrcpy(ptr, " ");
+			ptr = cli_strrcpy(ptr, *to);
+		}
+		(void)strcpy(ptr, "\n");
+
+		/* Include the sendmail queue ID in the log */
+		logg("%s: %s %s", sendmailId, mess, err);
+		free(err);
+
+		if(!qflag) {
+			char cmd[128];
+			FILE *sendmail;
+
+			/*
+			 * If the oflag is given this sendmail command
+			 * will cause the mail being generated here to be
+			 * scanned. So if oflag is given we just put the
+			 * item in the queue so there's no scanning of two
+			 * messages at once. It'll still be scanned, but
+			 * not at the same time as the incoming message
+			 *
+			 * FIXME: there is a race condition here when sendmail
+			 * and clamav-milter run on the same machine. If the
+			 * system is very overloaded this sendmail can
+			 * take a long time to start - and may even fail
+			 * is the LA is > REFUSE_LA. In all the time we're
+			 * taking to start this sendmail, the sendmail that's
+			 * started us may timeout waiting for a response and
+			 * let the virus through (albeit tagged with
+			 * X-Virus-Status: Infected) because we haven't
+			 * sent SMFIS_DISCARD or SMFIS_REJECT
+			 *
+			 * -i flag, suggested by Michal Jaegermann
+			 *	<michal at harddata.com>
+			 */
+			snprintf(cmd, sizeof(cmd) - 1,
+				(oflag || fflag) ? "%s -t -i -odq" : "%s -t -i",
+				SENDMAIL_BIN);
+
+			cli_dbgmsg("Calling %s\n", cmd);
+			sendmail = popen(cmd, "w");
+
+			if(sendmail) {
+				if(from && from[0])
+					fprintf(sendmail, "From: %s\n", from);
+				else
+					fprintf(sendmail, "From: %s\n", privdata->from);
+#ifdef	BOUNCE
+				if(bflag && privdata->from) {
+					fprintf(sendmail, "To: %s\n", privdata->from);
+					fprintf(sendmail, "Cc: %s\n", postmaster);
+				} else
+#endif
+					fprintf(sendmail, "To: %s\n", postmaster);
+
+				if((!pflag) && privdata->to)
+					for(to = privdata->to; *to; to++)
+						fprintf(sendmail, "Cc: %s\n", *to);
+				/*
+				 * Auto-submitted is still a draft, keep an
+				 * eye on its format
+				 */
+				fputs("Auto-Submitted: auto-submitted (antivirus notify)\n", sendmail);
+				/* "Sergey Y. Afonin" <asy at kraft-s.ru> */
+				if((ptr = smfi_getsymval(ctx, "{_}")) != NULL)
+					fprintf(sendmail,
+						"X-Infected-Received-From: %s\n",
+						ptr);
+				fputs(_("Subject: Virus intercepted\n"), sendmail);
+
+				if(templateHeaders) {
+					/*
+					 * For example, to state the character
+					 * set of the message:
+					 *	Content-Type: text/plain; charset=koi8-r
+					 *
+					 * Based on a suggestion by Denis
+					 *	Eremenko <moonshade at mail.kz>
+					 */
+					FILE *fin = fopen(templateHeaders, "r");
+
+					if(fin == NULL) {
+						perror(templateHeaders);
+						logg(_("!Can't open e-mail template header file %s"),
+								templateHeaders);
+					} else {
+						int c;
+						int lastc = EOF;
+
+						while((c = getc(fin)) != EOF) {
+							putc(c, sendmail);
+							lastc = c;
+						}
+						fclose(fin);
+						/*
+						 * File not new line terminated
+						 */
+						if(lastc != '\n')
+							fputs(_("\n"), sendmail);
+					}
+				}
+
+				fputs(_("\n"), sendmail);
+
+				if((templateFile == NULL) ||
+				   (sendtemplate(ctx, templateFile, sendmail, virusname) < 0)) {
+					/*
+					 * Use our own hardcoded template
+					 */
+#ifdef	BOUNCE
+					if(bflag)
+						fputs(_("A message you sent to\n"), sendmail);
+					else if(pflag)
+#else
+					if(pflag)
+#endif
+						/*
+						 * The message is only going to
+						 * the postmaster, so include
+						 * some useful information
+						 */
+						fprintf(sendmail, _("The message %1$s sent from %2$s to\n"),
+							sendmailId, privdata->from);
+					else
+						fprintf(sendmail, _("A message sent from %s to\n"),
+							privdata->from);
+
+					for(to = privdata->to; *to; to++)
+						fprintf(sendmail, "\t%s\n", *to);
+					fprintf(sendmail, _("contained %s and has not been accepted for delivery.\n"), virusname);
+
+					if(quarantine_dir != NULL)
+						fprintf(sendmail, _("\nThe message in question has been quarantined as %s\n"), privdata->filename);
+
+					if(hflag) {
+						fprintf(sendmail, _("\nThe message was received by %1$s from %2$s via %3$s\n\n"),
+							smfi_getsymval(ctx, "j"), privdata->from,
+							smfi_getsymval(ctx, "_"));
+						fputs(_("For your information, the original message headers were:\n\n"), sendmail);
+						header_list_print(privdata->headers, sendmail);
+					} else if(privdata->received)
+						/*
+						 * TODO: parse this to find
+						 * real infected machine.
+						 * Need to decide how to find
+						 * if it's a dynamic IP from a
+						 * dial up account in which
+						 * case there may not be much
+						 * we can do if that DHCP has
+						 * set the hostname...
+						 */
+						fprintf(sendmail, _("\nThe infected machine is likely to be here:\n%s\t\n"),
+							privdata->received);
+
+				}
+
+				cli_dbgmsg("Waiting for %s to finish\n", cmd);
+				if(pclose(sendmail) != 0)
+					logg(_("%s: Failed to notify clamAV interception - see dead.letter\n"), sendmailId);
+			} else
+				logg(_("^Can't execute '%s' to send virus notice"), cmd);
+		}
+
+		if(report && (quarantine == NULL) && (!advisory) &&
+		   (strstr(virusname, "Phishing") != NULL)) {
+			/*
+			 * Report phishing to an agency
+			 */
+			for(to = privdata->to; *to; to++) {
+				smfi_delrcpt(ctx, *to);
+				smfi_addheader(ctx, "X-Original-To", *to);
+			}
+			if(smfi_addrcpt(ctx, report) == MI_FAILURE) {
+				/* It's a remote site */
+				if(privdata->filename) {
+					char cmd[1024];
+
+					snprintf(cmd, sizeof(cmd) - 1,
+						"mail -s \"%s\" %s < %s",
+						virusname, report,
+						privdata->filename);
+					if(system(cmd) == 0)
+						logg(_("#Reported phishing to %s"), report);
+					else
+						logg(_("^Couldn't report to %s\n"), report);
+					if((!rejectmail) || privdata->discard)
+						rc = SMFIS_DISCARD;
+					else
+						rc = SMFIS_REJECT;
+				} else {
+					logg(_("^Can't set anti-phish header\n"));
+					rc = (privdata->discard) ? SMFIS_DISCARD : SMFIS_REJECT;
+				}
+			} else {
+				setsubject(ctx, "Phishing attempt trapped by ClamAV and redirected");
+
+				logg("Redirected phish to %s\n", report);
+			}
+		} else if(quarantine) {
+			for(to = privdata->to; *to; to++) {
+				smfi_delrcpt(ctx, *to);
+				smfi_addheader(ctx, "X-Original-To", *to);
+			}
+			/*
+			 * NOTE: on a closed relay this will not work
+			 * if the recipient is a remote address
+			 */
+			if(smfi_addrcpt(ctx, quarantine) == MI_FAILURE) {
+				logg(_("^Can't set quarantine user %s"), quarantine);
+				rc = (privdata->discard) ? SMFIS_DISCARD : SMFIS_REJECT;
+			} else {
+				if(report &&
+				   strstr(virusname, "Phishing") != NULL)
+					(void)smfi_addrcpt(ctx, report);
+				setsubject(ctx, virusname);
+
+				logg("Redirected virus to %s", quarantine);
+			}
+		} else if(advisory)
+			setsubject(ctx, virusname);
+		else if(rejectmail) {
+			if(privdata->discard)
+				rc = SMFIS_DISCARD;
+			else
+				rc = SMFIS_REJECT;	/* Delete the e-mail */
+		} else
+			rc = SMFIS_DISCARD;
+
+		if(quarantine_dir) {
+			/*
+			 * Cleanup filename here otherwise clamfi_free() will
+			 * delete the file that we wish to keep because it
+			 * is infected
+			 */
+			free(privdata->filename);
+			privdata->filename = NULL;
+		}
+
+		/*
+		 * Don't drop the message if it's been forwarded to a
+		 * quarantine email
+		 */
+		snprintf(reject, sizeof(reject) - 1, _("virus %s detected by ClamAV - http://www.clamav.net"), virusname);
+		smfi_setreply(ctx, (const char *)privdata->rejectCode, "5.7.1", reject);
+		broadcast(mess);
+
+		if(blacklist_time && privdata->ip[0]) {
+			logg(_("Will blacklist %s for %d seconds because of %s\n"),
+				privdata->ip, blacklist_time, virusname);
+			pthread_mutex_lock(&blacklist_mutex);
+			(void)tableUpdate(blacklist, privdata->ip,
+				(int)time((time_t *)0));
+			pthread_mutex_unlock(&blacklist_mutex);
+		}
+	} else if((strstr(mess, "OK") == NULL) && (strstr(mess, "Empty file") == NULL)) {
+		if(!nflag)
+			smfi_addheader(ctx, "X-Virus-Status", _("Unknown"));
+		logg(_("!%s: incorrect message \"%s\" from clamd"),
+				sendmailId, mess);
+		rc = cl_error;
+	} else {
+		if(!nflag)
+			smfi_addheader(ctx, "X-Virus-Status", _("Clean"));
+
+		/* Include the sendmail queue ID in the log */
+		if(logok)
+			logg(_("%s: clean message from %s\n"),
+				sendmailId,
+				(privdata->from) ? privdata->from : _("an unknown sender"));
+
+		if(privdata->body) {
+			/*
+			 * Add a signature that all has been scanned OK
+			 *
+			 * Note that this is simple minded and isn't aware of
+			 *	any MIME segments in the message. In practice
+			 *	this means that the message will only display
+			 *	on users' terminals if the message is
+			 *	plain/text
+			 */
+			off_t len = updateSigFile();
+
+			if(len) {
+				assert(Sflag != 0);
+
+				privdata->body = cli_realloc(privdata->body, privdata->bodyLen + len);
+				if(privdata->body) {
+					memcpy(&privdata->body[privdata->bodyLen], signature, len);
+					smfi_replacebody(ctx, privdata->body, privdata->bodyLen + len);
+				}
+			}
+		}
+	}
+
+	return rc;
+}
+
+static sfsistat
+clamfi_abort(SMFICTX *ctx)
+{
+	logg("*clamfi_abort\n");
+
+	clamfi_cleanup(ctx);
+	decrement_connexions();
+
+	logg("*clamfi_abort returns\n");
+
+	return cl_error;
+}
+
+static sfsistat
+clamfi_close(SMFICTX *ctx)
+{
+	logg("*clamfi_close\n");
+
+	clamfi_cleanup(ctx);
+	decrement_connexions();
+
+	return SMFIS_CONTINUE;
+}
+
+static void
+clamfi_cleanup(SMFICTX *ctx)
+{
+	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
+
+	cli_dbgmsg("clamfi_cleanup\n");
+
+	if(privdata) {
+		clamfi_free(privdata, 0);
+		smfi_setpriv(ctx, NULL);
+	}
+}
+
+static void
+clamfi_free(struct privdata *privdata, int keep)
+{
+	cli_dbgmsg("clamfi_free\n");
+
+	if(privdata) {
+#ifdef	SESSION
+		struct session *session;
+#endif
+		if(privdata->body)
+			free(privdata->body);
+
+		if(privdata->dataSocket >= 0)
+			close(privdata->dataSocket);
+
+		if(privdata->filename != NULL) {
+			/*
+			 * Don't print an error if the file hasn't been
+			 * created yet
+			 */
+			if((unlink(privdata->filename) < 0) && (errno != ENOENT)) {
+				perror(privdata->filename);
+				logg(_("!Can't remove clean file %s"),
+					privdata->filename);
+			}
+			free(privdata->filename);
+		}
+
+		if(privdata->from) {
+#ifdef	CL_DEBUG
+			if(debug_level >= 9)
+				cli_dbgmsg("Free privdata->from\n");
+#endif
+			free(privdata->from);
+		}
+
+		if(privdata->subject)
+			free(privdata->subject);
+		if(privdata->sender)
+			free(privdata->sender);
+
+		if(privdata->to) {
+			char **to;
+
+			for(to = privdata->to; *to; to++) {
+#ifdef	CL_DEBUG
+				if(debug_level >= 9)
+					cli_dbgmsg("Free *privdata->to\n");
+#endif
+				free(*to);
+			}
+#ifdef	CL_DEBUG
+			if(debug_level >= 9)
+				cli_dbgmsg("Free privdata->to\n");
+#endif
+			free(privdata->to);
+		}
+
+		if(external) {
+#ifdef	SESSION
+			session = &sessions[privdata->serverNumber];
+			pthread_mutex_lock(&sstatus_mutex);
+			if(session->status == CMDSOCKET_INUSE) {
+				/*
+				 * Probably we've got here because
+				 * StreamMaxLength has been reached
+				 */
+#if	0
+				pthread_mutex_unlock(&sstatus_mutex);
+				if(readTimeout) {
+					char buf[64];
+					const int fd = session->sock;
+
+					cli_dbgmsg("clamfi_free: flush server %d fd %d\n",
+						privdata->serverNumber, fd);
+
+					/*
+					 * FIXME: whenever this code gets
+					 *	executed, all of the PINGs fail
+					 *	in the next watchdog cycle
+					 */
+					while(clamd_recv(fd, buf, sizeof(buf)) > 0)
+						;
+				}
+				pthread_mutex_lock(&sstatus_mutex);
+#endif
+				/* Force a reset */
+				session->status = CMDSOCKET_DOWN;
+			}
+			pthread_mutex_unlock(&sstatus_mutex);
+#else
+			if(privdata->cmdSocket >= 0) {
+#if	0
+				char buf[64];
+
+				/*
+				 * Flush the remote end so that clamd doesn't
+				 * get a SIGPIPE
+				 */
+				if(readTimeout)
+					while(clamd_recv(privdata->cmdSocket, buf, sizeof(buf)) > 0)
+						;
+#endif
+				close(privdata->cmdSocket);
+			}
+#endif
+		}
+
+		if(privdata->headers)
+			header_list_free(privdata->headers);
+
+#ifdef	CL_DEBUG
+		if(debug_level >= 9)
+			cli_dbgmsg("Free privdata\n");
+#endif
+		if(privdata->received)
+			free(privdata->received);
+
+		if(keep) {
+			memset(privdata, '\0', sizeof(struct privdata));
+#ifdef	SESSION
+			privdata->dataSocket = -1;
+#else
+			privdata->dataSocket = privdata->cmdSocket = -1;
+#endif
+		} else
+			free(privdata);
+	}
+
+	cli_dbgmsg("clamfi_free returns\n");
+}
+
+/*
+ * Returns < 0 for failure, otherwise the number of bytes sent
+ */
+static int
+clamfi_send(struct privdata *privdata, size_t len, const char *format, ...)
+{
+	char output[BUFSIZ];
+	const char *ptr;
+	int ret = 0;
+	assert(format != NULL);
+
+	if(len > 0)
+		/*
+		 * It isn't a NUL terminated string. We have a set number of
+		 * bytes to output.
+		 */
+		ptr = format;
+	else {
+		va_list argp;
+
+		va_start(argp, format);
+		vsnprintf(output, sizeof(output) - 1, format, argp);
+		va_end(argp);
+
+		len = strlen(output);
+		ptr = output;
+	}
+#ifdef	CL_DEBUG
+	if(debug_level >= 9) {
+		time_t t;
+		const struct tm *tm;
+
+		time(&t);
+		tm = localtime(&t);
+
+		cli_dbgmsg("%d:%d:%d clamfi_send: len=%u bufsiz=%u, fd=%d\n",
+			tm->tm_hour, tm->tm_min, tm->tm_sec, len,
+			sizeof(output), privdata->dataSocket);
+	}
+#endif
+
+	while(len > 0) {
+		const int nbytes = (privdata->filename) ?
+			write(privdata->dataSocket, ptr, len) :
+			send(privdata->dataSocket, ptr, len, 0);
+
+		assert(privdata->dataSocket >= 0);
+
+		if(nbytes == -1) {
+			if(privdata->filename) {
+#ifdef HAVE_STRERROR_R
+				char buf[32];
+
+				perror(privdata->filename);
+				strerror_r(errno, buf, sizeof(buf));
+				logg(_("!write failure (%lu bytes) to %s: %s\n"),
+					(unsigned long)len, privdata->filename, buf);
+#else
+				perror(privdata->filename);
+				logg(_("!write failure (%lu bytes) to %s: %s\n"),
+					(unsigned long)len, privdata->filename,
+					strerror(errno));
+#endif
+			} else {
+				if(errno == EINTR)
+					continue;
+				perror("send");
+#ifdef HAVE_STRERROR_R
+				{
+					char buf[32];
+					strerror_r(errno, buf, sizeof(buf));
+					logg(_("!write failure (%lu bytes) to clamd: %s\n"),
+						(unsigned long)len, buf);
+				}
+#else
+				logg(_("!write failure (%lu bytes) to clamd: %s\n"),
+					(unsigned long)len, strerror(errno));
+#endif
+				checkClamd(1);
+			}
+
+			return -1;
+		}
+		ret += nbytes;
+		len -= nbytes;
+		ptr = &ptr[nbytes];
+
+		if(streamMaxLength > 0L) {
+			privdata->numBytes += nbytes;
+			if(privdata->numBytes >= streamMaxLength)
+				break;
+		}
+	}
+	return ret;
+}
+
+/*
+ * Like strcpy, but return the END of the destination, allowing a quicker
+ * means of adding to the end of a string than strcat
+ */
+#if	0
+static char *
+strrcpy(char *dest, const char *source)
+{
+	/* Pre assertions */
+	assert(dest != NULL);
+	assert(source != NULL);
+	assert(dest != source);
+
+	while((*dest++ = *source++) != '\0')
+		;
+	return(--dest);
+}
+#endif
+
+/*
+ * Read from clamav - timeout if necessary
+ */
+static long
+clamd_recv(int sock, char *buf, size_t len)
+{
+	struct timeval tv;
+	long ret;
+
+	assert(sock >= 0);
+
+	if(readTimeout == 0) {
+		do
+			/* TODO: Needs a test for ssize_t in configure */
+			ret = (long)recv(sock, buf, len, 0);
+		while((ret < 0) && (errno == EINTR));
+
+		return ret;
+	}
+
+	tv.tv_sec = readTimeout;
+	tv.tv_usec = 0;
+
+	for(;;) {
+		fd_set rfds;
+
+		FD_ZERO(&rfds);
+		FD_SET(sock, &rfds);
+
+		switch(select(sock + 1, &rfds, NULL, NULL, &tv)) {
+			case -1:
+				if(errno == EINTR)
+					/* FIXME: work out time left */
+					continue;
+				perror("select");
+				return -1;
+			case 0:
+				logg(_("!No data received from clamd in %d seconds\n"), readTimeout);
+				return 0;
+		}
+		break;
+	}
+
+	do
+		ret = recv(sock, buf, len, 0);
+	while((ret < 0) && (errno == EINTR));
+
+	return ret;
+}
+
+/*
+ * Read in the signature file
+ */
+static off_t
+updateSigFile(void)
+{
+	struct stat statb;
+	int fd;
+
+	if(sigFilename == NULL)
+		/* nothing to read */
+		return 0;
+
+	if(stat(sigFilename, &statb) < 0) {
+		perror(sigFilename);
+		logg(_("Can't stat %s"), sigFilename);
+		return 0;
+	}
+
+	if(statb.st_mtime <= signatureStamp)
+		return statb.st_size;	/* not changed */
+
+	fd = open(sigFilename, O_RDONLY);
+	if(fd < 0) {
+		perror(sigFilename);
+		logg(_("Can't open %s"), sigFilename);
+		return 0;
+	}
+
+	signatureStamp = statb.st_mtime;
+
+	signature = cli_realloc((void *)signature, statb.st_size);
+	if(signature)
+		cli_readn(fd, (void *)signature, statb.st_size);
+	close(fd);
+
+	return statb.st_size;
+}
+
+static header_list_t
+header_list_new(void)
+{
+	header_list_t ret;
+
+	ret = (header_list_t)cli_malloc(sizeof(struct header_list_struct));
+	if(ret) {
+		ret->first = NULL;
+		ret->last = NULL;
+	}
+	return ret;
+}
+
+static void
+header_list_free(header_list_t list)
+{
+	struct header_node_t *iter;
+
+	if(list == NULL)
+		return;
+
+	iter = list->first;
+	while(iter) {
+		struct header_node_t *iter2 = iter->next;
+		free(iter->header);
+		free(iter);
+		iter = iter2;
+	}
+	free(list);
+}
+
+static void
+header_list_add(header_list_t list, const char *headerf, const char *headerv)
+{
+	char *header;
+	size_t len;
+	struct header_node_t *new_node;
+
+	if(list == NULL)
+		return;
+
+	len = (size_t)(strlen(headerf) + strlen(headerv) + 3);
+
+	header = (char *)cli_malloc(len);
+	if(header == NULL)
+		return;
+
+	sprintf(header, "%s: %s", headerf, headerv);
+	new_node = (struct header_node_t *)cli_malloc(sizeof(struct header_node_t));
+	if(new_node == NULL) {
+		free(header);
+		return;
+	}
+	new_node->header = header;
+	new_node->next = NULL;
+	if(list->first == NULL)
+		list->first = new_node;
+	if(list->last)
+		list->last->next = new_node;
+
+	list->last = new_node;
+}
+
+static void
+header_list_print(const header_list_t list, FILE *fp)
+{
+	const struct header_node_t *iter;
+
+	if(list == NULL)
+		return;
+
+	for(iter = list->first; iter; iter = iter->next) {
+		if(strncmp(iter->header, "From ", 5) == 0)
+			putc('>', fp);
+		fprintf(fp, "%s\n", iter->header);
+	}
+}
+
+/*
+ * Establish a connexion to clamd
+ *	Returns success (1) or failure (0)
+ */
+static int
+connect2clamd(struct privdata *privdata)
+{
+	assert(privdata != NULL);
+	assert(privdata->dataSocket == -1);
+	assert(privdata->from != NULL);
+	assert(privdata->to != NULL);
+
+	logg("*connect2clamd\n");
+
+	if(quarantine_dir || tmpdir) {	/* store message in a temporary file */
+		int ntries = 5;
+		const char *dir = (tmpdir) ? tmpdir : quarantine_dir;
+
+		/*
+		 * TODO: investigate mkdtemp on LINUX and possibly others
+		 */
+#ifdef	C_AIX
+		/*
+		 * Patch by Andy Feldt <feldt at nhn.ou.edu>, AIX 5.2 sets errno
+		 * to ENOENT often and sometimes sets errno to 0 (after a
+		 * database reload) for the mkdir call
+		 */
+		if((mkdir(dir, 0700) < 0) && (errno != EEXIST) && (errno > 0) &&
+		    (errno != ENOENT)) {
+#else
+		if((mkdir(dir, 0700) < 0) && (errno != EEXIST)) {
+#endif
+			perror(dir);
+			logg(_("mkdir %s failed"), dir);
+			return 0;
+		}
+		privdata->filename = (char *)cli_malloc(strlen(dir) + 12);
+
+		if(privdata->filename == NULL)
+			return 0;
+
+		do {
+			sprintf(privdata->filename, "%s/msg.XXXXXX", dir);
+#if	defined(C_LINUX) || defined(C_BSD) || defined(HAVE_MKSTEMP) || defined(C_SOLARIS)
+			privdata->dataSocket = mkstemp(privdata->filename);
+#else
+			if(mktemp(privdata->filename) == NULL) {
+				logg(_("mktemp %s failed"), privdata->filename);
+				return 0;
+			}
+			privdata->dataSocket = open(privdata->filename, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC, 0600);
+#endif
+		} while((--ntries > 0) && (privdata->dataSocket < 0));
+
+		if(privdata->dataSocket < 0) {
+			perror(privdata->filename);
+			logg(_("Temporary quarantine file %s creation failed"),
+				privdata->filename);
+			free(privdata->filename);
+			privdata->filename = NULL;
+			return 0;
+		}
+		privdata->serverNumber = 0;
+		cli_dbgmsg("Saving message to %s to scan later\n", privdata->filename);
+	} else {	/* communicate to clamd */
+		int freeServer, nbytes;
+		in_port_t p;
+		struct sockaddr_in reply;
+		char buf[64];
+
+#ifdef	SESSION
+		struct session *session;
+#else
+		assert(privdata->cmdSocket == -1);
+#endif
+
+		/*
+		 * Create socket to talk to clamd. It will tell us the port to
+		 * use to send the data. That will require another socket.
+		 */
+		if(localSocket) {
+#ifndef	SESSION
+			struct sockaddr_un server;
+
+			memset((char *)&server, 0, sizeof(struct sockaddr_un));
+			server.sun_family = AF_UNIX;
+			strncpy(server.sun_path, localSocket, sizeof(server.sun_path));
+			server.sun_path[sizeof(server.sun_path)-1]='\0';
+
+			if((privdata->cmdSocket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+				perror("socket");
+				return 0;
+			}
+			if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) {
+				perror(localSocket);
+				return 0;
+			}
+			privdata->serverNumber = 0;
+#endif
+			freeServer = 0;
+		} else {	/* TCP/IP */
+#ifdef	SESSION
+			freeServer = findServer();
+			if(freeServer < 0)
+				return 0;
+			assert(freeServer < (int)max_children);
+#else
+			struct sockaddr_in server;
+
+			memset((char *)&server, 0, sizeof(struct sockaddr_in));
+			server.sin_family = AF_INET;
+			server.sin_port = (in_port_t)htons(tcpSocket);
+
+			assert(serverIPs != NULL);
+
+			freeServer = findServer();
+			if(freeServer < 0)
+				return 0;
+			assert(freeServer < (int)numServers);
+
+			server.sin_addr.s_addr = serverIPs[freeServer];
+
+			if((privdata->cmdSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+				perror("socket");
+				return 0;
+			}
+			if(connect(privdata->cmdSocket, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
+				char *hostname = cli_strtok(serverHostNames, freeServer, ":");
+
+				perror(hostname ? hostname : "connect");
+				close(privdata->cmdSocket);
+				privdata->cmdSocket = -1;
+				if(hostname)
+					free(hostname);
+				time(&last_failed_pings[freeServer]);
+				return 0;
+			}
+			last_failed_pings[freeServer] = (time_t)0;
+#endif
+			privdata->serverNumber = freeServer;
+		}
+
+#ifdef	SESSION
+		if(serverIPs[freeServer] == (int)inet_addr("127.0.0.1")) {
+			privdata->filename = cli_gentemp(NULL);
+			if(privdata->filename) {
+				cli_dbgmsg("connect2clamd(%d): creating %s\n", freeServer, privdata->filename);
+#ifdef	O_TEXT
+				privdata->dataSocket = open(privdata->filename, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_TEXT, 0600);
+#else
+				privdata->dataSocket = open(privdata->filename, O_WRONLY|O_CREAT|O_EXCL|O_TRUNC, 0600);
+#endif
+				if(privdata->dataSocket < 0) {
+					perror(privdata->filename);
+					free(privdata->filename);
+					privdata->filename = NULL;
+				} else
+					return sendToFrom(privdata);
+			}
+		}
+		cli_dbgmsg("connect2clamd(%d): STREAM\n", freeServer);
+
+		session = &sessions[freeServer];
+		if((session->sock < 0) || (send(session->sock, "STREAM\n", 7, 0) < 7)) {
+			perror("send");
+			pthread_mutex_lock(&sstatus_mutex);
+			session->status = CMDSOCKET_DOWN;
+			pthread_mutex_unlock(&sstatus_mutex);
+			logg(_("!failed to send STREAM command clamd server %d"),
+				freeServer);
+
+			return 0;
+		}
+#else
+		if(send(privdata->cmdSocket, "STREAM\n", 7, 0) < 7) {
+			perror("send");
+			logg(_("!failed to send STREAM command clamd"));
+			return 0;
+		}
+		shutdown(privdata->cmdSocket, SHUT_WR);
+#endif
+
+		/*
+		 * Create socket that we'll use to send the data to clamd
+		 */
+		if((privdata->dataSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+			perror("socket");
+			logg(_("!failed to create TCPSocket to talk to clamd\n"));
+			return 0;
+		}
+
+		shutdown(privdata->dataSocket, SHUT_RD);
+
+#ifdef	SESSION
+		nbytes = clamd_recv(session->sock, buf, sizeof(buf));
+		if(nbytes <= 0) {
+			if(nbytes < 0) {
+				perror("recv");
+				logg(_("!recv failed from clamd getting PORT\n"));
+			} else
+				logg(_("!EOF from clamd getting PORT\n"));
+
+			pthread_mutex_lock(&sstatus_mutex);
+			session->status = CMDSOCKET_DOWN;
+			return pthread_mutex_unlock(&sstatus_mutex);
+		}
+#else
+		nbytes = clamd_recv(privdata->cmdSocket, buf, sizeof(buf));
+		if(nbytes <= 0) {
+			if(nbytes < 0) {
+				perror("recv");
+				logg(_("!recv failed from clamd getting PORT\n"));
+			} else
+				logg(_("!EOF from clamd getting PORT\n"));
+
+			return 0;
+		}
+#endif
+		buf[nbytes] = '\0';
+#ifdef	CL_DEBUG
+		if(debug_level >= 4)
+			cli_dbgmsg("Received: %s\n", buf);
+#endif
+		if(sscanf(buf, "PORT %hu\n", &p) != 1) {
+			logg(_("!Expected port information from clamd, got '%s'\n"),
+				buf);
+#ifdef	SESSION
+			session->status = CMDSOCKET_DOWN;
+			pthread_mutex_unlock(&sstatus_mutex);
+#endif
+			return 0;
+		}
+
+		memset((char *)&reply, 0, sizeof(struct sockaddr_in));
+		reply.sin_family = AF_INET;
+		reply.sin_port = (in_port_t)htons(p);
+
+		assert(serverIPs != NULL);
+
+		reply.sin_addr.s_addr = serverIPs[freeServer];
+
+#ifdef	CL_DEBUG
+		if(debug_level >= 4)
+#ifdef	SESSION
+			cli_dbgmsg(_("Connecting to local port %d - data %d cmd %d\n"),
+				p, privdata->dataSocket, session->sock);
+#else
+			cli_dbgmsg(_("Connecting to local port %d - data %d cmd %d\n"),
+				p, privdata->dataSocket, privdata->cmdSocket);
+#endif
+#endif
+
+		if(connect(privdata->dataSocket, (struct sockaddr *)&reply, sizeof(struct sockaddr_in)) < 0) {
+			perror("connect");
+
+			cli_dbgmsg("Failed to connect to port %d given by clamd\n",
+				p);
+			/* 0.4 - use better error message */
+#ifdef HAVE_STRERROR_R
+			strerror_r(errno, buf, sizeof(buf));
+			logg(_("!Failed to connect to port %d given by clamd: %s"),
+					p, buf);
+#else
+			logg(_("!Failed to connect to port %d given by clamd: %s"), p, strerror(errno));
+#endif
+#ifdef	SESSION
+			pthread_mutex_lock(&sstatus_mutex);
+			session->status = CMDSOCKET_DOWN;
+			pthread_mutex_unlock(&sstatus_mutex);
+#endif
+			return 0;
+		}
+	}
+
+	if(!sendToFrom(privdata))
+		return 0;
+
+	cli_dbgmsg("connect2clamd: serverNumber = %d\n", privdata->serverNumber);
+
+	return 1;
+}
+
+/*
+ * Combine the To and From into one clamfi_send to save bandwidth
+ * when sending using TCP/IP to connect to a remote clamd, by band
+ * width here I mean number of packets
+ */
+static int
+sendToFrom(struct privdata *privdata)
+{
+	char **to;
+	char *msg;
+	int length;
+
+	length = strlen(privdata->from) + 34;
+	for(to = privdata->to; *to; to++)
+		length += strlen(*to) + 5;
+
+	msg = cli_malloc(length + 1);
+
+	if(msg) {
+		sprintf(msg, "Received: by clamav-milter\nFrom: %s\n",
+			privdata->from);
+
+		for(to = privdata->to; *to; to++) {
+			char *eom = strchr(msg, '\0');
+
+			sprintf(eom, "To: %s\n", *to);
+		}
+		if(clamfi_send(privdata, length, msg) != length) {
+			free(msg);
+			return 0;
+		}
+		free(msg);
+	} else {
+		if(clamfi_send(privdata, 0,
+		    "Received: by clamav-milter\nFrom: %s\n",
+		    privdata->from) <= 0)
+			return 0;
+
+		for(to = privdata->to; *to; to++)
+			if(clamfi_send(privdata, 0, "To: %s\n", *to) <= 0)
+				return 0;
+	}
+
+	return 1;
+}
+
+/*
+ * If possible, check if clamd has died, and, if requested, report if it has
+ * Returns true if OK or unknown, otherwise false
+ */
+static int
+checkClamd(int log_result)
+{
+	pid_t pid;
+	int fd, nbytes;
+	char buf[9];
+
+	if(!localSocket) {
+		/* communicating via TCP, is one of the servers localhost? */
+		int i, onlocal;
+
+		onlocal = 0;
+		for(i = 0; i < numServers; i++)
+#ifdef	INADDR_LOOPBACK
+			if(serverIPs[0] == htonl(INADDR_LOOPBACK)) {
+#else
+			if(serverIPs[0] == inet_addr("127.0.0.1")) {
+#endif
+				onlocal = 1;
+				break;
+			}
+
+		if(!onlocal) {
+			/* No local clamd, use pingServer() to tell */
+			for(i = 0; i < numServers; i++)
+				if(serverIPs[i] && pingServer(i))
+					return 1;
+			if(log_result)
+				logg(_("!Can't find any clamd server\n"));
+			return 0;
+		}
+	}
+
+	if(pidFile == NULL)
+		return 1;	/* PidFile directive missing from clamd.conf */
+
+	fd = open(pidFile, O_RDONLY);
+	if(fd < 0) {
+		if(log_result) {
+			perror(pidFile);
+			logg(_("!Can't open %s\n"), pidFile);
+		}
+		return 1;	/* unknown */
+	}
+	nbytes = read(fd, buf, sizeof(buf) - 1);
+	if(nbytes < 0)
+		perror(pidFile);
+	else
+		buf[nbytes] = '\0';
+	close(fd);
+	pid = atoi(buf);
+	if((kill(pid, 0) < 0) && (errno == ESRCH)) {
+		if(log_result) {
+			perror("clamd");
+			logg(_("!Clamd (pid %d) seems to have died\n"), (int)pid);
+		}
+		return 0;	/* down */
+	}
+	return 1;	/* up */
+}
+
+/*
+ * Send a templated message about an intercepted message. Very basic for
+ * now, just to prove it works, will enhance the flexability later, only
+ * supports %v and $sendmail_variables$ at present.
+ *
+ * TODO: more template features
+ * TODO: allow filename to start with a '|' taken to mean the output of
+ *	a program
+ */
+static int
+sendtemplate(SMFICTX *ctx, const char *filename, FILE *sendmail, const char *virusname)
+{
+	FILE *fin = fopen(filename, "r");
+	struct stat statb;
+	char *buf, *ptr /* , *ptr2 */;
+	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
+
+	if(fin == NULL) {
+		perror(filename);
+		logg(_("!Can't open e-mail template file %s"), filename);
+		return -1;
+	}
+
+	if(fstat(fileno(fin), &statb) < 0) {
+		/* File disappeared in race condition? */
+		perror(filename);
+		logg(_("!Can't stat e-mail template file %s"), filename);
+		fclose(fin);
+		return -1;
+	}
+	buf = cli_malloc(statb.st_size + 1);
+	if(buf == NULL) {
+		fclose(fin);
+		logg(_("!Out of memory"));
+		return -1;
+	}
+	if(fread(buf, sizeof(char), statb.st_size, fin) != (size_t)statb.st_size) {
+		perror(filename);
+		logg(_("!Error reading e-mail template file %s"),
+			filename);
+		fclose(fin);
+		free(buf);
+		return -1;
+	}
+	fclose(fin);
+	buf[statb.st_size] = '\0';
+
+	for(ptr = buf; *ptr; ptr++)
+		switch(*ptr) {
+			case '%': /* clamAV variable */
+				switch(*++ptr) {
+					case 'v':	/* virus name */
+						fputs(virusname, sendmail);
+						break;
+					case '%':
+						putc('%', sendmail);
+						break;
+					case 'h':	/* headers */
+						if(privdata)
+							header_list_print(privdata->headers, sendmail);
+						break;
+					case '\0':
+						putc('%', sendmail);
+						--ptr;
+						continue;
+					default:
+						logg(_("!%s: Unknown clamAV variable \"%c\"\n"),
+							filename, *ptr);
+						break;
+				}
+				break;
+			case '$': /* sendmail string */ {
+				const char *val;
+				char *end = strchr(++ptr, '$');
+
+				if(end == NULL) {
+					logg(_("!%s: Unterminated sendmail variable \"%s\"\n"),
+						filename, ptr);
+					continue;
+				}
+				*end = '\0';
+
+				val = smfi_getsymval(ctx, ptr);
+				if(val == NULL) {
+					fputs(ptr, sendmail);
+						logg(_("!%s: Unknown sendmail variable \"%s\"\n"),
+							filename, ptr);
+				} else
+					fputs(val, sendmail);
+				ptr = end;
+				break;
+			}
+			case '\\':
+				if(*++ptr == '\0') {
+					--ptr;
+					continue;
+				}
+				putc(*ptr, sendmail);
+				break;
+			default:
+				putc(*ptr, sendmail);
+		}
+
+	free(buf);
+
+	return 0;
+}
+
+/*
+ * Keep the infected file in quarantine, return success (0) or failure
+ *
+ * It's quicker if the quarantine directory is on the same filesystem
+ *	as the temporary directory
+ */
+static int
+qfile(struct privdata *privdata, const char *sendmailId, const char *virusname)
+{
+	int MM, YY, DD;
+	time_t t;
+	size_t len;
+	char *newname, *ptr;
+	const struct tm *tm;
+
+	assert(privdata != NULL);
+
+	if((privdata->filename == NULL) || (virusname == NULL))
+		return -1;
+
+	cli_dbgmsg("qfile filename '%s' sendmailId '%s' virusname '%s'\n", privdata->filename, sendmailId, virusname);
+
+	len = strlen(quarantine_dir);
+
+	newname = cli_malloc(len + strlen(sendmailId) + strlen(virusname) + 10);
+
+	if(newname == NULL)
+		return -1;
+
+	t = time((time_t *)0);
+	tm = localtime(&t);
+	MM = tm->tm_mon + 1;
+	YY = tm->tm_year - 100;
+	DD = tm->tm_mday;
+
+	sprintf(newname, "%s/%02d%02d%02d", quarantine_dir, YY, MM, DD);
+#ifdef	C_AIX
+	if((mkdir(newname, 0700) < 0) && (errno != EEXIST) && (errno > 0) &&
+	    (errno != ENOENT)) {
+#else
+	if((mkdir(newname, 0700) < 0) && (errno != EEXIST)) {
+#endif
+		perror(newname);
+		logg(_("!mkdir %s failed\n"), newname);
+		return -1;
+	}
+	sprintf(newname, "%s/%02d%02d%02d/%s.%s",
+		quarantine_dir, YY, MM, DD, sendmailId, virusname);
+
+	/*
+	 * Strip out funnies that may be in the name of the virus, such as '/'
+	 * that would cause the quarantine to fail to save since the name
+	 * of the virus is included in the filename
+	 */
+	for(ptr = &newname[len + 8]; *ptr; ptr++) {
+#ifdef	C_DARWIN
+		*ptr &= '\177';
+#endif
+#if	defined(MSDOS) || defined(C_WINDOWS) || defined(C_OS2)
+		if(strchr("/*?<>|\\\"+=,;:\t ", *ptr))
+#else
+		if(*ptr == '/')
+#endif
+			*ptr = '_';
+	}
+	cli_dbgmsg("qfile move '%s' to '%s'\n", privdata->filename, newname);
+
+	if(move(privdata->filename, newname) < 0) {
+		logg(_("^Can't rename %1$s to %2$s\n"),
+			privdata->filename, newname);
+		free(newname);
+		return -1;
+	}
+	free(privdata->filename);
+	privdata->filename = newname;
+
+	logg(_("Email quarantined as %s\n"), newname);
+
+	return 0;
+}
+
+/*
+ * Move oldfile to newfile using the fastest possible method
+ */
+static int
+move(const char *oldfile, const char *newfile)
+{
+	int ret, c;
+	FILE *fin, *fout;
+#ifdef	C_LINUX
+	struct stat statb;
+	int in, out;
+	off_t offset;
+#endif
+
+	ret = rename(oldfile, newfile);
+	if(ret >= 0)
+		return 0;
+
+	if((ret < 0) && (errno != EXDEV)) {
+		perror(newfile);
+		return -1;
+	}
+
+#ifdef	C_LINUX	/* >= 2.2 */
+	in = open(oldfile, O_RDONLY);
+	if(in < 0) {
+		perror(oldfile);
+		return -1;
+	}
+
+	if(fstat(in, &statb) < 0) {
+		perror(oldfile);
+		close(in);
+		return -1;
+	}
+	out = open(newfile, O_WRONLY|O_CREAT, 0600);
+	if(out < 0) {
+		perror(newfile);
+		close(in);
+		return -1;
+	}
+	offset = (off_t)0;
+	ret = sendfile(out, in, &offset, statb.st_size);
+	close(in);
+	if(ret < 0) {
+		/*
+		 * Fall back if sendfile fails, which will happen on Linux
+		 * 2.6 :-(. FreeBSD works correctly, so the ifdef should be
+		 * fixed
+		 */
+		close(out);
+		unlink(newfile);
+
+		fin = fopen(oldfile, "r");
+		if(fin == NULL)
+			return -1;
+
+		fout = fopen(newfile, "w");
+		if(fout == NULL) {
+			fclose(fin);
+			return -1;
+		}
+		while((c = getc(fin)) != EOF)
+			putc(c, fout);
+
+		fclose(fin);
+		fclose(fout);
+	} else
+		close(out);
+#else
+	fin = fopen(oldfile, "r");
+	if(fin == NULL)
+		return -1;
+
+	fout = fopen(newfile, "w");
+	if(fout == NULL) {
+		fclose(fin);
+		return -1;
+	}
+	while((c = getc(fin)) != EOF)
+		putc(c, fout);
+
+	fclose(fin);
+	fclose(fout);
+#endif
+
+	cli_dbgmsg("removing %s\n", oldfile);
+
+	return unlink(oldfile);
+}
+
+/*
+ * Store the name of the virus in the subject of the e-mail
+ */
+static void
+setsubject(SMFICTX *ctx, const char *virusname)
+{
+	struct privdata *privdata = (struct privdata *)smfi_getpriv(ctx);
+	char subject[128];
+
+	if(privdata->subject)
+		smfi_addheader(ctx, "X-Original-Subject", privdata->subject);
+
+	snprintf(subject, sizeof(subject) - 1, _("[Virus] %s"), virusname);
+	if(privdata->subject)
+		smfi_chgheader(ctx, "Subject", 1, subject);
+	else
+		smfi_addheader(ctx, "Subject", subject);
+}
+
+#if	0
+/*
+ * TODO: gethostbyname_r is non-standard so different operating
+ * systems do it in different ways. Need more examples
+ * Perhaps we could use res_search()?
+ * Perhaps we could use http://www.chiark.greenend.org.uk/~ian/adns/
+ *
+ * Returns 0 for success
+ */
+static int
+clamfi_gethostbyname(const char *hostname, struct hostent *hp, char *buf, size_t len)
+{
+#if	defined(HAVE_GETHOSTBYNAME_R_6)
+	/* e.g. Linux */
+	struct hostent *hp2;
+	int ret = -1;
+
+	if((hostname == NULL) || (hp == NULL))
+		return -1;
+	if(gethostbyname_r(hostname, hp, buf, len, &hp2, &ret) < 0)
+		return ret;
+#elif	defined(HAVE_GETHOSTBYNAME_R_5)
+	/* e.g. BSD, Solaris, Cygwin */
+	int ret = -1;
+
+	if((hostname == NULL) || (hp == NULL))
+		return -1;
+	if(gethostbyname_r(hostname, hp, buf, len, &ret) == NULL)
+		return ret;
+#elif	defined(HAVE_GETHOSTBYNAME_R_3)
+	/* e.g. HP/UX, AIX */
+	if((hostname == NULL) || (hp == NULL))
+		return -1;
+	if(gethostbyname_r(hostname, &hp, (struct hostent_data *)buf) < 0)
+		return h_errno;
+#else
+	/* Single thread the code */
+	struct hostent *hp2;
+	static pthread_mutex_t hostent_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+	if((hostname == NULL) || (hp == NULL))
+		return -1;
+
+	pthread_mutex_lock(&hostent_mutex);
+	if((hp2 = gethostbyname(hostname)) == NULL) {
+		pthread_mutex_unlock(&hostent_mutex);
+		return h_errno;
+	}
+	memcpy(hp, hp2, sizeof(struct hostent));
+	pthread_mutex_unlock(&hostent_mutex);
+#endif
+
+	return 0;
+}
+#endif
+
+/*
+ * Handle the -I flag
+ */
+static int
+add_local_ip(char *address)
+{
+	char *opt, *pref;
+	int preflen;
+	int retval;
+	struct in_addr ignoreIP;
+#ifdef	AF_INET6
+	struct in6_addr ignoreIP6;
+#endif
+
+	opt = cli_strdup(address);
+	if(opt == NULL)
+		return 0;
+
+	pref = strchr(opt, '/'); /* search for "/prefix" */
+	if(pref)
+		*pref = '\0';
+#ifdef HAVE_INET_NTOP
+	/* IPv4 address ? */
+	if(inet_pton(AF_INET, opt, &ignoreIP) > 0) {
+#else
+	if(inet_aton(address, &ignoreIP)) {
+#endif
+		struct cidr_net *net;
+
+		for(net = (struct cidr_net *)localNets; net->base; net++)
+			;
+		if(pref && *(pref+1))
+			preflen = atoi(pref+1);
+		else
+			preflen = 32;
+
+		net->base = ntohl(ignoreIP.s_addr);
+		net->mask = MAKEMASK(preflen);
+
+		retval = 1;
+	}
+
+#ifdef HAVE_INET_NTOP
+#ifdef AF_INET6
+	else if(inet_pton(AF_INET6, opt, &ignoreIP6) > 0) {
+		/* IPv6 address ? */
+		localNets6[localNets6_cnt].base = ignoreIP6;
+
+		if(pref && *(pref+1))
+			preflen = atoi (pref+1);
+		else
+			preflen = 128;
+		localNets6[localNets6_cnt].preflen = preflen;
+		localNets6_cnt++;
+
+		retval = 1;
+	}
+#endif
+#endif
+	else
+		retval = 0;
+
+	free(opt);
+	return retval;
+}
+
+/*
+ * Determine if an IPv6 email address is "local". The address is the
+ *	human readable version. Calls isLocalAddr if the given address is
+ *	IPv4
+ */
+static int
+isLocal(const char *addr)
+{
+	struct in_addr ip;
+#ifdef	AF_INET6
+	struct in6_addr ip6;
+#endif
+
+#ifdef HAVE_INET_NTOP
+	if(inet_pton(AF_INET, addr, &ip) > 0)
+		return isLocalAddr(ip.s_addr);
+#ifdef AF_INET6
+	else if(inet_pton (AF_INET6, addr, &ip6) > 0) {
+		int i;
+		const cidr_net6 *pnet6 = localNets6;
+
+		for (i = 0; i < localNets6_cnt; i++) {
+			int match = 1;
+			int j;
+
+			for(j = 0; match && j < (pnet6->preflen >> 3); j++)
+				if(pnet6->base.s6_addr[j] != ip6.s6_addr[j])
+					match = 0;
+			if(match && (j < 16)) {
+				uint8_t mask = (uint8_t)(0xff << (8 - (pnet6->preflen & 7)) & 0xFF);
+
+				if((pnet6->base.s6_addr[j] & mask) != (ip6.s6_addr[j] & mask))
+					match = 0;
+			}
+			if(match)
+				return 1;	/* isLocal */
+			pnet6++;
+		 }
+	}
+#endif	/* AF_INET6 */
+#endif	/* HAVE_INET_NTOP */
+	return isLocalAddr(inet_addr(addr));
+}
+
+/*
+ * David Champion <dgc at uchicago.edu>
+ *
+ * Check whether addr is on network by applying netmasks.
+ * addr must be a 32-bit integer-packed IPv4 address in network order.
+ * For example:
+ *	struct in_addr IPAddress;
+ *	isLocal = isLocalAddr(IPAddress.s_addr);
+ */
+static int
+isLocalAddr(in_addr_t addr)
+{
+	const struct cidr_net *net;
+
+	for(net = localNets; net->base; net++)
+		if((net->base & net->mask) == (ntohl(addr) & net->mask))
+			return 1;
+
+	return 0;	/* is non-local */
+}
+
+/*
+ * Can't connect to any clamd server. This is serious, we need to inform
+ * someone. In the absence of SNMP the best way is by e-mail. We
+ * don't want to flood so there's a need to restrict to
+ * no more than say one message every 15 minutes
+ */
+static void
+clamdIsDown(void)
+{
+	static time_t lasttime;
+	time_t thistime, diff;
+	static pthread_mutex_t time_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+	logg(_("!No response from any clamd server - your AV system is not scanning emails\n"));
+
+	time(&thistime);
+	pthread_mutex_lock(&time_mutex);
+	diff = thistime - lasttime;
+	pthread_mutex_unlock(&time_mutex);
+
+	if(diff >= (time_t)(15 * 60)) {
+		char cmd[128];
+		FILE *sendmail;
+
+		snprintf(cmd, sizeof(cmd) - 1, "%s -t -i", SENDMAIL_BIN);
+
+		sendmail = popen(cmd, "w");
+
+		if(sendmail) {
+			fprintf(sendmail, "To: %s\n", postmaster);
+			fprintf(sendmail, "From: %s\n", postmaster);
+			fputs(_("Subject: ClamAV Down\n"), sendmail);
+			fputs("Priority: High\n\n", sendmail);
+
+			fputs(_("This is an automatic message\n\n"), sendmail);
+
+			if(numServers == 1)
+				fputs(_("The clamd program cannot be contacted.\n"), sendmail);
+			else
+				fputs(_("No clamd server can be contacted.\n"), sendmail);
+
+			fputs(_("Emails may not be being scanned, please check your servers.\n"), sendmail);
+
+			if(pclose(sendmail) == 0) {
+				pthread_mutex_lock(&time_mutex);
+				time(&lasttime);
+				pthread_mutex_unlock(&time_mutex);
+			}
+		}
+	}
+}
+
+#ifdef	SESSION
+/*
+ * Thread to monitor the links to clamd sessions. Any marked as being in
+ * an error state because of previous I/O errors are restarted, and a heartbeat
+ * is sent the others
+ *
+ * It is woken up when the milter goes idle, when there are no free servers
+ * available and once every readTimeout-1 seconds
+ *
+ * TODO: reload the whiteList file if it's been changed
+ *
+ * TODO: localSocket support
+ */
+static void *
+watchdog(void *a)
+{
+	static pthread_mutex_t watchdog_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+	while(!quitting) {
+		unsigned int i;
+		struct timespec ts;
+		struct timeval tp;
+		struct session *session;
+
+		gettimeofday(&tp, NULL);
+
+		ts.tv_sec = tp.tv_sec + freshclam_monitor;
+		ts.tv_nsec = tp.tv_usec * 1000;
+		cli_dbgmsg("watchdog sleeps\n");
+		pthread_mutex_lock(&watchdog_mutex);
+		/*
+		 * Sometimes this returns EPIPE which isn't listed as a
+		 * return value in the Linux man page for pthread_cond_timedwait
+		 * so I'm not sure why it happens
+		 */
+		switch(pthread_cond_timedwait(&watchdog_cond, &watchdog_mutex, &ts)) {
+			case ETIMEDOUT:
+			case 0:
+				break;
+			default:
+				perror("pthread_cond_timedwait");
+		}
+		pthread_mutex_unlock(&watchdog_mutex);
+
+		cli_dbgmsg("watchdog wakes\n");
+
+		if(check_and_reload_database() != 0) {
+			if(cl_error != SMFIS_ACCEPT) {
+				smfi_stop();
+				return NULL;
+			}
+			logg(_("!No emails will be scanned"));
+		}
+
+		i = 0;
+		session = sessions;
+		pthread_mutex_lock(&sstatus_mutex);
+		for(; i < max_children; i++, session++) {
+			const int sock = session->sock;
+
+			/*
+			 * Check all free sessions are still usable
+			 * This could take some time with many free
+			 * sessions to slow remote servers, so only do this
+			 * when the system is quiet (not 100% accurate when
+			 * determining this since n_children isn't locked but
+			 * that doesn't really matter)
+			 */
+			cli_dbgmsg("watchdog: check server %d\n", i);
+			if((n_children == 0) &&
+			   (session->status == CMDSOCKET_FREE) &&
+			   (clamav_versions != NULL)) {
+				if(send(sock, "VERSION\n", 8, 0) == 8) {
+					char buf[81];
+					const int nbytes = clamd_recv(sock, buf, sizeof(buf) - 1);
+
+					if(nbytes <= 0)
+						session->status = CMDSOCKET_DOWN;
+					else {
+						buf[nbytes] = '\0';
+						if(strncmp(buf, "ClamAV ", 7) == 0) {
+							/* Remove the trailing new line from the reply */
+							char *ptr;
+
+							if((ptr = strchr(buf, '\n')) != NULL)
+								*ptr = '\0';
+							pthread_mutex_lock(&version_mutex);
+							if(clamav_versions[i] == NULL)
+								clamav_versions[i] = cli_strdup(buf);
+							else if(strcmp(buf, clamav_versions[i]) != 0) {
+								logg("New version received for server %d: '%s'\n", i, buf);
+								free(clamav_versions[i]);
+								clamav_versions[i] = cli_strdup(buf);
+							}
+							pthread_mutex_unlock(&version_mutex);
+						} else {
+							cli_warnmsg("watchdog: expected \"ClamAV\", got \"%s\"\n", buf);
+							session->status = CMDSOCKET_DOWN;
+						}
+					}
+				} else {
+					perror("send");
+					session->status = CMDSOCKET_DOWN;
+				}
+
+				if(session->status == CMDSOCKET_DOWN)
+					cli_warnmsg("Session %d has gone down\n", i);
+			}
+			/*
+			 * Reset all all dead sessions
+			 */
+			if(session->status == CMDSOCKET_DOWN) {
+				/*
+				 * The END command probably won't get through,
+				 * but let's give it a go anyway
+				 */
+				if(sock >= 0) {
+					send(sock, "END\n", 4, 0);
+					close(sock);
+				}
+
+				cli_dbgmsg("Trying to restart session %d\n", i);
+				if(createSession(i) == 0) {
+					session->status = CMDSOCKET_FREE;
+					cli_warnmsg("Session %d restarted OK\n", i);
+				}
+			}
+		}
+		for(i = 0; i < max_children; i++)
+			if(sessions[i].status != CMDSOCKET_DOWN)
+				break;
+
+		if(i == max_children)
+			clamdIsDown();
+		pthread_mutex_unlock(&sstatus_mutex);
+
+		/* Garbage collect IP addresses no longer blacklisted */
+		if(blacklist) {
+			pthread_mutex_lock(&blacklist_mutex);
+			tableIterate(blacklist, timeoutBlacklist, NULL);
+			pthread_mutex_unlock(&blacklist_mutex);
+		}
+	}
+	cli_dbgmsg("watchdog quits\n");
+	return NULL;
+}
+#else	/*!SESSION*/
+/*
+ * Reload the database from time to time, when using the internal scanner
+ *
+ * TODO: reload the whiteList file if it's been changed
+ */
+/*ARGSUSED*/
+static void *
+watchdog(void *a)
+{
+	static pthread_mutex_t watchdog_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+	if((!blacklist_time) && external)
+		return NULL;	/* no need for this thread */
+
+	while(!quitting) {
+		struct timespec ts;
+		struct timeval tp;
+
+		gettimeofday(&tp, NULL);
+
+		ts.tv_sec = tp.tv_sec + freshclam_monitor;
+		ts.tv_nsec = tp.tv_usec * 1000;
+		cli_dbgmsg("watchdog sleeps\n");
+
+		pthread_mutex_lock(&watchdog_mutex);
+		/*
+		 * Sometimes this returns EPIPE which isn't listed as a
+		 * return value in the Linux man page for pthread_cond_timedwait
+		 * so I'm not sure why it happens
+		 */
+		switch(pthread_cond_timedwait(&watchdog_cond, &watchdog_mutex, &ts)) {
+			case ETIMEDOUT:
+			case 0:
+				break;
+			default:
+				perror("pthread_cond_timedwait");
+		}
+		pthread_mutex_unlock(&watchdog_mutex);
+		cli_dbgmsg("watchdog wakes\n");
+
+		/*
+		 * TODO: sanity check that if n_children == 0, that
+		 * root->refcount == 0. Unfortunatly root->refcount isn't
+		 * thread-safe, since it's governed by a mutex that we can't
+		 * see, and there's no access to it via an approved method
+		 */
+		if(check_and_reload_database() != 0) {
+			if(cl_error != SMFIS_ACCEPT) {
+				smfi_stop();
+				return NULL;
+			}
+			logg(_("!No emails will be scanned"));
+		}
+		/* Garbage collect IP addresses no longer blacklisted */
+		if(blacklist) {
+			pthread_mutex_lock(&blacklist_mutex);
+			tableIterate(blacklist, timeoutBlacklist, NULL);
+			pthread_mutex_unlock(&blacklist_mutex);
+		}
+	}
+	cli_dbgmsg("watchdog quits\n");
+	return NULL;
+}
+#endif
+
+/*
+ * Check to see if the database needs to be reloaded
+ *	Return 0 for success
+ */
+static int
+check_and_reload_database(void)
+{
+	int rc;
+
+	if(external)
+		return 0;
+
+	if(reload) {
+		rc = 1;
+		reload = 0;
+	} else
+		rc = cl_statchkdir(&dbstat);
+
+	switch(rc) {
+		case 1:
+			logg("^Database has changed, loading updated database\n");
+			cl_statfree(&dbstat);
+			rc = loadDatabase();
+			if(rc != 0) {
+				logg("!Failed to load updated database\n");
+				return rc;
+			}
+			break;
+		case 0:
+			logg("*Database has not changed\n");
+			break;
+		default:
+			logg("Database error %d - %s is stopping\n",
+				rc, progname);
+			return 1;
+	}
+	return 0;	/* all OK */
+}
+
+static void
+timeoutBlacklist(char *ip_address, int time_of_blacklist, void *v)
+{
+	if(time_of_blacklist == 0)	/* Must not blacklist this IP address */
+		return;
+	if((time((time_t *)0) - time_of_blacklist) > blacklist_time)
+		tableRemove(blacklist, ip_address);
+}
+
+static void
+quit(void)
+{
+	quitting++;
+
+#ifdef	SESSION
+	pthread_mutex_lock(&version_mutex);
+#endif
+	logg(_("Stopping %s\n"), clamav_version);
+#ifdef	SESSION
+	pthread_mutex_unlock(&version_mutex);
+#endif
+
+	if(!external) {
+		pthread_mutex_lock(&engine_mutex);
+		if(engine)
+			cl_engine_free(engine);
+		pthread_mutex_unlock(&engine_mutex);
+	} else {
+#ifdef	SESSION
+		int i = 0;
+		struct session *session = sessions;
+
+		pthread_mutex_lock(&sstatus_mutex);
+		for(; i < ((localSocket != NULL) ? 1 : (int)max_children); i++) {
+			/*
+			 * Check all free sessions are still usable
+			 * This could take some time with many free
+			 * sessions to slow remote servers, so only do this
+			 * when the system is quiet (not 100% accurate when
+			 * determining this since n_children isn't locked but
+			 * that doesn't really matter)
+			 */
+			cli_dbgmsg("quit: close server %d\n", i);
+			if(session->status == CMDSOCKET_FREE) {
+				const int sock = session->sock;
+
+				send(sock, "END\n", 4, 0);
+				shutdown(sock, SHUT_WR);
+				session->status = CMDSOCKET_DOWN;
+				pthread_mutex_unlock(&sstatus_mutex);
+				close(sock);
+				pthread_mutex_lock(&sstatus_mutex);
+			}
+			session++;
+		}
+		pthread_mutex_unlock(&sstatus_mutex);
+#endif
+	}
+
+	if(tmpdir)
+		if(rmdir(tmpdir) < 0)
+			perror(tmpdir);
+
+	broadcast(_("Stopping clamav-milter"));
+
+	if(pidfile)
+		if(unlink(pidfile) < 0)
+			perror(pidfile);
+
+	logg_close();
+}
+
+static void
+broadcast(const char *mess)
+{
+	struct sockaddr_in s;
+
+	if(broadcastSock < 0)
+		return;
+
+	memset(&s, '\0', sizeof(struct sockaddr_in));
+	s.sin_family = AF_INET;
+	s.sin_port = (in_port_t)htons(tcpSocket ? tcpSocket : 3310);
+	s.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+
+	cli_dbgmsg("broadcast %s to %d\n", mess, broadcastSock);
+	if(sendto(broadcastSock, mess, strlen(mess), 0, (struct sockaddr *)&s, sizeof(struct sockaddr_in)) < 0)
+		perror("sendto");
+}
+
+/*
+ * Load a new database into the internal scanner
+ */
+static int
+loadDatabase(void)
+{
+	int ret;
+	unsigned int signatures, dboptions;
+	char *daily;
+	struct cl_cvd *d;
+	const struct cfgstruct *cpt;
+	static const char *dbdir;
+
+	assert(!external);
+
+	if(dbdir == NULL) {
+		/*
+		 * First time through, find out in which directory the signature
+		 * databases are
+		 */
+		if((cpt = cfgopt(copt, "DatabaseDirectory")) && cpt->enabled)
+			dbdir = cpt->strarg;
+		else
+			dbdir = cl_retdbdir();
+	}
+
+	daily = cli_malloc(strlen(dbdir) + 11);
+	sprintf(daily, "%s/daily.cvd", dbdir);
+	if(access(daily, R_OK) < 0)
+		sprintf(daily, "%s/daily.cld", dbdir);
+
+
+	cli_dbgmsg("loadDatabase: check %s for updates\n", daily);
+
+	d = cl_cvdhead(daily);
+
+	if(d) {
+		char *ptr;
+		time_t t = d->stime;
+		char buf[26];
+
+		snprintf(clamav_version, VERSION_LENGTH,
+			"ClamAV %s/%u/%s", get_version(), d->version,
+			cli_ctime(&t, buf, sizeof(buf)));
+
+		/* Remove ctime's trailing \n */
+		if((ptr = strchr(clamav_version, '\n')) != NULL)
+			*ptr = '\0';
+
+		cl_cvdfree(d);
+	} else
+		snprintf(clamav_version, VERSION_LENGTH,
+			"ClamAV version %s, clamav-milter version %s",
+			cl_retver(), get_version());
+
+	free(daily);
+
+#ifdef	SESSION
+	pthread_mutex_lock(&version_mutex);
+	if(clamav_versions == NULL) {
+		clamav_versions = (char **)cli_malloc(sizeof(char *));
+		if(clamav_versions == NULL) {
+			pthread_mutex_unlock(&version_mutex);
+			return -1;
+		}
+		clamav_version = cli_malloc(VERSION_LENGTH + 1);
+		if(clamav_version == NULL) {
+			free(clamav_versions);
+			clamav_versions = NULL;
+			pthread_mutex_unlock(&version_mutex);
+			return -1;
+		}
+	}
+	pthread_mutex_unlock(&version_mutex);
+#endif
+	signatures = 0;
+	pthread_mutex_lock(&engine_mutex);
+	if(engine) cl_engine_free(engine);
+	engine = cl_engine_new();
+	if (!engine) {
+		logg("!Can't initialize antivirus engine\n");
+		pthread_mutex_unlock(&engine_mutex);
+		return -1;
+	}
+	if(!cfgopt(copt, "PhishingSignatures")->enabled) {
+		logg("Not loading phishing signatures.\n");
+		dboptions = 0;
+	} else
+		dboptions = CL_DB_PHISHING;
+	if((ret = cl_engine_set(engine, CL_ENGINE_MAX_SCANSIZE, &maxscansize))) {
+		logg("!cli_engine_set(CL_ENGINE_MAX_SCANSIZE) failed: %s\n", cl_strerror(ret));
+		cl_engine_free(engine);
+		pthread_mutex_unlock(&engine_mutex);
+		return -1;
+	}
+	if((ret = cl_engine_set(engine, CL_ENGINE_MAX_FILESIZE, &maxfilesize))) {
+		logg("!cli_engine_set(CL_ENGINE_MAX_FILESIZE) failed: %s\n", cl_strerror(ret));
+		cl_engine_free(engine);
+		pthread_mutex_unlock(&engine_mutex);
+		return -1;
+	}
+	ret = cl_load(dbdir, engine, &signatures, dboptions);
+	if(ret != CL_SUCCESS) {
+		logg("!%s\n", cl_strerror(ret));
+		cl_engine_free(engine);
+		pthread_mutex_unlock(&engine_mutex);
+		return -1;
+	}
+	ret = cl_engine_compile(engine);
+	if(ret != CL_SUCCESS) {
+		logg("!Database initialization error: %s\n", cl_strerror(ret));
+		cl_engine_free(engine);
+		pthread_mutex_unlock(&engine_mutex);
+		return -1;
+	}
+	pthread_mutex_unlock(&engine_mutex);
+#ifdef	SESSION
+	pthread_mutex_lock(&version_mutex);
+#endif
+	logg( _("Loaded %s\n"), clamav_version);
+#ifdef	SESSION
+	pthread_mutex_unlock(&version_mutex);
+#endif
+	logg(_("ClamAV: Protecting against %u viruses\n"), signatures);
+	logg("#Database correctly (re)loaded (%u viruses)\n");
+	return cl_statinidir(dbdir, &dbstat);
+}
+
+static void
+sigsegv(int sig)
+{
+	signal(SIGSEGV, SIG_DFL);
+
+#ifdef HAVE_BACKTRACE
+	print_trace();
+#endif
+
+	logg("!Segmentation fault :-( Bye.., notify bugs at clamav.net\n");
+
+	quitting++;
+	smfi_stop();
+}
+
+extern FILE *logg_fd;
+static void
+sigusr1(int sig)
+{
+
+	signal(SIGUSR1, sigusr1);
+
+	if(!(cfgopt(copt, "LogFile"))->enabled)
+		return;
+
+	logg("SIGUSR1 caught: re-opening log file\n");
+	logg_close();
+	logg("*Log file re-opened\n");
+	dup2(fileno(logg_fd), 2);
+}
+
+static void
+sigusr2(int sig)
+{
+	signal(SIGUSR2, sigusr2);
+
+	logg("^SIGUSR2 caught: scheduling database reload\n");
+	reload++;
+}
+
+#ifdef HAVE_BACKTRACE
+static void
+print_trace(void)
+{
+	void *array[BACKTRACE_SIZE];
+	size_t size, i;
+	char **strings;
+	pid_t pid = getpid();
+
+	size = backtrace(array, BACKTRACE_SIZE);
+	strings = backtrace_symbols(array, size);
+
+	logg("*Backtrace of pid %d:\n", pid);
+
+	for(i = 0; i < size; i++)
+		logg("bt[%u]: %s", i, strings[i]);
+
+	/* TODO: dump the current email */
+
+	free(strings);
+}
+#endif
+
+/*
+ * Check that the correct port name has been given, i.e. that the
+ * input socket to clamav-milter from sendmail, is the same that
+ * sendmail has been configured to use as it's output socket
+ * Return:	<0 invalid
+ *		=0 valid
+ *		>0 unknown
+ *
+ * You wouldn't believe the amount of time I used to waste chasing bug reports
+ *	from people who's sendmail.cf didn't tally with the arguments given to
+ *	clamav-milter before I put this check in, which is why bug 726 must
+ *	never be acted upon.
+ *
+ * FIXME: return different codes for "the value is wrong" and "sendmail.cf"
+ *	hasn't been set up, though that's not so easy to work out.
+ */
+static int
+verifyIncomingSocketName(const char *sockName)
+{
+#if HAVE_MMAP
+	int fd, ret;
+	char *ptr;
+	size_t size;
+	struct stat statb;
+
+	if(strncmp(sockName, "inet:", 5) == 0)
+		/*
+		 * clamav-milter is running on a different machine from sendmail
+		 */
+		return 1;
+
+	if(sendmailCF)
+		fd = open(sendmailCF, O_RDONLY);
+	else {
+		fd = open("/etc/mail/sendmail.cf", O_RDONLY);
+		if(fd < 0)
+			fd = open("/etc/sendmail.cf", O_RDONLY);
+	}
+
+	if(fd < 0)
+		return 1;
+
+	if(fstat(fd, &statb) < 0) {
+		close(fd);
+		return 1;
+	}
+
+	size = statb.st_size;
+
+	if(size == 0) {
+		close(fd);
+		return -1;
+	}
+
+	ptr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+	if(ptr == MAP_FAILED) {
+		perror("mmap");
+		close(fd);
+		return -1;
+	}
+
+	ret = (cli_memstr(ptr, size, sockName, strlen(sockName)) != NULL) ? 1 : -1;
+
+	munmap(ptr, size);
+	close(fd);
+
+	return ret;
+#else	/*!HAVE_MMAP*/
+	return 1;
+#endif
+}
+
+/*
+ * If the given email address is whitelisted don't scan emails to them,
+ *	the addresses are in angle brackets e.g. <foo at bar.com>.
+ *
+ * TODO: Allow regular expressions in the addresses
+ * TODO: Syntax check the contents of the files
+ * TODO: Allow emails of the form "name <address>"
+ * TODO: Allow emails not of the form "<address>", i.e. no angle brackets
+ * TODO: Assume that if a '@' is missing from the address, that all emails
+ *	to that domain are to be whitelisted
+ */
+static int
+isWhitelisted(const char *emailaddress, int to)
+{
+	static table_t *to_whitelist, *from_whitelist;	/* never freed */
+	table_t *table;
+
+	logg("*isWhitelisted %s\n", emailaddress);
+
+	/*
+	 * Don't scan messages to the quarantine email address
+	 */
+	if(quarantine && (strcasecmp(quarantine, emailaddress) == 0))
+		return 1;
+
+	if((to_whitelist == NULL) && whitelistFile) {
+		FILE *fin;
+		char buf[BUFSIZ + 1];
+
+		fin = fopen(whitelistFile, "r");
+
+		if(fin == NULL) {
+			perror(whitelistFile);
+			logg(_("!Can't open whitelist file %s"), whitelistFile);
+			return 0;
+		}
+		to_whitelist = tableCreate();
+		from_whitelist = tableCreate();
+
+		if((to_whitelist == NULL) || (from_whitelist == NULL)) {
+			logg(_("!Can't create whitelist table"));
+			if(to_whitelist) {
+				tableDestroy(to_whitelist);
+				to_whitelist = NULL;
+			} else {
+				tableDestroy(from_whitelist);
+				from_whitelist = NULL;
+			}
+			fclose(fin);
+			return 0;
+		}
+
+		while(fgets(buf, sizeof(buf), fin) != NULL) {
+			const char *ptr;
+
+			/* comment line? */
+			switch(buf[0]) {
+				case '#':
+				case '/':
+				case ':':
+					continue;
+			}
+			if(cli_chomp(buf) > 0) {
+				if((ptr = strchr(buf, ':')) != NULL) {
+					do
+						ptr++;
+					while(*ptr && isspace(*ptr));
+
+					if(*ptr == '\0') {
+						logg("*Ignoring bad line '%s'\n",
+							buf);
+						continue;
+					}
+				} else
+					ptr = buf;
+
+				if(strncasecmp(buf, "From:", 5) == 0)
+					table = from_whitelist;
+				else
+					table = to_whitelist;
+
+				(void)tableInsert(table, ptr, 1);
+			}
+		}
+		fclose(fin);
+	}
+	table = (to) ? to_whitelist : from_whitelist;
+
+	if(table && (tableFind(table, emailaddress) == 1))
+		/*
+		 * This recipient is on the whitelist
+		 */
+		return 1;
+
+	return 0;
+}
+
+/*
+ * Blacklist IP addresses that send malware. Often in the phishing world, one
+ * phish is quickly followed by another. IP addresses are blacklisted for one
+ * minute. We can't blacklist for longer since DHCP means we could hit innocent
+ * parties, and in theory malware could go through a smart host and affect
+ * innocent parties
+ *
+ * Note that sites which can't be blacklisted will have their timestamp set
+ * to 0, since that can never be less than blacklist_time seconds from now
+ */
+static int
+isBlacklisted(const char *ip_address)
+{
+	time_t t;
+
+	if(blacklist_time == 0)
+		/* Blacklisting not being used */
+		return 0;
+
+	logg("*isBlacklisted %s\n", ip_address);
+
+	if(isLocal(ip_address))
+		return 0;
+
+	pthread_mutex_lock(&blacklist_mutex);
+	if(blacklist == NULL) {
+		blacklist = tableCreate();
+
+		pthread_mutex_unlock(&blacklist_mutex);
+
+		if(blacklist == NULL)
+			logg(_("!Can't create blacklist table"));
+		return 0;
+	}
+	t = tableFind(blacklist, ip_address);
+	pthread_mutex_unlock(&blacklist_mutex);
+
+	if(t == (time_t)-1)
+		/* IP address is not blacklisted */
+		return 0;
+
+	if(t == (time_t)0)
+		/* IP cannot be blacklisted */
+		return 0;
+
+	if((time((time_t *)0) - t) <= blacklist_time)
+		return 1;
+
+	/* timedout: remove the IP from the blacklist */
+	pthread_mutex_lock(&blacklist_mutex);
+	tableRemove(blacklist, ip_address);
+	pthread_mutex_unlock(&blacklist_mutex);
+
+	return 0;
+}
+
+#ifdef	HAVE_RESOLV_H
+/*
+ * Determine our MX peers, they must never be blacklisted
+ * See RFC1034 for the definition of the record formats
+ *
+ * This is only ever called once, which is wrong, but the overheard of calling
+ * this from the watchdog isn't worth it
+ */
+static table_t *
+mx(const char *host, table_t *t)
+{
+	u_char *p, *end;
+	const HEADER *hp;
+	int len, i;
+	union {
+		HEADER h;
+		u_char u[PACKETSZ];
+	} q;
+	char buf[BUFSIZ];
+
+	if(t == NULL) {
+		t = tableCreate();
+
+		if(t == NULL)
+			return NULL;
+	}
+
+	len = safe_res_query(host, C_IN, T_MX, (u_char *)&q, sizeof(q));
+	if(len < 0)
+		return t;	/* Host has no MX records */
+
+	if((unsigned int)len > sizeof(q))
+		return t;
+
+	hp = &(q.h);
+	p = q.u + HFIXEDSZ;
+	end = q.u + len;
+
+	for(i = ntohs(hp->qdcount); i--; p += len + QFIXEDSZ)
+		if((len = dn_skipname(p, end)) < 0)
+			return t;
+
+	i = ntohs(hp->ancount);
+
+	while((--i >= 0) && (p < end)) {
+		in_addr_t addr;
+		u_short type, pref;
+		u_long ttl;	/* unused */
+
+		if((len = dn_expand(q.u, end, p, buf, sizeof(buf) - 1)) < 0)
+			break;
+		p += len;
+		GETSHORT(type, p);
+		p += INT16SZ;
+		GETLONG(ttl, p);
+		GETSHORT(len, p);
+		if(type != T_MX) {
+			p += len;
+			continue;
+		}
+		GETSHORT(pref, p);
+		if((len = dn_expand(q.u, end, p, buf, sizeof(buf) - 1)) < 0)
+			break;
+		p += len;
+		addr = inet_addr(buf);
+#ifdef	INADDR_NONE
+		if(addr != INADDR_NONE) {
+#else
+		if(addr != (in_addr_t)-1) {
+#endif
+			(void)tableInsert(t, buf, 0);
+		} else
+			t = resolve(buf, t);
+	}
+	return t;
+}
+
+/*
+ * If the MX record points to a name, we need to resolve that name. This routine
+ * does that
+ */
+static table_t *
+resolve(const char *host, table_t *t)
+{
+	u_char *p, *end;
+	const HEADER *hp;
+	int len, i;
+	union {
+		HEADER h;
+		u_char u[PACKETSZ];
+	} q;
+	char buf[BUFSIZ];
+
+	if((host == NULL) || (*host == '\0'))
+		return t;
+
+	len = safe_res_query(host, C_IN, T_A, (u_char *)&q, sizeof(q));
+	if(len < 0)
+		return t;	/* Host has no A records */
+
+	if((unsigned int)len > sizeof(q))
+		return t;
+
+	hp = &(q.h);
+	p = q.u + HFIXEDSZ;
+	end = q.u + len;
+
+	for(i = ntohs(hp->qdcount); i--; p += len + QFIXEDSZ)
+		if((len = dn_skipname(p, end)) < 0)
+			return t;
+
+	i = ntohs(hp->ancount);
+
+	while((--i >= 0) && (p < end)) {
+		u_short type;
+		u_long ttl;
+		const char *ip;
+		struct in_addr addr;
+
+		if((len = dn_expand(q.u, end, p, buf, sizeof(buf) - 1)) < 0)
+			return t;
+		p += len;
+		GETSHORT(type, p);
+		p += INT16SZ;
+		GETLONG(ttl, p);	/* unused */
+		GETSHORT(len, p);
+		if(type != T_A) {
+			p += len;
+			continue;
+		}
+		memcpy(&addr, p, sizeof(struct in_addr));
+		p += 4;	/* Should check len == 4 */
+		ip = inet_ntoa(addr);
+		if(ip) {
+			if(t == NULL) {
+				t = tableCreate();
+
+				if(t == NULL)
+					return NULL;
+			}
+			(void)tableInsert(t, ip, 0);
+		}
+	}
+	return t;
+}
+
+/*
+ * Validate SPF records to help to stop Phish false positives
+ * http://www.openspf.org/SPF_Record_Syntax
+ *
+ * Currently only handles ip4, a and mx fields in the DNS record
+ * Having said that, this is NOT a replacement for spf-milter, it is NOT
+ *	an SPF system, we ONLY use SPF records to reduce phish false positives
+ * TODO: IPv6?
+ * TODO: cache queries?
+ *
+ * INPUT: prevhosts, a list of hosts already searched: stops include loops
+ *	e.g. mercado.com includes medrcadosw.com which includes mercado.com,
+ *	causing a loop
+ * Return 1 if SPF says this email is from a legitimate source
+ *	0 for fail or unknown
+ */
+static int
+spf(struct privdata *privdata, table_t *prevhosts)
+{
+	char *host, *ptr;
+	u_char *p, *end;
+	const HEADER *hp;
+	int len, i;
+	union {
+		HEADER h;
+		u_char u[PACKETSZ];
+	} q;
+	char buf[BUFSIZ];
+
+	if(privdata->spf_ok)
+		return 1;
+	if(privdata->ip[0] == '\0')
+		return 0;
+	if(strcmp(privdata->ip, "127.0.0.1") == 0) {
+		/* Loopback always pass SPF */
+		privdata->spf_ok = 1;
+		return 1;
+	}
+	if(isLocal(privdata->ip)) {
+		/* Local addresses always pass SPF */
+		privdata->spf_ok = 1;
+		return 1;
+	}
+
+	if(privdata->from == NULL)
+		return 0;
+	if((host = strrchr(privdata->from, '@')) == NULL)
+		return 0;
+
+	host = cli_strdup(++host);
+
+	if(host == NULL)
+		return 0;
+
+	ptr = strchr(host, '>');
+
+	if(ptr)
+		*ptr = '\0';
+
+	logg("*SPF query '%s'\n", host);
+	len = safe_res_query(host, C_IN, T_TXT, (u_char *)&q, sizeof(q));
+	if(len < 0) {
+		free(host);
+		return 0;	/* Host has no TXT records */
+	}
+
+	if((unsigned int)len > sizeof(q)) {
+		free(host);
+		return 0;
+	}
+
+	hp = &(q.h);
+	p = q.u + HFIXEDSZ;
+	end = q.u + len;
+
+	for(i = ntohs(hp->qdcount); i--; p += len + QFIXEDSZ)
+		if((len = dn_skipname(p, end)) < 0) {
+			free(host);
+			return 0;
+		}
+
+	i = ntohs(hp->ancount);
+
+	while((--i >= 0) && (p < end) && !privdata->spf_ok) {
+		u_short type;
+		u_long ttl;
+		char txt[BUFSIZ];
+
+		if((len = dn_expand(q.u, end, p, buf, sizeof(buf) - 1)) < 0) {
+			free(host);
+			return 0;
+		}
+		p += len;
+		GETSHORT(type, p);
+		p += INT16SZ;
+		GETLONG(ttl, p);	/* unused */
+		GETSHORT(len, p);
+		if(type != T_TXT) {
+			p += len;
+			continue;
+		}
+		strncpy(txt, (const char *)&p[1], sizeof(txt) - 1);
+		txt[sizeof(txt)-1]='\0';
+		txt[len - 1] = '\0';
+		if((strncmp(txt, "v=spf1 ", 7) == 0) || (strncmp(txt, "spf2.0/pra ", 11) == 0)) {
+			int j;
+			char *record;
+			struct in_addr remote_ip;	/* IP connecting to us */
+
+			logg("*%s(%s): SPF record %s\n",
+				host, privdata->ip, txt);
+#ifdef HAVE_INET_NTOP
+			/* IPv4 address ? */
+			if(inet_pton(AF_INET, privdata->ip, &remote_ip) <= 0) {
+				p += len;
+				continue;
+			}
+#else
+			if(inet_aton(privdata->ip, &remote_ip) == 0) {
+				p += len;
+				continue;
+			}
+#endif
+
+			j = 1;	/* strtok 0 would give the v= part */
+			while((record = cli_strtok(txt, j++, " ")) != NULL) {
+				if(strncmp(record, "ip4:", 4) == 0) {
+					int preflen;
+					char *ip, *pref;
+					uint32_t mask;
+					struct in_addr spf_range;	/* acceptable range of IPs */
+
+					ip = &record[4];
+
+					pref = strchr(ip, '/');
+					preflen = 32;
+					if(pref) {
+						*pref++ = '\0';
+						if(*pref)
+							preflen = atoi(pref);
+					}
+
+#ifdef HAVE_INET_NTOP
+					/* IPv4 address ? */
+					if(inet_pton(AF_INET, ip, &spf_range) <= 0) {
+						free(record);
+						continue;
+					}
+#else
+					if(inet_aton(ip, &spf_range) == 0) {
+						free(record);
+						continue;
+					}
+#endif
+					mask = MAKEMASK(preflen);
+					if((ntohl(remote_ip.s_addr) & mask) == (ntohl(spf_range.s_addr) & mask)) {
+						if(privdata->subject)
+							logg("#SPF ip4 pass (%s) %s is valid for %s\n",
+								privdata->subject, ip, host);
+						else
+							logg("#SPF ip4 pass %s is valid for %s\n", ip, host);
+						privdata->spf_ok = 1;
+					}
+				} else if(strcmp(record, "mx") == 0) {
+					table_t *t = mx(host, NULL);
+
+					if(t) {
+						tableIterate(t, spf_ip,
+							(void *)privdata);
+						tableDestroy(t);
+					}
+				} else if(strcmp(record, "a") == 0) {
+					table_t *t = resolve(host, NULL);
+
+					if(t) {
+						tableIterate(t, spf_ip,
+							(void *)privdata);
+						tableDestroy(t);
+					}
+				} else if(strncmp(record, "a:", 2) == 0) {
+					const char *ahost = &record[2];
+
+					if(*ahost && (strcmp(ahost, host) != 0)) {
+						table_t *t = resolve(ahost, NULL);
+
+						if(t) {
+							tableIterate(t, spf_ip,
+								(void *)privdata);
+							tableDestroy(t);
+						}
+					}
+				} else if(strncmp(record, "mx:", 3) == 0) {
+					const char *mxhost = &record[3];
+
+					if(*mxhost && (strcmp(mxhost, host) != 0)) {
+						table_t *t = mx(mxhost, NULL);
+
+						if(t) {
+							tableIterate(t, spf_ip,
+								(void *)privdata);
+							tableDestroy(t);
+						}
+					}
+				} else if(strncmp(record, "include:", 8) == 0) {
+					const char *inchost = &record[8];
+
+					/*
+					 * Ensure we haven't already looked at
+					 *	the host that's to be included
+					 */
+					if(*inchost &&
+					   (strcmp(inchost, host) != 0) &&
+					   (tableFind(prevhosts, inchost) == -1)) {
+						char *real_from = privdata->from;
+						privdata->from = cli_malloc(strlen(inchost) + 3);
+						sprintf(privdata->from, "n@%s", inchost);
+						tableInsert(prevhosts, host, 0);
+						spf(privdata, prevhosts);
+						free(privdata->from);
+						privdata->from = real_from;
+					}
+				}
+				free(record);
+				if(privdata->spf_ok)
+					break;
+			}
+		}
+		p += len;
+	}
+	free(host);
+
+	return privdata->spf_ok;
+}
+
+static void
+spf_ip(char *ip, int zero, void *v)
+{
+	struct privdata *privdata = (struct privdata *)v;
+
+	if(strcmp(ip, privdata->ip) == 0) {
+		if(privdata->subject)
+			logg("#SPF mx/a pass (%s) %s\n", privdata->subject, ip);
+		else
+			logg("#SPF mx/a pass %s\n", ip);
+		privdata->spf_ok = 1;
+	}
+}
+
+#else	/*!HAVE_RESOLV_H */
+static table_t *
+mx(const char *host, table_t *t)
+{
+	logg(_("^MX peers will not be immune from being blacklisted"));
+
+	if(blacklist == NULL)
+		blacklist = tableCreate();
+	return NULL;
+}
+#endif	/* HAVE_RESOLV_H */
+
+static sfsistat
+black_hole(const struct privdata *privdata)
+{
+	int must_scan;
+	char **to;
+
+	to = privdata->to;
+	must_scan = (*to) ? 0 : 1;
+
+	for(; *to; to++) {
+		pid_t pid, w;
+		int pv[2], status;
+		FILE *sendmail;
+		char buf[BUFSIZ];
+
+		logg("*Calling \"%s -bv %s\"\n", SENDMAIL_BIN, *to);
+
+		if(pipe(pv) < 0) {
+			perror("pipe");
+			logg(_("!Can't create pipe\n"));
+			must_scan = 1;
+			break;
+		}
+		pid = fork();
+		if(pid == 0) {
+			close(1);
+			close(pv[0]);
+			dup2(pv[1], 1);
+			close(pv[1]);
+
+			/*
+			 * Avoid calling popen() since *to isn't trusted
+			 */
+			execl(SENDMAIL_BIN, "sendmail", "-bv", *to, NULL);
+			perror(SENDMAIL_BIN);
+			logg("Can't execl %s\n", SENDMAIL_BIN);
+			_exit(errno ? errno : 1);
+		}
+		if(pid == -1) {
+			perror("fork");
+			logg(_("!Can't fork\n"));
+			close(pv[0]);
+			close(pv[1]);
+			must_scan = 1;
+			break;
+		}
+		close(pv[1]);
+		sendmail = fdopen(pv[0], "r");
+
+		if(sendmail == NULL) {
+			logg("fdopen failed\n");
+			close(pv[0]);
+			must_scan = 1;
+			break;
+		}
+
+		while(fgets(buf, sizeof(buf), sendmail) != NULL) {
+			if(cli_chomp(buf) == 0)
+				continue;
+
+			logg("*sendmail output: %s\n", buf);
+
+			if(strstr(buf, "... deliverable: mailer ")) {
+				const char *p = strstr(buf, ", user ");
+
+				if(strcmp(&p[7], "/dev/null") != 0) {
+					must_scan = 1;
+					break;
+				}
+			}
+		}
+		fclose(sendmail);
+
+		status = -1;
+		do
+			w = wait(&status);
+		while((w != pid) && (w != -1));
+
+		if(w == -1)
+			status = -1;
+		else
+			status = WEXITSTATUS(status);
+
+		switch(status) {
+			case EX_NOUSER:
+			case EX_OK:
+				break;
+			default:
+				logg(_("^Can't execute '%s' to expand '%s' (error %d)\n"),
+					SENDMAIL_BIN, *to, WEXITSTATUS(status));
+				must_scan = 1;
+		}
+		if(must_scan)
+			break;
+	}
+	if(!must_scan) {
+		/* All recipients map to /dev/null */
+		to = privdata->to;
+		if(*to)
+			logg("Discarded, since all recipients (e.g. \"%s\") are /dev/null\n", *to);
+		else
+			logg("Discarded, since all recipients are /dev/null\n");
+		return SMFIS_DISCARD;
+	}
+	return SMFIS_CONTINUE;
+}
+
+/* See also libclamav/mbox.c */
+static int
+useful_header(const char *cmd)
+{
+	if(strcasecmp(cmd, "From") == 0)
+		return 1;
+	if(strcasecmp(cmd, "Received") == 0)
+		return 1;
+	if(strcasecmp(cmd, "Content-Type") == 0)
+		return 1;
+	if(strcasecmp(cmd, "Content-Transfer-Encoding") == 0)
+		return 1;
+	if(strcasecmp(cmd, "Content-Disposition") == 0)
+		return 1;
+	if(strcasecmp(cmd, "De") == 0)
+		return 1;
+
+	return 0;
+}
+
+static int
+increment_connexions(void)
+{
+	if(max_children > 0) {
+		int rc = 0;
+
+		pthread_mutex_lock(&n_children_mutex);
+
+		/*
+		 * Wait a while since sendmail doesn't like it if we
+		 * take too long replying. Effectively this means that
+		 * max_children is more of a hint than a rule
+		 */
+		if(n_children >= max_children) {
+			struct timespec timeout;
+			struct timeval now;
+			struct timezone tz;
+
+			logg((dont_wait) ?
+					_("hit max-children limit (%u >= %u)\n") :
+					_("hit max-children limit (%u >= %u): waiting for some to exit\n"),
+				n_children, max_children);
+
+			if(dont_wait) {
+				pthread_mutex_unlock(&n_children_mutex);
+				return 0;
+			}
+			/*
+			 * Wait for an amount of time for a child to go
+			 *
+			 * Use pthread_cond_timedwait rather than
+			 * pthread_cond_wait since the sendmail which
+			 * calls us will have a timeout that we don't
+			 * want to exceed, stops sendmail getting
+			 * fidgety.
+			 *
+			 * Patch from Damian Menscher
+			 * <menscher at uiuc.edu> to ensure it wakes up
+			 * when a child goes away
+			 */
+			gettimeofday(&now, &tz);
+			do {
+				logg(_("n_children %d: waiting %d seconds for some to exit\n"),
+					n_children, child_timeout);
+
+				if(child_timeout == 0) {
+					pthread_cond_wait(&n_children_cond, &n_children_mutex);
+					rc = 0;
+				} else {
+					timeout.tv_sec = now.tv_sec + child_timeout;
+					timeout.tv_nsec = 0;
+
+					rc = pthread_cond_timedwait(&n_children_cond, &n_children_mutex, &timeout);
+				}
+			} while((n_children >= max_children) && (rc != ETIMEDOUT));
+			logg(_("Finished waiting, n_children = %d\n"), n_children);
+		}
+		n_children++;
+
+		logg("*>n_children = %d\n", n_children);
+		pthread_mutex_unlock(&n_children_mutex);
+
+		if(child_timeout && (rc == ETIMEDOUT))
+			logg(_("Timeout waiting for a child to die\n"));
+	}
+
+	return 1;
+}
+
+static void
+decrement_connexions(void)
+{
+	if(max_children > 0) {
+		pthread_mutex_lock(&n_children_mutex);
+		logg("*decrement_connexions: n_children = %d\n", n_children);
+		/*
+		 * Deliberately errs on the side of broadcasting too many times
+		 */
+		if(n_children > 0)
+			if(--n_children == 0) {
+				logg("*%s is idle\n", progname);
+				if(pthread_cond_broadcast(&watchdog_cond) < 0)
+					perror("pthread_cond_broadcast");
+			}
+#ifdef	CL_DEBUG
+		logg("*pthread_cond_broadcast\n");
+#endif
+		if(pthread_cond_broadcast(&n_children_cond) < 0)
+			perror("pthread_cond_broadcast");
+		logg("*<n_children = %d\n", n_children);
+		pthread_mutex_unlock(&n_children_mutex);
+	}
+}
+
+static void
+dump_blacklist(char *key, int value, void *v)
+{
+	logg(_("Won't blacklist %s\n"), key);
+}
+
+/*
+ * Non-blocking connect, based on an idea by Everton da Silva Marques
+ *	 <everton.marques at gmail.com>
+ * FIXME: There are lots of copies of this code :-(
+ */
+static int
+nonblock_connect(int sock, const struct sockaddr_in *sin, const char *hostname)
+{
+	int select_failures;	/* Max. of unexpected select() failures */
+	int attempts;
+	struct timeval timeout;	/* When we should time out */
+	int numfd;		/* Highest fdset fd plus 1 */
+	long flags;
+
+	gettimeofday(&timeout, 0);	/* store when we started to connect */
+
+	if(hostname == NULL)
+		hostname = "clamav-milter";	/* It's only used in debug messages */
+
+#ifdef	F_GETFL
+	flags = fcntl(sock, F_GETFL, 0);
+
+	if(flags == -1L)
+		logg("^getfl: %s\n", strerror(errno));
+	else if(fcntl(sock, F_SETFL, (long)(flags | O_NONBLOCK)) < 0)
+		logg("^setfl: %s\n", strerror(errno));
+#else
+	flags = -1L;
+#endif
+	if(connect(sock, (const struct sockaddr *)sin, sizeof(struct sockaddr_in)) != 0)
+		switch(errno) {
+			case EALREADY:
+			case EINPROGRESS:
+				logg("*%s: connect: %s\n", hostname,
+					strerror(errno));
+				break; /* wait for connection */
+			case EISCONN:
+				return 0; /* connected */
+			default:
+				logg("^%s: connect: %s\n", hostname,
+					strerror(errno));
+#ifdef	F_SETFL
+				if(flags != -1L)
+					if(fcntl(sock, F_SETFL, flags))
+						logg("^f_setfl: %s\n", strerror(errno));
+#endif
+				return -1; /* failed */
+		}
+	else {
+#ifdef	F_SETFL
+		if(flags != -1L)
+			if(fcntl(sock, F_SETFL, flags))
+				logg("^f_setfl: %s\n", strerror(errno));
+#endif
+		return connect_error(sock, hostname);
+	}
+
+	numfd = (int)sock + 1;
+	select_failures = NONBLOCK_SELECT_MAX_FAILURES;
+	attempts = 1;
+	timeout.tv_sec += CONNECT_TIMEOUT;
+
+	for (;;) {
+		int n, t;
+		fd_set fds;
+		struct timeval now, waittime;
+
+		/* Force timeout if we ran out of time */
+		gettimeofday(&now, 0);
+		t = (now.tv_sec == timeout.tv_sec) ?
+			(now.tv_usec > timeout.tv_usec) :
+			(now.tv_sec > timeout.tv_sec);
+
+		if(t) {
+			logg("^%s: connect timeout (%d secs)\n",
+				hostname, CONNECT_TIMEOUT);
+			break;
+		}
+
+		/* Calculate how long to wait */
+		waittime.tv_sec = timeout.tv_sec - now.tv_sec;
+		waittime.tv_usec = timeout.tv_usec - now.tv_usec;
+		if(waittime.tv_usec < 0) {
+			waittime.tv_sec--;
+			waittime.tv_usec += 1000000;
+		}
+
+		/* Init fds with 'sock' as the only fd */
+		FD_ZERO(&fds);
+		FD_SET(sock, &fds);
+
+		n = select(numfd, 0, &fds, 0, &waittime);
+		if(n < 0) {
+			logg("^%s: select attempt %d %s\n",
+				hostname, select_failures, strerror(errno));
+			if(--select_failures >= 0)
+				continue; /* not timed-out, try again */
+			break; /* failed */
+		}
+
+		logg("*%s: select = %d\n", hostname, n);
+
+		if(n) {
+#ifdef	F_SETFL
+			if(flags != -1L)
+				if(fcntl(sock, F_SETFL, flags))
+					logg("^f_setfl: %s\n", strerror(errno));
+#endif
+			return connect_error(sock, hostname);
+		}
+
+		/* timeout */
+		if(attempts++ == NONBLOCK_MAX_ATTEMPTS) {
+			logg("^timeout connecting to %s\n", hostname);
+			break;
+		}
+	}
+
+#ifdef	F_SETFL
+	if(flags != -1L)
+		if(fcntl(sock, F_SETFL, flags))
+			logg("^f_setfl: %s\n", strerror(errno));
+#endif
+	return -1; /* failed */
+}
+
+static int
+connect_error(int sock, const char *hostname)
+{
+#ifdef	SO_ERROR
+	int optval;
+	socklen_t optlen = sizeof(optval);
+
+	getsockopt(sock, SOL_SOCKET, SO_ERROR, &optval, &optlen);
+
+	if(optval) {
+		logg("^%s: %s\n", hostname, strerror(optval));
+		return -1;
+	}
+#endif
+	return 0;
+}
diff --git a/contrib/old-clamav-milter/clamav-milter.po b/contrib/old-clamav-milter/clamav-milter.po
new file mode 100644
index 0000000..4a261b2
--- /dev/null
+++ b/contrib/old-clamav-milter/clamav-milter.po
@@ -0,0 +1,1122 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR njh at bandsman.co.uk
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: bugs at clamav.net\n"
+"POT-Creation-Date: 2007-10-24 09:05+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: clamav-milter.c:585
+msgid "\t--advisory\t\t-A\tFlag viruses rather than deleting them."
+msgstr ""
+
+#: clamav-milter.c:586
+msgid "\t--blacklist-time=SECS\t-k\tTime (in seconds) to blacklist an IP."
+msgstr ""
+
+#: clamav-milter.c:587
+msgid "\t--black-hole-mode\t\tDon't scan messages aliased to /dev/null."
+msgstr ""
+
+#: clamav-milter.c:589
+msgid "\t--bounce\t\t-b\tSend a failure message to the sender."
+msgstr ""
+
+#: clamav-milter.c:591
+msgid ""
+"\t--broadcast\t\t-B [IFACE]\tBroadcast to a network manager when a virus is "
+"found."
+msgstr ""
+
+#: clamav-milter.c:592
+msgid "\t--chroot=DIR\t\t-C DIR\tChroot to dir when starting."
+msgstr ""
+
+#: clamav-milter.c:593
+msgid "\t--config-file=FILE\t-c FILE\tRead configuration from FILE."
+msgstr ""
+
+#: clamav-milter.c:594
+msgid "\t--debug\t\t\t-D\tPrint debug messages."
+msgstr ""
+
+#: clamav-milter.c:595
+msgid ""
+"\t--detect-forged-local-address\t-L\tReject mails that claim to be from us."
+msgstr ""
+
+#: clamav-milter.c:596
+msgid "\t--dont-blacklist\t-K\tDon't blacklist a given IP."
+msgstr ""
+
+#: clamav-milter.c:597
+msgid ""
+"\t--dont-scan-on-error\t-d\tPass e-mails through unscanned if a system error "
+"occurs."
+msgstr ""
+
+#: clamav-milter.c:598
+msgid "\t--dont-wait\t\t\tAsk remote end to resend if max-children exceeded."
+msgstr ""
+
+#: clamav-milter.c:599
+msgid "\t--external\t\t-e\tUse an external scanner (usually clamd)."
+msgstr ""
+
+#: clamav-milter.c:600
+msgid ""
+"\t--freshclam-monitor=SECS\t-M SECS\tHow often to check for database update."
+msgstr ""
+
+#: clamav-milter.c:601
+msgid "\t--from=EMAIL\t\t-a EMAIL\tError messages come from here."
+msgstr ""
+
+#: clamav-milter.c:602
+msgid "\t--force-scan\t\t-f\tForce scan all messages (overrides (-o and -l)."
+msgstr ""
+
+#: clamav-milter.c:603
+msgid "\t--help\t\t\t-h\tThis message."
+msgstr ""
+
+#: clamav-milter.c:604
+msgid "\t--headers\t\t-H\tInclude original message headers in the report."
+msgstr ""
+
+#: clamav-milter.c:605
+msgid ""
+"\t--ignore IPaddr\t\t-I IPaddr\tAdd IPaddr to LAN IP list (see --local)."
+msgstr ""
+
+#: clamav-milter.c:606
+msgid "\t--local\t\t\t-l\tScan messages sent from machines on our LAN."
+msgstr ""
+
+#: clamav-milter.c:607
+msgid "\t--max-childen\t\t-m\tMaximum number of concurrent scans."
+msgstr ""
+
+#: clamav-milter.c:608
+msgid "\t--outgoing\t\t-o\tScan outgoing messages from this machine."
+msgstr ""
+
+#: clamav-milter.c:609
+msgid "\t--noreject\t\t-N\tDon't reject viruses, silently throw them away."
+msgstr ""
+
+#: clamav-milter.c:610
+msgid "\t--noxheader\t\t-n\tSuppress X-Virus-Scanned/X-Virus-Status headers."
+msgstr ""
+
+#: clamav-milter.c:611
+msgid "\t--pidfile=FILE\t\t-i FILE\tLocation of pidfile."
+msgstr ""
+
+#: clamav-milter.c:612
+msgid "\t--postmaster\t\t-p EMAIL\tPostmaster address [default=postmaster]."
+msgstr ""
+
+#: clamav-milter.c:613
+msgid "\t--postmaster-only\t-P\tSend notifications only to the postmaster."
+msgstr ""
+
+#: clamav-milter.c:614
+msgid "\t--quiet\t\t\t-q\tDon't send e-mail notifications of interceptions."
+msgstr ""
+
+#: clamav-milter.c:615
+msgid "\t--quarantine=USER\t-Q EMAIL\tQuarantine e-mail account."
+msgstr ""
+
+#: clamav-milter.c:616
+msgid "\t--report-phish=EMAIL\t-r EMAIL\tReport phish to this email address."
+msgstr ""
+
+#: clamav-milter.c:617
+msgid ""
+"\t--report-phish-false-positives=EMAIL\t-R EMAIL\tReport phish false "
+"positves to this email address."
+msgstr ""
+
+#: clamav-milter.c:618
+msgid "\t--quarantine-dir=DIR\t-U DIR\tDirectory to store infected emails."
+msgstr ""
+
+#: clamav-milter.c:619
+msgid ""
+"\t--server=SERVER\t\t-s SERVER\tHostname/IP address of server(s) running "
+"clamd (when using TCPsocket)."
+msgstr ""
+
+#: clamav-milter.c:620
+msgid "\t--sendmail-cf=FILE\t\tLocation of the sendmail.cf file to verify"
+msgstr ""
+
+#: clamav-milter.c:621
+msgid "\t--sign\t\t\t-S\tAdd a hard-coded signature to each scanned message."
+msgstr ""
+
+#: clamav-milter.c:622
+msgid "\t--signature-file=FILE\t-F FILE\tLocation of signature file."
+msgstr ""
+
+#: clamav-milter.c:623
+msgid "\t--template-file=FILE\t-t FILE\tLocation of e-mail template file."
+msgstr ""
+
+#: clamav-milter.c:624
+msgid ""
+"\t--template-headers=FILE\t\tLocation of e-mail headers for template file."
+msgstr ""
+
+#: clamav-milter.c:625
+msgid "\t--timeout=SECS\t\t-T SECS\tTimeout waiting to childen to die."
+msgstr ""
+
+#: clamav-milter.c:626
+msgid ""
+"\t--whitelist-file=FILE\t-W FILE\tLocation of the file of whitelisted "
+"addresses"
+msgstr ""
+
+#: clamav-milter.c:627
+msgid "\t--version\t\t-V\tPrint the version number of this software."
+msgstr ""
+
+#: clamav-milter.c:629
+msgid "\t--debug-level=n\t\t-x n\tSets the debug level to 'n'."
+msgstr ""
+
+#: clamav-milter.c:631
+msgid ""
+"\n"
+"For more information type \"man clamav-milter\"."
+msgstr ""
+
+#: clamav-milter.c:632
+msgid "For bug reports, please refer to http://www.clamav.net/bugs"
+msgstr ""
+
+#: clamav-milter.c:931
+#, c-format
+msgid "%s: %s, -I may only be given %d times\n"
+msgstr ""
+
+#: clamav-milter.c:937
+#, c-format
+msgid "%s: Cannot convert -I%s to IPaddr\n"
+msgstr ""
+
+#: clamav-milter.c:1051
+#, c-format
+msgid "%s: SESSIONS mode requires --external\n"
+msgstr ""
+
+#: clamav-milter.c:1059
+#, c-format
+msgid "%s: No socket-addr given\n"
+msgstr ""
+
+#: clamav-milter.c:1066
+#, c-format
+msgid "%s: socket-addr (%s) doesn't agree with sendmail.cf\n"
+msgstr ""
+
+#: clamav-milter.c:1082
+#, c-format
+msgid "%s: when using inet: connexion to sendmail you must enable --local\n"
+msgstr ""
+
+#: clamav-milter.c:1094
+#, c-format
+msgid "%s: Can't parse the config file %s\n"
+msgstr ""
+
+#: clamav-milter.c:1101
+#, c-format
+msgid "%s: --detect-forged-local-addresses is not compatible with --outgoing\n"
+msgstr ""
+
+#: clamav-milter.c:1105
+#, c-format
+msgid "%s: --detect-forged-local-addresses is not compatible with --local\n"
+msgstr ""
+
+#: clamav-milter.c:1109
+#, c-format
+msgid "%s: --detect-forged-local-addresses is not compatible with --force\n"
+msgstr ""
+
+#: clamav-milter.c:1153
+#, c-format
+msgid ""
+"%s: The iface option to --broadcast is not supported on your operating "
+"system\n"
+msgstr ""
+
+#: clamav-milter.c:1162
+#, c-format
+msgid "%s: Can't get information about user %s\n"
+msgstr ""
+
+#: clamav-milter.c:1173
+#, c-format
+msgid "%s: AllowSupplementaryGroups: initgroups not supported.\n"
+msgstr ""
+
+#: clamav-milter.c:1191
+#, c-format
+msgid "Running as user %s (UID %d, GID %d)\n"
+msgstr ""
+
+#: clamav-milter.c:1247
+#, c-format
+msgid "%s: You cannot use black hole mode unless %s is a TrustedUser\n"
+msgstr ""
+
+#: clamav-milter.c:1253
+#, c-format
+msgid "^%s: running as root is not recommended (check \"User\" in %s)\n"
+msgstr ""
+
+#: clamav-milter.c:1255
+#, c-format
+msgid "%s: Only root can set an interface for --broadcast\n"
+msgstr ""
+
+#: clamav-milter.c:1260
+#, c-format
+msgid "%s: Advisory mode doesn't work with quarantine mode\n"
+msgstr ""
+
+#: clamav-milter.c:1268
+#, c-format
+msgid "%s: Advisory mode doesn't work with quarantine directories\n"
+msgstr ""
+
+#: clamav-milter.c:1274
+#, c-format
+msgid "%s: the quarantine directory must not contain the string 'ERROR'\n"
+msgstr ""
+
+#: clamav-milter.c:1280
+#, c-format
+msgid "%s: the quarantine directory must not contain the string 'FOUND'\n"
+msgstr ""
+
+#: clamav-milter.c:1286
+#, c-format
+msgid "%s: the quarantine directory must not contain the string 'OK'\n"
+msgstr ""
+
+#: clamav-milter.c:1303
+#, c-format
+msgid "%s: insecure quarantine directory %s (mode 0%o)\n"
+msgstr ""
+
+#: clamav-milter.c:1344
+#, c-format
+msgid "%s: ReadTimeout must not be negative in %s\n"
+msgstr ""
+
+#: clamav-milter.c:1353
+#, c-format
+msgid "%s: StreamMaxLength must not be negative in %s\n"
+msgstr ""
+
+#: clamav-milter.c:1386
+#, c-format
+msgid ""
+"%s: (-q && !LogSyslog): warning - all interception message methods are off\n"
+msgstr ""
+
+#: clamav-milter.c:1402
+#, c-format
+msgid "%s: --max-children must be given if --external is not given\n"
+msgstr ""
+
+#: clamav-milter.c:1406
+#, c-format
+msgid "%s: --freshclam_monitor must be at least one second\n"
+msgstr ""
+
+#: clamav-milter.c:1420
+#, c-format
+msgid "%s: --timeout must not be given if --external is not given\n"
+msgstr ""
+
+#: clamav-milter.c:1433
+#, c-format
+msgid "%s: No emails will be scanned"
+msgstr ""
+
+#: clamav-milter.c:1444
+#, c-format
+msgid "%s: You can select one server type only (local/TCP) in %s\n"
+msgstr ""
+
+#: clamav-milter.c:1449
+#, c-format
+msgid "%s: You cannot use the --server option when using LocalSocket in %s\n"
+msgstr ""
+
+#: clamav-milter.c:1459
+#, c-format
+msgid "The connexion from sendmail to %s (%s) must not\n"
+msgstr ""
+
+#: clamav-milter.c:1461
+#, c-format
+msgid "be the same as the connexion to clamd (%s) in %s\n"
+msgstr ""
+
+#: clamav-milter.c:1471 clamav-milter.c:1498
+#, c-format
+msgid "Can't talk to clamd server via %s\n"
+msgstr ""
+
+#: clamav-milter.c:1473 clamav-milter.c:1500
+#, c-format
+msgid "Check your entry for LocalSocket in %s\n"
+msgstr ""
+
+#: clamav-milter.c:1510
+msgid "!Can't create a clamd session"
+msgstr ""
+
+#: clamav-milter.c:1527
+#, c-format
+msgid "%s: --quarantine-dir not supported for TCPSocket - use --quarantine\n"
+msgstr ""
+
+#: clamav-milter.c:1542
+#, c-format
+msgid "%s: hostname %s is longer than %d characters\n"
+msgstr ""
+
+#: clamav-milter.c:1561 clamav-milter.c:1694
+#, c-format
+msgid "%s: --max-children must be given in sessions mode\n"
+msgstr ""
+
+#: clamav-milter.c:1567
+#, c-format
+msgid ""
+"%1$s: --max-children (%2$d) is lower than the number of servers you have (%3"
+"$d)\n"
+msgstr ""
+
+#: clamav-milter.c:1594
+#, c-format
+msgid "%s: Unknown host %s\n"
+msgstr ""
+
+#: clamav-milter.c:1624
+msgid "Waiting for clamd to come up\n"
+msgstr ""
+
+#: clamav-milter.c:1638
+#, c-format
+msgid "Can't talk to clamd server %s on port %d\n"
+msgstr ""
+
+#: clamav-milter.c:1642
+#, c-format
+msgid "Check the value for TCPAddr in %s\n"
+msgstr ""
+
+#: clamav-milter.c:1644
+#, c-format
+msgid "Check the value for TCPAddr in clamd.conf on %s\n"
+msgstr ""
+
+#: clamav-milter.c:1660 clamav-milter.c:1668 clamav-milter.c:4773
+msgid "!Can't find any clamd server\n"
+msgstr ""
+
+#: clamav-milter.c:1661 clamav-milter.c:1666
+#, c-format
+msgid "Check your entry for TCPSocket in %s\n"
+msgstr ""
+
+#: clamav-milter.c:1674
+#, c-format
+msgid "%s: You must select server type (local/TCP) in %s\n"
+msgstr ""
+
+#: clamav-milter.c:1777
+#, c-format
+msgid "When debugging it is recommended that you use Foreground mode in %s\n"
+msgstr ""
+
+#: clamav-milter.c:1778
+msgid "\tso that you can see all of the messages"
+msgstr ""
+
+#: clamav-milter.c:1886
+#, c-format
+msgid "%s: ScanMail not defined in %s (needed without --external), enabling\n"
+msgstr ""
+
+#: clamav-milter.c:1946
+msgid "Starting clamav-milter"
+msgstr ""
+
+#: clamav-milter.c:1974
+#, c-format
+msgid "!pidfile: '%s' must be a full pathname"
+msgstr ""
+
+#: clamav-milter.c:1990
+#, c-format
+msgid "!Can't save PID in file %s\n"
+msgstr ""
+
+#: clamav-milter.c:2058
+#, c-format
+msgid "Starting %s\n"
+msgstr ""
+
+#: clamav-milter.c:2059
+msgid "*Debugging is on\n"
+msgstr ""
+
+#: clamav-milter.c:2159
+#, c-format
+msgid "Check clamd server %s - it may be down\n"
+msgstr ""
+
+#: clamav-milter.c:2164
+msgid "Check clamd server - it may be down"
+msgstr ""
+
+#: clamav-milter.c:2374
+msgid "No free clamd sessions\n"
+msgstr ""
+
+#: clamav-milter.c:2495
+msgid "^Couldn't establish a connexion to any clamd server\n"
+msgstr ""
+
+#: clamav-milter.c:2519
+#, c-format
+msgid "^findServer: select failed (maxsock = %d)\n"
+msgstr ""
+
+#: clamav-milter.c:2533
+msgid "^findServer: No response from any server\n"
+msgstr ""
+
+#: clamav-milter.c:2602
+#, c-format
+msgid "^Check clamd server %s - it may be down\n"
+msgstr ""
+
+#: clamav-milter.c:2606
+msgid "Check clamd server - it may be down\n"
+msgstr ""
+
+#: clamav-milter.c:2630
+msgid "!clamfi_connect: ctx is null"
+msgstr ""
+
+#: clamav-milter.c:2634
+msgid "!clamfi_connect: hostname is null"
+msgstr ""
+
+#: clamav-milter.c:2670
+#, c-format
+msgid "clamfi_connect: Unexpected sa_family %d\n"
+msgstr ""
+
+#: clamav-milter.c:2680
+msgid "clamfi_connect: remoteIP is null"
+msgstr ""
+
+#: clamav-milter.c:2688
+#, c-format
+msgid "clamfi_connect: connexion from %s"
+msgstr ""
+
+#: clamav-milter.c:2690
+#, c-format
+msgid "clamfi_connect: connexion from %s [%s]"
+msgstr ""
+
+#: clamav-milter.c:2709
+msgid "Can't get sendmail hostname"
+msgstr ""
+
+#: clamav-milter.c:2717
+#, c-format
+msgid "^Access Denied: Host Unknown (%s)"
+msgstr ""
+
+#: clamav-milter.c:2726
+#, c-format
+msgid "Can't find entry for IP address %s in DNS - check your DNS setting\n"
+msgstr ""
+
+#: clamav-milter.c:2736
+#, c-format
+msgid "^Access Denied: Can't get IP address for (%s)"
+msgstr ""
+
+#: clamav-milter.c:2752
+#, c-format
+msgid "^Access Denied for %s[%s]"
+msgstr ""
+
+#: clamav-milter.c:2768
+msgid "*clamfi_connect: not scanning outgoing messages"
+msgstr ""
+
+#: clamav-milter.c:2774
+msgid "*clamfi_connect: not scanning local messages\n"
+msgstr ""
+
+#: clamav-milter.c:2787
+msgid "^clamfi_connect: gethostname failed"
+msgstr ""
+
+#: clamav-milter.c:2792
+msgid "Rejected connexion falsely claiming to be from here\n"
+msgstr ""
+
+#: clamav-milter.c:2793
+msgid "You have claimed to be me, but you are not"
+msgstr ""
+
+#: clamav-milter.c:2794 clamav-milter.c:3141
+msgid "Forged local address detected"
+msgstr ""
+
+#: clamav-milter.c:2810
+#, c-format
+msgid "%s is blacklisted because your machine is infected with a virus"
+msgstr ""
+
+#: clamav-milter.c:2812 clamav-milter.c:2924
+msgid "Blacklisted IP detected"
+msgstr ""
+
+#: clamav-milter.c:2868
+msgid "*clamfi_envfrom: ignoring whitelisted message"
+msgstr ""
+
+#: clamav-milter.c:2882
+msgid "Rejected email with empty from field"
+msgstr ""
+
+#: clamav-milter.c:2883
+msgid "You have not said who the email is from"
+msgstr ""
+
+#: clamav-milter.c:2884
+msgid "Reject email with empty from field"
+msgstr ""
+
+#: clamav-milter.c:2902
+msgid "AV system temporarily overloaded - please try later"
+msgstr ""
+
+#: clamav-milter.c:2994
+msgid "Suspicious recipient address blocked"
+msgstr ""
+
+#: clamav-milter.c:2998
+#, c-format
+msgid "Will blacklist %s for %d seconds because of cracking attempt\n"
+msgstr ""
+
+#: clamav-milter.c:3108
+msgid "*clamfi_eoh\n"
+msgstr ""
+
+#: clamav-milter.c:3133
+msgid "clamfi_eoh: gethostname failed"
+msgstr ""
+
+#: clamav-milter.c:3139
+#, c-format
+msgid "Rejected email falsely claiming to be from %s"
+msgstr ""
+
+#: clamav-milter.c:3140
+msgid "You have claimed to be from me, but you are not"
+msgstr ""
+
+#: clamav-milter.c:3187
+msgid "*clamfi_enveoh: ignoring whitelisted message"
+msgstr ""
+
+#: clamav-milter.c:3199
+#, c-format
+msgid "*clamfi_envbody: %lu bytes"
+msgstr ""
+
+#: clamav-milter.c:3256
+#, c-format
+msgid "%s: Message more than StreamMaxLength (%ld) bytes - not scanned\n"
+msgstr ""
+
+#: clamav-milter.c:3259 clamav-milter.c:3585
+msgid "Not Scanned - StreamMaxLength exceeded"
+msgstr ""
+
+#: clamav-milter.c:3322
+#, c-format
+msgid "^Failed to delete X-Virus-Status header %d\n"
+msgstr ""
+
+#: clamav-milter.c:3377
+#, c-format
+msgid "failed to send SCAN %s command to clamd\n"
+msgstr ""
+
+#: clamav-milter.c:3398
+msgid "failed to send SCAN command to clamd\n"
+msgstr ""
+
+#: clamav-milter.c:3415
+#, c-format
+msgid "Waiting to read status from fd %d\n"
+msgstr ""
+
+#: clamav-milter.c:3427
+#, c-format
+msgid "*clamfi_eom: read %s\n"
+msgstr ""
+
+#: clamav-milter.c:3445
+#, c-format
+msgid "clamfi_eom: read nothing from clamd on %s\n"
+msgstr ""
+
+#: clamav-milter.c:3490 clamav-milter.c:3537
+msgid "Error determining host"
+msgstr ""
+
+#: clamav-milter.c:3551
+#, c-format
+msgid "%s: Ignoring %s false positive from %s received from %s\n"
+msgstr ""
+
+#: clamav-milter.c:3567
+#, c-format
+msgid "#Reported phishing false positive to %s"
+msgstr ""
+
+#: clamav-milter.c:3569
+#, c-format
+msgid "^Couldn't report false positive to %s\n"
+msgstr ""
+
+#: clamav-milter.c:3571
+msgid "^Can't set phish FP header\n"
+msgstr ""
+
+#: clamav-milter.c:3582
+#, c-format
+msgid "%s: Message more than StreamMaxLength (%ld) bytes - not scanned"
+msgstr ""
+
+#: clamav-milter.c:3590
+msgid "Not Scanned"
+msgstr ""
+
+#: clamav-milter.c:3618
+msgid "Infected with"
+msgstr ""
+
+#: clamav-milter.c:3640
+#, c-format
+msgid "Intercepted virus from %s to"
+msgstr ""
+
+#: clamav-milter.c:3732
+msgid "Subject: Virus intercepted\n"
+msgstr ""
+
+#: clamav-milter.c:3747
+#, c-format
+msgid "!Can't open e-mail template header file %s"
+msgstr ""
+
+#: clamav-milter.c:3762 clamav-milter.c:3766
+msgid "\n"
+msgstr ""
+
+#: clamav-milter.c:3775
+msgid "A message you sent to\n"
+msgstr ""
+
+#: clamav-milter.c:3785
+#, c-format
+msgid "The message %1$s sent from %2$s to\n"
+msgstr ""
+
+#: clamav-milter.c:3788
+#, c-format
+msgid "A message sent from %s to\n"
+msgstr ""
+
+#: clamav-milter.c:3793
+#, c-format
+msgid "contained %s and has not been accepted for delivery.\n"
+msgstr ""
+
+#: clamav-milter.c:3796
+#, c-format
+msgid ""
+"\n"
+"The message in question has been quarantined as %s\n"
+msgstr ""
+
+#: clamav-milter.c:3799
+#, c-format
+msgid ""
+"\n"
+"The message was received by %1$s from %2$s via %3$s\n"
+"\n"
+msgstr ""
+
+#: clamav-milter.c:3802
+msgid ""
+"For your information, the original message headers were:\n"
+"\n"
+msgstr ""
+
+#: clamav-milter.c:3815
+#, c-format
+msgid ""
+"\n"
+"The infected machine is likely to be here:\n"
+"%s\t\n"
+msgstr ""
+
+#: clamav-milter.c:3822
+#, c-format
+msgid "%s: Failed to notify clamAV interception - see dead.letter\n"
+msgstr ""
+
+#: clamav-milter.c:3824
+#, c-format
+msgid "^Can't execute '%s' to send virus notice"
+msgstr ""
+
+#: clamav-milter.c:3846
+#, c-format
+msgid "#Reported phishing to %s"
+msgstr ""
+
+#: clamav-milter.c:3848
+#, c-format
+msgid "^Couldn't report to %s\n"
+msgstr ""
+
+#: clamav-milter.c:3854
+msgid "^Can't set anti-phish header\n"
+msgstr ""
+
+#: clamav-milter.c:3872
+#, c-format
+msgid "^Can't set quarantine user %s"
+msgstr ""
+
+#: clamav-milter.c:3906
+#, c-format
+msgid "virus %s detected by ClamAV - http://www.clamav.net"
+msgstr ""
+
+#: clamav-milter.c:3911
+#, c-format
+msgid "Will blacklist %s for %d seconds because of %s\n"
+msgstr ""
+
+#: clamav-milter.c:3920
+msgid "Unknown"
+msgstr ""
+
+#: clamav-milter.c:3921
+#, c-format
+msgid "!%s: incorrect message \"%s\" from clamd"
+msgstr ""
+
+#: clamav-milter.c:3926
+msgid "Clean"
+msgstr ""
+
+#: clamav-milter.c:3930
+#, c-format
+msgid "%s: clean message from %s\n"
+msgstr ""
+
+#: clamav-milter.c:3932
+msgid "an unknown sender"
+msgstr ""
+
+#: clamav-milter.c:4020
+#, c-format
+msgid "!Can't remove clean file %s"
+msgstr ""
+
+#: clamav-milter.c:4193 clamav-milter.c:4197
+#, c-format
+msgid "!write failure (%lu bytes) to %s: %s\n"
+msgstr ""
+
+#: clamav-milter.c:4209 clamav-milter.c:4213
+#, c-format
+msgid "!write failure (%lu bytes) to clamd: %s\n"
+msgstr ""
+
+#: clamav-milter.c:4290
+#, c-format
+msgid "!No data received from clamd in %d seconds\n"
+msgstr ""
+
+#: clamav-milter.c:4318
+#, c-format
+msgid "Can't stat %s"
+msgstr ""
+
+#: clamav-milter.c:4328
+#, c-format
+msgid "Can't open %s"
+msgstr ""
+
+#: clamav-milter.c:4447
+#, c-format
+msgid "mkdir %s failed"
+msgstr ""
+
+#: clamav-milter.c:4461
+#, c-format
+msgid "mktemp %s failed"
+msgstr ""
+
+#: clamav-milter.c:4470
+#, c-format
+msgid "Temporary quarantine file %s creation failed"
+msgstr ""
+
+#: clamav-milter.c:4581
+#, c-format
+msgid "!failed to send STREAM command clamd server %d"
+msgstr ""
+
+#: clamav-milter.c:4589
+msgid "!failed to send STREAM command clamd"
+msgstr ""
+
+#: clamav-milter.c:4600
+msgid "!failed to create TCPSocket to talk to clamd"
+msgstr ""
+
+#: clamav-milter.c:4611 clamav-milter.c:4624
+msgid "!recv failed from clamd getting PORT"
+msgstr ""
+
+#: clamav-milter.c:4613 clamav-milter.c:4626
+msgid "!EOF from clamd getting PORT"
+msgstr ""
+
+#: clamav-milter.c:4637
+#, c-format
+msgid "!Expected port information from clamd, got '%s'"
+msgstr ""
+
+#: clamav-milter.c:4657 clamav-milter.c:4660
+#, c-format
+msgid "Connecting to local port %d - data %d cmd %d\n"
+msgstr ""
+
+#: clamav-milter.c:4673 clamav-milter.c:4676
+#, c-format
+msgid "!Failed to connect to port %d given by clamd: %s"
+msgstr ""
+
+#: clamav-milter.c:4785
+#, c-format
+msgid "!Can't open %s\n"
+msgstr ""
+
+#: clamav-milter.c:4799
+#, c-format
+msgid "!Clamd (pid %d) seems to have died\n"
+msgstr ""
+
+#: clamav-milter.c:4825
+#, c-format
+msgid "!Can't open e-mail template file %s"
+msgstr ""
+
+#: clamav-milter.c:4832
+#, c-format
+msgid "!Can't stat e-mail template file %s"
+msgstr ""
+
+#: clamav-milter.c:4839
+msgid "!Out of memory"
+msgstr ""
+
+#: clamav-milter.c:4844
+#, c-format
+msgid "!Error reading e-mail template file %s"
+msgstr ""
+
+#: clamav-milter.c:4872
+#, c-format
+msgid "!%s: Unknown clamAV variable \"%c\"\n"
+msgstr ""
+
+#: clamav-milter.c:4882
+#, c-format
+msgid "!%s: Unterminated sendmail variable \"%s\"\n"
+msgstr ""
+
+#: clamav-milter.c:4891
+#, c-format
+msgid "!%s: Unknown sendmail variable \"%s\"\n"
+msgstr ""
+
+#: clamav-milter.c:4957
+#, c-format
+msgid "!mkdir %s failed\n"
+msgstr ""
+
+#: clamav-milter.c:4982
+#, c-format
+msgid "^Can't rename %1$s to %2$s\n"
+msgstr ""
+
+#: clamav-milter.c:4990
+#, c-format
+msgid "Email quarantined as %s\n"
+msgstr ""
+
+#: clamav-milter.c:5098
+#, c-format
+msgid "[Virus] %s"
+msgstr ""
+
+#: clamav-milter.c:5307
+msgid ""
+"!No response from any clamd server - your AV system is not scanning emails\n"
+msgstr ""
+
+#: clamav-milter.c:5325
+msgid "Subject: ClamAV Down\n"
+msgstr ""
+
+#: clamav-milter.c:5328
+msgid ""
+"This is an automatic message\n"
+"\n"
+msgstr ""
+
+#: clamav-milter.c:5331
+msgid "The clamd program cannot be contacted.\n"
+msgstr ""
+
+#: clamav-milter.c:5333
+msgid "No clamd server can be contacted.\n"
+msgstr ""
+
+#: clamav-milter.c:5335
+msgid "Emails may not be being scanned, please check your servers.\n"
+msgstr ""
+
+#: clamav-milter.c:5396 clamav-milter.c:5542
+msgid "!No emails will be scanned"
+msgstr ""
+
+#: clamav-milter.c:5606
+#, c-format
+msgid "Stopping %s\n"
+msgstr ""
+
+#: clamav-milter.c:5654
+msgid "Stopping clamav-milter"
+msgstr ""
+
+#: clamav-milter.c:5802
+#, c-format
+msgid "Loaded %s\n"
+msgstr ""
+
+#: clamav-milter.c:5806
+#, c-format
+msgid "ClamAV: Protecting against %u viruses\n"
+msgstr ""
+
+#: clamav-milter.c:5954
+#, c-format
+msgid "!Can't open whitelist file %s"
+msgstr ""
+
+#: clamav-milter.c:5961
+msgid "!Can't create whitelist table"
+msgstr ""
+
+#: clamav-milter.c:6049
+msgid "!Can't create blacklist table"
+msgstr ""
+
+#: clamav-milter.c:6472
+msgid "^MX peers will not be immune from being blacklisted"
+msgstr ""
+
+#: clamav-milter.c:6498
+msgid "!Can't create pipe\n"
+msgstr ""
+
+#: clamav-milter.c:6519
+msgid "!Can't fork\n"
+msgstr ""
+
+#: clamav-milter.c:6567
+#, c-format
+msgid "^Can't execute '%s' to expand '%s' (error %d)\n"
+msgstr ""
+
+#: clamav-milter.c:6625
+#, c-format
+msgid "hit max-children limit (%u >= %u)\n"
+msgstr ""
+
+#: clamav-milter.c:6626
+#, c-format
+msgid "hit max-children limit (%u >= %u): waiting for some to exit\n"
+msgstr ""
+
+#: clamav-milter.c:6648
+#, c-format
+msgid "n_children %d: waiting %d seconds for some to exit\n"
+msgstr ""
+
+#: clamav-milter.c:6661
+#, c-format
+msgid "Finished waiting, n_children = %d\n"
+msgstr ""
+
+#: clamav-milter.c:6669
+msgid "*Timeout waiting for a child to die\n"
+msgstr ""
+
+#: clamav-milter.c:6703
+#, c-format
+msgid "Won't blacklist %s\n"
+msgstr ""
diff --git a/contrib/phishing/generate_tables.c b/contrib/phishing/generate_tables.c
new file mode 100644
index 0000000..15003fd
--- /dev/null
+++ b/contrib/phishing/generate_tables.c
@@ -0,0 +1,169 @@
+/*
+ *   Phishing detection automated testing & tools.
+ *
+ *  Copyright (C) 2006 Torok Edvin <edwintorok at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA 02110-1301, USA.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+enum wctype_t {ALNUM,DIGIT,PUNCT,ALPHA,GRAPH,SPACE,BLANK,LOWER,UPPER,CNTRL,PRINT,XDIGIT};
+static struct std_classmap {
+		const char* classname;
+		const enum wctype_t type;
+} std_class[] = {
+	{"[:alnum:]",ALNUM},
+	{"[:digit:]",DIGIT},
+	{"[:punct:]",PUNCT},
+	{"[:alpha:]",ALPHA},
+	{"[:graph:]",GRAPH},
+	{"[:space:]",SPACE},
+	{"[:blank:]",BLANK},
+	{"[:lower:]",LOWER}, 
+	{"[:upper:]",UPPER},
+	{"[:cntrl:]",CNTRL},
+	{"[:print:]",PRINT},
+	{"[:xdigit:]",XDIGIT}
+};
+
+static int cli_iswctype(const char c,const enum wctype_t type);
+
+/* -------------- NON_THREAD_SAFE BEGIN --------------*/
+/* Global variables and functions accessing them, not thread-safe!
+ * they should be called on application startup/shutdown once! */
+static const size_t std_class_cnt =  sizeof(std_class)/sizeof(std_class[0]);
+#define STD_CLASS_CNT sizeof(std_class)/sizeof(std_class[0])
+typedef char char_bitmap_t[32];
+static unsigned char* char_class_bitmap[STD_CLASS_CNT];
+static unsigned short int char_class[256];
+static int engine_ok = 0;
+static int cli_iswctype(const char c,const enum wctype_t type)
+{
+	switch(type) {
+		case ALNUM:
+			return isalnum(c);
+		case DIGIT:
+			return isdigit(c);
+		case PUNCT:
+			return ispunct(c);
+		case ALPHA:
+			return isalpha(c);
+		case GRAPH:
+			return isgraph(c);
+		case SPACE:
+			return isspace(c);
+		case BLANK:
+			return c=='\t' || c==' ';
+		case LOWER:
+			return islower(c);
+		case UPPER:
+			return isupper(c);
+		case CNTRL:
+			return iscntrl(c);
+		case PRINT:
+			return isprint(c);
+		case XDIGIT:
+			return isxdigit(c);
+		default: {
+				 return 0;
+			 }
+	}
+}
+
+
+void setup_matcher_engine(void)
+{
+	/*Set up std character classes*/
+	printf("--------regex_list.c--------\n");
+	printf("/* generated by contrib/phishing/generate_tables.c */\n");
+	printf("static const unsigned char char_class_bitmap[STD_CLASS_CNT][32] = {\n");
+
+
+	size_t i;
+	size_t j;
+	memset(char_class,0,256);
+	for(i=0;i<std_class_cnt;i++) {
+		enum wctype_t type = std_class[i].type;
+		printf("        {");
+		char_class_bitmap[i]=calloc(256>>3,1);
+		for(j=0;j<256;j++)
+			if(cli_iswctype(j,type)) {
+				char_class[j] |= 1<<i;
+				char_class_bitmap[i][j>>3] |= 1<<(j&0x07);
+			}
+		for(j=0;j<32;j++) {
+			printf("0x%02x",char_class_bitmap[i][j]);
+			if(j!=31) {
+				printf(", ");
+				if(j%8==7)
+					printf("\n         ");
+			}
+		}
+		printf("}");
+		if(i!=std_class_cnt-1)
+			printf(",\n\n");
+	}	
+        printf("\n};\n");
+	printf("static const unsigned short int char_class[256] = {\n        ");
+	for(i=0;i<256;i++) {
+		printf("0x%03x",char_class[i]);
+		if(i!=255) {
+			printf(", ");
+			if(i%16==15)
+				printf("\n        ");
+		}
+	}
+
+	printf("\n};\n");
+	engine_ok  = 1;
+}
+static short int hextable[256];
+static void init_hextable(void)
+{
+	unsigned char c;
+	int i;
+	memset(hextable,0,256);
+	for(c='0';c<='9';c++)
+		hextable[c] = c-'0';
+	for(c='a';c<='f';c++)
+		hextable[c] = 10+c-'a';
+	for(c='A';c<='F';c++)
+		hextable[c] = 10+c-'A';
+	printf("-------phishcheck.c---------\n");
+	printf("/* generated by contrib/phishing/generate_tables.c */\n");
+	printf("static const short int hextable[256] = {\n       ");
+	for(i=0;i<256;i++) {
+		printf("0x%x",hextable[i]);
+		if(i!=255) {
+			printf(", ");
+			if(i%16==15)
+				printf("\n       ");
+		}
+	}
+	printf("\n};\n");
+}
+
+int main()
+{
+	setup_matcher_engine();
+	init_hextable();
+	return 0;
+}
+
diff --git a/contrib/phishing/regex_opt.py b/contrib/phishing/regex_opt.py
new file mode 100755
index 0000000..f6f2d60
--- /dev/null
+++ b/contrib/phishing/regex_opt.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+def strlen(a,b):
+	if len(a)<len(b):
+		return -1;
+	elif len(a)>len(b):
+		return 1;
+	else:
+		return 0;
+
+def getcommon_prefix(a,b):
+	if a==b:
+		return b;
+	if a[:-1]==b[:-1]:
+		return a[:-1];
+	else:
+		return ""
+
+fil = file("iana_tld.h")
+left = fil.read().split("(")
+out=[]
+for i in range(1,len(left)):
+	right = left[i].split(")")
+	regex_split = right[0].split("|")
+	regex_split.sort()
+	regex_split.sort(strlen)
+	prefix=''
+	prefixlen=0;
+	c_map=''
+	list=[]
+	for val in regex_split:
+		if val[:prefixlen] == prefix:
+			if len(val) == (prefixlen+1):
+				c_map = c_map+val[prefixlen]
+			else:
+	
+				if len(c_map)>1:
+					c_map = "["+c_map+"]"
+				if len(prefix+c_map)>0:
+					list.append(prefix+c_map)
+				prefix = val[:-1]
+				prefixlen=len(prefix)
+				c_map=val[prefixlen]
+		else:
+			if len(c_map)>1:
+				c_map = "["+c_map+"]"
+			list.append(prefix+c_map)
+			prefix = getcommon_prefix(prefix,val) 
+			if len(prefix)==0:
+				prefix=val[:-1]
+			prefixlen=len(prefix)
+			c_map=val[prefixlen]
+ 	if i==1:
+		left0=left[0]
+	else:
+		left0=""
+	out.append(left0)
+	out.append("(")
+	out.append("|".join(list))
+	out.append(")")
+	out.append(right[1])
+print "".join(out)
diff --git a/contrib/phishing/update_iana_data.sh b/contrib/phishing/update_iana_data.sh
new file mode 100755
index 0000000..ecef492
--- /dev/null
+++ b/contrib/phishing/update_iana_data.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+#
+#  Phishing detection automated testing & tools.
+#  Copyright (C) 2006 Torok Edvin <edwintorok at gmail.com>
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+#  MA 02110-1301, USA.
+#
+IANA_TLD="http://data.iana.org/TLD/tlds-alpha-by-domain.txt"
+IANA_CCTLD="http://www.iana.org/cctld/cctld-whois.htm";
+TMP=`tempfile`
+OUTFILE=iana_tld.h
+
+echo "Downloading updated tld list from iana.org"
+wget $IANA_TLD -O $TMP || exit 2
+echo "Download complete, parsing data"
+grep -Ev ^# $TMP | tr [A-Z] [a-z] | gperf -C -l -L ANSI-C -E -C -H tld_hash -N in_tld_set|grep -v '^#line' | sed -e 's/^const struct/static const struct/' -e 's/register //g' >iana_tld.h
+
+echo "Downloading updated country-code list from iana.org"
+wget $IANA_CCTLD -O $TMP || exit 2
+echo "Download complete, parsing data"
+cat $TMP | grep country-code|egrep -oi "<a
+href=[^>]+>\\.([a-zA-Z]+).+</a>"|egrep -o ">.[a-zA-Z]+" | colrm 1 2 | tr [A-Z]
+[a-z]| gperf -C -l -L ANSI-C -E -C -H cctld_hash -N in_cctld_set |grep -v '^#line'|sed -e 's/^const struct/static const struct/' -e 's/register //g' -e 's/^const char \*/static const char */' >iana_cctld.h
+echo "Done"
diff --git a/contrib/phishing/update_iana_tld.sh b/contrib/phishing/update_iana_tld.sh
new file mode 100755
index 0000000..2bf06ac
--- /dev/null
+++ b/contrib/phishing/update_iana_tld.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+#  Phishing detection automated testing & tools.
+#  Copyright (C) 2006 Torok Edvin <edwintorok at gmail.com>
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+#  MA 02110-1301, USA.
+#
+IANA_TLD="http://data.iana.org/TLD/tlds-alpha-by-domain.txt"
+TMP=`tempfile`
+OUTFILE=iana_tld.h
+
+echo "Downloading updated tld list from iana.org"
+wget $IANA_TLD -O $TMP || exit 2
+echo "Download complete, parsing data"
+# 174 is the code for |
+grep -Ev ^# $TMP | tr [A-Z] [a-z] | gperf -C -H tld_hash -N in_tld_set -l|grep -v '^#line' | sed -e 's/^const struct/static const struct/' -e 's/register //g'
diff --git a/contrib/phishing/whitelist_test.c b/contrib/phishing/whitelist_test.c
new file mode 100644
index 0000000..3742d87
--- /dev/null
+++ b/contrib/phishing/whitelist_test.c
@@ -0,0 +1,76 @@
+/*
+ *   Phishing detection automated testing & tools.
+ *
+ *  Copyright (C) 2006 Torok Edvin <edwintorok at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ *  MA 02110-1301, USA.
+ *
+ */
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+#include "whitelist.h"
+void show_time(struct timeval tv1,struct timeval tv2)
+{
+	struct timeval diff;
+	diff.tv_sec = tv2.tv_sec-tv1.tv_sec;
+	diff.tv_usec = tv2.tv_usec-tv1.tv_usec;
+	if(diff.tv_usec>0) {
+		diff.tv_sec += diff.tv_usec/1000000;
+		diff.tv_usec %= 1000000;
+	}
+	else {
+		int x = diff.tv_usec/1000000;//<0
+		diff.tv_sec += x-1;
+		diff.tv_usec -= (x-1)*1000000;
+	}
+	printf("%d.%06d,",diff.tv_sec,diff.tv_usec);
+}
+int main(int argc,char* argv[])
+{
+	if(argc<2)
+		return 1;
+	FILE* f=fopen("whitelist.wdb","rb");
+	init_whitelist();
+	printf("%d,",load_whitelist(f));
+	struct timeval tv0,tv01;
+	gettimeofday(&tv0,NULL);
+	build_whitelist();
+	gettimeofday(&tv01,NULL);
+	show_time(tv0,tv01);
+	fclose(f);
+	FILE* f2=fopen(argv[1],"rb");
+	fseek(f2,0,SEEK_END);
+	long p=ftell(f2);
+	fseek(f2,0,SEEK_SET);
+	char* x = malloc(p+1);
+	if(fread(x,p,1,f2)!=1)
+		return 2;
+	x[p]=0;
+	fclose(f2);
+	struct timeval tv1,tv2,diff;
+	gettimeofday(&tv1,NULL);
+	int rc=whitelist_match(x,"test",0);
+	gettimeofday(&tv2,NULL);
+	show_time(tv1,tv2);
+	printf("%d\n",rc);
+	free(x);
+	whitelist_done();
+/*	const char* real = "http://pics.ebaystatic.com/";
+	const char* display = "http://www.ebay.com/";
+	printf("%d\n",whitelist_match(real,display,0));*/
+	return 0;
+}
diff --git a/contrib/phishing/why.py b/contrib/phishing/why.py
new file mode 100755
index 0000000..a45d4f1
--- /dev/null
+++ b/contrib/phishing/why.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+from popen2 import popen4;
+import sys;
+import os;
+out = popen4("clamscan/clamscan -d database --phishing-strict-url-check --debug "+sys.argv[1])[0]
+lines = out.read().split("\n")
+PHISH_FOUND="Phishing found"
+URL_CHECK="Checking url"
+j=-1
+for i in range(0,len(lines)):
+	if lines[i].find(PHISH_FOUND)!=-1:
+		j=i
+		break
+
+if j!=-1:
+	print lines[j]
+	i=j
+	while lines[i].find(URL_CHECK)==-1:
+		i = i-1
+	for k in range(i,j):
+		print lines[k]
+#	os.system("TEMPFILE=`tempfile -s .eml` ; echo $TEMPFILE; cp "+sys.argv[1]+" $TEMPFILE; thunderbird $TEMPFILE")
+else:
+	print "Clean"
diff --git a/database/daily.cvd b/database/daily.cvd
index 6918208..8fa4635 100644
Binary files a/database/daily.cvd and b/database/daily.cvd differ

-- 
Debian repository for ClamAV



More information about the Pkg-clamav-commits mailing list