[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 "⨿" ><!--AMALGAMATION OR COPRODUCT -->
+<!ENTITY Barwed "⌆" ><!--PERSPECTIVE -->
+<!ENTITY barwed "⌅" ><!--PROJECTIVE -->
+<!ENTITY Cap "⋒" ><!--DOUBLE INTERSECTION -->
+<!ENTITY coprod "∐" ><!--N-ARY COPRODUCT -->
+<!ENTITY Cup "⋓" ><!--DOUBLE UNION -->
+<!ENTITY cuvee "⋎" ><!--CURLY LOGICAL OR -->
+<!ENTITY cuwed "⋏" ><!--CURLY LOGICAL AND -->
+<!ENTITY diam "⋄" ><!--DIAMOND OPERATOR -->
+<!ENTITY divonx "⋇" ><!--DIVISION TIMES -->
+<!ENTITY intcal "⊺" ><!--INTERCALATE -->
+<!ENTITY lthree "⋋" ><!--LEFT SEMIDIRECT PRODUCT -->
+<!ENTITY ltimes "⋉" ><!--LEFT NORMAL FACTOR SEMIDIRECT PRODUCT -->
+<!ENTITY minusb "⊟" ><!--SQUARED MINUS -->
+<!ENTITY oast "⊛" ><!--CIRCLED ASTERISK OPERATOR -->
+<!ENTITY ocir "⊚" ><!--CIRCLED RING OPERATOR -->
+<!ENTITY odash "⊝" ><!--CIRCLED DASH -->
+<!ENTITY odot "⊙" ><!--CIRCLED DOT OPERATOR -->
+<!ENTITY ominus "⊖" ><!--CIRCLED MINUS -->
+<!ENTITY oplus "⊕" ><!--CIRCLED PLUS -->
+<!ENTITY osol "⊘" ><!--CIRCLED DIVISION SLASH -->
+<!ENTITY otimes "⊗" ><!--CIRCLED TIMES -->
+<!ENTITY plusb "⊞" ><!--SQUARED PLUS -->
+<!ENTITY plusdo "∔" ><!--DOT PLUS -->
+<!ENTITY prod "∏" ><!--N-ARY PRODUCT -->
+<!ENTITY rthree "⋌" ><!--RIGHT SEMIDIRECT PRODUCT -->
+<!ENTITY rtimes "⋊" ><!--RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT -->
+<!ENTITY sdot "⋅" ><!--DOT OPERATOR -->
+<!ENTITY sdotb "⊡" ><!--SQUARED DOT OPERATOR -->
+<!ENTITY setmn "∖" ><!--SET MINUS -->
+<!ENTITY sqcap "⊓" ><!--SQUARE CAP -->
+<!ENTITY sqcup "⊔" ><!--SQUARE CUP -->
+<!ENTITY ssetmn "∖" ><!--SET MINUS -->
+<!ENTITY sstarf "⋆" ><!--STAR OPERATOR -->
+<!ENTITY sum "∑" ><!--N-ARY SUMMATION -->
+<!ENTITY timesb "⊠" ><!--SQUARED TIMES -->
+<!ENTITY top "⊤" ><!--DOWN TACK -->
+<!ENTITY uplus "⊎" ><!--MULTISET UNION -->
+<!ENTITY wreath "≀" ><!--WREATH PRODUCT -->
+<!ENTITY xcirc "◯" ><!--LARGE CIRCLE -->
+<!ENTITY xdtri "▽" ><!--WHITE DOWN-POINTING TRIANGLE -->
+<!ENTITY xutri "△" ><!--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 "⌞" ><!--BOTTOM LEFT CORNER -->
+<!ENTITY drcorn "⌟" ><!--BOTTOM RIGHT CORNER -->
+<!ENTITY lceil "⌈" ><!--LEFT CEILING -->
+<!ENTITY lfloor "⌊" ><!--LEFT FLOOR -->
+<!ENTITY lpargt "⦠" ><!--SPHERICAL ANGLE OPENING LEFT -->
+<!ENTITY rceil "⌉" ><!--RIGHT CEILING -->
+<!ENTITY rfloor "⌋" ><!--RIGHT FLOOR -->
+<!ENTITY rpargt "⦔" ><!--RIGHT ARC GREATER-THAN BRACKET -->
+<!ENTITY ulcorn "⌜" ><!--TOP LEFT CORNER -->
+<!ENTITY urcorn "⌝" ><!--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 "∠" ><!--ANGLE -->
+<!ENTITY angmsd "∡" ><!--MEASURED ANGLE -->
+<!ENTITY beth "ℶ" ><!--BET SYMBOL -->
+<!ENTITY bprime "‵" ><!--REVERSED PRIME -->
+<!ENTITY comp "∁" ><!--COMPLEMENT -->
+<!ENTITY daleth "ℸ" ><!--DALET SYMBOL -->
+<!ENTITY ell "ℓ" ><!--SCRIPT SMALL L -->
+<!ENTITY empty "∅" ><!--EMPTY SET -->
+<!ENTITY gimel "ℷ" ><!--GIMEL SYMBOL -->
+<!ENTITY inodot "ı" ><!--LATIN SMALL LETTER DOTLESS I -->
+<!ENTITY jnodot "j" ><!--LATIN SMALL LETTER J -->
+<!ENTITY nexist "∄" ><!--THERE DOES NOT EXIST -->
+<!ENTITY oS "Ⓢ" ><!--CIRCLED LATIN CAPITAL LETTER S -->
+<!ENTITY planck "ℏ" ><!--PLANCK CONSTANT OVER TWO PI -->
+<!ENTITY real "ℜ" ><!--BLACK-LETTER CAPITAL R -->
+<!ENTITY sbsol "﹨" ><!--SMALL REVERSE SOLIDUS -->
+<!ENTITY vprime "′" ><!--PRIME -->
+<!ENTITY weierp "℘" ><!--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 "≊" ><!--ALMOST EQUAL OR EQUAL TO -->
+<!ENTITY asymp "≈" ><!--ALMOST EQUAL TO -->
+<!ENTITY bcong "≌" ><!--ALL EQUAL TO -->
+<!ENTITY bepsi "϶" ><!--GREEK REVERSED LUNATE EPSILON SYMBOL -->
+<!ENTITY bowtie "⋈" ><!--BOWTIE -->
+<!ENTITY bsim "∽" ><!--REVERSED TILDE -->
+<!ENTITY bsime "⋍" ><!--REVERSED TILDE EQUALS -->
+<!ENTITY bump "≎" ><!--GEOMETRICALLY EQUIVALENT TO -->
+<!ENTITY bumpe "≏" ><!--DIFFERENCE BETWEEN -->
+<!ENTITY cire "≗" ><!--RING EQUAL TO -->
+<!ENTITY colone "≔" ><!--COLON EQUALS -->
+<!ENTITY cuepr "⋞" ><!--EQUAL TO OR PRECEDES -->
+<!ENTITY cuesc "⋟" ><!--EQUAL TO OR SUCCEEDS -->
+<!ENTITY cupre "≼" ><!--PRECEDES OR EQUAL TO -->
+<!ENTITY dashv "⊣" ><!--LEFT TACK -->
+<!ENTITY ecir "≖" ><!--RING IN EQUAL TO -->
+<!ENTITY ecolon "≕" ><!--EQUALS COLON -->
+<!ENTITY eDot "≑" ><!--GEOMETRICALLY EQUAL TO -->
+<!ENTITY efDot "≒" ><!--APPROXIMATELY EQUAL TO OR THE IMAGE OF -->
+<!ENTITY egs "⪖" ><!--SLANTED EQUAL TO OR GREATER-THAN -->
+<!ENTITY els "⪕" ><!--SLANTED EQUAL TO OR LESS-THAN -->
+<!ENTITY erDot "≓" ><!--IMAGE OF OR APPROXIMATELY EQUAL TO -->
+<!ENTITY esdot "≐" ><!--APPROACHES THE LIMIT -->
+<!ENTITY fork "⋔" ><!--PITCHFORK -->
+<!ENTITY frown "⌢" ><!--FROWN -->
+<!ENTITY gap "⪆" ><!--GREATER-THAN OR APPROXIMATE -->
+<!ENTITY gE "≧" ><!--GREATER-THAN OVER EQUAL TO -->
+<!ENTITY gEl "⪌" ><!--GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN -->
+<!ENTITY gel "⋛" ><!--GREATER-THAN EQUAL TO OR LESS-THAN -->
+<!ENTITY ges "⩾" ><!--GREATER-THAN OR SLANTED EQUAL TO -->
+<!ENTITY Gg "⋙" ><!--VERY MUCH GREATER-THAN -->
+<!ENTITY gl "≷" ><!--GREATER-THAN OR LESS-THAN -->
+<!ENTITY gsdot "⋗" ><!--GREATER-THAN WITH DOT -->
+<!ENTITY gsim "≳" ><!--GREATER-THAN OR EQUIVALENT TO -->
+<!ENTITY Gt "≫" ><!--MUCH GREATER-THAN -->
+<!ENTITY lap "⪅" ><!--LESS-THAN OR APPROXIMATE -->
+<!ENTITY ldot "⋖" ><!--LESS-THAN WITH DOT -->
+<!ENTITY lE "≦" ><!--LESS-THAN OVER EQUAL TO -->
+<!ENTITY lEg "⪋" ><!--LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN -->
+<!ENTITY leg "⋚" ><!--LESS-THAN EQUAL TO OR GREATER-THAN -->
+<!ENTITY les "⩽" ><!--LESS-THAN OR SLANTED EQUAL TO -->
+<!ENTITY lg "≶" ><!--LESS-THAN OR GREATER-THAN -->
+<!ENTITY Ll "⋘" ><!--VERY MUCH LESS-THAN -->
+<!ENTITY lsim "≲" ><!--LESS-THAN OR EQUIVALENT TO -->
+<!ENTITY Lt "≪" ><!--MUCH LESS-THAN -->
+<!ENTITY ltrie "⊴" ><!--NORMAL SUBGROUP OF OR EQUAL TO -->
+<!ENTITY mid "∣" ><!--DIVIDES -->
+<!ENTITY models "⊧" ><!--MODELS -->
+<!ENTITY pr "≺" ><!--PRECEDES -->
+<!ENTITY prap "⪷" ><!--PRECEDES ABOVE ALMOST EQUAL TO -->
+<!ENTITY pre "⪯" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN -->
+<!ENTITY prsim "≾" ><!--PRECEDES OR EQUIVALENT TO -->
+<!ENTITY rtrie "⊵" ><!--CONTAINS AS NORMAL SUBGROUP OR EQUAL TO -->
+<!ENTITY samalg "∐" ><!--N-ARY COPRODUCT -->
+<!ENTITY sc "≻" ><!--SUCCEEDS -->
+<!ENTITY scap "⪸" ><!--SUCCEEDS ABOVE ALMOST EQUAL TO -->
+<!ENTITY sccue "≽" ><!--SUCCEEDS OR EQUAL TO -->
+<!ENTITY sce "⪰" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN -->
+<!ENTITY scsim "≿" ><!--SUCCEEDS OR EQUIVALENT TO -->
+<!ENTITY sfrown "⌢" ><!--FROWN -->
+<!ENTITY smid "∣" ><!--DIVIDES -->
+<!ENTITY smile "⌣" ><!--SMILE -->
+<!ENTITY spar "∥" ><!--PARALLEL TO -->
+<!ENTITY sqsub "⊏" ><!--SQUARE IMAGE OF -->
+<!ENTITY sqsube "⊑" ><!--SQUARE IMAGE OF OR EQUAL TO -->
+<!ENTITY sqsup "⊐" ><!--SQUARE ORIGINAL OF -->
+<!ENTITY sqsupe "⊒" ><!--SQUARE ORIGINAL OF OR EQUAL TO -->
+<!ENTITY ssmile "⌣" ><!--SMILE -->
+<!ENTITY Sub "⋐" ><!--DOUBLE SUBSET -->
+<!ENTITY subE "⫅" ><!--SUBSET OF ABOVE EQUALS SIGN -->
+<!ENTITY Sup "⋑" ><!--DOUBLE SUPERSET -->
+<!ENTITY supE "⫆" ><!--SUPERSET OF ABOVE EQUALS SIGN -->
+<!ENTITY thkap "≈" ><!--ALMOST EQUAL TO -->
+<!ENTITY thksim "∼" ><!--TILDE OPERATOR -->
+<!ENTITY trie "≜" ><!--DELTA EQUAL TO -->
+<!ENTITY twixt "≬" ><!--BETWEEN -->
+<!ENTITY Vdash "⊩" ><!--FORCES -->
+<!ENTITY vDash "⊨" ><!--TRUE -->
+<!ENTITY vdash "⊢" ><!--RIGHT TACK -->
+<!ENTITY veebar "⊻" ><!--XOR -->
+<!ENTITY vltri "⊲" ><!--NORMAL SUBGROUP OF -->
+<!ENTITY vprop "∝" ><!--PROPORTIONAL TO -->
+<!ENTITY vrtri "⊳" ><!--CONTAINS AS NORMAL SUBGROUP -->
+<!ENTITY Vvdash "⊪" ><!--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 "╗" ><!--BOX DRAWINGS DOUBLE DOWN AND LEFT -->
+<!ENTITY boxDl "╖" ><!--BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE -->
+<!ENTITY boxdL "╕" ><!--BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE -->
+<!ENTITY boxdl "┐" ><!--BOX DRAWINGS LIGHT DOWN AND LEFT -->
+<!ENTITY boxDR "╔" ><!--BOX DRAWINGS DOUBLE DOWN AND RIGHT -->
+<!ENTITY boxDr "╓" ><!--BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE -->
+<!ENTITY boxdR "╒" ><!--BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE -->
+<!ENTITY boxdr "┌" ><!--BOX DRAWINGS LIGHT DOWN AND RIGHT -->
+<!ENTITY boxH "═" ><!--BOX DRAWINGS DOUBLE HORIZONTAL -->
+<!ENTITY boxh "─" ><!--BOX DRAWINGS LIGHT HORIZONTAL -->
+<!ENTITY boxHD "╦" ><!--BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL -->
+<!ENTITY boxHd "╤" ><!--BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE -->
+<!ENTITY boxhD "╥" ><!--BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE -->
+<!ENTITY boxhd "┬" ><!--BOX DRAWINGS LIGHT DOWN AND HORIZONTAL -->
+<!ENTITY boxHU "╩" ><!--BOX DRAWINGS DOUBLE UP AND HORIZONTAL -->
+<!ENTITY boxHu "╧" ><!--BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE -->
+<!ENTITY boxhU "╨" ><!--BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE -->
+<!ENTITY boxhu "┴" ><!--BOX DRAWINGS LIGHT UP AND HORIZONTAL -->
+<!ENTITY boxUL "╝" ><!--BOX DRAWINGS DOUBLE UP AND LEFT -->
+<!ENTITY boxUl "╜" ><!--BOX DRAWINGS UP DOUBLE AND LEFT SINGLE -->
+<!ENTITY boxuL "╛" ><!--BOX DRAWINGS UP SINGLE AND LEFT DOUBLE -->
+<!ENTITY boxul "┘" ><!--BOX DRAWINGS LIGHT UP AND LEFT -->
+<!ENTITY boxUR "╚" ><!--BOX DRAWINGS DOUBLE UP AND RIGHT -->
+<!ENTITY boxUr "╙" ><!--BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE -->
+<!ENTITY boxuR "╘" ><!--BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE -->
+<!ENTITY boxur "└" ><!--BOX DRAWINGS LIGHT UP AND RIGHT -->
+<!ENTITY boxV "║" ><!--BOX DRAWINGS DOUBLE VERTICAL -->
+<!ENTITY boxv "│" ><!--BOX DRAWINGS LIGHT VERTICAL -->
+<!ENTITY boxVH "╬" ><!--BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL -->
+<!ENTITY boxVh "╫" ><!--BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE -->
+<!ENTITY boxvH "╪" ><!--BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE -->
+<!ENTITY boxvh "┼" ><!--BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL -->
+<!ENTITY boxVL "╣" ><!--BOX DRAWINGS DOUBLE VERTICAL AND LEFT -->
+<!ENTITY boxVl "╢" ><!--BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE -->
+<!ENTITY boxvL "╡" ><!--BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE -->
+<!ENTITY boxvl "┤" ><!--BOX DRAWINGS LIGHT VERTICAL AND LEFT -->
+<!ENTITY boxVR "╠" ><!--BOX DRAWINGS DOUBLE VERTICAL AND RIGHT -->
+<!ENTITY boxVr "╟" ><!--BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE -->
+<!ENTITY boxvR "╞" ><!--BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE -->
+<!ENTITY boxvr "├" ><!--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 "А" ><!--CYRILLIC CAPITAL LETTER A -->
+<!ENTITY acy "а" ><!--CYRILLIC SMALL LETTER A -->
+<!ENTITY Bcy "Б" ><!--CYRILLIC CAPITAL LETTER BE -->
+<!ENTITY bcy "б" ><!--CYRILLIC SMALL LETTER BE -->
+<!ENTITY CHcy "Ч" ><!--CYRILLIC CAPITAL LETTER CHE -->
+<!ENTITY chcy "ч" ><!--CYRILLIC SMALL LETTER CHE -->
+<!ENTITY Dcy "Д" ><!--CYRILLIC CAPITAL LETTER DE -->
+<!ENTITY dcy "д" ><!--CYRILLIC SMALL LETTER DE -->
+<!ENTITY Ecy "Э" ><!--CYRILLIC CAPITAL LETTER E -->
+<!ENTITY ecy "э" ><!--CYRILLIC SMALL LETTER E -->
+<!ENTITY Fcy "Ф" ><!--CYRILLIC CAPITAL LETTER EF -->
+<!ENTITY fcy "ф" ><!--CYRILLIC SMALL LETTER EF -->
+<!ENTITY Gcy "Г" ><!--CYRILLIC CAPITAL LETTER GHE -->
+<!ENTITY gcy "г" ><!--CYRILLIC SMALL LETTER GHE -->
+<!ENTITY HARDcy "Ъ" ><!--CYRILLIC CAPITAL LETTER HARD SIGN -->
+<!ENTITY hardcy "ъ" ><!--CYRILLIC SMALL LETTER HARD SIGN -->
+<!ENTITY Icy "И" ><!--CYRILLIC CAPITAL LETTER I -->
+<!ENTITY icy "и" ><!--CYRILLIC SMALL LETTER I -->
+<!ENTITY IEcy "Е" ><!--CYRILLIC CAPITAL LETTER IE -->
+<!ENTITY iecy "е" ><!--CYRILLIC SMALL LETTER IE -->
+<!ENTITY IOcy "Ё" ><!--CYRILLIC CAPITAL LETTER IO -->
+<!ENTITY iocy "ё" ><!--CYRILLIC SMALL LETTER IO -->
+<!ENTITY Jcy "Й" ><!--CYRILLIC CAPITAL LETTER SHORT I -->
+<!ENTITY jcy "й" ><!--CYRILLIC SMALL LETTER SHORT I -->
+<!ENTITY Kcy "К" ><!--CYRILLIC CAPITAL LETTER KA -->
+<!ENTITY kcy "к" ><!--CYRILLIC SMALL LETTER KA -->
+<!ENTITY KHcy "Х" ><!--CYRILLIC CAPITAL LETTER HA -->
+<!ENTITY khcy "х" ><!--CYRILLIC SMALL LETTER HA -->
+<!ENTITY Lcy "Л" ><!--CYRILLIC CAPITAL LETTER EL -->
+<!ENTITY lcy "л" ><!--CYRILLIC SMALL LETTER EL -->
+<!ENTITY Mcy "М" ><!--CYRILLIC CAPITAL LETTER EM -->
+<!ENTITY mcy "м" ><!--CYRILLIC SMALL LETTER EM -->
+<!ENTITY Ncy "Н" ><!--CYRILLIC CAPITAL LETTER EN -->
+<!ENTITY ncy "н" ><!--CYRILLIC SMALL LETTER EN -->
+<!ENTITY numero "№" ><!--NUMERO SIGN -->
+<!ENTITY Ocy "О" ><!--CYRILLIC CAPITAL LETTER O -->
+<!ENTITY ocy "о" ><!--CYRILLIC SMALL LETTER O -->
+<!ENTITY Pcy "П" ><!--CYRILLIC CAPITAL LETTER PE -->
+<!ENTITY pcy "п" ><!--CYRILLIC SMALL LETTER PE -->
+<!ENTITY Rcy "Р" ><!--CYRILLIC CAPITAL LETTER ER -->
+<!ENTITY rcy "р" ><!--CYRILLIC SMALL LETTER ER -->
+<!ENTITY Scy "С" ><!--CYRILLIC CAPITAL LETTER ES -->
+<!ENTITY scy "с" ><!--CYRILLIC SMALL LETTER ES -->
+<!ENTITY SHCHcy "Щ" ><!--CYRILLIC CAPITAL LETTER SHCHA -->
+<!ENTITY shchcy "щ" ><!--CYRILLIC SMALL LETTER SHCHA -->
+<!ENTITY SHcy "Ш" ><!--CYRILLIC CAPITAL LETTER SHA -->
+<!ENTITY shcy "ш" ><!--CYRILLIC SMALL LETTER SHA -->
+<!ENTITY SOFTcy "Ь" ><!--CYRILLIC CAPITAL LETTER SOFT SIGN -->
+<!ENTITY softcy "ь" ><!--CYRILLIC SMALL LETTER SOFT SIGN -->
+<!ENTITY Tcy "Т" ><!--CYRILLIC CAPITAL LETTER TE -->
+<!ENTITY tcy "т" ><!--CYRILLIC SMALL LETTER TE -->
+<!ENTITY TScy "Ц" ><!--CYRILLIC CAPITAL LETTER TSE -->
+<!ENTITY tscy "ц" ><!--CYRILLIC SMALL LETTER TSE -->
+<!ENTITY Ucy "У" ><!--CYRILLIC CAPITAL LETTER U -->
+<!ENTITY ucy "у" ><!--CYRILLIC SMALL LETTER U -->
+<!ENTITY Vcy "В" ><!--CYRILLIC CAPITAL LETTER VE -->
+<!ENTITY vcy "в" ><!--CYRILLIC SMALL LETTER VE -->
+<!ENTITY YAcy "Я" ><!--CYRILLIC CAPITAL LETTER YA -->
+<!ENTITY yacy "я" ><!--CYRILLIC SMALL LETTER YA -->
+<!ENTITY Ycy "Ы" ><!--CYRILLIC CAPITAL LETTER YERU -->
+<!ENTITY ycy "ы" ><!--CYRILLIC SMALL LETTER YERU -->
+<!ENTITY YUcy "Ю" ><!--CYRILLIC CAPITAL LETTER YU -->
+<!ENTITY yucy "ю" ><!--CYRILLIC SMALL LETTER YU -->
+<!ENTITY Zcy "З" ><!--CYRILLIC CAPITAL LETTER ZE -->
+<!ENTITY zcy "з" ><!--CYRILLIC SMALL LETTER ZE -->
+<!ENTITY ZHcy "Ж" ><!--CYRILLIC CAPITAL LETTER ZHE -->
+<!ENTITY zhcy "ж" ><!--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 "Ђ" ><!--CYRILLIC CAPITAL LETTER DJE -->
+<!ENTITY djcy "ђ" ><!--CYRILLIC SMALL LETTER DJE -->
+<!ENTITY DScy "Ѕ" ><!--CYRILLIC CAPITAL LETTER DZE -->
+<!ENTITY dscy "ѕ" ><!--CYRILLIC SMALL LETTER DZE -->
+<!ENTITY DZcy "Џ" ><!--CYRILLIC CAPITAL LETTER DZHE -->
+<!ENTITY dzcy "џ" ><!--CYRILLIC SMALL LETTER DZHE -->
+<!ENTITY GJcy "Ѓ" ><!--CYRILLIC CAPITAL LETTER GJE -->
+<!ENTITY gjcy "ѓ" ><!--CYRILLIC SMALL LETTER GJE -->
+<!ENTITY Iukcy "І" ><!--CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I -->
+<!ENTITY iukcy "і" ><!--CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
+<!ENTITY Jsercy "Ј" ><!--CYRILLIC CAPITAL LETTER JE -->
+<!ENTITY jsercy "ј" ><!--CYRILLIC SMALL LETTER JE -->
+<!ENTITY Jukcy "Є" ><!--CYRILLIC CAPITAL LETTER UKRAINIAN IE -->
+<!ENTITY jukcy "є" ><!--CYRILLIC SMALL LETTER UKRAINIAN IE -->
+<!ENTITY KJcy "Ќ" ><!--CYRILLIC CAPITAL LETTER KJE -->
+<!ENTITY kjcy "ќ" ><!--CYRILLIC SMALL LETTER KJE -->
+<!ENTITY LJcy "Љ" ><!--CYRILLIC CAPITAL LETTER LJE -->
+<!ENTITY ljcy "љ" ><!--CYRILLIC SMALL LETTER LJE -->
+<!ENTITY NJcy "Њ" ><!--CYRILLIC CAPITAL LETTER NJE -->
+<!ENTITY njcy "њ" ><!--CYRILLIC SMALL LETTER NJE -->
+<!ENTITY TSHcy "Ћ" ><!--CYRILLIC CAPITAL LETTER TSHE -->
+<!ENTITY tshcy "ћ" ><!--CYRILLIC SMALL LETTER TSHE -->
+<!ENTITY Ubrcy "Ў" ><!--CYRILLIC CAPITAL LETTER SHORT U -->
+<!ENTITY ubrcy "ў" ><!--CYRILLIC SMALL LETTER SHORT U -->
+<!ENTITY YIcy "Ї" ><!--CYRILLIC CAPITAL LETTER YI -->
+<!ENTITY yicy "ї" ><!--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 "´" ><!--ACUTE ACCENT -->
+<!ENTITY breve "˘" ><!--BREVE -->
+<!ENTITY caron "ˇ" ><!--CARON -->
+<!ENTITY cedil "¸" ><!--CEDILLA -->
+<!ENTITY circ "ˆ" ><!--MODIFIER LETTER CIRCUMFLEX ACCENT -->
+<!ENTITY dblac "˝" ><!--DOUBLE ACUTE ACCENT -->
+<!ENTITY die "¨" ><!--DIAERESIS -->
+<!ENTITY dot "˙" ><!--DOT ABOVE -->
+<!ENTITY grave "`" ><!--GRAVE ACCENT -->
+<!ENTITY macr "¯" ><!--MACRON -->
+<!ENTITY ogon "˛" ><!--OGONEK -->
+<!ENTITY ring "˚" ><!--RING ABOVE -->
+<!ENTITY tilde "˜" ><!--SMALL TILDE -->
+<!ENTITY uml "¨" ><!--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 "Α" ><!--GREEK CAPITAL LETTER ALPHA -->
+<!ENTITY agr "α" ><!--GREEK SMALL LETTER ALPHA -->
+<!ENTITY Bgr "Β" ><!--GREEK CAPITAL LETTER BETA -->
+<!ENTITY bgr "β" ><!--GREEK SMALL LETTER BETA -->
+<!ENTITY Dgr "Δ" ><!--GREEK CAPITAL LETTER DELTA -->
+<!ENTITY dgr "δ" ><!--GREEK SMALL LETTER DELTA -->
+<!ENTITY EEgr "Η" ><!--GREEK CAPITAL LETTER ETA -->
+<!ENTITY eegr "η" ><!--GREEK SMALL LETTER ETA -->
+<!ENTITY Egr "Ε" ><!--GREEK CAPITAL LETTER EPSILON -->
+<!ENTITY egr "ε" ><!--GREEK SMALL LETTER EPSILON -->
+<!ENTITY Ggr "Γ" ><!--GREEK CAPITAL LETTER GAMMA -->
+<!ENTITY ggr "γ" ><!--GREEK SMALL LETTER GAMMA -->
+<!ENTITY Igr "Ι" ><!--GREEK CAPITAL LETTER IOTA -->
+<!ENTITY igr "ι" ><!--GREEK SMALL LETTER IOTA -->
+<!ENTITY Kgr "Κ" ><!--GREEK CAPITAL LETTER KAPPA -->
+<!ENTITY kgr "κ" ><!--GREEK SMALL LETTER KAPPA -->
+<!ENTITY KHgr "Χ" ><!--GREEK CAPITAL LETTER CHI -->
+<!ENTITY khgr "χ" ><!--GREEK SMALL LETTER CHI -->
+<!ENTITY Lgr "Λ" ><!--GREEK CAPITAL LETTER LAMDA -->
+<!ENTITY lgr "λ" ><!--GREEK SMALL LETTER LAMDA -->
+<!ENTITY Mgr "Μ" ><!--GREEK CAPITAL LETTER MU -->
+<!ENTITY mgr "μ" ><!--GREEK SMALL LETTER MU -->
+<!ENTITY Ngr "Ν" ><!--GREEK CAPITAL LETTER NU -->
+<!ENTITY ngr "ν" ><!--GREEK SMALL LETTER NU -->
+<!ENTITY Ogr "Ο" ><!--GREEK CAPITAL LETTER OMICRON -->
+<!ENTITY ogr "ο" ><!--GREEK SMALL LETTER OMICRON -->
+<!ENTITY OHgr "Ω" ><!--GREEK CAPITAL LETTER OMEGA -->
+<!ENTITY ohgr "ω" ><!--GREEK SMALL LETTER OMEGA -->
+<!ENTITY Pgr "Π" ><!--GREEK CAPITAL LETTER PI -->
+<!ENTITY pgr "π" ><!--GREEK SMALL LETTER PI -->
+<!ENTITY PHgr "Φ" ><!--GREEK CAPITAL LETTER PHI -->
+<!ENTITY phgr "φ" ><!--GREEK SMALL LETTER PHI -->
+<!ENTITY PSgr "Ψ" ><!--GREEK CAPITAL LETTER PSI -->
+<!ENTITY psgr "ψ" ><!--GREEK SMALL LETTER PSI -->
+<!ENTITY Rgr "Ρ" ><!--GREEK CAPITAL LETTER RHO -->
+<!ENTITY rgr "ρ" ><!--GREEK SMALL LETTER RHO -->
+<!ENTITY sfgr "ς" ><!--GREEK SMALL LETTER FINAL SIGMA -->
+<!ENTITY Sgr "Σ" ><!--GREEK CAPITAL LETTER SIGMA -->
+<!ENTITY sgr "σ" ><!--GREEK SMALL LETTER SIGMA -->
+<!ENTITY Tgr "Τ" ><!--GREEK CAPITAL LETTER TAU -->
+<!ENTITY tgr "τ" ><!--GREEK SMALL LETTER TAU -->
+<!ENTITY THgr "Θ" ><!--GREEK CAPITAL LETTER THETA -->
+<!ENTITY thgr "θ" ><!--GREEK SMALL LETTER THETA -->
+<!ENTITY Ugr "Υ" ><!--GREEK CAPITAL LETTER UPSILON -->
+<!ENTITY ugr "υ" ><!--GREEK SMALL LETTER UPSILON -->
+<!ENTITY Xgr "Ξ" ><!--GREEK CAPITAL LETTER XI -->
+<!ENTITY xgr "ξ" ><!--GREEK SMALL LETTER XI -->
+<!ENTITY Zgr "Ζ" ><!--GREEK CAPITAL LETTER ZETA -->
+<!ENTITY zgr "ζ" ><!--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 "Ά" ><!--GREEK CAPITAL LETTER ALPHA WITH TONOS -->
+<!ENTITY aacgr "ά" ><!--GREEK SMALL LETTER ALPHA WITH TONOS -->
+<!ENTITY Eacgr "Έ" ><!--GREEK CAPITAL LETTER EPSILON WITH TONOS -->
+<!ENTITY eacgr "έ" ><!--GREEK SMALL LETTER EPSILON WITH TONOS -->
+<!ENTITY EEacgr "Ή" ><!--GREEK CAPITAL LETTER ETA WITH TONOS -->
+<!ENTITY eeacgr "ή" ><!--GREEK SMALL LETTER ETA WITH TONOS -->
+<!ENTITY Iacgr "Ί" ><!--GREEK CAPITAL LETTER IOTA WITH TONOS -->
+<!ENTITY iacgr "ί" ><!--GREEK SMALL LETTER IOTA WITH TONOS -->
+<!ENTITY idiagr "ΐ" ><!--GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS -->
+<!ENTITY Idigr "Ϊ" ><!--GREEK CAPITAL LETTER IOTA WITH DIALYTIKA -->
+<!ENTITY idigr "ϊ" ><!--GREEK SMALL LETTER IOTA WITH DIALYTIKA -->
+<!ENTITY Oacgr "Ό" ><!--GREEK CAPITAL LETTER OMICRON WITH TONOS -->
+<!ENTITY oacgr "ό" ><!--GREEK SMALL LETTER OMICRON WITH TONOS -->
+<!ENTITY OHacgr "Ώ" ><!--GREEK CAPITAL LETTER OMEGA WITH TONOS -->
+<!ENTITY ohacgr "ώ" ><!--GREEK SMALL LETTER OMEGA WITH TONOS -->
+<!ENTITY Uacgr "Ύ" ><!--GREEK CAPITAL LETTER UPSILON WITH TONOS -->
+<!ENTITY uacgr "ύ" ><!--GREEK SMALL LETTER UPSILON WITH TONOS -->
+<!ENTITY udiagr "ΰ" ><!--GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS -->
+<!ENTITY Udigr "Ϋ" ><!--GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA -->
+<!ENTITY udigr "ϋ" ><!--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 "α" ><!--GREEK SMALL LETTER ALPHA -->
+<!ENTITY beta "β" ><!--GREEK SMALL LETTER BETA -->
+<!ENTITY chi "χ" ><!--GREEK SMALL LETTER CHI -->
+<!ENTITY Delta "Δ" ><!--GREEK CAPITAL LETTER DELTA -->
+<!ENTITY delta "δ" ><!--GREEK SMALL LETTER DELTA -->
+<!ENTITY epsi "ϵ" ><!--GREEK LUNATE EPSILON SYMBOL -->
+<!ENTITY epsis "ϵ" ><!--GREEK LUNATE EPSILON SYMBOL -->
+<!ENTITY epsiv "ε" ><!--GREEK SMALL LETTER EPSILON -->
+<!ENTITY eta "η" ><!--GREEK SMALL LETTER ETA -->
+<!ENTITY Gamma "Γ" ><!--GREEK CAPITAL LETTER GAMMA -->
+<!ENTITY gamma "γ" ><!--GREEK SMALL LETTER GAMMA -->
+<!ENTITY gammad "ϝ" ><!--GREEK SMALL LETTER DIGAMMA -->
+<!ENTITY iota "ι" ><!--GREEK SMALL LETTER IOTA -->
+<!ENTITY kappa "κ" ><!--GREEK SMALL LETTER KAPPA -->
+<!ENTITY kappav "ϰ" ><!--GREEK KAPPA SYMBOL -->
+<!ENTITY Lambda "Λ" ><!--GREEK CAPITAL LETTER LAMDA -->
+<!ENTITY lambda "λ" ><!--GREEK SMALL LETTER LAMDA -->
+<!ENTITY mu "μ" ><!--GREEK SMALL LETTER MU -->
+<!ENTITY nu "ν" ><!--GREEK SMALL LETTER NU -->
+<!ENTITY Omega "Ω" ><!--GREEK CAPITAL LETTER OMEGA -->
+<!ENTITY omega "ω" ><!--GREEK SMALL LETTER OMEGA -->
+<!ENTITY Phi "Φ" ><!--GREEK CAPITAL LETTER PHI -->
+<!ENTITY phis "ϕ" ><!--GREEK PHI SYMBOL -->
+<!ENTITY phiv "φ" ><!--GREEK SMALL LETTER PHI -->
+<!ENTITY Pi "Π" ><!--GREEK CAPITAL LETTER PI -->
+<!ENTITY pi "π" ><!--GREEK SMALL LETTER PI -->
+<!ENTITY piv "ϖ" ><!--GREEK PI SYMBOL -->
+<!ENTITY Psi "Ψ" ><!--GREEK CAPITAL LETTER PSI -->
+<!ENTITY psi "ψ" ><!--GREEK SMALL LETTER PSI -->
+<!ENTITY rho "ρ" ><!--GREEK SMALL LETTER RHO -->
+<!ENTITY rhov "ϱ" ><!--GREEK RHO SYMBOL -->
+<!ENTITY Sigma "Σ" ><!--GREEK CAPITAL LETTER SIGMA -->
+<!ENTITY sigma "σ" ><!--GREEK SMALL LETTER SIGMA -->
+<!ENTITY sigmav "ς" ><!--GREEK SMALL LETTER FINAL SIGMA -->
+<!ENTITY tau "τ" ><!--GREEK SMALL LETTER TAU -->
+<!ENTITY Theta "Θ" ><!--GREEK CAPITAL LETTER THETA -->
+<!ENTITY thetas "θ" ><!--GREEK SMALL LETTER THETA -->
+<!ENTITY thetav "ϑ" ><!--GREEK THETA SYMBOL -->
+<!ENTITY Upsi "ϒ" ><!--GREEK UPSILON WITH HOOK SYMBOL -->
+<!ENTITY upsi "υ" ><!--GREEK SMALL LETTER UPSILON -->
+<!ENTITY Xi "Ξ" ><!--GREEK CAPITAL LETTER XI -->
+<!ENTITY xi "ξ" ><!--GREEK SMALL LETTER XI -->
+<!ENTITY zeta "ζ" ><!--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 "Á" ><!--LATIN CAPITAL LETTER A WITH ACUTE -->
+<!ENTITY aacute "á" ><!--LATIN SMALL LETTER A WITH ACUTE -->
+<!ENTITY Acirc "Â" ><!--LATIN CAPITAL LETTER A WITH CIRCUMFLEX -->
+<!ENTITY acirc "â" ><!--LATIN SMALL LETTER A WITH CIRCUMFLEX -->
+<!ENTITY AElig "Æ" ><!--LATIN CAPITAL LETTER AE -->
+<!ENTITY aelig "æ" ><!--LATIN SMALL LETTER AE -->
+<!ENTITY Agrave "À" ><!--LATIN CAPITAL LETTER A WITH GRAVE -->
+<!ENTITY agrave "à" ><!--LATIN SMALL LETTER A WITH GRAVE -->
+<!ENTITY Aring "Å" ><!--LATIN CAPITAL LETTER A WITH RING ABOVE -->
+<!ENTITY aring "å" ><!--LATIN SMALL LETTER A WITH RING ABOVE -->
+<!ENTITY Atilde "Ã" ><!--LATIN CAPITAL LETTER A WITH TILDE -->
+<!ENTITY atilde "ã" ><!--LATIN SMALL LETTER A WITH TILDE -->
+<!ENTITY Auml "Ä" ><!--LATIN CAPITAL LETTER A WITH DIAERESIS -->
+<!ENTITY auml "ä" ><!--LATIN SMALL LETTER A WITH DIAERESIS -->
+<!ENTITY Ccedil "Ç" ><!--LATIN CAPITAL LETTER C WITH CEDILLA -->
+<!ENTITY ccedil "ç" ><!--LATIN SMALL LETTER C WITH CEDILLA -->
+<!ENTITY Eacute "É" ><!--LATIN CAPITAL LETTER E WITH ACUTE -->
+<!ENTITY eacute "é" ><!--LATIN SMALL LETTER E WITH ACUTE -->
+<!ENTITY Ecirc "Ê" ><!--LATIN CAPITAL LETTER E WITH CIRCUMFLEX -->
+<!ENTITY ecirc "ê" ><!--LATIN SMALL LETTER E WITH CIRCUMFLEX -->
+<!ENTITY Egrave "È" ><!--LATIN CAPITAL LETTER E WITH GRAVE -->
+<!ENTITY egrave "è" ><!--LATIN SMALL LETTER E WITH GRAVE -->
+<!ENTITY ETH "Ð" ><!--LATIN CAPITAL LETTER ETH -->
+<!ENTITY eth "ð" ><!--LATIN SMALL LETTER ETH -->
+<!ENTITY Euml "Ë" ><!--LATIN CAPITAL LETTER E WITH DIAERESIS -->
+<!ENTITY euml "ë" ><!--LATIN SMALL LETTER E WITH DIAERESIS -->
+<!ENTITY Iacute "Í" ><!--LATIN CAPITAL LETTER I WITH ACUTE -->
+<!ENTITY iacute "í" ><!--LATIN SMALL LETTER I WITH ACUTE -->
+<!ENTITY Icirc "Î" ><!--LATIN CAPITAL LETTER I WITH CIRCUMFLEX -->
+<!ENTITY icirc "î" ><!--LATIN SMALL LETTER I WITH CIRCUMFLEX -->
+<!ENTITY Igrave "Ì" ><!--LATIN CAPITAL LETTER I WITH GRAVE -->
+<!ENTITY igrave "ì" ><!--LATIN SMALL LETTER I WITH GRAVE -->
+<!ENTITY Iuml "Ï" ><!--LATIN CAPITAL LETTER I WITH DIAERESIS -->
+<!ENTITY iuml "ï" ><!--LATIN SMALL LETTER I WITH DIAERESIS -->
+<!ENTITY Ntilde "Ñ" ><!--LATIN CAPITAL LETTER N WITH TILDE -->
+<!ENTITY ntilde "ñ" ><!--LATIN SMALL LETTER N WITH TILDE -->
+<!ENTITY Oacute "Ó" ><!--LATIN CAPITAL LETTER O WITH ACUTE -->
+<!ENTITY oacute "ó" ><!--LATIN SMALL LETTER O WITH ACUTE -->
+<!ENTITY Ocirc "Ô" ><!--LATIN CAPITAL LETTER O WITH CIRCUMFLEX -->
+<!ENTITY ocirc "ô" ><!--LATIN SMALL LETTER O WITH CIRCUMFLEX -->
+<!ENTITY Ograve "Ò" ><!--LATIN CAPITAL LETTER O WITH GRAVE -->
+<!ENTITY ograve "ò" ><!--LATIN SMALL LETTER O WITH GRAVE -->
+<!ENTITY Oslash "Ø" ><!--LATIN CAPITAL LETTER O WITH STROKE -->
+<!ENTITY oslash "ø" ><!--LATIN SMALL LETTER O WITH STROKE -->
+<!ENTITY Otilde "Õ" ><!--LATIN CAPITAL LETTER O WITH TILDE -->
+<!ENTITY otilde "õ" ><!--LATIN SMALL LETTER O WITH TILDE -->
+<!ENTITY Ouml "Ö" ><!--LATIN CAPITAL LETTER O WITH DIAERESIS -->
+<!ENTITY ouml "ö" ><!--LATIN SMALL LETTER O WITH DIAERESIS -->
+<!ENTITY szlig "ß" ><!--LATIN SMALL LETTER SHARP S -->
+<!ENTITY THORN "Þ" ><!--LATIN CAPITAL LETTER THORN -->
+<!ENTITY thorn "þ" ><!--LATIN SMALL LETTER THORN -->
+<!ENTITY Uacute "Ú" ><!--LATIN CAPITAL LETTER U WITH ACUTE -->
+<!ENTITY uacute "ú" ><!--LATIN SMALL LETTER U WITH ACUTE -->
+<!ENTITY Ucirc "Û" ><!--LATIN CAPITAL LETTER U WITH CIRCUMFLEX -->
+<!ENTITY ucirc "û" ><!--LATIN SMALL LETTER U WITH CIRCUMFLEX -->
+<!ENTITY Ugrave "Ù" ><!--LATIN CAPITAL LETTER U WITH GRAVE -->
+<!ENTITY ugrave "ù" ><!--LATIN SMALL LETTER U WITH GRAVE -->
+<!ENTITY Uuml "Ü" ><!--LATIN CAPITAL LETTER U WITH DIAERESIS -->
+<!ENTITY uuml "ü" ><!--LATIN SMALL LETTER U WITH DIAERESIS -->
+<!ENTITY Yacute "Ý" ><!--LATIN CAPITAL LETTER Y WITH ACUTE -->
+<!ENTITY yacute "ý" ><!--LATIN SMALL LETTER Y WITH ACUTE -->
+<!ENTITY yuml "ÿ" ><!--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;" ><!--AMPERSAND -->
+<!ENTITY apos "'" ><!--APOSTROPHE -->
+<!ENTITY ast "*" ><!--ASTERISK -->
+<!ENTITY brvbar "¦" ><!--BROKEN BAR -->
+<!ENTITY bsol "\" ><!--REVERSE SOLIDUS -->
+<!ENTITY cent "¢" ><!--CENT SIGN -->
+<!ENTITY colon ":" ><!--COLON -->
+<!ENTITY comma "," ><!--COMMA -->
+<!ENTITY commat "@" ><!--COMMERCIAL AT -->
+<!ENTITY copy "©" ><!--COPYRIGHT SIGN -->
+<!ENTITY curren "¤" ><!--CURRENCY SIGN -->
+<!ENTITY darr "↓" ><!--DOWNWARDS ARROW -->
+<!ENTITY deg "°" ><!--DEGREE SIGN -->
+<!ENTITY divide "÷" ><!--DIVISION SIGN -->
+<!ENTITY dollar "$" ><!--DOLLAR SIGN -->
+<!ENTITY equals "=" ><!--EQUALS SIGN -->
+<!ENTITY excl "!" ><!--EXCLAMATION MARK -->
+<!ENTITY frac12 "½" ><!--VULGAR FRACTION ONE HALF -->
+<!ENTITY frac14 "¼" ><!--VULGAR FRACTION ONE QUARTER -->
+<!ENTITY frac18 "⅛" ><!--VULGAR FRACTION ONE EIGHTH -->
+<!ENTITY frac34 "¾" ><!--VULGAR FRACTION THREE QUARTERS -->
+<!ENTITY frac38 "⅜" ><!--VULGAR FRACTION THREE EIGHTHS -->
+<!ENTITY frac58 "⅝" ><!--VULGAR FRACTION FIVE EIGHTHS -->
+<!ENTITY frac78 "⅞" ><!--VULGAR FRACTION SEVEN EIGHTHS -->
+<!ENTITY gt ">" ><!--GREATER-THAN SIGN -->
+<!ENTITY half "½" ><!--VULGAR FRACTION ONE HALF -->
+<!ENTITY horbar "―" ><!--HORIZONTAL BAR -->
+<!ENTITY hyphen "‐" ><!--HYPHEN -->
+<!ENTITY iexcl "¡" ><!--INVERTED EXCLAMATION MARK -->
+<!ENTITY iquest "¿" ><!--INVERTED QUESTION MARK -->
+<!ENTITY laquo "«" ><!--LEFT-POINTING DOUBLE ANGLE QUOTATION MARK -->
+<!ENTITY larr "←" ><!--LEFTWARDS ARROW -->
+<!ENTITY lcub "{" ><!--LEFT CURLY BRACKET -->
+<!ENTITY ldquo "“" ><!--LEFT DOUBLE QUOTATION MARK -->
+<!ENTITY lowbar "_" ><!--LOW LINE -->
+<!ENTITY lpar "(" ><!--LEFT PARENTHESIS -->
+<!ENTITY lsqb "[" ><!--LEFT SQUARE BRACKET -->
+<!ENTITY lsquo "‘" ><!--LEFT SINGLE QUOTATION MARK -->
+<!ENTITY lt "&#60;" ><!--LESS-THAN SIGN -->
+<!ENTITY micro "µ" ><!--MICRO SIGN -->
+<!ENTITY middot "·" ><!--MIDDLE DOT -->
+<!ENTITY nbsp " " ><!--NO-BREAK SPACE -->
+<!ENTITY not "¬" ><!--NOT SIGN -->
+<!ENTITY num "#" ><!--NUMBER SIGN -->
+<!ENTITY ohm "Ω" ><!--OHM SIGN -->
+<!ENTITY ordf "ª" ><!--FEMININE ORDINAL INDICATOR -->
+<!ENTITY ordm "º" ><!--MASCULINE ORDINAL INDICATOR -->
+<!ENTITY para "¶" ><!--PILCROW SIGN -->
+<!ENTITY percnt "%" ><!--PERCENT SIGN -->
+<!ENTITY period "." ><!--FULL STOP -->
+<!ENTITY plus "+" ><!--PLUS SIGN -->
+<!ENTITY plusmn "±" ><!--PLUS-MINUS SIGN -->
+<!ENTITY pound "£" ><!--POUND SIGN -->
+<!ENTITY quest "?" ><!--QUESTION MARK -->
+<!ENTITY quot """ ><!--QUOTATION MARK -->
+<!ENTITY raquo "»" ><!--RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
+<!ENTITY rarr "→" ><!--RIGHTWARDS ARROW -->
+<!ENTITY rcub "}" ><!--RIGHT CURLY BRACKET -->
+<!ENTITY rdquo "”" ><!--RIGHT DOUBLE QUOTATION MARK -->
+<!ENTITY reg "®" ><!--REGISTERED SIGN -->
+<!ENTITY rpar ")" ><!--RIGHT PARENTHESIS -->
+<!ENTITY rsqb "]" ><!--RIGHT SQUARE BRACKET -->
+<!ENTITY rsquo "’" ><!--RIGHT SINGLE QUOTATION MARK -->
+<!ENTITY sect "§" ><!--SECTION SIGN -->
+<!ENTITY semi ";" ><!--SEMICOLON -->
+<!ENTITY shy "­" ><!--SOFT HYPHEN -->
+<!ENTITY sol "/" ><!--SOLIDUS -->
+<!ENTITY sung "♪" ><!--EIGHTH NOTE -->
+<!ENTITY sup1 "¹" ><!--SUPERSCRIPT ONE -->
+<!ENTITY sup2 "²" ><!--SUPERSCRIPT TWO -->
+<!ENTITY sup3 "³" ><!--SUPERSCRIPT THREE -->
+<!ENTITY times "×" ><!--MULTIPLICATION SIGN -->
+<!ENTITY trade "™" ><!--TRADE MARK SIGN -->
+<!ENTITY uarr "↑" ><!--UPWARDS ARROW -->
+<!ENTITY verbar "|" ><!--VERTICAL LINE -->
+<!ENTITY yen "¥" ><!--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 "␣" ><!--OPEN BOX -->
+<!ENTITY blk12 "▒" ><!--MEDIUM SHADE -->
+<!ENTITY blk14 "░" ><!--LIGHT SHADE -->
+<!ENTITY blk34 "▓" ><!--DARK SHADE -->
+<!ENTITY block "█" ><!--FULL BLOCK -->
+<!ENTITY bull "•" ><!--BULLET -->
+<!ENTITY caret "⁁" ><!--CARET INSERTION POINT -->
+<!ENTITY check "✓" ><!--CHECK MARK -->
+<!ENTITY cir "○" ><!--WHITE CIRCLE -->
+<!ENTITY clubs "♣" ><!--BLACK CLUB SUIT -->
+<!ENTITY copysr "℗" ><!--SOUND RECORDING COPYRIGHT -->
+<!ENTITY cross "✗" ><!--BALLOT X -->
+<!ENTITY Dagger "‡" ><!--DOUBLE DAGGER -->
+<!ENTITY dagger "†" ><!--DAGGER -->
+<!ENTITY dash "‐" ><!--HYPHEN -->
+<!ENTITY diams "♦" ><!--BLACK DIAMOND SUIT -->
+<!ENTITY dlcrop "⌍" ><!--BOTTOM LEFT CROP -->
+<!ENTITY drcrop "⌌" ><!--BOTTOM RIGHT CROP -->
+<!ENTITY dtri "▿" ><!--WHITE DOWN-POINTING SMALL TRIANGLE -->
+<!ENTITY dtrif "▾" ><!--BLACK DOWN-POINTING SMALL TRIANGLE -->
+<!ENTITY emsp " " ><!--EM SPACE -->
+<!ENTITY emsp13 " " ><!--THREE-PER-EM SPACE -->
+<!ENTITY emsp14 " " ><!--FOUR-PER-EM SPACE -->
+<!ENTITY ensp " " ><!--EN SPACE -->
+<!ENTITY female "♀" ><!--FEMALE SIGN -->
+<!ENTITY ffilig "ffi" ><!--LATIN SMALL LIGATURE FFI -->
+<!ENTITY fflig "ff" ><!--LATIN SMALL LIGATURE FF -->
+<!ENTITY ffllig "ffl" ><!--LATIN SMALL LIGATURE FFL -->
+<!ENTITY filig "fi" ><!--LATIN SMALL LIGATURE FI -->
+<!ENTITY flat "♭" ><!--MUSIC FLAT SIGN -->
+<!ENTITY fllig "fl" ><!--LATIN SMALL LIGATURE FL -->
+<!ENTITY frac13 "⅓" ><!--VULGAR FRACTION ONE THIRD -->
+<!ENTITY frac15 "⅕" ><!--VULGAR FRACTION ONE FIFTH -->
+<!ENTITY frac16 "⅙" ><!--VULGAR FRACTION ONE SIXTH -->
+<!ENTITY frac23 "⅔" ><!--VULGAR FRACTION TWO THIRDS -->
+<!ENTITY frac25 "⅖" ><!--VULGAR FRACTION TWO FIFTHS -->
+<!ENTITY frac35 "⅗" ><!--VULGAR FRACTION THREE FIFTHS -->
+<!ENTITY frac45 "⅘" ><!--VULGAR FRACTION FOUR FIFTHS -->
+<!ENTITY frac56 "⅚" ><!--VULGAR FRACTION FIVE SIXTHS -->
+<!ENTITY hairsp " " ><!--HAIR SPACE -->
+<!ENTITY hearts "♥" ><!--BLACK HEART SUIT -->
+<!ENTITY hellip "…" ><!--HORIZONTAL ELLIPSIS -->
+<!ENTITY hybull "⁃" ><!--HYPHEN BULLET -->
+<!ENTITY incare "℅" ><!--CARE OF -->
+<!ENTITY ldquor "„" ><!--DOUBLE LOW-9 QUOTATION MARK -->
+<!ENTITY lhblk "▄" ><!--LOWER HALF BLOCK -->
+<!ENTITY loz "◊" ><!--LOZENGE -->
+<!ENTITY lozf "⧫" ><!--BLACK LOZENGE -->
+<!ENTITY lsquor "‚" ><!--SINGLE LOW-9 QUOTATION MARK -->
+<!ENTITY ltri "◃" ><!--WHITE LEFT-POINTING SMALL TRIANGLE -->
+<!ENTITY ltrif "◂" ><!--BLACK LEFT-POINTING SMALL TRIANGLE -->
+<!ENTITY male "♂" ><!--MALE SIGN -->
+<!ENTITY malt "✠" ><!--MALTESE CROSS -->
+<!ENTITY marker "▮" ><!--BLACK VERTICAL RECTANGLE -->
+<!ENTITY mdash "—" ><!--EM DASH -->
+<!ENTITY mldr "…" ><!--HORIZONTAL ELLIPSIS -->
+<!ENTITY natur "♮" ><!--MUSIC NATURAL SIGN -->
+<!ENTITY ndash "–" ><!--EN DASH -->
+<!ENTITY nldr "‥" ><!--TWO DOT LEADER -->
+<!ENTITY numsp " " ><!--FIGURE SPACE -->
+<!ENTITY phone "☎" ><!--BLACK TELEPHONE -->
+<!ENTITY puncsp " " ><!--PUNCTUATION SPACE -->
+<!ENTITY rdquor "”" ><!--RIGHT DOUBLE QUOTATION MARK -->
+<!ENTITY rect "▭" ><!--WHITE RECTANGLE -->
+<!ENTITY rsquor "’" ><!--RIGHT SINGLE QUOTATION MARK -->
+<!ENTITY rtri "▹" ><!--WHITE RIGHT-POINTING SMALL TRIANGLE -->
+<!ENTITY rtrif "▸" ><!--BLACK RIGHT-POINTING SMALL TRIANGLE -->
+<!ENTITY rx "℞" ><!--PRESCRIPTION TAKE -->
+<!ENTITY sext "✶" ><!--SIX POINTED BLACK STAR -->
+<!ENTITY sharp "♯" ><!--MUSIC SHARP SIGN -->
+<!ENTITY spades "♠" ><!--BLACK SPADE SUIT -->
+<!ENTITY squ "□" ><!--WHITE SQUARE -->
+<!ENTITY squf "▪" ><!--BLACK SMALL SQUARE -->
+<!ENTITY star "☆" ><!--WHITE STAR -->
+<!ENTITY starf "★" ><!--BLACK STAR -->
+<!ENTITY target "⌖" ><!--POSITION INDICATOR -->
+<!ENTITY telrec "⌕" ><!--TELEPHONE RECORDER -->
+<!ENTITY thinsp " " ><!--THIN SPACE -->
+<!ENTITY uhblk "▀" ><!--UPPER HALF BLOCK -->
+<!ENTITY ulcrop "⌏" ><!--TOP LEFT CROP -->
+<!ENTITY urcrop "⌎" ><!--TOP RIGHT CROP -->
+<!ENTITY utri "▵" ><!--WHITE UP-POINTING SMALL TRIANGLE -->
+<!ENTITY utrif "▴" ><!--BLACK UP-POINTING SMALL TRIANGLE -->
+<!ENTITY vellip "⋮" ><!--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 "ℵ" ><!--ALEF SYMBOL -->
+<!ENTITY and "∧" ><!--LOGICAL AND -->
+<!ENTITY ang90 "∟" ><!--RIGHT ANGLE -->
+<!ENTITY angsph "∢" ><!--SPHERICAL ANGLE -->
+<!ENTITY angst "Å" ><!--ANGSTROM SIGN -->
+<!ENTITY ap "≈" ><!--ALMOST EQUAL TO -->
+<!ENTITY becaus "∵" ><!--BECAUSE -->
+<!ENTITY bernou "ℬ" ><!--SCRIPT CAPITAL B -->
+<!ENTITY bottom "⊥" ><!--UP TACK -->
+<!ENTITY cap "∩" ><!--INTERSECTION -->
+<!ENTITY compfn "∘" ><!--RING OPERATOR -->
+<!ENTITY cong "≅" ><!--APPROXIMATELY EQUAL TO -->
+<!ENTITY conint "∮" ><!--CONTOUR INTEGRAL -->
+<!ENTITY cup "∪" ><!--UNION -->
+<!ENTITY Dot "¨" ><!--DIAERESIS -->
+<!ENTITY DotDot " ⃜" ><!--COMBINING FOUR DOTS ABOVE -->
+<!ENTITY equiv "≡" ><!--IDENTICAL TO -->
+<!ENTITY exist "∃" ><!--THERE EXISTS -->
+<!ENTITY fnof "ƒ" ><!--LATIN SMALL LETTER F WITH HOOK -->
+<!ENTITY forall "∀" ><!--FOR ALL -->
+<!ENTITY ge "≥" ><!--GREATER-THAN OR EQUAL TO -->
+<!ENTITY hamilt "ℋ" ><!--SCRIPT CAPITAL H -->
+<!ENTITY iff "⇔" ><!--LEFT RIGHT DOUBLE ARROW -->
+<!ENTITY infin "∞" ><!--INFINITY -->
+<!ENTITY int "∫" ><!--INTEGRAL -->
+<!ENTITY isin "∈" ><!--ELEMENT OF -->
+<!ENTITY lagran "ℒ" ><!--SCRIPT CAPITAL L -->
+<!ENTITY lang "〈" ><!--LEFT-POINTING ANGLE BRACKET -->
+<!ENTITY lArr "⇐" ><!--LEFTWARDS DOUBLE ARROW -->
+<!ENTITY le "≤" ><!--LESS-THAN OR EQUAL TO -->
+<!ENTITY lowast "∗" ><!--ASTERISK OPERATOR -->
+<!ENTITY minus "−" ><!--MINUS SIGN -->
+<!ENTITY mnplus "∓" ><!--MINUS-OR-PLUS SIGN -->
+<!ENTITY nabla "∇" ><!--NABLA -->
+<!ENTITY ne "≠" ><!--NOT EQUAL TO -->
+<!ENTITY ni "∋" ><!--CONTAINS AS MEMBER -->
+<!ENTITY notin "∉" ><!--NOT AN ELEMENT OF -->
+<!ENTITY or "∨" ><!--LOGICAL OR -->
+<!ENTITY order "ℴ" ><!--SCRIPT SMALL O -->
+<!ENTITY par "∥" ><!--PARALLEL TO -->
+<!ENTITY part "∂" ><!--PARTIAL DIFFERENTIAL -->
+<!ENTITY permil "‰" ><!--PER MILLE SIGN -->
+<!ENTITY perp "⊥" ><!--UP TACK -->
+<!ENTITY phmmat "ℳ" ><!--SCRIPT CAPITAL M -->
+<!ENTITY Prime "″" ><!--DOUBLE PRIME -->
+<!ENTITY prime "′" ><!--PRIME -->
+<!ENTITY prop "∝" ><!--PROPORTIONAL TO -->
+<!ENTITY radic "√" ><!--SQUARE ROOT -->
+<!ENTITY rang "〉" ><!--RIGHT-POINTING ANGLE BRACKET -->
+<!ENTITY rArr "⇒" ><!--RIGHTWARDS DOUBLE ARROW -->
+<!ENTITY sim "∼" ><!--TILDE OPERATOR -->
+<!ENTITY sime "≃" ><!--ASYMPTOTICALLY EQUAL TO -->
+<!ENTITY square "□" ><!--WHITE SQUARE -->
+<!ENTITY sub "⊂" ><!--SUBSET OF -->
+<!ENTITY sube "⊆" ><!--SUBSET OF OR EQUAL TO -->
+<!ENTITY sup "⊃" ><!--SUPERSET OF -->
+<!ENTITY supe "⊇" ><!--SUPERSET OF OR EQUAL TO -->
+<!ENTITY tdot " ⃛" ><!--COMBINING THREE DOTS ABOVE -->
+<!ENTITY there4 "∴" ><!--THEREFORE -->
+<!ENTITY tprime "‴" ><!--TRIPLE PRIME -->
+<!ENTITY Verbar "‖" ><!--DOUBLE VERTICAL LINE -->
+<!ENTITY wedgeq "≙" ><!--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 " "> <!-- no-break space = non-breaking space,
+ U+00A0 ISOnum -->
+<!ENTITY iexcl "¡"> <!-- inverted exclamation mark, U+00A1 ISOnum -->
+<!ENTITY cent "¢"> <!-- cent sign, U+00A2 ISOnum -->
+<!ENTITY pound "£"> <!-- pound sign, U+00A3 ISOnum -->
+<!ENTITY curren "¤"> <!-- currency sign, U+00A4 ISOnum -->
+<!ENTITY yen "¥"> <!-- yen sign = yuan sign, U+00A5 ISOnum -->
+<!ENTITY brvbar "¦"> <!-- broken bar = broken vertical bar,
+ U+00A6 ISOnum -->
+<!ENTITY sect "§"> <!-- section sign, U+00A7 ISOnum -->
+<!ENTITY uml "¨"> <!-- diaeresis = spacing diaeresis,
+ U+00A8 ISOdia -->
+<!ENTITY copy "©"> <!-- copyright sign, U+00A9 ISOnum -->
+<!ENTITY ordf "ª"> <!-- feminine ordinal indicator, U+00AA ISOnum -->
+<!ENTITY laquo "«"> <!-- left-pointing double angle quotation mark
+ = left pointing guillemet, U+00AB ISOnum -->
+<!ENTITY not "¬"> <!-- not sign = discretionary hyphen,
+ U+00AC ISOnum -->
+<!ENTITY shy "­"> <!-- soft hyphen = discretionary hyphen,
+ U+00AD ISOnum -->
+<!ENTITY reg "®"> <!-- registered sign = registered trade mark sign,
+ U+00AE ISOnum -->
+<!ENTITY macr "¯"> <!-- macron = spacing macron = overline
+ = APL overbar, U+00AF ISOdia -->
+<!ENTITY deg "°"> <!-- degree sign, U+00B0 ISOnum -->
+<!ENTITY plusmn "±"> <!-- plus-minus sign = plus-or-minus sign,
+ U+00B1 ISOnum -->
+<!ENTITY sup2 "²"> <!-- superscript two = superscript digit two
+ = squared, U+00B2 ISOnum -->
+<!ENTITY sup3 "³"> <!-- superscript three = superscript digit three
+ = cubed, U+00B3 ISOnum -->
+<!ENTITY acute "´"> <!-- acute accent = spacing acute,
+ U+00B4 ISOdia -->
+<!ENTITY micro "µ"> <!-- micro sign, U+00B5 ISOnum -->
+<!ENTITY para "¶"> <!-- pilcrow sign = paragraph sign,
+ U+00B6 ISOnum -->
+<!ENTITY middot "·"> <!-- middle dot = Georgian comma
+ = Greek middle dot, U+00B7 ISOnum -->
+<!ENTITY cedil "¸"> <!-- cedilla = spacing cedilla, U+00B8 ISOdia -->
+<!ENTITY sup1 "¹"> <!-- superscript one = superscript digit one,
+ U+00B9 ISOnum -->
+<!ENTITY ordm "º"> <!-- masculine ordinal indicator,
+ U+00BA ISOnum -->
+<!ENTITY raquo "»"> <!-- right-pointing double angle quotation mark
+ = right pointing guillemet, U+00BB ISOnum -->
+<!ENTITY frac14 "¼"> <!-- vulgar fraction one quarter
+ = fraction one quarter, U+00BC ISOnum -->
+<!ENTITY frac12 "½"> <!-- vulgar fraction one half
+ = fraction one half, U+00BD ISOnum -->
+<!ENTITY frac34 "¾"> <!-- vulgar fraction three quarters
+ = fraction three quarters, U+00BE ISOnum -->
+<!ENTITY iquest "¿"> <!-- inverted question mark
+ = turned question mark, U+00BF ISOnum -->
+<!ENTITY Agrave "À"> <!-- latin capital letter A with grave
+ = latin capital letter A grave,
+ U+00C0 ISOlat1 -->
+<!ENTITY Aacute "Á"> <!-- latin capital letter A with acute,
+ U+00C1 ISOlat1 -->
+<!ENTITY Acirc "Â"> <!-- latin capital letter A with circumflex,
+ U+00C2 ISOlat1 -->
+<!ENTITY Atilde "Ã"> <!-- latin capital letter A with tilde,
+ U+00C3 ISOlat1 -->
+<!ENTITY Auml "Ä"> <!-- latin capital letter A with diaeresis,
+ U+00C4 ISOlat1 -->
+<!ENTITY Aring "Å"> <!-- latin capital letter A with ring above
+ = latin capital letter A ring,
+ U+00C5 ISOlat1 -->
+<!ENTITY AElig "Æ"> <!-- latin capital letter AE
+ = latin capital ligature AE,
+ U+00C6 ISOlat1 -->
+<!ENTITY Ccedil "Ç"> <!-- latin capital letter C with cedilla,
+ U+00C7 ISOlat1 -->
+<!ENTITY Egrave "È"> <!-- latin capital letter E with grave,
+ U+00C8 ISOlat1 -->
+<!ENTITY Eacute "É"> <!-- latin capital letter E with acute,
+ U+00C9 ISOlat1 -->
+<!ENTITY Ecirc "Ê"> <!-- latin capital letter E with circumflex,
+ U+00CA ISOlat1 -->
+<!ENTITY Euml "Ë"> <!-- latin capital letter E with diaeresis,
+ U+00CB ISOlat1 -->
+<!ENTITY Igrave "Ì"> <!-- latin capital letter I with grave,
+ U+00CC ISOlat1 -->
+<!ENTITY Iacute "Í"> <!-- latin capital letter I with acute,
+ U+00CD ISOlat1 -->
+<!ENTITY Icirc "Î"> <!-- latin capital letter I with circumflex,
+ U+00CE ISOlat1 -->
+<!ENTITY Iuml "Ï"> <!-- latin capital letter I with diaeresis,
+ U+00CF ISOlat1 -->
+<!ENTITY ETH "Ð"> <!-- latin capital letter ETH, U+00D0 ISOlat1 -->
+<!ENTITY Ntilde "Ñ"> <!-- latin capital letter N with tilde,
+ U+00D1 ISOlat1 -->
+<!ENTITY Ograve "Ò"> <!-- latin capital letter O with grave,
+ U+00D2 ISOlat1 -->
+<!ENTITY Oacute "Ó"> <!-- latin capital letter O with acute,
+ U+00D3 ISOlat1 -->
+<!ENTITY Ocirc "Ô"> <!-- latin capital letter O with circumflex,
+ U+00D4 ISOlat1 -->
+<!ENTITY Otilde "Õ"> <!-- latin capital letter O with tilde,
+ U+00D5 ISOlat1 -->
+<!ENTITY Ouml "Ö"> <!-- latin capital letter O with diaeresis,
+ U+00D6 ISOlat1 -->
+<!ENTITY times "×"> <!-- multiplication sign, U+00D7 ISOnum -->
+<!ENTITY Oslash "Ø"> <!-- latin capital letter O with stroke
+ = latin capital letter O slash,
+ U+00D8 ISOlat1 -->
+<!ENTITY Ugrave "Ù"> <!-- latin capital letter U with grave,
+ U+00D9 ISOlat1 -->
+<!ENTITY Uacute "Ú"> <!-- latin capital letter U with acute,
+ U+00DA ISOlat1 -->
+<!ENTITY Ucirc "Û"> <!-- latin capital letter U with circumflex,
+ U+00DB ISOlat1 -->
+<!ENTITY Uuml "Ü"> <!-- latin capital letter U with diaeresis,
+ U+00DC ISOlat1 -->
+<!ENTITY Yacute "Ý"> <!-- latin capital letter Y with acute,
+ U+00DD ISOlat1 -->
+<!ENTITY THORN "Þ"> <!-- latin capital letter THORN,
+ U+00DE ISOlat1 -->
+<!ENTITY szlig "ß"> <!-- latin small letter sharp s = ess-zed,
+ U+00DF ISOlat1 -->
+<!ENTITY agrave "à"> <!-- latin small letter a with grave
+ = latin small letter a grave,
+ U+00E0 ISOlat1 -->
+<!ENTITY aacute "á"> <!-- latin small letter a with acute,
+ U+00E1 ISOlat1 -->
+<!ENTITY acirc "â"> <!-- latin small letter a with circumflex,
+ U+00E2 ISOlat1 -->
+<!ENTITY atilde "ã"> <!-- latin small letter a with tilde,
+ U+00E3 ISOlat1 -->
+<!ENTITY auml "ä"> <!-- latin small letter a with diaeresis,
+ U+00E4 ISOlat1 -->
+<!ENTITY aring "å"> <!-- latin small letter a with ring above
+ = latin small letter a ring,
+ U+00E5 ISOlat1 -->
+<!ENTITY aelig "æ"> <!-- latin small letter ae
+ = latin small ligature ae, U+00E6 ISOlat1 -->
+<!ENTITY ccedil "ç"> <!-- latin small letter c with cedilla,
+ U+00E7 ISOlat1 -->
+<!ENTITY egrave "è"> <!-- latin small letter e with grave,
+ U+00E8 ISOlat1 -->
+<!ENTITY eacute "é"> <!-- latin small letter e with acute,
+ U+00E9 ISOlat1 -->
+<!ENTITY ecirc "ê"> <!-- latin small letter e with circumflex,
+ U+00EA ISOlat1 -->
+<!ENTITY euml "ë"> <!-- latin small letter e with diaeresis,
+ U+00EB ISOlat1 -->
+<!ENTITY igrave "ì"> <!-- latin small letter i with grave,
+ U+00EC ISOlat1 -->
+<!ENTITY iacute "í"> <!-- latin small letter i with acute,
+ U+00ED ISOlat1 -->
+<!ENTITY icirc "î"> <!-- latin small letter i with circumflex,
+ U+00EE ISOlat1 -->
+<!ENTITY iuml "ï"> <!-- latin small letter i with diaeresis,
+ U+00EF ISOlat1 -->
+<!ENTITY eth "ð"> <!-- latin small letter eth, U+00F0 ISOlat1 -->
+<!ENTITY ntilde "ñ"> <!-- latin small letter n with tilde,
+ U+00F1 ISOlat1 -->
+<!ENTITY ograve "ò"> <!-- latin small letter o with grave,
+ U+00F2 ISOlat1 -->
+<!ENTITY oacute "ó"> <!-- latin small letter o with acute,
+ U+00F3 ISOlat1 -->
+<!ENTITY ocirc "ô"> <!-- latin small letter o with circumflex,
+ U+00F4 ISOlat1 -->
+<!ENTITY otilde "õ"> <!-- latin small letter o with tilde,
+ U+00F5 ISOlat1 -->
+<!ENTITY ouml "ö"> <!-- latin small letter o with diaeresis,
+ U+00F6 ISOlat1 -->
+<!ENTITY divide "÷"> <!-- division sign, U+00F7 ISOnum -->
+<!ENTITY oslash "ø"> <!-- latin small letter o with stroke,
+ = latin small letter o slash,
+ U+00F8 ISOlat1 -->
+<!ENTITY ugrave "ù"> <!-- latin small letter u with grave,
+ U+00F9 ISOlat1 -->
+<!ENTITY uacute "ú"> <!-- latin small letter u with acute,
+ U+00FA ISOlat1 -->
+<!ENTITY ucirc "û"> <!-- latin small letter u with circumflex,
+ U+00FB ISOlat1 -->
+<!ENTITY uuml "ü"> <!-- latin small letter u with diaeresis,
+ U+00FC ISOlat1 -->
+<!ENTITY yacute "ý"> <!-- latin small letter y with acute,
+ U+00FD ISOlat1 -->
+<!ENTITY thorn "þ"> <!-- latin small letter thorn with,
+ U+00FE ISOlat1 -->
+<!ENTITY yuml "ÿ"> <!-- 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 """> <!-- quotation mark = APL quote,
+ U+0022 ISOnum -->
+<!ENTITY amp "&#38;"> <!-- ampersand, U+0026 ISOnum -->
+<!ENTITY lt "&#60;"> <!-- less-than sign, U+003C ISOnum -->
+<!ENTITY gt ">"> <!-- greater-than sign, U+003E ISOnum -->
+<!ENTITY apos "'"> <!-- apostrophe mark, U+0027 ISOnum -->
+
+<!-- Latin Extended-A -->
+<!ENTITY OElig "Œ"> <!-- latin capital ligature OE,
+ U+0152 ISOlat2 -->
+<!ENTITY oelig "œ"> <!-- latin small ligature oe, U+0153 ISOlat2 -->
+<!-- ligature is a misnomer, this is a separate character in some languages -->
+<!ENTITY Scaron "Š"> <!-- latin capital letter S with caron,
+ U+0160 ISOlat2 -->
+<!ENTITY scaron "š"> <!-- latin small letter s with caron,
+ U+0161 ISOlat2 -->
+<!ENTITY Yuml "Ÿ"> <!-- latin capital letter Y with diaeresis,
+ U+0178 ISOlat2 -->
+
+<!-- Spacing Modifier Letters -->
+<!ENTITY circ "ˆ"> <!-- modifier letter circumflex accent,
+ U+02C6 ISOpub -->
+<!ENTITY tilde "˜"> <!-- small tilde, U+02DC ISOdia -->
+
+<!-- General Punctuation -->
+<!ENTITY ensp " "> <!-- en space, U+2002 ISOpub -->
+<!ENTITY emsp " "> <!-- em space, U+2003 ISOpub -->
+<!ENTITY thinsp " "> <!-- thin space, U+2009 ISOpub -->
+<!ENTITY zwnj "‌"> <!-- zero width non-joiner,
+ U+200C NEW RFC 2070 -->
+<!ENTITY zwj "‍"> <!-- zero width joiner, U+200D NEW RFC 2070 -->
+<!ENTITY lrm "‎"> <!-- left-to-right mark, U+200E NEW RFC 2070 -->
+<!ENTITY rlm "‏"> <!-- right-to-left mark, U+200F NEW RFC 2070 -->
+<!ENTITY ndash "–"> <!-- en dash, U+2013 ISOpub -->
+<!ENTITY mdash "—"> <!-- em dash, U+2014 ISOpub -->
+<!ENTITY lsquo "‘"> <!-- left single quotation mark,
+ U+2018 ISOnum -->
+<!ENTITY rsquo "’"> <!-- right single quotation mark,
+ U+2019 ISOnum -->
+<!ENTITY sbquo "‚"> <!-- single low-9 quotation mark, U+201A NEW -->
+<!ENTITY ldquo "“"> <!-- left double quotation mark,
+ U+201C ISOnum -->
+<!ENTITY rdquo "”"> <!-- right double quotation mark,
+ U+201D ISOnum -->
+<!ENTITY bdquo "„"> <!-- double low-9 quotation mark, U+201E NEW -->
+<!ENTITY dagger "†"> <!-- dagger, U+2020 ISOpub -->
+<!ENTITY Dagger "‡"> <!-- double dagger, U+2021 ISOpub -->
+<!ENTITY permil "‰"> <!-- per mille sign, U+2030 ISOtech -->
+<!ENTITY lsaquo "‹"> <!-- single left-pointing angle quotation mark,
+ U+2039 ISO proposed -->
+<!-- lsaquo is proposed but not yet ISO standardized -->
+<!ENTITY rsaquo "›"> <!-- single right-pointing angle quotation mark,
+ U+203A ISO proposed -->
+<!-- rsaquo is proposed but not yet ISO standardized -->
+<!ENTITY euro "€"> <!-- 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 "ƒ"> <!-- latin small f with hook = function
+ = florin, U+0192 ISOtech -->
+
+<!-- Greek -->
+<!ENTITY Alpha "Α"> <!-- greek capital letter alpha, U+0391 -->
+<!ENTITY Beta "Β"> <!-- greek capital letter beta, U+0392 -->
+<!ENTITY Gamma "Γ"> <!-- greek capital letter gamma,
+ U+0393 ISOgrk3 -->
+<!ENTITY Delta "Δ"> <!-- greek capital letter delta,
+ U+0394 ISOgrk3 -->
+<!ENTITY Epsilon "Ε"> <!-- greek capital letter epsilon, U+0395 -->
+<!ENTITY Zeta "Ζ"> <!-- greek capital letter zeta, U+0396 -->
+<!ENTITY Eta "Η"> <!-- greek capital letter eta, U+0397 -->
+<!ENTITY Theta "Θ"> <!-- greek capital letter theta,
+ U+0398 ISOgrk3 -->
+<!ENTITY Iota "Ι"> <!-- greek capital letter iota, U+0399 -->
+<!ENTITY Kappa "Κ"> <!-- greek capital letter kappa, U+039A -->
+<!ENTITY Lambda "Λ"> <!-- greek capital letter lambda,
+ U+039B ISOgrk3 -->
+<!ENTITY Mu "Μ"> <!-- greek capital letter mu, U+039C -->
+<!ENTITY Nu "Ν"> <!-- greek capital letter nu, U+039D -->
+<!ENTITY Xi "Ξ"> <!-- greek capital letter xi, U+039E ISOgrk3 -->
+<!ENTITY Omicron "Ο"> <!-- greek capital letter omicron, U+039F -->
+<!ENTITY Pi "Π"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->
+<!ENTITY Rho "Ρ"> <!-- greek capital letter rho, U+03A1 -->
+<!-- there is no Sigmaf, and no U+03A2 character either -->
+<!ENTITY Sigma "Σ"> <!-- greek capital letter sigma,
+ U+03A3 ISOgrk3 -->
+<!ENTITY Tau "Τ"> <!-- greek capital letter tau, U+03A4 -->
+<!ENTITY Upsilon "Υ"> <!-- greek capital letter upsilon,
+ U+03A5 ISOgrk3 -->
+<!ENTITY Phi "Φ"> <!-- greek capital letter phi,
+ U+03A6 ISOgrk3 -->
+<!ENTITY Chi "Χ"> <!-- greek capital letter chi, U+03A7 -->
+<!ENTITY Psi "Ψ"> <!-- greek capital letter psi,
+ U+03A8 ISOgrk3 -->
+<!ENTITY Omega "Ω"> <!-- greek capital letter omega,
+ U+03A9 ISOgrk3 -->
+
+<!ENTITY alpha "α"> <!-- greek small letter alpha,
+ U+03B1 ISOgrk3 -->
+<!ENTITY beta "β"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->
+<!ENTITY gamma "γ"> <!-- greek small letter gamma,
+ U+03B3 ISOgrk3 -->
+<!ENTITY delta "δ"> <!-- greek small letter delta,
+ U+03B4 ISOgrk3 -->
+<!ENTITY epsilon "ε"> <!-- greek small letter epsilon,
+ U+03B5 ISOgrk3 -->
+<!ENTITY zeta "ζ"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->
+<!ENTITY eta "η"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->
+<!ENTITY theta "θ"> <!-- greek small letter theta,
+ U+03B8 ISOgrk3 -->
+<!ENTITY iota "ι"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->
+<!ENTITY kappa "κ"> <!-- greek small letter kappa,
+ U+03BA ISOgrk3 -->
+<!ENTITY lambda "λ"> <!-- greek small letter lambda,
+ U+03BB ISOgrk3 -->
+<!ENTITY mu "μ"> <!-- greek small letter mu, U+03BC ISOgrk3 -->
+<!ENTITY nu "ν"> <!-- greek small letter nu, U+03BD ISOgrk3 -->
+<!ENTITY xi "ξ"> <!-- greek small letter xi, U+03BE ISOgrk3 -->
+<!ENTITY omicron "ο"> <!-- greek small letter omicron, U+03BF NEW -->
+<!ENTITY pi "π"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->
+<!ENTITY rho "ρ"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->
+<!ENTITY sigmaf "ς"> <!-- greek small letter final sigma,
+ U+03C2 ISOgrk3 -->
+<!ENTITY sigma "σ"> <!-- greek small letter sigma,
+ U+03C3 ISOgrk3 -->
+<!ENTITY tau "τ"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->
+<!ENTITY upsilon "υ"> <!-- greek small letter upsilon,
+ U+03C5 ISOgrk3 -->
+<!ENTITY phi "φ"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->
+<!ENTITY chi "χ"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->
+<!ENTITY psi "ψ"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->
+<!ENTITY omega "ω"> <!-- greek small letter omega,
+ U+03C9 ISOgrk3 -->
+<!ENTITY thetasym "ϑ"> <!-- greek small letter theta symbol,
+ U+03D1 NEW -->
+<!ENTITY upsih "ϒ"> <!-- greek upsilon with hook symbol,
+ U+03D2 NEW -->
+<!ENTITY piv "ϖ"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->
+
+<!-- General Punctuation -->
+<!ENTITY bull "•"> <!-- bullet = black small circle,
+ U+2022 ISOpub -->
+<!-- bullet is NOT the same as bullet operator, U+2219 -->
+<!ENTITY hellip "…"> <!-- horizontal ellipsis = three dot leader,
+ U+2026 ISOpub -->
+<!ENTITY prime "′"> <!-- prime = minutes = feet, U+2032 ISOtech -->
+<!ENTITY Prime "″"> <!-- double prime = seconds = inches,
+ U+2033 ISOtech -->
+<!ENTITY oline "‾"> <!-- overline = spacing overscore,
+ U+203E NEW -->
+<!ENTITY frasl "⁄"> <!-- fraction slash, U+2044 NEW -->
+
+<!-- Letterlike Symbols -->
+<!ENTITY weierp "℘"> <!-- script capital P = power set
+ = Weierstrass p, U+2118 ISOamso -->
+<!ENTITY image "ℑ"> <!-- blackletter capital I = imaginary part,
+ U+2111 ISOamso -->
+<!ENTITY real "ℜ"> <!-- blackletter capital R = real part symbol,
+ U+211C ISOamso -->
+<!ENTITY trade "™"> <!-- trade mark sign, U+2122 ISOnum -->
+<!ENTITY alefsym "ℵ"> <!-- 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 "←"> <!-- leftwards arrow, U+2190 ISOnum -->
+<!ENTITY uarr "↑"> <!-- upwards arrow, U+2191 ISOnum-->
+<!ENTITY rarr "→"> <!-- rightwards arrow, U+2192 ISOnum -->
+<!ENTITY darr "↓"> <!-- downwards arrow, U+2193 ISOnum -->
+<!ENTITY harr "↔"> <!-- left right arrow, U+2194 ISOamsa -->
+<!ENTITY crarr "↵"> <!-- downwards arrow with corner leftwards
+ = carriage return, U+21B5 NEW -->
+<!ENTITY lArr "⇐"> <!-- 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 "⇑"> <!-- upwards double arrow, U+21D1 ISOamsa -->
+<!ENTITY rArr "⇒"> <!-- 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 "⇓"> <!-- downwards double arrow, U+21D3 ISOamsa -->
+<!ENTITY hArr "⇔"> <!-- left right double arrow,
+ U+21D4 ISOamsa -->
+
+<!-- Mathematical Operators -->
+<!ENTITY forall "∀"> <!-- for all, U+2200 ISOtech -->
+<!ENTITY part "∂"> <!-- partial differential, U+2202 ISOtech -->
+<!ENTITY exist "∃"> <!-- there exists, U+2203 ISOtech -->
+<!ENTITY empty "∅"> <!-- empty set = null set = diameter,
+ U+2205 ISOamso -->
+<!ENTITY nabla "∇"> <!-- nabla = backward difference,
+ U+2207 ISOtech -->
+<!ENTITY isin "∈"> <!-- element of, U+2208 ISOtech -->
+<!ENTITY notin "∉"> <!-- not an element of, U+2209 ISOtech -->
+<!ENTITY ni "∋"> <!-- contains as member, U+220B ISOtech -->
+<!-- should there be a more memorable name than 'ni'? -->
+<!ENTITY prod "∏"> <!-- 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 "∑"> <!-- 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 "−"> <!-- minus sign, U+2212 ISOtech -->
+<!ENTITY lowast "∗"> <!-- asterisk operator, U+2217 ISOtech -->
+<!ENTITY radic "√"> <!-- square root = radical sign,
+ U+221A ISOtech -->
+<!ENTITY prop "∝"> <!-- proportional to, U+221D ISOtech -->
+<!ENTITY infin "∞"> <!-- infinity, U+221E ISOtech -->
+<!ENTITY ang "∠"> <!-- angle, U+2220 ISOamso -->
+<!ENTITY and "∧"> <!-- logical and = wedge, U+2227 ISOtech -->
+<!ENTITY or "∨"> <!-- logical or = vee, U+2228 ISOtech -->
+<!ENTITY cap "∩"> <!-- intersection = cap, U+2229 ISOtech -->
+<!ENTITY cup "∪"> <!-- union = cup, U+222A ISOtech -->
+<!ENTITY int "∫"> <!-- integral, U+222B ISOtech -->
+<!ENTITY there4 "∴"> <!-- therefore, U+2234 ISOtech -->
+<!ENTITY sim "∼"> <!-- 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 "≅"> <!-- approximately equal to, U+2245 ISOtech -->
+<!ENTITY asymp "≈"> <!-- almost equal to = asymptotic to,
+ U+2248 ISOamsr -->
+<!ENTITY ne "≠"> <!-- not equal to, U+2260 ISOtech -->
+<!ENTITY equiv "≡"> <!-- identical to, U+2261 ISOtech -->
+<!ENTITY le "≤"> <!-- less-than or equal to, U+2264 ISOtech -->
+<!ENTITY ge "≥"> <!-- greater-than or equal to,
+ U+2265 ISOtech -->
+<!ENTITY sub "⊂"> <!-- subset of, U+2282 ISOtech -->
+<!ENTITY sup "⊃"> <!-- 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 "⊄"> <!-- not a subset of, U+2284 ISOamsn -->
+<!ENTITY sube "⊆"> <!-- subset of or equal to, U+2286 ISOtech -->
+<!ENTITY supe "⊇"> <!-- superset of or equal to,
+ U+2287 ISOtech -->
+<!ENTITY oplus "⊕"> <!-- circled plus = direct sum,
+ U+2295 ISOamsb -->
+<!ENTITY otimes "⊗"> <!-- circled times = vector product,
+ U+2297 ISOamsb -->
+<!ENTITY perp "⊥"> <!-- up tack = orthogonal to = perpendicular,
+ U+22A5 ISOtech -->
+<!ENTITY sdot "⋅"> <!-- dot operator, U+22C5 ISOamsb -->
+<!-- dot operator is NOT the same character as U+00B7 middle dot -->
+
+<!-- Miscellaneous Technical -->
+<!ENTITY lceil "⌈"> <!-- left ceiling = apl upstile,
+ U+2308 ISOamsc -->
+<!ENTITY rceil "⌉"> <!-- right ceiling, U+2309 ISOamsc -->
+<!ENTITY lfloor "⌊"> <!-- left floor = apl downstile,
+ U+230A ISOamsc -->
+<!ENTITY rfloor "⌋"> <!-- right floor, U+230B ISOamsc -->
+<!ENTITY lang "〈"> <!-- 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 "〉"> <!-- 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 "◊"> <!-- lozenge, U+25CA ISOpub -->
+
+<!-- Miscellaneous Symbols -->
+<!ENTITY spades "♠"> <!-- black spade suit, U+2660 ISOpub -->
+<!-- black here seems to mean filled as opposed to hollow -->
+<!ENTITY clubs "♣"> <!-- black club suit = shamrock,
+ U+2663 ISOpub -->
+<!ENTITY hearts "♥"> <!-- black heart suit = valentine,
+ U+2665 ISOpub -->
+<!ENTITY diams "♦"> <!-- 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