##################################################################### $Id$ ##################################################################### WHAT IS IT: modularized contentfilter for Zmailer and Sendmail COPYRIGHT: (c) 2003-2005 Eugene G. Crosser LICENSE: The same set as apply to Zmailer code NOTE: distribution contains third party source code with possibly different copyright: - strlcpy implementation from OpenBSD - lhash implementation from SSLeay/OpenSSL - getopt implementation from FSF HOMEPAGE: http://www.average.org/zmscanner/ ##################################################################### The goal of this project is to get a reasonable tradeoff between speed and features. It sould be easy to add or exclude modules, to write new modules and distribute them independently from the core package. The program makes use of the `new' (as of this writing) contentfilter interface of Zmailer (http://www.zmailer.org/) where communication takes place over unix domain socket. When it gets a path to Zmailer message file, the program starts a thread or forks, the thread/child mmaps the file(readonly) (or pulls into memory if you don't have mmap()), and invokes plugins that claimed the initial `stage' (by default, "zmsg"). Each plugin may fill in the `ans' buffer and must return either ZMSCAN_CONTINUE or ZMSCAN_STOP. In the latter case, no further plugins are called and the `ans' buffer is passed over to smtpserver. Each plugin can also invoke other `stages' or even the same `stage', in which case it will be called resursively. It should check the return code of stage invocation and return immediately if it gets anything other than ZMSCAN_CONTINUE. Starting from version 2, there is a Milter frontend binary, named smscanner. It aims at providing Sendmail interface to the same plugin modules as zmscanner, but there is a number of differences. First of all, milter does not provide the whole received RFC822 message, so it is not possible to use a module that wants the whole message on input. Second, milter passes control in the course of reception, so it is possible to reject a message before it's body (or even headers) are received. This is unlike zmailer filter that passes control after the whole message is received and written to disk (but before reception is acknowledged to the peer). Third, because milter approach assumes passing the whole message over a socket, milter version may be much more resource hungry, both cpu-wise and memory-wise, than Zmailer version which mmaps the message and tries to avoid data copying. Because milter does not allow scanning the whole message, smscanner cannot use a "single point of entry" stage 'initstage'. Rather, it calls separately 'peerdata', 'helo', 'envfrom', 'envrcpt', 'rfc822hdr' and 'body' stages. Plugin scanning function must me thread-safe and reentrant. Initialization function does not need to be. Plugin functions may be compiled in or dynamically loadable (if your OS supports dynamic objects). To compile in a plugin, put the source into `plugins' subdirectory and run ./configure --with-plugins="blank separated list"; include the name of your source file (without ".c" suffix). Dynamically loadbale plugins may be built independantly; but they need access to "zmscanner.h" and "libzmscanner.a"; these are normally installed at default locations, /usr/local/include and /usr/local/lib. Plugin source must include a ZMS_MODULE() macro which specifies what `stage' to claim, internal name of the module, and three functions - plugin initialization (may do configuration parsing etc.), plugin termination, and data scanning. Plugin may request scanning of parts of the passed chunk of data, or allocated chunk(s) of data, by plugins(s) that belong to a different (or the same!) stage. Allocated memory must be freed before return. Plugin scanning function may return ZMSCAN_CONTINUE (proceed with scanning by other plugins), or ZMSCAN_STOP (cease scanning of this message alltogether). Optionally, it may set environment variable with a predefined name (defined by VP_ANSWER_STR macro) telling what to do with the message (default is accept). Within the same stage, plugins are run sequentially in the order they where registered, that is, internal plugins - in the order they are referred in (default) --with-plugins ./configure option, and external plugins - in the order they are listed in the master configuration file. See the source for examples. The daemon itself is designed in such a way that when you kill the parent process with SIGUSR1, it forks and execs its own binary (which could have been upgraded in the meanwhile) without closing the control socket. When the child initializes and is ready to process requests, it signals the parent with SIGUSR2 which causes graceful shudown of the process after all active processing threads terminate. There are several useful external plugins available, e.g. zms_clamav that doe virus scanning on non-text body parts, zms_dehtml that strips HTML tags off text/html parts, and zms_pcre that scans text body parts for regular expresstions using pcre library (http://www.pcre.org/). ##################################################################### Appendix: building from Subversion: $ svn co svn://svn.average.org/zmscanner/zmscanner/trunk zmscanner $ cd zmscanner $ aclocal -I m4 $ autoheader $ libtoolize $ automake -a $ autoconf $ ./configure --various-options Plugin modules are in svn://svn.average.org:/zmscanner//trunk