pax_global_header00006660000000000000000000000064147576306720014533gustar00rootroot0000000000000052 comment=5bb08bc45b21d75de1831383d9592799fac35a76 mimedefang-3.6/000077500000000000000000000000001475763067200134775ustar00rootroot00000000000000mimedefang-3.6/.github/000077500000000000000000000000001475763067200150375ustar00rootroot00000000000000mimedefang-3.6/.github/workflows/000077500000000000000000000000001475763067200170745ustar00rootroot00000000000000mimedefang-3.6/.github/workflows/main.yml000066400000000000000000000024511475763067200205450ustar00rootroot00000000000000# This is a basic workflow to help you get started with Actions name: CI for MIMEDefang # Controls when the workflow will run on: # Triggers the workflow on push or pull request events but only for the master branch push: branches: [ master ] pull_request: branches: [ master ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on strategy: matrix: platform: [ubuntu-latest] runs-on: ${{ matrix.platform }} # Steps represent a sequence of tasks that will be executed as part of the job steps: - name: Checkout code uses: actions/checkout@v3 - name: Install Perl modules run: | sudo apt-get update sudo apt-get -y install libmail-spf-perl libmail-dkim-perl libio-socket-ssl-perl libio-stringy-perl libmailtools-perl libmime-tools-perl libnet-smtp-ssl-perl libnet-ssleay-perl libtimedate-perl libunix-syslog-perl libhtml-parser-perl libarchive-zip-perl libtest-class-most-perl spamassassin libunix-syslog-perl - name: Regression tests run: env NET_TEST=yes prove -lv t mimedefang-3.6/.gitignore000066400000000000000000000010541475763067200154670ustar00rootroot00000000000000*.bak *.o .DS_Store Makefile config.h config.log config.status examples/init-script md-mx-ctrl md-mx-ctrl.8 mimedefang mimedefang-*.tar.gz mimedefang-*.tar.gz.sig mimedefang-filter.5 mimedefang-multiplexor mimedefang-multiplexor.8 mimedefang-notify.7 mimedefang-protocol.7 mimedefang.8 mimedefang.pl mimedefang-release.8 mimedefang-release.pl mimedefang.pl.8 modules/man/*.3 modules/man/*.3p redhat/mimedefang-init redhat/mimedefang-sysconfig redhat/mimedefang.spec script/mimedefang-util script/mimedefang-util.1 watch-mimedefang xs_init.c autom4te.cache mimedefang-3.6/Changelog000066400000000000000000003644141475763067200153250ustar00rootroot00000000000000WARNING: Before upgrading MIMEDefang, please search this file for *** NOTE INCOMPATIBILITY ** to see if anything has changed that might affect your filter. 2025-01-02 Giovanni Bechis * add the possibility to output some stats in JSON format 2024-12-13 Giovanni Bechis * fix hang on next request after call to rspamd_check() issue #81 2024-10-03 Giovanni Bechis * improve synthesize_received_header sub to avoid FPs in rdns checks 2024-09-05 Giovanni Bechis * fix re_match_in_7zip_directory crash 2024-07-30 Giovanni Bechis * MIMEDefang 3.5 RELEASED 2024-07-03 Giovanni Bechis * improve how filter elapsed time is calculated 2024-06-19 Marc Aurèle La France * remove socket file on exit 2024-06-14 Giovanni Bechis * add an option to disable DKIM header lines wrap 2024-06-10 Giovanni Bechis * add action_greylist to support basic greylisting 2024-03-26 Giovanni Bechis * remove CR from multiline header's values 2024-03-15 Philip Prindeville * simplify newline and return/newline handling (#79) 2024-01-23 Giovanni Bechis * add a sub to send a multipart mail message using Sendmail 2023-11-22 Giovanni Bechis * add a sub to check emails using Mail::SpamAssassin::Client 2023-10-03 Giovanni Bechis * add re_match_in_tgz_directory sub to block attachments in .tgz files 2023-06-07 Giovanni Bechis * add a Mail::MIMEDefang::SPF module to do Sender Policy Framework checks 2023-05-01 Giovanni Bechis * MIMEDefang 3.4.1 RELEASED 2023-04-25 Giovanni Bechis * MIMEDefang 3.4 RELEASED 2023-04-16 Giovanni Bechis * add a mimedefang-release program to release a message from quarantine directory 2023-04-16 Giovanni Bechis * add email_is_blacklisted to check an email address against an hashbl rbl server 2023-03-31 Giovanni Bechis * UTF-8 support improvements 2023-02-07 Giovanni Bechis * Authentication-Results header improvements 2023-01-16 Giovanni Bechis * MIMEDefang 3.3 RELEASED 2023-01-10 Giovanni Bechis * add UTF-8 support to md_graphdefang_log 2022-11-24 Giovanni Bechis * add a gen_mx_id Perl implementation, needed for OpenSMTPd support 2022-10-21 Giovanni Bechis * MIMEDefang 3.2 RELEASED 2022-09-26 Giovanni Bechis * make graphdefang compatible with current php versions 2022-08-24 Giovanni Bechis * MIMEDefang 3.1 RELEASED 2022-08-22 Giovanni Bechis * make more subs public 2022-08-07 Giovanni Bechis * make md_authres headers parsable by Mail::DKIM <= 0.54 2022-06-14 Giovanni Bechis * MIMEDefang 3.0 RELEASED 2022-05-24 Giovanni Bechis * add is_public_ip6_address to check if an ipv6 address is local 2022-05-19 Giovanni Bechis * add md_authres method to generate a basic Authentication-Results header for the message 2022-05-17 Giovanni Bechis * add md_arc_sign method to sign email messages with DKIM ARC signatures 2022-05-12 Giovanni Bechis * add md_dkim_verify method to verify DKIM signatures 2022-05-03 Giovanni Bechis * add md_dkim_sign method to sign email messages with DKIM signatures 2022-04-19 Giovanni Bechis * add anonymize_uri to remove utm_* parameters from uris. 2022-04-14 Giovanni Bechis * use new Rspamd connection method by default 2022-02-10 Giovanni Bechis * switch to Digest::SHA 2022-02-04 Giovanni Bechis * split mimedefang.pl code in Perl modules 2022-02-02 Giovanni Bechis * add re_match_in_7zip_directory to check for files inside 7zip archives 2021-12-17 Giovanni Bechis * MIMEDefang 2.86 RELEASED 2021-12-12 Giovanni Bechis * Rspamd support by forking rspamc(1) binary is now deprecated, added Rspamd support by implementing its protocol 2021-12-09 Giovanni Bechis * fallback to plaintext when md_check_against_smtp_server fails SSL connection for unknown reasons 2021-08-25 Giovanni Bechis * MIMEDefang 2.85 RELEASED 2021-07-28 Giovanni Bechis * add experimental support to scan emails with Rspamd antispam 2021-07-20 Giovanni Bechis * remove --enable_cleanup_with_rm "configure" parameter, switch to non reentrant version of readdir(3) 2021-04-20 Dianne Skoll * Obtain the Queue-ID as early as possible in the SMTP session. Requires the "-y" command-line option to mimedefang. * Add support for USE_SETSYMLIST in the system unit and Red Hat init script; setting USE_SETSYMLIST=yes adds the "-y" command-line option to mimedefang. 2020-11-18 Giovanni Bechis * mimedefang.pl: Add support for a configuration file to separate data from code 2020-08-19 Giovanni Bechis * mimedefang.pl: Add support to scan messages for viruses on a remote Clamav server using clamdscan client. 2020-08-16 Giovanni Bechis * mimedefang.pl: Add re_match_in_rar_directory function to match unwanted file names extensions inside a rar archive file. 2020-06-02 Bill Cole * mimedefang.pl: Added TLS support to md_check_against_smtp_server 2018-03-21 Dianne Skoll * MIMEDefang 2.84 RELEASED * mimedefang.pl: Correctly use "$mon" rather than "$min" to generate quarantine file names. * mimedefang-multiplexor: Make "workerinfo nnn" show how long ago the last state change was for a given worker. 2017-10-30 Dianne Skoll * MIMEDefang 2.83 RELEASED * mimedefang.pl: Do not add a Message-ID: header when handing a message to SpamAssassin if the original message lacks such a header. * Add systemd unit files; thanks to Richard Laager. * Minor tweaks to the sample filter. * mimedefang-multiplexor: Change the maxLifetime option to kick in only once a worker has processed at least one request; also check for exceeded lifetimes during the periodic idle-time check. * mimedefang-multiplexor: Fix an exit(EXIT_FAILURE) to be exit(EXIT_SUCCESS) in on place. 2017-09-08 Dianne Skoll * MIMEDefang 2.82 RELEASED * Update contrib/graphdefang with improvements from Kevin A. McGrail. * Fix Red Hat init script (thanks to Robert Scheck) * Exit with EXIT_SUCCESS if mimedefang-multiplexor is told to terminate. * Terminology change: Change "slave" to "worker" everywhere. *** NOTE INCOMPATIBILITY *** Check your init scripts to make sure they use current names for shell variables; a few "SLAVE" strings have been changed to "WORKER" * Add a new -V maxLifetime option to mimedefang-multiplexor that terminates worker processes after maxLifetime seconds (approximately). This is in addition to the -r maxRequests option. * Log the lifetime and number of requests processed when we terminate a worker process. 2017-08-31 Dianne Skoll * MIMEDefang 2.81 RELEASED * Don't barf if the installed version of Sys::Syslog has a developer tag added (like 0.33_01 on Debian Stretch). * Make mimedefang and mimedefang-multiplexor write their PID files as root to avoid an unprivileged user tampering with the pidfiles. Thanks to Michael Orlitzky for pointing this issue out. *** NOTE INCOMPATIBILITY *** You should move your PID files out of the MIMEDefang spool directory and into a standard root-owned directory like /var/run. Use the -o option to create lock files in the spool directory. The sample init scripts have been updated to reflect this. 2017-07-24 Dianne Skoll * MIMEDefang 2.80 RELEASED * md-mx-ctrl: Add newline to mimedefang-multiplexor output that lacks a newline. * mimedefang-util: Properly substitute @PERL@ at configure time. * mimedefang-multiplexor.c: Move variable declarations to start of compound statement to avoid problems with older C compilers. * mimedefang.pl: Add an extra level of subdirectories in the quarantine to avoid 32K subdirectory limit on ext3. Idea by Kevin McGrail. *** NOTE INCOMPATIBILITY *** Quarantine subdirectory naming changed. * mimedefang.c: Fix bug that caused Queue-ID not to show up when using MIMEDefang with Postfix (thanks to Kris Deugau). 2016-09-26 Dianne Skoll * MIMEDefang 2.79 RELEASED * Add the --data-dump option to scripts/mimedefang-util * Improve Postfix compatibility by trying to get QueueID after first RCPT command, and if not found, at the EOH milter phase. * Make mimedefang-multiplexor exit with a successful return code upon receipt of SIGTERM. * Use 64-bit variables where supported for some statstics counters that could overflow with only 32-bit variables, yielding incorrect statistics. * Fix configure.in to correctly detect that an embedded Perl interpreter can be destroyed/recreated on systems that need the -pthread GCC flag. 2015-04-23 Dianne Skoll * MIMEDefang 2.78 RELEASED * Fix bug in logic that coalesces multiparts to single-parts if possible; the bug broke DKIM signing. Fix is courtesy of Peter Nagel. 2015-04-20 Dianne Skoll * MIMEDefang 2.77 RELEASED * Change old author's name to "Dianne Skoll" in many places. 2015-03-27 Dianne Skoll * MIMEDefang 2.76 RELEASED 2015-03-24 Dianne Skoll * mimedefang.pl.in: Get rid of all Perl function prototypes. Perl prototypes are badly-implemented and consensus among modern Perl 5 programmers is they shouldn't be used. https://www.securecoding.cert.org/confluence/display/perl/DCL00-PL.+Do+not+use+subroutine+prototypes 2015-03-24 Faraz Vahabzadeh * Add support for filter_wrapup callback. This is called at the very end and permits header modifications, but not body modifications. Useful for DKIM-signing. 2015-03-09 Dianne Skoll * mimedefang.pl.in: Fix typo: SOPHOS should have been SAVSCAN 2015-01-15 Dianne Skoll * mimedefang.c: Don't add a MIME-Version header if there is already one. 2014-10-03 Dianne Skoll * Fix https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=646347 courtesy of Chrisoph Martin 2014-09-09 Dianne Skoll * Minor clarifications to mimedefang-filter man page. 2014-06-21 Dianne Skoll * Add "All / Summary" button to watch-multiple-mimedefangs.tcl 2014-05-21 Dianne Skoll * MIMEDefang 2.75 RELEASED * Many cosmetic improvements to watch-multiple-mimedefangs.tcl * Fix md_get_bogus_mx_hosts so it checks A records iff a domain has no MX records. * Add a forward declaration of rebuild_entity to avoid warnings on recent Perl versions. 2013-05-27 Dianne Skoll * MIMEDefang 2.74 RELEASED 2013-05-25 Dianne Skoll * Increase buffer size for md-mx-ctrl responses. 2013-01-22 Dianne Skoll * Close input file handle in append_to_html_part. Bug found by Kees Theunissen. 2012-09-17 Dianne Skoll * Add action_add_entity function. * Simplify code in action_replace_with_warning. 2012-06-18 Dianne Skoll * Remove obsolete text from man page. * Avoid deprecated "defined(@array)" construct. 2012-03-27 Dianne Skoll * Implement new "load1" md-mx-ctrl command which gives statistics in more useful format than "load" * get_mx_ip_addresses: Treat MX records of '', '.', '0', '0.', '0 .' and '0 ' as bogus. * watch-multiple-mimedefangs.tcl: Major improvements; see the new -n, -r, -s and -t command-line options. 2012-02-07 Dianne Skoll * Add rcpt_addr, rcpt_host and rcpt_mailer to default set of macros that we ask for. * Log Sendmail queue ID in more places. * Remove dead "connect_to_socket" routine in mimedefang.pl * Do not invokve smfi_setsymlist unless "-y" option to mimedefang is given. smfi_setsymlist leaks memory in versions of Sendmail prior to 8.14.4. 2012-01-23 Dianne Skoll * MIMEDefang 2.73 RELEASED 2012-01-20 Dianne Skoll * Create /var/spool/MIMEDefang with mode 0750 by default. 2012-01-18 Dianne Skoll * Make the -G option cause files created by mimedefang to be group-readable. Add the new MD_ALLOW_GROUP_ACCESS init script variable. 2011-12-21 Dianne Skoll * Make the multiplexor snoop in on communications and save the Sendmail queue-ID for logging purposes. It logs the queue ID when logging a worker's STDERR. 2011-12-12 Dianne Skoll * Make configure.in check whether or not libmilter requires -lldap. * Fix Graphdefang to handle new md_syslog output style. * Always check return code from chdir() in mimedefang.pl. In certain cases on large and heavily-loaded servers, if the chdir() failed MIMEDefang would end up working in the wrong directory with attendant chaos. * Add "-G" option to mimedefang and mimedefang-multiplexor. This makes their sockets group-readable and group-writable. * Pass along the client port number, server IP address and server port number to all filter functions. This feature was sponsored by Scayl. 2011-07-20 Dianne Skoll * MIMEDefang 2.72 RELEASED 2011-06-20 Dianne Skoll * MIMEDefang 2.72-BETA-2 * The "make unstripped" target has disappeared. Instead, use: make INSTALL_STRIP_FLAG='' * The RPM_INSTALL_ROOT make variable has disappeared. Instead, use the standard DESTDIR: make install DESTDIR=/some/dir * In mimedefang.c, truncate overlong responses from the multiplexor. Also sanitize replies so "\r" doesn't get fed to smfi_setmlreply. * If a worker process replies with a very long reply, have the multiplexor consume (and discard) the excess input so the multiplexor-to-worker protocol does not become de-synchronized. 2011-06-16 Dianne Skoll * When mimedefang becomes a daemon, have it wait for a "go/no-go" message from the child before exiting. This should eliminate race conditions whereby the MTA starts before the milter socket is present. * Revert change in 2.72-BETA-1 that passed client port number. It was a hack; we need a proper way to pass largish amounts of information to the filter and that will have to wait for a major reworking of MIMEDefang. 2010-11-01 Dianne Skoll * MIMEDefang 2.72-BETA-1 * Avoid run-time errors from Unix::Syslog on some platforms. * Change md_syslog to log the Sendmail Queue-ID if it is available. * Pass SMTP client port number to filter_relay, filter_helo, filter_sender and filter_recipient. Also make it available to filter_begin/filter/filter_end in $RelayPort global variable. * Remove references to ParanoidFiler. 2010-08-18 Dianne Skoll * MIMEDefang 2.71 RELEASED * More spelunking in the awful innards of Perl reveals that our original fix in 2.70 for handling of $SIG{FOO}... didn't completely fix the problem. On systems where Perl was compiled to use threading, running "md-mx-ctrl reread" could result in subsequent failure by scanners to set signal dispositions. This has been fixed. 2010-06-30 Dianne Skoll * Fix typo in examples/init-script.in * Fix compatibility with Postfix (broken in 2.70.) 2010-06-24 Dianne Skoll * MIMEDefang 2.70 RELEASED * Fixed a bug in embedded Perl: We have to call PERL_SET_CONTEXT after forking or Perl gets confused. In particular, setting signal-handling dispositions using $SIG{FOO} = sub { ... } breaks. 2010-06-16 Dianne Skoll * MIMEDefang 2.69 RELEASED * Clarify wording of mimedefang-filter man page. 2010-06-16 Dave O'Neill * Remove obsolete code that used to attempt to generate working directory names. Deactivate the no-longer-needed "-M" mimedefang option. * Makefile.in: "make install" target obeys only DESTDIR and now ignores RPM_INSTALL_ROOT 2010-05-13 Dianne Skoll * Add new "-y" option to mimedefang-multiplexor. This limits the number of concurrent "recipok" commands on a per-domain basis. 2010-05-13 Dave O'Neill * Remove Anomy::HTMLCleaner support. * use MIME::Parser::Filer's ignore_filename() call instead of subclassing to override evil_filename(). Same effect, less code. * refactor resend_message_one_recipient() to use resend_message_specifying_mode() instead of reimplementing it. * header_timezone() now generates a strictly RFC2822-compliant timezone string without needing POSIX::strftime() 2010-03-02 Dave O'Neill * Ensure that decode_mimewords() is called in scalar context. 2010-02-24 Dianne Skoll * MIMEDefang 2.68 RELEASED * The functions add_recipient, change_sender, delete_recipient, action_add_header and action_insert_header can be called from outside message context (that is, from filter_sender and filter_recipient). Based on suggestion from D. Stussy. 2010-02-16 Dianne Skoll * MIMEDefang 2.68-BETA-5 * Detect Sys::Syslog vs. Unix::Syslog at run-time rather than when running ./configure. * Fix a crash with embedded Perl on FreeBSD with Perl 5.10.0. Problem noted by Martin Blapp. 2010-02-03 Dianne Skoll * MIMEDefang 2.68-BETA-4. * Bug fix: Don't change Content-Disposition to "inline" by default. This was causing weird bugs with Outlook iCalendar attachments: http://lists.roaringpenguin.com/pipermail/mimedefang/2006-December/031525.html http://lists.roaringpenguin.com/pipermail/mimedefang/2004-November/025461.html * Fix a really stupid segmentation fault when handling multiline replies. Bug found and fixed by Michiel Brandenburg. * Make relay_is_blacklisted and relay_is_blacklisted_multi handle IPv6 addresses. Patch loosely based on submission by Michiel Brandenburg. NOTE: relay_is_blacklisted_multi and relay_is_blacklisted are DEPRECATED. Use the CPAN module Net::DNSBL::Client instead. * Guard the rewriting of IPv4-compatible IPv6 addresses to plain IPv4 with N6_IS_ADDR_V4MAPPED and IN6_IS_ADDR_V4COMPAT tests. 2009-12-30 Dianne Skoll * MIMEDefang 2.68-BETA-3. * Work around File::Spec::Unix's behaviour of caching $ENV{TMPDIR}. (I consider this a bug; see https://rt.cpan.org/Ticket/Display.html?id=53236) * Don't add a To: line for SpamAssassin's benefit; adding such a line could mask a useful SpamAssassin rule. 2009-12-30 Dianne Skoll * MIMEDefang 2.68-BETA-2. * Try hard not to lose any STDERR messages before reaping a worker. * Make the C code call smfi_setmlreply if (1) the milter library supports it and (2) the Perl code returns a multi-line reply. * Convert an IPv6-mapped IPv4 address to pure IPv4. That is, convert ::ffff:a.b.c.d simply to a.b.c.d. * Make rm_r more robust. * Set TMPDIR environment variable to $workdir/tmp before scanning; this should make Perl temporary files use the ramdisk. * Various code cleanups. * When creating the Mail::SpamAssassin object, set user_dir to /var/spool/MD-Quarantine. Fixes problems with SpamAssassin 3.3.0. 2009-03-31 Dianne Skoll * MIMEDefang 2.68-BETA-1. * Make "Overlong line in RESULTS file" a permanent, rather than temporary, failure. * Eliminate a possible race condition in SIGTERM handling. On busy, underpowered servers, this could result in the multiplexor spontaneously terminating all workers and unlinking its socket. * Check for both POLLIN and POLLHUP if we use poll() * Fix bug in closing of file descriptors after forking; we'd sometimes close our status descriptor by mistake. * Remove some pointless fcntl() calls. * Fix bug with Perl 5.10 and embedded perl, mentioned at http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=516913 NOTE: This is a bug in Perl, not MIMEDefang, but we need to work around it. * Consume and log any STDERR output even if worker has terminated. 2009-01-06 Dianne Skoll * VERSION 2.67 RELEASED * Added support for FPROTD version 6 daemonized scanner. 2008-10-31 Dianne Skoll * VERSION 2.66 RELEASED * Added the option to use poll(2) instead of select(2) in mimedefang-multiplexor. Use the --enable-poll ./configure option. This will eliminate problems with file descriptors > 1023 on many systems. Thanks to Concordia University for sponsoring this development. 2008-02-02 Dianne Skoll * VERSION 2.65 RELEASED * Fix a few minor compiler warnings 2008-08-12 Dianne Skoll * VERSION 2.65-BETA-1 RELEASED * embperl.c, configure.in: Fix problems with embedded Perl on Debian HPPA architecture. http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=486069 2008-01-24 Dianne Skoll * VERSION 2.64 RELEASED * Fix typo in the generic init-script.in file. Also, on FreeBSD/NetBSD, generic init-script.in fits into the *BSD init structure natively. * watch-multiple-mimedefangs.tcl: Works better with Tcl/Tk 8.5. Displays message volumes/day in more human-readable form. New -archive option logs statistics to files. 2007-09-24 Dianne Skoll * VERSION 2.64-BETA-1 RELEASED * Add support for NOD32 command-line scanner (Dusan Zovinec) * Add support for Sophos "savscan" scanner (Adam Lanier) * embperl.c: Fix Perl's $$ variable so it reflects the actual PID of the worker process. If you are using Embedded Perl, this should remove a major source of irritation (log messages previously used the PID of the master multiplexor process.) 2007-09-04 Dianne Skoll * mimedefang.pl.in: Make md_check_against_smtp_server include the Sendmail Queue-ID in the syslog message. * mimedefang.c: If mail is submitted via a UNIX-domain socket (yes, this is possible, apparently!) consider the sending relay to be 127.0.0.1. Fixes Debian bug #439432 * mimedefang-filter.5.in: Minor clarifications * redhat/mimedefang-init.in: Minor fixes * examples/init-script.in: Minor fixes 2007-08-13 Dianne Skoll * VERSION 2.63 RELEASED * mimedefang-multiplexor.c: Relax the umask when creating the unprivileged socket ("-a" command-line option.) * mimedefang.c(eom): If we do not have a queue ID yet, try to obtain one in eom. This is designed to improve operation with Postfix, which does not assign a queue ID until after the first successful RCPT. Based on a patch from Henrik Krohns. * examples/init-script.in: Added MD_SKIP_BAD_RCPTS init script option (suggested by John Nemeth) * Remove support for OpenAntivirus. It's a dead product. * mimedefang.pl.in(spam_assassin_status): Call $mail->finish() to prevent temporary files from accumulating. * redhat/mimedefang-init.in: Add configtest routine to check filter syntax. 2007-04-10 Dianne Skoll * VERSION 2.62 RELEASED * milter_cap.c: Minor code cleanups. 2007-03-30 Dianne Skoll * VERSION 2.62-BETA-1 * You can invoke mimedefang like this: mimedefang prcap and it prints details about the version of libmilter it's linked with and exit. * mimedefang.pl.in: A new "change_sender" action lets you change the envelope sender. Only works with Sendmail/Milter 8.14.0 and newer! * mimedefang.c: A new "-N" flag causes Sendmail not to make filter_recipient callbacks for invalid recipients. Only works with Sendmail/Milter 8.14.0! Note that without the -N flag, MIMEDefang now works the same with 8.14.0 and 8.13.x -- you always see all recipients by default, even invalid ones. * mimedefang.pl.in: Clam interface code has been fixed to work properly with ClamAV 0.90 and later. * redhat/mimedefang-init.in: Understand MX_TICK_REQUEST and MX_TICK_PARALLEL variables which get translated into -X and -P mimedefang-multiplexor options, respectively. 2007-02-09 Dianne Skoll * VERSION 2.61 RELEASED * SECURITY FIX: Versions 2.59 and 2.60 contained a programming error that could lead to a buffer overflow. This is definitely exploitable as a denial-of-service attack, and potentially may allow arbitrary code execution. The bug is fixed in 2.61. * mimedefang.c: If a message is going to end up being rejected, discarded or tempfailed, we don't bother carrying out requests to add/delete/modify headers or recipients, change the message body, etc. 2007-02-02 Dianne Skoll * VERSION 2.60 RELEASED * mimedefang.c: Fix filter registration so MIMEDefang works correctly against libmilter from Sendmail 8.14 * Fix a number of "pointer differs in signedness" warnings. (Problem noted by Ashley Kirchner.) 2007-01-20 Dianne Skoll * VERSION 2.59 RELEASED * watch-multiple-mimedefangs: Add grid-lines; tweak GUI a bit. 2007-01-19 Dianne Skoll * VERSION 2.59-BETA-3 * configure.in and Makefile.in: Instead of explicitly linking against limilter.a, just supply the -lmilter link option. This means it should work properly on 64-bit systems that keep their libraries in /usr/lib64. It also means that if you have a libmilter.so lying around, we'll link against it instead of linking statically. * configure.in: Require only 0.15 of Sys::Syslog to check for setlogsock('native'). (Matt Selsky) * mimedefang.c: Major changes: We build up the contents of COMMANDS in memory and write it out in one big chunk per milter callback. Not only does this reduce the number of system calls, but we also now _check the return code_ of those calls! * mimedefang.pl.in(item_contains_virus_fprotd): More careful inspection of F-PROT output to determine virus name. (Jan-Pieter Cornet) 2007-01-12 Dianne Skoll * VERSION 2.59-BETA-2 * Added a new tool (watch-multiple-mimedefangs.tcl) for monitoring a cluster of MIMEDefang scanners * mimedefang.pl.in: (dmo) Change "use POSIX;" to "use POSIX ();" to save several hundred kilobytes of memory per worker. * mimedefang.pl.in: (dmo) Remove useless "use Getopt::Std;" * mimedefang.pl.in: (dmo) Some code refactoring. 2006-12-18 Dianne Skoll * VERSION 2.59-BETA-1 * Modify multiplexor and mimedefang.pl.in so worker status updates work correctly (the -Z multiplexor flag.) Previously, the worker status wasn't being reset correctly. * Modify multiplexor so worker status changes are broadcast using the notification facility (-O multiplexor flag). A new "S" message is used for worker status changes. * mimedefang.pl.in(read_commands_file): If the COMMANDS file did not end with an F, the worker would give up and become idle, but not inform the multiplexor. As a result, the multiplexor would think the worker was busy, and the worker would be unavailable until the busy timeout elapsed and it was killed by the multiplexor. This bug has been fixed. * redhat/mimedefang-spec.in: Changes as suggested by Philip Prindeville for cleaning up RPM builds and detecting proper libraries on x86-64 systems. 2006-11-07 Dianne Skoll * VERSION 2.58 RELEASED * Memory leak in mimedefang found and fixed. If a client issues more than one MAIL command in a single SMTP session, then the milter used to leak approximately 16 bytes for each subsequent MAIL command. * Running ./configure --enable-debugging includes much more debugging output, especially to diagnose memory allocation and deallocation. DO NOT USE ON A PRODUCTION SERVER. * If we have Sys::Syslog 0.16 or higher, do not call setlogsock (which is deprecated). Patch based on suggestion from Matt Selsky. * Sample init script sets HOME=/var/spool/MIMEDefang. * Sample filter for Windows clients tweaked slightly: We don't complain about non-multipart .eml attachments (was causing false positives.) * Fixed typo in Red Hat sample init script. 2006-10-19 Dianne Skoll * VERSION 2.58-BETA-1 RELEASED * mimedefang.pl.in: If SpamAssassin version >= 3.1.5, do not supply LOCAL_RULES_DIR or LOCAL_STATE_DIR in constructor. Use defaults from Perl modules. * examples/init-script.in: Add ALLOW_NEW_CONNECTIONS_TO_QUEUE config variable. * mimedefang-multiplexor.c: Fix useless call to sigprocmask. (Used SIG_BLOCK; should have been SIG_SETMASK) * mimedefang.c: Make sure that we're given the -p option. * embperl.c: Remove warning about "Something in your filter has opened a file descriptor..." because there are way too many systems that trigger this warning, and they don't seem to have problems. 2006-07-20 Dianne Skoll * Remove all support for the File::Scan module. 2006-06-19 Dianne Skoll * VERSION 2.57 RELEASED * suggested-minimum-filter-for-windows-clients: Explicitly set $entity variable in filter_begin. * mimedefang.pl.in: If clamdscan fails with zip module failure, attempt to use scanner in $Features{'Virus:CLAMAV'} rather than a hard-coded call to "clamscan" 2006-05-04 Dianne Skoll * VERSION 2.57-BETA-1 RELEASED * Minor fixes to man pages. Some cleanups courtesy of Brandon Hutchinson * mimedefang-multiplexor.c: New "md-mx-ctrl hload" command keeps track of load for past 1, 4, 12 and 24 hours. Gives long-term data to complement the short-term "md-mx-ctrl load" data. * mimedefang-multiplexor: New scheduling algorithm tries to keep commands "sticky". For example, when looking for a worker to run "recipok", we prefer to use a worker that recently ran "recipok". NOTE!!! If your filter incorrectly retains state from earlier callbacks into filter_begin, this scheduling change WILL expose the bugs in your filter. * mimedefang.c: Bug fix for NULL pointer dereference when running "sendmail -bs". Problem noted by Leena Heino. * mimedefang.pl.in: Fix for FPROTD integration courtesy of Jonathan Hankins. * mimedefang.pl.in: Fix for H+BEDV integration courtesy of Thorsten Schlichting. * mimedefang.pl.in: Pass LOCAL_STATE_DIR => '/var/lib' to Mail::SpamAssassin constructor. If your LOCAL_STATE_DIR is elsewhere, you'll have to hack the Perl code, I'm afraid. 2006-02-13 Dianne Skoll * VERSION 2.56 RELEASED * Remove spam_assassin_init()->compile_now(1) call from sample filter. * mimedefang-multiplexor.c: Fix off-by-one error that could result in a worker thinking that the global generation counter had changed, causing the worker to restart unnecessarily. * redhat/mimedefang-init.in: Add support for MX_HELO_CHECK configuration variable. 2006-02-03 Dianne Skoll * VERSION 2.56-BETA-1 RELEASED * mimedefang.c: Fix compilation problem on some systems. * mimedefang.pl.in: entity_contains_virus_nai, message_contains_virus_nai: Add the --mime option when invoking uvscan. * mimedefang.pl.in: message_contains_virus_clamd: Use more reasonable timeouts when talking to clamd. 2006-01-24 Dianne Skoll * VERSION 2.55 RELEASED 2006-01-20 Dianne Skoll * VERSION 2.55-BETA-4 RELEASED * mimedefang.c: The new '-R' option lets you reserve a specified number of workers for connections from localhost. The idea is to try to allow clientmqueue runs to succeed even on heavily-loaded systems. * Patched to look for more modern "vascan" virus-scanner rather than older "vexira" scanner. Support for the older Vexira scanner has been dropped; please see README.VEXIRA. Changes courtesy of Matt Selsky and Ken Cormack. 2006-01-18 Dianne Skoll * VERSION 2.55-BETA-3 RELEASED 2006-01-17 Dianne Skoll * Added support for "filter_helo" function, based on a patch from Philip Prindeville. * examples/init-script.in: Fixed typo. * mimedefang.c: Use symbolic constants (MD_TEMPFAIL, MD_CONTINUE, etc.) instead of hard-coded integers, based on suggestion from Philip Prindeville. 2006-01-11 Dianne Skoll * VERSION 2.55-BETA-2 RELEASED * mimedefang.pl.in: The filter_begin function is now passed a single argument ($entity) representing the parsed message. *** NOTE INCOMPATIBILITY *** filter_begin NOW TAKES ONE ARGUMENT, NOT ZERO. IF YOUR FILTER HAS A PROTOTYPE FOR filter_begin, YOU SHOULD FIX OR REMOVE THE PROTOTYPE * mimedefang.c, mimedefang.pl.in: Added new action_insert_header to prepend headers (rather than appending them). Only works properly with Sendmail 8.13; on older versions of Sendmail, falls back to action_add_header. Based on patch from Matthew van Eerde. * mimedefang.pl.in: Added new function md_get_bogus_mx_hosts. Allows to test for sender domains with bogus MX hosts (such as hosts that resolve to the loopback or private IP addresses.) * mimedefang.pl.in: Invoke the "fsav" virus scanner with the --mime option. Fix courtesy of Mack Wharton. * mimedefang.pl.in: Correctly interpret kavscanner return code 9 (password-protected ZIP.) Fix courtesy of Mack Wharton. 2005-11-17 Dianne Skoll * VERSION 2.55-BETA-1 RELEASED * examples/init-script.in: Fix typo that resulted in the shell complaining of a syntax error (pointed out by Jason Englander). * Clean up man pages by removing some obsolete material. * mimedefang.c: Do NOT strip "bare CR" characters from e-mails by default. The new "-c" command-line option enables the older behavior. *** NOTE INCOMPATIBILITY *** WE NO LONGER STRIP BARE CR's FROM MESSAGES BY DEFAULT. TEST YOUR FILTERS CAREFULLY TO MAKE SURE THEY CAN COPE WITH THIS, OR USE THE -c FLAG. * mimedefang.c(rcptto): If you returned ACCEPT_AND_NO_MORE_FILTERING from filter_recipient, the spool files wouldn't get cleaned up, eventually clogging the spool directory. This has been fixed. * mimedefang.pl.in(interpret_hbedv_code): Fix interpretation of H+BEDV return codes (pointed out by Henning Schmiedehausen). 2005-11-04 Dianne Skoll * VERSION 2.54 RELEASED * Makefile and configure script now allow MIMEDefang to be built against a shared library version of libmilter (libmilter.so). * Added experimental support for Kaspersky "kavscanner". * Both mimedefang and mimedefang-multiplexor now accept a "-z spooldir" argument so you can move the spool directory away from the compiled-in default at run-time. 2005-09-07 Dianne Skoll * VERSION 2.53 RELEASED * mimedefang-protocol.7.in: Documented the "map" and "tick" protocol commands. * Remove call to anomy_clean_html from sample filter. Anomy::HTMLCleaner is simply way too buggy for production use. 2005-08-30 Dianne Skoll * VERSION 2.53-BETA-2 RELEASED * mimedefang.pl.in: If clamd fails with a "Zip module error", we fall back on "clamscan --unzip". If both clamscan and unzip are installed, this lets us handle "deflate64" compression, which is NOT available in libz or clamd. 2005-08-28 Dianne Skoll * VERSION 2.53-BETA-1 RELEASED * mimedefang-multiplexor.c: Add more load commands: load-relayok, load-senderok and load-recipok to time processing of relayok, senderok and recipok callbacks. * mimedefang-multiplexor.c: Reset SIGCHLD handler after creating embedded Perl interpreter. Designed to work around problems with SpamAssassin 3.1RC1. * mimedefang.c: Always create an empty HEADERS file, even if (somehow) we get an e-mail with no headers. * mimedefang.pl: Correctly set $Features{"Virus:FileScan"} * mimedefang.pl(takeStabAtFilename): Do not use the Content-Decription field as an indication of the filename. 2005-06-01 Dianne Skoll * VERSION 2.52 RELEASED * mimedefang.pl.in (item_contains_virus_fprotd): Remove all references to $` and $', which can SIGNIFICANTLY slow down Perl regexp matching. 2005-03-18 Dianne Skoll * mimedefang.c(mfconnect): Do not call set_dsn from mfconnect, because the Milter API specifies that you can't call smfi_setreply from the connect callback. * mimedefang-filter.5.in: Document the fact that filter_relay cannot set the text of the SMTP reply code. 2005-02-14 Dianne Skoll * mimedefang.pl: We don't detect and load Perl modules until the detect_and_load_perl_modules() function is called. *** NOTE INCOMPATIBILITY *** You *MUST* call detect_and_load_perl_modules() inside your filter before you can rely on the %Features hash being set correctly, and before you can rely on SpamAssassin being loaded!!! 2005-02-08 Dianne Skoll * VERSION 2.51 RELEASED Note: There was no public 2.50 release; the 2.50 version was a private release that was available only with CanIt. * Added "-q" option to mimedefang. This permits the multiplexor to queue new incoming connections. It may make higher utilization of workers and improve throughput. * ESMTP arguments in MAIL FROM: and RCPT TO: are now available to the Perl filter. See the mimedefang-filter(5) man page for details. * Documentation fixes: We don't refer to non-multiplexor mode any more, because that mode hasn't been available for ages. * The "tick" facility has been enhanced to permit multiple tick types. At any given instance, only one tick of a given type can be active, but ticks of different types can be active at the same time. * Log a warning if a message has more than one Subject: header. 2004-11-29 Dianne Skoll * Version 2.49 RELEASED * README: Fixed URL for wvware tools. (Brad Tarver) * mimedefang-filter.5.in: Corrected error in documentation. The man page used to state (incorrectly) that if the host name could not be resolved, it was set to the host IP address. In fact, it's set to [host.ip.addr.ess] with square brackets. * mimedefang-multiplexor.c: Insist that argument to -s switch be a UNIX-domain socket. * mimedefang.c: protect inet_ntoa with a mutex (for those systems that lack inet_ntop). * mimedefang.c: Fix stupid logic error that made MIMEDefang ignore the Sendmail {if_addr} macro when setting IP address for X-Scanned-By: header. * embperl.c: Soften warning about file descriptors being opened in filter. Some systems seem to do this (Solaris). * mimedefang.pl.in (synthesize_received_header): Add (envelope-sender $Sender) comment as desired by SpamAssassin: http://wiki.apache.org/spamassassin/EnvelopeSenderInReceived * mimedefang.pl.in (interpret_nvcc_code): Handle return code of 11 from Norman Virus Control as "suspicious". 2004-10-29 Dianne Skoll * Version 2.48 RELEASED * Fix dumb bugs introducted in 2.46 and 2.47 related to worker status reports. * embperl.c: Detect if user opens file descriptors inside his/her filter. If so, log a loud and nasty warning that such code should be moved to filter_initialize. 2004-10-28 Dianne Skoll * Version 2.47 RELEASED * Move worker "status reports" onto their own file descriptor. If you want the status reports, you must invoke mimedefang-multiplexor with the "-Z" flag. In the sample init scripts, set MX_STATUS_UDPATES=yes 2004-10-28 Dianne Skoll * Version 2.46 RELEASED * mimedefang-multiplexor.c: Added mechanism for workers to send back "status reports" to the multiplexor. The command "md-mx-ctrl workers" now shows the current status of busy workers (eg, "Running SpamAssassin", "recipok ", etc.) * redhat/mimedefang-init.in: Unconditionally execute "ulimit -s 2048" before invoking mimedefang. 2004-10-28 Bill Maidment * Added support for Command "csav" anti-virus. 2004-10-27 Dianne Skoll * Version 2.46-BETA-2 released. * mimedefang.c: Print and log an error if we can't determine our own IP address. * mimedefang.pl.in: append_html_boilerplate and append_text_boilerplate refuse to tamper with S/MIME messages. They won't descend into multipart/signed or multipart/encrypted parts. Similarly for remove_redundant_html_parts. * mimedefang.pl.in: Split-and-rebuild algorithm is greatly improved. In particular: In filter_end, the $entity->head correctly contains all message headers. And we try to avoid creating useless multipart containers -- if we would end up with a multipart/mixed or multipart/alternative with only one sub-part, we "pop" the sub-part up to the top level. 2004-10-26 Dianne Skoll * Version 2.46-BETA-1 released. * mimedefang-filter.5.in: Corrected an error in one of the examples * mimedefang.c: Add IP address of scanning host to X-Scanned-By: header. * SECURITY FIX: mimedefang.c: Tempfail message if RESULTS file doesn't terminate with 'F' line. (Detects disk-full condition.) * mimedefang.pl.in (rebuild_entity): Add a Content-Type: header if MIME part lacks one. Some marginal e-mail software chokes on a part with a missing content-type header. * mimedefang.pl.in: flatten_mime removed. Support for $Stupidity{"flatten"} removed. *** NOTE INCOMPATIBILITY *** * action_add_part revamped completely; we try to preserve original multipart type of message. action_add_part now simply keeps a list of parts to be added. At the end: a) If original message was multipart/mixed, we simply add the part. b) Otherwise, we make a new multipart/mixed container, put original message as the first part of this new container, and then add part to the multipart/mixed container. *** NOTE INCOMPATIBILITY *** * Proper multipart type passed to filter_end. * All mimedefang.pl-generated messages have an Auto-Submitted: auto-generated header. * mimedefang.pl.in: Return codes of I/O operations are checked; we die if any fail. This is a security fix. * mimedefang.pl.in (interpret_trend_code): Treat any code from 1 to 9 as indicative of a virus, upon recommendation of Stephane Lentz. * mimedefang.pl.in (spam_assassin_init): Add a LOCAL_RULES_DIR => @CONFDIR@/spamassassin argument to SpamAssassin constructor. 2004-09-22 Dianne Skoll * Version 2.45 RELEASED 2004-09-22 Dianne Skoll * mimedefang-multiplexor.c: Add the "-a" command-line option for opening a socket that only allows unprivileged commands. These are commands that fetch status, but can't affect operation of multiplexor. 2004-09-15 Dianne Skoll * Version 2.45-BETA-4 released. * mimedefang.pl.in: Put a "use libs" directive at the top to use Perl modules from the "site" directory before searching the core directory. * mimedefang.pl.in: Added filter_create_parser user-supplied callback to create a MIME::Parser object. This lets you customize how parsing happens. * mimedefang-multiplexor.8.in: Added warning that "$$" will be incorrect if you use embedded Perl. * mimedefang-multiplexor.c: Keep track of "age" of workers, and track activations and reaps over last 10 minutes. * mimedefang-multiplexor.c: Add new notification messages: "B" indicates a busy timeout, and "U" indicates unexpected worker death. * mimedefang.c: Add "-b" option to set the backlog parameter in listen(2). * notifier.c: Fixed (harmless) bug which would attempt to send out notifications even if no notification socket was specified. It would just waste a tiny bit of CPU time before. * watch-mimedefang.in: Fix Tcl code so you can run watch-mimedefang on a Windows box, monitoring the mail server via SSH. 2004-09-14 Dianne Skoll * mimedefang.pl.in (md_check_against_smtp_server): Add optional $port argument to specify checking against a port other than 25. 2004-08-23 Dianne Skoll * Version 2.45-BETA-3 released. * Makefile.in: Prevent sa-mimedefang.cf from being overwritten. * mimedefang.filter.5.in: Correct some documentation errors. * mimedefang.pl.in: Added and documented read_commands_file function so that you can initialize certain global variables in filter_sender and filter_recipient. Code contributed by Jan Pieter Cornet. * mimedefang-multiplexor.c: Log UNIX error code if problem communicating with a worker. * mimedefang-multiplexor.c: Implement new commands "help", "workers", and "workerinfo". * watch-mimedefang: added -command, -interval, -10s, -1m, -5m, -10m and -title command-line options. * mimedefang.pl.in(message_contains_virus_clamd): Time out if clamd doesn't respond in 8 seconds. Based on a patch from Chris Myers. * mimedefang-spec.in: Fix error in %preun script. * watch-mimedefang.in: Many bug fixes. 2004-07-28 Dianne Skoll * Version 2.45-BETA-2 released. * Makefile.in: Do not overwrite /etc/mail/sa-mimedefang.cf in install-redhat target * mimedefang-multiplexor.c: Track average latency of scan commands. * watch-mimedefang.in: Huge rewrite. Displays a lot more info about the MIMEDefang server. Can monitor a remote MIMEDefang server over a low-bandwidth SSH connection. * mimedefang.pl.in: Log helpful messages if clamd fails with an error (Tomas Kopal) * md-mx-ctrl.c: Support the "-i" command-line option to read commands from stdin and send results to stdout. Used to support watch-mimedefang's low-bandwidth remote monitoring. 2004-07-23 Dianne Skoll * Version 2.45-BETA-1 released. * mimedefang-filter.5.in: Clarified description of action_bounce. * mimedefang-multiplexor.c: Added support for keeping "load average" histories so you can see how loaded your system is. * mimedefang-multiplexor.c: Raw 'status' output includes time when multiplexor was first started. * md-mx-ctrl.8.in: Documented new 'load' and 'rawload' commands. * mimedefang.pl.in: Implemented a new set of RBL lookup functions that perform multiple lookups in parallel and allow you to set a timeout (the timeout applies to ALL lookups, not each individual lookup.) These new functions require the Net::DNS module. * md-mx-ctrl.c: Implement 'load' and 'rawload' commands. 2004-07-15 Dianne Skoll * Version 2.44 RELEASED 2004-07-15 Dianne Skoll * Move /etc/mail/spamassassin/sa-mimedefang.cf to /etc/mail/sa-mimedefang.cf *** NOTE INCOMPATIBILITY *** * README, README.ANOMY, README.NONROOT, README.SOPHIE, README.SPAMASSASSIN: Update some README files that haven't been touched in ages. * configure.in: Remove --enable-running-on-scummy-sco command-line option. * configure.in: Hard-code a successful test for "wait3" on Solaris 9. * mimedefang.c: Add -v option to print version and exit. * mimedefang-multiplexor.c: Add -v option to print version and exit. * mimedefang.pl.in: Check that the "IP Validation Header" begins with X-MIMEDefang-Relay and refuse to use it if not. * mimedefang-multiplexor.c (sigterm): If multiplexor is killed, we kill all workers with SIGTERM. We wait for up to 10 seconds, and if there are still workers that haven't exited, we kill them with SIGKILL. 2004-06-21 Dianne Skoll * Version 2.44-BETA-1 released. * NEW: Support for Sendmail's SOCKETMAP map class. See mimedefang-filter(5) and mimedefang-multiplexor(8) for details. This code has been present for a long time, but was commented out; it is now enabled. * NEW: Support for Sendmail's QUARANTINE feature. This leaves quarantined messages in your mail queue, and is NOT THE SAME as MIMEDefang's quarantine. See the Sendmail documentation for details. * mimedefang-multiplexor.c: If platform does not safely support an embedded Perl interpreter, return an informative message when user does "md-mx-ctrl reread". * embperl.c: Fix a memory leak that apparently occurs on FreeBSD. * mimedefang.pl.in: Add "Precedence: bulk" headers to all MIMEDefang-generated notifications. * configure.in: Fix a couple of typos * configure.in: Check for Unix::Syslog or Sys::Syslog, even if invoked with --disable-check-perl-modules * init scripts: Kill mimedefang with TERM rather than KILL signal, and wait for it to exit. * mimedefang-filter.5.in: Correct error in documentation of md_check_against_smtp_server. 2004-05-10 Dianne Skoll * Version 2.43 RELEASED 2004-05-07 Dianne Skoll * filter_relay, filter_sender and filter_recipient can return a fifth element specifying a delay before returning a code to the SMTP client. This lets you implement tarpitting without tying up a Perl worker. However, the delay does tie up a libmilter thread. * mimedefang.pl.in: If resending a message fails during streaming, we bounce the message and log an error at LOG_CRIT importance. * Modified C and Perl code so that filter_relay is called when remote client connects rather than after MAIL FROM. This means the $helo argument is NOT available! *** NOTE INCOMPATIBILITY *** filter_relay no longer has access to the HELO argument, nor does the MIMEDefang spool directory exist when filter_relay is called. 2004-04-20 Dianne Skoll * mimedefang-multiplexor.c: Add -X option to run a "tick" request every so often. Added -Y option to set syslog label. * mimedefang.pl.in: Call md_openlog lazily so users can call it from filter to change syslog label. * mimedefang.pl.in: Added hooks for filter_tick routine. 2004-03-31 Dianne Skoll * Version 2.42 RELEASED 2004-03-30 Dianne Skoll * redhat/mimedefang-init.in: Use "md-mx-ctrl reread" in preference to "kill -INT" to force a rules reread. 2004-03-26 Dianne Skoll * mimedefang.pl.in (md_copy_orig_msg_to_work_dir): Added md_copy_orig_msg_to_work_dir and md_copy_orig_msg_to_work_dir_as_mbox_file functions to help virus-scanners that want the entire message, or want it in UNIX mbox format. 2004-03-24 Dianne Skoll * MIMEDefang 2.42-BETA-1 * mimedefang.pl.in (spam_assassin_mail): Add support for SpamAssassin 3.0.0's new Perl API. * mimedefang-multiplexor.c (activateWorker): Call closelog() so embedded Perl interpreter doesn't accidentally reuse syslog file decriptor (Josh Kelley) 2004-03-16 Dianne Skoll * Version 2.41 RELEASED * mimedefang.pl.in (send_quarantine_notifications): Include host name in quarantine mail body (Dirk Mueller). * mimedefang.c (envfrom): Create directories with mode 0750 instead of 0700. 2004-03-09 Dianne Skoll * mimedefang.pl.in (entity_contains_virus_clamd): Check for "ERROR" return message from clamd (Nate Carlson). (action_quarantine_entire_message): Do not send out an e-mail message if $msg is non-blank (pointed out by many users...) (entity_contains_virus_hbedv): Replace -allfiles with correct --allfiles (Ken Cormack) (entity_contains_virus_sophos): Add -mime option for Sophos sweep (Dirk Mueller) 2004-03-05 Dianne Skoll * Version 2.40 RELEASED * mimedefang.pl.in (entity_contains_virus_avp5): Added support for Kaspersky "aveclient" program, based on patch from Enrico Ansaloni. 2004-03-04 Dianne Skoll * mimedefang.pl.in (re_match_in_zip_directory): Added function to look inside zip archives (if Archive::Zip is installed) to do filename matching. Modified example filter to call it. 2004-03-03 Dianne Skoll * MIMEDefang 2.40-BETA-3 * mimedefang.pl.in (do_scan): Make a replica of INPUTMSG under Work/ so that virus-scanners with built-in MIME decoders can have a crack at the original input message. Also added --mbox option for clamscan. 2004-03-01 Dianne Skoll * mimedefang.pl.in (action_quarantine_entire_message): Do not include $msg argument in original mail; just use it in admin notification. 2004-02-24 Dianne Skoll * Documentation and cosmetic fixes from Matt Selsky. * Loosened spool directory permissions -- made them group-readable so you can run ClamAV as its own user (as long as it's in the defang group.) * Modified spec file to allow detection of AV software at build time using --with 'antivirus' (From No. 6) 2004-02-23 Dianne Skoll * mimedefang.pl.in (md_openlog): Added LOG_NDELAY option (Recommended by "Don") (entity_contains_virus_trend): Added "-a" option and other minor fixes from "Number 6". 2004-02-20 Dianne Skoll * MIMEDefang 2.40-BETA-2 * notifier.c: Added the multiplexor "notification" facility. This is an experimental new interface that lets the multiplexor inform external programs about state changes; see the mimedefang-notify(7) man page for details. 2004-02-11 Dianne Skoll * mimedefang.pl.in (interpret_hbedv_code): Make the regexp that picks out virus name for Vexira and H+BEDV more forgiving. 2004-02-03 Dianne Skoll * examples/suggested-minimum-filter-for-windows-clients: Just discard viruses. Don't bother checking each entity. * mimedefang.pl.in (entity_contains_virus_trend): Use the "-za" flag (suggested by "Number 6") 2004-01-29 Dianne Skoll * MIMEDefang 2.40-BETA-1. * mimedefang.pl.in: Enable "use warnings" so we get warnings even in embedded interpreter. (problem noted by Dave O'Neill). * mimedefang.pl.in: Added message_contains_virus and entity_contains_virus functions to mimedefang.pl.in. They use *every* installed virus scanner. Based on idea from Chris Myers. *** NOTE INCOMPATIBILITY *** The previous example filter defined functions called message_contains_virus and entity_contains_virus. These are now defined in mimedefang.pl itself; you should remove the definitions from your filter! 2004-01-28 Dianne Skoll * examples/suggested-minimum-filter-for-windows-clients: Remove all action_quarantine* from sample filter. 2004-01-26 Dianne Skoll * contrib/fang.pl (make_message): Patch to handle multiple parts (contributed by Eric Emerson). 2004-01-22 Dianne Skoll * configure.in: Fix bug in BDC virus-scanner detection * mimedefang.pl.in: Remove confusing "8.12.9/8.12.9" text from synthesized Received: header * mimedefang.pl.in: use MIME::Entity::dup() to fix destruction of multipart/digest messages. Fix due to Bryan Stansell. 2004-01-08 Dianne Skoll * configure.in: Use $PERL everywhere, not perl. Patch submitted by Jeff Makey * examples/suggested-minimum-filter-for-windows-clients (filter_end): Remove call to remove_redundant_html_parts from default filter. 2003-12-03 Dianne Skoll * mimedefang.pl.in (interpret_sweep_code): Return 'ok' for a Sophos return code of 2. Sophos Sweep seems to choke on M$ docs; this is dangerous!! Use another virus-scanner if possible. 2003-11-23 Dianne Skoll * Version 2.39 RELEASED 2003-11-14 Dianne Skoll * MIMEDefang 2.39-BETA-2. * mimedefang.c (set_reply): Double '%' characters in message argument to smfi_setreply. * suggested-minimum-filter-for-windows-clients: Fix syntax error in bad filename regular expression. 2003-11-12 Dianne Skoll * MIMEDefang 2.39-BETA-1. 2003-11-10 Dianne Skoll * mimedefang-multiplexor.c: Added support for embedding a Perl interpreter, which should improve performance a fair bit. * mimedefang.pl.in: Added support for "filter_initialize" function that gets called once each time a worker is activated. If you are using an embedded Perl interpreter, read the mimedefang-filter man page carefully, especially the INITIALIZATION AND CLEANUP section! 2003-10-25 Dianne Skoll * mimedefang-multiplexor.c (doStatus): Fix memory leak in doStatus. (doHistogram): Added "histo" command to md-mx-ctrl. It prints a histogram showing how often a given number of workers have been busy. 2003-10-22 Dianne Skoll * Makefile (MANIFEST): Updated contrib/graphdefang to graphdefang 0.9 (contributed by John Kirkland). 2003-10-14 Dianne Skoll * mimedefang.pl.in (entity_contains_virus_filescan): Set $CurrentVirusScannerMessage (problem noted by Ernst Du Plooy) 2003-10-09 Dianne Skoll * mimedefang.pl.in (serverloop): Fixed a bug in which recipient address was sometimes percent-escaped (eg "foo'bar@domain.net" became "foo%27bar@domain.net"). Problem noted by Patrick Morris. * examples/suggested-minimum-filter-for-windows-clients: Made the filter_bad_filename tests less paranoid. 2003-10-08 Dianne Skoll * Version 2.38 RELEASED 2003-10-08 Dianne Skoll * mimedefang.pl.in: Disable action_notify_sender if a virus is detected. 2003-09-24 Dianne Skoll * mimedefang.h: Change SMALLBUF definition from 4096 to 16384 for longer SpamAssassin reports. 2003-09-04 Dianne Skoll * configure.in: Fixed typo in --help output. 2003-09-04 Dianne Skoll * Version 2.37 RELEASED * mimedefang-multiplexor: Set FD_CLOEXEC flag on most descriptors so they are closed when Perl filter executed. 2003-09-03 Dianne Skoll * mimedefang.c (envfrom): Fixed bug whereby a file descriptor was leaked for _each_ message if -C flag given. 2003-08-12 Dianne Skoll * Version 2.36 RELEASED 2003-08-11 Dianne Skoll * mimedefang.c: Make more conservative use of file descriptors. Added "-C" option to enable ultra-conservative use of file descriptors (by closing/reopening files in each callback.) 2003-08-08 Dianne Skoll * MIMEDefang 2.36-BETA-3 released. * Added support for Bitdefender's bdc scanner (http://www.bitdefender.com). Based on code suggested by Philipp Baer. * examples/suggested-minimum-filter-for-windows-clients: Always add X-Spam-Score header if we run SpamAssassin. 2003-08-05 Dianne Skoll * MIMEDefang 2.36-BETA-2 released. * Makefile.in: "clean" target removes md-mx-ctrl (from Debian patch) * configure.in, utils.c: Try harder to get a reasonable definition of uint32_t * examples/suggested-minimum-filter-for-windows-clients: Proper regexp to detect CLSID attacks (Nik Clayton) * mimedefang-filter.5.in: Fixed typos. * mimedefang.pl.in: Check more stringently on the context of functions called by the filter. 2003-07-23 Dianne Skoll * MIMEDefang 2.36-BETA-1 released. * mimedefang-multiplexor.c, mimedefang.c: Added -D option to stay in foreground instead of daemonizing (Ben Kamen). * examples/init-script.in: Added MD_EXTRA variable (Jeremy Mates). * configure.in: Search for and use * mimedefang-filter.5.in: Clarify availability of global variables. * mimedefang.c: Get rid of last use of stdio in non-scalable location. Check every single smfi_xxxx callback for success and log a message on failure. 2003-07-11 Dianne Skoll * mimedefang.pl.in: Initialize $SALocalTestsOnly to 1. 2003-07-02 Dianne Skoll * Version 2.35 RELEASED * Documentation cleanups. * mimedefang.pl.in: Replace multiple incorrect instances of "sock" with "$sock". (action_notify_administrator): action_notify_administrator can be called OUTSIDE a message context, in which case it immediately sends e-mail to the administrator. (Suggested by Dirk Mueller.) (action_replace_with_url): Added optional "$salt" argument to perturb SHA1 hash calculation and avoid leaking information about whether an attachment has been received. Problem noted by Jeffrey Goldberg. (gen_date_msgid_headers): Generate proper time zone information in Date: and Received: headers. Noted by Stephane Lentz. 2003-06-26 Dianne Skoll * Version 2.34 RELEASED * mimedefang-multiplexor.c: Added "-I" option so you can specify the "backlog" argument to listen(2). Suggested by Kevin Brierly. * mimedefang.c: Added 'DISCARD' return value for filter_relay, filter_sender and filter_recipient (suggested by Ernst Du Plooy). 2003-06-20 Dianne Skoll * mimedefang-multiplexor.c: Added "-q" and "-Q" options. These are experimental; they allow requests to be queued until workers become free, rather than failing them immediately. See the mimedefang-multiplexor(8) man page for details. * mimedefang.pl.in (action_replace_with_url): Added optional fifth $cd_data (suggested by Jeremy Mates). (action_replace_with_warning): Name the warnings warning1.txt, warning2.txt, etc. instead of all warning.txt. Suggested by Steffen Kaiser. (interpret_nai_code): Handle (ED) in parsing of uvscan output. (Noted by Jeremy McCarty). 2003-06-19 Dianne Skoll * mimedefang.pl.in (entity_contains_virus_filescan): If File::Scan is not installed, return "not-installed" instead of "tempfail" (Problem noted by Richard Laager). * mimedefang.c: Added "-a" command-line option to pass additional Sendmail macros through to the filter. 2003-06-18 Dianne Skoll * examples/suggested-minimum-filter-for-windows-clients: Sample filter pre-compiles SpamAssassin rules; this may improve performance. Idea from Richard Laager 2003-06-10 Dianne Skoll * REMOVED support for RAV Antivirus. *** NOTE INCOMPATIBILITY *** 2003-06-04 Dianne Skoll * redhat/mimedefang-init: Copy the PID files into /var/run to keep Red Hat's silly killproc() function happy. * redhat/mimedefang-spec.in: The spec file now generates two RPM's: mimedefang and mimedefang-contrib * Renamed md_log to md_graphdefang_log. *** NOTE INCOMPATIBILITY *** YOU MUST update your filter, and change all instances of "md_log_enable" to "md_graphdefang_log_enable" and "md_log" to "md_graphdefang_log" 2003-06-03 Dianne Skoll * Experimental support for Sendmail SOCKETMAP feature (currently disabled because it requires a Sendmail patch.) * New feature: Added the filter_unknown_cmd hook so user-filters can extend the MIMEDefang protocol. Updated md-mx-ctrl as well. See mimedefang-protocol(7) and mimedefang-filter(5) for details. 2003-05-28 Dianne Skoll * SpamAssassin/spamassassin.cf: Tidied things up a bit. Added comments about how SA cannot modify the e-mail if used from MIMEDefang. * configure.in: Add /opt/rav/bin to ANTIVIR_PATH * mimedefang.pl.in (spam_assassin_mail): More fixes to the headers that get generated for SpamAssassin (Dirk Mueller). Also created and documented the $AddApparentlyToForSpamAssassin variable. 2003-05-27 Dianne Skoll * mimedefang.pl.in (spam_assassin_mail): Fix the way the synthesized Received: header was handed to SpamAssassin. * configure.in: Changed default location of quarantine directory to /var/spool/MD-Quarantine. *** NOTE INCOMPATIBILITY *** Use --with-quarantinedir=/var/spool/MIMEDefang if you want the old behaviour. * mimedefang.c: Removed support for non-multiplexor operation. It is now mandatory to use the multiplexor. *** NOTE INCOMPATIBILITY *** 2003-05-26 Dianne Skoll * configure.in, Makefile.in, mimedefang.pl.in, README.SOPHIE: Improved Sophie support, courtesy of Jason Englander. 2003-05-25 Dianne Skoll * Makefile.in: "make install" target obeys DESTDIR (as well as former RPM_INSTALL_ROOT) to change installation root. 2003-05-21 Dianne Skoll * mimedefang.pl.in: Check for socket errors when talking to daemonized virus scanners like Sophie, Trophie, Clamd and CarrierScan, and return tempfail on error. Problem noted by Chris Stromsoe and Dave Shrimpton. (relay_is_blacklisted): Rather than just returning true or false, we return the actual DNS lookup value (like "127.0.0.2") if a host is in a DNS-based blacklist. Feature requested by Matthew Hall. * Tempfail codes default to 451/4.3.0 rather than 450/4.7.1. These new codes are more consistent and in line with RFCs 2821 and 1893. 2003-05-14 Dianne Skoll * configure.in: Because of SCO's disgusting behaviour, MIMEDefang will refuse to build on SCO UNIX or SCO Linux unless you supply the --enable-running-on-scummy-sco configure option. 2003-05-12 Dianne Skoll * mimedefang-filter.5.in: Documented md_syslog. md_syslog is now an officially-supported API function. * mimedefang.c (mfconnect): Do not use strncpy in potentially-unsafe way (Dirk Mueller) * mimedefang.c, mimedefang.pl.in: Allow Perl filters to specify SMTP reply codes (4xx, 5xx) and DSN status codes (4.x.y, 5.x.y) (Suggested by user "jkohan" on the MIMEDefang Web site.) * mimedefang.pl.in: md_check_against_smtp_server returns the same SMTP reply code and DSN status as the forwarding server (rather than its own codes on failure.) 2003-04-25 Dianne Skoll * Version 2.33 RELEASED * mimedefang.c: Clean up working directory sooner in many different places. * mimedefang.c(eom): Delete all but the first "Content-Type:" header in the e-mail message, and log a warning if there is more than one such header. 2003-04-21 Dianne Skoll * syslog-fac.c: Added "-S" option to mimedefang and mimedefang-multiplexor to set syslog facility. Also, created and documented global variable $SyslogFacility in mimedefang-filter. 2003-04-19 Dianne Skoll * mimedefang.pl.in (synthesize_received_header): Add a Received: header when remailing messages. 2003-04-15 Dianne Skoll * Version 2.32 RELEASED * The function filter_recipient gets passed three additional arguments: $rcpt_mailer, $rcpt_host and $rcpt_addr, which are taken from the corresponding Sendmail macros. See the Sendmail documentation for more information. *** NOTE INCOMPATIBILITY *** filter_recipient is passed three additional arguments; if you use function prototypes, you may need to adjust your filter! * From filter_begin to filter_end, the hash %RecipientMailers contains rcpt_mailer, rcpt_host and rcpt_addr for each recipient. * Added support for Vexira Virus Scanner from Central Command, courtesy of John Rowan Littell. 2003-04-14 Dianne Skoll * mimedefang.pl.in (get_quarantine_dir): Save Sendmail queue-ID in quarantine directory. 2003-04-11 Dianne Skoll * mimedefang.pl.in (md_check_against_smtp_server): Add a timeout of 15 seconds to the socket connect call; otherwise, a down downstream SMTP server could cause the worker to be killed. 2003-03-27 Dianne Skoll * mimedefang.c (envfrom): When we create the spool directory, call it "mdefang-qid" where "qid" is the Sendmail queue identifier. If this fails, we fall back to the old way of generating spool directory names. * mimedefang.pl.in (replace_entire_message): Added replace_entire_message function to replace the entire message with a user-supplied MIME::Entity in filter_end. 2003-03-26 Dianne Skoll * mimedefang.pl.in: Use the "-oi" option to Sendmail when resending messages (Michael Sofka). 2003-03-21 Dianne Skoll * mimedefang.pl.in (md_log_enable): Added optional $enum_recips argument to control whether a line is logged for each recipient, or just a single line per message. (John Kirkland) * mimedefang.pl.in (gen_date_msgid_headers): We add proper "Date:" and "Message-ID:" headers to internally-generated MIMEDefang notifications. 2003-03-17 Dianne Skoll * Version 2.31 RELEASED 2003-03-13 Dianne Skoll * Manual page fixes * Add support for FPROTD scanner, courtesy of Steffen Kaiser * Add support for remote scanning with Symantec CarrierScan Server. 2003-03-05 Dianne Skoll * mimedefang-multiplexor.c (doStatusLog): Added "-L" option to mimedefang-multiplexor to periodically log worker status. * mimedefang.pl.in: Add support for Symantec CarrierScan Server virus scanner. 2003-03-04 Dianne Skoll * mimedefang.pl.in (serverloop): Escape "<" and ">" if $AddWarningsInline is true and we're appending the warning to an HTML part (Mickey Hill). 2003-03-03 Dianne Skoll * Makefile.in (MANIFEST): Sync to version 0.7 of graphdefang (John Kirkland) 2003-02-28 Dianne Skoll * mimedefang.pl.in: Added remove_redundant_html_parts() to delete HTML parts if a corresponding text/plain part is present in the message. * mimedefang-multiplexor.c: Major changes to the internal logic of the worker scheduler. Should be more efficient than the old system. 2003-02-27 Dianne Skoll * mimedefang-multiplexor.c (putOnFreeList): Fix logic errors in putOnFreeList and putOnBusyList 2003-02-25 Dianne Skoll * mimedefang.c: Reduce the use of stdio library to avoid hitting limits on the number of streams. Problem discovered by Nik Clayton. 2003-02-21 Dianne Skoll * Updated documentation (mimedefang-filter.5, mimedefang-protocol.7) to reflect current reality. * mimedefang.c (mfconnect): Added support for IPv6 addresses in mfconnect * mimedefang.pl: The global variable $MsgID is set to the Sendmail queue identifier in filter_relay, filter_sender and filter_recipient. 2003-02-14 Dianne Skoll * Version 2.30 RELEASED * mimedefang-multiplexor.c: On systems that support wait3 and fill in the usage structure, we log the worker's system and user CPU usage when it exits. The autoconf test is not too reliable on Solaris; sorry... * Create the temporary spool directory before calling filter_relay; store it in $CWD for filter_relay, filter_sender, filter_recipient, and the other filter functions, so we can pass state around. 2003-01-28 Dianne Skoll * Officially deprecated non-use of the multiplexor. I plan on dropping support for non-multiplexor operation by 31 July 2003. * mimedefang.c: A few typos fixed. * mimedefang.c: Better syslog messages on certain system call failures. * mimedefang.c: Added -M option to protect work directory creation with a mutex. * mimedefang.pl.in: resend_message returns a meaningful value (true on success; false on failure.) 2003-01-27 Dianne Skoll * mimedefang.c (envfrom): Fix potential descriptor leak. Problem noted by Nik Clayton. * mimedefang.c (envfrom): Fixed some incorrect syslog messages. Problem noted by Nik Clayton. * mimedefang-multiplexor.c: Add handling for filter_cleanup, that lets you run Perl code just before a worker is killed. Based on a suggestion by Brian Landers. 2003-01-22 Dianne Skoll * Version 2.29 RELEASED * COPYING (IMPORTANT NOTE): IMPORTANT CLARIFICATION ABOUT MIMEDEFANG'S LICENSE. PLEASE READ THE FILE "COPYING" VERY CAREFULLY. * Update contrib/graphdefang to version 0.6 2003-01-20 Dianne Skoll * mimedefang.pl.in (send_mail): Redirect sendmail's STDOUT to STDERR; otherwise, complaints from Sendmail can mess up the communication between the multiplexor and the workers. 2003-01-18 Dianne Skoll * mimedefang.c: Added -x option to set content of X-Scanned-By: header. 2002-12-30 Dianne Skoll * event_tcp.c (handle_readable): Multiplexor reads commands in chunks rather than a character at a time. Greatly reduces system-call overhead, but not likely to make much difference except on incredibly busy mail servers. 2002-12-29 Dianne Skoll * mimedefang.pl.in: Add support for Trophie scanning library. (Jason Englander) * Makefile.in: Clean up md-mx-ctrl in make distclean (Jason Englander) * mimedefang.pl.in: Add global $CWD variable so we don't need to exec pwd to find current working directory. 2002-12-24 Dianne Skoll * Log the Sendmail QUEUE-ID in most logging messages (mimedefang.c, mimedefang.pl.in) 2002-12-17 Dianne Skoll * Version 2.28 RELEASED * configure.in: Added --enable-pthread-flag to force use of "-pthread" C compiler flag (required for Tru64 UNIX) * mimedefang.c (body): Added hackish workaround for an optimization bug in gcc 3.2 on Sparc Solaris. 2002-12-12 Dianne Skoll * mimedefang.pl.in (spam_assassin_mail): Synthesize "Return-Path:" and "Received:" headers for SpamAssassin (Nels Lindquist). 2002-12-10 Dianne Skoll * mimedefang.c: For filter_relay, filter_sender and filter_recipient, use 'CONTINUE', 'TEMPFAIL' and 'REJECT' instead of 1, -1, and 0. Also add 'ACCEPT_AND_NO_MORE_FILTERING' to accept mail without further processing. The old numeric return codes still work, but are deprecated. * mimedefang-multiplexor.c (MAX_CMD_LEN): Increased MAX_CMD_LEN to 4096 from 512. 2002-12-05 Dianne Skoll * mimedefang-filter.5.in: Fixed many typos (Jason Englander) 2002-12-04 Dianne Skoll * mimedefang-multiplexor.c (findFreeWorker): Do not return a killed-but-not-yet-reaped worker. 2002-12-03 Dianne Skoll * Version 2.27 RELEASED 2002-12-03 Dianne Skoll * mimedefang-multiplexor.c: Count all worker commands as a "request", not just a "scan" command. Increase MX_MAXIMUM default to 200 from 100 to compensate. * mimedefang-multiplexor.c: More explicit log messages. * redhat/mimedefang-init: Call "ulimit -s 2048" if we're using lots of workers with the multiplexor. This prevents pthreads from complaining on Linux if you create hundreds of threads. (Brad Dameron) 2002-11-28 Dianne Skoll * mimedefang.pl.in (recipient_ok): Set global variables in filter_relay, filter_sender and filter_recipient based on the information available so far in the SMTP transaction. (Anne Bennet) * mimedefang.c: Fix warnings about const/non-const and signed/unsigned conversions (Anne Bennet) 2002-11-21 Dianne Skoll * md-mx-ctrl.c (MXCommand): Better error message for common case of permission denied. * mimedefang.pl.in: Fix regular expression code so we don't use $1 unless expression matched (Rudolph Pereira) * configure.in: Allow the use of either Unix::Syslog or Sys::Syslog, preferring Unix::Syslog if both are present. MIMEDefang will now build if you have Unix::Syslog, but not Sys::Syslog. Bug reported by Rudolph Pereira. 2002-11-20 Dianne Skoll * mimedefang-multiplexor.c (findFreeWorker): When looking for a free worker, prefer one that is running over one that needs startup. DOH! 2002-11-19 Dianne Skoll * Version 2.26 RELEASED * Quarantine notifications no longer sent unless you explicitly ask for them. *** INCOMPATIBILITY *** * mimedefang.pl.in (send_quarantine_notifications): Added send_quarantine_notifications routine which actually sends quarantine notifications. Unless you call this function in filter_end, quarantine notifications are NO LONGER SENT. * contrib: Update to graphdefang-0.5 (John Kirkland) 2002-11-12 Dianne Skoll * Version 2.26-BETA-1 released. * mimedefang.pl.in (md_check_against_smtp_server): Added md_check_against_smtp_server to check recipient addresses before accepting them. 2002-11-07 Dianne Skoll * mimedefang.pl.in (resend_message_one_recipient): Do not hardcode sendmail path (Wolfgang Solfrank) * Version 2.25 RELEASED * Finally!! Documented /etc/mail/mimedefang-ip-key. Please see mimedefang-filter(5) and the section "PRESERVING RELAY INFORMATION". * mimedefang.pl.in: Added $RealRelayAddr and $RealRelayHostname which give the actual relay host, ignoring the IP address validation header. (Used by CanIt.) 2002-11-05 Dianne Skoll * mimedefang.pl.in (md_version): Added md_version() function which returns MIMEDefang version. 2002-10-31 Dianne Skoll * mimedefang.c (envfrom): Write out the value of the "verify" macro (Jeremy Mates) 2002-10-30 Dianne Skoll *mimedefang.pl.in: Remove append_boilerplate *** INCOMPATIBILITY *** The append_boilerplate function is gone. It never really worked properly. Instead, use append_text_boilerplate and append_html_boilerplate. * mimedefang.pl.in (message_contains_virus_clamd): Let clamd recurse through work directory. Make sure you run Clam AntiVirus version 0.52 or higher! (Jason Englander) * watch-mimedefang: Now generated at configure time from watch-mimedefang.in. Let's us specify @SPOOLDIR@ (Jason Englander) * mimedefang.pl.in (md_syslog): Use "%s" formatting string to avoid problems with % characters in message (Steffen Kaiser) * mimedefang.c (mfconnect): Check return value of smfi_setpriv. 2002-10-29 Dianne Skoll * mimedefang.c, mimedefang-multiplexor.c: We now flat-out refuse to run as root. * examples/suggested-minimum-filter-for-windows-clients: Sample filter now calls action_bounce() for viruses and message/partial parts. * configure.in: Add /usr/local/sbin:/usr/sbin:/sbin to search path for virus scanners. (Douglas Hunley) * examples/init-script.in: Change @DEFANGUSER_DEFAULT@ to @DEFANGUSER@ (Andrey Pevnev) 2002-10-28 Dianne Skoll * examples/suggested-minimum-filter-for-windows-clients: Check for and obey "tempfail" suggested action from virus scanner (Martin Bene) * Forgot to mention that default location for multiplexor socket is now @SPOOLDIR@/mimedefang-multiplexor.sock. 2002-10-24 Dianne Skoll * Version 2.24 RELEASED * Makefile.in: Silence some warnings in "install" target. * mimedefang.pl.in: Many functions which only make sense if called from filter_begin, filter, filter_multipart or filter_end syslog error messages if they are called from outside one of those functions. * md-mx-ctrl.c: Fix an off-by-one error. * watch-mimedefang: Minor GUI improvements. * mimedefang.pl.in (init_globals): Clear out globals after serverloop() so extraneous values don't hang around for filter_recipient, filter_relay and filter_sender * configure.in: Added --disable-anti-virus to turn off all searching for anti-virus programs. Building with RPM uses this option; you can set $Features{'Virus:XXX'} directly in your filter. * redhat/mimedefang-spec.in: Use --disable-anti-virus when building RPM * mimedefang-multiplexor.c (activateWorker): Reset signal handlers to default before starting Perl filter program. * Added md-mx-ctrl program and watch-mimedefang GUI. Tcl/Tk is required for "watch-mimedefang". 2002-10-22 Dianne Skoll * Do not generate /etc/mail/mimedefang-ip-key unless --with-ipheader configure argument given. * mimedefang-protocol.7.in: Updated protocol documentation. * mimedefang.c (body): Strip carriage-returns (\r) in C code before writing to INPUTMSG file. Saves time in Perl filter and disk I/O. * rm_r.c: Allocate proper space for dirent entry. Thanks to Heidi Hornstein * configure.in: Do not test for -pthread unless we're using gcc. Thanks to Heidi Hornstein * mimedefang.pl.in (resend_message): Do not remove angle brackets from $Sender when resending message. 2002-10-18 Dianne Skoll * Version 2.23 RELEASED * SECURITY UPDATE: An attacker with sufficient bandwidth may be able to crash mimedefang-multiplexor for versions up to 2.22. This attack cannot be used to execute attacker's code; it's only a denial-of-service attack. See next changelog entry for details: * event_tcp.c (handle_writeable): Check that state->f is non-NULL before dereferencing it. * event_tcp.c: Check for EINTR/EAGAIN on read() and write() system calls. * configure.in: Default DEFANGUSER to "defang" if --with-user not supplied. 2002-10-17 Dianne Skoll * Version 2.22 RELEASED * Added "-validate" flag to mimedefang.pl; see mimedefang.pl(8). Used by CanIt. * mimedefang and mimedefang-multiplexor chdir into the spool directory on startup. 2002-10-15 Dianne Skoll * mimedefang.pl.in: Use Unix::Syslog if it's found at ./configure time. * mimedefang.c (eom): The index argument to action_delete_header and action_change_header was not being obeyed. * mimedefang.pl.in: Added action_delete_all_headers * mimedefang.pl.in: Added support for clamd daemonized virus-scanner (Jason Englander) * mimedefang.pl.in: Fall back on setlogsock('inet') if setlogsock('unix') fails. Also, check for this in configure script so we don't fill logs with error messages unnecessarily. Thanks to Brian Landers and others for assistance with this. 2002-10-08 Dianne Skoll * mimedefang.c (eom): Log filter time even if we reject/discard/tempfail deliberately. This is the first time I've used "goto" in a very long time... :-) 2002-10-07 Dianne Skoll * mimedefang.pl.in: Added $MaxMIMEParts variable to terminate parsing and bounce the message if there are more than $MaxMIMEParts parts. This does *NOT* work unless you use our specially-patched MIME::Tools package, MIME-tools-5.411a-RP-Patched-02 or newer. * Update contrib/graphdefang to version 0.3 (John Kirkland) * Execute "rm" to clean up on systems which lack readdir_r * Fix bug in "RAV" anti-virus invocation (was Linux-specific) 2002-10-02 Dianne Skoll * Add "-dl" to kavdaemon options (Marcelo) * Clarified SpamAssassin documentation. * Added -pthread flag when compiling rm_r.c. May fix IRIX problems. * Compile two versions of drop_privs.c: A threaded one for mimedefang and a non-threaded one for mimedefang-multiplexor. * configure fixes for Tru64 UNIX. You may once again be able to use MIMEDefang with Sendmail 8.11.x. * Use sm_vsnprintf and sm_snprintf if platform lacks (v)snprintf Requires libsm.a in this case. 2002-10-01 Dianne Skoll * mimedefang-filter.5.in: Clarified filter documentation, especially with regards to global variables. Thanks to Tony Nugent for his useful post at http://lists.roaringpenguin.com/pipermail/mimedefang/2002-October/002576.html * mimedefang.pl.in: Added $VirusName variable (John Kirkland) * examples/suggested-minimum-filter-for-windows-clients: Added calls to md_log (John Kirkland) 2002-09-27 Dianne Skoll * utils.c (MXCommand): Slightly better error messages * mimedefang.pl.in: Added md_log_enable and md_log (John Kirkland) 2002-09-25 Dianne Skoll * redhat/mimedefang-spec.in: Remove references to mime-tools-patch.txt; change group of spool dirs to "defang" (Stephane Lentz) 2002-09-24 Dianne Skoll * rm_r.c (rm_r): Add compile-time option for forking/exec'ing /bin/rm to clean up, instead of using built-in C code. * mimedefang.pl.in (entity_contains_virus_sophos): Better regexp for filtering virus-scanner messages. (Michael McCarthy) * mimedefang.pl.in (action_defang): Make last three arguments to action_defang optional. (Ben Reser) * mimedefang.pl.in (anomy_clean_html): Workaround for Anomy "Use of uninitialized variable" errors (Aaron Paetznick) * mimedefang.pl.in: Add "MIME-Version: 1.0" and "Content-Type: text/plain" headers to internally-generated messages (Enrico Scholz) 2002-09-18 Dianne Skoll * mimedefang.c (eom): Added "-T" option to mimedefang. 2002-09-12 Dianne Skoll * mimedefang.c (envfrom): Log the directory name which could not be created if we were unable to create spool dir. * Version 2.21 RELEASED * Removed mime-tools-patch.txt. Instead, download the patched MIME-Tools tarball from the MIMEDefang site. * Documented $WarningLocation * SECURITY UPDATE: Default filter rejects attachments of type "message/partial". See http://online.securityfocus.com/archive/1/291514 2002-09-10 Dianne Skoll * mimedefang-multiplexor.c (statsLog): Do not log the date/time if we log stats using syslog; it's redundant. We still include a UNIX timestamp. 2002-09-06 Dianne Skoll * Version 2.20 RELEASED * mimedefang.pl.in: Quarantine functions try to make a hard link when copying messages; fall back to actual copy if hard link fails. This can greatly improve performance. * examples/suggested-minimum-filter-for-windows-clients: More "dangerous" extensions; tighter conditions for suspecting CLSID attack (thanks to Nik Clayton). 2002-09-04 Dianne Skoll * mimedefang.pl.in: The "-features" output includes MIMEDefang version as well as versions of selected Perl modules. 2002-08-29 Dianne Skoll * examples/suggested-minimum-filter-for-windows-clients: Added three new "dangerous" extensions: .app, .fxp and .prg. Thanks to Marco Berizzi. * examples/suggested-minimum-filter-for-windows-clients: Allow filenames like "foo@bar.com,innocuous.txt" rather than choking on the ".com," part. * mimedefang.c (cleanup): Use an internal C implementation of "rm -rf" rather than forking and execing /bin/rm. This should improve performance on heavily-loaded systems. 2002-08-25 Dianne Skoll * configure.in: Added --with-user=LOGIN configure-time option. We do not check for existence of this user at configure-time, because it would complicate building of RPM. * RPM creates "defang" user when installed. * Cleaned up configure script to use AC_MSG_xxx instead of echo in a lot of places. 2002-08-23 Dianne Skoll * Version 2.19 RELEASED * mimedefang.pl.in (signal_complete): Improved quarantine notification message. * Lowered some syslog output to "debug" level. * Fixed warnings about uninitialized variables. * "make install" target uses tighter permissions for /etc/mail/mimedefang-ip-key. (This experimental feature is for future release.) 2002-08-21 Dianne Skoll * Version 2.18 RELEASED * All internally-generated messages and resent messages are delivered in "deferred" mode now. IMPORTANT: If you run Sendmail 8.12, you MUST run a "client-submission queue runner", something like this at system startup: sendmail -Ac -qp1m 2002-08-20 Dianne Skoll * mimedefang.c (eom): Fixed dumb error in which cmdFP was closed before the final command was written. * mimedefang.c (eom): Write a final 'F' line to signify end of COMMANDS file. * mimedefang.pl.in (send_mail): Invoke Sendmail with "-odb" (background delivery) rather than "-odi" (immediate delivery). 2002-08-13 Dianne Skoll * Version 2.17 RELEASED * mimedefang.c (helo): Added the $Helo global variable to hold "HELO/EHLO" argument. Also, the HELO argument is passed to filter_relay, filter_sender and filter_recipient. See the mimedefang-filter(5) man page for details and examples. 2002-07-25 Dianne Skoll * mimedefang.pl.in (serverloop): Experimental change: Delete "\r" characters in message. Seemed to cause lots of difficulty with MIME::Tools. 2002-07-23 Dianne Skoll * mimedefang-protocol.7: Fixed typo: "I" is used to change a header value, not "U". Thanks to Mathias Herbert. * mimedefang.pl.in: Fixed typo: "tmpfail" should have been "tempfail" 2002-07-17 Dianne Skoll * Version 2.16 RELEASED 2002-07-17 Dianne Skoll * configure.in: Take out tests for libsm.a and libsmutil.a -- they are internal Sendmail libraries which should not be required. INCOMPATIBILITY: YOU MUST NOW USE SENDMAIL 8.12.X 2002-07-16 Dianne Skoll * mimedefang.pl.in (rebuild_entity): Fixed incorrect setting of $ext for parts with no filename (thanks to Javier Kohan) (action_discard): Set $Actions{'discard'} 2002-07-12 Dianne Skoll * mimedefang.c: Made X-Scanned-By: header a bit less verbose. 2002-07-11 Dianne Skoll * mimedefang.pl.in (entity_contains_virus_rav): Filter output of RAV to make it less verbose. (message_contains_virus_sophos): Filter output of Sophos to make it less verbose. 2002-07-10 Dianne Skoll * mimedefang.pl.in: Filter the output of H+BEDV, NAI, TREND and AVP to make output less verbose. * mimedefang.pl.in: message_contains_virus_trend: Fixed typo. 2002-07-08 Dianne Skoll * utils.c (MXRecipientOK): Pass additional first_recipient argument to filter_recipient. * Added support for "Clam AntiVirus" (http://www.clamav.net/) courtesy of Dejan Muhamedagic 2002-07-05 Dianne Skoll * mimedefang.pl.in (run_virus_scanner): Added "$match" argument to pick out interesting lines from virus-scanner messages. 2002-07-05 Dianne Skoll * mimedefang.pl.in: Integrated Norman Virus Control (nvcc) (http://www.norman.no/) 2002-07-04 Dianne Skoll * utils.c: Allow filter_sender, filter_recipient and filter_relay to explicitly indicate a tempfail. 2002-06-24 Dianne Skoll * mimedefang.pl.in: Better support for kav anti-virus (pointed out by Vadim Smelyansky) * redhat/mimedefang-spec.in: Minor fixes * mimedefang-multiplexor.c: Fix for compilation problems on BSD. 2002-06-14 Dianne Skoll * Version 2.15 RELEASED 2002-06-14 Dianne Skoll * Added README.SECURITY * examples/suggested-minimum-filter-for-windows-clients: Added calls to virus-scanner in sample filter. * Split mimedefang.c into mimedefang.c (milter-specific stuff) and utils.c (utilities for talking to multiplexor) 2002-06-13 Dianne Skoll * mimedefang.pl.in (message_contains_virus_rav): Fixed incorrect return code in message_contains_virus_rav * mimedefang-multiplexor: Added "-R" and "-M" options to limit memory usage of workers. Strongly recommended to help mitigate DoS attacks. * mimedefang-multiplexor.c (limit_mem_usage): Added ability to limit memory usage of workers to mitigate DoS attacks which use complicated MIME messages to consume lots of memory. All such messages will be tempfailed forever, so keep an eye on your logs. You'll see lines like this: Worker 0 stderr: Out of memory! Worker died prematurely -- check your filter rules 2002-06-11 Dianne Skoll * Added filter_recipient function; added ip and hostname arguments to filter_sender. Improved mechanism for communicating with filter_sender, filter_relay and filter_recipient functions. * INCOMPATIBILITY: filter_sender is now passed 3 arguments (sender, relay_ip, relay_hostname) instead of 1 (sender). You may have to adjust your filter rules. 2002-06-07 Dianne Skoll * mimedefang.c, mimedefang.pl.in: Pass a number of sendmail macros down to the filter. * mimedefang.c: MAJOR internal change to communication mechanism between C and Perl. Instead of lots of little files, the outbound (C to Perl) direction uses three files: INPUTMSG -- input message HEADERS -- headers COMMANDS -- commands The inbound (Perl to C) uses (mostly) a single RESULTS file to pass results back. If message is changed, we use a NEWBODY file. 2002-06-06 Dianne Skoll * mimedefang.pl.in (action_replace_with_warning): Try to keep warnings inline. 2002-06-03 Dianne Skoll * Version 2.14 RELEASED 2002-06-03 Dianne Skoll * configure.in: Added support for --sysconfdir autoconf variable (which defaults to /etc, not PREFIX/etc]. Thanks to Andrey V. Pevnev. * configure.in: Added --with-confsubdir option (default mail) * configure.in: Added --with-milterinc and --with-milterlib arguments. (Thanks to Martin Matuska) * Added support for Sophie virus-scanning daemon, courtesy of Jason Englander. * Minor documentation cleanups. * Man pages are now generated by autoconf so they have correct path names. 2002-05-31 Dianne Skoll * mimedefang.pl.in (stream_by_recipient): Added stream_by_recipient function. * Version 2.13 RELEASED 2002-05-31 Dianne Skoll * examples/suggested-minimum-filter-for-windows-clients: Do not set Stupidity{"Flatten"}. * mimedefang.pl.in: Proper handling of action_add_part for messages of type multipart/alternative. 2002-05-29 Dianne Skoll * examples/init-script.in: Generic init script which should work on most UNIXes. * mimedefang.c (main): Drop privileges as soon as possible in mimedefang and mimedefang-multiplexor. That means you have to keep the pid files and sockets in /var/spool/MIMEDefang instead of /var/run. YOU MAY HAVE TO ADJUST YOUR SENDMAIL CONFIGURATION! 2002-05-24 Dianne Skoll * mimedefang.pl.in: Added support for F-Risk F-Prot, courtesy of Javier Kohan. 2002-05-23 Dianne Skoll * Version 2.12 RELEASED 2002-05-23 Dianne Skoll * suggested-minimum-filter-for-windows-clients: Moved SpamAssassin check to filter_end -- cleans up the code a bit. Also reject filenames with curly brackets in them to prevent CLSID attacks. * mimedefang, mimedefang-multiplexor: Refuse to run suid or sgid. * mimedefang.pl.in: Do not convert "multipart/alternative" to "multipart/mixed." 2002-05-22 Dianne Skoll * We now have a Sparc/Solaris machine for development! Thanks to Ben Kadish. * Fixed problem with not locating "libsm.a" on Solaris (and possibly other systems.) 2002-05-21 Dianne Skoll * mimedefang.pl.in: Added support for Trend Micro "vscan" virus scanner, courtesy of Stephane Lentz. Stephane considers the code alpha-quality... * mimedefang.pl.in: Use MIME::Word's "decode_mimewords" function instead of MIME::WordDecoder's "unmime". The latter would sometimes refuse to accept certain character sets. decode_mimewords is potentially lossy, but should be safer than unmime. 2002-05-18 Dianne Skoll * mimedefang.c: Did away with need for getpwnam_r; we do one password lookup and save results instead of doing a lookup in each thread. 2002-05-17 Dianne Skoll * drop_privs.c: Add thread-safe workaround for systems which lack the getpwnam_r function. 2002-05-17 Dianne Skoll * drop_privs.c: Use reentrant getpwnam_r routine instead of non-thread-safe getpwnam. * mimedefang.pl.in: Added $AddWarningsInline variable to add warnings right in the e-mail message text itself instead of adding an additional MIME part. * mimedefang-filter.5: Fixed typo (changed append_add_part to action_add_part). * configure.in: Added --disable-check-perl-modules option to skip Perl module checks. Do not use this option unless you know exactly what you're doing!!! 2002-05-15 Dianne Skoll * Move default stats location to /var/log/mimedefang/stats to more easily accomodate -U option. * mimedefang.c: Added -U option to run as non-root user. * mimedefang-multiplexor.c: Added -U option to run as non-root user. 2002-05-14 Michael McLagan * mimedefang.pl.in: Fixed RAV return code handling for non array requesting calls. 2002-05-14 Dianne Skoll * examples/suggested-minimum-filter-for-windows-clients: Added to list of banned extensions (thanks to Mickey Hill.) 2002-05-14 Michael McLagan * mimedefang.pl.in: Added spam_assassin_object and spam_assassin_mail * mimedefang-filter.5: Added spam_assassin _object and _mail descriptions and cleaned up language. * configure.in: Require SpamAssassin version >= 1.6. Removed the SAVER check as it's not used anywhere. 2002-05-13 Michael McLagan * mimedefang.pl.in: Added Reliable AntiVirus * mimedefang-filter.5: Added Reliable AntiVirus * configure.in: Added Reliable AntiVirus 2002-05-13 Dianne Skoll * Version 2.11 RELEASED 2002-05-13 Dianne Skoll * mimedefang.pl.in (spam_assassin_status): Keep a single SpamAssassin object persistent rather than creating a new one for each spam check. * Fixed stupid typos in mimedefang.pl.in and configure.in 2002-05-10 Dianne Skoll * Version 2.10 RELEASED 2002-05-10 Dianne Skoll * mimedefang-multiplexor.c (activateWorker): Added "-W" option to strictly limit rate of worker activation. 2002-05-09 Michael McLagan * mimedefang.pl.in: Added action_add_part * mimedefang-filter.5: Added description for action_add_part * examples/suggested-minimum-filter-for-windows-clients: Modified to use new function action_add_part * contrib/README: Added in linuxorg directory entry * contrib/linuxorg/README: A description of the files and how to install them. * contrib/linuxorg/filter: Included the filter file written for Linux Online & Linux Headquarters * contrib/linuxorg/spam-trusted-hosts: This lists hosts that we trust to insert proper SpamAssassin headers per the filter above. This file is a sample and it's empty. NOTE from dfs: Because this relies on message headers, it is possible to spoof, although real-world spammers would not likely do so. * contrib/linuxorg/spam-deliver: A collection of regex expressions which indicate email addresses to which SPAM mail is to be delivered to always, regardless of SpamAssassin's estimation of whether or not it is SPAM. The sample file excludes abuse@ and postmaster@ from having their SPAM discarded * Makefile.in: Modified install-redhat to create /etc/mail/mimedefang directory * Makefile.in: Modified to create mimedefang.spec from a .in file which will allow building BETA releases in addition to production ones. * redhat/mimedefang-spec.in: Created this file to use with above makefile change. It's a copy of the old mimedefang.spec file which was in this directory. * mimedefang.pl.in: Added $config_file optional parameter to spam_assassin_* calls. * mimedefang-filter.5: Added optional $config_file to spam_assassin_* calls. Added $OpenAVHost to list of global variables. * configure.in: dropped search for wvHtml since it's not used anymore 2002-05-09 Dianne Skoll * mimedefang.pl.in: Added append_text_boilerplate and append_html_boilerplate functions. These actually add boilerplate text in a semi-sensible way that should work even with HTML mail. 2002-05-08 Dianne Skoll * Added "-F" option to mimedefang and mimedefang-multiplexor to specify filter rules files. 2002-05-07 Dianne Skoll * mimedefang.pl.in (serverloop): Do not add a level of nesting when we rebuild messages. * mimedefang.c (eom): Set MIME-Version if we're mucking with MIME headers. * examples/suggested-minimum-filter-for-windows-clients: Add SpamAssassin report as a separate text/plain type rather than appending boilerplate text to message. * mimedefang.pl.in: Added global variables $AdminName, $AdminAddress, $NotifySenderSubject, $NotifyAdministratorSubject, $QuarantineSubject, $NotifyNoPreamble, $SALocalTestsOnly as suggested by Michael McLagan. * mimedefang.pl.in: Added new %Features keys Virus:FileScan, Virus:OpenAV, Virus:NAI, Virus:HBEDV, Virus:SOPHOS, Virus:AVP and Virus:FSAV as suggested by Michael McLagan. * mimedefang.pl.in: Added prototypes to almost all Perl routines as suggested by Michael McLagan. * Added support for F-Secure "fsav" anti-virus as suggested by David Green. 2002-05-06 Dianne Skoll * redhat/mimdefang-sysconfig: Added MX_WORKER_RATE variable. * mimedefang.pl.in: Fixed action_change_header to accept multiline headers. * mimedefang-multiplexor.c: Added "-w" option. The multiplexor now waits a small period of time between worker activations rather than activating them all at once. This should reduce the load on the server if you run many workers. * examples/suggested-minimum-filter-for-windows-clients: Do not call SpamAssassin for messages larger than 256kB. * event_tcp.c: Fixed syntax error if socklen_t not defined. * configure.in: Better detection of socklen_t typedef. * mimedefang-filter.5: Documented $DaemonName, $DaemonAddress and defang_warning. * examples/suggested-minimum-filter-for-windows-clients (filter): Call anomy_clean_html if Anomy::HTMLCleaner is installed. * mimedefang.pl.in (action_quarantine_entire_message): Added optional $msg argument which gets added to warning list and saved in quarantine dir. * mimedefang-filter.5: Documented $SuspiciousCharsInBody * mimedefang.pl.in: Fixed warnings about using uninitialized variables in sender_ok and relay_ok * mimedefang.pl.in: Renamed internal "sendmail" routine to "send_mail". 2002-05-03 Dianne Skoll * Version 2.9 RELEASED 2002-05-03 Dianne Skoll * Filters can check for presence of SpamAssassin at run-time using the %Features hash; therefore, the suggested filter has been merged with the SpamAssassin-enabled filter and we distribute only one filter. * mimedefang.c: Made default value for "-n" option 10. * Merged patches from Michael McLagan for packaging. 2002-05-02 Dianne Skoll * Improved scripts for Red Hat. The "mimedefang" init script is separated out from "sendmail", and settings are stored in /etc/sysconfig/mimedefang. * Added redhat/ directory for building RPMs. * Moved word-to-html to contrib/ dir; do not install it by default. * mimedefang.pl.in (message_contains_virus_filescan): Minor cleanups. * Updated documentation. * mimedefang.c (main): Added '-P' option to write mimedefang's process-ID to a file. * mimedefang.pl.in: SpamAssassin, Anomy::HTMLCleaner and File::Scan are detected at run-time, so if you install or remove those Perl modules, you do not need to rerun ./configure and install a new version of mimedefang.pl. 2002-05-01 Dianne Skoll * mimedefang.pl.in: Use the first found of: /etc/mail/spamassassin/sa-mimedefang.cf /etc/mail/spamassassin/local.cf /etc/mail/spamassassin.cf as the SpamAssassin preferences file. * Install our SpamAssassin preferences file as: /etc/mail/spamassassin/sa-mimedefang.cf * Updated examples/filter-using-spam-assassin. * Makefile.in: Added MANDIR and LIBDIR macros; added RPM_INSTALL_ROOT in front of install: targets to ease building of RPM's. Thanks to Michael McLagan for suggesting this. 2002-04-29 Dianne Skoll * mimedefang-multiplexor.c (handleWorkerReceivedAnswer): Distinguish between a timeout and the premature death of a worker. * Makefile.in (MANIFEST): Added contrib/ directory. * mimedefang.c (cleanup): Do not remove spool directories if "-k" command-line option given and the filter fails. * configure.in: Check for socklen_t at configure time. * mimedefang-multiplexor.c (handlePipe): Removed possibility of an extremely unlikely race condition. 2002-04-26 Dianne Skoll * mimedefang.pl.in (rebuild_entity): Added support for filter_multipart to examine the headers of parts with sub-parts. (interpret_avp_code): Updated intepretation of AVP return codes. 2002-04-26 Dianne Skoll * Version 2.8 RELEASED 2002-04-26 Dianne Skoll * configure.in: Added detection of unpatched MIME-Tools; moved virus-scanner status display to the end. * mimedefang.pl.in (message_rejected): Made message_rejected return true for action_bounce, action_tempfail AND action_discard. * examples/suggested-minimum-filter-for-windows-clients: Use re_match instead of re_match_ext to mitigate problems with malformed MIME. 2002-04-18 Dianne Skoll * mimedefang-filter.5: Added warning about unintended consequences of using action_bounce(). * Added "-p" option to mimedefang-multiplexor to write process-ID to a file. * If you send mimedefang-multiplexor a SIGINT signal, it terminates idle workers and forces busy workers to terminate as soon as they become idle. This is useful for forcing a reread of the filter rule file without stopping and restarting Sendmail. 2002-04-12 Dianne Skoll * Version 2.7 RELEASED 2002-04-12 Dianne Skoll * README: Updated documentation. * mimedefang-filter.5: Clarified documentation on action_rebuild(). * mimedefang.pl.in: Added support for File::Scan, thanks to Nels Lindquist. 2002-04-09 Dianne Skoll * mimedefang.c (MXSenderOK): Added calls to filter_sender to reject messages from blacklisted senders early. * INCOMPATIBILITY: You must now supply the "-r" flag to mimedefang if you want filter_relay to be called! Most sites do not use filter_relay, so it's a waste of resources to call it unnecessarily. 2002-04-02 Dianne Skoll * configure.in: Added "--with-quarantinedir" configure option. This lets you keep quarantined files in a separate directory from the spool directory. You can then keep the main spool directory on a RAM disk for better performance. 2002-03-25 Dianne Skoll * mimedefang.pl.in (message_rejected): Added message_rejected function to test if something earlier on has bounced or tempfailed message. 2002-03-15 Dianne Skoll * mimedefang.pl.in (action_notify_sender): Do nothing if $Sender is "<>". Thanks to Jason Englander. 2002-03-03 Dianne Skoll * mimedefang.pl.in (signal_complete): Do not notify sender if sender is '<>' * Correct typo in action array to make logging of actions accurate. Thanks to Martin Bene. * Redirect Sendmail's stdout to /dev/null when sending mail. 2002-02-26 Dianne Skoll * Version 2.6 RELEASED 2002-02-26 Dianne Skoll * examples/suggested-minimum-filter-for-windows-clients: Commented out anomy_clean_html so filter works out-of-the-box on all systems. 2002-02-25 Dianne Skoll * mimedefang.pl.in (rebuild_entity): Work around bug in MIME::Tools which fails to rebuild message of type "message/rfc822" * Added support for filter_relay function which lets you reject connections early on in the SMTP transaction in multiplex mode. Still works in non-multiplex mode, but not early on in the SMTP dialog. 2002-02-22 Dianne Skoll * mimedefang.pl.in (action_tempfail): Added $msg parameter which lets you customize the tempfail message. 2002-02-21 Dianne Skoll * mimedefang.c: Relax test for "suspicious" characters to worry only about embedded and characters. * Increased SMALLBUF from 256 to 2048 to handle long headers better. 2002-02-20 Dianne Skoll * mimedefang.c (body): Added checks for suspicious characters in e-mail body. * mimedefang.pl.in: Added $SuspiciousCharsInBody variable. * mimedefang.pl.in (spam_assassin_status()): Fixed all the spam_assassin functions to work with SpamAssasssin 2.x as well as 1.5. (time_str): Made generated quarantine directory names use dots instead of colons in time -- this makes them more Samba-friendly. * Deprecated action_rebuild. It causes problems. 2002-02-19 Dianne Skoll * mimedefang.pl.in (rebuild_entity): Reworked logic to avoid undefined ->bodyhandle calls. * More examples and a README in the examples/ directory. * mimedefang.pl.in (spam_assassin_check()): Made it work with SpamAssassin 2.0.1. Thanks to Mark Roedel. * (action_add_header): Correctly wrap headers whose values contain embedded newlines. 2002-02-18 Dianne Skoll * Version 2.5 RELEASED * mimedefang-multiplexor.c (statsLog): Added "-T" option to log statistics using syslog. 2002-02-17 Dianne Skoll * mimedefang.c (safeWriteHeader): Set a flag if suspicious characters are found in header; communicate that to mimedefang.pl * mimedefang.pl.in: Added action_rebuild and $SuspiciousCharsInHeaders * Remove examples/high-risk-filter and examples/low-risk-filter 2002-02-15 Dianne Skoll Version 2.4 RELEASED 2002-02-15 Dianne Skoll * mimedefang.c (safeWriteHeader): Added workaround for MIME-parsing bug in Microsoft Outlook. 2002-01-29 Dianne Skoll * Reworked the internals of event.c and event_tcp.c to handle timeouts more efficiently. 2002-01-26 Dianne Skoll * mimedefang-multiplexor.8: Updated synopsis 2002-01-25 Dianne Skoll * mimedefang-multiplexor.c: Add "-t" and "-u" options to log statistical information. * mimedefang.pl.in (action_bounce): Add newline to end of message. 2002-01-18 Dianne Skoll * Version 2.3 RELEASED 2002-01-18 Dianne Skoll * mimedefang.pl.in: Copy any headers added with action_add_header to NEWHEADERS in the quarantine directory if message is quarantined. 2002-01-17 Dianne Skoll * mimedefang-multiplexor.c (handleWorkerStderr): Log Perl stderr directly to syslog, so we catch error messages even in idle workers. Anything you print to STDERR in Perl gets sent to syslog. * Much more aggressive logging of errors. 2002-01-16 Dianne Skoll * mimedefang.c (envfrom): Apparently, mkdir(2) on Solaris can fail with EBADF, so we retry in that case. Thanks to Nathan Schimke for discovering this. 2002-01-15 Dianne Skoll * Added action_tempfail to force an SMTP "try again" code. * Got rid of generic message_contains_virus() and entity_contains_virus() functions. You have to use scanner-specific functions now. * The virus-scanner functions attempt to interpret scanner exit codes and suggest courses of action (tempfail, virus, etc.) * Added action_quarantine_entire_message() 2002-01-09 Dianne Skoll * mimedefang-multiplexor.c: Better error-condition handling. 2002-01-08 Dianne Skoll * mimedefang.pl.in (spam_assassin_is_spam()): Added call to finish() method on Spam Assassin status. * Added spam_assassin_check and spam_assassin_status, courtesy of Jeff Heinen. * mimedefang.pl.in (anomy_clean_html): Added support for the Anomy HTML Cleaner (see http://mailtools.anomy.net/) 2002-01-07 Dianne Skoll * mimedefang-multiplexor.c (handleWorkerStderr): Fixed bug in which errors would be directed to /STDERR instead of /var/spool/MIMEDefang/mdefang-xxxx/STDERR. DOH! 2002-01-03 Dianne Skoll * mimedefang.pl.in (action_external_filter): Made sure to set $Changed if external filter produces output. 2001-12-21 Dianne Skoll * mimedefang.pl.in: added message_contains_virus_openantivirus and entity_contains_virus_openantivirus * Integrated MIMEDefang with SpamAssassin. See http://spamassassin.taint.org/ for details on SpamAssassin. 2001-12-19 Dianne Skoll * mimedefang.pl.in: Added $MsgID and $QueueID variables. * mimedefang.c (envfrom): Put sendmail queue identifier in ./QUEUEID 2001-12-05 Dianne Skoll * Version 2.2 RELEASED 2001-12-05 Dianne Skoll * mimedefang.pl.in: Added action_notify_administrator. * mimedefang-multiplexor.8: Clarified effect of "-f" flag. 2001-12-04 Dianne Skoll * mimedefang.c (eom): If filter fails for any reason, TEMPFAIL the mail rather than adding X-MIMEDefang-Warning: header. 2001-12-03 Dianne Skoll * mimedefang.c, mimedefang-multiplexor.c: Error messages from Perl process are now logged to syslog at DEBUG level. 2001-11-30 Dianne Skoll * mimedefang-multiplexor.c (putOnFreeList): Made logging somewhat less verbose. * mimedefang.pl.in: Added add_recipient and del_recipient functions. * mimedefang.c: Added code to add/delete envelope recipients. 2001-11-29 Dianne Skoll * mimedefang.pl.in: Added stream_by_domain. * mimedefang.pl.in: Added resend_message subroutine. * examples/stream-by-domain-filter: Made an example showing how to "stream" messages by domain. This lets you have (for example) different rules for "foo@abc.com" than "foo@def.com" if you host virtual domains. * mimedefang.pl.in (action_quarantine): Made name of quarantine directory look like this: qdir-YYYY-MM-DD-HH:mm:ss-count. For example: qdir-2001-11-29-09:14:36-001 2001-11-28 Dianne Skoll * mimedefang.pl.in: added action_replace_with_url * mimedefang.pl.in: more comments * mimedefang.c: Do not add X-Scanned-By: header if an identical header to what would be added exists already. * mimedefang.c: Working directory based on time() instead of pthread_self(). 2001-10-26 Dianne Skoll * Version 2.1 RELEASED 2001-10-26 Dianne Skoll * Makefile.in: Install suggested-minimum-filter-for-windows-clients as mimedefang-filter.example * mimedefang-multiplexor.c: Handle SIGTERM and kill workers before exiting. * mimedefang-multiplexor.c: Change default busy timeout to 30 from 300 * examples/redhat-sendmail-init-script-with-multiplexor: Fix minor problems. * examples/redhat-sendmail-init-script: Fix minor problems. 2001-10-24 Dianne Skoll * mimedefang-multiplexor.c: More debug logging; put worker on free list if a busy worker dies (for whatever reason). * mimedefang-multiplexor.c: Scrupulous error checking of return codes from system calls. 2001-10-22 Dianne Skoll * Version 2.0 RELEASED 2001-10-22 Dianne Skoll * mimedefang.c: Added usage() function. * Added support for "-f" option to specify an alternate filter program. * Updated man pages. 2001-10-18 Dianne Skoll * Version 2.0 Beta 2: Support for Sendmail 8.12 Support for multiplexor to re-use long-lived Perl processes. 2001-07-19 Dianne Skoll * Version 1.3 RELEASED 2001-07-19 Dianne Skoll * configure.in: Added checks for libsm and libldap, courtesy of Nels Lindquist 2001-07-05 Dianne Skoll * mimedefang.c: Added "-d" flag to mimedefang which causes it not to delete spool files. DO NOT USE THIS FLAG ON A PRODUCTION MAIL SERVER. 2001-07-02 Dianne Skoll * mimedefang.pl.in: Added append_boilerplate function to append text to first text/plain or text/html part encountered. * Use mime_type method instead of mime_attr("Content-Type") 2001-06-30 Dianne Skoll * mimedefang.c (header): Removed embedded newlines and carriage returns so all headers in the HEADERS file are guaranteed to exist on a single line. (Makes parsing headers from Perl easier.) (header): Earlier closing of file descriptors to conserve them. 2001-06-19 Dianne Skoll * mimedefang.pl.in: Added the "-f filter" option and "-test" feature. * mimedefang.c: Complete restructuring to avoid memory allocation problems first noticed on Solaris. 2001-06-04 Dianne Skoll * Version 1.2 RELEASED. 2001-06-04 Dianne Skoll * configure.in: Added --disable-uvscan, --disable-sweep, --disable-AvpLinux and --disable-antivir to disable use of virus scanners. By default, MIMEDefang uses every virus scanner it can find on your system. * mimedefang.pl.in: Changed arguments to virus-scanners to make them less verbose (Thanks to Nels Lindquist) 2001-05-24 Dianne Skoll * mimedefang.pl.in: Added generic "message_contains_virus" and "entity_contains_virus" functions which use any supported virus scanner. * configure.in: Added checks for Sophos, NAI, Kaspersky and H+BEDV anti-virus scanners. * mimedefang.c: Close descriptors after fork() to reduce unnecessary file descriptor usage. 2001-05-07 Dianne Skoll * mimedefang.c, Makefile.in, configure.in: Detect -pthread option to GCC; fix a whole bunch of compilation warnings. Eliminate dependency on GNU "make". 2001-05-03 Dianne Skoll * mimedefang.pl.in: Added relay_is_blacklisted function. * mimedefang.c, mimedefang.pl.in: Added mechanism for adding headers from the Perl filter. * More error checking and syslogging of failures. * Notification e-mails are sent using a safer way to execute sendmail -- no shell is involved in the exec call. 2001-03-14 Dianne Skoll * Version 1.0 released. 2001-03-12 Dianne Skoll * mimedefang.c: Added "-n" option to limit number of concurrent Perl processes. Also added calls to syslog. 2001-03-08 Dianne Skoll * mimedefang.c: Made mfconnect tolerate NULL hostname and sa. * mimedefang.pl.in: Made quarantine notification messages include relay hostname and address, message headers and quarantined part headers. 2001-03-05 Dianne Skoll * mimedefang.pl.in: Incorporated patch from Nels Lindquist which adds support for NAI anti-virus and checks for sfio when linking against libmilter. * mimdefang.c: Re-worked code so that relay machine name and address are available. 2001-01-22 Dianne Skoll * mimedefang.pl.in (re_match_ext): Fixed re_match_ext so it doesn't complain about file names with no extensions. 2001-01-08 Dianne Skoll * mimedefang.pl.in: Now requires MIME::Tools version 5.410 * mimedefang.pl.in: Added $DaemonName to set the "full name" of mail from the daemon. * mimedefang.pl.in: Warning messages are appended to quarantine reports. * mimedefang.pl.in: Fixed bug in message_contains_virus_hbedv subroutine. 2001-01-02 Dianne Skoll * mimedefang.pl.in (main): Explicitly set output_to_core to 0 so we don't rely on MIME::tools defaults. 2000-12-18 Dianne Skoll (0.7 RELEASE) * all: Added test suite and test filter. * configure.in: Made spool directory for processing mail configurable (./configure --with-spooldir=DIR). In particular, we NO LONGER use /tmp by default; it's /var/spool/MIMEDefang. * mimedefang.pl.in: Added action_discard() action. * mimedefang.c (eom): Added check for DISCARD file to support action_discard in filter. 2000-12-11 Dianne Skoll * mimedefang.pl.in: Re-worked the way we handle Stupidity{"NoMultipleInlines"} for Microsoft Outlook. Thanks to Robert A. Levene for all of his testing and his patience. 2000-12-07 Dianne Skoll * mimedefang.c: Remove potentially troublesome MIME headers when converting a single-part message to a multi-part message. * Makefile.in: Added distro-beta target. 2000-12-06 Dianne Skoll * mimedefang.pl.in (re_match): Made the re_match functions case-insensitive. 2000-11-28 Dianne Skoll * mimedefang.c: Added ability to change headers so that single-part messages are correctly handled rather than being mangled. 2000-11-20 Dianne Skoll * configure.in: Added checks for -lnsl, -lresolv, -lsocket 2000-11-11 Dianne Skoll * configure.in: Added check for getopt.h; added config.h.in * mimedefang.pl.in: Added calls to filter_begin and filter_end; added support for H+BEDV AntiVir virus scanner. 2000-10-29 Dianne Skoll * configure.in: Added /usr/lib/libmilter to search path for libmilter.a (thanks to Jörgen Hägg). 2000-10-27 Dianne Skoll * mimedefang.pl.in: Made Perl script send mail using "sendmail" instead of "mail" so we can set the originating address to whatever we like. * mimedefang.pl.in: Added action_notify_sender to alert sender to the fact that their e-mail message has been modified. 2000-10-25 Dianne Skoll * mimedefang.pl.in: Changed "bounce" action to continue processing remaining parts of the message and only bounce at the end. 2000-07-10 Dianne Skoll * mimedefang-filter.5: Documented re_match and re_match_ext functions. * mimedefang.pl.in: Added re_match and re_match_ext functions to make filters more foolproof. * mimedefang.pl.in: Added "takeStabAtFilename" to make filter more reliable. 2000-07-03 Dianne Skoll * README: Added note about increasing milter timeout. 2000-06-19 Dianne Skoll * Renamed "MIMESweeper" to "MIMEDefanger" so as to avoid conflict with a trademark. 2000-06-20 Dianne Skoll * mimedefang.pl.in: Made "quarantine" action send only one notification per message, rather than one notification per quarantined part. Made "quarantine" action save more information to quarantine directory. Each quarantined message is in its own subdirectory, and the subdirectory contains message headers, sender, recipients and header and body of each quarantined part. mimedefang-3.6/LICENSE000066400000000000000000000432541475763067200145140ustar00rootroot00000000000000 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 Lesser 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. Copyright (C) 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. , 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 Lesser General Public License instead of this License. mimedefang-3.6/MANIFEST000066400000000000000000000075201475763067200146340ustar00rootroot00000000000000Changelog config.h.in configure configure.ac contrib/fang.pl contrib/filebeat.yml contrib/graphdefang-0.91/ChangeLog contrib/graphdefang-0.91/COPYING contrib/graphdefang-0.91/event/dovecot/general contrib/graphdefang-0.91/event/imapd/general contrib/graphdefang-0.91/event/ipop3d/general contrib/graphdefang-0.91/event/kernel/gshield contrib/graphdefang-0.91/event/kernel/ipchains contrib/graphdefang-0.91/event/mimedefang.pl/general contrib/graphdefang-0.91/event/sendmail/reject contrib/graphdefang-0.91/event/sendmail/spamd contrib/graphdefang-0.91/event/sendmail/sympa_outgoing contrib/graphdefang-0.91/event/sendmail/user_unknown contrib/graphdefang-0.91/event/spamd/general contrib/graphdefang-0.91/event/sympa/subscribe contrib/graphdefang-0.91/event/sympa/unsubscribe contrib/graphdefang-0.91/graphdefang-config-gshield-example contrib/graphdefang-0.91/graphdefang-config-mimedefang-example contrib/graphdefang-0.91/graphdefang-config-spamd-example contrib/graphdefang-0.91/graphdefang-config-sympa-example contrib/graphdefang-0.91/graphdefang.pl contrib/graphdefang-0.91/graphdefanglib.pl contrib/graphdefang-0.91/Makefile.PL contrib/graphdefang-0.91/README contrib/graphdefang-0.91/reset-max-date.pl contrib/graphdefang-0.91/TODO contrib/graphdefang-0.91/web/graphdefang.cgi contrib/graphdefang-0.91/web/index.php contrib/greylisting/greylist-mysql.sql contrib/greylisting/greylist-pgsql.sql contrib/greylisting/greylist-sqlite.sql contrib/greylisting/mimedefang-filter contrib/linuxorg/filter contrib/linuxorg/README contrib/linuxorg/spam-deliver contrib/linuxorg/spam-trusted-hosts contrib/munin/mimedefang_munin_plugin contrib/README contrib/word-to-html docker-compose-postfix.yml docker-compose-sendmail.yml drop_privs.c dynbuf.c dynbuf.h embperl.c event.c event.h event_tcp.c event_tcp.h eventpriv.h examples/init-script.in examples/README examples/redhat-logrotate-file examples/stream-by-domain-filter examples/suggested-minimum-filter-for-windows-clients gen-ip-validator.pl gen_id.c install-sh LICENSE Makefile.in md-mx-ctrl.8.in md-mx-ctrl.c milter_cap.c milter_cap.h mimedefang-filter.5.in mimedefang-multiplexor.8.in mimedefang-multiplexor.c mimedefang-notify.7.in mimedefang-protocol.7.in mimedefang-release.pl.in mimedefang.8.in mimedefang.c mimedefang.h mimedefang.pl.8.in mimedefang.pl.in modules/lib/Mail/MIMEDefang.pm modules/lib/Mail/MIMEDefang/Actions.pm modules/lib/Mail/MIMEDefang/Antispam.pm modules/lib/Mail/MIMEDefang/Antivirus.pm modules/lib/Mail/MIMEDefang/Authres.pm modules/lib/Mail/MIMEDefang/DKIM.pm modules/lib/Mail/MIMEDefang/DKIM/ARC.pm modules/lib/Mail/MIMEDefang/Mail.pm modules/lib/Mail/MIMEDefang/MIME.pm modules/lib/Mail/MIMEDefang/Net.pm modules/lib/Mail/MIMEDefang/RFC2822.pm modules/lib/Mail/MIMEDefang/SPF.pm modules/lib/Mail/MIMEDefang/Unit.pm modules/lib/Mail/MIMEDefang/Utils.pm notifier.c README.md README.NONROOT README.SECURITY README.SOPHIE README.SPAMASSASSIN redhat/mimedefang-init.in redhat/mimedefang-spec.in redhat/mimedefang-sysconfig.in redhat/mimedefang.spec rm_r.c script/mimedefang-util.1 script/mimedefang-util.in SpamAssassin/spamassassin.cf syslog-fac.c systemd-units/mimedefang-multiplexor.service systemd-units/mimedefang.service t/actions.t t/antispam.t t/arc.t t/authres.t t/core.t t/critic.t t/data/arc-wrong.eml t/data/arc1.eml t/data/arc2.eml t/data/COMMANDS t/data/dkim.pem t/data/dkim1.eml t/data/dkim2.eml t/data/dkim3.eml t/data/dkim_sig.txt t/data/dkim_sig2.txt t/data/dkim_sig2a.txt t/data/exe.eml t/data/gtube.eml t/data/md.conf t/data/mimedefang-test-filter t/data/multipart.eml t/data/rspamd.conf t/data/spf1.eml t/data/tgz.eml t/data/uri-html.eml t/data/uri.eml t/data/zip.eml t/dates.t t/dkim.t t/dockerPostfix.sh t/dockerSendmail.sh t/headers.t t/mime.t t/net.t t/relay.t t/sender.t t/smtp.t t/spf.t t/utils.t utils.c watch-mimedefang.8 watch-mimedefang.in watch-multiple-mimedefangs.8 watch-multiple-mimedefangs.tcl xs_init.c mimedefang-3.6/MANIFEST.SKIP000066400000000000000000000001131475763067200153700ustar00rootroot00000000000000\.git/ \.github/ \.gitignore ^autom4te.cache/ \.o$ \.tar.gz$ \.tar.gz.sig$ mimedefang-3.6/Makefile.in000066400000000000000000000417411475763067200155530ustar00rootroot00000000000000# DO NOT EDIT MAKEFILE; EDIT MAKEFILE.IN INSTEAD # Makefile.in for mimedefang # Needed for autoconf to behave properly... datarootdir=@datarootdir@ srcdir=@srcdir@ top_srcdir=@top_srcdir@ VPATH=@srcdir@ prefix=@prefix@ exec_prefix=@exec_prefix@ sysconfdir=@sysconfdir@ CONFSUBDIR=@CONFSUBDIR@ CONFDIR=${sysconfdir}${CONFSUBDIR} DEFANGUSER=@DEFANGUSER@ MANDIR=@mandir@ MINCLUDE=@MINCLUDE@ HAVE_SPAM_ASSASSIN=@HAVE_SPAM_ASSASSIN@ DEFS=-D_POSIX_PTHREAD_SEMANTICS \ @EMBPERLDEFS@ \ @USEPOLL@ \ @ENABLE_DEBUGGING@ -DPERL_PATH=\"@PERL@\" \ -DMIMEDEFANG_PL=\"$(prefix)/bin/mimedefang.pl\" \ -DVERSION=\"@VERSION@\" \ -DSPOOLDIR=\"@SPOOLDIR@\" \ -DQDIR=\"@QDIR@\" \ -DCONFDIR=\"${CONFDIR}\" CC=@CC@ PERL=@PERL@ INSTALL=@INSTALL@ CFLAGS=@CFLAGS@ EMBPERLCFLAGS=@EMBPERLCFLAGS@ PTHREAD_FLAG=@PTHREAD_FLAG@ EMBPERLOBJS=@EMBPERLOBJS@ EMBPERLLDFLAGS=@EMBPERLLDFLAGS@ EMBPERLLIBS=@EMBPERLLIBS@ LIBS_WITHOUT_PTHREAD=@LIBS_WITHOUT_PTHREAD@ LIBS=@LIBS@ PERLINSTALLSITELIB=@PERLINSTALLSITELIB@ PERLINSTALLPRIVLIB=@PERLINSTALLPRIVLIB@ PERLVENDORPREFIX=@PERLVENDORPREFIX@ PERLVENDORLIB=@PERLVENDORLIB@ LDFLAGS=@LDFLAGS@ IP_HEADER=@IP_HEADER@ SPOOLDIR=@SPOOLDIR@ QDIR=@QDIR@ VERSION=@VERSION@ INSTALL_STRIP_FLAG=-s ## NO MORE ./configure substitutions beyond this point! all: mimedefang mimedefang-multiplexor md-mx-ctrl pod2man mimedefang-multiplexor: mimedefang-multiplexor.o event.o event_tcp.o drop_privs_nothread.o notifier.o syslog-fac.o utils.o $(EMBPERLOBJS) $(CC) $(CFLAGS) -o mimedefang-multiplexor mimedefang-multiplexor.o event.o event_tcp.o drop_privs_nothread.o syslog-fac.o notifier.o utils.o $(EMBPERLOBJS) $(LIBS_WITHOUT_PTHREAD) $(EMBPERLLDFLAGS) $(EMBPERLLIBS) embperl.o: embperl.c $(CC) $(CFLAGS) $(EMBPERLCFLAGS) $(PTHREAD_FLAG) $(DEFS) $(MINCLUDE) -c -o embperl.o $(srcdir)/embperl.c xs_init.o: xs_init.c $(CC) $(CFLAGS) $(EMBPERLCFLAGS) $(PTHREAD_FLAG) $(DEFS) $(MINCLUDE) -c -o xs_init.o $(srcdir)/xs_init.c xs_init.c: embperl.c $(PERL) -MExtUtils::Embed -e xsinit -- -o $(srcdir)/xs_init.c test-embed-perl.o: test-embed-perl.c $(CC) $(CFLAGS) $(EMBPERLCFLAGS) $(PTHREAD_FLAG) $(DEFS) $(MINCLUDE) -c -o test-embed-perl.o $(srcdir)/test-embed-perl.c te: test-embed-perl.o $(CC) $(CFLAGS) -o te test-embed-perl.o $(LIBS_WITHOUT_PTHREAD) $(EMBPERLLDFLAGS) $(EMBPERLLIBS) rm_r.o: rm_r.c $(CC) $(CFLAGS) $(PTHREAD_FLAG) $(DEFS) $(MINCLUDE) -c -o rm_r.o $(srcdir)/rm_r.c syslog-fac.o: syslog-fac.c $(CC) $(CFLAGS) $(PTHREAD_FLAG) $(DEFS) $(MINCLUDE) -c -o syslog-fac.o $(srcdir)/syslog-fac.c md-mx-ctrl: md-mx-ctrl.o $(CC) $(CFLAGS) -o md-mx-ctrl md-mx-ctrl.o $(LIBS_WITHOUT_PTHREAD) md-mx-ctrl.o: md-mx-ctrl.c $(CC) $(CFLAGS) $(DEFS) $(MINCLUDE) -c -o md-mx-ctrl.o $(srcdir)/md-mx-ctrl.c event_tcp.o: event_tcp.c $(CC) $(CFLAGS) $(DEFS) $(MINCLUDE) -c -o event_tcp.o $(srcdir)/event_tcp.c notifier.o: notifier.c $(CC) $(CFLAGS) $(DEFS) $(MINCLUDE) -c -o notifier.o $(srcdir)/notifier.c drop_privs_nothread.o: drop_privs.c $(CC) $(CFLAGS) $(DEFS) $(MINCLUDE) -c -o drop_privs_nothread.o $(srcdir)/drop_privs.c drop_privs_threaded.o: drop_privs.c $(CC) $(CFLAGS) $(PTHREAD_FLAG) $(DEFS) $(MINCLUDE) -c -o drop_privs_threaded.o $(srcdir)/drop_privs.c event.o: event.c $(CC) $(CFLAGS) $(DEFS) $(MINCLUDE) -c -o event.o $(srcdir)/event.c mimedefang-multiplexor.o: mimedefang-multiplexor.c $(CC) $(CFLAGS) $(DEFS) $(MINCLUDE) -c -o mimedefang-multiplexor.o $(srcdir)/mimedefang-multiplexor.c mimedefang: mimedefang.o drop_privs_threaded.o utils.o rm_r.o syslog-fac.o dynbuf.o milter_cap.o gen_id.o $(CC) $(CFLAGS) $(PTHREAD_FLAG) -o mimedefang mimedefang.o drop_privs_threaded.o utils.o rm_r.o syslog-fac.o dynbuf.o milter_cap.o gen_id.o $(LDFLAGS) -lmilter $(LIBS) mimedefang.o: mimedefang.c mimedefang.h $(CC) $(CFLAGS) $(PTHREAD_FLAG) $(DEFS) $(MINCLUDE) -c -o mimedefang.o $(srcdir)/mimedefang.c utils.o: utils.c mimedefang.h $(CC) $(CFLAGS) $(PTHREAD_FLAG) $(DEFS) $(MINCLUDE) -c -o utils.o $(srcdir)/utils.c milter_cap.o: milter_cap.c mimedefang.h $(CC) $(CFLAGS) $(PTHREAD_FLAG) $(DEFS) $(MINCLUDE) -c -o milter_cap.o $(srcdir)/milter_cap.c dynbuf.o: dynbuf.c dynbuf.h $(CC) $(CFLAGS) $(PTHREAD_FLAG) $(DEFS) $(MINCLUDE) -c -o dynbuf.o $(srcdir)/dynbuf.c gen_id.o: gen_id.c mimedefang.h $(CC) $(CFLAGS) $(PTHREAD_FLAG) $(DEFS) $(MINCLUDE) -c -o gen_id.o $(srcdir)/gen_id.c clean:: FORCE rm -f *~ *.o mimedefang mimedefang-multiplexor md-mx-ctrl xs_init.c INPUTMSG pod2man:: script/mimedefang-util.1 lib/manpages script/mimedefang-util.1: script/mimedefang-util $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=1 --perm_rw=644 \ --center="MIMEDefang Documentation" \ --release=$(VERSION) \ script/mimedefang-util script/mimedefang-util.1 lib/manpages: $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=8 --perm_rw=644 \ --center="mimedefang-release Documentation" \ --name="mimedefang-release" \ --release=$(VERSION) \ mimedefang-release.pl mimedefang-release.8 mkdir -p modules/man $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang Documentation" \ --name="Mail::MIMEDefang" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang.pm modules/man/Mail::MIMEDefang.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::Actions Documentation" \ --name="Mail::MIMEDefang::Actions" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/Actions.pm modules/man/Mail::MIMEDefang::Actions.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::Antispam Documentation" \ --name="Mail::MIMEDefang::Antispam" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/Antispam.pm modules/man/Mail::MIMEDefang::Antispam.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::Antivirus Documentation" \ --name="Mail::MIMEDefang::Antivirus" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/Antivirus.pm modules/man/Mail::MIMEDefang::Antivirus.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::DKIM Documentation" \ --name="Mail::MIMEDefang::DKIM" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/DKIM.pm modules/man/Mail::MIMEDefang::DKIM.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::DKIM::ARC Documentation" \ --name="Mail::MIMEDefang::DKIM::ARC" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/DKIM/ARC.pm modules/man/Mail::MIMEDefang::DKIM::ARC.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::Authres Documentation" \ --name="Mail::MIMEDefang::Authres" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/Authres.pm modules/man/Mail::MIMEDefang::Authres.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::Mail Documentation" \ --name="Mail::MIMEDefang::Mail" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/Mail.pm modules/man/Mail::MIMEDefang::Mail.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::MIME Documentation" \ --name="Mail::MIMEDefang::MIME" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/MIME.pm modules/man/Mail::MIMEDefang::MIME.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::Net Documentation" \ --name="Mail::MIMEDefang::Net" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/Net.pm modules/man/Mail::MIMEDefang::Net.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::SPF Documentation" \ --name="Mail::MIMEDefang::SPF" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/SPF.pm modules/man/Mail::MIMEDefang::SPF.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::RFC2822 Documentation" \ --name="Mail::MIMEDefang::RFC2822" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/RFC2822.pm modules/man/Mail::MIMEDefang::RFC2822.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::Unit Documentation" \ --name="Mail::MIMEDefang::Unit" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/Unit.pm modules/man/Mail::MIMEDefang::Unit.3 $(PERL) "-MExtUtils::Command::MM" -e pod2man "--" --section=3 --perm_rw=644 \ --center="Mail::MIMEDefang::Utils Documentation" \ --name="Mail::MIMEDefang::Utils" \ --release=$(VERSION) \ modules/lib/Mail/MIMEDefang/Utils.pm modules/man/Mail::MIMEDefang::Utils.3 distclean:: clean rm -f config.log config.status Makefile config.cache config.h mimedefang.pl \ mimedefang-release.pl examples/init-script \ script/mimedefang-util \ mimedefang-filter.5 mimedefang-multiplexor.8 mimedefang-protocol.7 \ mimedefang-notify.7 \ mimedefang.8 mimedefang.pl.8 mimedefang-release.8 md-mx-ctrl.8 watch-mimedefang \ redhat/mimedefang-init redhat/mimedefang.spec redhat/mimedefang-sysconfig \ script/mimedefang-util.1 modules/man/*.3 rm -rf autom4te.cache install-redhat: install $(INSTALL) -m 755 -d $(DESTDIR)${sysconfdir}/rc.d/init.d $(INSTALL) -m 755 -d $(DESTDIR)${sysconfdir}/sysconfig $(INSTALL) -m 755 -d $(DESTDIR)${sysconfdir}/logrotate.d $(INSTALL) -m 0644 examples/redhat-logrotate-file $(DESTDIR)${sysconfdir}/logrotate.d/mimedefang # We install SpamAssassin config file unconditionally for Red Hat... $(INSTALL) -m 755 -d $(DESTDIR)${CONFDIR} if test -f $(DESTDIR)${CONFDIR}/sa-mimedefang.cf ; then \ $(INSTALL) -m 644 SpamAssassin/spamassassin.cf $(DESTDIR)${CONFDIR}/sa-mimedefang.cf.example || exit 1; \ else \ $(INSTALL) -m 644 SpamAssassin/spamassassin.cf $(DESTDIR)${CONFDIR}/sa-mimedefang.cf || exit 1; \ fi $(INSTALL) -m 755 redhat/mimedefang-init $(DESTDIR)${sysconfdir}/rc.d/init.d/mimedefang $(INSTALL) -m 644 redhat/mimedefang-sysconfig $(DESTDIR)${sysconfdir}/sysconfig/mimedefang $(INSTALL) -m 644 examples/suggested-minimum-filter-for-windows-clients $(DESTDIR)${CONFDIR}/mimedefang-filter install: all $(INSTALL) -m 755 -d $(DESTDIR)${CONFDIR} -if test "$(IP_HEADER)" = "yes" -a ! -f "$(DESTDIR)${CONFDIR}/mimedefang-ip-key" ; then \ $(PERL) ./gen-ip-validator.pl > $(DESTDIR)${CONFDIR}/mimedefang-ip-key ;\ -chmod 600 $(DESTDIR)${CONFDIR}/mimedefang-ip-key > /dev/null 2>&1 ; \ fi if test "$(DEFANGUSER)" != "" ; then \ if id "$(DEFANGUSER)" > /dev/null 2>&1 ; then \ chown "$(DEFANGUSER)" $(DESTDIR)${CONFDIR}/mimedefang-ip-key > /dev/null 2>&1 || true; \ test ! -d $(DESTDIR)$(SPOOLDIR) && $(INSTALL) -m 750 -o $(DEFANGUSER) -d $(DESTDIR)$(SPOOLDIR) > /dev/null 2>&1 || true; \ test ! -d $(DESTDIR)$(QDIR) && $(INSTALL) -m 750 -o $(DEFANGUSER) -d $(DESTDIR)$(QDIR) > /dev/null 2>&1 || true; \ fi \ else \ echo ""; \ echo "Please create the spool directory, '$(SPOOLDIR)',"; \ echo "if it does not exist. Give it mode 700 or 750, and make"; \ echo "it owned by the user and group you intend to run MIMEDefang as."; \ if test "$(QDIR)" != "$(SPOOLDIR)" ; then \ echo "Please do the same with the quarantine directory, '$(QDIR)'."; \ fi; \ fi $(INSTALL) -m 755 -d $(DESTDIR)$(prefix)/bin $(INSTALL) -m 755 -d $(DESTDIR)$(MANDIR)/man1 $(INSTALL) -m 755 -d $(DESTDIR)$(MANDIR)/man3 $(INSTALL) -m 755 -d $(DESTDIR)$(MANDIR)/man5 $(INSTALL) -m 755 -d $(DESTDIR)$(MANDIR)/man7 $(INSTALL) -m 755 -d $(DESTDIR)$(MANDIR)/man8 -test ! -d $(DESTDIR)$(SPOOLDIR) && mkdir -p $(DESTDIR)$(SPOOLDIR) && chmod 750 $(DESTDIR)$(SPOOLDIR) || true -test ! -d $(DESTDIR)$(QDIR) && mkdir -p $(DESTDIR)$(QDIR) && chmod 750 $(DESTDIR)$(QDIR) || true if test -f $(DESTDIR)${CONFDIR}/mimedefang-filter ; then \ $(INSTALL) -m 644 examples/suggested-minimum-filter-for-windows-clients $(DESTDIR)${CONFDIR}/mimedefang-filter.example || exit 1; \ else \ $(INSTALL) -m 644 examples/suggested-minimum-filter-for-windows-clients $(DESTDIR)${CONFDIR}/mimedefang-filter || exit 1; \ fi $(INSTALL) -m 755 $(INSTALL_STRIP_FLAG) mimedefang-multiplexor $(DESTDIR)$(prefix)/bin/mimedefang-multiplexor $(INSTALL) -m 755 $(INSTALL_STRIP_FLAG) md-mx-ctrl $(DESTDIR)$(prefix)/bin/md-mx-ctrl $(INSTALL) -m 755 $(INSTALL_STRIP_FLAG) mimedefang $(DESTDIR)$(prefix)/bin/mimedefang # Install .pm files if test -d $(DESTDIR)$(PERLINSTALLSITELIB) ; then \ $(INSTALL) -m 755 -d $(DESTDIR)$(PERLINSTALLSITELIB)/Mail ; \ $(INSTALL) -m 755 -d $(DESTDIR)$(PERLINSTALLSITELIB)/Mail/MIMEDefang ; \ $(INSTALL) -m 755 -d $(DESTDIR)$(PERLINSTALLSITELIB)/Mail/MIMEDefang/DKIM ; \ $(INSTALL) -m 644 modules/lib/Mail/MIMEDefang.pm $(DESTDIR)$(PERLINSTALLSITELIB)/Mail/MIMEDefang.pm ; \ $(INSTALL) -m 644 modules/lib/Mail/MIMEDefang/*.pm $(DESTDIR)$(PERLINSTALLSITELIB)/Mail/MIMEDefang/ ; \ $(INSTALL) -m 644 modules/lib/Mail/MIMEDefang/DKIM/*.pm $(DESTDIR)$(PERLINSTALLSITELIB)/Mail/MIMEDefang/DKIM/ ; \ else \ $(INSTALL) -m 755 -d $(DESTDIR)$(PERLVENDORLIB)/Mail ; \ $(INSTALL) -m 755 -d $(DESTDIR)$(PERLVENDORLIB)/Mail/MIMEDefang ; \ $(INSTALL) -m 755 -d $(DESTDIR)$(PERLVENDORLIB)/Mail/MIMEDefang/DKIM ; \ $(INSTALL) -m 644 modules/lib/Mail/MIMEDefang.pm $(DESTDIR)$(PERLVENDORLIB)/Mail/MIMEDefang.pm ; \ $(INSTALL) -m 644 modules/lib/Mail/MIMEDefang/*.pm $(DESTDIR)$(PERLVENDORLIB)/Mail/MIMEDefang/ ; \ $(INSTALL) -m 644 modules/lib/Mail/MIMEDefang/DKIM/*.pm $(DESTDIR)$(PERLVENDORLIB)/Mail/MIMEDefang/DKIM/ ; \ fi $(INSTALL) -m 755 watch-mimedefang $(DESTDIR)$(prefix)/bin/watch-mimedefang $(INSTALL) -m 755 watch-multiple-mimedefangs.tcl $(DESTDIR)$(prefix)/bin/watch-multiple-mimedefangs.tcl $(INSTALL) -m 755 mimedefang.pl $(DESTDIR)$(prefix)/bin/mimedefang.pl $(INSTALL) -m 755 mimedefang-release.pl $(DESTDIR)$(prefix)/bin/mimedefang-release $(INSTALL) -m 755 script/mimedefang-util $(DESTDIR)$(prefix)/bin/mimedefang-util $(INSTALL) -m 644 script/mimedefang-util.1 $(DESTDIR)$(MANDIR)/man1/mimedefang-util.1 $(INSTALL) -m 644 modules/man/Mail::MIMEDefang*.3 $(DESTDIR)$(MANDIR)/man3/ $(INSTALL) -m 644 mimedefang.8 $(DESTDIR)$(MANDIR)/man8/mimedefang.8 $(INSTALL) -m 644 mimedefang-release.8 $(DESTDIR)$(MANDIR)/man8/mimedefang-release.8 $(INSTALL) -m 644 watch-mimedefang.8 $(DESTDIR)$(MANDIR)/man8/watch-mimedefang.8 $(INSTALL) -m 644 watch-multiple-mimedefangs.8 $(DESTDIR)$(MANDIR)/man8/watch-multiple-mimedefangs.8 $(INSTALL) -m 644 mimedefang.pl.8 $(DESTDIR)$(MANDIR)/man8/mimedefang.pl.8 $(INSTALL) -m 644 mimedefang-multiplexor.8 $(DESTDIR)$(MANDIR)/man8/mimedefang-multiplexor.8 $(INSTALL) -m 644 md-mx-ctrl.8 $(DESTDIR)$(MANDIR)/man8/md-mx-ctrl.8 $(INSTALL) -m 644 mimedefang-filter.5 $(DESTDIR)$(MANDIR)/man5/mimedefang-filter.5 $(INSTALL) -m 644 mimedefang-protocol.7 $(DESTDIR)$(MANDIR)/man7/mimedefang-protocol.7 $(INSTALL) -m 644 mimedefang-notify.7 $(DESTDIR)$(MANDIR)/man7/mimedefang-notify.7 if test "$(HAVE_SPAM_ASSASSIN)" = "yes" ; then \ if test -f $(DESTDIR)${CONFDIR}/sa-mimedefang.cf ; then \ $(INSTALL) -m 644 SpamAssassin/spamassassin.cf $(DESTDIR)${CONFDIR}/sa-mimedefang.cf.example || exit 1; \ else \ $(INSTALL) -m 644 SpamAssassin/spamassassin.cf $(DESTDIR)${CONFDIR}/sa-mimedefang.cf || exit 1; \ fi \ fi distro: FORCE mkdir mimedefang-$(VERSION) tar -c -T MANIFEST -f - | (cd mimedefang-$(VERSION); tar xf -) $(PERL) -p -e 's/#VERSION#/$(VERSION)/;s/#RELEASE#/1/;s/#BETA#//g' < redhat/mimedefang-spec.in > mimedefang-$(VERSION)/redhat/mimedefang.spec tar cvf mimedefang-$(VERSION).tar mimedefang-$(VERSION) gzip -v -f -9 mimedefang-$(VERSION).tar rm -rf mimedefang-$(VERSION) gpg --detach-sign mimedefang-$(VERSION).tar.gz distro-beta: beta-check mkdir mimedefang-$(VERSION)-BETA-$(BETA) tar -c -T MANIFEST -f - | (cd mimedefang-$(VERSION)-BETA-$(BETA); tar xf -) $(PERL) -p -e 's/#VERSION#/$(VERSION)/;s/#RELEASE#/0.b$(BETA)/;s/#BETA#/-BETA-$(BETA)/g' < redhat/mimedefang-spec.in > mimedefang-$(VERSION)-BETA-$(BETA)/redhat/mimedefang.spec tar cvf mimedefang-$(VERSION)-BETA-$(BETA).tar mimedefang-$(VERSION)-BETA-$(BETA) gzip -v -f -9 mimedefang-$(VERSION)-BETA-$(BETA).tar rm -rf mimedefang-$(VERSION)-BETA-$(BETA) gpg --detach-sign mimedefang-$(VERSION)-BETA-$(BETA).tar.gz beta-check: @if test "$(BETA)" = "" ; then \ echo "Usage: make distro-beta BETA="; \ exit 1; \ fi distro-rc: rc-check mkdir mimedefang-$(VERSION)-rc$(RC) tar -c -T MANIFEST -f - | (cd mimedefang-$(VERSION)-rc$(RC); tar xf -) $(PERL) -p -e 's/#VERSION#/$(VERSION)/;s/#RELEASE#/0.rc$(RC)/;s/#RC#/-rc$(RC)/g' < redhat/mimedefang-spec.in > mimedefang-$(VERSION)-rc$(RC)/redhat/mimedefang.spec tar cvf mimedefang-$(VERSION)-rc$(RC).tar mimedefang-$(VERSION)-rc$(RC) gzip -v -f -9 mimedefang-$(VERSION)-rc$(RC).tar rm -rf mimedefang-$(VERSION)-rc$(RC) gpg --detach-sign mimedefang-$(VERSION)-rc$(RC).tar.gz rc-check: @if test "$(RC)" = "" ; then \ echo "Usage: make distro-rc RC="; \ exit 1; \ fi test: prove -It/lib --recurse t manifest: $(PERL) "-MExtUtils::Manifest=mkmanifest" -e mkmanifest FORCE: .PHONY: FORCE test manifest mimedefang-3.6/README.NONROOT000066400000000000000000000015611475763067200155170ustar00rootroot00000000000000Running MIMEDefang as non-root ------------------------------ It is required to run MIMEDefang and the multiplexor as non-root. You should create a dedicated user for MIMEDefang. In the examples, I'll call this user "defang". To run as defang: 1) Supply the "-U defang" option to mimedefang. 2) Supply the "-U defang" option to mimedefang-multiplexor. 3) Make the spool and quarantine directories owned by defang, with mode 700. 4) If you are using statistics logging in the default /var/log/mimedefang directory, make that directory owned by defang with mode 755 or 700, depending on your tastes. 5) You *MUST* create the socket(s) and pid file(s) in the spool directory. As of MIMEDefang 2.13, this is the default. You may have to edit your sendmail.cf or sendmail.mc file to use /var/spool/MIMEDefang instead of /var/run as the directory containing the milter socket. mimedefang-3.6/README.SECURITY000066400000000000000000000025521475763067200156310ustar00rootroot00000000000000SECURITY -------- Here are some tips for improving the security of your MIMEDefang installation. 1) Do NOT run mimedefang or mimedefang-multiplexor as root. (In fact, modern versions of MIMEDefang explicitly prohibit this.) If you start them as root, use the "-U" flag to get them to drop their privileges. I recommend creating a dedicated "defang" user just for MIMEDefang. See README.NONROOT 2) Use the multiplexor, and use the "-M" option to limit the memory space of workers. A limit of 50000 (50MB) should be OK on most CPU architectures. If you don't limit the worker address space, specially-crafted MIME messages can make workers consume lots of memory and grind your box to a thrashing halt. 3) Be wary of virus scanners which look into compressed archives. Compressing large blocks of zeros can result in small archives which lead to denial-of-service when uncompressed. Depending on your scanner, the "-M" option may mitigate this. 4) Do not use "action_notify_sender" unless you are absolutely, 100% sure that the sender address has not been spoofed. Oh, by the way, it is impossible to be sure the sender address has not been spoofed. 5) You may use action_bounce, but use it with care. You may wish to test the sender address first. (For example, don't bounce a message if the relay it was received from is not an MX host for the purported sender's domain.) mimedefang-3.6/README.SOPHIE000066400000000000000000000035011475763067200153440ustar00rootroot00000000000000Running Sophie with MIMEDefang ============================== Sophie (http://www.vanja.com/tools/sophie/) is a virus-scanning daemon that uses libsavi from Sophos AntiVirus (http://www.sophos.com/). To use Sophie with MD, you should use a configure line like this one when building Sophie: ./configure \ --with-socketfile=/var/spool/MIMEDefang/sophie \ --with-pidfile=/var/spool/MIMEDefang/sophie.pid \ --with-user=defang \ --with-group=defang This would tell Sophie to run as the same user that MD runs as and to put the socket and pid file in MD's spool directory. If MD doesn't run as defang on your system, replace that with whatever user and group you use. If your spool directory is not /var/spool/MIMEDefang, replace that above with whatever directory you use. Make sure you put the compiled sophie binary somewhere in your PATH. /usr/local/sbin is a commonly used location. If you're adding Sophie to an already installed MD, install Sophie first then go into the MD source directory. Run 'make distclean' before you reinstall MIMEDefang, otherwise you'll be stuck with what ./configure saw the last time you ran it. In your startup scripts, sophie should start before MIMEDefang. See the mimedefang-filter man page for more information about setting up Sophie in your filter. Specifically, look for $SophieSock, message_contains_virus_sophie(), and entity_contains_virus_sophie() MD works with Sophie 1.x and 3.x If you use 3.x, the default sophie.cfg with these changes should be enough: pidfile: /var/spool/MIMEDefang/sophie.pid socketfile: /var/spool/MIMEDefang/sophie user: defang group: defang Make sure show_virusname is set to yes or MD will not be able to set the name of detected viruses in the $VirusName variable. If error_strings is set to yes, MD will be able to report specific error messages returned by Sophie. mimedefang-3.6/README.SPAMASSASSIN000066400000000000000000000026311475763067200162650ustar00rootroot00000000000000MIMEDefang includes support for the SpamAssassin SPAM detector. To use SpamAssassin with MIMEDefang: 1) Download the latest SpamAssassin tools from http://spamassassin.apache.org/ 2) Install the SpamAssassin tools. 3) Configure, make and install MIMEDefang. 4) Use the one of the functions spam_assassin_is_spam or spam_assassin_check in filter_end(). See the sample filter in the examples/ directory. The sample filter adds a header if SpamAssassin thinks the message is SPAM. You can then use MIMEDefang to control message disposition on a global basis, or procmail to control it on a per-user basis. SpamAssassin is pretty slow, so it's better to run SpamAssassin from MIMEDefang, and let per-user tools like procmail control message disposition based on the presence of the X-Spam-Score header. IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT ------------------------------------------------------------------------------- READ THIS SECTION NOW BEFORE POSTING QUESTIONS ON THE MIMEDEFANG MAILING LIST: MIMEDefang does *NOT* allow SpamAssassin to modify e-mail messages. If you set up SpamAssassin to add headers or tag subjects, it will not work. Instead, you must have MIMEDefang do the modifications. For example: if (spam_assassin_is_spam()) { # Change Subject: header action_change_header("Subject", "** SPAM ** $Subject"); } See the sample filter for more details. mimedefang-3.6/README.md000066400000000000000000000235421475763067200147640ustar00rootroot00000000000000# MIMEDefang [![CI for MIMEDefang](https://github.com/The-McGrail-Foundation/MIMEDefang/actions/workflows/main.yml/badge.svg)](https://github.com/The-McGrail-Foundation/MIMEDefang/actions/workflows/main.yml) [![GitHub license](https://img.shields.io/github/license/The-McGrail-Foundation/MIMEDefang)](https://github.com/The-McGrail-Foundation/MIMEDefang/blob/master/LICENSE) [![GitHub release](https://img.shields.io/github/release/The-McGrail-Foundation/MIMEDefang.svg)](https://GitHub.com/The-McGrail-Foundation/MIMEDefang/releases/) 1. INTRODUCTION --------------- MIMEDefang is an e-mail filter program which works with Sendmail 8.12 and later or Postfix. MIMEDefang filters all e-mail messages sent via SMTP. MIMEDefang splits multi-part MIME messages into their components and potentially deletes or modifies the various parts. It then reassembles the parts back into an e-mail message and sends it on its way. MIMEDefang is written (mostly) in Perl, and the filter actions are expressed in Perl. This makes MIMEDefang highly flexible and configurable. As a simple example, you can delete all *.exe and *.com files, convert all Word documents to HTML, and allow other attachments through. MIMEDefang uses the "milter" feature of Sendmail to "listen in" to SMTP connections. It runs a scan once for each message, not once for each recipient (as simpler procmail-based systems do.) Therefore, it is more CPU-friendly than procmail-based systems. In addition, because MIMEDefang can participate in the SMTP connection, you can bounce messages (something impossible to do with procmail-based systems.) 2. WARNINGS ----------- There are some caveats you should be aware of before using MIMEDefang. MIMEDefang potentially alters e-mail messages. This breaks a "gentleman's agreement" that mail transfer agents do not modify message bodies. This could cause problems, for example, with encrypted or signed messages. Deleting attachments could cause a loss of information. Recipients must be aware of this possibility, and must be willing to explain to senders exactly why they cannot mail certain types of files. You must have the willingness of your e-mail users to commit to security, or they will complain loudly about MIMEDefang. 3. PREREQUISITES ---------------- MIMEDefang has the following software requirements: 1) A UNIX-like operating system (MIMEDefang is developed and tested on Linux) 2) Perl 5.8.0 or higher 3) Required Perl modules: Digest::SHA FindBin MailTools 1.1401 or higher MIME::tools 5.413 or higher MIME::Base64 3.03 or higher MIME::WordDecoder These modules are available from http://www.cpan.org 4) Optional Perl modules: Crypt::OpenSSL::Random - Needed to generate a truly random ipheader file HTML::Parser (CPAN) - Needed for append_html_boilerplate function IO::Socket::SSL - Needed for md_check_against_smtp_server SSL checks JSON and LWP::UserAgent - Needed for rspamd support Mail::SpamAssassin (https://www.spamassassin.org/) - spam detector Test::Class, Test::Most and tzdata files - Needed to run regression tests 4) Sendmail 8.12.x, 8.13.x or Postfix. Get the latest version. 4. INSTALLATION --------------- There's an excellent MIMEDefang-HOWTO contributed by Mickey Hill at http://www.mickeyhill.com/mimedefang-howto/. It explains everything in this README in much greater detail. Anyway, on with it: 1) Sendmail **You must be using Sendmail 8.12.x or higher** Obtain the latest Sendmail 8.12.x or higher source release from http://www.sendmail.org. Unpack it. If you are building 8.12.x, add the following lines to devtools/Site/site.config.m4: dnl Milter APPENDDEF(`conf_sendmail_ENVDEF', `-DMILTER') This enables the mail filter feature. (For 8.13.x and higher versions, Milter is enabled by default.) Go ahead and build Sendmail following the instructions in the Sendmail documentation. Install and configure Sendmail. You *MUST* run a client-queue runner, because MIMEDefang now uses deferred mode to deliver internally-generated messages. We recommend running this command as part of the Sendmail startup: sendmail -Ac -q5m Compile and Install Sendmail: ----------------------------- Next, you need to make the Sendmail headers and libraries visible for compiling and linking MIMEDefang. The most reliable way to do this is to run these commands from the main Sendmail directory: mkdir -p /usr/local/include/sendmail cp -R include/* /usr/local/include/sendmail cp -R sendmail/*.h /usr/local/include/sendmail mkdir -p /usr/local/lib cp obj.Linux.2.2.14-5.0.i686/*/*.a /usr/local/lib NOTE: On the last "cp" command, replace "obj.Linux.2.2.14-5.0.i686" with the appropriate "obj.*" directory created by the Sendmail build script. 2) Obtain and install the necessary Perl modules. These generally build and install as follows: perl Makefile.PL make install If you are using any of the optional Perl modules, install them before starting to build MIMEDefang. 3) Optionally, obtain and install the "wv" library. Install the wvHtml program in your favourite bin directory (/usr/bin or /usr/local/bin). 4) Configure, build and install the MIMEDefang software: ./configure make make install NOTE: Unlike most autoconf scripts, the default --sysconfdir for this version of ./configure is "/etc". You can change it to /usr/local/etc as follows: ./configure --sysconfdir=/usr/local/etc Also, the actual configuration files go in the subdirectory "mail" under --sysconfdir. You can put them elsewhere (eg, /usr/local/etc/mimedefang) like this: ./configure --sysconfdir=/usr/local/etc --with-confsubdir=mimedefang If you want them right in /usr/local/etc, you'd say: ./configure --sysconfdir=/usr/local/etc --with-confsubdir= By default, MIMEDefang processes incoming messages in the directory /var/spool/MIMEDefang. You can change this by typing: ./configure --with-spooldir=DIRNAME By default, MIMEDefang quarantines mail in the directory /var/spool/MD-Quarantine. You can change this by typing: ./configure --with-quarantinedir=DIR2 You should create the spool and quarantine directories with mode 700, owned by the user you run MIMEDefang as. Summary of useful ./configure options: --with-sendmail=PATH specify location of Sendmail binary --with-user=LOGIN use LOGIN as the MIMEDefang user --with-milterinc=PATH specify alternative location of milter includes --with-milterlib=PATH specify alternative location of milter libraries --with-ipheader install /etc/mail/mimedefang-ip-key --with-confsubdir=DIR specify configuration subdirectory (mail) --with-spooldir=DIR specify location of spool directory (/var/spool/MIMEDefang) --with-quarantinedir=DIR specify location of quarantine directory (/var/spool/MD-Quarantine) --enable-poll Use poll(2) instead of select(2) in multiplexor --disable-check-perl-modules Disable compile-time checks for Perl modules --disable-embedded-perl Disable embedded Perl interpreter --enable-debugging Add debugging messages to syslog --disable-anti-virus Do not search for ANY anti-virus programs --disable-antivir Do not include support for H+BEDV antivir --disable-vexira Do not include support for Central Command Vexira --disable-uvscan Do not include support for NAI uvscan --disable-sweep Do not include support for Sophos sweep --disable-trend Do not include support for Trend Filescanner/Interscan --disable-AvpLinux Do not include support for AVP AvpLinux --disable-clamav Do not include support for clamav --disable-csav Do not include support for Command Anti-Virus --disable-fsav Do not include support for F-Secure Anti-Virus --disable-fprot Do not include support for F-prot Anti-Virus --disable-fpscan Do not include support for F-prot Anti-Virus v6 --disable-sophie Do not include support for Sophie --disable-nvcc Do not include support for Nvcc 5) Add the following line to your Sendmail "m4" configuration file. (You DO use the m4 configuration method, right?) INPUT_MAIL_FILTER(`mimedefang', `S=unix:/var/spool/MIMEDefang/mimedefang.sock, F=T, T=S:360s;R:360s;E:15m') (If you keep your spool directory elsewhere, use its location instead of /var/spool/MIMEDefang/mimedefang.sock) The "T=..." equate increases the default timeouts for milter, which are way too small. 6) Ensure that mimedefang starts when Sendmail does. In whatever shell script starts sendmail at boot time, add the lines: rm -f /var/spool/MIMEDefang/mimedefang.sock /usr/local/bin/mimedefang -p /var/spool/MIMEDefang/mimedefang.sock & before the line which actually starts Sendmail. When you shut down Sendmail, remember to kill the mimedefang processes. A sample /etc/rc.d/init.d script for Red Hat Linux is in the redhat directory. A sample generic init script which should work on most UNIXes is in the examples directory. DOCKER ------------- There are docker images available on Docker Hub at https://hub.docker.com/u/mimedefang with MIMEDefang configured with postfix(8) or sendmail(8) MTA. CONFIGURATION ------------- To configure your filter, you have to edit the file `/etc/mail/mimedefang-filter'. This is a Perl source file, so you have to know Perl. Go ahead and read the man pages mimedefang(8), mimedefang.pl(8) and mimedefang-filter(5). There are some sample filters in the examples directory. THE MULTIPLEXOR --------------- On a busy mail server, it is too expensive to start a new Perl process for each incoming e-mail. MIMEDefang includes a multiplexor which manages a pool of long-lived Perl processes and reuses them for successive e-mails. Read the mimedefang-multiplexor(8) man page for details. A sample start/stop script is shown in examples/init-script; this script is generic and should work on most flavours of UNIX. mimedefang-3.6/SpamAssassin/000077500000000000000000000000001475763067200161045ustar00rootroot00000000000000mimedefang-3.6/SpamAssassin/spamassassin.cf000066400000000000000000000052521475763067200211270ustar00rootroot00000000000000# SpamAssassin user preferences file. # # Format: # # required_hits n # (how many hits are required to tag a mail as spam.) # # score SYMBOLIC_TEST_NAME n # (if this is omitted, 1 is used as a default score. # Set the score to 0 to ignore the test.) # # # starts a comment, whitespace is not significant. # # NOTE! In conjunction with MIMEDefang, SpamAssassin can *NOT* make any # changes to the message header or body. Any SpamAssassin settings that # relate to changing the message will have *NO EFFECT* when used from # MIMEDefang. Instead, use the various MIMEDefang Perl functions if you # need to alter the message. ########################################################################### ########################################################################### # First of all, the generally useful stuff; thresholds and the whitelist # of addresses which, for some reason or another, often trigger false # positives. required_hits 5 # Whitelist and blacklist addresses are *not* patterns; they're just normal # strings. one exception is that "*@isp.com" is allowed. They should be in # lower-case. You can either add multiple addrs on one line, # whitespace-separated, or you can use multiple lines. # # whitelist_from friend@example.com # Add your blacklist entries in the same format... # # blacklist_from enemy@example.net # Mail using languages used in these country codes will not be marked # as being possibly spam in a foreign language. # ok_locales en # By default, the subject lines of suspected spam will be tagged. # This can be disabled here. # rewrite_subject 0 # By default, spamassassin will include its report in the body # of suspected spam. Enabling this causes the report to go in the # headers instead. Using 'use_terse_report' for this is recommended. # # report_header 1 # By default, SpamAssassin uses a fairly long report format. # Enabling this uses a shorter format which includes all the # information in the normal one, but without the superfluous # explanations. # # use_terse_report 0 # By default, spamassassin will change the Content-type: header of # suspected spam to "text/plain". This is a safety feature. If you # prefer to leave the Content-type header alone, set this to 0. # # defang_mime 0 # By default, SpamAssassin will run RBL checks. If your ISP already # does this, set this to 1. skip_rbl_checks 1 ########################################################################### # Add your own customised scores for some tests below. The default scores are # read from the installed "spamassassin.cf" file, but you can override them # here. To see the list of tests and their default scores, go to # http://spamassassin.taint.org/tests.html . mimedefang-3.6/config.h.in000066400000000000000000000060241475763067200155240ustar00rootroot00000000000000/* config.h.in. Generated from configure.ac by autoheader. */ /* Define to 1 if you have the header file. */ #undef HAVE_GETOPT_H /* Define to 1 if you have the `getpwnam_r' function. */ #undef HAVE_GETPWNAM_R /* Define to 1 if you have the `inet_ntop' function. */ #undef HAVE_INET_NTOP /* Define to 1 if you have the `initgroups' function. */ #undef HAVE_INITGROUPS /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* Define to 1 if you have the `nsl' library (-lnsl). */ #undef HAVE_LIBNSL /* Define to 1 if you have the `pthread' library (-lpthread). */ #undef HAVE_LIBPTHREAD /* Define to 1 if you have the `resolv' library (-lresolv). */ #undef HAVE_LIBRESOLV /* Define to 1 if you have the `socket' library (-lsocket). */ #undef HAVE_LIBSOCKET /* Define to 1 if the system has the type `long long int'. */ #undef HAVE_LONG_LONG_INT /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H /* Define to 1 if you have the `pathconf' function. */ #undef HAVE_PATHCONF /* Define to 1 if you have the header file. */ #undef HAVE_POLL_H /* Define to 1 if you have the `readdir_r' function. */ #undef HAVE_READDIR_R /* Define to 1 if you have the `setrlimit' function. */ #undef HAVE_SETRLIMIT /* "Whether we have the atomic_t variable type " */ #undef HAVE_SIG_ATOMIC_T /* Define to 1 if you have the `snprintf' function. */ #undef HAVE_SNPRINTF /* "Whether we have the variable type socklen_t" */ #undef HAVE_SOCKLEN_T /* "Whether we have CLOCK_MONOTONIC defined" */ #undef HAVE_CLOCK_MONOTONIC /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H /* Define to 1 if you have the header file. */ #undef HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H /* "whether uint32_t is defined" */ #undef HAVE_UINT32_T /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Define to 1 if the system has the type `unsigned long long int'. */ #undef HAVE_UNSIGNED_LONG_LONG_INT /* Define to 1 if you have the `vsnprintf' function. */ #undef HAVE_VSNPRINTF /* Define to 1 if you have the `wait3' system call. Deprecated, you should no longer depend upon `wait3'. */ #undef HAVE_WAIT3 /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the home page for this package. */ #undef PACKAGE_URL /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS mimedefang-3.6/configure000077500000000000000000007234231475763067200154210ustar00rootroot00000000000000#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.71. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, # Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else $as_nop case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="as_nop=: if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else \$as_nop case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : else \$as_nop exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah)) test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes else $as_nop as_have_required=no fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : else $as_nop as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$as_shell as_have_required=yes if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null then : break 2 fi fi done;; esac as_found=false done IFS=$as_save_IFS if $as_found then : else $as_nop if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes fi fi if test "x$CONFIG_SHELL" != x then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno then : printf "%s\n" "$0: This script requires a shell more modern than all" printf "%s\n" "$0: the shells that I found on your system." if test ${ZSH_VERSION+y} ; then printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." else printf "%s\n" "$0: Please tell bug-autoconf@gnu.org about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 fi fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_nop # --------- # Do nothing but, unlike ":", preserve the value of $?. as_fn_nop () { return $? } as_nop=as_fn_nop # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else $as_nop as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else $as_nop as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith # as_fn_nop # --------- # Do nothing but, unlike ":", preserve the value of $?. as_fn_nop () { return $? } as_nop=as_fn_nop # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='' PACKAGE_TARNAME='' PACKAGE_VERSION='' PACKAGE_STRING='' PACKAGE_BUGREPORT='' PACKAGE_URL='' ac_unique_file="mimedefang.c" # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_STDIO_H # include #endif #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_header_c_list= ac_subst_vars='LTLIBOBJS LIBOBJS PERLINSTALLVENDORCONF PERLINSTALLSITECONF PERLINSTALLCONF PERLINSTALLVENDORDATA PERLINSTALLSITEDATA PERLINSTALLDATA PERLINSTALLSITELIB PERLINSTALLSITEARCH PERLINSTALLSCRIPT PERLINSTALLMAN3DIR PERLINSTALLMAN1DIR PERLINSTALLBIN PERLINSTALLPRIVLIB PERLINSTALLARCHLIB PERLVENDORLIB PERLVENDORPREFIX PERLSITEPREFIX PERLPREFIX CONFDIR_EVAL LIBS_WITHOUT_PTHREAD LIBMILTERSO LIBSM LIBMILTER MINCLUDE VERSION PTHREAD_FLAG ENABLE_DEBUGGING SENDMAILPROG RSPAMC NOD32 TROPHIE CLAMD NVCC SOPHIE FPSCAN FPROT FSAV CSAV AVP5 AVP_KAVDAEMON AVP CLAMDSCAN CLAMSCAN KAVSCANNER TREND SAVSCAN SOPHOS BDC NAI VEXIRA HBEDV QDIR SPOOLDIR USEPOLL EMBPERLDEFS EMBPERLOBJS EMBPERLLIBS EMBPERLLDFLAGS EMBPERLCFLAGS HAVE_SPAM_ASSASSIN CONFSUBDIR IP_HEADER DEFANGUSER NM PERL INSTALL_DATA INSTALL_SCRIPT INSTALL_PROGRAM AR OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir runstatedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking enable_embedded_perl enable_poll with_sendmail with_user with_ipheader with_milterinc with_milterlib with_confsubdir with_spooldir with_quarantinedir enable_pthread_flag enable_check_perl_modules enable_debugging enable_anti_virus enable_antivir enable_vexira enable_uvscan enable_bdc enable_sweep enable_savscan enable_trend enable_AvpLinux enable_kavscanner enable_aveclient enable_clamav enable_fsav enable_csav enable_fprot enable_fpscan enable_sophie enable_nvcc enable_clamd enable_trophie enable_nod32 enable_rspamc ' ac_precious_vars='build_alias host_alias target_alias CC CFLAGS LDFLAGS LIBS CPPFLAGS' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -runstatedir | --runstatedir | --runstatedi | --runstated \ | --runstate | --runstat | --runsta | --runst | --runs \ | --run | --ru | --r) ac_prev=runstatedir ;; -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ | --run=* | --ru=* | --r=*) runstatedir=$ac_optarg ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: \`$ac_option' Try \`$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures this package to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF _ACEOF fi if test -n "$ac_init_help"; then cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --disable-embedded-perl Disable embedded Perl interpreter --enable-poll Use poll(2) instead of select(2) in multiplexor --enable-pthread-flag Supply the -pthread flag to the C compiler --disable-check-perl-modules Disable compile-time checks for Perl modules --enable-debugging Add debugging messages to syslog --disable-anti-virus Do not look for anti-virus scanners --disable-antivir Do not include support for H+BEDV antivir --disable-vexira Do not include support for Central Command Vexira --disable-uvscan Do not include support for NAI uvscan --disable-bdc Do not include support for Bitdefender bdc --disable-sweep Do not include support for Sophos sweep --disable-savscan Do not include support for Sophos savscan --disable-trend Do not include support for Trend Filescanner/Interscan --disable-AvpLinux Do not include support for AVP AvpLinux --disable-kavscanner Do not include support for Kaspersky kavscanner --disable-aveclient Do not include support for AVP5 aveclient --disable-clamav Do not include support for clamav --disable-fsav Do not include support for F-Secure Anti-Virus --disable-csav Do not include support for Command Software CSAV --disable-fprot Do not include support for F-prot Anti-Virus --disable-fpscan Do not include support for F-prot Anti-Virus v6 --disable-sophie Do not include support for Sophie --disable-nvcc Do not include support for Nvcc --disable-clamd Do not include support for clamd --disable-trophie Do not include support for Trophie --disable-nod32 Do not include support for Eset NOD32 --disable-rspamc Do not include support for Rspamd Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-sendmail=PATH specify location of Sendmail binary --with-user=LOGIN use LOGIN as the MIMEDefang user --with-ipheader install /etc/mail/mimedefang-ip-key --with-milterinc=PATH specify alternative location of milter includes --with-milterlib=PATH specify alternative location of milter libraries --with-confsubdir=DIR specify configuration subdirectory (mail) --with-spooldir=DIR specify location of spool directory (/var/spool/MIMEDefang) --with-quarantinedir=DIR specify location of quarantine directory (/var/spool/MD-Quarantine) Some influential environment variables: CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to the package provider. _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for configure.gnu first; this name is used for a wrapper for # Metaconfig's "Configure" on case-insensitive file systems. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF configure generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 else $as_nop printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_c_try_link LINENO # ----------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext } then : ac_retval=0 else $as_nop printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link # ac_fn_c_try_run LINENO # ---------------------- # Try to run conftest.$ac_ext, and return whether this succeeded. Assumes that # executables *can* be run. ac_fn_c_try_run () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; } then : ac_retval=0 else $as_nop printf "%s\n" "$as_me: program exited with status $ac_status" >&5 printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=$ac_status fi rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_run # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" else $as_nop eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile # ac_fn_c_check_func LINENO FUNC VAR # ---------------------------------- # Tests whether FUNC exists, setting the cache variable VAR accordingly ac_fn_c_check_func () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $2 (); below. */ #include #undef $2 /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $2 (); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_$2 || defined __stub___$2 choke me #endif int main (void) { return $2 (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : eval "$3=yes" else $as_nop eval "$3=no" fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func ac_configure_args_raw= for ac_arg do case $ac_arg in *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append ac_configure_args_raw " '$ac_arg'" done case $ac_configure_args_raw in *$as_nl*) ac_safe_unquote= ;; *) ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. ac_unsafe_a="$ac_unsafe_z#~" ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; esac cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by $as_me, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac printf "%s\n" "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Sanitize IFS. IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. { echo printf "%s\n" "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo printf "%s\n" "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then printf "%s\n" "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then printf "%s\n" "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && printf "%s\n" "$as_me: caught signal $ac_signal" printf "%s\n" "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h printf "%s\n" "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. if test -n "$CONFIG_SITE"; then ac_site_files="$CONFIG_SITE" elif test "x$prefix" != xNONE; then ac_site_files="$prefix/share/config.site $prefix/etc/config.site" else ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" fi for ac_site_file in $ac_site_files do case $ac_site_file in #( */*) : ;; #( *) : ac_site_file=./$ac_site_file ;; esac if test -f "$ac_site_file" && test -r "$ac_site_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See \`config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 printf "%s\n" "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 printf "%s\n" "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Test code for whether the C compiler supports C89 (global declarations) ac_c_conftest_c89_globals=' /* Does the compiler advertise C89 conformance? Do not test the value of __STDC__, because some compilers set it to 0 while being otherwise adequately conformant. */ #if !defined __STDC__ # error "Compiler does not advertise C89 conformance" #endif #include #include struct stat; /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int); static char *e (p, i) char **p; int i; { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated as an "x". The following induces an error, until -std is added to get proper ANSI mode. Curiously \x00 != x always comes out true, for an array size at least. It is necessary to write \x00 == 0 to get something that is true only with -std. */ int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) '\''x'\'' int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), int, int);' # Test code for whether the C compiler supports C89 (body of main). ac_c_conftest_c89_main=' ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); ' # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals=' // Does the compiler advertise C99 conformance? #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif #include extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare // FILE and stderr. #define debug(...) dprintf (2, __VA_ARGS__) #define showlist(...) puts (#__VA_ARGS__) #define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) static void test_varargs_macros (void) { int x = 1234; int y = 5678; debug ("Flag"); debug ("X = %d\n", x); showlist (The first, second, and third items.); report (x>y, "x is %d but y is %d", x, y); } // Check long long types. #define BIG64 18446744073709551615ull #define BIG32 4294967295ul #define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) #if !BIG_OK #error "your preprocessor is broken" #endif #if BIG_OK #else #error "your preprocessor is broken" #endif static long long int bignum = -9223372036854775807LL; static unsigned long long int ubignum = BIG64; struct incomplete_array { int datasize; double data[]; }; struct named_init { int number; const wchar_t *name; double average; }; typedef const char *ccp; static inline int test_restrict (ccp restrict text) { // See if C++-style comments work. // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) continue; return 0; } // Check varargs and va_copy. static bool test_varargs (const char *format, ...) { va_list args; va_start (args, format); va_list args_copy; va_copy (args_copy, args); const char *str = ""; int number = 0; float fnumber = 0; while (*format) { switch (*format++) { case '\''s'\'': // string str = va_arg (args_copy, const char *); break; case '\''d'\'': // int number = va_arg (args_copy, int); break; case '\''f'\'': // float fnumber = va_arg (args_copy, double); break; default: break; } } va_end (args_copy); va_end (args); return *str && number && fnumber; } ' # Test code for whether the C compiler supports C99 (body of main). ac_c_conftest_c99_main=' // Check bool. _Bool success = false; success |= (argc != 0); // Check restrict. if (test_restrict ("String literal") == 0) success = true; char *restrict newvar = "Another string"; // Check varargs. success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); test_varargs_macros (); // Check flexible array members. struct incomplete_array *ia = malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10)); ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; // Check named initializers. struct named_init ni = { .number = 34, .name = L"Test wide string", .average = 543.34343, }; ni.number = 58; int dynamic_array[ni.number]; dynamic_array[0] = argv[0][0]; dynamic_array[ni.number - 1] = 543; // work around unused variable warnings ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' || dynamic_array[ni.number - 1] != 543); ' # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals=' // Does the compiler advertise C11 conformance? #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif // Check _Alignas. char _Alignas (double) aligned_as_double; char _Alignas (0) no_special_alignment; extern char aligned_as_int; char _Alignas (0) _Alignas (int) aligned_as_int; // Check _Alignof. enum { int_alignment = _Alignof (int), int_array_alignment = _Alignof (int[100]), char_alignment = _Alignof (char) }; _Static_assert (0 < -_Alignof (int), "_Alignof is signed"); // Check _Noreturn. int _Noreturn does_not_return (void) { for (;;) continue; } // Check _Static_assert. struct test_static_assert { int x; _Static_assert (sizeof (int) <= sizeof (long int), "_Static_assert does not work in struct"); long int y; }; // Check UTF-8 literals. #define u8 syntax error! char const utf8_literal[] = u8"happens to be ASCII" "another string"; // Check duplicate typedefs. typedef long *long_ptr; typedef long int *long_ptr; typedef long_ptr long_ptr; // Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. struct anonymous { union { struct { int i; int j; }; struct { int k; long int l; } w; }; int m; } v1; ' # Test code for whether the C compiler supports C11 (body of main). ac_c_conftest_c11_main=' _Static_assert ((offsetof (struct anonymous, i) == offsetof (struct anonymous, w.k)), "Anonymous union alignment botch"); v1.i = 2; v1.w.k = 5; ok |= v1.i != 5; ' # Test code for whether the C compiler supports C11 (complete). ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} ${ac_c_conftest_c11_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} ${ac_c_conftest_c11_main} return ok; } " # Test code for whether the C compiler supports C99 (complete). ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} return ok; } " # Test code for whether the C compiler supports C89 (complete). ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} return ok; } " as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H" as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H" as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H" as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H" as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H" as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H" as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H" # Auxiliary files required by this configure script. ac_aux_files="install-sh" # Locations in which to look for auxiliary files. ac_aux_dir_candidates="${srcdir}${PATH_SEPARATOR}${srcdir}/..${PATH_SEPARATOR}${srcdir}/../.." # Search for a directory containing all of the required auxiliary files, # $ac_aux_files, from the $PATH-style list $ac_aux_dir_candidates. # If we don't find one directory that contains all the files we need, # we report the set of missing files from the *first* directory in # $ac_aux_dir_candidates and give up. ac_missing_aux_files="" ac_first_candidate=: printf "%s\n" "$as_me:${as_lineno-$LINENO}: looking for aux files: $ac_aux_files" >&5 as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in $ac_aux_dir_candidates do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: printf "%s\n" "$as_me:${as_lineno-$LINENO}: trying $as_dir" >&5 ac_aux_dir_found=yes ac_install_sh= for ac_aux in $ac_aux_files do # As a special case, if "install-sh" is required, that requirement # can be satisfied by any of "install-sh", "install.sh", or "shtool", # and $ac_install_sh is set appropriately for whichever one is found. if test x"$ac_aux" = x"install-sh" then if test -f "${as_dir}install-sh"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install-sh found" >&5 ac_install_sh="${as_dir}install-sh -c" elif test -f "${as_dir}install.sh"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install.sh found" >&5 ac_install_sh="${as_dir}install.sh -c" elif test -f "${as_dir}shtool"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}shtool found" >&5 ac_install_sh="${as_dir}shtool install -c" else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} install-sh" else break fi fi else if test -f "${as_dir}${ac_aux}"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}${ac_aux} found" >&5 else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} ${ac_aux}" else break fi fi fi done if test "$ac_aux_dir_found" = yes; then ac_aux_dir="$as_dir" break fi ac_first_candidate=false as_found=false done IFS=$as_save_IFS if $as_found then : else $as_nop as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. if test -f "${ac_aux_dir}config.guess"; then ac_config_guess="$SHELL ${ac_aux_dir}config.guess" fi if test -f "${ac_aux_dir}config.sub"; then ac_config_sub="$SHELL ${ac_aux_dir}config.sub" fi if test -f "$ac_aux_dir/configure"; then ac_configure="$SHELL ${ac_aux_dir}configure" fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 printf "%s\n" "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 printf "%s\n" "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 printf "%s\n" "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 printf "%s\n" "$as_me: former value: \`$ac_old_val'" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run \`${MAKE-make} distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu echo $* | fgrep -e '--sysconfdir' > /dev/null 2>&1 || sysconfdir='/etc' ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. set dummy ${ac_tool_prefix}gcc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "gcc", so it can be a program name with args. set dummy gcc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. set dummy ${ac_tool_prefix}cc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi fi if test -z "$CC"; then # Extract the first word of "cc", so it can be a program name with args. set dummy cc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else ac_prog_rejected=no as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue fi ac_cv_prog_CC="cc" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS if test $ac_prog_rejected = yes; then # We found a bogon in the path, so make sure we never use it. set dummy $ac_cv_prog_CC shift if test $# != 0; then # We chose a different compiler from the bogus one. # However, it has the same basename, so the bogon will be chosen # first if we set CC to just the basename; use the full file name. shift ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" fi fi fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then for ac_prog in cl.exe do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in cl.exe do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi fi if test -z "$CC"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. set dummy ${ac_tool_prefix}clang; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}clang" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_CC"; then ac_ct_CC=$CC # Extract the first word of "clang", so it can be a program name with args. set dummy clang; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="clang" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi else CC="$ac_cv_prog_CC" fi fi test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See \`config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion -version; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 printf %s "checking whether the C compiler works... " >&6; } ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. # So ignore a value of `no', otherwise this would lead to `EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an `-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else $as_nop ac_file='' fi if test -z "$ac_file" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See \`config.log' for more details" "$LINENO" 5; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 printf "%s\n" "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 printf %s "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # If both `conftest.exe' and `conftest' are `present' (well, observable) # catch `conftest.exe'. For instance with Cygwin, `ls conftest' will # work properly (i.e., refer to `conftest.exe'), while it won't with # `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else $as_nop { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest conftest$ac_cv_exeext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 printf "%s\n" "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { FILE *f = fopen ("conftest.out", "w"); return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 printf %s "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error 77 "cannot run C compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details" "$LINENO" 5; } fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 printf "%s\n" "$cross_compiling" >&6; } rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else $as_nop printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See \`config.log' for more details" "$LINENO" 5; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 printf "%s\n" "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes else $as_nop ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_c_compiler_gnu if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+y} ac_save_CFLAGS=$CFLAGS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 else $as_nop ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes else $as_nop CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else $as_nop ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf "%s\n" "$ac_cv_prog_cc_g" >&6; } if test $ac_test_CFLAGS; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi ac_prog_cc_stdc=no if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c11_program _ACEOF for ac_arg in '' -std=gnu11 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else $as_nop if test "x$ac_cv_prog_cc_c11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } CC="$CC $ac_cv_prog_cc_c11" fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 ac_prog_cc_stdc=c11 fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c99_program _ACEOF for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c99=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else $as_nop if test "x$ac_cv_prog_cc_c99" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } CC="$CC $ac_cv_prog_cc_c99" fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 ac_prog_cc_stdc=c99 fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c89_program _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else $as_nop if test "x$ac_cv_prog_cc_c89" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } CC="$CC $ac_cv_prog_cc_c89" fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 ac_prog_cc_stdc=c89 fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu for ac_prog in ar do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_AR+y} then : printf %s "(cached) " >&6 else $as_nop if test -n "$AR"; then ac_cv_prog_AR="$AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_AR="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi AR=$ac_cv_prog_AR if test -n "$AR"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 printf "%s\n" "$AR" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$AR" && break done test -n "$AR" || AR="no" ac_config_headers="$ac_config_headers config.h" PATH=$PATH:/usr/local/bin # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 printf %s "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then if test ${ac_cv_path_install+y} then : printf %s "(cached) " >&6 else $as_nop as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac # Account for fact that we put trailing slashes in our PATH walk. case $as_dir in #(( ./ | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext"; then if test $ac_prog = install && grep dspmsg "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else rm -rf conftest.one conftest.two conftest.dir echo one > conftest.one echo two > conftest.two mkdir conftest.dir if "$as_dir$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir/" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then ac_cv_path_install="$as_dir$ac_prog$ac_exec_ext -c" break 3 fi fi fi done done ;; esac done IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir fi if test ${ac_cv_path_install+y}; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 printf "%s\n" "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for unsigned long long int" >&5 printf %s "checking for unsigned long long int... " >&6; } if test ${ac_cv_type_unsigned_long_long_int+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_type_unsigned_long_long_int=yes case $ac_prog_cc_stdc in no | c89) ;; *) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* For now, do not test the preprocessor; as of 2007 there are too many implementations with broken preprocessors. Perhaps this can be revisited in 2012. In the meantime, code should not expect #if to work with literals wider than 32 bits. */ /* Test literals. */ long long int ll = 9223372036854775807ll; long long int nll = -9223372036854775807LL; unsigned long long int ull = 18446744073709551615ULL; /* Test constant expressions. */ typedef int a[((-9223372036854775807LL < 0 && 0 < 9223372036854775807ll) ? 1 : -1)]; typedef int b[(18446744073709551615ULL <= (unsigned long long int) -1 ? 1 : -1)]; int i = 63; int main (void) { /* Test availability of runtime routines for shift and division. */ long long int llmax = 9223372036854775807ll; unsigned long long int ullmax = 18446744073709551615ull; return ((ll << 63) | (ll >> 63) | (ll < i) | (ll > i) | (llmax / ll) | (llmax % ll) | (ull << 63) | (ull >> 63) | (ull << i) | (ull >> i) | (ullmax / ull) | (ullmax % ull)); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : else $as_nop ac_cv_type_unsigned_long_long_int=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_unsigned_long_long_int" >&5 printf "%s\n" "$ac_cv_type_unsigned_long_long_int" >&6; } if test $ac_cv_type_unsigned_long_long_int = yes; then printf "%s\n" "#define HAVE_UNSIGNED_LONG_LONG_INT 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for long long int" >&5 printf %s "checking for long long int... " >&6; } if test ${ac_cv_type_long_long_int+y} then : printf %s "(cached) " >&6 else $as_nop ac_cv_type_long_long_int=yes case $ac_prog_cc_stdc in no | c89) ;; *) ac_cv_type_long_long_int=$ac_cv_type_unsigned_long_long_int if test $ac_cv_type_long_long_int = yes; then if test "$cross_compiling" = yes then : : else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #ifndef LLONG_MAX # define HALF \ (1LL << (sizeof (long long int) * CHAR_BIT - 2)) # define LLONG_MAX (HALF - 1 + HALF) #endif int main (void) { long long int n = 1; int i; for (i = 0; ; i++) { long long int m = n << i; if (m >> i != n) return 1; if (LLONG_MAX / 2 < m) break; } return 0; ; return 0; } _ACEOF if ac_fn_c_try_run "$LINENO" then : else $as_nop ac_cv_type_long_long_int=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_long_long_int" >&5 printf "%s\n" "$ac_cv_type_long_long_int" >&6; } if test $ac_cv_type_long_long_int = yes; then printf "%s\n" "#define HAVE_LONG_LONG_INT 1" >>confdefs.h fi # Check whether --enable-embedded-perl was given. if test ${enable_embedded_perl+y} then : enableval=$enable_embedded_perl; ac_cv_embedded_perl=$enableval else $as_nop ac_cv_embedded_perl=yes fi # Check whether --enable-poll was given. if test ${enable_poll+y} then : enableval=$enable_poll; ac_cv_use_poll=$enableval else $as_nop ac_cv_use_poll=no fi # Extract the first word of "perl", so it can be a program name with args. set dummy perl; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_PERL+y} then : printf %s "(cached) " >&6 else $as_nop case $PERL in [\\/]* | ?:[\\/]*) ac_cv_path_PERL="$PERL" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_PERL="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi PERL=$ac_cv_path_PERL if test -n "$PERL"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PERL" >&5 printf "%s\n" "$PERL" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether socklen_t is defined" >&5 printf %s "checking whether socklen_t is defined... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { socklen_t x; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_have_socklen_t=yes else $as_nop ac_have_socklen_t=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_have_socklen_t" >&5 printf "%s\n" "$ac_have_socklen_t" >&6; } if test "$ac_have_socklen_t" = "yes" ; then printf "%s\n" "#define HAVE_SOCKLEN_T /**/" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether clock_gettime can use CLOCK_MONOTONIC" >&5 printf %s "checking whether clock_gettime can use CLOCK_MONOTONIC... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { struct timespec s; clock_gettime(CLOCK_MONOTONIC, &s); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_have_clock_monotonic=yes else $as_nop ac_have_clock_monotonic=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_have_clock_monotonic" >&5 printf "%s\n" "$ac_have_clock_monotonic" >&6; } if test "$ac_have_clock_monotonic" = "yes" ; then printf "%s\n" "#define HAVE_CLOCK_MONOTONIC /**/" >>confdefs.h else OLDLIBS="$LIBS" LIBS="$LIBS -lrt" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether fPIC compiler option is accepted" >&5 printf %s "checking whether fPIC compiler option is accepted... " >&6; } OLD_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -fPIC -Werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { return 0; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } CFLAGS="$OLD_CFLAGS -fPIC" else $as_nop { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } CFLAGS="$OLD_CFLAGS" fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext if test -z "$PERL" ; then as_fn_error $? "*** Cannot continue without Perl. Sorry." "$LINENO" 5 exit 1 fi if test -f "${PERL}" ; then perl_version=`${PERL} -e "print $]"` long_perl_version=`${PERL} -e "print ($] * 1000000)"` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Perl version" >&5 printf %s "checking Perl version... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${perl_version}" >&5 printf "%s\n" "${perl_version}" >&6; } if test ${long_perl_version} -lt 5010000 ; then as_fn_error $? "At least Perl 5.10 is required" "$LINENO" 5 fi fi for thing in prefix siteprefix vendorprefix vendorlib installarchlib installprivlib installbin installman1dir installman3dir installscript installsitearch installsitelib; do { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl installation variable $thing" >&5 printf %s "checking for Perl installation variable $thing... " >&6; } val=`$PERL -V:$thing | sed -e "s/^.*='//" -e "s/';$//"` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $val" >&5 printf "%s\n" "$val" >&6; } up=`echo $thing | tr '[a-z]' '[A-Z]'` eval "PERL$up=$val" done un=`uname -s -r` if test "$un" = "SunOS 5.9" ; then ac_cv_func_wait3_rusage=yes fi ac_header= ac_cache= for ac_item in $ac_header_c_list do if test $ac_cache; then ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then printf "%s\n" "#define $ac_item 1" >> confdefs.h fi ac_header= ac_cache= elif test $ac_header; then ac_cache=$ac_item else ac_header=$ac_item fi done if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes then : printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for wait3 that fills in rusage" >&5 printf %s "checking for wait3 that fills in rusage... " >&6; } if test ${ac_cv_func_wait3_rusage+y} then : printf %s "(cached) " >&6 else $as_nop if test "$cross_compiling" = yes then : ac_cv_func_wait3_rusage=no else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_includes_default #include #include #include /* HP-UX has wait3 but does not fill in rusage at all. */ int main (void) { struct rusage r; int i; /* Use a field that we can force nonzero -- voluntary context switches. For systems like NeXT and OSF/1 that don't set it, also use the system CPU time. And page faults (I/O) for Linux. */ r.ru_nvcsw = 0; r.ru_stime.tv_sec = 0; r.ru_stime.tv_usec = 0; r.ru_majflt = r.ru_minflt = 0; switch (fork ()) { case 0: /* Child. */ sleep(1); /* Give up the CPU. */ _exit(0); break; case -1: /* What can we do? */ _exit(0); break; default: /* Parent. */ wait3(&i, 0, &r); /* Avoid "text file busy" from rm on fast HP-UX machines. */ sleep(2); return (r.ru_nvcsw == 0 && r.ru_majflt == 0 && r.ru_minflt == 0 && r.ru_stime.tv_sec == 0 && r.ru_stime.tv_usec == 0); } } _ACEOF if ac_fn_c_try_run "$LINENO" then : ac_cv_func_wait3_rusage=yes else $as_nop ac_cv_func_wait3_rusage=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_wait3_rusage" >&5 printf "%s\n" "$ac_cv_func_wait3_rusage" >&6; } if test $ac_cv_func_wait3_rusage = yes; then printf "%s\n" "#define HAVE_WAIT3 1" >>confdefs.h fi # Extract the first word of "nm", so it can be a program name with args. set dummy nm; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_NM+y} then : printf %s "(cached) " >&6 else $as_nop case $NM in [\\/]* | ?:[\\/]*) ac_cv_path_NM="$NM" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_NM="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi NM=$ac_cv_path_NM if test -n "$NM"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $NM" >&5 printf "%s\n" "$NM" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi SENDMAILPROG=no # Check whether --with-sendmail was given. if test ${with_sendmail+y} then : withval=$with_sendmail; SENDMAILPROG=$with_sendmail else $as_nop SENDMAILPROG=no fi DEFANGUSER="" # Check whether --with-user was given. if test ${with_user+y} then : withval=$with_user; DEFANGUSER=$with_user else $as_nop DEFANGUSER=defang fi IP_HEADER=no # Check whether --with-ipheader was given. if test ${with_ipheader+y} then : withval=$with_ipheader; IP_HEADER=$with_ipheader else $as_nop IP_HEADER=no fi MILTERINC= # Check whether --with-milterinc was given. if test ${with_milterinc+y} then : withval=$with_milterinc; MILTERINC=$with_milterinc else $as_nop MILTERINC= fi MILTERLIB= # Check whether --with-milterlib was given. if test ${with_milterlib+y} then : withval=$with_milterlib; MILTERLIB=$with_milterlib else $as_nop MILTERLIB= fi # Check whether --with-confsubdir was given. if test ${with_confsubdir+y} then : withval=$with_confsubdir; CONFSUBDIR="/$with_confsubdir" else $as_nop CONFSUBDIR=/mail fi if test "$CONFSUBDIR" = "/" -o "$CONFSUBDIR" = "//" ; then CONFSUBDIR="" fi # Check whether --with-spooldir was given. if test ${with_spooldir+y} then : withval=$with_spooldir; SPOOLDIR=$with_spooldir else $as_nop SPOOLDIR=/var/spool/MIMEDefang fi # Check whether --with-quarantinedir was given. if test ${with_quarantinedir+y} then : withval=$with_quarantinedir; QDIR=$with_quarantinedir else $as_nop QDIR=/var/spool/MD-Quarantine fi # Check whether --enable-pthread-flag was given. if test ${enable_pthread_flag+y} then : enableval=$enable_pthread_flag; FORCE_PTHREAD_FLAG=-pthread else $as_nop FORCE_PTHREAD_FLAG="" fi # Check whether --enable-check-perl-modules was given. if test ${enable_check_perl_modules+y} then : enableval=$enable_check_perl_modules; ac_cv_perlmodcheck=$enableval else $as_nop ac_cv_perlmodcheck=yes fi if test "$ac_cv_perlmodcheck" = "no" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Compile-time checking for Perl modules disabled" >&5 printf "%s\n" "Compile-time checking for Perl modules disabled" >&6; } HAVE_SPAM_ASSASSIN=yes else for module in 'IO::Socket' 'MIME::Tools 5.410 ()' 'MIME::WordDecoder' 'Digest::SHA' 'FindBin' ; do { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module $module" >&5 printf %s "checking for Perl module $module... " >&6; } (echo "use lib '$PERLINSTALLSITELIB'; use $module;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: *** Error trying to use Perl module $module" >&5 printf "%s\n" "$as_me: WARNING: *** Error trying to use Perl module $module" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: *** Make sure the following Perl modules are installed:" >&5 printf "%s\n" "$as_me: WARNING: *** Make sure the following Perl modules are installed:" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: *** MIME::Tools version 5.410 or higher (5.411a recommended)" >&5 printf "%s\n" "$as_me: WARNING: *** MIME::Tools version 5.410 or higher (5.411a recommended)" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: *** MIME::WordDecoder" >&5 printf "%s\n" "$as_me: WARNING: *** MIME::WordDecoder" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: *** Digest::SHA" >&5 printf "%s\n" "$as_me: WARNING: *** Digest::SHA" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: *** FindBin" >&5 printf "%s\n" "$as_me: WARNING: *** FindBin" >&2;} exit 1 fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ok" >&5 printf "%s\n" "ok" >&6; } done { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module Mail::SpamAssassin 3.0 or better" >&5 printf %s "checking for Perl module Mail::SpamAssassin 3.0 or better... " >&6; } (echo "use Mail::SpamAssassin 3.0 ();" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_SPAM_ASSASSIN=no else HAVE_SPAM_ASSASSIN=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_SPAM_ASSASSIN" >&5 printf "%s\n" "$HAVE_SPAM_ASSASSIN" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module HTML::Parser" >&5 printf %s "checking for Perl module HTML::Parser... " >&6; } (echo "use HTML::Parser;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_HTML_PARSER=no else HAVE_HTML_PARSER=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_HTML_PARSER" >&5 printf "%s\n" "$HAVE_HTML_PARSER" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module Archive::Zip" >&5 printf %s "checking for Perl module Archive::Zip... " >&6; } (echo "use Archive::Zip;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_ARCHIVE_ZIP=no else HAVE_ARCHIVE_ZIP=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_ARCHIVE_ZIP" >&5 printf "%s\n" "$HAVE_ARCHIVE_ZIP" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module IO::Socket::SSL" >&5 printf %s "checking for Perl module IO::Socket::SSL... " >&6; } (echo "use IO::Socket::SSL;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_IO_SOCKET_SSL=no else HAVE_IO_SOCKET_SSL=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_IO_SOCKET_SSL" >&5 printf "%s\n" "$HAVE_IO_SOCKET_SSL" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module JSON" >&5 printf %s "checking for Perl module JSON... " >&6; } (echo "use JSON;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_JSON=no else HAVE_JSON=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_JSON" >&5 printf "%s\n" "$HAVE_JSON" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module LWP::UserAgent" >&5 printf %s "checking for Perl module LWP::UserAgent... " >&6; } (echo "use LWP::UserAgent;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_LWP=no else HAVE_LWP=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_LWP" >&5 printf "%s\n" "$HAVE_LWP" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module Mail::SPF" >&5 printf %s "checking for Perl module Mail::SPF... " >&6; } (echo "use Mail::SPF;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_SPF=no else HAVE_SPF=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_SPF" >&5 printf "%s\n" "$HAVE_SPF" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module Mail::DKIM" >&5 printf %s "checking for Perl module Mail::DKIM... " >&6; } (echo "use Mail::DKIM;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_DKIM=no else HAVE_DKIM=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_DKIM" >&5 printf "%s\n" "$HAVE_DKIM" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module Digest::MD5" >&5 printf %s "checking for Perl module Digest::MD5... " >&6; } (echo "use Digest::MD5;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_DIGEST_MD5=no else HAVE_DIGEST_MD5=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_DIGEST_MD5" >&5 printf "%s\n" "$HAVE_DIGEST_MD5" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module Net::SMTP" >&5 printf %s "checking for Perl module Net::SMTP... " >&6; } (echo "use Net::SMTP;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_NET_SMTP=no else HAVE_NET_SMTP=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HAVE_NET_SMTP" >&5 printf "%s\n" "$HAVE_NET_SMTP" >&6; } dirs=`echo 'print "@INC\n";' | $PERL` foundit="" for i in $dirs ; do if test -r "$i/MIME/Field/ParamVal.pm" ; then foundit="$i/MIME/Field/ParamVal.pm" break fi done if test "$foundit" != "" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking MIME-Tools version" >&5 printf %s "checking MIME-Tools version... " >&6; } mtversion=`echo '$x = $MIME::Tools::VERSION; $y = int($x) * 10000; $z = ($x - int($x)) * 10000; print $y + $z;' | $PERL -I$PERLINSTALLSITELIB -MMIME::Tools 2>/dev/null` mt_actual_version=`echo 'print "$MIME::Tools::VERSION";' | $PERL -I$PERLINSTALLSITELIB -MMIME::Tools 2>/dev/null` if test "$mtversion" = "" ; then mtversion="unknown" mt_actual_version="unknown" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $mt_actual_version" >&5 printf "%s\n" "$mt_actual_version" >&6; } else mtversion="unknown" fi fi HAVE_A_SYSLOG=0 for module in 'Sys::Syslog' 'Unix::Syslog' ; do { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module $module" >&5 printf %s "checking for Perl module $module... " >&6; } (echo "use $module;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? = 0 ; then HAVE_A_SYSLOG=1 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ok" >&5 printf "%s\n" "ok" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi done if test $HAVE_A_SYSLOG = 0 ; then if test "$ac_cv_perlmodcheck" = "yes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: *** Error: Could not find Sys::Syslog or Unix::Syslog" >&5 printf "%s\n" "$as_me: WARNING: *** Error: Could not find Sys::Syslog or Unix::Syslog" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: *** One of those Perl modules is required." >&5 printf "%s\n" "$as_me: WARNING: *** One of those Perl modules is required." >&2;}; exit 1 fi fi if test "$ac_cv_embedded_perl" = "no" ; then echo "Check for embedded perl disabled by --disable-embedded-perl option" HAVE_EXTUTILS_EMBED=no else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Perl module ExtUtils::Embed" >&5 printf %s "checking for Perl module ExtUtils::Embed... " >&6; } (echo "use ExtUtils::Embed;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? = 0 ; then HAVE_EXTUTILS_EMBED=yes { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ok" >&5 printf "%s\n" "ok" >&6; } else HAVE_EXTUTILS_EMBED=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi ac_fn_c_check_header_compile "$LINENO" "getopt.h" "ac_cv_header_getopt_h" "$ac_includes_default" if test "x$ac_cv_header_getopt_h" = xyes then : printf "%s\n" "#define HAVE_GETOPT_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "unistd.h" "ac_cv_header_unistd_h" "$ac_includes_default" if test "x$ac_cv_header_unistd_h" = xyes then : printf "%s\n" "#define HAVE_UNISTD_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "stdint.h" "ac_cv_header_stdint_h" "$ac_includes_default" if test "x$ac_cv_header_stdint_h" = xyes then : printf "%s\n" "#define HAVE_STDINT_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "poll.h" "ac_cv_header_poll_h" "$ac_includes_default" if test "x$ac_cv_header_poll_h" = xyes then : printf "%s\n" "#define HAVE_POLL_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "stdint.h" "ac_cv_header_stdint_h" "$ac_includes_default" if test "x$ac_cv_header_stdint_h" = xyes then : printf "%s\n" "#define HAVE_STDINT_H 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether stdint.h defines uint32_t" >&5 printf %s "checking whether stdint.h defines uint32_t... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { uint32_t foo; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_uint32_t_defined=yes else $as_nop ac_uint32_t_defined=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_uint32_t_defined" >&5 printf "%s\n" "$ac_uint32_t_defined" >&6; } if test "$ac_uint32_t_defined" = "yes" ; then printf "%s\n" "#define HAVE_UINT32_T /**/" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether sys/types.h defines uint32_t" >&5 printf %s "checking whether sys/types.h defines uint32_t... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { uint32_t foo; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_uint32_t_defined=yes else $as_nop ac_uint32_t_defined=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_uint32_t_defined" >&5 printf "%s\n" "$ac_uint32_t_defined" >&6; } if test "$ac_uint32_t_defined" = "yes" ; then printf "%s\n" "#define HAVE_UINT32_T /**/" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether sig_atomic_t is defined" >&5 printf %s "checking whether sig_atomic_t is defined... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { sig_atomic_t foo; ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_have_sig_atomic_t=yes else $as_nop ac_have_sig_atomic_t=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_have_sig_atomic_t" >&5 printf "%s\n" "$ac_have_sig_atomic_t" >&6; } if test "$ac_have_sig_atomic_t" = "yes" ; then printf "%s\n" "#define HAVE_SIG_ATOMIC_T /**/" >>confdefs.h fi if test "$FORCE_PTHREAD_FLAG" = "-pthread" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Forcing use of -pthread C compiler flag" >&5 printf "%s\n" "Forcing use of -pthread C compiler flag" >&6; } PTHREAD_FLAG=-pthread elif test "$GCC" = yes ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ${CC-cc} accepts -pthread" >&5 printf %s "checking whether ${CC-cc} accepts -pthread... " >&6; } echo 'void f(){}' > conftest.c if test -z "`${CC-cc} -pthread -c conftest.c 2>&1`"; then ac_cv_prog_cc_pthread=yes PTHREAD_FLAG="-pthread" else PTHREAD_FLAG="" ac_cv_prog_cc_pthread=no fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_pthread" >&5 printf "%s\n" "$ac_cv_prog_cc_pthread" >&6; } rm -f conftest* else PTHREAD_FLAG="" fi if test "$HAVE_EXTUTILS_EMBED" = "yes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we can embed a Perl interpreter in C" >&5 printf %s "checking if we can embed a Perl interpreter in C... " >&6; } OLDCFLAGS="$CFLAGS" OLDLDFLAGS="$LDFLAGS" OLDLIBS="$LIBS" LIBS="-lperl $LIBS" EMBPERLLDFLAGS="`$PERL -MExtUtils::Embed -e ldopts`" EMBPERLCFLAGS="`$PERL -MExtUtils::Embed -e ccopts`" LDFLAGS="$EMBPERLLDFLAGS $LDFLAGS" CFLAGS="$EMBPERLCFLAGS $CFLAGS" if test "$cross_compiling" = yes then : { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include static PerlInterpreter *my_perl; int main(int argc, char **argv, char **env) { my_perl = perl_alloc(); if (!my_perl) exit(1); exit(0); } _ACEOF if ac_fn_c_try_run "$LINENO" then : EMBED_PERL=yes else $as_nop EMBED_PERL=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi LIBS="$OLDLIBS" CFLAGS="$OLDCFLAGS" LDFLAGS="$OLDLDFLAGS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $EMBED_PERL" >&5 printf "%s\n" "$EMBED_PERL" >&6; } else EMBED_PERL=no fi if test "$EMBED_PERL" = "no" ; then EMBPERLCFLAGS="" EMBPERLLDFLAGS="" EMBPERLLIBS="" EMBPERLDEFS="" EMBPERLOBJS="" else EMBPERLLIBS="-lperl" EMBPERLDEFS="-DEMBED_PERL" EMBPERLOBJS="embperl.o xs_init.o" # Check for buggy perl interpreter { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if it is safe to destroy and recreate a Perl interpreter" >&5 printf %s "checking if it is safe to destroy and recreate a Perl interpreter... " >&6; } LIBS="-lperl $LIBS" LDFLAGS="$EMBPERLLDFLAGS $LDFLAGS" CFLAGS="$EMBPERLCFLAGS $CFLAGS $PTHREAD_FLAG" if test "$cross_compiling" = yes then : { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "cannot run test program while cross compiling See \`config.log' for more details" "$LINENO" 5; } else $as_nop cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include static PerlInterpreter *my_perl = NULL; static char **argv = NULL; int make_embedded_interpreter(char **env) { int argc; if (!argv) { argv = (char **) malloc(6 * sizeof(char *)); } if (my_perl != NULL) { perl_destruct(my_perl); perl_free(my_perl); my_perl = NULL; } argv[0] = ""; argv[1] = "-e"; argv[2] = "print(\"\");"; argv[3] = NULL; argc = 3; #ifdef PERL_SYS_INIT3 PERL_SYS_INIT3(&argc, &argv, &env); #endif my_perl = perl_alloc(); if (!my_perl) { return -1; } PERL_SET_CONTEXT(my_perl); PL_perl_destruct_level = 1; perl_construct(my_perl); PL_perl_destruct_level = 1; argv[0] = ""; argv[1] = "-e"; argv[2] = "print(\"\");"; argv[3] = NULL; argc = 3; perl_parse(my_perl, NULL, argc, argv, NULL); perl_run(my_perl); return 0; } int main(int argc, char **argv, char **env) { make_embedded_interpreter(env); make_embedded_interpreter(env); return 0; } _ACEOF if ac_fn_c_try_run "$LINENO" then : SAFE_EMBED_PERL=yes else $as_nop SAFE_EMBED_PERL=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ conftest.$ac_objext conftest.beam conftest.$ac_ext fi LIBS="$OLDLIBS" CFLAGS="$OLDCFLAGS" LDFLAGS="$OLDLDFLAGS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SAFE_EMBED_PERL" >&5 printf "%s\n" "$SAFE_EMBED_PERL" >&6; } if test "$SAFE_EMBED_PERL" = "yes" ; then EMBPERLDEFS="$EMBPERLDEFS -DSAFE_EMBED_PERL" fi fi if test "$ac_cv_use_poll" = "no" ; then USEPOLL="" else if test "$ac_cv_header_poll_h" = "no" ; then USEPOLL="" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: *** You used --enable-poll, but I cannot find the" >&5 printf "%s\n" "$as_me: WARNING: *** You used --enable-poll, but I cannot find the" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: *** poll.h header. Turning OFF --enable-poll" >&5 printf "%s\n" "$as_me: WARNING: *** poll.h header. Turning OFF --enable-poll" >&2;} else USEPOLL="-DEVENT_USE_POLL=1" fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for res_init in -lresolv" >&5 printf %s "checking for res_init in -lresolv... " >&6; } if test ${ac_cv_lib_resolv_res_init+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-lresolv $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char res_init (); int main (void) { return res_init (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_resolv_res_init=yes else $as_nop ac_cv_lib_resolv_res_init=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_resolv_res_init" >&5 printf "%s\n" "$ac_cv_lib_resolv_res_init" >&6; } if test "x$ac_cv_lib_resolv_res_init" = xyes then : printf "%s\n" "#define HAVE_LIBRESOLV 1" >>confdefs.h LIBS="-lresolv $LIBS" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for htons in -lsocket" >&5 printf %s "checking for htons in -lsocket... " >&6; } if test ${ac_cv_lib_socket_htons+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-lsocket $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char htons (); int main (void) { return htons (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_socket_htons=yes else $as_nop ac_cv_lib_socket_htons=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_htons" >&5 printf "%s\n" "$ac_cv_lib_socket_htons" >&6; } if test "x$ac_cv_lib_socket_htons" = xyes then : printf "%s\n" "#define HAVE_LIBSOCKET 1" >>confdefs.h LIBS="-lsocket $LIBS" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for gethostbyname in -lnsl" >&5 printf %s "checking for gethostbyname in -lnsl... " >&6; } if test ${ac_cv_lib_nsl_gethostbyname+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-lnsl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char gethostbyname (); int main (void) { return gethostbyname (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_nsl_gethostbyname=yes else $as_nop ac_cv_lib_nsl_gethostbyname=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_gethostbyname" >&5 printf "%s\n" "$ac_cv_lib_nsl_gethostbyname" >&6; } if test "x$ac_cv_lib_nsl_gethostbyname" = xyes then : printf "%s\n" "#define HAVE_LIBNSL 1" >>confdefs.h LIBS="-lnsl $LIBS" fi LIBS_WITHOUT_PTHREAD="$LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pthread_once in -lpthread" >&5 printf %s "checking for pthread_once in -lpthread... " >&6; } if test ${ac_cv_lib_pthread_pthread_once+y} then : printf %s "(cached) " >&6 else $as_nop ac_check_lib_save_LIBS=$LIBS LIBS="-lpthread $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ char pthread_once (); int main (void) { return pthread_once (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_pthread_pthread_once=yes else $as_nop ac_cv_lib_pthread_pthread_once=no fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_once" >&5 printf "%s\n" "$ac_cv_lib_pthread_pthread_once" >&6; } if test "x$ac_cv_lib_pthread_pthread_once" = xyes then : printf "%s\n" "#define HAVE_LIBPTHREAD 1" >>confdefs.h LIBS="-lpthread $LIBS" fi ac_fn_c_check_func "$LINENO" "initgroups" "ac_cv_func_initgroups" if test "x$ac_cv_func_initgroups" = xyes then : printf "%s\n" "#define HAVE_INITGROUPS 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "getpwnam_r" "ac_cv_func_getpwnam_r" if test "x$ac_cv_func_getpwnam_r" = xyes then : printf "%s\n" "#define HAVE_GETPWNAM_R 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "setrlimit" "ac_cv_func_setrlimit" if test "x$ac_cv_func_setrlimit" = xyes then : printf "%s\n" "#define HAVE_SETRLIMIT 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "snprintf" "ac_cv_func_snprintf" if test "x$ac_cv_func_snprintf" = xyes then : printf "%s\n" "#define HAVE_SNPRINTF 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "vsnprintf" "ac_cv_func_vsnprintf" if test "x$ac_cv_func_vsnprintf" = xyes then : printf "%s\n" "#define HAVE_VSNPRINTF 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "readdir_r" "ac_cv_func_readdir_r" if test "x$ac_cv_func_readdir_r" = xyes then : printf "%s\n" "#define HAVE_READDIR_R 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "pathconf" "ac_cv_func_pathconf" if test "x$ac_cv_func_pathconf" = xyes then : printf "%s\n" "#define HAVE_PATHCONF 1" >>confdefs.h fi ac_fn_c_check_func "$LINENO" "inet_ntop" "ac_cv_func_inet_ntop" if test "x$ac_cv_func_inet_ntop" = xyes then : printf "%s\n" "#define HAVE_INET_NTOP 1" >>confdefs.h fi if test "$SPOOLDIR" = "no" -o "$SPOOLDIR" = "" ; then SPOOLDIR=/var/spool/MIMEDefang fi if test "$QDIR" = "no" -o "$QDIR" = "" ; then QDIR=/var/spool/MIMEDefang fi # Check whether --enable-debugging was given. if test ${enable_debugging+y} then : enableval=$enable_debugging; ac_cv_debugging=$enableval else $as_nop ac_cv_debugging=no fi # Check whether --enable-anti-virus was given. if test ${enable_anti_virus+y} then : enableval=$enable_anti_virus; ac_cv_antivirus=$enableval else $as_nop ac_cv_antivirus=yes fi # Check whether --enable-antivir was given. if test ${enable_antivir+y} then : enableval=$enable_antivir; ac_cv_antivir=$enableval else $as_nop ac_cv_antivir=yes fi # Check whether --enable-vexira was given. if test ${enable_vexira+y} then : enableval=$enable_vexira; ac_cv_vexira=$enableval else $as_nop ac_cv_vexira=yes fi # Check whether --enable-uvscan was given. if test ${enable_uvscan+y} then : enableval=$enable_uvscan; ac_cv_uvscan=$enableval else $as_nop ac_cv_uvscan=yes fi # Check whether --enable-bdc was given. if test ${enable_bdc+y} then : enableval=$enable_bdc; ac_cv_bdc=$enableval else $as_nop ac_cv_bdc=yes fi # Check whether --enable-sweep was given. if test ${enable_sweep+y} then : enableval=$enable_sweep; ac_cv_sweep=$enableval else $as_nop ac_cv_sweep=yes fi # Check whether --enable-savscan was given. if test ${enable_savscan+y} then : enableval=$enable_savscan; ac_cv_savscan=$enableval else $as_nop ac_cv_savscan=yes fi # Check whether --enable-trend was given. if test ${enable_trend+y} then : enableval=$enable_trend; ac_cv_trend=$enableval else $as_nop ac_cv_trend=yes fi # Check whether --enable-AvpLinux was given. if test ${enable_AvpLinux+y} then : enableval=$enable_AvpLinux; ac_cv_AvpLinux=$enableval else $as_nop ac_cv_AvpLinux=yes fi # Check whether --enable-kavscanner was given. if test ${enable_kavscanner+y} then : enableval=$enable_kavscanner; ac_cv_kavscanner=$enableval else $as_nop ac_cv_kavscanner=yes fi # Check whether --enable-aveclient was given. if test ${enable_aveclient+y} then : enableval=$enable_aveclient; ac_cv_aveclient=$enableval else $as_nop ac_cv_aveclient=yes fi # Check whether --enable-clamav was given. if test ${enable_clamav+y} then : enableval=$enable_clamav; ac_cv_clamav=$enableval else $as_nop ac_cv_clamav=yes fi # Check whether --enable-fsav was given. if test ${enable_fsav+y} then : enableval=$enable_fsav; ac_cv_fsav=$enableval else $as_nop ac_cv_fsav=yes fi # Check whether --enable-csav was given. if test ${enable_csav+y} then : enableval=$enable_csav; ac_cv_csav=$enableval else $as_nop ac_cv_csav=yes fi # Check whether --enable-fprot was given. if test ${enable_fprot+y} then : enableval=$enable_fprot; ac_cv_fprot=$enableval else $as_nop ac_cv_fprot=yes fi # Check whether --enable-fpscan was given. if test ${enable_fpscan+y} then : enableval=$enable_fpscan; ac_cv_fpscan=$enableval else $as_nop ac_cv_fpscan=yes fi # Check whether --enable-sophie was given. if test ${enable_sophie+y} then : enableval=$enable_sophie; ac_cv_sophie=$enableval else $as_nop ac_cv_sophie=yes fi # Check whether --enable-nvcc was given. if test ${enable_nvcc+y} then : enableval=$enable_nvcc; ac_cv_nvcc=$enableval else $as_nop ac_cv_nvcc=yes fi # Check whether --enable-clamd was given. if test ${enable_clamd+y} then : enableval=$enable_clamd; ac_cv_clamd=$enableval else $as_nop ac_cv_clamd=yes fi # Check whether --enable-trophie was given. if test ${enable_trophie+y} then : enableval=$enable_trophie; ac_cv_trophie=$enableval else $as_nop ac_cv_trophie=yes fi # Check whether --enable-nod32 was given. if test ${enable_nod32+y} then : enableval=$enable_nod32; ac_cv_nod32=$enableval else $as_nop ac_cv_nod32=yes fi # Check whether --enable-rspamc was given. if test ${enable_rspamc+y} then : enableval=$enable_rspamc; ac_cv_rspamc=$enableval else $as_nop ac_cv_rspamc=yes fi ANTIVIR_PATH="$PATH:/usr/lib/AntiVir:/usr/local/uvscan:/opt/AVP:/etc/iscan:/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bd7:/usr/local/bd7/bin:/opt/kav/bin:/opt/kav/5.5/kav4unix/bin/:/opt/eset/nod32/bin" if test "$ac_cv_antivirus" = "yes"; then if test "$ac_cv_antivir" = yes; then # Extract the first word of "antivir", so it can be a program name with args. set dummy antivir; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_HBEDV+y} then : printf %s "(cached) " >&6 else $as_nop case $HBEDV in [\\/]* | ?:[\\/]*) ac_cv_path_HBEDV="$HBEDV" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_HBEDV="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_HBEDV" && ac_cv_path_HBEDV="/bin/false" ;; esac fi HBEDV=$ac_cv_path_HBEDV if test -n "$HBEDV"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $HBEDV" >&5 printf "%s\n" "$HBEDV" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_vexira" = yes; then # Extract the first word of "vascan", so it can be a program name with args. set dummy vascan; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_VEXIRA+y} then : printf %s "(cached) " >&6 else $as_nop case $VEXIRA in [\\/]* | ?:[\\/]*) ac_cv_path_VEXIRA="$VEXIRA" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_VEXIRA="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_VEXIRA" && ac_cv_path_VEXIRA="/bin/false" ;; esac fi VEXIRA=$ac_cv_path_VEXIRA if test -n "$VEXIRA"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $VEXIRA" >&5 printf "%s\n" "$VEXIRA" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_uvscan" = yes; then # Extract the first word of "uvscan", so it can be a program name with args. set dummy uvscan; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_NAI+y} then : printf %s "(cached) " >&6 else $as_nop case $NAI in [\\/]* | ?:[\\/]*) ac_cv_path_NAI="$NAI" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_NAI="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_NAI" && ac_cv_path_NAI="/bin/false" ;; esac fi NAI=$ac_cv_path_NAI if test -n "$NAI"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $NAI" >&5 printf "%s\n" "$NAI" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_bdc" = yes; then # Extract the first word of "bdc", so it can be a program name with args. set dummy bdc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_BDC+y} then : printf %s "(cached) " >&6 else $as_nop case $BDC in [\\/]* | ?:[\\/]*) ac_cv_path_BDC="$BDC" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_BDC="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_BDC" && ac_cv_path_BDC="/bin/false" ;; esac fi BDC=$ac_cv_path_BDC if test -n "$BDC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $BDC" >&5 printf "%s\n" "$BDC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_sweep" = yes; then # Extract the first word of "sweep", so it can be a program name with args. set dummy sweep; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_SOPHOS+y} then : printf %s "(cached) " >&6 else $as_nop case $SOPHOS in [\\/]* | ?:[\\/]*) ac_cv_path_SOPHOS="$SOPHOS" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_SOPHOS="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_SOPHOS" && ac_cv_path_SOPHOS="/bin/false" ;; esac fi SOPHOS=$ac_cv_path_SOPHOS if test -n "$SOPHOS"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SOPHOS" >&5 printf "%s\n" "$SOPHOS" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_savscan" = yes; then # Extract the first word of "savscan", so it can be a program name with args. set dummy savscan; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_SAVSCAN+y} then : printf %s "(cached) " >&6 else $as_nop case $SAVSCAN in [\\/]* | ?:[\\/]*) ac_cv_path_SAVSCAN="$SAVSCAN" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_SAVSCAN="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_SAVSCAN" && ac_cv_path_SAVSCAN="/bin/false" ;; esac fi SAVSCAN=$ac_cv_path_SAVSCAN if test -n "$SAVSCAN"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SAVSCAN" >&5 printf "%s\n" "$SAVSCAN" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_trend" = yes; then # Extract the first word of "vscan", so it can be a program name with args. set dummy vscan; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_TREND+y} then : printf %s "(cached) " >&6 else $as_nop case $TREND in [\\/]* | ?:[\\/]*) ac_cv_path_TREND="$TREND" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_TREND="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_TREND" && ac_cv_path_TREND="/bin/false" ;; esac fi TREND=$ac_cv_path_TREND if test -n "$TREND"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $TREND" >&5 printf "%s\n" "$TREND" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_kavscanner" = yes ; then # Extract the first word of "kavscanner", so it can be a program name with args. set dummy kavscanner; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_KAVSCANNER+y} then : printf %s "(cached) " >&6 else $as_nop case $KAVSCANNER in [\\/]* | ?:[\\/]*) ac_cv_path_KAVSCANNER="$KAVSCANNER" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_KAVSCANNER="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_KAVSCANNER" && ac_cv_path_KAVSCANNER="/bin/false" ;; esac fi KAVSCANNER=$ac_cv_path_KAVSCANNER if test -n "$KAVSCANNER"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $KAVSCANNER" >&5 printf "%s\n" "$KAVSCANNER" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_clamav" = yes; then # Extract the first word of "clamscan", so it can be a program name with args. set dummy clamscan; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_CLAMSCAN+y} then : printf %s "(cached) " >&6 else $as_nop case $CLAMSCAN in [\\/]* | ?:[\\/]*) ac_cv_path_CLAMSCAN="$CLAMSCAN" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_CLAMSCAN="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_CLAMSCAN" && ac_cv_path_CLAMSCAN="/bin/false" ;; esac fi CLAMSCAN=$ac_cv_path_CLAMSCAN if test -n "$CLAMSCAN"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CLAMSCAN" >&5 printf "%s\n" "$CLAMSCAN" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_clamav" = yes; then # Extract the first word of "clamdscan", so it can be a program name with args. set dummy clamdscan; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_CLAMDSCAN+y} then : printf %s "(cached) " >&6 else $as_nop case $CLAMDSCAN in [\\/]* | ?:[\\/]*) ac_cv_path_CLAMDSCAN="$CLAMDSCAN" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_CLAMDSCAN="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_CLAMDSCAN" && ac_cv_path_CLAMDSCAN="/bin/false" ;; esac fi CLAMDSCAN=$ac_cv_path_CLAMDSCAN if test -n "$CLAMDSCAN"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CLAMDSCAN" >&5 printf "%s\n" "$CLAMDSCAN" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_AvpLinux" = yes; then # Extract the first word of "AvpLinux", so it can be a program name with args. set dummy AvpLinux; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_AVP+y} then : printf %s "(cached) " >&6 else $as_nop case $AVP in [\\/]* | ?:[\\/]*) ac_cv_path_AVP="$AVP" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_AVP="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_AVP" && ac_cv_path_AVP="/bin/false" ;; esac fi AVP=$ac_cv_path_AVP if test -n "$AVP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AVP" >&5 printf "%s\n" "$AVP" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi # Extract the first word of "kavdaemon", so it can be a program name with args. set dummy kavdaemon; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_AVP_KAVDAEMON+y} then : printf %s "(cached) " >&6 else $as_nop case $AVP_KAVDAEMON in [\\/]* | ?:[\\/]*) ac_cv_path_AVP_KAVDAEMON="$AVP_KAVDAEMON" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_AVP_KAVDAEMON="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_AVP_KAVDAEMON" && ac_cv_path_AVP_KAVDAEMON="/bin/false" ;; esac fi AVP_KAVDAEMON=$ac_cv_path_AVP_KAVDAEMON if test -n "$AVP_KAVDAEMON"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AVP_KAVDAEMON" >&5 printf "%s\n" "$AVP_KAVDAEMON" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_aveclient" = yes; then # Extract the first word of "aveclient", so it can be a program name with args. set dummy aveclient; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_AVP5+y} then : printf %s "(cached) " >&6 else $as_nop case $AVP5 in [\\/]* | ?:[\\/]*) ac_cv_path_AVP5="$AVP5" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_AVP5="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_AVP5" && ac_cv_path_AVP5="/bin/false" ;; esac fi AVP5=$ac_cv_path_AVP5 if test -n "$AVP5"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AVP5" >&5 printf "%s\n" "$AVP5" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi # Update AVP for best scanner: kavdaemon preferred over AvpLinux if test "$AVP_KAVDAEMON" != "/bin/false" ; then AVP="$AVP_KAVDAEMON" fi if test "$ac_cv_csav" = yes ; then # Extract the first word of "csav", so it can be a program name with args. set dummy csav; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_CSAV+y} then : printf %s "(cached) " >&6 else $as_nop case $CSAV in [\\/]* | ?:[\\/]*) ac_cv_path_CSAV="$CSAV" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_CSAV="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_CSAV" && ac_cv_path_CSAV="/bin/false" ;; esac fi CSAV=$ac_cv_path_CSAV if test -n "$CSAV"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CSAV" >&5 printf "%s\n" "$CSAV" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_fsav" = yes; then # Extract the first word of "fsav", so it can be a program name with args. set dummy fsav; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_FSAV+y} then : printf %s "(cached) " >&6 else $as_nop case $FSAV in [\\/]* | ?:[\\/]*) ac_cv_path_FSAV="$FSAV" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_FSAV="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_FSAV" && ac_cv_path_FSAV="/bin/false" ;; esac fi FSAV=$ac_cv_path_FSAV if test -n "$FSAV"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $FSAV" >&5 printf "%s\n" "$FSAV" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_fprot" = yes; then # Extract the first word of "f-prot", so it can be a program name with args. set dummy f-prot; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_FPROT+y} then : printf %s "(cached) " >&6 else $as_nop case $FPROT in [\\/]* | ?:[\\/]*) ac_cv_path_FPROT="$FPROT" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_FPROT="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_FPROT" && ac_cv_path_FPROT="/bin/false" ;; esac fi FPROT=$ac_cv_path_FPROT if test -n "$FPROT"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $FPROT" >&5 printf "%s\n" "$FPROT" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_fpscan" = yes; then # Extract the first word of "fpscan", so it can be a program name with args. set dummy fpscan; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_FPSCAN+y} then : printf %s "(cached) " >&6 else $as_nop case $FPSCAN in [\\/]* | ?:[\\/]*) ac_cv_path_FPSCAN="$FPSCAN" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_FPSCAN="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_FPSCAN" && ac_cv_path_FPSCAN="/bin/false" ;; esac fi FPSCAN=$ac_cv_path_FPSCAN if test -n "$FPSCAN"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $FPSCAN" >&5 printf "%s\n" "$FPSCAN" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_sophie" = yes; then # Extract the first word of "sophie", so it can be a program name with args. set dummy sophie; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_SOPHIE+y} then : printf %s "(cached) " >&6 else $as_nop case $SOPHIE in [\\/]* | ?:[\\/]*) ac_cv_path_SOPHIE="$SOPHIE" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_SOPHIE="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_SOPHIE" && ac_cv_path_SOPHIE="/bin/false" ;; esac fi SOPHIE=$ac_cv_path_SOPHIE if test -n "$SOPHIE"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SOPHIE" >&5 printf "%s\n" "$SOPHIE" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_nvcc" = yes; then # Extract the first word of "nvcc", so it can be a program name with args. set dummy nvcc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_NVCC+y} then : printf %s "(cached) " >&6 else $as_nop case $NVCC in [\\/]* | ?:[\\/]*) ac_cv_path_NVCC="$NVCC" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_NVCC="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_NVCC" && ac_cv_path_NVCC="/bin/false" ;; esac fi NVCC=$ac_cv_path_NVCC if test -n "$NVCC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $NVCC" >&5 printf "%s\n" "$NVCC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_clamd" = yes; then # Extract the first word of "clamd", so it can be a program name with args. set dummy clamd; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_CLAMD+y} then : printf %s "(cached) " >&6 else $as_nop case $CLAMD in [\\/]* | ?:[\\/]*) ac_cv_path_CLAMD="$CLAMD" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_CLAMD="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_CLAMD" && ac_cv_path_CLAMD="/bin/false" ;; esac fi CLAMD=$ac_cv_path_CLAMD if test -n "$CLAMD"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CLAMD" >&5 printf "%s\n" "$CLAMD" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_trophie" = yes; then # Extract the first word of "trophie", so it can be a program name with args. set dummy trophie; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_TROPHIE+y} then : printf %s "(cached) " >&6 else $as_nop case $TROPHIE in [\\/]* | ?:[\\/]*) ac_cv_path_TROPHIE="$TROPHIE" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_TROPHIE="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_TROPHIE" && ac_cv_path_TROPHIE="/bin/false" ;; esac fi TROPHIE=$ac_cv_path_TROPHIE if test -n "$TROPHIE"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $TROPHIE" >&5 printf "%s\n" "$TROPHIE" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$ac_cv_nod32" = yes; then # Extract the first word of "nod32cli", so it can be a program name with args. set dummy nod32cli; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_NOD32+y} then : printf %s "(cached) " >&6 else $as_nop case $NOD32 in [\\/]* | ?:[\\/]*) ac_cv_path_NOD32="$NOD32" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $ANTIVIR_PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_NOD32="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_NOD32" && ac_cv_path_NOD32="/bin/false" ;; esac fi NOD32=$ac_cv_path_NOD32 if test -n "$NOD32"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $NOD32" >&5 printf "%s\n" "$NOD32" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi fi if test "$ac_cv_rspamc" = yes; then # Extract the first word of "rspamc", so it can be a program name with args. set dummy rspamc; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_RSPAMC+y} then : printf %s "(cached) " >&6 else $as_nop case $RSPAMC in [\\/]* | ?:[\\/]*) ac_cv_path_RSPAMC="$RSPAMC" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_RSPAMC="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_RSPAMC" && ac_cv_path_RSPAMC="/bin/false" ;; esac fi RSPAMC=$ac_cv_path_RSPAMC if test -n "$RSPAMC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $RSPAMC" >&5 printf "%s\n" "$RSPAMC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi test -z "$HBEDV" && HBEDV=/bin/false test -z "$VEXIRA" && VEXIRA=/bin/false test -z "$NAI" && NAI=/bin/false test -z "$BDC" && BDC=/bin/false test -z "$SOPHOS" && SOPHOS=/bin/false test -z "$SAVSCAN" && SAVSCAN=/bin/false test -z "$TREND" && TREND=/bin/false test -z "$CLAMSCAN" && CLAMSCAN=/bin/false test -z "$CLAMDSCAN" && CLAMDSCAN=/bin/false test -z "$AVP" && AVP=/bin/false test -z "$AVP5" && AVP5=/bin/false test -z "$AVP_KAVDAEMON" && AVP_KAVDAEMON=/bin/false test -z "$KAVSCANNER" && KAVSCANNER=/bin/false test -z "$CSAV" && CSAV=/bin/false test -z "$FSAV" && FSAV=/bin/false test -z "$FPROT" && FPROT=/bin/false test -z "$FPSCAN" && FPSCAN=/bin/false test -z "$SOPHIE" && SOPHIE=/bin/false test -z "$NVCC" && NVCC=/bin/false test -z "$CLAMD" && CLAMD=/bin/false test -z "$TROPHIE" && TROPHIE=/bin/false test -z "$NOD32" && NOD32=/bin/false test -z "$RSPAMC" && RSPAMC=/bin/false if test "$ac_cv_debugging" = yes ; then ENABLE_DEBUGGING=-DENABLE_DEBUGGING else ENABLE_DEBUGGING= fi if test "$SENDMAILPROG" = "no" ; then # Extract the first word of "sendmail", so it can be a program name with args. set dummy sendmail; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_SENDMAILPROG+y} then : printf %s "(cached) " >&6 else $as_nop case $SENDMAILPROG in [\\/]* | ?:[\\/]*) ac_cv_path_SENDMAILPROG="$SENDMAILPROG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="$PATH:/sbin:/usr/sbin:/usr/lib:/usr/libexec" for as_dir in $as_dummy do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_SENDMAILPROG="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_SENDMAILPROG" && ac_cv_path_SENDMAILPROG="no" ;; esac fi SENDMAILPROG=$ac_cv_path_SENDMAILPROG if test -n "$SENDMAILPROG"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SENDMAILPROG" >&5 printf "%s\n" "$SENDMAILPROG" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test "$SENDMAILPROG" = "no" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Oops.. I couldn't find the 'sendmail' program. Please install it." >&5 printf "%s\n" "$as_me: WARNING: Oops.. I couldn't find the 'sendmail' program. Please install it." >&2;} PROBLEM=1 fi if test "$PROBLEM" = 1 ; then exit 1 fi if test "$GCC" = yes; then if test "`uname -s`" = Linux; then CFLAGS="$CFLAGS -Wall -Wstrict-prototypes" fi fi VERSION=`$PERL -I modules/lib -e 'use Mail::MIMEDefang; print Mail::MIMEDefang->md_version'` if test "$ac_cv_func_snprintf" != "yes" -o "$ac_cv_func_vsnprintf" != "yes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Your OS lacks snprintf or vsnprintf. Sorry!" >&5 printf "%s\n" "Your OS lacks snprintf or vsnprintf. Sorry!" >&6; } exit 1 fi SMMILTER=`echo ../sendmail-*/include` as_test_x='test -e' as_executable_p='test -e' # Redefine so AC_PATH_PROG works as_fn_executable_p() { test -e "$1" } # Extract the first word of "libmilter/mfapi.h", so it can be a program name with args. set dummy libmilter/mfapi.h; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_MINCLUDE+y} then : printf %s "(cached) " >&6 else $as_nop case $MINCLUDE in [\\/]* | ?:[\\/]*) ac_cv_path_MINCLUDE="$MINCLUDE" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="$MILTERINC:$SMMILTER:/usr/include:/usr/local/include:/usr/local/include/sendmail:/opt/local/include" for as_dir in $as_dummy do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_MINCLUDE="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_MINCLUDE" && ac_cv_path_MINCLUDE="no" ;; esac fi MINCLUDE=$ac_cv_path_MINCLUDE if test -n "$MINCLUDE"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MINCLUDE" >&5 printf "%s\n" "$MINCLUDE" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi MINCLUDE=`dirname "$MINCLUDE"` MINCLUDE=`dirname "$MINCLUDE"` if test "$MINCLUDE" = "/usr/include" ; then MINCLUDE="" else MINCLUDE="-I${MINCLUDE}" fi SMPATH=`echo ../sendmail-*/obj.*/libmilter` MDLIBPATH=/usr/local/lib:/usr/local/lib64:/usr/lib:/usr/lib64:/lib:/lib64:/usr/lib/libmilter:/usr/lib64/libmilter # Extract the first word of "libmilter.a", so it can be a program name with args. set dummy libmilter.a; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_LIBMILTER+y} then : printf %s "(cached) " >&6 else $as_nop case $LIBMILTER in [\\/]* | ?:[\\/]*) ac_cv_path_LIBMILTER="$LIBMILTER" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="$MILTERLIB:$SMPATH:$MDLIBPATH" for as_dir in $as_dummy do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_LIBMILTER="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_LIBMILTER" && ac_cv_path_LIBMILTER="no" ;; esac fi LIBMILTER=$ac_cv_path_LIBMILTER if test -n "$LIBMILTER"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LIBMILTER" >&5 printf "%s\n" "$LIBMILTER" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi SMPATH=`echo ../sendmail-*/obj.*/libsm` # Extract the first word of "libsm.a", so it can be a program name with args. set dummy libsm.a; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_LIBSM+y} then : printf %s "(cached) " >&6 else $as_nop case $LIBSM in [\\/]* | ?:[\\/]*) ac_cv_path_LIBSM="$LIBSM" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="$SMPATH:$MDLIBPATH" for as_dir in $as_dummy do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_LIBSM="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_LIBSM" && ac_cv_path_LIBSM="no" ;; esac fi LIBSM=$ac_cv_path_LIBSM if test -n "$LIBSM"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LIBSM" >&5 printf "%s\n" "$LIBSM" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi # Extract the first word of "libmilter.so", so it can be a program name with args. set dummy libmilter.so; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_LIBMILTERSO+y} then : printf %s "(cached) " >&6 else $as_nop case $LIBMILTERSO in [\\/]* | ?:[\\/]*) ac_cv_path_LIBMILTERSO="$LIBMILTERSO" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_dummy="$MILTERLIB:$SMPATH:$MDLIBPATH" for as_dir in $as_dummy do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_LIBMILTERSO="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_LIBMILTERSO" && ac_cv_path_LIBMILTERSO="no" ;; esac fi LIBMILTERSO=$ac_cv_path_LIBMILTERSO if test -n "$LIBMILTERSO"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LIBMILTERSO" >&5 printf "%s\n" "$LIBMILTERSO" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi PROBLEM=0 if test "$LIBMILTER" = "no" -a "$LIBMILTERSO" = "no" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Oops.. I couldn't find libmilter.a or libmilter.so. Please install Sendmail" >&5 printf "%s\n" "$as_me: WARNING: Oops.. I couldn't find libmilter.a or libmilter.so. Please install Sendmail" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: and its libraries. You must run Build in the libmilter/ directory" >&5 printf "%s\n" "$as_me: WARNING: and its libraries. You must run Build in the libmilter/ directory" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: to compile libmilter." >&5 printf "%s\n" "$as_me: WARNING: to compile libmilter." >&2;} PROBLEM=1 fi if test "$LIBMILTER" != "no" ; then LIBMILTERACTUAL="$LIBMILTER" else LIBMILTERACTUAL="$LIBMILTERSO" fi LIBMILTERDIR=`dirname "$LIBMILTERACTUAL"` if test "$LIBMILTERDIR" = "/usr/lib" -o "$LIBMILTERDIR" = "/lib" -o "$LIBMILTERDIR" = "/usr/lib64" -o "$LIBMILTERDIR" = "/lib64" ; then LIBMILTERDIR="" else LDFLAGS="$LDFLAGS -L$LIBMILTERDIR" fi if test "$MINCLUDE" = "no" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Oops.. I couldn't find libmilter/mfapi.h. Please install Sendmail 8.12" >&5 printf "%s\n" "$as_me: WARNING: Oops.. I couldn't find libmilter/mfapi.h. Please install Sendmail 8.12" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: and its header files." >&5 printf "%s\n" "$as_me: WARNING: and its header files." >&2;} PROBLEM=1 fi OLD_LIBS="$LIBS" LIBS="$LIBS -lmilter" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether libmilter requires -lsm" >&5 printf %s "checking whether libmilter requires -lsm... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include "libmilter/mfapi.h" #include int main (void) { extern size_t sm_strlcpy(char *, char const *, ssize_t); char foo[10]; sm_strlcpy(foo, "", 0); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : NEED_LIBSM=no else $as_nop NEED_LIBSM=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $NEED_LIBSM" >&5 printf "%s\n" "$NEED_LIBSM" >&6; } LIBS="$OLD_LIBS" if test "$NEED_LIBSM" = yes ; then LIBS="$LIBS -lsm" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether libsm requires -lldap" >&5 printf %s "checking whether libsm requires -lldap... " >&6; } RESULT=`$NM $LIBSM | grep ldap_` if test -z "$RESULT" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } LIBS="$LIBS -lldap -llber" fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether libmilter requires -lldap" >&5 printf %s "checking whether libmilter requires -lldap... " >&6; } RESULT=`$NM $LIBMILTER 2>/dev/null | grep ldap_; $NM -D $LIBMILTERSO 2>/dev/null | grep ldap_` if test -z "$RESULT" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } LIBS="$LIBS -lldap -llber" fi fi CONFDIR_EVAL=`echo ${sysconfdir}${CONFSUBDIR}` ac_config_files="$ac_config_files Makefile mimedefang.pl mimedefang-release.pl script/mimedefang-util examples/init-script mimedefang-filter.5 mimedefang-multiplexor.8 mimedefang-protocol.7 mimedefang-notify.7 mimedefang.8 mimedefang.pl.8 md-mx-ctrl.8 watch-mimedefang redhat/mimedefang-init redhat/mimedefang-sysconfig" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 printf "%s\n" "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh as_nop=: if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else $as_nop case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else $as_nop as_fn_append () { eval $1=\$$1\$2 } fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else $as_nop as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by $as_me, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac case $ac_config_headers in *" "*) set x $ac_config_headers; shift; ac_config_headers=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Report bugs to the package provider." _ACEOF ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ config.status configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" Copyright (C) 2021 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) printf "%s\n" "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) printf "%s\n" "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header as_fn_error $? "ambiguous option: \`$1' Try \`$0 --help' for more information.";; --help | --hel | -h ) printf "%s\n" "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: \`$1' Try \`$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX printf "%s\n" "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "mimedefang.pl") CONFIG_FILES="$CONFIG_FILES mimedefang.pl" ;; "mimedefang-release.pl") CONFIG_FILES="$CONFIG_FILES mimedefang-release.pl" ;; "script/mimedefang-util") CONFIG_FILES="$CONFIG_FILES script/mimedefang-util" ;; "examples/init-script") CONFIG_FILES="$CONFIG_FILES examples/init-script" ;; "mimedefang-filter.5") CONFIG_FILES="$CONFIG_FILES mimedefang-filter.5" ;; "mimedefang-multiplexor.8") CONFIG_FILES="$CONFIG_FILES mimedefang-multiplexor.8" ;; "mimedefang-protocol.7") CONFIG_FILES="$CONFIG_FILES mimedefang-protocol.7" ;; "mimedefang-notify.7") CONFIG_FILES="$CONFIG_FILES mimedefang-notify.7" ;; "mimedefang.8") CONFIG_FILES="$CONFIG_FILES mimedefang.8" ;; "mimedefang.pl.8") CONFIG_FILES="$CONFIG_FILES mimedefang.pl.8" ;; "md-mx-ctrl.8") CONFIG_FILES="$CONFIG_FILES md-mx-ctrl.8" ;; "watch-mimedefang") CONFIG_FILES="$CONFIG_FILES watch-mimedefang" ;; "redhat/mimedefang-init") CONFIG_FILES="$CONFIG_FILES redhat/mimedefang-init" ;; "redhat/mimedefang-sysconfig") CONFIG_FILES="$CONFIG_FILES redhat/mimedefang-sysconfig" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files test ${CONFIG_HEADERS+y} || CONFIG_HEADERS=$config_headers fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with `./config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. # This happens for instance with `./config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF # Transform confdefs.h into an awk script `defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. # Create a delimiter string that does not exist in confdefs.h, to ease # handling of long lines. ac_delim='%!_!# ' for ac_last_try in false false :; do ac_tt=`sed -n "/$ac_delim/p" confdefs.h` if test -z "$ac_tt"; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done # For the awk script, D is an array of macro values keyed by name, # likewise P contains macro parameters if any. Preserve backslash # newline sequences. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* sed -n ' s/.\{148\}/&'"$ac_delim"'/g t rset :rset s/^[ ]*#[ ]*define[ ][ ]*/ / t def d :def s/\\$// t bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3"/p s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p d :bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3\\\\\\n"\\/p t cont s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p t cont d :cont n s/.\{148\}/&'"$ac_delim"'/g t clear :clear s/\\$// t bsnlc s/["\\]/\\&/g; s/^/"/; s/$/"/p d :bsnlc s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >$CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } /^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { line = \$ 0 split(line, arg, " ") if (arg[1] == "#") { defundef = arg[2] mac1 = arg[3] } else { defundef = substr(arg[1], 2) mac1 = arg[2] } split(mac1, mac2, "(") #) macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { # Preserve the white space surrounding the "#". print prefix "define", macro P[macro] D[macro] next } else { # Replace #undef with comments. This is necessary, for example, # in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. if (defundef == "undef") { print "/*", prefix defundef, macro, "*/" next } } } { print } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS " shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 printf "%s\n" "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`printf "%s\n" "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= ac_sed_dataroot=' /datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t $ac_datarootdir_hack " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :H) # # CONFIG_HEADER # if test x"$ac_file" != x-; then { printf "%s\n" "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" } >"$ac_tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 printf "%s\n" "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$ac_tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else printf "%s\n" "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi chmod a+x examples/init-script > /dev/null 2>&1 chmod a+x watch-mimedefang > /dev/null 2>&1 chmod a+x redhat/mimedefang-init > /dev/null 2>&1 chmod a+x script/mimedefang-util > /dev/null 2>&1 echo "" echo "*** Virus scanner detection results:" PROBLEM=0 GOT_VIRUS_SCANNER=0 if test "$ac_cv_antivirus" != "yes" ; then echo "All virus-scanner detection disabled by --disable-anti-virus" else if test "$HBEDV" = "/bin/false" ; then if test "$ac_cv_antivir" != "yes" ; then echo "H+BEDV 'antivir' NO (Disabled by configure command)" else echo "H+BEDV 'antivir' NO (not found)" fi else echo "H+BEDV 'antivir' YES - $HBEDV" GOT_VIRUS_SCANNER=1 fi if test "$VEXIRA" = "/bin/false" ; then if test "$ac_cv_vexira" != "yes" ; then echo "Vexira 'vascan' NO (Disabled by configure command)" else echo "Vexira 'vascan' NO (not found)" fi else echo "Vexira 'vascan' YES - $VEXIRA" GOT_VIRUS_SCANNER=1 fi if test "$NAI" = "/bin/false" ; then if test "$ac_cv_uvscan" != "yes" ; then echo "NAI 'uvscan' NO (Disabled by configure command)" else echo "NAI 'uvscan' NO (not found)" fi else echo "NAI 'uvscan' YES - $NAI" GOT_VIRUS_SCANNER=1 fi if test "$BDC" = "/bin/false" ; then if test "$ac_cv_bdc" != "yes" ; then echo "BDC 'bdc' NO (Disabled by configure command)" else echo "BDC 'bdc' NO (not found)" fi else echo "BDC 'bdc' YES - $BDC" GOT_VIRUS_SCANNER=1 fi if test "$SOPHOS" = "/bin/false" ; then if test "$ac_cv_sweep" != "yes" ; then echo "Sophos 'sweep' NO (Disabled by configure command)" else echo "Sophos 'sweep' NO (not found)" fi else echo "Sophos 'sweep' YES - $SOPHOS" GOT_VIRUS_SCANNER=1 fi if test "$SAVSCAN" = "/bin/false" ; then if test "$ac_cv_savscan" != "yes" ; then echo "Sophos 'savscan' NO (Disabled by configure command)" else echo "Sophos 'savscan' NO (not found)" fi else echo "Sophos 'savscan' YES - $SAVSCAN" GOT_VIRUS_SCANNER=1 fi if test "$TREND" = "/bin/false" ; then if test "$ac_cv_trend" != "yes" ; then echo "TREND 'vscan' NO (Disabled by configure command)" else echo "TREND 'vscan' NO (not found)" fi else echo "TREND 'vscan' YES - $TREND" GOT_VIRUS_SCANNER=1 fi if test "$CLAMSCAN" = "/bin/false" ; then if test "$ac_cv_clamav" != "yes" ; then echo "CLAMSCAN 'clamav' NO (Disabled by configure command)" else echo "CLAMSCAN 'clamav' NO (not found)" fi else echo "CLAMSCAN 'clamav' YES - $CLAMSCAN" GOT_VIRUS_SCANNER=1 fi if test "$CLAMDSCAN" = "/bin/false" ; then if test "$ac_cv_clamav" != "yes" ; then echo "CLAMDSCAN 'clamav' NO (Disabled by configure command)" else echo "CLAMDSCAN 'clamav' NO (not found)" fi else echo "CLAMDSCAN 'clamav' YES - $CLAMDSCAN" GOT_VIRUS_SCANNER=1 fi if test "$AVP" = "/bin/false" ; then if test "$ac_cv_AvpLinux" != "yes" ; then echo "AVP 'AvpLinux' NO (Disabled by configure command)" else echo "AVP 'AvpLinux' NO (not found)" fi else echo "AVP 'AvpLinux' YES - $AVP" GOT_VIRUS_SCANNER=1 fi if test "$AVP5" = "/bin/false" ; then if test "$ac_cv_aveclient" != "yes" ; then echo "AVP5 'aveclient' NO (Disabled by configure command)" else echo "AVP5 'aveclient' NO (not found)" fi else echo "AVP5 'aveclient' YES - $AVP5" GOT_VIRUS_SCANNER=1 fi if test "$KAVSCANNER" = "/bin/false" ; then if test "$ac_cv_kavscanner" != "yes" ; then echo "KAVSCANNER 'kavscanner' NO (Disabled by configure command)" else echo "KAVSCANNER 'kavscanner' NO (not found)" fi else echo "KAVSCANNER 'kavscanner' YES - $KAVSCANNER" GOT_VIRUS_SCANNER=1 fi if test "$CSAV" = "/bin/false" ; then if test "$ac_cv_csav" != "yes" ; then echo "CSAV 'csav' NO (Disabled by configure command)" else echo "CSAV 'csav' NO (not found)" fi else echo "CSAV 'csav' YES - $CSAV" GOT_VIRUS_SCANNER=1 fi if test "$FSAV" = "/bin/false" ; then if test "$ac_cv_fsav" != "yes" ; then echo "FSAV 'fsav' NO (Disabled by configure command)" else echo "FSAV 'fsav' NO (not found)" fi else echo "FSAV 'fsav' YES - $FSAV" GOT_VIRUS_SCANNER=1 fi if test "$FPROT" = "/bin/false" ; then if test "$ac_cv_fprot" != "yes" ; then echo "FPROT 'f-prot' NO (Disabled by configure command)" else echo "FPROT 'f-prot' NO (not found)" fi else echo "FPROT 'f-prot' YES - $FPROT" GOT_VIRUS_SCANNER=1 fi if test "$FPSCAN" = "/bin/false" ; then if test "$ac_cv_fpscan" != "yes" ; then echo "FPSCAN 'fpscan' NO (Disabled by configure command)" else echo "FPSCAN 'fpscan' NO (not found)" fi else echo "FPSCAN 'fpscan' YES - $FPSCAN" GOT_VIRUS_SCANNER=1 fi if test "$SOPHIE" = "/bin/false" ; then if test "$ac_cv_sophie" != "yes" ; then echo "SOPHIE 'sophie' NO (Disabled by configure command)" else echo "SOPHIE 'sophie' NO (not found)" fi else echo "SOPHIE 'sophie' YES - $SOPHIE" GOT_VIRUS_SCANNER=1 fi if test "$NVCC" = "/bin/false" ; then if test "$ac_cv_nvcc" != "yes" ; then echo "NVCC 'nvcc' NO (Disabled by configure command)" else echo "NVCC 'nvcc' NO (not found)" fi else echo "NVCC 'nvcc' YES - $NVCC" GOT_VIRUS_SCANNER=1 fi if test "$CLAMD" = "/bin/false" ; then if test "$ac_cv_clamd" != "yes" ; then echo "CLAMD 'clamd' NO (Disabled by configure command)" else echo "CLAMD 'clamd' NO (not found)" fi else echo "CLAMD 'clamd' YES - $CLAMD" GOT_VIRUS_SCANNER=1 fi if test "$TROPHIE" = "/bin/false" ; then if test "$ac_cv_trophie" != "yes" ; then echo "TROPHIE 'trophie' NO (Disabled by configure command)" else echo "TROPHIE 'trophie' NO (not found)" fi else echo "TROPHIE 'trophie' YES - $TROPHIE" GOT_VIRUS_SCANNER=1 fi if test "$NOD32" = "/bin/false" ; then if test "$ac_cv_nod32" != "yes" ; then echo "NOD32 'nod32cli' NO (Disabled by configure command)" else echo "NOD32 'nod32cli' NO (not found)" fi else echo "NOD32 'nod32cli' YES - $NOD32" GOT_VIRUS_SCANNER=1 fi fi if test "$GOT_VIRUS_SCANNER" = "0" ; then echo "" echo "Could not find any recognized virus scanner... do not use" echo "any of the contains_virus functions in your filter." fi if test "$CLAMD" != "/bin/false" -o "$SOPHIE" != "/bin/false" -o "$TROPHIE" != "/bin/false" ; then echo "" if test "$CLAMD" != "/bin/false" ; then echo "Make sure clamd runs as the $DEFANGUSER user!" fi if test "$SOPHIE" != "/bin/false" ; then echo "See README.SOPHIE for more information about running Sophie." fi if test "$TROPHIE" != "/bin/false" ; then echo "Make sure Trophie runs as the $DEFANGUSER user!" fi echo "" fi if test "$ac_cv_perlmodcheck" = "yes" ; then if test "$HAVE_SPAM_ASSASSIN" = "yes" ; then echo "Found Mail::SpamAssassin. You may use spam_assassin_* functions" else echo "Did not find Mail::SpamAssassin. Do not use spam_assassin_* functions" fi echo "" if test "$HAVE_HTML_PARSER" = "yes" ; then echo "Found HTML::Parser. You may use append_html_boilerplate()" else echo "Did not find HTML::Parser. Do not use append_html_boilerplate()" fi fi if test "$HAVE_IO_SOCKET_SSL" = "no" ; then echo "" echo "Could not find IO::Socket::SSL Perl module... do not use" echo "md_check_against_smtp_server function in your filter." echo "" fi if test "$HAVE_LWP" = "no" ; then if test "$HAVE_JSON" = "no" ; then echo "" echo "Could not find LWP::UserAgent and JSON Perl modules..." echo "it's not possible to check your emails with rspamd(8) " echo "using rspamd_check function in your filter." echo "" fi fi if test "$HAVE_SPF" = "no" ; then echo "" echo "Could not find Mail::SPF Perl module... do not use" echo "md_authres function in your filter." echo "" fi if test "$HAVE_DKIM" = "no" ; then echo "" echo "Could not find Mail::DKIM Perl module... do not use" echo "md_dkim_sign, md_dkim_verify or md_authres functions in your filter." echo "" fi if test "$HAVE_DIGEST_MD5" = "no" ; then echo "" echo "Could not find Digest::MD5 Perl module... do not use" echo "email_is_blacklisted function in your filter." echo "" fi if test "$HAVE_NET_SMTP" = "no" ; then echo "" echo "Could not find Net::SMTP Perl module... you will not be able" echo "to release quarantined messages using mimedefang-release(8)" echo "in smtp mode." echo "" fi echo "Note: SpamAssassin and HTML::Parser are" echo "detected at run-time, so if you install or remove any of those modules, you" echo "do not need to re-run ./configure and make a new mimedefang.pl." if test "$ac_cv_perlmodcheck" = "yes" ; then if test "$mtversion" = "unknown" ; then echo "" echo "Could not determine version of MIME::Tools. Please use 5.412 or later." 1>&6 elif test $mtversion -lt 54120 ; then echo "" 1>&6 echo "**** WARNING: We strongly recommend that you use MIME::Tools" 1>&6 echo "**** version 5.412 or later instead of version $mt_actual_version." 1>&6 fi fi if test "$DEFANGUSER" != "" ; then id $DEFANGUSER > /dev/null 2>&1 if test "$?" != 0 ; then echo "" echo "Note: The MIMEDefang user '$DEFANGUSER' does not seem to" echo "exist. Please create this user before continuing." fi fi if test "$ENABLE_DEBUGGING" != "" ; then echo "" echo "*** WARNING: You have configured with --enable-debugging." echo "*** This will produce LARGE VOLUMES of syslog messages and" echo "*** is NOT RECOMMENDED for a production system." fi mimedefang-3.6/configure.ac000066400000000000000000001127621475763067200157760ustar00rootroot00000000000000dnl $Id$ dnl Process this file with autoconf to produce a configure script. AC_INIT AC_CONFIG_SRCDIR([mimedefang.c]) dnl UGLY hack... set the --sysconfdir to /etc UNLESS it was explicitly dnl supplied on the command line. Autoconf has no other nice way to set dnl the default to other than ${prefix}/etc echo $* | fgrep -e '--sysconfdir' > /dev/null 2>&1 || sysconfdir='/etc' AC_PROG_CC dnl Find C compiler AC_CHECK_PROGS(AR, ar, no) AC_CONFIG_HEADERS([config.h]) dnl Add some common directories to PATH in case they are not there PATH=$PATH:/usr/local/bin AC_PROG_INSTALL AC_TYPE_LONG_LONG_INT AC_ARG_ENABLE(embedded-perl, [ --disable-embedded-perl Disable embedded Perl interpreter], ac_cv_embedded_perl=$enableval, ac_cv_embedded_perl=yes) AC_ARG_ENABLE(poll, [ --enable-poll Use poll(2) instead of select(2) in multiplexor], ac_cv_use_poll=$enableval, ac_cv_use_poll=no) AC_PATH_PROG(PERL, perl) dnl Check for socklen_t type AC_MSG_CHECKING(whether socklen_t is defined) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[socklen_t x;]])],[ac_have_socklen_t=yes],[ac_have_socklen_t=no]) AC_MSG_RESULT($ac_have_socklen_t) if test "$ac_have_socklen_t" = "yes" ; then AC_DEFINE(HAVE_SOCKLEN_T,[],["Whether we have the variable type socklen_t"]) fi dnl Check if clock_gettime can use CLOCK_MONOTONIC AC_MSG_CHECKING(whether clock_gettime can use CLOCK_MONOTONIC) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[struct timespec s; clock_gettime(CLOCK_MONOTONIC, &s);]])],[ac_have_clock_monotonic=yes],[ac_have_clock_monotonic=no]) AC_MSG_RESULT($ac_have_clock_monotonic) if test "$ac_have_clock_monotonic" = "yes" ; then AC_DEFINE(HAVE_CLOCK_MONOTONIC,[],["Whether clock_gettime can use CLOCK_MONOTONIC"]) else OLDLIBS="$LIBS" LIBS="$LIBS -lrt" fi AC_MSG_CHECKING(whether fPIC compiler option is accepted) OLD_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -fPIC -Werror" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[return 0;]])], [AC_MSG_RESULT(yes) CFLAGS="$OLD_CFLAGS -fPIC"], [AC_MSG_RESULT(no) CFLAGS="$OLD_CFLAGS"]) if test -z "$PERL" ; then AC_MSG_ERROR([*** Cannot continue without Perl. Sorry.]) exit 1 fi if test -f "${PERL}" ; then perl_version=`${PERL} -e "print $]"` long_perl_version=`${PERL} -e "print ($] * 1000000)"` AC_MSG_CHECKING([Perl version]) AC_MSG_RESULT(${perl_version}) if test ${long_perl_version} -lt 5010000 ; then AC_MSG_ERROR([At least Perl 5.10 is required]) fi fi for thing in prefix siteprefix vendorprefix vendorlib installarchlib installprivlib installbin installman1dir installman3dir installscript installsitearch installsitelib; do AC_MSG_CHECKING([for Perl installation variable $thing]) val=`$PERL -V:$thing | sed -e "s/^.*='//" -e "s/';$//"` AC_MSG_RESULT($val) dnl Stupid m4.... up=`echo $thing | tr '[[a-z]]' '[[A-Z]]'` eval "PERL$up=$val" done dnl wait3 dnl We know that wait3 works fine on Solaris 9, but autoconf dnl sometimes misses it un=`uname -s -r` if test "$un" = "SunOS 5.9" ; then ac_cv_func_wait3_rusage=yes fi AC_FUNC_WAIT3 AC_PATH_PROG(NM, nm) dnl Allow user to tell us where Sendmail is (or will be) SENDMAILPROG=no AC_ARG_WITH(sendmail, [ --with-sendmail=PATH specify location of Sendmail binary], SENDMAILPROG=$with_sendmail, SENDMAILPROG=no) DEFANGUSER="" AC_ARG_WITH(user, [ --with-user=LOGIN use LOGIN as the MIMEDefang user], DEFANGUSER=$with_user, DEFANGUSER=defang) AC_SUBST(DEFANGUSER) IP_HEADER=no AC_ARG_WITH(ipheader, [ --with-ipheader install /etc/mail/mimedefang-ip-key], IP_HEADER=$with_ipheader, IP_HEADER=no) AC_SUBST(IP_HEADER) dnl Allow user to tell us where milter includes are MILTERINC= AC_ARG_WITH(milterinc, [ --with-milterinc=PATH specify alternative location of milter includes], MILTERINC=$with_milterinc, MILTERINC=) dnl Allow user to tell us where milter libraries are MILTERLIB= AC_ARG_WITH(milterlib, [ --with-milterlib=PATH specify alternative location of milter libraries], MILTERLIB=$with_milterlib, MILTERLIB=) dnl Allow specification of sysconfig subdir AC_ARG_WITH(confsubdir, [ --with-confsubdir=DIR specify configuration subdirectory (mail) ], CONFSUBDIR="/$with_confsubdir", CONFSUBDIR=/mail) if test "$CONFSUBDIR" = "/" -o "$CONFSUBDIR" = "//" ; then CONFSUBDIR="" fi AC_SUBST(CONFSUBDIR) dnl Allow specification of spool dir AC_ARG_WITH(spooldir, [ --with-spooldir=DIR specify location of spool directory (/var/spool/MIMEDefang)], SPOOLDIR=$with_spooldir, SPOOLDIR=/var/spool/MIMEDefang) dnl Allow specification of quarantine dir AC_ARG_WITH(quarantinedir, [ --with-quarantinedir=DIR specify location of quarantine directory (/var/spool/MD-Quarantine)], QDIR=$with_quarantinedir, QDIR=/var/spool/MD-Quarantine) AC_ARG_ENABLE(pthread-flag, [ --enable-pthread-flag Supply the -pthread flag to the C compiler], FORCE_PTHREAD_FLAG=-pthread, FORCE_PTHREAD_FLAG="") AC_ARG_ENABLE(check-perl-modules, [ --disable-check-perl-modules Disable compile-time checks for Perl modules], ac_cv_perlmodcheck=$enableval, ac_cv_perlmodcheck=yes) dnl Check for Perl modules if test "$ac_cv_perlmodcheck" = "no" ; then AC_MSG_RESULT([Compile-time checking for Perl modules disabled]) dnl for installation of spamassassin.cf HAVE_SPAM_ASSASSIN=yes else for module in 'IO::Socket' 'MIME::Tools 5.410 ()' 'MIME::WordDecoder' 'Digest::SHA' 'FindBin' ; do AC_MSG_CHECKING([for Perl module $module]) (echo "use lib '$PERLINSTALLSITELIB'; use $module;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then AC_MSG_WARN([*** Error trying to use Perl module $module]) AC_MSG_WARN([*** Make sure the following Perl modules are installed:]) AC_MSG_WARN([*** MIME::Tools version 5.410 or higher (5.411a recommended)]) AC_MSG_WARN([*** MIME::WordDecoder]) AC_MSG_WARN([*** Digest::SHA]) AC_MSG_WARN([*** FindBin]) exit 1 fi AC_MSG_RESULT(ok) done dnl Check for *optional* Perl modules AC_MSG_CHECKING([for Perl module Mail::SpamAssassin 3.0 or better]) (echo "use Mail::SpamAssassin 3.0 ();" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_SPAM_ASSASSIN=no else HAVE_SPAM_ASSASSIN=yes fi AC_MSG_RESULT($HAVE_SPAM_ASSASSIN) AC_MSG_CHECKING([for Perl module HTML::Parser]) (echo "use HTML::Parser;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_HTML_PARSER=no else HAVE_HTML_PARSER=yes fi AC_MSG_RESULT($HAVE_HTML_PARSER) AC_MSG_CHECKING([for Perl module Archive::Zip]) (echo "use Archive::Zip;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_ARCHIVE_ZIP=no else HAVE_ARCHIVE_ZIP=yes fi AC_MSG_RESULT($HAVE_ARCHIVE_ZIP) AC_MSG_CHECKING([for Perl module IO::Socket::SSL]) (echo "use IO::Socket::SSL;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_IO_SOCKET_SSL=no else HAVE_IO_SOCKET_SSL=yes fi AC_MSG_RESULT($HAVE_IO_SOCKET_SSL) AC_MSG_CHECKING([for Perl module JSON]) (echo "use JSON;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_JSON=no else HAVE_JSON=yes fi AC_MSG_RESULT($HAVE_JSON) AC_MSG_CHECKING([for Perl module LWP::UserAgent]) (echo "use LWP::UserAgent;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_LWP=no else HAVE_LWP=yes fi AC_MSG_RESULT($HAVE_LWP) AC_MSG_CHECKING([for Perl module Mail::SPF]) (echo "use Mail::SPF;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_SPF=no else HAVE_SPF=yes fi AC_MSG_RESULT($HAVE_SPF) AC_MSG_CHECKING([for Perl module Mail::DKIM]) (echo "use Mail::DKIM;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_DKIM=no else HAVE_DKIM=yes fi AC_MSG_RESULT($HAVE_DKIM) AC_MSG_CHECKING([for Perl module Digest::MD5]) (echo "use Digest::MD5;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_DIGEST_MD5=no else HAVE_DIGEST_MD5=yes fi AC_MSG_RESULT($HAVE_DIGEST_MD5) AC_MSG_CHECKING([for Perl module Net::SMTP]) (echo "use Net::SMTP;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? != 0 ; then HAVE_NET_SMTP=no else HAVE_NET_SMTP=yes fi AC_MSG_RESULT($HAVE_NET_SMTP) dnl Check MIME::Tools version dirs=`echo 'print "@INC\n";' | $PERL` foundit="" for i in $dirs ; do if test -r "$i/MIME/Field/ParamVal.pm" ; then foundit="$i/MIME/Field/ParamVal.pm" break fi done if test "$foundit" != "" ; then AC_MSG_CHECKING([MIME-Tools version]) mtversion=`echo '$x = $MIME::Tools::VERSION; $y = int($x) * 10000; $z = ($x - int($x)) * 10000; print $y + $z;' | $PERL -I$PERLINSTALLSITELIB -MMIME::Tools 2>/dev/null` mt_actual_version=`echo 'print "$MIME::Tools::VERSION";' | $PERL -I$PERLINSTALLSITELIB -MMIME::Tools 2>/dev/null` if test "$mtversion" = "" ; then mtversion="unknown" mt_actual_version="unknown" fi AC_MSG_RESULT($mt_actual_version) else mtversion="unknown" fi fi dnl We need at least *one* of Sys::Syslog or Unix::Syslog HAVE_A_SYSLOG=0 for module in 'Sys::Syslog' 'Unix::Syslog' ; do AC_MSG_CHECKING([for Perl module $module]) (echo "use $module;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? = 0 ; then HAVE_A_SYSLOG=1 AC_MSG_RESULT(ok) else AC_MSG_RESULT(no) fi done if test $HAVE_A_SYSLOG = 0 ; then if test "$ac_cv_perlmodcheck" = "yes" ; then AC_MSG_WARN([*** Error: Could not find Sys::Syslog or Unix::Syslog]) AC_MSG_WARN([*** One of those Perl modules is required.]); exit 1 fi fi dnl Check for ExtUtils::Embed if test "$ac_cv_embedded_perl" = "no" ; then echo "Check for embedded perl disabled by --disable-embedded-perl option" HAVE_EXTUTILS_EMBED=no else AC_MSG_CHECKING([for Perl module ExtUtils::Embed]) (echo "use ExtUtils::Embed;" ; echo "exit(0);") | $PERL > /dev/null 2>&1 if test $? = 0 ; then HAVE_EXTUTILS_EMBED=yes AC_MSG_RESULT(ok) else HAVE_EXTUTILS_EMBED=no AC_MSG_RESULT(no) fi fi AC_SUBST(HAVE_SPAM_ASSASSIN) AC_CHECK_HEADERS(getopt.h unistd.h stdint.h poll.h stdint.h) dnl Check if stdint.h defines uint32_t AC_MSG_CHECKING(whether stdint.h defines uint32_t) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[uint32_t foo;]])],[ac_uint32_t_defined=yes],[ac_uint32_t_defined=no]) AC_MSG_RESULT($ac_uint32_t_defined) if test "$ac_uint32_t_defined" = "yes" ; then AC_DEFINE(HAVE_UINT32_T,[],["whether uint32_t is defined"]) fi dnl Check if sys/types.h defines uint32_t AC_MSG_CHECKING(whether sys/types.h defines uint32_t) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[uint32_t foo;]])],[ac_uint32_t_defined=yes],[ac_uint32_t_defined=no]) AC_MSG_RESULT($ac_uint32_t_defined) if test "$ac_uint32_t_defined" = "yes" ; then AC_DEFINE(HAVE_UINT32_T,[],["whether uint32_t is defined"]) fi dnl sig_atomic_t AC_MSG_CHECKING(whether sig_atomic_t is defined) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include ]], [[sig_atomic_t foo;]])],[ac_have_sig_atomic_t=yes],[ac_have_sig_atomic_t=no]) AC_MSG_RESULT($ac_have_sig_atomic_t) if test "$ac_have_sig_atomic_t" = "yes" ; then AC_DEFINE(HAVE_SIG_ATOMIC_T,[],["Whether we have the atomic_t variable type "]) fi dnl Check if compiler allows "-pthread" option, but only if dnl we are using GCC if test "$FORCE_PTHREAD_FLAG" = "-pthread" ; then AC_MSG_RESULT(Forcing use of -pthread C compiler flag) PTHREAD_FLAG=-pthread elif test "$GCC" = yes ; then AC_MSG_CHECKING([whether ${CC-cc} accepts -pthread]) echo 'void f(){}' > conftest.c if test -z "`${CC-cc} -pthread -c conftest.c 2>&1`"; then ac_cv_prog_cc_pthread=yes PTHREAD_FLAG="-pthread" else PTHREAD_FLAG="" ac_cv_prog_cc_pthread=no fi AC_MSG_RESULT($ac_cv_prog_cc_pthread) rm -f conftest* else PTHREAD_FLAG="" fi if test "$HAVE_EXTUTILS_EMBED" = "yes" ; then AC_MSG_CHECKING([if we can embed a Perl interpreter in C]) OLDCFLAGS="$CFLAGS" OLDLDFLAGS="$LDFLAGS" OLDLIBS="$LIBS" LIBS="-lperl $LIBS" EMBPERLLDFLAGS="`$PERL -MExtUtils::Embed -e ldopts`" EMBPERLCFLAGS="`$PERL -MExtUtils::Embed -e ccopts`" LDFLAGS="$EMBPERLLDFLAGS $LDFLAGS" CFLAGS="$EMBPERLCFLAGS $CFLAGS" AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include #include #include static PerlInterpreter *my_perl; int main(int argc, char **argv, char **env) { my_perl = perl_alloc(); if (!my_perl) exit(1); exit(0); } ]])],[EMBED_PERL=yes],[EMBED_PERL=no],[]) LIBS="$OLDLIBS" CFLAGS="$OLDCFLAGS" LDFLAGS="$OLDLDFLAGS" AC_MSG_RESULT($EMBED_PERL) else EMBED_PERL=no fi if test "$EMBED_PERL" = "no" ; then EMBPERLCFLAGS="" EMBPERLLDFLAGS="" EMBPERLLIBS="" EMBPERLDEFS="" EMBPERLOBJS="" else EMBPERLLIBS="-lperl" EMBPERLDEFS="-DEMBED_PERL" EMBPERLOBJS="embperl.o xs_init.o" # Check for buggy perl interpreter AC_MSG_CHECKING([if it is safe to destroy and recreate a Perl interpreter]) LIBS="-lperl $LIBS" LDFLAGS="$EMBPERLLDFLAGS $LDFLAGS" CFLAGS="$EMBPERLCFLAGS $CFLAGS $PTHREAD_FLAG" AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include #include static PerlInterpreter *my_perl = NULL; static char **argv = NULL; int make_embedded_interpreter(char **env) { int argc; if (!argv) { argv = (char **) malloc(6 * sizeof(char *)); } if (my_perl != NULL) { perl_destruct(my_perl); perl_free(my_perl); my_perl = NULL; } argv[0] = ""; argv[1] = "-e"; argv[2] = "print(\"\");"; argv[3] = NULL; argc = 3; #ifdef PERL_SYS_INIT3 PERL_SYS_INIT3(&argc, &argv, &env); #endif my_perl = perl_alloc(); if (!my_perl) { return -1; } PERL_SET_CONTEXT(my_perl); PL_perl_destruct_level = 1; perl_construct(my_perl); PL_perl_destruct_level = 1; argv[0] = ""; argv[1] = "-e"; argv[2] = "print(\"\");"; argv[3] = NULL; argc = 3; perl_parse(my_perl, NULL, argc, argv, NULL); perl_run(my_perl); return 0; } int main(int argc, char **argv, char **env) { make_embedded_interpreter(env); make_embedded_interpreter(env); return 0; } ]])],[SAFE_EMBED_PERL=yes],[SAFE_EMBED_PERL=no],[]) LIBS="$OLDLIBS" CFLAGS="$OLDCFLAGS" LDFLAGS="$OLDLDFLAGS" AC_MSG_RESULT($SAFE_EMBED_PERL) if test "$SAFE_EMBED_PERL" = "yes" ; then EMBPERLDEFS="$EMBPERLDEFS -DSAFE_EMBED_PERL" fi fi if test "$ac_cv_use_poll" = "no" ; then USEPOLL="" else if test "$ac_cv_header_poll_h" = "no" ; then USEPOLL="" AC_MSG_WARN([*** You used --enable-poll, but I cannot find the]) AC_MSG_WARN([*** poll.h header. Turning OFF --enable-poll]) else USEPOLL="-DEVENT_USE_POLL=1" fi fi AC_SUBST(EMBPERLCFLAGS) AC_SUBST(EMBPERLLDFLAGS) AC_SUBST(EMBPERLLIBS) AC_SUBST(EMBPERLOBJS) AC_SUBST(EMBPERLDEFS) AC_SUBST(USEPOLL) AC_CHECK_LIB(resolv, res_init) AC_CHECK_LIB(socket, htons) AC_CHECK_LIB(nsl, gethostbyname) LIBS_WITHOUT_PTHREAD="$LIBS" AC_CHECK_LIB(pthread, pthread_once) dnl Do we have initgroups? AC_CHECK_FUNCS(initgroups) dnl How about getpwnam_r? AC_CHECK_FUNCS(getpwnam_r) AC_CHECK_FUNCS(setrlimit) AC_CHECK_FUNCS(snprintf) AC_CHECK_FUNCS(vsnprintf) AC_CHECK_FUNCS(readdir_r) AC_CHECK_FUNCS(pathconf) AC_CHECK_FUNCS(inet_ntop) if test "$SPOOLDIR" = "no" -o "$SPOOLDIR" = "" ; then SPOOLDIR=/var/spool/MIMEDefang fi if test "$QDIR" = "no" -o "$QDIR" = "" ; then QDIR=/var/spool/MIMEDefang fi AC_SUBST(SPOOLDIR) AC_SUBST(QDIR) dnl debugging AC_ARG_ENABLE(debugging, [ --enable-debugging Add debugging messages to syslog], ac_cv_debugging=$enableval, ac_cv_debugging=no) dnl Check for virus scanners AC_ARG_ENABLE(anti-virus, [ --disable-anti-virus Do not look for anti-virus scanners], ac_cv_antivirus=$enableval, ac_cv_antivirus=yes) AC_ARG_ENABLE(antivir, [ --disable-antivir Do not include support for H+BEDV antivir], ac_cv_antivir=$enableval, ac_cv_antivir=yes) AC_ARG_ENABLE(vexira, [ --disable-vexira Do not include support for Central Command Vexira], ac_cv_vexira=$enableval, ac_cv_vexira=yes) AC_ARG_ENABLE(uvscan, [ --disable-uvscan Do not include support for NAI uvscan], ac_cv_uvscan=$enableval, ac_cv_uvscan=yes) AC_ARG_ENABLE(bdc, [ --disable-bdc Do not include support for Bitdefender bdc], ac_cv_bdc=$enableval, ac_cv_bdc=yes) AC_ARG_ENABLE(sweep, [ --disable-sweep Do not include support for Sophos sweep], ac_cv_sweep=$enableval, ac_cv_sweep=yes) AC_ARG_ENABLE(savscan, [ --disable-savscan Do not include support for Sophos savscan], ac_cv_savscan=$enableval, ac_cv_savscan=yes) AC_ARG_ENABLE(trend, [ --disable-trend Do not include support for Trend Filescanner/Interscan], ac_cv_trend=$enableval, ac_cv_trend=yes) AC_ARG_ENABLE(AvpLinux, [ --disable-AvpLinux Do not include support for AVP AvpLinux], ac_cv_AvpLinux=$enableval, ac_cv_AvpLinux=yes) AC_ARG_ENABLE(kavscanner, [ --disable-kavscanner Do not include support for Kaspersky kavscanner], ac_cv_kavscanner=$enableval, ac_cv_kavscanner=yes) AC_ARG_ENABLE(aveclient, [ --disable-aveclient Do not include support for AVP5 aveclient], ac_cv_aveclient=$enableval, ac_cv_aveclient=yes) AC_ARG_ENABLE(clamav, [ --disable-clamav Do not include support for clamav], ac_cv_clamav=$enableval, ac_cv_clamav=yes) AC_ARG_ENABLE(fsav, [ --disable-fsav Do not include support for F-Secure Anti-Virus], ac_cv_fsav=$enableval, ac_cv_fsav=yes) AC_ARG_ENABLE(csav, [ --disable-csav Do not include support for Command Software CSAV], ac_cv_csav=$enableval, ac_cv_csav=yes) AC_ARG_ENABLE(fprot, [ --disable-fprot Do not include support for F-prot Anti-Virus], ac_cv_fprot=$enableval, ac_cv_fprot=yes) AC_ARG_ENABLE(fpscan, [ --disable-fpscan Do not include support for F-prot Anti-Virus v6], ac_cv_fpscan=$enableval, ac_cv_fpscan=yes) AC_ARG_ENABLE(sophie, [ --disable-sophie Do not include support for Sophie], ac_cv_sophie=$enableval, ac_cv_sophie=yes) AC_ARG_ENABLE(nvcc, [ --disable-nvcc Do not include support for Nvcc], ac_cv_nvcc=$enableval, ac_cv_nvcc=yes) AC_ARG_ENABLE(clamd, [ --disable-clamd Do not include support for clamd], ac_cv_clamd=$enableval, ac_cv_clamd=yes) AC_ARG_ENABLE(trophie, [ --disable-trophie Do not include support for Trophie], ac_cv_trophie=$enableval, ac_cv_trophie=yes) AC_ARG_ENABLE(nod32, [ --disable-nod32 Do not include support for Eset NOD32], ac_cv_nod32=$enableval, ac_cv_nod32=yes) AC_ARG_ENABLE(rspamc, [ --disable-rspamc Do not include support for Rspamd], ac_cv_rspamc=$enableval, ac_cv_rspamc=yes) ANTIVIR_PATH="$PATH:/usr/lib/AntiVir:/usr/local/uvscan:/opt/AVP:/etc/iscan:/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bd7:/usr/local/bd7/bin:/opt/kav/bin:/opt/kav/5.5/kav4unix/bin/:/opt/eset/nod32/bin" if test "$ac_cv_antivirus" = "yes"; then if test "$ac_cv_antivir" = yes; then AC_PATH_PROG(HBEDV, antivir, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_vexira" = yes; then AC_PATH_PROG(VEXIRA, vascan, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_uvscan" = yes; then AC_PATH_PROG(NAI, uvscan, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_bdc" = yes; then AC_PATH_PROG(BDC, bdc, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_sweep" = yes; then AC_PATH_PROG(SOPHOS, sweep, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_savscan" = yes; then AC_PATH_PROG(SAVSCAN, savscan, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_trend" = yes; then AC_PATH_PROG(TREND, vscan, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_kavscanner" = yes ; then AC_PATH_PROG(KAVSCANNER, kavscanner, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_clamav" = yes; then AC_PATH_PROG(CLAMSCAN, clamscan, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_clamav" = yes; then AC_PATH_PROG(CLAMDSCAN, clamdscan, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_AvpLinux" = yes; then AC_PATH_PROG(AVP, AvpLinux, /bin/false, $ANTIVIR_PATH) AC_PATH_PROG(AVP_KAVDAEMON, kavdaemon, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_aveclient" = yes; then AC_PATH_PROG(AVP5, aveclient, /bin/false, $ANTIVIR_PATH) fi # Update AVP for best scanner: kavdaemon preferred over AvpLinux if test "$AVP_KAVDAEMON" != "/bin/false" ; then AVP="$AVP_KAVDAEMON" fi if test "$ac_cv_csav" = yes ; then AC_PATH_PROG(CSAV, csav, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_fsav" = yes; then AC_PATH_PROG(FSAV, fsav, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_fprot" = yes; then AC_PATH_PROG(FPROT, f-prot, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_fpscan" = yes; then AC_PATH_PROG(FPSCAN, fpscan, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_sophie" = yes; then AC_PATH_PROG(SOPHIE, sophie, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_nvcc" = yes; then AC_PATH_PROG(NVCC, nvcc, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_clamd" = yes; then AC_PATH_PROG(CLAMD, clamd, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_trophie" = yes; then AC_PATH_PROG(TROPHIE, trophie, /bin/false, $ANTIVIR_PATH) fi if test "$ac_cv_nod32" = yes; then AC_PATH_PROG(NOD32, nod32cli, /bin/false, $ANTIVIR_PATH) fi fi if test "$ac_cv_rspamc" = yes; then AC_PATH_PROG(RSPAMC, rspamc, /bin/false, $PATH) fi test -z "$HBEDV" && HBEDV=/bin/false test -z "$VEXIRA" && VEXIRA=/bin/false test -z "$NAI" && NAI=/bin/false test -z "$BDC" && BDC=/bin/false test -z "$SOPHOS" && SOPHOS=/bin/false test -z "$SAVSCAN" && SAVSCAN=/bin/false test -z "$TREND" && TREND=/bin/false test -z "$CLAMSCAN" && CLAMSCAN=/bin/false test -z "$CLAMDSCAN" && CLAMDSCAN=/bin/false test -z "$AVP" && AVP=/bin/false test -z "$AVP5" && AVP5=/bin/false test -z "$AVP_KAVDAEMON" && AVP_KAVDAEMON=/bin/false test -z "$KAVSCANNER" && KAVSCANNER=/bin/false test -z "$CSAV" && CSAV=/bin/false test -z "$FSAV" && FSAV=/bin/false test -z "$FPROT" && FPROT=/bin/false test -z "$FPSCAN" && FPSCAN=/bin/false test -z "$SOPHIE" && SOPHIE=/bin/false test -z "$NVCC" && NVCC=/bin/false test -z "$CLAMD" && CLAMD=/bin/false test -z "$TROPHIE" && TROPHIE=/bin/false test -z "$NOD32" && NOD32=/bin/false test -z "$RSPAMC" && RSPAMC=/bin/false if test "$ac_cv_debugging" = yes ; then ENABLE_DEBUGGING=-DENABLE_DEBUGGING else ENABLE_DEBUGGING= fi dnl find Sendmail if test "$SENDMAILPROG" = "no" ; then AC_PATH_PROG(SENDMAILPROG, sendmail, no, $PATH:/sbin:/usr/sbin:/usr/lib:/usr/libexec) fi AC_DEFUN([MD_MILTER_SFIO],[ AC_MSG_CHECKING([whether libmilter requires -lsfio]) RESULT=`$NM $LIBMILTER 2>/dev/null | grep sfsprintf; $NM -D $LIBMILTERSO 2>/dev/null | grep sfsprintf` if test -z "$RESULT" ; then AC_MSG_RESULT(no) else AC_MSG_RESULT(yes) LIBS="$LIBS -lsfio" fi ]) AC_DEFUN([MD_SM_LDAP],[ AC_MSG_CHECKING([whether libsm requires -lldap]) RESULT=`$NM $LIBSM | grep ldap_` if test -z "$RESULT" ; then AC_MSG_RESULT(no) else AC_MSG_RESULT(yes) LIBS="$LIBS -lldap -llber" fi ]) AC_DEFUN([MD_MILTER_LDAP],[ AC_MSG_CHECKING([whether libmilter requires -lldap]) RESULT=`$NM $LIBMILTER 2>/dev/null | grep ldap_; $NM -D $LIBMILTERSO 2>/dev/null | grep ldap_` if test -z "$RESULT" ; then AC_MSG_RESULT(no) else AC_MSG_RESULT(yes) LIBS="$LIBS -lldap -llber" fi ]) AC_DEFUN([MD_MILTER_SM],[ OLD_LIBS="$LIBS" LIBS="$LIBS -lmilter" AC_MSG_CHECKING([whether libmilter requires -lsm]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include "libmilter/mfapi.h" #include ]], [[extern size_t sm_strlcpy(char *, char const *, ssize_t); char foo[10]; sm_strlcpy(foo, "", 0);]])],[NEED_LIBSM=no],[NEED_LIBSM=yes]) AC_MSG_RESULT($NEED_LIBSM) LIBS="$OLD_LIBS" if test "$NEED_LIBSM" = yes ; then LIBS="$LIBS -lsm" MD_SM_LDAP else MD_MILTER_LDAP fi ]) if test "$SENDMAILPROG" = "no" ; then AC_MSG_WARN([Oops.. I couldn't find the 'sendmail' program. Please install it.]) PROBLEM=1 fi if test "$PROBLEM" = 1 ; then exit 1 fi dnl GCC warning level if test "$GCC" = yes; then if test "`uname -s`" = Linux; then CFLAGS="$CFLAGS -Wall -Wstrict-prototypes" fi fi AC_SUBST(ENABLE_DEBUGGING) AC_SUBST(PTHREAD_FLAG) VERSION=`$PERL -I modules/lib -e 'use Mail::MIMEDefang; print Mail::MIMEDefang->md_version'` AC_SUBST(VERSION) dnl Jigger for machines without snprintf if test "$ac_cv_func_snprintf" != "yes" -o "$ac_cv_func_vsnprintf" != "yes" ; then AC_MSG_RESULT([Your OS lacks snprintf or vsnprintf. Sorry!]) exit 1 fi dnl Find sendmail include file. The nasty path is in case you're building dnl Sendmail at the same level as MIMEDefang... we want to use that include dnl file... SMMILTER=`echo ../sendmail-*/include` as_test_x='test -e' as_executable_p='test -e' # Redefine so AC_PATH_PROG works as_fn_executable_p() { test -e "$1" } AC_PATH_PROG(MINCLUDE, libmilter/mfapi.h, no, $MILTERINC:$SMMILTER:/usr/include:/usr/local/include:/usr/local/include/sendmail:/opt/local/include) dnl Fix up the include stuff MINCLUDE=`dirname "$MINCLUDE"` MINCLUDE=`dirname "$MINCLUDE"` dnl If MINCLUDE is "/usr/include", do NOT add to include path, because dnl this messes up compilation with gcc on Solaris. if test "$MINCLUDE" = "/usr/include" ; then MINCLUDE="" else MINCLUDE="-I${MINCLUDE}" fi dnl find libmilter.a and libsm.a SMPATH=`echo ../sendmail-*/obj.*/libmilter` MDLIBPATH=/usr/local/lib:/usr/local/lib64:/usr/lib:/usr/lib64:/lib:/lib64:/usr/lib/libmilter:/usr/lib64/libmilter AC_PATH_PROG(LIBMILTER, libmilter.a, no, $MILTERLIB:$SMPATH:$MDLIBPATH) SMPATH=`echo ../sendmail-*/obj.*/libsm` AC_PATH_PROG(LIBSM, libsm.a, no, $SMPATH:$MDLIBPATH) dnl find libmilter.so in case we have shared libraries AC_PATH_PROG(LIBMILTERSO, libmilter.so, no, $MILTERLIB:$SMPATH:$MDLIBPATH) dnl Sanity checks PROBLEM=0 if test "$LIBMILTER" = "no" -a "$LIBMILTERSO" = "no" ; then AC_MSG_WARN([Oops.. I couldn't find libmilter.a or libmilter.so. Please install Sendmail]) AC_MSG_WARN([and its libraries. You must run Build in the libmilter/ directory]) AC_MSG_WARN([to compile libmilter.]) PROBLEM=1 fi if test "$LIBMILTER" != "no" ; then LIBMILTERACTUAL="$LIBMILTER" else LIBMILTERACTUAL="$LIBMILTERSO" fi dnl The directory containing libmilter LIBMILTERDIR=`dirname "$LIBMILTERACTUAL"` dnl Don't include standard directory paths if test "$LIBMILTERDIR" = "/usr/lib" -o "$LIBMILTERDIR" = "/lib" -o "$LIBMILTERDIR" = "/usr/lib64" -o "$LIBMILTERDIR" = "/lib64" ; then LIBMILTERDIR="" else LDFLAGS="$LDFLAGS -L$LIBMILTERDIR" fi if test "$MINCLUDE" = "no" ; then AC_MSG_WARN([Oops.. I couldn't find libmilter/mfapi.h. Please install Sendmail 8.12]) AC_MSG_WARN([and its header files.]) PROBLEM=1 fi dnl Check if our libmilter is old and needs LIBSM. Also checks if it dnl needs libldap MD_MILTER_SM AC_SUBST(LIBS_WITHOUT_PTHREAD) dnl evaluated versions of conf dir CONFDIR_EVAL=`echo ${sysconfdir}${CONFSUBDIR}` AC_SUBST(CONFDIR_EVAL) AC_SUBST(LDFLAGS) AC_SUBST(PERLPREFIX) AC_SUBST(PERLSITEPREFIX) AC_SUBST(PERLVENDORPREFIX) AC_SUBST(PERLVENDORLIB) AC_SUBST(PERLINSTALLARCHLIB) AC_SUBST(PERLINSTALLPRIVLIB) AC_SUBST(PERLINSTALLBIN) AC_SUBST(PERLINSTALLMAN1DIR) AC_SUBST(PERLINSTALLMAN3DIR) AC_SUBST(PERLINSTALLSCRIPT) AC_SUBST(PERLINSTALLSITEARCH) AC_SUBST(PERLINSTALLSITELIB) AC_SUBST(PERLINSTALLDATA) AC_SUBST(PERLINSTALLSITEDATA) AC_SUBST(PERLINSTALLVENDORDATA) AC_SUBST(PERLINSTALLCONF) AC_SUBST(PERLINSTALLSITECONF) AC_SUBST(PERLINSTALLVENDORCONF) AC_CONFIG_FILES([Makefile mimedefang.pl mimedefang-release.pl script/mimedefang-util examples/init-script mimedefang-filter.5 mimedefang-multiplexor.8 mimedefang-protocol.7 mimedefang-notify.7 mimedefang.8 mimedefang.pl.8 md-mx-ctrl.8 watch-mimedefang redhat/mimedefang-init redhat/mimedefang-sysconfig]) AC_OUTPUT chmod a+x examples/init-script > /dev/null 2>&1 chmod a+x watch-mimedefang > /dev/null 2>&1 chmod a+x redhat/mimedefang-init > /dev/null 2>&1 chmod a+x script/mimedefang-util > /dev/null 2>&1 dnl Status info echo "" echo "*** Virus scanner detection results:" PROBLEM=0 GOT_VIRUS_SCANNER=0 if test "$ac_cv_antivirus" != "yes" ; then echo "All virus-scanner detection disabled by --disable-anti-virus" else if test "$HBEDV" = "/bin/false" ; then if test "$ac_cv_antivir" != "yes" ; then echo "H+BEDV 'antivir' NO (Disabled by configure command)" else echo "H+BEDV 'antivir' NO (not found)" fi else echo "H+BEDV 'antivir' YES - $HBEDV" GOT_VIRUS_SCANNER=1 fi if test "$VEXIRA" = "/bin/false" ; then if test "$ac_cv_vexira" != "yes" ; then echo "Vexira 'vascan' NO (Disabled by configure command)" else echo "Vexira 'vascan' NO (not found)" fi else echo "Vexira 'vascan' YES - $VEXIRA" GOT_VIRUS_SCANNER=1 fi if test "$NAI" = "/bin/false" ; then if test "$ac_cv_uvscan" != "yes" ; then echo "NAI 'uvscan' NO (Disabled by configure command)" else echo "NAI 'uvscan' NO (not found)" fi else echo "NAI 'uvscan' YES - $NAI" GOT_VIRUS_SCANNER=1 fi if test "$BDC" = "/bin/false" ; then if test "$ac_cv_bdc" != "yes" ; then echo "BDC 'bdc' NO (Disabled by configure command)" else echo "BDC 'bdc' NO (not found)" fi else echo "BDC 'bdc' YES - $BDC" GOT_VIRUS_SCANNER=1 fi if test "$SOPHOS" = "/bin/false" ; then if test "$ac_cv_sweep" != "yes" ; then echo "Sophos 'sweep' NO (Disabled by configure command)" else echo "Sophos 'sweep' NO (not found)" fi else echo "Sophos 'sweep' YES - $SOPHOS" GOT_VIRUS_SCANNER=1 fi if test "$SAVSCAN" = "/bin/false" ; then if test "$ac_cv_savscan" != "yes" ; then echo "Sophos 'savscan' NO (Disabled by configure command)" else echo "Sophos 'savscan' NO (not found)" fi else echo "Sophos 'savscan' YES - $SAVSCAN" GOT_VIRUS_SCANNER=1 fi if test "$TREND" = "/bin/false" ; then if test "$ac_cv_trend" != "yes" ; then echo "TREND 'vscan' NO (Disabled by configure command)" else echo "TREND 'vscan' NO (not found)" fi else echo "TREND 'vscan' YES - $TREND" GOT_VIRUS_SCANNER=1 fi if test "$CLAMSCAN" = "/bin/false" ; then if test "$ac_cv_clamav" != "yes" ; then echo "CLAMSCAN 'clamav' NO (Disabled by configure command)" else echo "CLAMSCAN 'clamav' NO (not found)" fi else echo "CLAMSCAN 'clamav' YES - $CLAMSCAN" GOT_VIRUS_SCANNER=1 fi if test "$CLAMDSCAN" = "/bin/false" ; then if test "$ac_cv_clamav" != "yes" ; then echo "CLAMDSCAN 'clamav' NO (Disabled by configure command)" else echo "CLAMDSCAN 'clamav' NO (not found)" fi else echo "CLAMDSCAN 'clamav' YES - $CLAMDSCAN" GOT_VIRUS_SCANNER=1 fi if test "$AVP" = "/bin/false" ; then if test "$ac_cv_AvpLinux" != "yes" ; then echo "AVP 'AvpLinux' NO (Disabled by configure command)" else echo "AVP 'AvpLinux' NO (not found)" fi else echo "AVP 'AvpLinux' YES - $AVP" GOT_VIRUS_SCANNER=1 fi if test "$AVP5" = "/bin/false" ; then if test "$ac_cv_aveclient" != "yes" ; then echo "AVP5 'aveclient' NO (Disabled by configure command)" else echo "AVP5 'aveclient' NO (not found)" fi else echo "AVP5 'aveclient' YES - $AVP5" GOT_VIRUS_SCANNER=1 fi if test "$KAVSCANNER" = "/bin/false" ; then if test "$ac_cv_kavscanner" != "yes" ; then echo "KAVSCANNER 'kavscanner' NO (Disabled by configure command)" else echo "KAVSCANNER 'kavscanner' NO (not found)" fi else echo "KAVSCANNER 'kavscanner' YES - $KAVSCANNER" GOT_VIRUS_SCANNER=1 fi if test "$CSAV" = "/bin/false" ; then if test "$ac_cv_csav" != "yes" ; then echo "CSAV 'csav' NO (Disabled by configure command)" else echo "CSAV 'csav' NO (not found)" fi else echo "CSAV 'csav' YES - $CSAV" GOT_VIRUS_SCANNER=1 fi if test "$FSAV" = "/bin/false" ; then if test "$ac_cv_fsav" != "yes" ; then echo "FSAV 'fsav' NO (Disabled by configure command)" else echo "FSAV 'fsav' NO (not found)" fi else echo "FSAV 'fsav' YES - $FSAV" GOT_VIRUS_SCANNER=1 fi if test "$FPROT" = "/bin/false" ; then if test "$ac_cv_fprot" != "yes" ; then echo "FPROT 'f-prot' NO (Disabled by configure command)" else echo "FPROT 'f-prot' NO (not found)" fi else echo "FPROT 'f-prot' YES - $FPROT" GOT_VIRUS_SCANNER=1 fi if test "$FPSCAN" = "/bin/false" ; then if test "$ac_cv_fpscan" != "yes" ; then echo "FPSCAN 'fpscan' NO (Disabled by configure command)" else echo "FPSCAN 'fpscan' NO (not found)" fi else echo "FPSCAN 'fpscan' YES - $FPSCAN" GOT_VIRUS_SCANNER=1 fi if test "$SOPHIE" = "/bin/false" ; then if test "$ac_cv_sophie" != "yes" ; then echo "SOPHIE 'sophie' NO (Disabled by configure command)" else echo "SOPHIE 'sophie' NO (not found)" fi else echo "SOPHIE 'sophie' YES - $SOPHIE" GOT_VIRUS_SCANNER=1 fi if test "$NVCC" = "/bin/false" ; then if test "$ac_cv_nvcc" != "yes" ; then echo "NVCC 'nvcc' NO (Disabled by configure command)" else echo "NVCC 'nvcc' NO (not found)" fi else echo "NVCC 'nvcc' YES - $NVCC" GOT_VIRUS_SCANNER=1 fi if test "$CLAMD" = "/bin/false" ; then if test "$ac_cv_clamd" != "yes" ; then echo "CLAMD 'clamd' NO (Disabled by configure command)" else echo "CLAMD 'clamd' NO (not found)" fi else echo "CLAMD 'clamd' YES - $CLAMD" GOT_VIRUS_SCANNER=1 fi if test "$TROPHIE" = "/bin/false" ; then if test "$ac_cv_trophie" != "yes" ; then echo "TROPHIE 'trophie' NO (Disabled by configure command)" else echo "TROPHIE 'trophie' NO (not found)" fi else echo "TROPHIE 'trophie' YES - $TROPHIE" GOT_VIRUS_SCANNER=1 fi if test "$NOD32" = "/bin/false" ; then if test "$ac_cv_nod32" != "yes" ; then echo "NOD32 'nod32cli' NO (Disabled by configure command)" else echo "NOD32 'nod32cli' NO (not found)" fi else echo "NOD32 'nod32cli' YES - $NOD32" GOT_VIRUS_SCANNER=1 fi fi if test "$GOT_VIRUS_SCANNER" = "0" ; then echo "" echo "Could not find any recognized virus scanner... do not use" echo "any of the contains_virus functions in your filter." fi if test "$CLAMD" != "/bin/false" -o "$SOPHIE" != "/bin/false" -o "$TROPHIE" != "/bin/false" ; then echo "" if test "$CLAMD" != "/bin/false" ; then echo "Make sure clamd runs as the $DEFANGUSER user!" fi if test "$SOPHIE" != "/bin/false" ; then echo "See README.SOPHIE for more information about running Sophie." fi if test "$TROPHIE" != "/bin/false" ; then echo "Make sure Trophie runs as the $DEFANGUSER user!" fi echo "" fi if test "$ac_cv_perlmodcheck" = "yes" ; then if test "$HAVE_SPAM_ASSASSIN" = "yes" ; then echo "Found Mail::SpamAssassin. You may use spam_assassin_* functions" else echo "Did not find Mail::SpamAssassin. Do not use spam_assassin_* functions" fi echo "" if test "$HAVE_HTML_PARSER" = "yes" ; then echo "Found HTML::Parser. You may use append_html_boilerplate()" else echo "Did not find HTML::Parser. Do not use append_html_boilerplate()" fi fi if test "$HAVE_IO_SOCKET_SSL" = "no" ; then echo "" echo "Could not find IO::Socket::SSL Perl module... do not use" echo "md_check_against_smtp_server function in your filter." echo "" fi if test "$HAVE_LWP" = "no" ; then if test "$HAVE_JSON" = "no" ; then echo "" echo "Could not find LWP::UserAgent and JSON Perl modules..." echo "it's not possible to check your emails with rspamd(8) " echo "using rspamd_check function in your filter." echo "" fi fi if test "$HAVE_SPF" = "no" ; then echo "" echo "Could not find Mail::SPF Perl module... do not use" echo "md_authres function in your filter." echo "" fi if test "$HAVE_DKIM" = "no" ; then echo "" echo "Could not find Mail::DKIM Perl module... do not use" echo "md_dkim_sign, md_dkim_verify or md_authres functions in your filter." echo "" fi if test "$HAVE_DIGEST_MD5" = "no" ; then echo "" echo "Could not find Digest::MD5 Perl module... do not use" echo "email_is_blacklisted function in your filter." echo "" fi if test "$HAVE_NET_SMTP" = "no" ; then echo "" echo "Could not find Net::SMTP Perl module... you will not be able" echo "to release quarantined messages using mimedefang-release(8)" echo "in smtp mode." echo "" fi echo "Note: SpamAssassin and HTML::Parser are" echo "detected at run-time, so if you install or remove any of those modules, you" echo "do not need to re-run ./configure and make a new mimedefang.pl." if test "$ac_cv_perlmodcheck" = "yes" ; then if test "$mtversion" = "unknown" ; then echo "" echo "Could not determine version of MIME::Tools. Please use 5.412 or later." 1>&6 elif test $mtversion -lt 54120 ; then echo "" 1>&6 echo "**** WARNING: We strongly recommend that you use MIME::Tools" 1>&6 echo "**** version 5.412 or later instead of version $mt_actual_version." 1>&6 fi fi if test "$DEFANGUSER" != "" ; then id $DEFANGUSER > /dev/null 2>&1 if test "$?" != 0 ; then echo "" echo "Note: The MIMEDefang user '$DEFANGUSER' does not seem to" echo "exist. Please create this user before continuing." fi fi if test "$ENABLE_DEBUGGING" != "" ; then echo "" echo "*** WARNING: You have configured with --enable-debugging." echo "*** This will produce LARGE VOLUMES of syslog messages and" echo "*** is NOT RECOMMENDED for a production system." fi mimedefang-3.6/contrib/000077500000000000000000000000001475763067200151375ustar00rootroot00000000000000mimedefang-3.6/contrib/README000066400000000000000000000016311475763067200160200ustar00rootroot00000000000000#$Id$ This directory contains user-contributed programs and files. See each file for copyright information. Please don't ask me for help with anything in this directory. I didn't write any of it. :-) Contents: ------------------------------------------------------------------------------ fang.pl -- A Perl script which attempts to reconstruct a message from the contents of a quarantine directory. Requires the MIME::Lite Perl module. linuxorg -- A collection of files contributed by Michael McLagan used to implement the filtering in place within Linux Online and Linux Headquarters. word-to-html -- A simple shell script which converts Word documents to HTML. Requires wvHtml. graphdefang -- Utilities to make pretty graphs of MIMEDefang activity. mimedefang-3.6/contrib/fang.pl000066400000000000000000000164501475763067200164150ustar00rootroot00000000000000#!/usr/bin/perl ###################################################################### # PROGRAM: fang -d -s -e # PURPOSE: To put messages back together after defang # is done with them. # AUTHOR: N. McKellar (mckellar@telusplanet.net) # DATE: 9 Jan 2002 # MODIFICATIONS: # # 17 March 2002, N. McKellar # - Moved MIME::Lite connection down past the USAGE output so # you can run 'fang -h' without specifying the mail server # first. # - Added exit() to USAGE so program doesn't just run anyway. # - Added a check to ensure that the directory passed in is # really a directory. # - Modified all the open() calls to explicitly open files as # read (should keep open() from executing programs). # # Copyright (C) 2002 Neil McKellar (mckellar@telusplanet.net) # # * This program may be distributed under the terms of the GNU General # * Public License, Version 2. # # Notes: # Modify the name of the mail server to use when sending the # reconstructed mail messages. Ideally this will be your own mail # server. Look for 'your.mail.server.here' in the code below. # # It may be worth noting that a properly misnamed file or directory # might still be used to spoof reading or processing of files and # directories you don't intend. # # Using -T won't help much as the command line arguments are passed # through Getopt::Std first before arriving here and Taint won't # see whether they've been cleaned or not. # # DON'T make this script setuid. # BE CAUTIOUS if your defang directory needs root permissions to fix # messages with this script. A better idea would be to make a quick # and temporary working directory to use this script in that doesn't # need root. # ###################################################################### use strict; use warnings; use Net::SMTP; use MIME::Lite; use Getopt::Std; use Date::Parse; ######################################## # Setup ######################################## my $DEBUG = 0; ## DEBUG MODE my %opts; my %folders; my $start_time = -1; ## less than epoch my $end_time = time + 3600; ## current time + 1 hour my $mail_server = 'your.mail.server.here'; ######################################## # Get arguments ######################################## getopt('dse?h', \%opts); if (exists $opts{'?'} || exists $opts{'h'}) { print <] [-s ] [-e ] and must be parsable by Date::Parse. A good format would be: YYYY:MM:DD:HH:TT:SS eg. 2002:01:09:21:32:00 OR DD Mon YYYY HH:MM eg. 9 Jan 2002 19:30 USAGE exit 0; } $opts{'d'} = '.' unless ($opts{'d'} ne '' && $opts{'d'} != 1); $start_time = str2time($opts{'s'}) if ($opts{'s'} ne '' && $opts{'s'} != 1); $end_time = str2time($opts{'e'}) if ($opts{'e'} ne '' && $opts{'e'} != 1); ## Make sure we didn't get something bogus here Error("$opts{'d'} is not a valid directory!") unless (-d $opts{'d'}); if ($DEBUG) { print < next unless (-d $curr); ## Only directories my @dir_stat = stat($curr); if ($dir_stat[9] >= $start_time && $dir_stat[9] <= $end_time) { $folders{$item} = $dir_stat[9]; ## track mtime print "[DEBUG] $item matches\n" if ($DEBUG); } } ######################################## # Connect to Mail Server ######################################## MIME::Lite->send('smtp', $mail_server); foreach my $msg (sort keys %folders) { make_message($msg); } exit 0; ###################################################################### # SUBROUTINES ###################################################################### ######################################## sub Error { ######################################## # Print error messages ######################################## my $msg = shift(@_); print "ERROR: $msg\n\n"; exit -1; } ######################################## sub make_message { ######################################## # Read files in directory and # make a MIME::Lite message ######################################## my $dir = shift(@_); my %body; my %file; my %type; ###################################### # Get list of recipients ###################################### my @to; open(TO,"<$opts{'d'}/$dir/RECIPIENTS") || Error("Can't read $dir/RECIPIENTS: $!"); while () { chomp; push @to,$_; } close(TO); ###################################### # Get sender ###################################### my $from; open(FROM,"<$opts{'d'}/$dir/SENDER") || Error("Can't read $dir/SENDER: $!"); $from = ; close(FROM); ###################################### # Get Subject line and Date ###################################### my $subject; my $date; open(HEADER,"<$opts{'d'}/$dir/HEADERS") || Error("Can't read $dir/HEADERS: $!"); while (
) { chomp; SWITCH: { /Date:/ && do { s/Date: //; $date = $_; }; /Subject:/ && do { s/Subject: //; $subject = $_; }; } } close(HEADER); ###################################### # List PART.* # Get 'BODY' and 'HEADERS' for each # part. ###################################### opendir(FOLDER,"$opts{'d'}/$dir") || Error("Can't list $dir: $!"); my @files = readdir(FOLDER); close(FOLDER); foreach (@files) { next unless (/^PART/); ## Parse 'part' name my ($part,$idx,$piece) = split(/\./); ## Store body content and headers separately SWITCH: foreach ($piece) { /BODY/ && do { $body{$idx} = 'body'; }; /HEADERS/ && do { ## Read HEADERS for 'part' my $header = "$opts{'d'}/$dir/" . $part . "." . $idx . "." . $piece; open(DATA,"<$header") || Error("Can't read $header: $!"); while () { if (/Content-Type/ && /name=/) { s/Content-Type: (.*?);/$1/; $type{$idx} = $_; s/.*name="(.*)"/$1/; $file{$idx} = $_; } elsif (/Content-Disposition/ && /filename=/) { s/.*name="(.*)"/$1/; $file{$idx} = $_; } if ($DEBUG) { print <new( From => $from, To => join(',',@to), Subject => $subject, Type => 'multipart/mixed', ); foreach my $idx (sort {$a <=> $b} keys %body) { $msg->attach( Type => $type{$idx}, Path => "$opts{'d'}/$dir/PART.$idx.BODY", Filename => $file{$idx}, ); } ###################################### # Send finished message ###################################### $msg->send() unless ($DEBUG); } mimedefang-3.6/contrib/filebeat.yml000066400000000000000000000007521475763067200174410ustar00rootroot00000000000000--- - type: filestream id: mimedefang # sample filebeat configuration file to store MIMEDefang status # to ELK stack paths: - /var/log/mimedefang.json encoding: plain document_type: json-logs prospector: scanner: check_interval: 10s harvester_buffer_size: 16384 message_max_bytes: 10485760 ### JSON configuration json: keys_under_root: true add_error_key: true tail_files: false fields: logtype: json-logs fields_under_root: true mimedefang-3.6/contrib/graphdefang-0.91/000077500000000000000000000000001475763067200177725ustar00rootroot00000000000000mimedefang-3.6/contrib/graphdefang-0.91/COPYING000066400000000000000000000431101475763067200210240ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. , 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. mimedefang-3.6/contrib/graphdefang-0.91/ChangeLog000066400000000000000000000131621475763067200215470ustar00rootroot000000000000002017-02-06 Kevin A. McGrail * Release 0.91 * Added Makefile.PL to assist in handling dependencies as well as MANIFEST * Added imapd event to mirror ipop3d event * Added dovecot event for imap (ipop is a TODO) 2011-12-12 Dianne Skoll * Fix Graphdefang to handle new md_syslog output style. 2003-10-09 John Kirkland * Fixed bug with missing TZ value in graphdefang.cgi. * Fixed bug with badly formed regexp for user_unknown parsing. * Fixed bug in the "trim" function that wasn't trimming as much as it should have been! This reduced the size of my SummaryDB by 10x!!! This defect and fix was reported by PRECIGOUT Raphael . 2003-06-22 John Kirkland * Release 0.9-beta2 * Added support for setting timezones in the mimedefang-config file. This is useful if you have 2 different hosts logging to a given syslog file, and the hosts are in 2 different time zones. 2003-06-19 John Kirkland * Changed $DATAFILE[0] to $DATAFILES[0] in mimedefang-config- mimedefang-example. 2003-06-18 John Kirkland * Release 0.9-beta1 * added host-based max time tracking * added ability to load logs from more than one datafile * fixed bug with deferred mail sendmail/sympa event. It was not looking at the maxDB time when loading, so the event count was higher than it should have been. * fixed bug in month handling when we have 31 days in the current month and the first month on our monthly graph has fewer than 31 days. 2003-06-11 John Kirkland * added sympa log handling 2003-05-03 John Kirkland * Release 0.8 2003-04-24 John Kirkland * fixed month handling defect when dst changes * adjusted spamd log format to match new format in Spamassassin 2.52. 2003-03-04 John Kirkland * added back in STDERR text for non-top25 deletes in db_trim * slightly modified event/sendmail/reject to account for more types of rejects (from Stefano McGhee). 2003-02-16 John Kirkland * graphdefang.cgi was not respecting the number of hours value. now it is! 2003-02-16 John Kirkland * Release 0.7 * Added sender and relay address (value2) values to the user_unknown event. * Added sender address to reject event. 2003-02-02 John Kirkland * Added support for ipchains event. Code contributed by Dave Willis . * Added filename as a configurable element to graphdefang-config. * Added graphdefang.cgi, which allows you to draw graphdefang graphs with a web browser interface. * Added --nomax flag so graphdefang can ignore the max time data and load graphs anyway (courtesy of Brent J. Nordquist ). * index.php now works with register_globals off (courtesy of Brent J. Nordquist and others who have submitted this patch before.) * index.php is now xhtml compliant. (courtesy of Brent J. Nordquist ). * Backup SummaryDB.db file before writing to it. 2003-01-06 John Kirkland * Added support for the 'event' subdirectory. Now all event parsing is done from perl stubs under the 'event' subdirectory in the graphdefang root dir. * Changed --trim option to trim out 1.25X instead of 2X. 2003-01-02 John Kirkland * Release 0.6 * Removed support for Parse::Syslog.pm -- I didn't like its date handling around the new year. * Added support for File::ReadBackwards. This should improve log reading times for large log files. * Added regexp support for Solaris syslog lines. * Fixed a bug in the graph creation code where it was off at the beginning of some months. 2002-11-19 John Kirkland * Added a new default mimedefang graph for relayaddr graphing. * Changed syslog parsing to use the Parse::Syslog pm. * Added support for dropped packet logging from gShield. * Added support for sendmail user unknown & reject stats * Optimized speed a bit by not trying to match against lines we've already seen. 2002-11-18 John Kirkland * Release 0.5 2002-11-14 John Kirkland * Fixed bug whereby no data points on a graph would cause an error message. 2002-10-09 John Kirkland * Added support for spamd logs * Release 0.4 2002-10-04 John Kirkland * Release 0.3 2002-10-03 John Kirkland * Lotsa changes... let me see if I can remember them! * Now works against the logs produced by md_log within mimedefang. * Added data caching so that we don't have to recalculate summaries for previously seen log messages. * Added a database trim ability to trim out older detailed data from summary database. * Added command line options to graphdefang.pl for help, quiet, nodb, trim, and file. * Added config file for configuring graphs * Added 'all' option for data_types * Added regex filtering (say, to only show recipients from a given domain when showing a stacked bar by recipient) * Added more default colors 2002-10-02 John Kirkland * Release 0.2 2002-10-01 John Kirkland * Fixed a bug in the syslog parsing code for single digit days of the month. * Fixed a bug in the Totals on the spam and virus summary chart where it was totalling counts that were outside of the time span of the given chart. * Fixed a bug in the Legend Code, where old legends were not cleared out when creating new ones. 2002-09-23 John Kirkland * Release 0.1 mimedefang-3.6/contrib/graphdefang-0.91/MANIFEST000066400000000000000000000011001475763067200211130ustar00rootroot00000000000000MANIFEST COPYING ChangeLog README TODO graphdefang-config-gshield-example graphdefang-config-mimedefang-example graphdefanglib.pl graphdefang-config-sympa-example graphdefang.pl reset-max-date.pl event/ipop3d/general event/dovecot/general event/kernel/gshield event/kernel/ipchains event/mimedefang.pl/general event/sendmail/reject event/sendmail/spamd event/sendmail/sympa_outgoing event/sendmail/user_unknown event/spamd/general event/sympa/subscribe event/sympa/unsubscribe event/imapd/general web/graphdefang.cgi web/index.php Makefile.PL graphdefang-config-spamd-example mimedefang-3.6/contrib/graphdefang-0.91/Makefile.PL000066400000000000000000000016341475763067200217500ustar00rootroot00000000000000use ExtUtils::MakeMaker; use strict; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( 'NAME' => 'GraphDefang', 'DISTNAME' => 'GraphDefang', 'VERSION' => '0.91', 'PREREQ_PM' => { 'File::ReadBackwards'=>'1.00', 'GD'=>'2.07', 'GD::Graph'=>'1.35', 'GD::Text::Align'=>'1.18', 'Date::Parse'=>'2.27', 'Date::Format'=>'2.22', 'MLDBM'=>'2.01', 'Storable'=>'2.04' }, 'linkext' => { LINKTYPE=>'' }, 'PM' => { 'graphdefang.pl'=>'graphdefang.pl', 'graphdefanglib.pl' => 'graphdefanglib.pl', }, 'dist' => {'COMPRESS'=>'gzip', 'SUFFIX' => 'gz'} ); print "\nRun 'make install' to install.\n"; mimedefang-3.6/contrib/graphdefang-0.91/README000066400000000000000000000057241475763067200206620ustar00rootroot00000000000000# # GraphDefang -- a set of tools to create graphs of your mimedefang # spam and virus logs. # # Written by: John Kirkland # jpk@bl.org # # Copyright (c) 2002-2003, John Kirkland #============================================================================= DESCRIPTION ----------- GraphDefang is a configurable perl utility that parses arbitrary syslog entries and creates a set of configurable charts (png files) with the data. It is possible to output the png files to the filesystem or to a web browser via a cgi. The following attributes are definable: * Chart Type (stacked_bar or line) * Chart Time (hourly, weekly, or monthly) * X-Axis (Summary, Value1, Value2, Sender, Recipient, Subject, or All) * Y-Axis (any event logged with md_log within mimedefang-filter) * Top Number of Items to Display * Chart Title * Filter * X and Y Graph Sizes * Graph Filename Graphdefang will also cache summary data so that it is not necessary to parse and recalculate old data each time new graphs are drawn. You can see the output in action at: http://www.westover.org/~jpk/spam DOWNLOAD -------- http://www.bl.org/~jpk/graphdefang REQUIRED PERL MODULES --------------------- File::ReadBackwards GD GD::Graph Date::Parse; Date::Format; MLDBM Storable INCLUDED FILES -------------- graphdefanglib.pl => Calculation and graphing routines. graphdefang.pl => Front-end perl script. graphdefang-config-mimedefang-example => Example configuration file for use with mimedefang graphdefang-config-spamd-example => Example configuration file for use with spamd web/index.php => Very simple php file to display the resulting png files web/graphdefang.cgi=> CGI that provides a functional web interface for using graphdefang. event/* => Event configuration files. README => This file TODO => Prioritized list of next things to work on. COPYING => Copy of the GPL INSTALLATION INSTRUCTIONS ------------------------- 1. Install Required Perl Modules 2. Untar the graphdefang distribution into a directory from where it can run. 3. (for mimedefang only) Add md_log commands per the example mimedefang-filter (included with mimedefang-2.22+). 4. Create a file, graphdefang-config, from the examples included with the distribution. Set $DATAFILE and $OUTPUT_DIR in this file. 5. Copy web/index.php to the $OUTPUT_DIR web directory. Set $OUTPUT_DIR in this file. 6. Copy graphdefang.cgi to the $OUTPUT_DIR web directory. Configure perl cgi access for this directory and set $GRAPHDEFANGDIR in this file. 7. Run graphdefang.pl via cron. I run mine every 15 mins. You can also run adhoc graphdefang queries against the DB by hitting graphdefang.cgi with a web browser. LICENSE ------- GraphDefang is released under the GPL license. A copy of the GPL is included in this distribution under the file, COPYING. mimedefang-3.6/contrib/graphdefang-0.91/TODO000066400000000000000000000022411475763067200204610ustar00rootroot00000000000000# # GraphDefang -- a set of tools to create graphs of your mimedefang # spam and virus logs. # # Written by: John Kirkland # jpk@bl.org # # Copyright (c) 2002-2003, John Kirkland #============================================================================= TODO * Add #@PERL@ instead of a hardcode perl binary to scripts and change install files to rename xyz.pl.in to xyz.pl installing everything in the correct places * Figure out how to graph imap and pop3 separately when using dovecot - I believe event/dovecot/general just needs a sub that looks for both imap and pop3 lines (DONE) * Add filtering so you can look at spam and virus patterns only for a certain domain. I'm interested in this because I host several domains with my sendmail install. (DONE) * Add recipientdomain as an accumulator type so I can look at spam and virus totals by domain on a single chart. * Add pie charts and histograms * Add data caching so we don't need to recalculate summary values that we've already seen. (DONE) * Add more generalized sendmail send and receive stats to the graphs so we know the significance of given spam counts. mimedefang-3.6/contrib/graphdefang-0.91/event/000077500000000000000000000000001475763067200211135ustar00rootroot00000000000000mimedefang-3.6/contrib/graphdefang-0.91/event/dovecot/000077500000000000000000000000001475763067200225565ustar00rootroot00000000000000mimedefang-3.6/contrib/graphdefang-0.91/event/dovecot/general000066400000000000000000000017411475763067200241210ustar00rootroot00000000000000#!/usr/bin/perl -w #Feb 5 23:50:04 intel1 dovecot: imap-login: Login: user=, method=PLAIN, rip=100.36.131.234, lip=38.124.232.10, mpid=15867, TLS, session= $event{'dovecot'}{'general'} = sub { #print "DEBUG: $text\n"; if ($text =~ m/^imap-login: Login: user=\<(\S+)\>,.*rip=(\S+)/) { #print "DEBUG: match\n"; if ($unixtime > $MaxDBUnixTime) { my $user = $1; my $host = $2; $event = 'dovecat-imapd'; $value1 = $user; $value2 = $host; $FoundNewRow = 1; } } #print "DEBUG: $text\n"; if ($text =~ m/^pop3-login: Login: user=\<(\S+)\>,.*rip=(\S+)/) { #print "DEBUG: match\n"; if ($unixtime > $MaxDBUnixTime) { my $user = $1; my $host = $2; $event = 'dovecat-pop3d'; $value1 = $user; $value2 = $host; $FoundNewRow = 1; } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/imapd/000077500000000000000000000000001475763067200222055ustar00rootroot00000000000000mimedefang-3.6/contrib/graphdefang-0.91/event/imapd/general000066400000000000000000000005751475763067200235540ustar00rootroot00000000000000#!/usr/bin/perl -w #Oct 11 12:32:19 intel1 imapd[22733]: Login user=shannon host=ip68-100-89-190.dc.dc.cox.net [68.100.89.190] $event{'imapd'}{'general'} = sub { if ($text =~ m/^Login user=(\S+) host=(\S+)/) { if ($unixtime > $MaxDBUnixTime) { my $user = $1; my $host = $2; $event = 'imapd'; $value1 = $user; $value2 = $host; $FoundNewRow = 1; } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/ipop3d/000077500000000000000000000000001475763067200223115ustar00rootroot00000000000000mimedefang-3.6/contrib/graphdefang-0.91/event/ipop3d/general000066400000000000000000000006151475763067200236530ustar00rootroot00000000000000#!/usr/bin/perl -w # Oct 17 21:58:33 westover ipop3d[21207]: Login user=ben host=c68.115.81.145.roc.mn.charter.com [68.115.81.145] nmsgs=0/0 $event{'ipop3d'}{'general'} = sub { if ($text =~ m/^Login user=(\S+) host=(\S+)/) { if ($unixtime > $MaxDBUnixTime) { my $user = $1; my $host = $2; $event = 'ipop3d'; $value1 = $user; $value2 = $host; $FoundNewRow = 1; } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/kernel/000077500000000000000000000000001475763067200223735ustar00rootroot00000000000000mimedefang-3.6/contrib/graphdefang-0.91/event/kernel/gshield000066400000000000000000000011111475763067200237270ustar00rootroot00000000000000#!/usr/bin/perl -w #Oct 27 04:02:30 hdnetwork kernel: gShield (default drop) IN=eth0 OUT= MAC=ff:ff:ff:ff:ff:ff:00:a0:c8:08:2b:2c:08:00 SRC=66.139.79.158 DST=216.201.156.32 LEN=60 TOS=0x00 PREC=0x00 TTL=52 ID=49735 DF PROTO=TCP SPT=51023 DPT=21 WINDOW=5840 RES=0x00 SYN URGP=0 $event{'kernel'}{'gshield'} = sub { if ($text =~ m/^gShield .* SRC=(\S+) DST=(\S+) .* DPT=(\d+)/) { if ($unixtime > $MaxDBUnixTime) { my $src = $1; my $dst = $2; my $dpt = $3; $event = 'gShield'; $sender = $src; $recipient = $dst; $value1 = $dpt; $FoundNewRow = 1; } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/kernel/ipchains000066400000000000000000000015221475763067200241140ustar00rootroot00000000000000#!/usr/bin/perl -w # Jan 1 04:04:00 tnt kernel: Packet log: input DENY eth0 PROTO=17 216.217.194.131:858 24.123.123.122:53 L=66 S=0x00 I=65219 F=0x0000 T=107 (#405) # Code Contributed by Dave Willis $event{'kernel'}{'ipchains'} = sub { if ($text =~ m/^Packet log: .* DENY (\S+) PROTO=(\S+) ([0-9.]+):(\d+) ([0-9.]+):(\d+)/) { if ($unixtime > $MaxDBUnixTime) { # I don't know if interface or proto would be useful to # graph. I don't think source port is my $interface = $1; # eth0, eth1... my $proto = $2; my $src = $3; # Source IP address my $spt = $4; # Source Port my $dst = $5; # Dest IP address my $dpt = $6; # Dest Port $event = 'ipchains'; $sender = $src; $recipient = $dst; $value1 = $dpt; $value2 = $proto; $FoundNewRow = 1; } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/mimedefang.pl/000077500000000000000000000000001475763067200236215ustar00rootroot00000000000000mimedefang-3.6/contrib/graphdefang-0.91/event/mimedefang.pl/general000066400000000000000000000030151475763067200251600ustar00rootroot00000000000000#!/usr/bin/perl -w # Sample Rows from mimedefang's md_log() #Feb 5 07:16:05 intel1 mimedefang.pl[5688]: v15CFuMS029484: MDLOG,v15CFuMS029484,mail_in,,,,,=?UTF-8?B?Rmxhbm5lbCBNYWRlIHRvIExhc3TigKZhbmQgTGFzdA==?= #Feb 5 07:16:20 intel1 mimedefang.pl[25949]: v15CGKIa029792: MDLOG,v15CGKIa029792,mail_in,,,,,Cron run-parts /etc/cron.daily #Feb 5 07:16:20 intel1 mimedefang.pl[25949]: v15CGKIa029792: MDLOG,v15CGKIa029792,mail_out,,,,,Cron run-parts /etc/cron.daily #Feb 5 07:16:25 intel1 mimedefang.pl[5688]: v15CGChQ029546: MDLOG,v15CGChQ029546,spam,11.4,193.169.245.121,,,See Photos of Mature Singles Near You Now FREE at Match.com #Feb 5 07:16:33 intel1 mimedefang.pl[5688]: v15CGSYX029801: MDLOG,v15CGSYX029801,non-spam,-5.0,66.48.80.119,,,Tuesday: Cash Management Webinar%2C Sales Tax Edition $event{'mimedefang.pl'}{'general'} = sub { if ($text =~ m/(?:^[A-Za-z0-9]{9}|^[A-Za-z0-9]{10,14}):\s*MDLOG,\S+?,(\S+?),(\S*?),(\S*?),(.*?),(.*?),(.*)$/ ) { # get values from regular expression # Only summarize data if it is newer than our current MaxDBUnixTime if ($unixtime > $MaxDBUnixTime) { $event = $1; $value1 = $2; $value2 = $3; $sender = $4; $recipient = $5; $subject = $6; $FoundNewRow = 1; } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/sendmail/000077500000000000000000000000001475763067200227075ustar00rootroot00000000000000mimedefang-3.6/contrib/graphdefang-0.91/event/sendmail/reject000066400000000000000000000016551475763067200241150ustar00rootroot00000000000000#!/usr/bin/perl -w #gAABdPd17840: ruleset=check_rcpt, arg1=<0206241317.54101.bj@bl.org>, relay=200-207-163-140.dsl.telesp.net.br [200.207.163.140], reject=550 5.0.0 <0206241317.54101.bj@bl.org>... We dont accept mail from br #gAF0bmd11725: ruleset=check_rcpt, arg1=, relay=[209.163.156.31], reject=451 4.1.8 Domain of sender address pqwa625@100pesos.com does not resolve #h1H0wArt006658: ruleset=check_mail, arg1=, relay=ip68-106-211-59.om.om.cox.net [68.106.211.59], reject=550 5.0.0 ... Error $event{'sendmail'}{'reject'} = sub { if ($text =~ m/^.*ruleset=check_(?:rcpt|relay|mail), arg1=(\S+),.*relay=.*?(\d+\.\d+\.\d+\.\d+).* reject=(\d+) (\d\.\d.\d) .*$/) { if ($unixtime > $MaxDBUnixTime) { $event = "reject"; $sender = $1; # sender email addr $value1 = "$3 $4"; #reject code $value2 = $2; # relay addr $FoundNewRow = 1; } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/sendmail/spamd000066400000000000000000000012261475763067200237370ustar00rootroot00000000000000#!/usr/bin/perl -w $event{'sendmail'}{'spamd'} = sub { if ($text =~ m/^\S+: from=(.+), size=.+ msgid=(.+), proto=.+ relay=(.*)$/) { # our spamd times may be separated from our sendmail times by a processing # delay. I've set the max potential processing delay to 5 minutes here if ($unixtime > ($MaxDBUnixTime-60*5)) { my $from = $1; my $msgid = $2; my $relay = $3; if (defined $spamd{$msgid}) { my $spamdpid = $spamd{$msgid}; $event = $spamd{$spamdpid}{'event'}; $sender = $from; $recipient = $spamd{$spamdpid}{'for'}; $value1 = $spamd{$spamdpid}{'score'}; $value2 = $relay; $FoundNewRow = 1; } } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/sendmail/sympa_outgoing000066400000000000000000000055141475763067200257030ustar00rootroot00000000000000#!/usr/bin/perl -w # From sympa to sendmail #Jun 9 17:32:25 hdnetwork sendmail[17858]: h59MWKC17858: from=todaysverse-owner@maillists.heartlight.org, size=3914, class=-30, nrcpts=25, msgid=<200306092213.RAA1554 5@gehrig.heartlight.org>, relay=sympa@localhost # From sendmail to recipient: #Jun 9 17:42:31 hdnetwork sendmail[18546]: h59MfGq18546: to=dbracy@worldbank.org,cyalexander@worldbank.org, delay=00:01:15, xdelay=00:00:06, mailer=esmtp, pri=387914, relay=wbln0024.worldbank.org. [138.220.29.28], dsn=2.0.0, stat=Sent (Message received OK) #Jun 9 17:42:32 hdnetwork sendmail[18625]: h59Mg6N18625: to=patricia_holmes@ama-assn.org,pamela_burks-beck@ama-assn.org,sheri_bergstrand@ama-assn.org, delay=00:00:26, xdelay=00:00:16, mailer=esmtp, pri=447914, relay=smtp.ama-assn.org. [208.252.129.101], dsn=2.0.0, stat=Sent (h59MWA3m022457 Message accepted for delivery) # Bounce #Jun 10 18:46:57 hdnetwork sendmail[21944]: h5A7KMT19430: to="| /home/sympa/bin/bouncequeue todaysverse-kjv@maillists.heartlight.org", ctladdr=todaysverse-kjv-owner@maillists.heartlight.org (8/0), delay=16:26:16, xdelay=00:00:01, mailer=prog, pri=4890100, dsn=2.0.0, stat=Sent # Deferred #Jun 9 18:38:45 hdnetwork sendmail[27415]: h59NLsX26174: to=preston@chesserstudios.com, delay=00:16:51, xdelay=00:00:00, mailer=esmtp, pri=567914, relay=chesserstudios.com., dsn=4.0.0, stat=Deferred: Name server: chesserstudios.com.: host name lookup failure #Jun 9 17:41:16 hdnetwork sendmail[18105]: h59MWlv17888: to=llowelyn.stainbank@liblink.co.za,joanne.toerien@liblink.co.za,allen.meredith@liblink.co.za,alwyn.burger@liblink.co.za, delay=00:08:29, xdelay=00:02:00, mailer=esmtp, pri=897914, relay=mx2.liberty.co.za. [163.202.197.31], dsn=4.0.0, stat=Deferred: Connection timed out with mx2.liberty.co.za. $event{'sendmail'}{'sympa_outgoing'} = sub { if ($text =~ m/^\S+: from=(.+), size=.+, class=.+, nrcpts=(\d+), msgid=(.+), relay=sympa\@localhost$/) { if ($unixtime > ($MaxDBUnixTime)) { $event = "sympa_outgoing"; $sender = $1; $NumEvents = $2; $FoundNewRow = 1; } } elsif ($text =~ m/^\S+: to=(.+), delay=.*?, mailer=esmtp, .*?, stat=Sent.*$/) { if ($unixtime > ($MaxDBUnixTime)) { $event = "sendmail_outgoing"; my @recipients = split(/,/, $1); $NumEvents = @recipients; $FoundNewRow = 1; } } elsif ($text =~ m/^\S+: to=.*?bouncequeue (\S+?)\", ctladdr=(\S+) .*?, mailer=prog, .*?, stat=Sent.*?$/) { if ($unixtime > ($MaxDBUnixTime)) { $event = "bounce"; $sender = $1; $FoundNewRow = 1; } } elsif ($text =~ m/^\S+: to=(.*), delay=.*?, stat=Deferred: .*$/) { if ($unixtime > ($MaxDBUnixTime)) { $event = "deferred"; my @recipients = split(/,/, $1); $NumEvents = @recipients; $FoundNewRow = 1; } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/sendmail/user_unknown000066400000000000000000000077011475763067200253740ustar00rootroot00000000000000#!/usr/bin/perl -w # matching against: #Feb 16 18:58:13 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:13 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:13 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:14 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:15 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:16 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:17 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:18 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:19 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:20 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:21 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:22 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:23 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:24 westover sendmail[6660]: h1H0wCrt006660: <705aO12943L14764c67@moi.net>... User unknown #Feb 16 18:58:25 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:26 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:27 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:28 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:29 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:30 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:31 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:32 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:33 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:34 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:35 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:36 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:37 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:38 westover sendmail[6660]: h1H0wCrt006660: ... User unknown #Feb 16 18:58:38 westover sendmail[6660]: h1H0wCrt006660: from=, size=0, class=0, nrcpts=0, proto=ESMTP, daemon=MTA, relay=cae168-215-231.sc.rr.com [24.168.215.231] $event{'sendmail'}{'user_unknown'} = sub { if ($text =~ m/^(\S+): from=(.+), size=.+ nrcpts=0, proto=.+ relay=(.*)$/) { # Create a temp data hash to store the from and relay info for # user unknown attempts if there were no valid recipients in the # entire message (nrcpts=0). if ($unixtime > ($MaxDBUnixTime)) { my $id = $1; my $from = $2; my $relay = $3; $user_unknown{$id}{'from'} = $2; $user_unknown{$id}{'relay'} = $3; } } elsif ($text =~ m/^(\S+): (\<\S+\>)\.\.\. User unknown$/) { if ($unixtime > $MaxDBUnixTime) { $event = 'user_unknown'; my $id = $1; $recipient = $2; # extract the domain from the unknown user's email address my $domain; if ($recipient =~ m/<.*@(.*)>/) { $domain = $1; } $value1 = "none"; $value1 = $domain if defined($domain); # extract the 'from' and 'relay' from the temp user_unknown hash $sender = "unknown"; $sender = $user_unknown{$id}{'from'} if (defined($user_unknown{$id}{'from'})); $value2 = "none"; $value2 = $user_unknown{$id}{'relay'} if (defined($user_unknown{$id}{'relay'})); $FoundNewRow = 1; } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/spamd/000077500000000000000000000000001475763067200222175ustar00rootroot00000000000000mimedefang-3.6/contrib/graphdefang-0.91/event/spamd/general000066400000000000000000000037061475763067200235650ustar00rootroot00000000000000#!/usr/bin/perl -w # Sample Rows from spamd's syslog: # SPAM: #Oct 6 06:25:04 wbs sendmail[23124]: g96BP4C23124: from=, size=1358, class=0, nrcpts=1, msgid=<200210051058.TZK5253@smtp.fastweb.it>, proto=ESMTP, daemon=MTA, relay=localhost.localdomain [127.0.0.1] #Oct 6 06:25:04 wbs spamd[11318]: connection from localhost.localdomain [127.0.0.1] at port 38273 #Oct 6 06:25:04 wbs spamd[23129]: info: setuid to tex succeeded #Oct 6 06:25:04 wbs spamd[23129]: processing message <200210051058.TZK5253@smtp.fastweb.it> for tex:502, expecting 1623 bytes. #Oct 6 06:25:06 wbs spamd[23129]: identified spam (6.2/5.0) for tex:502 in 1.3 seconds, 1623 bytes. #CLEAN MESSAGE #Oct 6 06:40:28 wbs sendmail[23163]: g96BeSC23163: from=, size=892, class=0, nrcpts=1, msgid=<200210061137.g96Bbv410245@moses.wbschool.net>, proto=ESMTP, daemon=MTA, relay=localhost.localdomain [127.0.0.1] #Oct 6 06:40:28 wbs spamd[11318]: connection from localhost.localdomain [127.0.0.1] at port 38318 #Oct 6 06:40:28 wbs spamd[23168]: info: setuid to dottie succeeded #Oct 6 06:40:28 wbs spamd[23168]: processing message <200210061137.g96Bbv410245@moses.wbschool.net> for dottie:509, expecting 1167 bytes. #Oct 6 06:40:30 wbs spamd[23168]: clean message (-96.5/8.0) for dottie:509 in 1.5 seconds, 1167 bytes. $event{'spamd'}{'general'} = sub { if ($text =~ m/^processing message (.+) for (\S+):\d+.+$/) { if ($unixtime > $MaxDBUnixTime) { my $msgid = $1; my $for = $2; $spamd{$pid}{'msgid'} = $msgid; $spamd{$pid}{'for'} = $for; #provide a lookup for the sendmail parsing section $spamd{$msgid} = $pid; } } elsif ($text =~ m/^(\w+ \w+) \((.+)\/.+\) for .+$/) { if ($unixtime > $MaxDBUnixTime) { my $words = $1; my $score = $2; if ($words eq 'identified spam') { $event = 'spam' } elsif ($words eq 'clean message') { $event = 'non-spam' } $spamd{$pid}{'event'} = $event; $spamd{$pid}{'score'} = $score; } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/sympa/000077500000000000000000000000001475763067200222445ustar00rootroot00000000000000mimedefang-3.6/contrib/graphdefang-0.91/event/sympa/subscribe000066400000000000000000000024221475763067200241500ustar00rootroot00000000000000#!/usr/bin/perl -w # Sample Rows from sympa's log: #subscribe attempted per list: #Jun 11 06:16:59 hdnetwork sympa[18462]: SUB todayspoll from renerules@bigpond.com, auth requested (0 seconds) #subscribe per list: #Jun 11 06:17:51 hdnetwork sympa[18462]: SUB todaysverse from renerules@bigpond.com accepted (1 seconds, 48560 subscribers) #subscribe failed per list: #Jun 10 16:01:47 hdnetwork sympa[1412]: SUB dailywisdom-niv from sean@zbanchomemortgage.com refused, auth failed $event{'sympa'}{'subscribe'} = sub { if ($text =~ m/^SUB (\S+) from \S+, auth requested .*$/ ) { # get values from regular expression # Only summarize data if it is newer than our current MaxDBUnixTime if ($unixtime > $MaxDBUnixTime) { $event = "subscribe-attempt"; $subject = "$1_$event"; $value1 = $1; $FoundNewRow = 1; } } elsif ($text =~ m/^SUB (\S+) from \S+ accepted .*? (\d+) subscribers\).*?$/ ) { if ($unixtime > $MaxDBUnixTime) { $event = "subscribe-success"; $subject = "$1_$event"; $value1 = $1; $value2 = $2; $FoundNewRow = 1; } } elsif ($text =~ m/^SUB (\S+) from \S+ refused, auth failed.*?$/ ) { if ($unixtime > $MaxDBUnixTime) { $event = "subscribe-authfail"; $subject = "$1_$event"; $value1 = $1; $FoundNewRow = 1; } } }; mimedefang-3.6/contrib/graphdefang-0.91/event/sympa/unsubscribe000066400000000000000000000032451475763067200245170ustar00rootroot00000000000000#!/usr/bin/perl -w # Sample Rows from sympa's log: #unsubscribe attempted per list: #Jun 11 06:23:24 hdnetwork sympa[18462]: SIG dailywisdom-niv from flyvet@juno.com auth requested (1 seconds) #unsubscribe per list: #Jun 11 06:17:15 hdnetwork sympa[18462]: SIG myutmost from faye.mcgreggor@trcoc.org accepted (1 seconds, 7195 subscribers) #unsubscribe failed per list: #made up 'cuz I couldn't find one: #Jun 10 16:01:47 hdnetwork sympa[1412]: SIG dailywisdom-niv from sean@zbanchomemortgage.com refused, auth failed #unsubscribe rejected -- not on list: #Jun 10 16:25:32 hdnetwork sympa[1412]: SIG todaysverse from cwells@stirlingprop.com refused, not on list $event{'sympa'}{'unsubscribe'} = sub { if ($text =~ m/^SIG (\S+) from \S+ auth requested .*$/ ) { # get values from regular expression # Only summarize data if it is newer than our current MaxDBUnixTime if ($unixtime > $MaxDBUnixTime) { $event = "unsubscribe-attempt"; $subject = "$1_$event"; $value1 = $1; $FoundNewRow = 1; } } elsif ($text =~ m/^SIG (\S+) from \S+ accepted .* (\d+) subscribers\).*?$/ ) { if ($unixtime > $MaxDBUnixTime) { $event = "unsubscribe-success"; $subject = "$1_$event"; $value1 = $1; $value2 = $2; $FoundNewRow = 1; } } elsif ($text =~ m/^SIG (\S+) from \S+ refused, auth failed.*?$/ ) { if ($unixtime > $MaxDBUnixTime) { $event = "subscribe-authfail"; $subject = "$1_$event"; $value1 = $1; $FoundNewRow = 1; } } elsif ($text =~ m/^SIG (\S+) from \S+ refused, not on list.*?$/ ) { if ($unixtime > $MaxDBUnixTime) { $event = "unsubscribe-notonlist"; $subject = "$1_$event"; $value1 = $1; $FoundNewRow = 1; } } }; mimedefang-3.6/contrib/graphdefang-0.91/graphdefang-config-gshield-example000066400000000000000000000121771475763067200265040ustar00rootroot00000000000000#!/usr/bin/perl -w # # GraphDefang -- a set of tools to create graphs of your mimedefang # spam and virus logs. # # Written by: John Kirkland # jpk@bl.org # # Copyright (c) 2002, John Kirkland # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #============================================================================= # # Path to incoming log file # $DATAFILE = '/var/log/messages'; # # Output directory for png files that are created # $OUTPUT_DIR = '/home/jpk/public_html/gshield'; # Set graph settings # # Possible settings: # # Name: data_types (required) # Description: Array of events from the mimedefang log file to # graph. # Supported Values: These event names are not fixed. If you put an event # in mimedefang-filter with md_log('event'), then you # can use it here. The values used in the example # mimedefang-filter are 'spam', 'virus', # 'suspicious_chars', 'message/partial', 'bad_filename', # 'non_rfc822', 'non_multipart'. # 'all' is also supported, but it must be listed by itself. # # Name: graph_type (required) # Description: Type of graph to output. # Supported Values: 'line' or 'stacked_bar' # # Name: grouping (required) # Description: Which value to graph from the md_log file. # Supported Values: 'summary', 'value1', 'value2', 'sender', 'recipient' # 'subject'. value1 and value2 are the optional # parameters that can be logged with the md_log command # from mimedefang-filter. # # Name: grouping_times (required) # Description: Array of Time intervals to use for grouping # Supported Values: 'hourly', 'daily', or 'monthly' # # Name: top_n (optional) # Description: Limit number of values to the top n. This # value is recommended when looking at # senders, recipients, or subjects. # # Name: value1_title (optional) # Description: Title used in the header if value1 is # graphed. # # Name: value2_title (optional) # Description: Title used in the header if value2 is # graphed. # # Name: filter (optional) # Description: Regular expression filter that can be # used with value1, value2, sender, # recipient, and subject # Common uses: # '@westover.org' to filter sender or recipient by domain # '^(?:(?!klez).)*$' to filter OUT klez in a virusname # # Name: filter_name (optional) # Description: If a filter is used, the filtername will be appended # to the Graph Title as "filtered by $filter_name" and # appended to the end of the filename. # # Name: $GraphSettings{'title'} (optional) # Description: Assigns a title for the chart. my %GraphSettings; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'summary', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['gShield'], 'graph_type' => 'stacked_bar', 'grouping' => 'value1', 'value1_title' => 'Dest Port', 'grouping_times'=> ['hourly','daily','monthly'], 'top_n' => '10', ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['gShield'], 'graph_type' => 'stacked_bar', 'grouping' => 'recipient', 'top_n' => '10', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['gShield'], 'graph_type' => 'stacked_bar', 'grouping' => 'sender', 'top_n' => '10', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; mimedefang-3.6/contrib/graphdefang-0.91/graphdefang-config-mimedefang-example000066400000000000000000000202201475763067200271450ustar00rootroot00000000000000#!/usr/bin/perl -w # # GraphDefang -- a set of tools to create graphs of your mimedefang # spam and virus logs. # # Written by: John Kirkland # jpk@bl.org # # Copyright (c) 2002-2003, John Kirkland # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #============================================================================= # # Path to incoming log file or log files # If you have only one log file, use $DATAFILE. # # If you have more than one log file to parse, say from different hosts, # use @DATAFILES as follows and you MUST COMMENT OUT OR DELETE $DATAFILE: # # $DATAFILES[0] = '/var/log/maillog.host1'; # $DATAFILES[1] = '/var/log/maillog.host2'; $DATAFILE = '/var/log/maillog'; # # Optional Timezone variable by host name. The host name must match # the host name presented in the syslog file(s). This variable is # useful when you have a central syslog server collecting logs for # machines that are in different timezones. By default, graphdefang # uses the timezone that is local to the machine upon which it is # running. It is not necessary to define the TZ for EVERY host, but # only for the ones that aren't in the same timezone as the log # server. The timezone must be understood by the Time::Zone perl # module. # # $TZ{'westover'} = 'cst6cdt'; # $TZ{'GD_Display'} = 'cst6cdt'; # # # Output directory for png files that are created # $OUTPUT_DIR = '/home/jpk/public_html/spam'; # Set graph settings # # Possible settings: # # Name: data_types (required) # Description: Array of events from the mimedefang log file to # graph. # Supported Values: These event names are not fixed. If you put an event # in mimedefang-filter with md_log('event'), then you # can use it here. The values used in the example # mimedefang-filter are 'spam', 'virus', # 'suspicious_chars', 'message/partial', 'bad_filename', # 'non_rfc822', 'non_multipart'. # 'all' is also supported, but it must be listed by itself. # # Name: graph_type (required) # Description: Type of graph to output. # Supported Values: 'line' or 'stacked_bar' # # Name: grouping (required) # Description: Which value to graph from the md_log file. # Supported Values: 'summary', 'value1', 'value2', 'sender', 'recipient' # 'subject'. value1 and value2 are the optional # parameters that can be logged with the md_log command # from mimedefang-filter. # # Name: grouping_times (required) # Description: Array of Time intervals to use for grouping # Supported Values: 'hourly', 'daily', or 'monthly' # # Name: top_n (optional) # Description: Limit number of values to the top n. This # value is recommended when looking at # senders, recipients, or subjects. # # Name: value1_title (optional) # Description: Title used in the header if value1 is # graphed. # # Name: value2_title (optional) # Description: Title used in the header if value2 is # graphed. # # Name: filter (optional) # Description: Regular expression filter that can be # used with value1, value2, sender, # recipient, and subject # Common uses: # '@westover.org' to filter sender or recipient by domain # '^(?:(?!klez).)*$' to filter OUT klez in a virusname # # Name: filter_name (optional) # Description: If a filter is used, the filtername will be appended # to the Graph Title as "filtered by $filter_name" and # appended to the end of the filename. # # Name: $GraphSettings{'title'} (optional) # Description: Assigns a title for the chart. # # Name: $GraphSettings{'filename'} (optional) # Description: Sets the filename for a given chart (note: # the grouping_time is appended to this variable # to determine the final file name. # # Name: $GraphSettings{'x_graph_size'} (optional) # Name: $GraphSettings{'y_graph_size'} (optional) # Description: Size, in pixels, for this graph. This overrides the # default. # # Name: $GraphSettings{'num_hourly_values'} (optional) # Name: $GraphSettings{'num_daily_values'} (optional) # Name: $GraphSettings{'num_monthly_values'} (optional) # Description: Number of data points for this graph. This overrides # the default. my %GraphSettings; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['spam', 'probable_spam', 'virus', 'mail_in'], 'graph_type' => 'line', 'grouping' => 'summary', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['virus'], 'graph_type' => 'stacked_bar', 'grouping' => 'value1', 'value1_title' => 'VirusName', 'grouping_times'=> ['hourly','daily','monthly'], #'filter' => '^(?:(?!klez).)*$', #'filter_name' => 'Not Klez', ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['spam'], 'graph_type' => 'stacked_bar', 'grouping' => 'recipient', 'top_n' => '9', 'grouping_times'=> ['hourly','daily','monthly'], 'filter' => '^(?:(?!westover.org).)*$', 'filter_name' => 'Heartlight Traffic', 'title' => 'Heartlight Spam Traffic', ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['spam', 'virus'], 'graph_type' => 'stacked_bar', 'grouping' => 'recipient', 'top_n' => '9', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['spam'], 'graph_type' => 'stacked_bar', 'grouping' => 'sender', 'top_n' => '9', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['spam'], 'graph_type' => 'stacked_bar', 'grouping' => 'value2', 'value2_title' => 'Relay Address', 'top_n' => '9', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['virus'], 'graph_type' => 'stacked_bar', 'grouping' => 'value2', 'value2_title' => 'Relay Address', 'top_n' => '9', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; mimedefang-3.6/contrib/graphdefang-0.91/graphdefang-config-spamd-example000066400000000000000000000143641475763067200261710ustar00rootroot00000000000000#!/usr/bin/perl -w # # GraphDefang -- a set of tools to create graphs of your mimedefang # spam and virus logs. # # Written by: John Kirkland # jpk@bl.org # # Copyright (c) 2002-2003, John Kirkland # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #============================================================================= # # Path to incoming log file # $DATAFILE = '/var/log/maillog'; # # Output directory for png files that are created # $OUTPUT_DIR = '/home/jpk/public_html/newspam'; # Set graph settings # # Possible settings: # # Name: data_types (required) # Description: Array of events from the mimedefang log file to # graph. # Supported Values: These event names are not fixed. If you put an event # in mimedefang-filter with md_log('event'), then you # can use it here. The values used in the example # mimedefang-filter are 'spam', 'virus', # 'suspicious_chars', 'message/partial', 'bad_filename', # 'non_rfc822', 'non_multipart'. # 'all' is also supported, but it must be listed by itself. # # Name: graph_type (required) # Description: Type of graph to output. # Supported Values: 'line' or 'stacked_bar' # # Name: grouping (required) # Description: Which value to graph from the md_log file. # Supported Values: 'summary', 'value1', 'value2', 'sender', 'recipient' # 'subject'. value1 and value2 are the optional # parameters that can be logged with the md_log command # from mimedefang-filter. # # Name: grouping_times (required) # Description: Array of Time intervals to use for grouping # Supported Values: 'hourly', 'daily', or 'monthly' # # Name: top_n (optional) # Description: Limit number of values to the top n. This # value is recommended when looking at # senders, recipients, or subjects. # # Name: value1_title (optional) # Description: Title used in the header if value1 is # graphed. # # Name: value2_title (optional) # Description: Title used in the header if value2 is # graphed. # # Name: filter (optional) # Description: Regular expression filter that can be # used with value1, value2, sender, # recipient, and subject # Common uses: # '@westover.org' to filter sender or recipient by domain # '^(?:(?!klez).)*$' to filter OUT klez in a virusname # # Name: filter_name (optional) # Description: If a filter is used, the filtername will be appended # to the Graph Title as "filtered by $filter_name" and # appended to the end of the filename. # # Name: $GraphSettings{'title'} (optional) # Description: Assigns a title for the chart. # # Name: $GraphSettings{'filename'} (optional) # Description: Sets the filename for a given chart (note: # the grouping_time is appended to this variable # to determine the final file name. # # Name: $GraphSettings{'x_graph_size'} (optional) # Name: $GraphSettings{'y_graph_size'} (optional) # Description: Size, in pixels, for this graph. This overrides the # default. # # Name: $GraphSettings{'num_hourly_values'} (optional) # Name: $GraphSettings{'num_daily_values'} (optional) # Name: $GraphSettings{'num_monthly_values'} (optional) # Description: Number of data points for this graph. This overrides # the default. my %GraphSettings; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'summary', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['non-spam'], 'graph_type' => 'stacked_bar', 'grouping' => 'recipient', 'top_n' => '9', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['spam'], 'graph_type' => 'stacked_bar', 'grouping' => 'recipient', 'top_n' => '9', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['spam'], 'graph_type' => 'stacked_bar', 'grouping' => 'sender', 'top_n' => '9', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['non-spam'], 'graph_type' => 'stacked_bar', 'grouping' => 'sender', 'top_n' => '9', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; mimedefang-3.6/contrib/graphdefang-0.91/graphdefang-config-sympa-example000066400000000000000000000207511475763067200262130ustar00rootroot00000000000000#!/usr/bin/perl -w # # GraphDefang -- a set of tools to create graphs of your mimedefang # spam and virus logs. # # Written by: John Kirkland # jpk@bl.org # # Copyright (c) 2002, John Kirkland # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #============================================================================= # # Path to incoming log file # $DATAFILE = '/var/log/sympa.log'; # # Output directory for png files that are created # $OUTPUT_DIR = '/home/jpk/public_html/sympa'; # Set graph settings # # Possible settings: # # Name: data_types (required) # Description: Array of events from the mimedefang log file to # graph. # Supported Values: These event names are not fixed. If you put an event # in mimedefang-filter with md_log('event'), then you # can use it here. The values used in the example # mimedefang-filter are 'spam', 'virus', # 'suspicious_chars', 'message/partial', 'bad_filename', # 'non_rfc822', 'non_multipart'. # 'all' is also supported, but it must be listed by itself. # # Name: graph_type (required) # Description: Type of graph to output. # Supported Values: 'line' or 'stacked_bar' # # Name: grouping (required) # Description: Which value to graph from the md_log file. # Supported Values: 'summary', 'value1', 'value2', 'sender', 'recipient' # 'subject'. value1 and value2 are the optional # parameters that can be logged with the md_log command # from mimedefang-filter. # # Name: grouping_times (required) # Description: Array of Time intervals to use for grouping # Supported Values: 'hourly', 'daily', or 'monthly' # # Name: top_n (optional) # Description: Limit number of values to the top n. This # value is recommended when looking at # senders, recipients, or subjects. # # Name: value1_title (optional) # Description: Title used in the header if value1 is # graphed. # # Name: value2_title (optional) # Description: Title used in the header if value2 is # graphed. # # Name: filter (optional) # Description: Regular expression filter that can be # used with value1, value2, sender, # recipient, and subject # Common uses: # '@westover.org' to filter sender or recipient by domain # '^(?:(?!klez).)*$' to filter OUT klez in a virusname # # Name: filter_name (optional) # Description: If a filter is used, the filtername will be appended # to the Graph Title as "filtered by $filter_name" and # appended to the end of the filename. # # Name: $GraphSettings{'title'} (optional) # Description: Assigns a title for the chart. my %GraphSettings; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'summary', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'dailywisdom-kjv', 'filter_name' => 'DailyWisdom-KJV', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'dailywisdom-niv', 'filter_name' => 'DailyWisdom-NIV', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'heartlight_', 'filter_name' => 'Heartlight', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'heartlight-update', 'filter_name' => 'Heartlight Update', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'myutmost', 'filter_name' => 'My Utmost', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'prayingwithpaul', 'filter_name' => 'Praying With Paul', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'quotemeal', 'filter_name' => 'Quote Meal', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'spurgeon', 'filter_name' => 'Spurgeon', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'todayspoll', 'filter_name' => 'Todays Poll', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'todaysverse_', 'filter_name' => 'Todays Verse', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'todaysverse-kjv', 'filter_name' => 'Todays Verse KJV', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; #------------------------------------------------------------- %GraphSettings = (); %GraphSettings = ( 'data_types' => ['all'], 'graph_type' => 'line', 'grouping' => 'subject', 'filter' => 'wjd', 'filter_name' => 'WJD', 'grouping_times'=> ['hourly','daily','monthly'], ); push @GRAPHS, { %GraphSettings }; mimedefang-3.6/contrib/graphdefang-0.91/graphdefang.pl000077500000000000000000000111041475763067200225750ustar00rootroot00000000000000#!/usr/bin/perl # # GraphDefang -- a set of tools to create graphs of your mimedefang # spam and virus logs. # # Written by: John Kirkland # jpk@bl.org # # Copyright (c) 2002-2003, John Kirkland # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #============================================================================= use strict; use warnings; use vars qw($MYDIR $OUTPUT_DIR $SUMMARYDB $QUIET $NODB $DATAFILE @DATAFILES @GRAPHS %TZ); # Argument parsing use Getopt::Long; use Pod::Usage; $QUIET = 0; # No output $NODB = 0; # Don't use SummaryDB, just produce charts from logfile my $trim = 0; # Trim database my $nomax = 0; # Ignore max date/time my $help = 0; # Show help? my $man = 0; # Show bigger help? my $file; # Log file to parse (optional) GetOptions( 'quiet' => \$QUIET, 'nodb' => \$NODB, 'trim' => \$trim, 'nomax' => \$nomax, 'help|?' => \$help, 'man' => \$man, 'file=s' => \$file ) or pod2usage(2); pod2usage(1) if $help; pod2usage(-exitstatus => 0, -verbose => 2) if $man; # Get the directory from where graphdefang.pl is running use File::Basename (); ($MYDIR) = (File::Basename::dirname($0) =~ /(.*)/); # Get graph configurations require("$MYDIR/graphdefang-config"); # Require the graphdefang library file require ("$MYDIR/graphdefanglib.pl"); # # Path to summary database # $SUMMARYDB = "$MYDIR/SummaryDB.db"; # Do we do a database trim? if ($trim) { print STDERR "Beginning SummaryDB Trim\n" if (!$QUIET); trim_database(); print STDERR "Completed SummaryDB Trim\n" if (!$QUIET); exit; } # Did the user specify a file on the command line? $DATAFILE = $file if (defined($file)); my %DataSummary; if ($DATAFILE) { print STDERR "Processing data file: $DATAFILE\n" if (!$QUIET); # Open DATAFILE and Summarize It %DataSummary = read_and_summarize_data($DATAFILE, $nomax) or die "No valid mimedefang logs in $DATAFILE"; } elsif (@DATAFILES) { foreach my $datafile (@DATAFILES) { print STDERR "Processing data file: $datafile\n" if (!$QUIET); %DataSummary = read_and_summarize_data($datafile, $nomax) or die "No valid mimedefang logs in $datafile"; } } else { # No DATAFILE or DATAFILES specified! die "No DATAFILES specified on the command line or in your config file"; } print STDERR "Processing graphs\n" if (!$QUIET); # Draw graphs foreach my $settings (@GRAPHS) { graph(\%{$settings}, \%DataSummary); } __END__ =head1 graphdefang.pl Application for generating graphs from mimedefang log files. =head1 SYNOPSIS graphdefang.pl [options] Options: --help brief help message --man full documentation --quiet quiet output --nodb do not update SummaryDB --trim trim the SummaryDB --nomax ignore the max date/time in SummaryDB --file optional log file to parse If called with no options, graphdefang.pl will parse the logfile as defined by the $DATAFILE variable. =head1 OPTIONS =over 8 =item B<--help> Print a brief help message and exits. =item B<--man> Prints the manual page and exits. =item B<--quiet> Do not produce status output from mimedefang.pl. =item B<--nodb> Do not use nor update the SummaryDB, just parse the file and draw graphs from it. =item B<--trim> Trim the SummaryDB to cut out old data. It trims out: 1. hourly data older than 1.25x$NUM_HOURS_SUMMARY hours 2. daily data older than 1.25x$NUM_DAYS_SUMMARY days 3. all but top 25 sender, recipient, value1, value2, subject values for all dates prior to the current hour, day, and month.. =item B<--nomax> Ignore the max date/time in the SummaryDB; add all lines from the parsed file to the database. =item B<--file> Optional log file to parse. If this option is not set, graphdefang will use the $DATAFILE variable. =back =head1 DESCRIPTION B will read a file that contains syslog messages from mimedefang, update its internal summary database, and produce graphs as requested by the user. =cut mimedefang-3.6/contrib/graphdefang-0.91/graphdefanglib.pl000066400000000000000000000644531475763067200233000ustar00rootroot00000000000000#!/usr/bin/perl # # GraphDefang -- a set of tools to create graphs of your mimedefang # spam and virus logs. # # Written by: John Kirkland # jpk@bl.org # # Copyright (c) 2002-2003, John Kirkland # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #============================================================================= use strict; use warnings; use Time::Local; use Time::Zone; use Date::Parse; use Date::Format; use Data::Dumper; use File::ReadBackwards; use MLDBM qw(DB_File Storable); use Fcntl; use File::Copy; # for move() function use GD::Graph::linespoints; use GD::Graph::bars; # X and Y Graph Sizes in pixels my $X_GRAPH_SIZE = 700; my $Y_GRAPH_SIZE = 300; # Number of hours, days, and months in the hourly, daily, and monthly charts, respectively. my $NUM_HOURS_SUMMARY = 48; my $NUM_DAYS_SUMMARY = 60; my $NUM_MONTH_SUMMARY = 24; sub get_unixtime_by_timesummary($$) { my $timesummary = shift; my $unixtime = shift; # Get the number of seconds past the day for a given unixtime my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($unixtime); # zero out appropriate seconds,minutes,hours,days,etc... $sec = 0; #if ($timesummary =~ m/hourly|daily|monthly/); $min = 0; #if ($timesummary =~ m/hourly|daily|monthly/); $hour = 0 if ($timesummary ne 'hourly'); $mday = 1 if ($timesummary eq 'monthly'); # get unixtime for our new values $unixtime = timelocal($sec, $min, $hour, $mday, $mon, $year); return $unixtime; } sub trim_database() { my %data_db = (); my %data = (); read_summarydb(\%data, O_RDONLY); # Start the DB Trim my $now = time(); my $trimcounter = 0; # Delete hourly data older than 1.25*$NUM_HOURS_SUMMARY hours my $deletetime = get_unixtime_by_timesummary('hourly',$now - 1.25*$NUM_HOURS_SUMMARY*60*60); foreach my $entrytime (keys %{$data{'hourly'}}) { if ($entrytime < $deletetime) { delete($data{'hourly'}{$entrytime}); $trimcounter++; } } print STDERR "\tTrimmed $trimcounter 'hourly' entries from SummaryDB\n" if (!$QUIET); # Delete daily data older than 1.25*$NUM_DAYS_SUMMARY days $deletetime = get_unixtime_by_timesummary('daily',$now - 1.25*$NUM_DAYS_SUMMARY*60*60*24); $trimcounter=0; foreach my $entrytime (keys %{$data{'daily'}}) { if ($entrytime < $deletetime) { delete $data{'daily'}{$entrytime}; $trimcounter++; } } print STDERR "\tTrimmed $trimcounter 'daily' entries from SummaryDB\n" if (!$QUIET); # Delete all but Top25 entries in hours, days, and months # other than the current one! my @DeleteTimes = ('hourly', 'daily', 'monthly'); $trimcounter = 0; foreach my $deletetime (@DeleteTimes) { my $nowdeletetime = get_unixtime_by_timesummary($deletetime,$now); foreach my $entrytime (keys %{$data{$deletetime}}) { if ($entrytime < $nowdeletetime ) { foreach my $event (keys %{$data{$deletetime}{$entrytime}}) { foreach my $type (keys %{$data{$deletetime}{$entrytime}{$event}}) { if ($type ne 'summary') { my %total = (); foreach my $value (keys %{$data{$deletetime}{$entrytime}{$event}{$type}}) { $total{$value} = $data{$deletetime}{$entrytime}{$event}{$type}{$value}; } # Create list of top 25 items. my $i = 0; my %keep = (); foreach my $TopName (sort { $total{$b} <=> $total{$a} } keys %total) { $keep{$TopName} = 1; $i++; last if $i >= 25; } # delete the entries unless it is in the topList. foreach my $value (keys %{$data{$deletetime}{$entrytime}{$event}{$type}}) { if (!defined($keep{$value})) { delete $data{$deletetime}{$entrytime}{$event}{$type}{$value}; $trimcounter++; } } } } } } } } print STDERR "\tTrimmed $trimcounter 'non top25' entries from SummaryDB\n" if (!$QUIET); backup_and_save_summarydb(\%data); } sub read_summarydb($$) { my $data = shift; my $method = shift; my %data_db; tie (%data_db, 'MLDBM', $SUMMARYDB, $method, 0644) or die "Can't open $SUMMARYDB:$!\n"; %$data = %data_db; untie %data_db; } sub backup_and_save_summarydb($) { my $dataPtr = shift; my %data_db; # Backup the old summarydb file and recreate it using the new data move("$SUMMARYDB", "$SUMMARYDB.bak") or die "Can't move $SUMMARYDB to $SUMMARYDB.bak: $!"; tie (%data_db, 'MLDBM', $SUMMARYDB, O_RDWR|O_CREAT, 0644) or die "Can't open $SUMMARYDB:$!\n"; %data_db = %$dataPtr; untie %data_db or die "Database Save Failed... restore from $SUMMARYDB.bak:$!\n"; # Leave the .bak around in case the original got corrupted. # unlink ("$SUMMARYDB.bak") # or die "Can't unlink $SUMMARYDB.bak:$!\n"; return 1; } sub read_and_summarize_data($$) { use vars qw(%event $text $pid %spamd %user_unknown $event $value1 $value2 $sender $recipient $subject $NumEvents $FoundNewRow $unixtime $MaxDBUnixTime); my $fn = shift; my $nomax = shift; my %data = (); my %data_db = (); # Temporary variable for lookup information %spamd = (); my %NumNewLines; # Set graphtimes my @GraphTimes = ("hourly","daily","monthly"); # Load event processing perl code from the events subdirectory my $dirname = "$MYDIR/event"; opendir(DIR, $dirname) or die "can't opendir $dirname: $!"; while (defined(my $file = readdir(DIR))) { if (!($file =~ m/^\./) and !($file =~ m/^CVS/)) { # do nothing if file starts with '.' opendir(SUBDIR, "$dirname/$file") or die "can't opendir $dirname/$file: $!"; while (defined(my $file2 = readdir(SUBDIR))) { if (!($file2 =~ m/^\./) and !($file2 =~ m/^CVS/)) { require "$dirname/$file/$file2"; } } } } closedir(SUBDIR); closedir(DIR); # Open SummaryDB read_summarydb(\%data, O_RDONLY|O_CREAT) if (!$NODB); # Open log file tie *ZZZ, 'File::ReadBackwards', $fn || die("can't open datafile: $!"); # Get max unixtime value from DBM file # This is left here for backwards compatibility... we now track MAX times per host # to support log files from multiples hosts. $MaxDBUnixTime = 0; if (!$nomax && defined($data{'max'})) { $MaxDBUnixTime = $data{'max'}; # delete the max entry 'cuz we won't use it again delete($data{'max'}); print STDERR "\tConverting to host-based max times\n" if (!$QUIET and !$NODB); print STDERR "\tPrevious Max Unixtime from SummaryDB: $MaxDBUnixTime\n" if (!$QUIET and !$NODB); } # print out the list of max times per host my %ReadMaxHostTime; if (defined($data{'maxhosttime'})) { foreach my $host (sort keys %{$data{'maxhosttime'}}) { $ReadMaxHostTime{$host} = $data{'maxhosttime'}{$host}; print STDERR "\tMax Unixtime from SummaryDB for $host: $data{'maxhosttime'}{$host}\n" if (!$QUIET and !$NODB); } } while () { chomp; # Parse syslog line m/^(\S+\s+\d+\s+\d+:\d+:\d+)\s # datestring -- 1 (\S+)\s # host -- 2 (\S+?) # program -- 3 (?:\[(\d+)\])?:\s # pid -- 4 (?:\[ID\ \d+\ [a-z0-9]+\.[a-z]+\]\ )? # Solaris stuff -- not used (.*)/x; # text -- 5 my $datestring = $1; my $host = $2; my $program = $3; $pid = $4; $text = $5; # Parse date string from syslog using any TIMEZONE info from the config file. if (defined $TZ{$host}) { my $zone = tz2zone($TZ{$host}); $unixtime=str2time($datestring,$zone); } else { $unixtime=str2time($datestring); } # don't examine the line if it is greater than 5 minutes # older than the maximum time in our DB. The 5 minutes # comes from the PID, From, and Relay caching with sendmail # and spamd that occurs below. $MaxDBUnixTime = $ReadMaxHostTime{$host} if (!$nomax && defined($ReadMaxHostTime{$host})); last if ($unixtime < ($MaxDBUnixTime-60*5)); $event = ''; $value1 = ''; $value2 = ''; $sender = ''; $recipient = ''; $subject = ''; $NumEvents = 1; $FoundNewRow = 0; if (defined $event{$program}) { foreach my $subroutine (sort keys %{$event{$program}} ) { $event{$program}{$subroutine}->(); last if ($FoundNewRow); } } if ($FoundNewRow) { # Increment Number of New Lines Found $NumNewLines{$host}++; # rollup hourly, daily, and monthly summaries for every variable foreach my $timesummary (@GraphTimes) { my $summarytime = get_unixtime_by_timesummary($timesummary, $unixtime); $data{$timesummary}{$summarytime}{$event}{'summary'}+=$NumEvents; $data{$timesummary}{$summarytime}{$event}{'value1'}{$value1}+=$NumEvents if ($value1 ne ''); $data{$timesummary}{$summarytime}{$event}{'value2'}{$value2}+=$NumEvents if ($value2 ne ''); $data{$timesummary}{$summarytime}{$event}{'sender'}{$sender}+=$NumEvents if ($sender ne '');; $data{$timesummary}{$summarytime}{$event}{'recipient'}{$recipient}+=$NumEvents if ($recipient ne ''); $data{$timesummary}{$summarytime}{$event}{'subject'}{$subject}+=$NumEvents if ($subject ne ''); # Store the maximum unixtime per timesummary for later reference $data{'maxhosttime'}{$host} = $unixtime if (!defined($data{'maxhosttime'}{$host}) or $unixtime > $data{'maxhosttime'}{$host}); } } } close (ZZZ); if (!$NODB) { if (backup_and_save_summarydb(\%data)) { if (%NumNewLines) { foreach my $host (sort keys %NumNewLines) { print STDERR "\t$NumNewLines{$host} new log lines processed for $host\n" if (!$QUIET); } } else { print STDERR "\t 0 new log lines processed\n" if (!$QUIET); } } } return %data; } sub get_all_data_types($) { my $dataPtr = shift; my %all; my @return_all; # get list of potential event values foreach my $date (keys %{$dataPtr->{monthly}}) { foreach my $data_type (keys %{$dataPtr->{monthly}{$date}}) { $all{$data_type} = 1; } } foreach my $key (sort keys %all) { push @return_all, $key; } return @return_all; } sub graph($$) { my $settings = shift; my $data = shift; foreach my $grouping_time (@{$settings->{grouping_times}}) { $settings->{grouping_time} = $grouping_time; # Set the settings for the graph we've been asked to draw set_graph_settings($settings); print STDERR "\t$settings->{chart_filename}\n" if (!$QUIET); # Get the data for the graph we've been asked to draw my @GraphData = get_graph_data($settings,$data); # Draw Graph if ($settings->{graph_type} eq 'line') { draw_line_graph($settings,\@GraphData); } elsif ($settings->{graph_type} eq 'stacked_bar') { draw_stacked_bar_graph($settings,\@GraphData); } else { die ("Invalid GraphSettings{graph_type} = $settings->{graph_type}"); } } } sub set_graph_settings($) { my $settings = shift; # Set the graph title and filename according to the options chosen # Initialize the title and filename $settings->{chart_title} = ""; $settings->{chart_filename} = ""; my $autotitle = ""; my $autofilename = ""; # Set graph x & y dimensions $settings->{x_graph_size} = $X_GRAPH_SIZE if (!defined($settings->{x_graph_size})); $settings->{y_graph_size} = $Y_GRAPH_SIZE if (!defined($settings->{y_graph_size})); # Add "Top N" to the beginning of the Title if necessary if ($settings->{top_n}) { $autotitle = "Top $settings->{top_n} "; } # Set Data Type Title my $i = 0; foreach my $data_type (@{$settings->{data_types}}) { # Uppercase the first letter of the data_type $autotitle .= "\u$data_type"; $autofilename .= $data_type; $i++; if ( $i == ($#{$settings->{data_types}}) ) { $autotitle .= " and " } elsif ( $i < ($#{$settings->{data_types}}) ) { $autotitle .= ", " } } # Set Grouping Title if ($settings->{grouping} eq 'summary') { $autotitle = $autotitle . " Total Counts "; } elsif ($settings->{grouping} eq 'value1') { if (defined($settings->{value1_title})) { $autotitle = $autotitle . " Counts by $settings->{value1_title}"; } else { $autotitle = $autotitle . " Counts by Value1"; } } elsif ($settings->{grouping} eq 'value2') { if (defined($settings->{value2_title})) { $autotitle = $autotitle . " Counts by $settings->{value2_title}"; } else { $autotitle = $autotitle . " Counts by Value2"; } } elsif ($settings->{grouping} eq 'sender') { $autotitle = $autotitle . " Counts by Sender"; } elsif ($settings->{grouping} eq 'recipient') { $autotitle = $autotitle . " Counts by Recipient"; } elsif ($settings->{grouping} eq 'subject') { $autotitle = $autotitle . " Counts by Subject"; } else { die ("Invalid settings{grouping} value"); } # Put top_n in the filename? if ($settings->{top_n}) { $autofilename .= "_$settings->{top_n}"; } else { $autofilename .= "_"; } $autofilename .= "$settings->{grouping}_$settings->{graph_type}"; # The final portion of the title will be set in the section below if ($settings->{grouping_time} eq 'hourly') { $settings->{x_axis_num_values} = $NUM_HOURS_SUMMARY; # Number of x-axis values on graph $settings->{x_axis_num_values} = $settings->{num_hourly_values} if defined($settings->{num_hourly_values}); $settings->{x_axis_num_sec_incr}= 60*60; # Incremental number of seconds represented by each x-axis value $settings->{x_axis_date_format} = "%h %d, %I%p"; # Format of date string on x-axis $settings->{x_label} = 'Hours'; $settings->{y_label} = 'Counts per Hour'; $settings->{chart_title} = $autotitle . " per Hour (last $settings->{x_axis_num_values} hours)" unless defined($settings->{title}); $autofilename = "hourly_" . $autofilename; } elsif ($settings->{grouping_time} eq 'daily') { $settings->{x_axis_num_values} = $NUM_DAYS_SUMMARY; $settings->{x_axis_num_values} = $settings->{num_daily_values} if defined($settings->{num_daily_values}); $settings->{x_axis_num_sec_incr}= 60*60*24; $settings->{x_axis_date_format} = "%h %d"; $settings->{x_label} = 'Days'; $settings->{y_label} = 'Counts per Day'; $settings->{chart_title} = $autotitle . " per Day (Last $settings->{x_axis_num_values} days)" unless defined($settings->{title}); $autofilename = "daily_" . $autofilename; } elsif ($settings->{grouping_time} eq 'monthly') { $settings->{x_axis_num_values} = $NUM_MONTH_SUMMARY; $settings->{x_axis_num_values} = $settings->{num_monthly_values} if defined($settings->{num_monthly_values}); $settings->{x_axis_num_sec_incr}= 60*60*24*31; $settings->{x_axis_date_format} = "%h"; $settings->{x_label} = 'Months'; $settings->{y_label} = 'Counts per Month'; $settings->{chart_title} = $autotitle . " per Month (Last $settings->{x_axis_num_values} months)" unless defined($settings->{title}); $autofilename = "monthly_" . $autofilename; } if (defined $settings->{filter_name}) { my $filter; ($filter = $settings->{filter_name}) =~ s/\W/_/g; $settings->{chart_title} .= " filtered by $settings->{filter_name}"; $autofilename .= "_$filter"; } # Use the title from graphdefang-config if specified, else use the autotitle $settings->{chart_title} = $settings->{title} if (defined($settings->{title})); # Use the filename from graphdefang-config if specified, else use the autofilename $settings->{chart_filename} = $autofilename; $settings->{chart_filename} = "$settings->{grouping_time}_$settings->{filename}" if (defined($settings->{filename})); $settings->{chart_filename} =~ s/\//_/g; # Replace any '/' chars with '_' } sub get_graph_data($$) { my $settings = shift; my $data = shift; # Calculate the date cutoff for our graph my $currenttime = time(); my $cutofftime; my $currentyear; my $currentmon; my $currentisdst; if ($settings->{grouping_time} eq 'monthly') { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($currenttime); $currentyear = $year; $currentmon = $mon; $currentisdst = $isdst; # Decrement the month/year value n times for (my $i = 0; $i < $settings->{x_axis_num_values}-1; $i++) { if ($mon == 0) { $year--; $mon = 11; } else { $mon--; } } # get unixtime for our new values $mday = 1; # Get around a bug that only shows itself on the 30th or 31st of the month $cutofftime = timelocal($sec, $min, $hour, $mday, $mon, $year); } else { $cutofftime = $currenttime - ($settings->{x_axis_num_sec_incr}*($settings->{x_axis_num_values}-1)); } # Create Data Array for Graph my @GraphData = (); my @TopNNames = (); my %Total = (); my @Legend = (); # Handle data_types = 'all' my $allset; if ($settings->{'data_types'}[0] eq 'all') { $allset = 1; my %all; foreach my $date (keys %{$data->{$settings->{grouping_time}}}) { foreach my $data_type (keys %{$data->{$settings->{grouping_time}}{$date}}) { $all{$data_type} = 1; } } $settings->{'data_types'} = (); foreach my $key (sort keys %all) { push @{$settings->{'data_types'}}, $key; } } # Summarize totals across time interval for (my $time=$cutofftime; $time<=$currenttime; $time += $settings->{x_axis_num_sec_incr}) { my $date = get_unixtime_by_timesummary($settings->{grouping_time},$time); # Get total for summary grouping if ($settings->{'grouping'} eq 'summary') { foreach my $datatype (@{$settings->{'data_types'}}) { if (defined($data->{$settings->{grouping_time}}{$date}{$datatype}{'summary'})) { $Total{$datatype} += $data-> {$settings->{grouping_time}} {$date} {$datatype} {'summary'}; } else { $Total{$datatype} += 0; } } } else { # Get total for other groupings foreach my $datatype (@{$settings->{'data_types'}}) { foreach my $value (keys %{$data-> {$settings->{grouping_time}} {$date} {$datatype} {$settings->{'grouping'}}} ) { $Total{'value'}{$value} += $data-> {$settings->{grouping_time}} {$date} {$datatype} {$settings->{'grouping'}} {$value}; $Total{$date}{$value} += $data-> {$settings->{grouping_time}} {$date} {$datatype} {$settings->{'grouping'}} {$value}; } } } # Recalculate the x_axis_num_sec_incr value if we are graphing monthly. # Determine the current month, increment it by one, and then get a time delta.. if ($settings->{grouping_time} eq 'monthly') { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($date); # Increment the month/year value if ($mon == 11) { $year++; $mon = 0; } else { $mon++; } # Has dst kicked in this month? If so, adjust for it my $dstadjustment = 0; if (($currentyear == $year) && ($currentmon == $mon)) { if ($currentisdst > $isdst) { $dstadjustment = -3600; } elsif ($currentisdst < $isdst) { $dstadjustment = 3600; } else { $dstadjustment = 0; } } # get unixtime for our new values my $newmonthtime = timelocal($sec, $min, $hour, $mday, $mon, $year); $settings->{x_axis_num_sec_incr} = $newmonthtime - $date + $dstadjustment; } } # Sort the TopNNames list so we have it largest to smallest and keep only the top N. if ($settings->{'grouping'} eq 'summary') { foreach my $datatype (@{$settings->{'data_types'}}) { push @Legend, "\u$datatype, Total = $Total{$datatype}"; } } else { my $i=0; foreach my $TopNName (sort { $Total{'value'}{$b} <=> $Total{'value'}{$a} } keys %{$Total{'value'}} ) { if (!defined($settings->{'filter'}) or $TopNName =~ m/$settings->{'filter'}/i) { push @TopNNames, $TopNName; push @Legend, "$TopNName, Total=$Total{'value'}{$TopNName}"; $i++; } last if (defined($settings->{'top_n'}) and $settings->{'top_n'} > 0 and $i >= $settings->{'top_n'} ); } } # If we have no legend, create one so graph doesn't error push @Legend,"No values of this type!" if (!@Legend); @{$settings->{legend}} = @Legend; for (my $time=$cutofftime; $time<=$currenttime; $time += $settings->{x_axis_num_sec_incr}) { my $date = get_unixtime_by_timesummary($settings->{grouping_time},$time); my $datestring = ''; if (defined($TZ{GD_Display})) { my $zone = tz2zone($TZ{GD_Display}); $datestring = time2str($settings->{x_axis_date_format},$date,$zone); } else { $datestring = time2str($settings->{x_axis_date_format},$date); } my $i=0; push @{$GraphData[$i]}, $datestring; if ( $settings->{'grouping'} eq 'summary' ) { foreach my $datatype (@{$settings->{'data_types'}}) { # Data format: #$data{$timesummary}{$summarytime}{$event}{'summary'}++; #$data{$timesummary}{$summarytime}{$event}{'value1'}{$value1}++ $i++; # Set any undefined values to 0 so GD::Graph # has something to graph if ( defined($data-> {$settings->{grouping_time}} {$date} {$datatype} {'summary'}) ) { push @{$GraphData[$i]}, $data-> {$settings->{grouping_time}} {$date} {$datatype} {'summary'}; } else { push @{$GraphData[$i]}, 0; } } } else { # iterate over top_n values if they exist, else push 0 if ($#TopNNames > -1) { foreach my $TopNName (@TopNNames) { $i++; # Set any undefined values to 0 so GD::Graph # has something to graph if ( defined ($Total{$date}{$TopNName}) ) { push @{$GraphData[$i]}, $Total{$date}{$TopNName}; } else { push @{$GraphData[$i]}, 0; } } } else { $i++; push @{$GraphData[$i]}, 0; } } # Recalculate the x_axis_num_sec_incr value if we are graphing monthly. # Determine the current month, increment it by one, and then get a time delta.. if ($settings->{grouping_time} eq 'monthly') { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($date); # Increment the month/year value if ($mon == 11) { $year++; $mon = 0; } else { $mon++; } # Has dst kicked in this month? If so, adjust for it my $dstadjustment = 0; if (($currentyear == $year) && ($currentmon == $mon)) { if ($currentisdst > $isdst) { $dstadjustment = -3600; } elsif ($currentisdst < $isdst) { $dstadjustment = 3600; } else { $dstadjustment = 0; } } # get unixtime for our new values my $newmonthtime = timelocal($sec, $min, $hour, $mday, $mon, $year); $settings->{x_axis_num_sec_incr} = $newmonthtime - $date + $dstadjustment; } } @{$settings->{'data_types'}} = ('all') if ($allset); return @GraphData; } sub draw_line_graph($$) { my $settings = shift; my $data = shift; my $my_graph = new GD::Graph::linespoints($settings->{x_graph_size}, $settings->{y_graph_size}); $my_graph->set( x_label => $settings->{x_label}, y_label => $settings->{y_label}, title => $settings->{chart_title}, x_labels_vertical => 1, x_label_position => 1/2, bgclr => 'white', fgclr => 'gray', boxclr => 'lgray', y_tick_number => 10, y_label_skip => 2, long_ticks => 1, marker_size => 1, skip_undef => 1, line_width => 1, transparent => 0, ); $my_graph->set( dclrs => [ qw(lred lgreen lblue lyellow lpurple cyan lorange dred dgreen dblue dyellow dpurple) ] ); $my_graph->set_legend( @{$settings->{legend}} ); $my_graph->plot($data); save_chart($my_graph, "$OUTPUT_DIR/$settings->{chart_filename}"); } sub draw_stacked_bar_graph($$) { my $settings = shift; my $data = shift; my $my_graph = new GD::Graph::bars($settings->{x_graph_size}, $settings->{y_graph_size}); $my_graph->set( x_label => $settings->{x_label}, y_label => $settings->{y_label}, title => $settings->{chart_title}, x_labels_vertical => 1, x_label_position => 1/2, bgclr => 'white', fgclr => 'gray', boxclr => 'lgray', y_tick_number => 10, y_label_skip => 2, long_ticks => 1, cumulate => 1, transparent => 0, correct_width => 0, ); $my_graph->set( dclrs => [ qw(lred lgreen lblue lyellow lpurple cyan lorange dred dgreen dblue dyellow dpurple) ] ); $my_graph->set_legend( @{$settings->{legend}} ); $my_graph->plot($data); save_chart($my_graph, "$OUTPUT_DIR/$settings->{chart_filename}"); } sub save_chart($$) { my $chart = shift or die "Need a chart!"; my $name = shift or die "Need a name!"; local(*OUT); my $ext = $chart->export_format; open(OUT, ">$name.png") or die "Cannot open $name.$ext for write: $!"; binmode OUT; print OUT $chart->gd->png; close OUT; } 1; mimedefang-3.6/contrib/graphdefang-0.91/reset-max-date.pl000077500000000000000000000032021475763067200231470ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use MLDBM qw(DB_File Storable); use Fcntl; use Time::Local qw( timelocal_modern ); my $SUMMARYDB = "./SummaryDB.db"; my %data_db = (); my %data = (); tie (%data_db, 'MLDBM', $SUMMARYDB, O_RDWR|O_CREAT, 0644) or die "Can't open $SUMMARYDB:$!\n"; %data = %data_db; untie (%data_db); #reset the max time to 12/31/2020 23:59:59 in the local timezone my $year = 2020; my $mon = 11; # 0 - 11 my $mday = 31; # 1 - 31 my $hour = 23; my $min = 59; my $sec = 59; my $unixtime = timelocal_modern($sec, $min, $hour, $mday, $mon, $year); print "Current max time: $data{'max'}\n" if defined $data{'max'}; $data{'max'} = $unixtime; print "Reset max time to $unixtime\n"; # Delete future data from SummaryDB my $deletetime = $unixtime; my $trimcounter = 0; foreach my $entrytime (keys %{$data{'hourly'}}) { if ($entrytime > $deletetime) { delete($data{'hourly'}{$entrytime}); $trimcounter++; } } print "Trimmed $trimcounter 'hourly' future entries from SummaryDB\n"; $trimcounter=0; foreach my $entrytime (keys %{$data{'daily'}}) { if ($entrytime > $deletetime) { delete $data{'daily'}{$entrytime}; $trimcounter++; } } print "Trimmed $trimcounter 'daily' future entries from SummaryDB\n"; $trimcounter=0; foreach my $entrytime (keys %{$data{'monthly'}}) { if ($entrytime > $deletetime) { delete $data{'monthly'}{$entrytime}; $trimcounter++; } } print "Trimmed $trimcounter 'monthly' future entries from SummaryDB\n"; tie (%data_db, 'MLDBM', $SUMMARYDB, O_RDWR|O_CREAT, 0644) or die "Can't open $SUMMARYDB:$!\n"; %data_db = %data; untie (%data_db); mimedefang-3.6/contrib/graphdefang-0.91/web/000077500000000000000000000000001475763067200205475ustar00rootroot00000000000000mimedefang-3.6/contrib/graphdefang-0.91/web/graphdefang.cgi000077500000000000000000000142041475763067200235050ustar00rootroot00000000000000#!/usr/bin/perl # # GraphDefang -- a set of tools to create graphs of your mimedefang # spam and virus logs. # # Written by: John Kirkland # jpk@bl.org # # Copyright (c) 2002-2003, John Kirkland # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #============================================================================= use strict; use warnings; use CGI qw(:standard); use CGI::Carp qw(fatalsToBrowser); use Fcntl; use Data::Dumper; use vars qw($MYDIR $OUTPUT_DIR $SUMMARYDB $QUIET $NODB $NOFILE $DATAFILE @GRAPHS %TZ); # CONFIGURE HERE my $GRAPHDEFANGDIR = '/home/jpk/graphdefang'; $SUMMARYDB = "$GRAPHDEFANGDIR/SummaryDB.db"; # THE REST SHOULD BE OKAY # Require the graphdefang library file require ("$GRAPHDEFANGDIR/graphdefanglib.pl"); # Don't output messages for the web app $QUIET = 1; # Open the SummaryDB file my %data = ();; read_summarydb(\%data, O_RDONLY); # Get the cmd from the parameter list: my $cmd = ''; if (param('cmd')) { $cmd = param('cmd'); } else { $cmd = 'null'; } # Check for errors my $error = ''; my $submit = param('submit'); if ($submit && !param('data_types')) { $error = "No Data Types Selected. Please Try Again!\n"; } # Create image if ($cmd eq 'get-image') { my $grouping_time = param('grouping_time'); my $grouping = param('grouping'); my $graph_type = param('graph_type'); my @data_types = param('data_types'); my $top_n = param('top_n'); my %settings = (); push @{$settings{grouping_times}}, $grouping_time; $settings{grouping} = $grouping; $settings{graph_type} = $graph_type; $settings{data_types} = (); push @{$settings{data_types}}, @data_types; $settings{top_n} = $top_n; $settings{num_hourly_values} = 48; $settings{num_hourly_values} = param('num_hourly_values') if param('num_hourly_values'); $settings{num_daily_values} = 60; $settings{num_daily_values} = param('num_daily_values') if param('num_daily_values'); $settings{num_monthly_values} = 24; $settings{num_monthly_values} = param('num_monthly_values') if param('num_monthly_values'); $settings{x_graph_size} = 700; $settings{x_graph_size} = param('x_graph_size') if param('x_graph_size'); $settings{y_graph_size} = 300; $settings{y_graph_size} = param('y_graph_size') if param('y_graph_size'); $OUTPUT_DIR="/tmp"; graph(\%settings, \%data); my $filename = $settings{chart_filename}; open IMG, "< $OUTPUT_DIR/$filename.png" or die $!; binmode IMG; print header(-type=>'image/png', -expires=>'now'); print while ; close IMG; unlink ("$OUTPUT_DIR/$filename.png"); } elsif ($cmd eq 'null') { my @all_data_types = get_all_data_types(\%data); print header(-type=>'text/html', -expires=>'now'); print start_html(-title=>'GraphDefang CGI Interface'); print h2('Select graph attributes and click the Submit button'); print startform(); print "\n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print "
Data TypesGrouping TimeGroupingGraph Type
\n"; print checkbox_group(-name=>'data_types', -values=>\@all_data_types, -linebreak=>'true'); print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print " \n"; print "
\n"; print radio_group(-name=>'grouping_time', -values=>['hourly'], -default=>'hourly'); print " \n"; print textfield(-name=>'num_hourly_values', -default=>48, -size=>2, -maxlength=>3); print "
\n"; print radio_group(-name=>'grouping_time', -values=>['daily'], -default=>'hourly'); print " \n"; print textfield(-name=>'num_daily_values', -default=>60, -size=>2, -maxlength=>3); print "
\n"; print radio_group(-name=>'grouping_time', -values=>['monthly'], -default=>'hourly'); print " \n"; print textfield(-name=>'num_monthly_values', -default=>24, -size=>2, -maxlength=>3); print "
\n"; print "
\n"; print radio_group(-name=>'grouping', -values=>['summary','sender','recipient','subject','value1','value2'], -linebreak=>'true', -default=>'summary'); print "
Top N: "; print textfield(-name=>'top_n', -default=>10, -size=>2, -maxlength=>2); print "
\n"; print radio_group(-name=>'graph_type', -values=>['line','stacked_bar'], -linebreak=>'true', -default=>'line'); print "
\n"; print "
\n"; print "X Graph Size: "; print textfield(-name=>'x_graph_size', -default=>700, -size=>3, -maxlength=>4); print "  \n"; print "Y Graph Size: "; print textfield(-name=>'y_graph_size', -default=>300, -size=>3, -maxlength=>4); print "

\n"; print submit(-value=>'Submit'); print hidden(-name=>'submit', 'default'=>['1']); print endform(); print "\n" if (param('submit') && !$error); if ($error) { print "$error"; } print end_html(); } mimedefang-3.6/contrib/graphdefang-0.91/web/index.php000066400000000000000000000050271475763067200223730ustar00rootroot00000000000000 GraphDefang

Hourly Daily Monthly

"; print "\"$value\""; print ""; print "

"; } } ?>

Graphs created with GraphDefang.

Interactive CGI Version at: GraphDefang CGI.

Valid XHTML 1.0!

mimedefang-3.6/contrib/greylisting/000077500000000000000000000000001475763067200174775ustar00rootroot00000000000000mimedefang-3.6/contrib/greylisting/greylist-mysql.sql000066400000000000000000000005171475763067200232300ustar00rootroot00000000000000CREATE TABLE greylist ( sender_host_ip VARCHAR(40) NOT NULL, sender VARCHAR(256) NOT NULL, recipient VARCHAR(256) NOT NULL, first_received INT(11) NOT NULL, last_received INT(11) NOT NULL, known_ip TINYINT(1) UNSIGNED NOT NULL, UNIQUE (sender, recipient, sender_host_ip) ); mimedefang-3.6/contrib/greylisting/greylist-pgsql.sql000066400000000000000000000005131475763067200232050ustar00rootroot00000000000000CREATE TABLE greylist ( sender_host_ip VARCHAR(40) NOT NULL, sender VARCHAR(256) NOT NULL, recipient VARCHAR(256) NOT NULL, first_received INTEGER NOT NULL, last_received INTEGER NOT NULL, known_ip SMALLINT NOT NULL, PRIMARY KEY (sender, recipient, sender_host_ip) ); mimedefang-3.6/contrib/greylisting/greylist-sqlite.sql000066400000000000000000000005171475763067200233640ustar00rootroot00000000000000CREATE TABLE greylist ( sender_host_ip VARCHAR(40) NOT NULL, sender VARCHAR(256) NOT NULL, recipient VARCHAR(256) NOT NULL, first_received DATETIME NOT NULL, last_received DATETIME NOT NULL, known_ip UNSIGNED TINYINT(1) NOT NULL, UNIQUE (sender, recipient, sender_host_ip) ); mimedefang-3.6/contrib/greylisting/mimedefang-filter000066400000000000000000000014641475763067200230060ustar00rootroot00000000000000my $dbh; sub filter_initialize { my($entity) = @_; $dbh = DBI->connect($dsn, $username, $auth, {RaiseError => 1}); } sub filter_recipient { my ($recipient, $sender, $ip, $hostname, $first, $helo, $rcpt_mailer, $rcpt_host, $rcpt_addr) = @_; my $ip_address = $ip; # Greylist all the /24 subnet # # my @ip = split(/\./, $ip); # $ip_address = $ip[0] . '.' . $ip[1] . '.' . $ip[2] . '.0'; my $ret = Mail::MIMEDefang::Actions::action_greylist($dbh, $sender, $recipient, $ip_address); if($ret eq "tempfail") { return('TEMPFAIL', "Email greylisted, please come back later"); } elsif($ret eq "reject") { return('REJECT', "Go away or deliver email faster"); } else { return ('CONTINUE', "ok"); } } sub filter_cleanup { $dbh->disconnect(); } mimedefang-3.6/contrib/linuxorg/000077500000000000000000000000001475763067200170065ustar00rootroot00000000000000mimedefang-3.6/contrib/linuxorg/README000066400000000000000000000013511475763067200176660ustar00rootroot00000000000000The files in this directory are installed in /etc/mail/mimedefang/ on mail servers within the Linux Online and Linux Headquarters domains. They are used to filter viruses and SPAM/UCE mail from being received by our staff & users. In order to use the filter in the above directory, either the /etc/sysconfig/mimedefang file needs to be edited to: SUBFILTER=/etc/mail/mimedefang/filter or a symlink needs to be made from /etc/mail/mimedefang-filter to /etc/mail/mimedefang/filter. The contents of each file are described within the header of the file. Please read those files to determine what they contain. Any questions or comments about the files in this directory should be sent to Michael McLagan . mimedefang-3.6/contrib/linuxorg/filter000066400000000000000000000412141475763067200202200ustar00rootroot00000000000000#! /usr/bin/perl ########################################################################## # Copyright (c)2002 - Linux Online, Inc. All rights reserved. # This program may be distributed under the terms of the GNU General # Public License, Version 2. # # Project : Email processing # Component : /etc/mail/mimedefang/filter # Author : Michael McLagan # Creation : 05-May-2002 10:18 # Description : Implement email filtering for various hosted domains # including Linux Online & Linux Headquarters # # Current Revision: # # $Source$ # $Revision$ # $Author$ # $Date$ # # Revision History: # # 16-May-2002 15:44pm - Michael McLagan # Satisfied that required*2 is a safe criteria for tossing # SPAM messages, reworked filter_begin to silently discard # messages that get that score from SpamAssassin # # 09-May-2002 11:13am - Michael McLagan # Fixed missing . in hostname match for trusted hosts. Made spam # deliver regex case insensitive. # # 08-May-2002 19:28pm - Michael McLagan # Updated to include .eml handling from suggested-minimum-... in # 2.10-BETA-3. # # 08-May-2002 12:57pm - Michael McLagan # Added BEGIN routine and @FilterSpamTrusted, @FilterSpamDeliver # # 08-May-2002 10:45am - Michael McLagan # No point filling up postmaster's mailbox with virus notifications. # Moved to /etc/mail/mimedefang/filter to gather related files into # one directory. # # 07-May-2002 11:07am - Michael McLagan # Fixed FilterSpamComplaint so it actually does what it was designed # to do (it was inverted). Updated to use 2.10-BETA-2 variables. # # 06-May-2002 12:28pm - Michael McLagan # Changed "action_add_header" to "action_add_header" for # spam messages so if the message is filtered twice it will # only show 1 set of headers (change adds missing headers) # # 06-May-2002 10:13am - Michael McLagan # Emails to abuse@ and postmaster@ should not be discarded when # they are classified as spam by SpamAssassin. They could very # well be complaint emails from people receiving such a message # and trying to inform us about it. # # $Log$ # Revision 1.3 2002/05/17 12:40:53 dfs # Updated linuxorg filter. # # Revision 1.2 2002/05/10 11:30:20 dfs # Updated linuxorg filter. # # Revision 1.1 2002/05/09 20:18:24 dfs # Added from Michael McLagan. # ########################################################################## # Modules use IO::File; use POSIX qw(strftime); use vars qw($FilterFQDN @FilterHostNames @FilterSpamTrusted @FilterSpamDeliver $FilterSpam $FilterSpamReport $FilterExplained); # MIMEDefang global variables $AdminName = "Postmaster"; $AdminAddress = 'postmaster'; $DaemonName = "Mailer Daemon"; $DaemonAddress = 'MAILER-DAEMON'; $NotifyNoPreamble = 1; $SALocalTestsOnly = 0; $Stupidity{"flatten"} = 0; $Stupidity{"NoMultipleInlines"} = 0; # Check attachments for undesirable extensions. We have no need of # executable code -- binary or text. sub executable ($$$) { my($entity) = shift; my($type) = shift; my($multipart) = shift; return 1 if re_match($entity, '\.(ade|adp|bas|bat|chm|cmd|com|cpl|crt|' . 'dll|exe|hlp|hta|inf|ini|ins|isp|js|jse|' . 'lib|lnk|mdb|mde|msc|msi|msp|mst|ocx|pcd|' . 'pif|reg|scr|sct|shb|shs|sys|url|vb|vbe|' . 'vbs|vxd|wsc|wsf|wsh)'); return 1 if !$multipart && re_match($entity, '\.eml'); return 1 if ($type ne 'message/rfc822') && re_match($entity, '\.eml'); return 0; } # Copy the original headers. The HEADERS file combines multi-line # headers into one -- not what we want for notifying users. Return # a string with the original headers.i sub headers () { my ($msg); $msg = "Original headers:\n"; $msg .= "\n"; # Create a "From " line using the $Sender and time() $msg .= "From: $Sender " . strftime("%a %b %e %T %Y", localtime()) . "\n"; # Read the headers in $headers = IO::File->new("getline) { #Get rid of those nasty CRs s/\r//g; # Read till we get the EOH (blank line) last if /^\n$/; $msg .= ">" . $_; } $headers->close(); $msg .= "\n"; return ($msg); } # Collect the information about this message into a string to use # to return to sender. sub details { my ($msg, $recip); $msg = "----------------------------------------------------------------\n"; $msg .= "Sender : $Sender\n"; foreach $recip (@Recipients) { $msg .= "Recipient : $recip\n"; } $msg .= "Message-Id : $MessageID\n" if $MessageID ne ""; $msg .= "Subject : $Subject\n"; return ($msg); } # Build an email message with information on the virus found # to send back to the originator of the original message sub virus ($) { my ($scanner) = shift; my ($msg, @lines, $value); @lines = split("\n", $VirusScannerMessages); if ($scanner eq "Virus:RAV") { foreach $_ (@lines) { if (/^Version: (.+)\.$/) { $value = $1; last; } } $msg = "We received a message claiming to be from you which contained a\n"; $msg .= "virus according to Reliable Antivirus (RAV) v$value available from\n"; $msg .= "http://www.ravantivirus.com/\n"; } elsif ($scanner eq "Virus:FileScan") { $msg = "We received a message claiming to be from you which contained a\n"; $msg .= "virus according to File::Scan v$File::Scan::VERSION, a Perl module from CPAN at\n"; $msg .= "http://www.cpan.org/authors/id/H/HD/HDIAS\n"; } $msg .= "\n"; $msg .= "This message was not delivered to the intended recipient, it has\n"; $msg .= "been discarded. For information on removing viruses from your\n"; $msg .= "computer, please see http://www.google.com/search?q=antivirus or\n"; $msg .= "http://hotbot.lycos.com/?query=antivirus\n"; $msg .= "\n"; $msg .= " Postmaster\n"; $msg .= "\n"; $msg .= details(); if ($scanner eq 'Virus:RAV') { foreach $_ (@lines) { $msg .= "Virus : $1\n" if (/\sInfected: (.+)$/); } $msg .= "\n"; } elsif ($scanner eq 'Virus:FileScan') { $VirusScannerMessages =~ /found the '(.+)' virus.\n/m; $msg .= "Virus : $1\n"; } $msg .= "\n"; $msg .= headers(); $NotifySenderSubject = "Virus discarded"; action_notify_sender($msg); action_discard(); } # Notify the sender that we are removing the attachment from the # message and tell them to resend it in a different form if they # want it delivered to the recipient. sub rejected ($$$) { my ($entity) = shift; my ($fname) = shift; my ($type) = shift; my ($msg); unless ($FilterSpam) { unless ($FilterExplained) { $msg = "We received a message claiming to be from you which contained an\n"; $msg .= "executable attachment (batch file, script, program, etc). In\n"; $msg .= "order to protect users from malicious programs, we do not accept\n"; $msg .= "these file types thru this mail server. If you need to send the\n"; $msg .= "file to it's intended recipient, you must send it in an archived\n"; $msg .= "and/or compressed format.\n"; $msg .= "\n"; $msg .= "Your email has been sent to the intended recipient without this\n"; $msg .= "file included. A message detailing why it was dropped has been\n"; $msg .= "substituted in it's place.\n"; $msg .= "\n"; $msg .= " Postmaster\n"; $msg .= "\n"; $msg .= details(); $FilterExplained = 1; } $msg .= "Mime type : $type\n"; $msg .= "File name : $fname\n"; $msg .= "\n"; $NotifySenderSubject = "Executable discarded"; action_notify_sender($msg); } $msg = "An executable attachment (batch file, script file, program, etc)\n"; $msg .= "was received with this email and has been discarded. It was replaced\n"; $msg .= "with this message for your security. The sender has been informed\n"; $msg .= "that in order to send you this file they will need to use an archived\n"; $msg .= "and/or compressed format.\n"; $msg .= "\n"; $msg .= " Postmaster\n"; $msg .= "\n"; action_replace_with_warning($msg); } # Take an FQDN and add it to the list of names, FQDN and each higher # level domain name sub break_names ($) { $host = shift; # As long as this represents a valid host name while ($host =~ /[a-z0-9\-]+\.[a-z][a-z]+$/i) { push(@FilterHostNames, $host); # Chop off the first part of the name $host =~ s/^[a-z0-9\-]+\.//i; } } # Determine if the message has this header. If it does change it, # if not add it. sub header ($$) { my ($header) = shift; my ($value) = shift; my ($fh, $exists); $exists = 0; $fh = IO::File->new("< HEADERS"); while (!$exists && ($_ = $fh->getline)) { $exists = /^$header: /i; } $fh->close; if ($exists) { action_change_header($header, $value); } else { action_add_header($header, $value); } } # Has this message already been spam tested by a trusted host? If so, # we don't duplicate another host's work, append multiple headers, etc. sub untested { my ($fh, $site, $host); $fh = IO::File->new("< HEADERS"); while ($_ = $fh->getline) { chomp;s/\r//g; if (/^X-Spam-Scanner: SpamAssassin \d\.\d\d \([^\)]*\) on ([a-z0-9\-\.]+)$/) { $site = $1; last; } } $fh->close; return 1 unless $site; # Check it against our list of trusted hosts foreach $host (@FilterSpamTrusted) { return 0 if $site eq $host; } return 1; } # Initialize a couple of local lists BEGIN { my ($fh); # Our own global variables $FilterFQDN = ""; @FilterHostNames = (); @FilterSpamTrusted = (); @FilterSpamDeliver = (); # These are the "per message" variables $FilterSpam = 0; $FilterSpamReport = ""; $FilterExplained = 0; # A little discovery mission. What host is this running on? # First, the obvious choices push (@FilterHostNames, "localhost"); push (@FilterHostNames, "localhost.localdomain"); # Ask the system who we are $fh = IO::File->new("hostname --fqdn |"); $FilterFQDN = $fh->getline; $fh->close; # Throw away the offending EOLN chomp($FilterFQDN); break_names($FilterFQDN); # One more possibility, pick up known local names $fh = IO::File->new("< /etc/mail/local-host-names"); if ($fh) { while ($_ = $fh->getline) { next if /^\s*#/; # Throw away the offending whitespace and EOLN s/^\s//g;s/\s$//g;chomp(); break_names($_); } $fh->close; } # I deliberately do not check all interfaces on the machine. # While it may be appropriate to check each of the interfaces # by getting it's FQDN and the breaking that up into it's # constitiuent pieces, the work involved is excessive and # could be temporally expensive. Besides, if I do that then # I should start checking mailertable, virtusertable, virtdomtable # and a pile of other places. I'll leave that as an exercise # to the reader. # Keep the spam headers added in by one of our own hosts # read /etc/mail/mimedefang/trusted-hosts $fh = IO::File->new("< /etc/mail/mimedefang/spam-trusted-hosts"); if ($fh) { while ($_ = $fh->getline) { next if /^\s*#/; # Throw away the offending whitespace and EOLN s/^\s//g;s/\s$//g;chomp(); push(@FilterSpamTrusted, $_); } $fh->close; } # Who really, really, really wants their spam delivered to # them? $fh = IO::File->new("< /etc/mail/mimedefang/spam-deliver"); if ($fh) { while ($_ = $fh->getline) { next if /^\s*#/; # Throw away the offending whitespace and EOLN s/^\s//g;s/\s$//g;chomp(); push(@FilterSpamDeliver, $_); } $fh->close; } } # # # mimedefang.pl looks for these functions # # # Check the entire message before it's broken into parts sub filter_begin () { # Initialize a few variables $FilterSpam = 0; $FilterExplained = 0; # Messages containing viruses are rejected return virus('Virus:RAV') if $Features{'Virus:RAV'} && message_contains_virus_rav(); return virus('Virus:FileScan') if $Features{'Virus:FileScan'} && message_contains_virus_filescan(); # All messages smaller than 49512 (48k) are checked for spam # Headers are added to the message to allow filtering by recipients # based on their preferences. Give them the score & tests to work # with. if ($Features{"SpamAssassin"} && (-s "./INPUTMSG" < 49512) && untested()) { my ($hits, $required, $tests, $deliver, $status); # Get SpamAssassin's opinion on this message ($hits, $required, $tests, $FilterSpamReport) = spam_assassin_check(); # Check against our regex list of users to determine if this # high scoring message should be delivered anyways. If not, # indicate to mimedefang.pl that we need to discard the message. if ($hits >= $required * 2) { $deliver = 0; foreach $addr (@Recipients) { # Braces are not desirable $addr =~ s/[<>]//g; foreach $user (@FilterSpamDeliver) { if ($addr =~ /$user/i) { $deliver = 1; last; } } last if $deliver; } # Blast it away! return action_discard() unless ($deliver); } if ($hits >= $required / 2) { header("X-Spam-Scanner", "SpamAssassin $Mail::SpamAssassin::VERSION " . "(http://www.spamassassin.org/) on $FilterFQDN"); header("X-Spam-Score", sprintf("%2.1f / %2.1f: %2.1f%%", $hits, $required, $hits * 100 / $required)); header("X-Spam-Tests", $tests); $status = "Suspected"; if ($hits >= $required) { $FilterSpam = 1; $status = ($hits < $required * 2) ? "Confirmed" : "Assassinated!"; } header("X-Spam-Disposition", $status); } } } # Main filtering routine, handles each part within the message sub filter ($$$$) { my ($entity) = shift; my ($fname) = shift; my ($ext) = shift; my ($type) = shift; # No sense doing any extra work return if message_rejected(); # Local uses don't get executable attachments return rejected($entity, $fname, $type) if (executable($entity, $type, 0)); return action_accept(); } # Filter multipart container parts such as message/rfc822 sub filter_multipart ($$$$) { my ($entity) = shift; my ($fname) = shift; my ($ext) = shift; my ($type) = shift; # No sense doing any extra work return if message_rejected(); # Local uses don't get executable attachments return rejected($entity, $fname, $type) if (executable($entity, $type, 1)); return action_accept(); } # Final action before message is sent to the recipients sub filter_end { my ($entity) = shift; # No sense doing any extra work return if message_rejected(); # If SpamAssassin found SPAM, add report. if ($Features{'SpamAssassin'} && $FilterSpam) { action_add_part($entity, "text/plain", "-suggest", "$FilterSpamReport\n", "SpamAssassinReport.txt", "inline"); } } # DO NOT delete the next line, or Perl will complain. 1; mimedefang-3.6/contrib/linuxorg/spam-deliver000066400000000000000000000025271475763067200213270ustar00rootroot00000000000000########################################################################## # Copyright 2002 - Linux Online, Inc. All rights reserved # This program may be distributed under the terms of the GNU General # Public License, Version 2. # # Project : Email processing # Component : /etc/mail/mimedefang/spam-deliver # Author : Michael McLagan # Creation : 08-May-2002 14:03pm # Description : A list of regex expressions to match against the recipient # of the current message. It is used by the mimedefang filter # to determine if a message declared to be SPAM/UCE by the # SpamAssassin should be delivered rather than discarded. # # Current Revision: # # $Source$ # $Revision$ # $Author$ # $Date$ # # Revision History: # # $Log$ # Revision 1.1 2002/05/09 20:18:24 dfs # Added from Michael McLagan. # ########################################################################## # A couple of addresses that really should not have their mail thrown # away. It could be someone complaining that they got spam from this # domain or a machine serving this domain. The down side of this is # that abuse@ and postmaster@ will receive undesirable spam messages. # Not the best solution but losing valid complaints isn't tolerable # in the least. ^abuse@ ^postmaster@ # Local entries mimedefang-3.6/contrib/linuxorg/spam-trusted-hosts000066400000000000000000000016711475763067200225240ustar00rootroot00000000000000########################################################################## # Copyright 2002 - Linux Online, Inc. All rights reserved # This program may be distributed under the terms of the GNU General # Public License, Version 2. # # Project : Email processing # Component : /etc/mail/mimedefang/spam-deliver # Author : Michael McLagan # Creation : 08-May-2002 14:03pm # Description : A list of hosts that we trust to insert legitimate X-Spam-* # headers. The filter only scans messages which either don't # have the X-Spam-Scanner: header or have a header from an # untrusted host. # # Current Revision: # # $Source$ # $Revision$ # $Author$ # $Date$ # # Revision History: # # $Log$ # Revision 1.1 2002/05/09 20:18:24 dfs # Added from Michael McLagan. # ########################################################################## localhost localhost.localdomain mimedefang-3.6/contrib/munin/000077500000000000000000000000001475763067200162655ustar00rootroot00000000000000mimedefang-3.6/contrib/munin/mimedefang_munin_plugin000077500000000000000000000167561475763067200231120ustar00rootroot00000000000000#!/usr/bin/perl # # Munin plugin to measure important MIMEDefang performance metrics. # # Copyright (C) 2011 Roaring Penguin Software Inc. # # The program may be distributed under the terms of the GNU General # Public License, version 2 or (at your option) any later version. use warnings; use strict; use File::Basename; use IO::Socket::UNIX; my $MX_SOCK = $ENV{'MX_SOCK'} || '/tmp/mx-read.sock'; my $name = basename($0); # Allow forcing for test purposes: # Example: ./mimedefang_munin_plugin force scans_per_sec [config] if (defined($ARGV[0]) && defined($ARGV[1])) { if ($ARGV[0] eq 'force') { shift(@ARGV); $name = shift(@ARGV); } } call_munin_helper($name, $ARGV[0]); exit(0); sub get_mimedefang_data { my ($cmd, $index) = @_; my $sock = IO::Socket::UNIX->new(Type => SOCK_STREAM, Peer => $MX_SOCK); unless ($sock) { print STDERR "Could not connect to multiplexor socket.\n"; exit(1); } $sock->print("$cmd\n"); $sock->flush(); my $line = $sock->getline(); $sock->close(); unless ($line) { print STDERR "No response from multiplexor.\n"; exit(1); } my @array = split(/\s+/, $line); if ($index >= scalar(@array)) { print STDERR "Invalid index (?) Internal error!\n"; exit(1); } return $array[$index]; } sub print_config { my($title, $var, $label, $info) = @_; print "graph_category MIMEDefang\n"; print "graph_title $title\n"; print "graph_scale no\n"; print "graph_vlabel $var\n"; print "$var.label $label\n"; print "$var.type GAUGE\n"; print "graph_info $info\n"; exit(0); } sub call_munin_helper { my ($name, $arg) = @_; $arg ||= ''; $name =~ s/^mimedefang_//; if (defined(&{'munin_handle_' . $name})) { no strict 'refs'; &{'munin_handle_' . $name}($arg); use strict 'refs'; } else { munin_handle_default($name, $arg); } } sub munin_handle_default { my ($name, $arg) = @_; if ($name eq 'munin_plugin') { print STDERR "Please symlink this file to the measurement you want.\nRun 'perldoc $0' for more information.\n"; } else { print STDERR "Unknown parameter '$name'\n"; } exit(1); } sub munin_handle_busy_workers { my ($arg) = @_; print_config('Busy MIMEDefang Workers', 'workers', 'Busy workers', 'The average number of busy MIMEDefang worker processes.') if ($arg eq 'config'); my $ans = get_mimedefang_data('load', 7); print "workers.value $ans\n"; exit(0); } sub munin_handle_scan_time { my ($arg) = @_; print_config('MIMEDefang Scan Time', 'time', 'Scan time (ms)', 'The average scan time in milliseconds.') if ($arg eq 'config'); my $ans = get_mimedefang_data('load', 11); print "time.value $ans\n"; exit(0); } sub munin_handle_rcpt_time { my ($arg) = @_; print_config('MIMEDefang RCPT Time', 'time', 'RCPT time (ms)', 'The average RCPT time in milliseconds.') if ($arg eq 'config'); my $ans = get_mimedefang_data('load-recipok', 11); print "time.value $ans\n"; exit(0); } sub munin_handle_mail_time { my ($arg) = @_; print_config('MIMEDefang MAIL Time', 'time', 'MAIL time (ms)', 'The average MAIL time in milliseconds.') if ($arg eq 'config'); my $ans = get_mimedefang_data('load-senderok', 11); print "time.value $ans\n"; exit(0); } sub munin_handle_relay_time { my ($arg) = @_; print_config('MIMEDefang relayok Time', 'time', 'relayok time (ms)', 'The average relayok time in milliseconds.') if ($arg eq 'config'); my $ans = get_mimedefang_data('load-relayok', 11); print "time.value $ans\n"; exit(0); } sub munin_handle_scans_per_sec { my ($arg) = @_; print_config('MIMEDefang Scans per Second', 'scans', 'Scans per Second', 'The number of scans per second.') if ($arg eq 'config'); my $ans = get_mimedefang_data('load', 3); # Divide by ten minutes to get scans/second $ans /= 600.0; print "scans.value $ans\n"; exit(0); } sub munin_handle_rcpts_per_sec { my ($arg) = @_; print_config('MIMEDefang RCPTs per Second', 'rcpts', 'RCPTs per Second', 'The number of RCPTs per second.') if ($arg eq 'config'); my $ans = get_mimedefang_data('load-recipok', 3); $ans /= 600.0; print "rcpts.value $ans\n"; exit(0); } sub munin_handle_mails_per_sec { my ($arg) = @_; print_config('MIMEDefang MAILs per Second', 'mails', 'MAILs per Second', 'The number of MAILs per second.') if ($arg eq 'config'); my $ans = get_mimedefang_data('load-senderok', 3); $ans /= 600.0; print "mails.value $ans\n"; exit(0); } sub munin_handle_busy_workers_by_cmd { my ($arg) = @_; my @cmds = qw(scan relayok senderok recipok other); my %counts; # Config is different for this one if ($arg eq 'config') { print "graph_category MIMEDefang\n"; print "graph_title Busy Workers by Command\n"; print "graph_scale no\n"; print "graph_vlabel Workers\n"; print "graph_info Busy workers categorized by current command\n"; foreach my $cmd (@cmds) { print "$cmd.label $cmd\n"; print "$cmd.type GAUGE\n"; if ($cmd eq 'scan') { print "$cmd.draw AREA\n"; } else { print "$cmd.draw STACK\n"; } } exit(0); } foreach my $cmd (@cmds) { $counts{$cmd} = 0; } # Get the info my $sock = IO::Socket::UNIX->new(Type => SOCK_STREAM, Peer => $MX_SOCK); unless ($sock) { print STDERR "Could not connect to multiplexor socket.\n"; exit(1); } $sock->print("busyworkers\n"); $sock->flush(); while (my $line = $sock->getline()) { if ($line =~ /^\d+ [A-Z] \d+ (\S+)/) { my $cmd = $1; if ($cmd ne 'other' && grep { $_ eq $cmd } @cmds) { $counts{$cmd}++; } else { $counts{other}++; } } else { $counts{other}++; } } $sock->close(); foreach my $cmd (@cmds) { print "$cmd.value " . $counts{$cmd} . "\n"; } exit(0); } sub munin_handle_relayoks_per_sec { my ($arg) = @_; print_config('MIMEDefang relayoks per Second', 'relayoks', 'relayoks per Second', 'The number of relayoks per second.') if ($arg eq 'config'); my $ans = get_mimedefang_data('load-relayok', 3); $ans /= 600.0; print "relayoks.value $ans\n"; exit(0); } __END__ =head1 NAME mimedefang_munin_plugin - Plugin to monitor various MIMEDefang statistics. =head1 CONFIGURATION The environment variable MX_SOCK should be set to a (world-readable) multiplexor socket. See the -a option to L. If MX_SOCK is not set, the socket defaults to /tmp/mx-read.sock. =head1 USAGE This plugin returns different values depending on what its filename is. You can make the following symbolic links in the munin plugins directory. (All measurements are the average over the last 10 minutes unless otherwise indicated.) =over =item mimedefang_busy_workers Return the average number of busy workers. =item mimedefang_scan_time Return the scan time in milliseconds. =item mimedefang_rcpt_time Return the RCPT-handling time in milliseconds. =item mimedefang_mail_time Return the MAIL-handling time in milliseconds. =item mimedefang_relay_time Return the "relay_ok" handling time in milliseconds. =item mimedefang_scans_per_sec Return the number of scans per second. =item mimedefang_rcpts_per_sec Return the number of RCPTs per second. =item mimedefang_mails_per_sec Return the number of MAILs per second. =item mimedefang_relayoks_per_sec Return the number of "relay_oks" per second. =item mimedefang_busy_workers_by_cmd Return the busy workers by command. Unlike the other measurements, this measurement is a snapshot, not an average over the last 10 minutes. As a result, graphs for this measurement are likely to be spikier than the other graphs. =back mimedefang-3.6/contrib/word-to-html000066400000000000000000000004371475763067200174230ustar00rootroot00000000000000#!/bin/sh # MIMEDefang filter to convert MS Word documents to HTML # No core files, thanks! ulimit -c 0 # Don't spend more than 30s CPU time on the job ulimit -t 30 prog="/usr/local/bin/wvHtml" if test -x $prog ; then exec $prog FILTERINPUT > FILTEROUTPUT 2>/dev/null fi exit 1 mimedefang-3.6/docker-compose-postfix.yml000066400000000000000000000003671475763067200206340ustar00rootroot00000000000000name: mimedefang-postfix services: mimedefang-postfix: container_name: mimedefang-postfix image: mimedefang/postfix:latest working_dir: /tests volumes: - .:/tests command: sh t/dockerPostfix.sh mimedefang-3.6/docker-compose-sendmail.yml000066400000000000000000000003741475763067200207320ustar00rootroot00000000000000name: mimedefang-sendmail services: mimedefang-sendmail: container_name: mimedefang-sendmail image: mimedefang/sendmail:latest working_dir: /tests volumes: - .:/tests command: sh t/dockerSendmail.sh mimedefang-3.6/drop_privs.c000066400000000000000000000027051475763067200160360ustar00rootroot00000000000000/*********************************************************************** * * drop_privs.c * * Defines the "drop_privs" function which switches to specified user name. * * Copyright (C) 2002-2003 by Roaring Penguin Software Inc. * http://www.roaringpenguin.com * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #include "config.h" #include #include #include #include /********************************************************************** * %FUNCTION: drop_privs * %ARGUMENTS: * user -- name of user to become * uid, gid -- uid and gid to use * %RETURNS: * 0 on success; -1 on failure. * %DESCRIPTION: * Switches uid to uid of "user"; also enters that user's group and calls * initgroups. ***********************************************************************/ int drop_privs(char const *user, uid_t uid, gid_t gid) { /* Call initgroups */ #ifdef HAVE_INITGROUPS if (initgroups((char *) user, gid) < 0) { syslog(LOG_ERR, "drop_privs: initgroups for '%s' failed: %m", user); return -1; } #endif /* Call setgid */ if (setgid(gid) < 0) { syslog(LOG_ERR, "drop_privs: setgid for '%s' failed: %m", user); return -1; } /* Finally, call setuid */ if (setuid(uid) < 0) { syslog(LOG_ERR, "drop_privs: setuid for '%s' failed: %m", user); return -1; } return 0; } mimedefang-3.6/dynbuf.c000066400000000000000000000070631475763067200151400ustar00rootroot00000000000000/********************************************************************** * * DYNBUF.C * * Implementation of functions for manipulating dynamic * buffers. * * This file was part of REMIND. * Copyright (C) 1992-1998 by Dianne Skoll * Copyright (C) 1999-2007 by Roaring Penguin Software Inc. * This program may be distributed under the terms of the GNU General * Public License, Version 2. * **********************************************************************/ #include "dynbuf.h" #include #include /********************************************************************** %FUNCTION: dbuf_makeroom %ARGUMENTS: dbuf -- pointer to a dynamic buffer n -- size to expand to %RETURNS: 0 if all went well, -1 otherwise. %DESCRIPTION: Doubles the size of dynamic buffer until it has room for at least 'n' characters, not including trailing '\0' **********************************************************************/ static int dbuf_makeroom(dynamic_buffer *dbuf, int n) { /* Double size until it's greater than n (strictly > to leave room for trailing '\0' */ int size = dbuf->allocated_len; char *buf; if (size > n) return 0; while (size <= n) { size *= 2; } /* Allocate memory */ buf = (char *) malloc(size); if (!buf) return -1; /* Copy contents */ strcpy(buf, dbuf->buffer); /* Free old contents if necessary */ if (dbuf->buffer != dbuf->static_buf) free(dbuf->buffer); dbuf->buffer = buf; dbuf->allocated_len = size; return 0; } /********************************************************************** %FUNCTION: dbuf_init %ARGUMENTS: dbuf -- pointer to a dynamic buffer %RETURNS: Nothing %DESCRIPTION: Initializes a dynamic buffer **********************************************************************/ void dbuf_init(dynamic_buffer *dbuf) { dbuf->buffer = dbuf->static_buf; dbuf->len = 0; dbuf->allocated_len = DBUF_STATIC_SIZE; dbuf->buffer[0] = 0; } /********************************************************************** %FUNCTION: dbuf_putc %ARGUMENTS: dbuf -- pointer to a dynamic buffer c -- character to append to buffer %RETURNS: 0 if all went well; -1 if out of memory %DESCRIPTION: Appends a character to the buffer. **********************************************************************/ int dbuf_putc(dynamic_buffer *dbuf, char const c) { if (dbuf->allocated_len <= dbuf->len+1) { if (dbuf_makeroom(dbuf, dbuf->len+1) != 0) return -1; } dbuf->buffer[dbuf->len++] = c; dbuf->buffer[dbuf->len] = 0; return 0; } /********************************************************************** %FUNCTION: dbuf_puts %ARGUMENTS: dbuf -- pointer to a dynamic buffer str -- string to append to buffer %RETURNS: OK if all went well; E_NO_MEM if out of memory %DESCRIPTION: Appends a string to the buffer. **********************************************************************/ int dbuf_puts(dynamic_buffer *dbuf, char const *str) { int l = strlen(str); if (!l) return 0; if (dbuf->allocated_len <= dbuf->len + l) { if (dbuf_makeroom(dbuf, dbuf->len+l) != 0) return -1; } strcpy((dbuf->buffer+dbuf->len), str); dbuf->len += l; return 0; } /********************************************************************** %FUNCTION: dbuf_free %ARGUMENTS: dbuf -- pointer to a dynamic buffer %RETURNS: Nothing %DESCRIPTION: Frees and reinitializes a dynamic buffer **********************************************************************/ void dbuf_free(dynamic_buffer *dbuf) { if (dbuf->buffer != dbuf->static_buf) free(dbuf->buffer); dbuf_init(dbuf); } mimedefang-3.6/dynbuf.h000066400000000000000000000016701475763067200151430ustar00rootroot00000000000000/*********************************************************************** * * DYNBUF.H * * Declaration of functions for manipulating dynamic buffers * * This file was part of REMIND. * Copyright (C) 1992-1998 by Dianne Skoll * Copyright (C) 1999-2007 by Roaring Penguin Software Inc. * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #ifndef DYNBUF_H #define DYNBUF_H #define DBUF_STATIC_SIZE 4096 typedef struct { char *buffer; int len; int allocated_len; char static_buf[DBUF_STATIC_SIZE]; } dynamic_buffer; void dbuf_init(dynamic_buffer *dbuf); int dbuf_putc(dynamic_buffer *dbuf, char const c); int dbuf_puts(dynamic_buffer *dbuf, char const *str); void dbuf_free(dynamic_buffer *dbuf); #define DBUF_VAL(buf_ptr) ((buf_ptr)->buffer) #define DBUF_LEN(buf_ptr) ((buf_ptr)->len) #endif /* DYNBUF_H */ mimedefang-3.6/embperl.c000066400000000000000000000066301475763067200152760ustar00rootroot00000000000000/*********************************************************************** * * embperl.c * * Routines for manipulating embedded Perl interpreter * * Copyright (C) 2003 by Roaring Penguin Software Inc. * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #ifdef EMBED_PERL #include #include #include #include #ifdef PERL_SET_CONTEXT #define PSC(x) PERL_SET_CONTEXT(x) #else #define PSC(x) (void) 0 #endif #define PERLPARSE_NUM_ARGS 6 static PerlInterpreter *my_perl = NULL; extern void xs_init (pTHX); void init_embedded_interpreter(int argc, char **argv, char **env) { #ifdef PERL_SYS_INIT3 PERL_SYS_INIT3(&argc, &argv, &env); #endif } void deinit_embedded_interpreter(void) { #ifdef PERL_SYS_TERM PERL_SYS_TERM(); #endif } void term_embedded_interpreter(void) { if (my_perl != NULL) { PSC(my_perl); PERL_SET_INTERP(my_perl); PL_perl_destruct_level = 1; perl_destruct(my_perl); perl_free(my_perl); my_perl = NULL; } } static char **argv = NULL; int make_embedded_interpreter(char const *progPath, char const *subFilter, int wantStatusReports, char **env) { int argc; /* Why do we malloc argv instead of making it static? Because on some systems, Perl makes horrendously evil assumptions about the alignment of argv... we use malloc to get guaranteed worst-case alignment. Yes, the Perl innards are completely horrible. */ if (!argv) { argv = (char **) malloc(PERLPARSE_NUM_ARGS * sizeof(char *)); if (!argv) { fprintf(stderr, "Out of memory allocating argv[] array for embedded Perl!"); syslog(LOG_ERR, "Out of memory allocating argv[] array for embedded Perl!"); exit(EXIT_FAILURE); } } memset(argv, 0, PERLPARSE_NUM_ARGS * sizeof(char *)); if (my_perl != NULL) { #ifdef SAFE_EMBED_PERL PSC(my_perl); PERL_SET_INTERP(my_perl); PL_perl_destruct_level = 1; perl_destruct(my_perl); perl_free(my_perl); my_perl = NULL; #else syslog(LOG_WARNING, "Cannot destroy and recreate a Perl interpreter safely on this platform. Filter rules will NOT be reread."); return 0; #endif } if (subFilter) { argv[0] = ""; argv[1] = (char *) progPath; argv[2] = "-f"; argv[3] = (char *) subFilter; if (wantStatusReports) { argv[4] = "-embserveru"; } else { argv[4] = "-embserver"; } argv[5] = NULL; argc = 5; } else { argv[0] = ""; argv[1] = (char *) progPath; if (wantStatusReports) { argv[2] = "-embserveru"; } else { argv[2] = "-embserver"; } argv[3] = NULL; argc = 3; } my_perl = perl_alloc(); if (!my_perl) { errno = ENOMEM; return -1; } PSC(my_perl); PERL_SET_INTERP(my_perl); PL_perl_destruct_level = 1; perl_construct(my_perl); perl_parse(my_perl, xs_init, argc, argv, NULL); perl_run(my_perl); return 0; } /* Perl caches $$ so the PID is wrong after we fork. This routine fixes it up */ static void embperl_fix_pid(void) { GV *tmpgv; if ((tmpgv = gv_fetchpv("$",TRUE, SVt_PV))) { SvREADONLY_off(GvSV(tmpgv)); sv_setiv(GvSV(tmpgv), PerlProc_getpid()); SvREADONLY_on(GvSV(tmpgv)); } } void run_embedded_filter(void) { char *args[] = { NULL }; PSC(my_perl); PERL_SET_INTERP(my_perl); embperl_fix_pid(); perl_call_argv("do_main_loop", G_DISCARD | G_NOARGS, args); } #endif mimedefang-3.6/event.c000066400000000000000000000533121475763067200147700ustar00rootroot00000000000000/*********************************************************************** * * event.c * * Abstraction of select call into "event-handling" to make programming * easier. * * Copyright (C) 2001-2003 Roaring Penguin Software Inc. * * This program may be distributed according to the terms of the GNU * General Public License, version 2 or (at your option) any later version. * * Copyright (C) 2001 by Roaring Penguin Software Inc. * ***********************************************************************/ #include "event.h" #include #include #include #include static void DestroySelector(EventSelector *es); static void DestroyHandler(EventHandler *eh); static void DoPendingChanges(EventSelector *es); #ifdef DEBUG_EVENT #ifndef EVENT_USE_POLL static void print_select_sets(char const *tag, int maxfdp1, fd_set *rd, fd_set *wr); #endif #endif #ifdef EVENT_USE_POLL static struct pollfd *Pollfds = NULL; static EventHandler **Fd_to_eh = NULL; static int Size_of_pollfds = 0; static int adjust_pollfds(int num) { int new_size = Size_of_pollfds * 2; if (new_size < num) new_size = num; if (new_size < 16) new_size = 16; Pollfds = realloc(Pollfds, (size_t) new_size * sizeof(struct pollfd)); if (!Pollfds) { return -1; } Fd_to_eh = realloc(Fd_to_eh, (size_t) new_size * sizeof(EventHandler *)); if (!Fd_to_eh) { return -1; } Size_of_pollfds = new_size; return 0; } #endif /********************************************************************** * %FUNCTION: set_cloexec * %ARGUMENTS: * fd -- file descriptor * %RETURNS: * Nothing * %DESCRIPTION: * Sets the FD_CLOEXEC flag on descriptor ***********************************************************************/ int set_cloexec(int fd) { int flags; flags = fcntl(fd, F_GETFD); if (flags >= 0) { flags |= FD_CLOEXEC; if(fcntl(fd, F_SETFD, flags) == 0) { return 0; } return -1; } return -1; } /********************************************************************** * %FUNCTION: set_nonblocking * %ARGUMENTS: * fd -- file descriptor * %RETURNS: * 0 on success, -1 on failure * %DESCRIPTION: * Sets the O_NONBLOCK flag on fd ***********************************************************************/ int set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags < 0) return flags; if (!(flags & O_NONBLOCK)) { if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { return -1; } } return 0; } /********************************************************************** * %FUNCTION: Event_CreateSelector * %ARGUMENTS: * None * %RETURNS: * A newly-allocated EventSelector, or NULL if out of memory. * %DESCRIPTION: * Creates a new EventSelector. ***********************************************************************/ EventSelector * Event_CreateSelector(void) { EventSelector *es = malloc(sizeof(EventSelector)); if (!es) return NULL; es->handlers = NULL; es->nestLevel = 0; es->destroyPending = 0; es->opsPending = 0; EVENT_DEBUG(("CreateSelector() -> %p\n", (void *) es)); return es; } /********************************************************************** * %FUNCTION: Event_DestroySelector * %ARGUMENTS: * es -- EventSelector to destroy * %RETURNS: * Nothing * %DESCRIPTION: * Destroys an EventSelector. Destruction may be delayed if we * are in the HandleEvent function. ***********************************************************************/ void Event_DestroySelector(EventSelector *es) { if (es->nestLevel) { es->destroyPending = 1; es->opsPending = 1; return; } DestroySelector(es); } #ifndef EVENT_USE_POLL /********************************************************************** * %FUNCTION: Event_HandleEventUsingSelect * %ARGUMENTS: * es -- EventSelector * %RETURNS: * 0 if OK, non-zero on error. errno is set appropriately. * %DESCRIPTION: * Handles a single event (uses select() to wait for an event.) ***********************************************************************/ int Event_HandleEventUsingSelect(EventSelector *es) { fd_set readfds, writefds; fd_set *rd, *wr; unsigned int flags; struct timeval abs_timeout, now; struct timeval timeout; struct timeval *tm; EventHandler *eh; int r = 0; int errno_save = 0; int foundTimeoutEvent = 0; int foundReadEvent = 0; int foundWriteEvent = 0; int maxfd = -1; int pastDue; EVENT_DEBUG(("Enter Event_HandleEvent(es=%p)\n", (void *) es)); /* Silence stupid compiler warning */ abs_timeout.tv_sec = 0; abs_timeout.tv_usec = 0; /* Build the select sets */ FD_ZERO(&readfds); FD_ZERO(&writefds); for (eh=es->handlers; eh; eh=eh->next) { if (eh->flags & EVENT_FLAG_DELETED) continue; if (eh->flags & EVENT_FLAG_READABLE) { foundReadEvent = 1; FD_SET(eh->fd, &readfds); if (eh->fd > maxfd) maxfd = eh->fd; } if (eh->flags & EVENT_FLAG_WRITEABLE) { foundWriteEvent = 1; FD_SET(eh->fd, &writefds); if (eh->fd > maxfd) maxfd = eh->fd; } if (eh->flags & EVENT_TIMER_BITS) { if (!foundTimeoutEvent) { abs_timeout = eh->tmout; foundTimeoutEvent = 1; } else { if (eh->tmout.tv_sec < abs_timeout.tv_sec || (eh->tmout.tv_sec == abs_timeout.tv_sec && eh->tmout.tv_usec < abs_timeout.tv_usec)) { abs_timeout = eh->tmout; } } } } if (foundReadEvent) { rd = &readfds; } else { rd = NULL; } if (foundWriteEvent) { wr = &writefds; } else { wr = NULL; } if (foundTimeoutEvent) { gettimeofday(&now, NULL); /* Convert absolute timeout to relative timeout for select */ timeout.tv_usec = abs_timeout.tv_usec - now.tv_usec; timeout.tv_sec = abs_timeout.tv_sec - now.tv_sec; if (timeout.tv_usec < 0) { timeout.tv_usec += 1000000; timeout.tv_sec--; } if (timeout.tv_sec < 0 || (timeout.tv_sec == 0 && timeout.tv_usec < 0)) { timeout.tv_sec = 0; timeout.tv_usec = 0; } tm = &timeout; } else { tm = NULL; } if (foundReadEvent || foundWriteEvent || foundTimeoutEvent) { for(;;) { #ifdef DEBUG_EVENT print_select_sets("before", maxfd+1, rd, wr); #endif r = select(maxfd+1, rd, wr, NULL, tm); #ifdef DEBUG_EVENT print_select_sets("after ", maxfd+1, rd, wr); #endif if (r < 0) { if (errno == EINTR) continue; } break; } } if (foundTimeoutEvent) { gettimeofday(&now, NULL); } errno_save = errno; es->nestLevel++; if (r >= 0) { /* Call handlers */ for (eh=es->handlers; eh; eh=eh->next) { /* Pending delete for this handler? Ignore it */ if (eh->flags & EVENT_FLAG_DELETED) continue; flags = 0; if ((eh->flags & EVENT_FLAG_READABLE) && FD_ISSET(eh->fd, &readfds)) { flags |= EVENT_FLAG_READABLE; } if ((eh->flags & EVENT_FLAG_WRITEABLE) && FD_ISSET(eh->fd, &writefds)) { flags |= EVENT_FLAG_WRITEABLE; } if (eh->flags & EVENT_TIMER_BITS) { pastDue = (eh->tmout.tv_sec < now.tv_sec || (eh->tmout.tv_sec == now.tv_sec && eh->tmout.tv_usec <= now.tv_usec)); if (pastDue) { flags |= EVENT_TIMER_BITS; if (eh->flags & EVENT_FLAG_TIMER) { /* Timer events are only called once */ es->opsPending = 1; eh->flags |= EVENT_FLAG_DELETED; } } } /* Do callback */ if (flags) { EVENT_DEBUG(("Enter callback: eh=%p flags=%u\n", eh, flags)); eh->fn(es, eh->fd, flags, eh->data); EVENT_DEBUG(("Leave callback: eh=%p flags=%u\n", eh, flags)); } } } es->nestLevel--; if (!es->nestLevel && es->opsPending) { DoPendingChanges(es); } errno = errno_save; return r; } #endif #ifdef EVENT_USE_POLL /********************************************************************** * %FUNCTION: Event_HandleEventUsingPoll * %ARGUMENTS: * es -- EventSelector * %RETURNS: * 0 if OK, non-zero on error. errno is set appropriately. * %DESCRIPTION: * Handles a single event (uses poll() to wait for an event.) ***********************************************************************/ int Event_HandleEventUsingPoll(EventSelector *es) { struct timeval abs_timeout, now; struct timeval timeout; int tm; EventHandler *eh; int r = 0; int errno_save = 0; int foundTimeoutEvent = 0; int pastDue; int num_handlers = 0; int nfds; int i; EVENT_DEBUG(("Enter Event_HandleEventUsingPoll(es=%p)\n", (void *) es)); /* Silence stupid compiler warning */ abs_timeout.tv_sec = 0; abs_timeout.tv_usec = 0; /* Build the pollfds array */ eh = es->handlers; while(eh) { num_handlers++; eh = eh->next; } if (num_handlers > Size_of_pollfds) { if (adjust_pollfds(num_handlers) < 0) { return -1; } } eh = es->handlers; nfds = 0; for (eh=es->handlers; eh; eh=eh->next) { if (eh->flags & EVENT_FLAG_DELETED) continue; eh->pollflags = 0; if (eh->flags & (EVENT_FLAG_READABLE|EVENT_FLAG_WRITEABLE)) { Pollfds[nfds].fd = eh->fd; Pollfds[nfds].events = 0; Fd_to_eh[nfds] = eh; if (eh->flags & EVENT_FLAG_READABLE) { Pollfds[nfds].events |= POLLIN; } if (eh->flags & EVENT_FLAG_WRITEABLE) { Pollfds[nfds].events |= POLLOUT; } nfds++; } if (eh->flags & EVENT_TIMER_BITS) { if (!foundTimeoutEvent) { abs_timeout = eh->tmout; foundTimeoutEvent = 1; } else { if (eh->tmout.tv_sec < abs_timeout.tv_sec || (eh->tmout.tv_sec == abs_timeout.tv_sec && eh->tmout.tv_usec < abs_timeout.tv_usec)) { abs_timeout = eh->tmout; } } } } if (foundTimeoutEvent) { gettimeofday(&now, NULL); /* Convert absolute timeout to relative timeout for poll */ timeout.tv_usec = abs_timeout.tv_usec - now.tv_usec; timeout.tv_sec = abs_timeout.tv_sec - now.tv_sec; if (timeout.tv_usec < 0) { timeout.tv_usec += 1000000; timeout.tv_sec--; } if (timeout.tv_sec < 0 || (timeout.tv_sec == 0 && timeout.tv_usec < 0)) { timeout.tv_sec = 0; timeout.tv_usec = 0; } tm = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000); } else { tm = -1; } if (nfds || foundTimeoutEvent) { for(;;) { r = poll(Pollfds, nfds, tm); if (r < 0) { if (errno == EINTR) continue; } break; } } if (foundTimeoutEvent) { gettimeofday(&now, NULL); } errno_save = errno; es->nestLevel++; if (r >= 0) { /* Go through poll set and set pollflags */ for (i=0; iflags & EVENT_FLAG_READABLE) { eh->pollflags |= EVENT_FLAG_READABLE; } } if (Pollfds[i].revents & POLLOUT) { if (eh->flags & EVENT_FLAG_WRITEABLE) { eh->pollflags |= EVENT_FLAG_WRITEABLE; } } } /* Call handlers */ for (eh=es->handlers; eh; eh=eh->next) { /* Pending delete for this handler? Ignore it */ if (eh->flags & EVENT_FLAG_DELETED) continue; if (eh->flags & EVENT_TIMER_BITS) { pastDue = (eh->tmout.tv_sec < now.tv_sec || (eh->tmout.tv_sec == now.tv_sec && eh->tmout.tv_usec <= now.tv_usec)); if (pastDue) { eh->pollflags |= EVENT_TIMER_BITS; if (eh->flags & EVENT_FLAG_TIMER) { /* Timer events are only called once */ es->opsPending = 1; eh->flags |= EVENT_FLAG_DELETED; } } } /* Do callback */ if (eh->pollflags) { EVENT_DEBUG(("Enter callback: eh=%p flags=%u\n", eh, eh->pollflags)); eh->fn(es, eh->fd, eh->pollflags, eh->data); EVENT_DEBUG(("Leave callback: eh=%p flags=%u\n", eh, eh->pollflags)); } } } es->nestLevel--; if (!es->nestLevel && es->opsPending) { DoPendingChanges(es); } errno = errno_save; return r; } #endif /********************************************************************** * %FUNCTION: Event_AddHandler * %ARGUMENTS: * es -- event selector * fd -- file descriptor to watch * flags -- combination of EVENT_FLAG_READABLE and EVENT_FLAG_WRITEABLE * fn -- callback function to call when event is triggered * data -- extra data to pass to callback function * %RETURNS: * A newly-allocated EventHandler, or NULL. ***********************************************************************/ EventHandler * Event_AddHandler(EventSelector *es, int fd, unsigned int flags, EventCallbackFunc fn, void *data) { EventHandler *eh; /* Specifically disable timer and deleted flags */ flags &= (~(EVENT_TIMER_BITS | EVENT_FLAG_DELETED)); /* Bad file descriptor */ if (fd < 0) { errno = EBADF; return NULL; } eh = malloc(sizeof(EventHandler)); if (!eh) return NULL; eh->fd = fd; eh->flags = flags; eh->tmout.tv_usec = 0; eh->tmout.tv_sec = 0; eh->fn = fn; eh->data = data; /* Add immediately. This is safe even if we are in a handler. */ eh->next = es->handlers; es->handlers = eh; EVENT_DEBUG(("Event_AddHandler(es=%p, fd=%d, flags=%u) -> %p\n", es, fd, flags, eh)); return eh; } /********************************************************************** * %FUNCTION: Event_AddHandlerWithTimeout * %ARGUMENTS: * es -- event selector * fd -- file descriptor to watch * flags -- combination of EVENT_FLAG_READABLE and EVENT_FLAG_WRITEABLE * t -- Timeout after which to call handler, even if not readable/writable. * If t.tv_sec < 0, calls normal Event_AddHandler with no timeout. * fn -- callback function to call when event is triggered * data -- extra data to pass to callback function * %RETURNS: * A newly-allocated EventHandler, or NULL. ***********************************************************************/ EventHandler * Event_AddHandlerWithTimeout(EventSelector *es, int fd, unsigned int flags, struct timeval t, EventCallbackFunc fn, void *data) { EventHandler *eh; struct timeval now; /* If timeout is negative, just do normal non-timing-out event */ if (t.tv_sec < 0 || t.tv_usec < 0) { return Event_AddHandler(es, fd, flags, fn, data); } /* Specifically disable timer and deleted flags */ flags &= (~(EVENT_FLAG_TIMER | EVENT_FLAG_DELETED)); flags |= EVENT_FLAG_TIMEOUT; /* Bad file descriptor? */ if (fd < 0) { errno = EBADF; return NULL; } /* Bad timeout? */ if (t.tv_usec >= 1000000) { errno = EINVAL; return NULL; } eh = malloc(sizeof(EventHandler)); if (!eh) return NULL; /* Convert time interval to absolute time */ gettimeofday(&now, NULL); t.tv_sec += now.tv_sec; t.tv_usec += now.tv_usec; if (t.tv_usec >= 1000000) { t.tv_usec -= 1000000; t.tv_sec++; } eh->fd = fd; eh->flags = flags; eh->tmout = t; eh->fn = fn; eh->data = data; /* Add immediately. This is safe even if we are in a handler. */ eh->next = es->handlers; es->handlers = eh; EVENT_DEBUG(("Event_AddHandlerWithTimeout(es=%p, fd=%d, flags=%u, t=%d/%d) -> %p\n", es, fd, flags, t.tv_sec, t.tv_usec, eh)); return eh; } /********************************************************************** * %FUNCTION: Event_AddTimerHandler * %ARGUMENTS: * es -- event selector * t -- time interval after which to trigger event * fn -- callback function to call when event is triggered * data -- extra data to pass to callback function * %RETURNS: * A newly-allocated EventHandler, or NULL. ***********************************************************************/ EventHandler * Event_AddTimerHandler(EventSelector *es, struct timeval t, EventCallbackFunc fn, void *data) { EventHandler *eh; struct timeval now; /* Check time interval for validity */ if (t.tv_sec < 0 || t.tv_usec < 0 || t.tv_usec >= 1000000) { errno = EINVAL; return NULL; } eh = malloc(sizeof(EventHandler)); if (!eh) return NULL; /* Convert time interval to absolute time */ gettimeofday(&now, NULL); t.tv_sec += now.tv_sec; t.tv_usec += now.tv_usec; if (t.tv_usec >= 1000000) { t.tv_usec -= 1000000; t.tv_sec++; } eh->fd = -1; eh->flags = EVENT_FLAG_TIMER; eh->tmout = t; eh->fn = fn; eh->data = data; /* Add immediately. This is safe even if we are in a handler. */ eh->next = es->handlers; es->handlers = eh; EVENT_DEBUG(("Event_AddTimerHandler(es=%p, t=%d/%d) -> %p\n", es, t.tv_sec,t.tv_usec, eh)); return eh; } /********************************************************************** * %FUNCTION: Event_DelHandler * %ARGUMENTS: * es -- event selector * eh -- event handler * %RETURNS: * 0 if OK, non-zero if there is an error * %DESCRIPTION: * Deletes the event handler eh ***********************************************************************/ int Event_DelHandler(EventSelector *es, EventHandler *eh) { /* Scan the handlers list */ EventHandler *cur, *prev; EVENT_DEBUG(("Event_DelHandler(es=%p, eh=%p)\n", es, eh)); for (cur=es->handlers, prev=NULL; cur; prev=cur, cur=cur->next) { if (cur == eh) { if (es->nestLevel) { eh->flags |= EVENT_FLAG_DELETED; es->opsPending = 1; return 0; } else { if (prev) prev->next = cur->next; else es->handlers = cur->next; DestroyHandler(cur); return 0; } } } /* Handler not found */ return 1; } /********************************************************************** * %FUNCTION: DestroySelector * %ARGUMENTS: * es -- an event selector * %RETURNS: * Nothing * %DESCRIPTION: * Destroys selector and all associated handles. ***********************************************************************/ static void DestroySelector(EventSelector *es) { EventHandler *cur, *next; for (cur=es->handlers; cur; cur=next) { next = cur->next; DestroyHandler(cur); } free(es); } /********************************************************************** * %FUNCTION: DestroyHandler * %ARGUMENTS: * eh -- an event handler * %RETURNS: * Nothing * %DESCRIPTION: * Destroys handler ***********************************************************************/ static void DestroyHandler(EventHandler *eh) { EVENT_DEBUG(("DestroyHandler(eh=%p)\n", eh)); free(eh); } /********************************************************************** * %FUNCTION: DoPendingChanges * %ARGUMENTS: * es -- an event selector * %RETURNS: * Nothing * %DESCRIPTION: * Makes all pending insertions and deletions happen. ***********************************************************************/ static void DoPendingChanges(EventSelector *es) { EventHandler *cur, *prev, *next; es->opsPending = 0; /* If selector is to be deleted, do it and skip everything else */ if (es->destroyPending) { DestroySelector(es); return; } /* Do deletions */ cur = es->handlers; prev = NULL; while(cur) { if (!(cur->flags & EVENT_FLAG_DELETED)) { prev = cur; cur = cur->next; continue; } /* Unlink from list */ if (prev) { prev->next = cur->next; } else { es->handlers = cur->next; } next = cur->next; DestroyHandler(cur); cur = next; } } /********************************************************************** * %FUNCTION: Event_GetCallback * %ARGUMENTS: * eh -- the event handler * %RETURNS: * The callback function ***********************************************************************/ EventCallbackFunc Event_GetCallback(EventHandler *eh) { return eh->fn; } /********************************************************************** * %FUNCTION: Event_GetData * %ARGUMENTS: * eh -- the event handler * %RETURNS: * The "data" field. ***********************************************************************/ void * Event_GetData(EventHandler *eh) { return eh->data; } /********************************************************************** * %FUNCTION: Event_SetCallbackAndData * %ARGUMENTS: * eh -- the event handler * fn -- new callback function * data -- new data value * %RETURNS: * Nothing * %DESCRIPTION: * Sets the callback function and data fields. ***********************************************************************/ void Event_SetCallbackAndData(EventHandler *eh, EventCallbackFunc fn, void *data) { eh->fn = fn; eh->data = data; } #ifdef DEBUG_EVENT #include #include FILE *Event_DebugFP = NULL; /********************************************************************** * %FUNCTION: Event_DebugMsg * %ARGUMENTS: * fmt, ... -- format string * %RETURNS: * Nothing * %DESCRIPTION: * Writes a debug message to the debug file. ***********************************************************************/ void Event_DebugMsg(char const *fmt, ...) { va_list ap; struct timeval now; if (!Event_DebugFP) return; gettimeofday(&now, NULL); fprintf(Event_DebugFP, "%03d.%03d ", (int) now.tv_sec % 1000, (int) now.tv_usec / 1000); va_start(ap, fmt); vfprintf(Event_DebugFP, fmt, ap); va_end(ap); fflush(Event_DebugFP); } #ifndef EVENT_USE_POLL static void print_select_sets(char const *tag, int maxfdp1, fd_set *rd, fd_set *wr) { int fd; int done; if (!Event_DebugFP) return; fprintf(Event_DebugFP, "%s select: rd = [", tag); done = 0; if (rd) { for (fd=0; fd #include #include #include #include #include #include static void free_state(EventTcpState *state); static void finish_netstring(EventSelector *, int, char *, int, int, void *); /* Extra chunk of data for reading netstrings */ typedef struct netstring_extra_t { EventTcpIOFinishedFunc f; int timeout; void *data; } netstring_extra; /* Do not read absurdly-long netstrings */ #define NETSTRING_MAX 65536 /********************************************************************** * %FUNCTION: handle_accept * %ARGUMENTS: * es -- event selector * fd -- socket * flags -- ignored * data -- the accept callback function * %RETURNS: * Nothing * %DESCRIPTION: * Calls accept; if a connection arrives, calls the accept callback * function with connected descriptor ***********************************************************************/ static void handle_accept(EventSelector *es, int fd, unsigned int flags, void *data) { int conn; EventTcpAcceptFunc f; EVENT_DEBUG(("tcp_handle_accept(es=%p, fd=%d, flags=%u, data=%p)\n", es, fd, flags, data)); f = (EventTcpAcceptFunc) data; /* More than one connection may have come in, so pull them all off! */ while (1) { conn = accept(fd, NULL, NULL); if (conn < 0) return; /* Set close-on-exec flag */ if(set_cloexec(conn) < 0) { close(conn); return; } /* Make conn non-blocking */ if (set_nonblocking(conn) < 0) { close(conn); return; } f(es, conn); } } /********************************************************************** * %FUNCTION: handle_connect * %ARGUMENTS: * es -- event selector * fd -- socket * flags -- ignored * data -- the accept callback function * %RETURNS: * Nothing * %DESCRIPTION: * Calls accept; if a connection arrives, calls the accept callback * function with connected descriptor ***********************************************************************/ static void handle_connect(EventSelector *es, int fd, unsigned int flags, void *data) { int error = 0; socklen_t len = sizeof(error); EventTcpConnectState *state = (EventTcpConnectState *) data; EVENT_DEBUG(("tcp_handle_connect(es=%p, fd=%d, flags=%u, data=%p)\n", es, fd, flags, data)); /* Cancel writable event */ Event_DelHandler(es, state->conn); state->conn = NULL; /* Timeout? */ if (flags & EVENT_FLAG_TIMEOUT) { errno = ETIMEDOUT; if (state->f) { state->f(es, fd, EVENT_TCP_FLAG_TIMEOUT, state->data); } else { close(fd); } free(state); return; } /* Check for pending error */ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { if (state->f) { state->f(es, fd, EVENT_TCP_FLAG_IOERROR, state->data); } else { close(fd); } free(state); return; } if (error) { errno = error; if (state->f) { state->f(es, fd, EVENT_TCP_FLAG_IOERROR, state->data); } else { close(fd); } free(state); return; } /* It looks cool! */ if (state->f) { state->f(es, fd, EVENT_TCP_FLAG_COMPLETE, state->data); } else { close(fd); } free(state); } /********************************************************************** * %FUNCTION: EventTcp_CreateAcceptor * %ARGUMENTS: * es -- event selector * socket -- listening socket * f -- function to call when a connection is accepted * data -- extra data to pass to f. * %RETURNS: * An event handler on success, NULL on failure. * %DESCRIPTION: * Sets up an accepting socket and calls "f" whenever a new * connection arrives. ***********************************************************************/ EventHandler * EventTcp_CreateAcceptor(EventSelector *es, int socket, EventTcpAcceptFunc f) { EVENT_DEBUG(("EventTcp_CreateAcceptor(es=%p, socket=%d)\n", es, socket)); if (set_nonblocking(socket) < 0) { return NULL; } return Event_AddHandler(es, socket, EVENT_FLAG_READABLE, handle_accept, (void *) f); } /********************************************************************** * %FUNCTION: free_state * %ARGUMENTS: * state -- EventTcpState to free * %RETURNS: * Nothing * %DESCRIPTION: * Frees all state associated with the TcpEvent. ***********************************************************************/ static void free_state(EventTcpState *state) { if (!state) return; EVENT_DEBUG(("tcp_free_state(state=%p)\n", state)); if (state->buf) free(state->buf); if (state->eh) Event_DelHandler(state->es, state->eh); free(state); } /********************************************************************** * %FUNCTION: handle_readable * %ARGUMENTS: * es -- event selector * fd -- the readable socket * flags -- ignored * data -- the EventTcpState object * %RETURNS: * Nothing * %DESCRIPTION: * Continues to fill buffer. Calls callback when done. ***********************************************************************/ static void handle_readable(EventSelector *es, int fd, unsigned int flags, void *data) { EventTcpState *state = (EventTcpState *) data; int done = state->cur - state->buf; int togo = state->len - done; int nread = 0; int flag = EVENT_TCP_FLAG_INIT; EVENT_DEBUG(("tcp_handle_readable(es=%p, fd=%d, flags=%u, data=%p)\n", es, fd, flags, data)); /* Timed out? */ if (flags & EVENT_FLAG_TIMEOUT) { errno = ETIMEDOUT; if (state->f) { *(state->cur) = 0; (state->f)(es, state->socket, state->buf, done, EVENT_TCP_FLAG_TIMEOUT, state->data); } else { close(state->socket); } free_state(state); return; } if (state->delim < 0) { /* Not looking for a delimiter */ /* togo had better not be zero here! */ nread = read(fd, state->cur, togo); /* We'll get it next time through the select loop */ if (nread < 0 && errno == EINTR) return; if (nread <= 0) { /* Change connection reset to EOF if we have read at least one char */ if (nread < 0 && errno == ECONNRESET && done > 0) { nread = 0; } flag = (nread) ? EVENT_TCP_FLAG_IOERROR : EVENT_TCP_FLAG_EOF; /* error or EOF */ if (state->f) { *(state->cur) = 0; (state->f)(es, state->socket, state->buf, done, flag, state->data); } else { close(state->socket); } free_state(state); return; } state->cur += nread; done += nread; if (done >= state->len) { /* Read enough! */ if (state->f) { *(state->cur) = 0; (state->f)(es, state->socket, state->buf, done, EVENT_TCP_FLAG_COMPLETE, state->data); } else { close(state->socket); } free_state(state); return; } } else { /* Looking for a delimiter */ /* If we have filled buffer, just keep looking for delimiter */ if (!togo && !state->got_delim) { if (!state->chunked) { char x; while(1) { nread = read(fd, &x, 1); if (nread <= 0) break; if (x == state->delim) { state->got_delim = 1; break; } } } else { char slop[128]; int i; while(1) { nread = read(fd, slop, 128); if (nread <= 0) break; for (i=0; idelim) { state->got_delim = 1; } } if (state->got_delim) break; } } } else { if (!state->chunked) { /* Read one char at a time */ while ( (togo > 0) && (nread = read(fd, state->cur, 1)) == 1) { togo--; done++; state->cur++; if (*(state->cur - 1) == state->delim) { state->got_delim = 1; break; } } } else { int tmpread; while (togo > 0) { nread = read(fd, state->cur, togo); if (nread <= 0) break; tmpread = nread; while (tmpread--) { state->cur++; togo--; done++; if (*(state->cur - 1) == state->delim) { togo = 0; state->got_delim = 1; break; } } } } } if (nread <= 0) { /* Error or EOF -- check for EAGAIN */ if (nread < 0 && (errno == EAGAIN || errno == EINTR)) return; } /* Some other error, or EOF, or delimiter, or read enough */ if (nread < 0) { flag = EVENT_TCP_FLAG_IOERROR; } else if (nread == 0) { flag = EVENT_TCP_FLAG_EOF; } else { if (state->delim < 0 || state->got_delim) { flag = EVENT_TCP_FLAG_COMPLETE; } } /* If we're not done yet, keep going around the select loop */ if (flag == EVENT_TCP_FLAG_INIT) return; if (state->f) { *(state->cur) = 0; (state->f)(es, state->socket, state->buf, done, flag, state->data); } else { close(state->socket); } free_state(state); return; } } /********************************************************************** * %FUNCTION: handle_writeable * %ARGUMENTS: * es -- event selector * fd -- the writeable socket * flags -- ignored * data -- the EventTcpState object * %RETURNS: * Nothing * %DESCRIPTION: * Continues to fill buffer. Calls callback when done. ***********************************************************************/ static void handle_writeable(EventSelector *es, int fd, unsigned int flags, void *data) { EventTcpState *state = (EventTcpState *) data; int done = state->cur - state->buf; int togo = state->len - done; int n; /* Timed out? */ if (flags & EVENT_FLAG_TIMEOUT) { errno = ETIMEDOUT; if (state->f) { (state->f)(es, state->socket, state->buf, done, EVENT_TCP_FLAG_TIMEOUT, state->data); } else { close(state->socket); } free_state(state); return; } /* togo had better not be zero here! */ n = write(fd, state->cur, togo); EVENT_DEBUG(("tcp_handle_writeable(es=%p, fd=%d, flags=%u, data=%p)\n", es, fd, flags, data)); if (n < 0 && errno == EINTR) return; if (n <= 0) { /* error */ if (state->f) { (state->f)(es, state->socket, state->buf, done, EVENT_TCP_FLAG_IOERROR, state->data); } else { close(state->socket); } free_state(state); return; } state->cur += n; done += n; if (done >= state->len) { /* Written enough! */ if (state->f) { (state->f)(es, state->socket, state->buf, done, EVENT_TCP_FLAG_COMPLETE, state->data); } else { close(state->socket); } free_state(state); return; } } /********************************************************************** * %FUNCTION: EventTcp_ReadNetstring * %ARGUMENTS: * es -- event selector * socket -- socket to read from * f -- function to call on EOF or when netstring has been read * timeout -- if non-zero, timeout in seconds after which we cancel * operation. * data -- extra data to pass to function f * %RETURNS: * A new EventTcpState token or NULL on error * %DESCRIPTION: * Sets up a handler to read a netstring. A netstring looks like this: * len:data where "len" is the ASCII representation of the decimal * length of "data". For example: 7:example ***********************************************************************/ EventTcpState * EventTcp_ReadNetstring(EventSelector *es, int socket, EventTcpIOFinishedFunc f, int timeout, void *data) { EventTcpState *s; netstring_extra *e = malloc(sizeof(netstring_extra)); if (!e) return NULL; e->f = f; e->timeout = timeout; e->data = data; /* Read the "len:" part and then hand off to data-reading part */ s = EventTcp_ReadBuf(es, socket, 16, ':', finish_netstring, timeout, 0, e); if (!s) { free(e); return NULL; } return s; } /********************************************************************** * %FUNCTION: finish_netstring (static function) * %ARGUMENTS: * es -- event selector * fd -- descriptor we've read from * buf -- buffer containing "len:" part * len -- number of chars read * flag -- result flag * data -- extra data to pass along * %RETURNS: * Nothing * %DESCRIPTION: * Processes the second part of reading a netstring ***********************************************************************/ void finish_netstring(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { netstring_extra *e = (netstring_extra *) data; int readlen; EventTcpState *state; /* If something went wrong, pass on to chained function */ if (flag != EVENT_TCP_FLAG_COMPLETE) { if (e->f) { e->f(es, fd, buf, len, flag, e->data); } else { close(fd); } free(e); return; } /* Get length of netstring to read */ if ((sscanf(buf, "%d", &readlen) != 1) || readlen < 0 || readlen > NETSTRING_MAX) { if (e->f) { e->f(es, fd, "", 0, EVENT_TCP_FLAG_IOERROR, e->data); } else { close(fd); } free(e); return; } /* Add closing comma */ readlen++; state = EventTcp_ReadBuf(es, fd, readlen, -1, e->f, e->timeout, 1, e->data); if (!state) { if (e->f) { e->f(es, fd, "", 0, EVENT_TCP_FLAG_IOERROR, e->data); } else { close(fd); } } free(e); return; } /********************************************************************** * %FUNCTION: EventTcp_WriteNetstring * es -- event selector * socket -- socket to read from * buf -- buffer to write * len -- number of bytes to write * f -- function to call on EOF or when all bytes have been read * timeout -- timeout after which to cancel operation * data -- extra data to pass to function f. * %RETURNS: * A new EventTcpState token or NULL on error * %DESCRIPTION: * Sets up a handler to write a buffer to a socket. ***********************************************************************/ EventTcpState * EventTcp_WriteNetstring(EventSelector *es, int socket, char const *buf, int len, EventTcpIOFinishedFunc f, int timeout, void *data) { char *mybuf; int extralen; EventTcpState *state; if (len < 0 || len > NETSTRING_MAX) { return NULL; } mybuf = malloc(18+len); if (!mybuf) return NULL; sprintf(mybuf, "%d:", len); extralen = strlen(mybuf); memcpy(mybuf+extralen, buf, len); mybuf[extralen+len] = ','; state = EventTcp_WriteBuf(es, socket, mybuf, len+extralen+1, f, timeout, data); free(mybuf); return state; } /********************************************************************** * %FUNCTION: EventTcp_ReadBuf * %ARGUMENTS: * es -- event selector * socket -- socket to read from * len -- maximum number of bytes to read * delim -- delimiter at which to stop reading, or -1 if we should * read exactly len bytes * f -- function to call on EOF or when all bytes have been read * timeout -- if non-zero, timeout in seconds after which we cancel * operation. * chunked -- if non-zero, do reads in "chunks". If we read too much, * excess MAY BE DISCARDED. * data -- extra data to pass to function f. * %RETURNS: * A new EventTcpState token or NULL on error * %DESCRIPTION: * Sets up a handler to fill a buffer from a socket. ***********************************************************************/ EventTcpState * EventTcp_ReadBuf(EventSelector *es, int socket, int len, int delim, EventTcpIOFinishedFunc f, int timeout, int chunked, void *data) { EventTcpState *state; struct timeval t; EVENT_DEBUG(("EventTcp_ReadBuf(es=%p, socket=%d, len=%d, delim=%d, timeout=%d)\n", es, socket, len, delim, timeout)); if (len <= 0) return NULL; if (socket < 0) return NULL; state = malloc(sizeof(EventTcpState)); if (!state) return NULL; memset(state, 0, sizeof(EventTcpState)); state->socket = socket; state->buf = malloc(len+1); if (!state->buf) { free_state(state); return NULL; } state->cur = state->buf; state->len = len; state->f = f; state->es = es; state->chunked = chunked; state->got_delim = 0; if (timeout <= 0) { t.tv_sec = -1; t.tv_usec = -1; } else { t.tv_sec = timeout; t.tv_usec = 0; } state->eh = Event_AddHandlerWithTimeout(es, socket, EVENT_FLAG_READABLE, t, handle_readable, (void *) state); if (!state->eh) { free_state(state); return NULL; } state->data = data; state->delim = delim; EVENT_DEBUG(("EventTcp_ReadBuf() -> %p\n", state)); return state; } /********************************************************************** * %FUNCTION: EventTcp_WriteBuf * %ARGUMENTS: * es -- event selector * socket -- socket to read from * buf -- buffer to write * len -- number of bytes to write * f -- function to call on EOF or when all bytes have been read * timeout -- timeout after which to cancel operation * data -- extra data to pass to function f. * %RETURNS: * A new EventTcpState token or NULL on error * %DESCRIPTION: * Sets up a handler to write a buffer to a socket. ***********************************************************************/ EventTcpState * EventTcp_WriteBuf(EventSelector *es, int socket, char const *buf, int len, EventTcpIOFinishedFunc f, int timeout, void *data) { EventTcpState *state; struct timeval t; EVENT_DEBUG(("EventTcp_WriteBuf(es=%p, socket=%d, len=%d, timeout=%d)\n", es, socket, len, timeout)); if (socket < 0) return NULL; if (len <= 0) { if (!f) close(socket); return NULL; } state = malloc(sizeof(EventTcpState)); if (!state) { if (!f) close(socket); return NULL; } memset(state, 0, sizeof(EventTcpState)); state->socket = socket; state->buf = malloc(len); if (!state->buf) { free_state(state); if (!f) close(socket); return NULL; } memcpy(state->buf, buf, len); state->cur = state->buf; state->len = len; state->f = f; state->es = es; if (timeout <= 0) { t.tv_sec = -1; t.tv_usec = -1; } else { t.tv_sec = timeout; t.tv_usec = 0; } state->eh = Event_AddHandlerWithTimeout(es, socket, EVENT_FLAG_WRITEABLE, t, handle_writeable, (void *) state); if (!state->eh) { free_state(state); if (!f) close(socket); return NULL; } state->data = data; state->delim = -1; EVENT_DEBUG(("EventTcp_WriteBuf() -> %p\n", state)); return state; } /********************************************************************** * %FUNCTION: EventTcp_Connect * %ARGUMENTS: * es -- event selector * fd -- descriptor to connect * addr -- address to connect to * addrlen -- length of address * f -- function to call with connected socket * data -- extra data to pass to f * %RETURNS: * Nothing * %DESCRIPTION: * Does a non-blocking connect on fd ***********************************************************************/ void EventTcp_Connect(EventSelector *es, int fd, struct sockaddr const *addr, socklen_t addrlen, EventTcpConnectFunc f, int timeout, void *data) { int n; EventTcpConnectState *state; struct timeval t; /* Make sure socket is non-blocking */ if (set_nonblocking(fd) < 0) { f(es, fd, EVENT_TCP_FLAG_IOERROR, data); return; } n = connect(fd, addr, addrlen); if (n < 0) { if (errno != EINPROGRESS) { f(es, fd, EVENT_TCP_FLAG_IOERROR, data); return; } } if (n == 0) { /* Connect succeeded immediately */ f(es, fd, EVENT_TCP_FLAG_COMPLETE, data); return; } state = malloc(sizeof(*state)); if (!state) { f(es, fd, EVENT_TCP_FLAG_IOERROR, data); return; } state->f = f; state->fd = fd; state->data = data; if (timeout <= 0) { t.tv_sec = -1; t.tv_usec = -1; } else { t.tv_sec = timeout; t.tv_usec = 0; } state->conn = Event_AddHandlerWithTimeout(es, fd, EVENT_FLAG_WRITEABLE, t, handle_connect, (void *) state); if (!state->conn) { free(state); f(es, fd, EVENT_TCP_FLAG_IOERROR, data); return; } } /********************************************************************** * %FUNCTION: EventTcp_CancelPending * %ARGUMENTS: * s -- an EventTcpState * %RETURNS: * Nothing * %DESCRIPTION: * Cancels the pending event handler ***********************************************************************/ void EventTcp_CancelPending(EventTcpState *s) { free_state(s); } mimedefang-3.6/event_tcp.h000066400000000000000000000051451475763067200156440ustar00rootroot00000000000000/*********************************************************************** * * event-tcp.h * * Event-driven TCP functions to allow for single-threaded "concurrent" * server. * * Copyright (C) 2001-2003 Roaring Penguin Software Inc. * * This program may be distributed according to the terms of the GNU * General Public License, version 2 or (at your option) any later version. * ***********************************************************************/ #ifndef INCLUDE_EVENT_TCP_H #define INCLUDE_EVENT_TCP_H 1 #include "event.h" #include #ifndef HAVE_SOCKLEN_T typedef int socklen_t; #endif typedef void (*EventTcpAcceptFunc)(EventSelector *es, int fd); typedef void (*EventTcpConnectFunc)(EventSelector *es, int fd, int flag, void *data); typedef void (*EventTcpIOFinishedFunc)(EventSelector *es, int fd, char *buf, int len, int flag, void *data); #define EVENT_TCP_FLAG_INIT -1 #define EVENT_TCP_FLAG_COMPLETE 0 #define EVENT_TCP_FLAG_IOERROR 1 #define EVENT_TCP_FLAG_EOF 2 #define EVENT_TCP_FLAG_TIMEOUT 3 typedef struct EventTcpState_t { int socket; char *buf; char *cur; int len; int chunked; int delim; int got_delim; EventTcpIOFinishedFunc f; EventSelector *es; EventHandler *eh; void *data; } EventTcpState; typedef struct EventTcpConnectState_t { int fd; EventHandler *conn; EventTcpConnectFunc f; void *data; } EventTcpConnectState; extern EventHandler *EventTcp_CreateAcceptor(EventSelector *es, int socket, EventTcpAcceptFunc f); extern void EventTcp_Connect(EventSelector *es, int fd, struct sockaddr const *addr, socklen_t addrlen, EventTcpConnectFunc f, int timeout, void *data); extern EventTcpState *EventTcp_ReadBuf(EventSelector *es, int socket, int len, int delim, EventTcpIOFinishedFunc f, int timeout, int chunked, void *data); extern EventTcpState *EventTcp_WriteBuf(EventSelector *es, int socket, char const *buf, int len, EventTcpIOFinishedFunc f, int timeout, void *data); extern EventTcpState * EventTcp_ReadNetstring(EventSelector *es, int socket, EventTcpIOFinishedFunc f, int timeout, void *data); extern EventTcpState * EventTcp_WriteNetstring(EventSelector *es, int socket, char const *buf, int len, EventTcpIOFinishedFunc f, int timeout, void *data); extern void EventTcp_CancelPending(EventTcpState *s); #endif mimedefang-3.6/eventpriv.h000066400000000000000000000033751475763067200157020ustar00rootroot00000000000000/*********************************************************************** * * eventpriv.h * * Abstraction of select call into "event-handling" to make programming * easier. This header includes "private" definitions which users * of the event-handling code should not care about. * * Copyright (C) 2001-2003 Roaring Penguin Software Inc. * * This program may be distributed according to the terms of the GNU * General Public License, version 2 or (at your option) any later version. * ***********************************************************************/ #ifndef INCLUDE_EVENTPRIV_H #define INCLUDE_EVENTPRIV_H 1 #include "config.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef EVENT_USE_POLL #include #endif /* Handler structure */ typedef struct EventHandler_t { struct EventHandler_t *next; /* Link in list */ int fd; /* File descriptor for select */ unsigned int flags; /* Select on read or write; enable timeout */ #ifdef EVENT_USE_POLL unsigned int pollflags; /* Flags returned by poll() */ #endif struct timeval tmout; /* Absolute time for timeout */ EventCallbackFunc fn; /* Callback function */ void *data; /* Extra data to pass to callback */ } EventHandler; /* Selector structure */ typedef struct EventSelector_t { EventHandler *handlers; /* Linked list of EventHandlers */ int nestLevel; /* Event-handling nesting level */ int opsPending; /* True if operations are pending */ int destroyPending; /* If true, a destroy is pending */ } EventSelector; /* Private flags */ #define EVENT_FLAG_DELETED 256 #endif mimedefang-3.6/examples/000077500000000000000000000000001475763067200153155ustar00rootroot00000000000000mimedefang-3.6/examples/README000066400000000000000000000014651475763067200162030ustar00rootroot00000000000000Files in this directory: suggested-minimum-filter-for-windows-clients If you are protecting MS Windows clients, this is the minimum suggested filter. You might want to use a stronger filter. stream-by-domain-filter An example showing how to use the stream_by_domain function. redhat-logrotate-file If you log statistics to /var/log/mimedefang/stats, you want to rotate the log file. You can copy redhat-logrotate-file to /etc/logrotate.d/mimedefang on Red Hat Linux systems. init-script A generic /etc/init.d script which should work on most versions of UNIX. Typically, you'd rename it to mimedefang-ctrl and call it from your startup scripts: mimedefang-ctrl start -- Start mimedefang mimedefang-ctrl stop -- Stop mimedefang mimedefang-ctrl reread -- Re-read filter rules (if using multiplexor) mimedefang-3.6/examples/init-script.in000077500000000000000000000266601475763067200201270ustar00rootroot00000000000000#!/bin/sh # # FreeBSD/NetBSD start/stop script for MIMEDefang. # # PROVIDE: mimedefang # REQUIRE: LOGIN # BEFORE: mail # KEYWORD: shutdown RETVAL=0 prog='mimedefang' SPOOLDIR='@SPOOLDIR@' PID="/var/run/$prog.pid" MXPID="/var/run/$prog-multiplexor.pid" LOCK="$SPOOLDIR/$prog.lock" MXLOCK="$SPOOLDIR/$prog-multiplexor.lock" # These lines keep SpamAssassin happy. Not needed if you # aren't using SpamAssassin. HOME="$SPOOLDIR" export HOME # Is the program executable? We search in /usr/bin and /usr/local/bin. if [ -x @prefix@/bin/$prog ] ; then PROGDIR=@prefix@/bin elif [ -x /usr/bin/$prog ] ; then PROGDIR=/usr/bin elif [ -x /usr/local/bin/$prog ] ; then PROGDIR=/usr/local/bin else exit 0 fi # Locale should be set to "C" for generating valid date headers LC_ALL=C export LC_ALL # The settings which follow are defaults. You can override them by # placing assignments in @CONFDIR_EVAL@/mimedefang.conf # The socket used by mimedefang to communicate with sendmail # SOCKET=$SPOOLDIR/mimedefang.sock # Run the multiplexor and filters as this user, not root. RECOMMENDED MX_USER=@DEFANGUSER@ # Syslog facility # SYSLOG_FACILITY=mail # If you want to keep spool directories around if the filter fails, # set the next one to yes # KEEP_FAILED_DIRECTORIES=no # "yes" turns on the multiplexor relay checking function # MX_RELAY_CHECK=no # "yes" turns on the multiplexor HELO checking function # MX_HELO_CHECK=no # "yes" turns on the multiplexor sender checking function # MX_SENDER_CHECK=no # "yes" turns on the multiplexor recipient checking function # MX_RECIPIENT_CHECK=no # Set to yes if you want the multiplexor to log events to syslog MX_LOG=yes # Set to yes if you want the milter and multiplexor sockets to # be group-writable and files created by mimedefang group-readable. MD_ALLOW_GROUP_ACCESS=no # Set to yes if you want to use an embedded Perl interpreter # MX_EMBED_PERL=yes # Set to full path of socket for Sendmail's SOCKETMAP map, if you # want to use it with MIMEDefang # MX_MAP_SOCKET=$SPOOLDIR/map.sock # The multiplexor does not start all workers at the same time. Instead, # it starts one worker every MX_WORKER_DELAY seconds when the system is idle. # (If the system is busy, the multiplexor starts workers as incoming mail # requires attention.) # MX_WORKER_DELAY=3 # The next setting is an absolute limit on worker activation. The multiplexor # will NEVER activate a worker within MX_MIN_WORKER_DELAY seconds of another. # The default of zero means that the multiplexor will activate workers as # quickly as necessary to keep up with incoming mail. # MX_MIN_WORKER_DELAY=0 # Set to yes if you want the multiplexor to log stats in # /var/log/mimedefang/stats The /var/log/mimedefang directory must # exist and be writable by the user you're running MIMEDefang as. # MX_STATS=no # Number of workers reserved for connections from loopback. Use -1 # for default behaviour, 0 to allow loopback connections to queue, # or >0 to reserve workers for loopback connections LOOPBACK_RESERVED_CONNECTIONS=-1 # If you want new connections to be allowed to queue, set the # next variable to yes. Normally, only existing connections are # allowed to queue requests for work. ALLOW_NEW_CONNECTIONS_TO_QUEUE=no # Set to yes if you want the stats file flushed after each entry # MX_FLUSH_STATS=no # Set to yes if you want the multiplexor to log stats to syslog # MX_STATS_SYSLOG=no # The socket used by the multiplexor # MX_SOCKET=$SPOOLDIR/mimedefang-multiplexor.sock # Maximum # of requests a process handles # MX_REQUESTS=200 # Minimum number of processes to keep. The default of 0 is probably # too low; we suggest 2 instead. MX_MINIMUM=2 # Maximum number of processes to run (mail received while this many # processes are running is rejected with a temporary failure, so be # wary of how many emails you receive at a time). This applies only # if you DO use the multiplexor. The default value of 2 is probably # too low; we suggest 10 instead MX_MAXIMUM=10 # Uncomment to log worker status; it will be logged every # MX_LOG_WORKER_STATUS_INTERVAL seconds # MX_LOG_WORKER_STATUS_INTERVAL=30 # Uncomment next line to have busy workers send status updates to the # multiplexor. NOTE: Consumes one extra file descriptor per worker, plus # a bit of CPU time. # MX_STATUS_UPDATES=yes # Limit worker processes' resident-set size to this many kilobytes. Default # is unlimited. # MX_MAX_RSS=10000 # Limit total size of worker processes' memory space to this many kilobytes. # Default is unlimited. # MX_MAX_AS=30000 # If you want to use the "notification" facility, set the appropriate port. # See the mimedefang-notify man page for details. # MX_NOTIFIER=inet:4567 # Number of seconds a process should be idle before checking for # minimum number and killed # MX_IDLE=300 # Number of seconds a process is allowed to scan an email before it is # considered dead. The default is 30 seconds; we suggest 600. MX_BUSY=600 # Maximum number of concurrent recipok requests on a per-domain basis. # 0 means no limit MX_RECIPOK_PERDOMAIN_LIMIT=0 # Extra sendmail macros to pass. Actually, you can add any extra # mimedefang options here... # MD_EXTRA="-a auth_author" # Multiplexor queue size -- default is 0 (no queueing) # MX_QUEUE_SIZE=10 # Multiplexor queue timeout -- default is 30 seconds # MX_QUEUE_TIMEOUT=30 # Set to yes if you don't want MIMEDefang to see invalid recipients. # Only works with Sendmail 8.14.0 and later. # MD_SKIP_BAD_RCPTS=no # SUBFILTER specifies which filter rules file to use # SUBFILTER=@CONFDIR_EVAL@/mimedefang-filter # Source configuration if [ -f @CONFDIR_EVAL@/$prog.conf ] ; then . @CONFDIR_EVAL@/$prog.conf fi # BSD specific setup if [ -f /etc/rc.subr ] then . /etc/rc.subr name=$prog rcvar=`set_rcvar` # default to not enabled, enable in rc.conf eval $rcvar=\${$rcvar:-NO} load_rc_config $name pidfile=$MXPID procname=$PROGDIR/$prog-multiplexor start_cmd="start_it" stop_cmd="stop_it" sig_reload="INT" reread_cmd="reread_it" # provide both "reload", the FreeBSD default, with a direct signal to # the multiplexor, and "reread", the MIMEDefang default, using md-mx-ctrl extra_commands="reload reread" fi # Make sure required vars are set SOCKET=${SOCKET:=$SPOOLDIR/$prog.sock} MX_SOCKET=${MX_SOCKET:=$SPOOLDIR/$prog-multiplexor.sock} start_it() { if test -r $PID ; then if kill -0 `cat $PID` > /dev/null 2>&1 ; then echo "mimedefang (`cat $PID`) seems to be running." return 1 fi fi if test -r $MXPID ; then if kill -0 `cat $MXPID` > /dev/null 2>&1 ; then echo "mimedefang-multiplexor (`cat $MXPID`) seems to be running." return 1 fi fi printf "%-60s" "Starting $prog-multiplexor: " rm -f $MX_SOCKET > /dev/null 2>&1 if [ "$MX_EMBED_PERL" = "yes" ] ; then EMBEDFLAG=-E else EMBEDFLAG="" fi $PROGDIR/$prog-multiplexor -p $MXPID -o $MXLOCK \ $EMBEDFLAG \ `[ -n "$SPOOLDIR" ] && echo "-z $SPOOLDIR"` \ `[ -n "$FILTER" ] && echo "-f $FILTER"` \ `[ -n "$SYSLOG_FACILITY" ] && echo "-S $SYSLOG_FACILITY"` \ `[ -n "$SUBFILTER" ] && echo "-F $SUBFILTER"` \ `[ -n "$MX_MINIMUM" ] && echo "-m $MX_MINIMUM"` \ `[ -n "$MX_MAXIMUM" ] && echo "-x $MX_MAXIMUM"` \ `[ -n "$MX_MAP_SOCKET" ] && echo "-N $MX_MAP_SOCKET"` \ `[ -n "$MX_LOG_WORKER_STATUS_INTERVAL" ] && echo "-L $MX_LOG_WORKER_STATUS_INTERVAL"` \ `[ -n "$MX_USER" ] && echo "-U $MX_USER"` \ `[ -n "$MX_IDLE" ] && echo "-i $MX_IDLE"` \ `[ -n "$MX_BUSY" ] && echo "-b $MX_BUSY"` \ `[ -n "$MX_REQUESTS" ] && echo "-r $MX_REQUESTS"` \ `[ -n "$MX_WORKER_DELAY" ] && echo "-w $MX_WORKER_DELAY"` \ `[ -n "$MX_MIN_WORKER_DELAY" ] && echo "-W $MX_MIN_WORKER_DELAY"` \ `[ -n "$MX_MAX_RSS" ] && echo "-R $MX_MAX_RSS"` \ `[ -n "$MX_MAX_AS" ] && echo "-M $MX_MAX_AS"` \ `[ "$MX_LOG" = "yes" ] && echo "-l"` \ `[ "$MX_STATS" = "yes" ] && echo "-t /var/log/mimedefang/stats"` \ `[ "$MX_STATS" = "yes" -a "$MX_FLUSH_STATS" = "yes" ] && echo "-u"` \ `[ "$MX_STATS_SYSLOG" = "yes" ] && echo "-T"` \ `[ "$MD_ALLOW_GROUP_ACCESS" = "yes" ] && echo "-G"` \ `[ "$MX_STATUS_UPDATES" = "yes" ] && echo "-Z"` \ `[ -n "$MX_QUEUE_SIZE" ] && echo "-q $MX_QUEUE_SIZE"` \ `[ -n "$MX_QUEUE_TIMEOUT" ] && echo "-Q $MX_QUEUE_TIMEOUT"` \ `[ -n "$MX_NOTIFIER" ] && echo "-O $MX_NOTIFIER"` \ `[ -n "$MX_RECIPOK_PERDOMAIN_LIMIT" ] && echo "-y $MX_RECIPOK_PERDOMAIN_LIMIT"` \ -s $MX_SOCKET RETVAL=$? if [ $RETVAL = 0 ] ; then echo "[ OK ]" else echo "[FAILED]" return 1 fi # Start mimedefang printf "%-60s" "Starting $prog: " rm -f $SOCKET > /dev/null 2>&1 $PROGDIR/$prog -P $PID -o $LOCK -R $LOOPBACK_RESERVED_CONNECTIONS \ -m $MX_SOCKET \ `[ -n "$SPOOLDIR" ] && echo "-z $SPOOLDIR"` \ `[ -n "$MX_USER" ] && echo "-U $MX_USER"` \ `[ -n "$SYSLOG_FACILITY" ] && echo "-S $SYSLOG_FACILITY"` \ `[ "$MX_RELAY_CHECK" = "yes" ] && echo "-r"` \ `[ "$MX_HELO_CHECK" = "yes" ] && echo "-H"` \ `[ "$MX_SENDER_CHECK" = "yes" ] && echo "-s"` \ `[ "$MX_RECIPIENT_CHECK" = "yes" ] && echo "-t"` \ `[ "$KEEP_FAILED_DIRECTORIES" = "yes" ] && echo "-k"` \ `[ "$MD_EXTRA" != "" ] && echo $MD_EXTRA` \ `[ "$MD_SKIP_BAD_RCPTS" = "yes" ] && echo "-N"` \ `[ "$MD_ALLOW_GROUP_ACCESS" = "yes" ] && echo "-G"` \ `[ "$ALLOW_NEW_CONNECTIONS_TO_QUEUE" = "yes" ] && echo "-q"` \ -p $SOCKET RETVAL=$? if [ $RETVAL = 0 ] ; then echo "[ OK ]" else echo "[FAILED]" kill `cat $MXPID` return 1 fi return 0 } stop_it() { # Stop daemon printf "%-60s" "Shutting down $prog: " if test -f "$PID" ; then kill `cat $PID` RETVAL=$? else RETVAL=1 fi if [ $RETVAL = 0 ] ; then echo "[ OK ]" else echo "[FAILED]" fi # Stop daemon printf "%-60s" "Shutting down $prog-multiplexor: " if test -f "$MXPID" ; then kill `cat $MXPID` RETVAL=$? else RETVAL=1 fi if [ $RETVAL = 0 ] ; then echo "[ OK ]" else echo "[FAILED]" fi rm -f $MX_SOCKET > /dev/null 2>&1 rm -f $SOCKET > /dev/null 2>&1 if [ "$1" = "wait" ] ; then printf "Waiting for daemons to exit." WAITPID="" test -f $PID && WAITPID=`cat $PID` test -f $MXPID && WAITPID="$WAITPID `cat $MXPID`" n=0 while [ -n "$WAITPID" ] ; do W2="" for pid in $WAITPID ; do if kill -0 $pid > /dev/null 2>&1 ; then W2="$W2 $pid" fi done printf "." n=`expr $n + 1` test $n -eq 30 && kill -KILL $WAITPID > /dev/null 2>&1 test $n -eq 60 && break WAITPID=$W2 sleep 1 done echo "" fi rm -f $MXPID > /dev/null 2>&1 rm -f $PID > /dev/null 2>&1 } reread_it() { if [ -x $PROGDIR/md-mx-ctrl ] ; then $PROGDIR/md-mx-ctrl -s $MX_SOCKET reread > /dev/null 2>&1 RETVAL=$? if [ $RETVAL = 0 ] ; then echo "Told $prog-multiplexor to force reread of filter rules." else echo "Could not communicate with $prog-multiplexor" fi else if [ -r $MXPID ] ; then kill -INT `cat $MXPID` RETVAL=$? if [ $RETVAL = 0 ] ; then echo "Told $prog-multiplexor to force reread of filter rules." else echo "Could not signal $prog-multiplexor" fi else RETVAL=1 echo "Could not find process-ID of $prog-multiplexor" fi fi } if type run_rc_command > /dev/null 2>&1 then # NetBSD/FreeBSD compatible startup script run_rc_command "$1" exit $RETVAL fi # See how we were called. case "$1" in start) start_it ;; stop) stop_it $2 ;; restart) stop_it wait start_it RETVAL=$? ;; reread|reload) reread_it ;; *) echo "Usage: $0 {start|stop|restart|reread|reload}" exit 1 esac exit $RETVAL mimedefang-3.6/examples/redhat-logrotate-file000066400000000000000000000002141475763067200214170ustar00rootroot00000000000000/var/log/mimedefang/stats { missingok postrotate /usr/bin/killall -HUP mimedefang-multiplexor 2> /dev/null || true endscript } mimedefang-3.6/examples/stream-by-domain-filter000066400000000000000000000066161475763067200217040ustar00rootroot00000000000000# -*- Perl -*- #*********************************************************************** # # stream-by-domain-filter # # This example shows you how to "stream" a message by domain. It # lets you apply different filtering rules for different domains. # # Copyright (C) 2001 Roaring Penguin Software Inc. # # This program may be distributed under the terms of the GNU General # Public License, Version 2. # # $Id$ #*********************************************************************** $Administrator = 'postmaster@localhost'; $DaemonAddress = 'mailer-daemon@localhost'; $Stupidity{"NoMultipleInlines"} = 0; #*********************************************************************** # %PROCEDURE: filter_begin # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Called just before e-mail parts are processed. This checks the recipients. # If they all belong to the SAME domain (@foo.com), then it sets the global # variable $Domain to the domain, and sets $Discard to 0. If # they belong to DIFFERENT domains (@foo.com, @bar.com), it sets $Discard # to 1 and resends the message, once for each domain # # WARNING WARNING WARNING: YOU MUST USE SENDMAIL 8.12 for this to work. # Sendmail 8.12 uses SMTP to handle locally-submitted messages. If you # use Sendmail 8.11 or earlier, then the re-sent messages ARE NOT SCANNED # by MIMEDefang!!!!! You had better run tests to ensure that the resent # messages are scanned by MIMEDefang on your system before using this # streaming mechanism. #*********************************************************************** sub filter_begin { if (stream_by_domain()) { # More than one domain -- do nothing! return; } # Rest of filter-begin stuff goes here. We are guaranteed that all # recipients belong to the same domain. } #*********************************************************************** # %PROCEDURE: filter # %ARGUMENTS: # entity -- a Mime::Entity object (see MIME-tools documentation for details) # fname -- the suggested filename, taken from the MIME Content-Disposition: # header. If no filename was suggested, then fname is "" # ext -- the file extension (everything from the last period in the name # to the end of the name, including the period.) # type -- the MIME type, taken from the Content-Type: header. # # %RETURNS: # Nothing # %DESCRIPTION: # This function is called once for each part of a MIME message. #*********************************************************************** sub filter { my($entity, $fname, $ext, $type) = @_; if ($Domain eq "abc.com") { # Filter actions for abc.com } elsif ($Domain eq "xyz.com") { # Filter actions for xyz.com } else { # Default filter actions } } #*********************************************************************** # %PROCEDURE: defang_warning # %ARGUMENTS: # oldfname -- the old file name of an attachment # fname -- the new "defanged" name # %RETURNS: # A warning message # %DESCRIPTION: # This function customizes the warning message when an attachment # is defanged. #*********************************************************************** sub defang_warning { my($oldfname, $fname) = @_; return "An attachment named '$oldfname' was converted to '$fname'.\n" . "To recover the file, right-click on the attachment and Save As\n" . "'$oldfname'\n"; } # DO NOT delete the next line, or Perl will complain. 1; mimedefang-3.6/examples/suggested-minimum-filter-for-windows-clients000066400000000000000000000332211475763067200261020ustar00rootroot00000000000000# -*- Perl -*- #*********************************************************************** # # mimedefang-filter # # Suggested minimum-protection filter for Microsoft Windows clients, plus # SpamAssassin checks if SpamAssassin is installed. # # Copyright (C) 2002 Roaring Penguin Software Inc. # # This program may be distributed under the terms of the GNU General # Public License, Version 2. # # $Id$ #*********************************************************************** #*********************************************************************** # Set administrator's e-mail address here. The administrator receives # quarantine messages and is listed as the contact for site-wide # MIMEDefang policy. A good example would be 'defang-admin@mydomain.com' #*********************************************************************** $AdminAddress = 'postmaster@localhost'; $AdminName = "MIMEDefang Administrator's Full Name"; #*********************************************************************** # Set the e-mail address from which MIMEDefang quarantine warnings and # user notifications appear to come. A good example would be # 'mimedefang@mydomain.com'. Make sure to have an alias for this # address if you want replies to it to work. #*********************************************************************** $DaemonAddress = 'mimedefang@localhost'; #*********************************************************************** # If you set $AddWarningsInline to 1, then MIMEDefang tries *very* hard # to add warnings directly in the message body (text or html) rather # than adding a separate "WARNING.TXT" MIME part. If the message # has no text or html part, then a separate MIME part is still used. #*********************************************************************** $AddWarningsInline = 0; #*********************************************************************** # To enable syslogging of virus and spam activity, add the following # to the filter: # md_graphdefang_log_enable(); # You may optionally provide a syslogging facility by passing an # argument such as: md_graphdefang_log_enable('local4'); If you do this, be # sure to setup the new syslog facility (probably in /etc/syslog.conf). # An optional second argument causes a line of output to be produced # for each recipient (if it is 1), or only a single summary line # for all recipients (if it is 0.) The default is 1. # Comment this line out to disable logging. #*********************************************************************** md_graphdefang_log_enable('mail', 1); #*********************************************************************** # Uncomment this to block messages with more than 50 parts. This will # *NOT* work unless you're using Roaring Penguin's patched version # of MIME tools, version MIME-tools-5.411a-RP-Patched-02 or later. # # WARNING: DO NOT SET THIS VARIABLE unless you're using at least # MIME-tools-5.411a-RP-Patched-02; otherwise, your filter will fail. #*********************************************************************** # $MaxMIMEParts = 50; #*********************************************************************** # Set various stupid things your mail client does below. #*********************************************************************** # Set the next one if your mail client cannot handle multiple "inline" # parts. $Stupidity{"NoMultipleInlines"} = 0; # Detect and load Perl modules detect_and_load_perl_modules(); # This procedure returns true for entities with bad filenames. sub filter_bad_filename { my($entity) = @_; my($bad_exts, $re); # Bad extensions $bad_exts = '(ade|adp|app|asd|asf|asx|bas|bat|chm|cmd|com|cpl|crt|dll|exe|fxp|hlp|hta|hto|inf|ini|ins|isp|jse?|lib|lnk|mdb|mde|msc|msi|msp|mst|ocx|pcd|pif|prg|reg|scr|sct|sh|shb|shs|sys|url|vb|vbe|vbs|vcs|vxd|wmd|wms|wmz|wsc|wsf|wsh|\{[^\}]+\})'; # Do not allow: # - CLSIDs {foobarbaz} # - bad extensions (possibly with trailing dots) at end $re = '\.' . $bad_exts . '\.*$'; return 1 if (re_match($entity, $re)); # Look inside ZIP files if (re_match($entity, '\.zip$') and $Features{"Archive::Zip"}) { my $bh = $entity->bodyhandle(); if (defined($bh)) { my $path = $bh->path(); if (defined($path)) { return re_match_in_zip_directory($path, $re); } } } return 0; } #*********************************************************************** # %PROCEDURE: filter_begin # %ARGUMENTS: # $entity -- the parsed MIME::Entity # %RETURNS: # Nothing # %DESCRIPTION: # Called just before e-mail parts are processed #*********************************************************************** sub filter_begin { my($entity) = @_; # ALWAYS drop messages with suspicious chars in headers if ($SuspiciousCharsInHeaders) { md_graphdefang_log('suspicious_chars'); action_quarantine_entire_message("Message quarantined because of suspicious characters in headers"); # Do NOT allow message to reach recipient(s) return action_discard(); } # Copy original message into work directory as an "mbox" file for # virus-scanning md_copy_orig_msg_to_work_dir_as_mbox_file(); # Scan for viruses if any virus-scanners are installed my($code, $category, $action) = message_contains_virus(); # Lower level of paranoia - only looks for actual viruses $FoundVirus = ($category eq "virus"); # Higher level of paranoia - takes care of "suspicious" objects # $FoundVirus = ($action eq "quarantine"); if ($FoundVirus) { md_graphdefang_log('virus', $VirusName, $RelayAddr); md_syslog('warning', "Discarding because of virus $VirusName"); return action_discard(); } if ($action eq "tempfail") { action_tempfail("Problem running virus-scanner"); md_syslog('warning', "Problem running virus scanner: code=$code, category=$category, action=$action"); } } #*********************************************************************** # %PROCEDURE: filter # %ARGUMENTS: # entity -- a Mime::Entity object (see MIME-tools documentation for details) # fname -- the suggested filename, taken from the MIME Content-Disposition: # header. If no filename was suggested, then fname is "" # ext -- the file extension (everything from the last period in the name # to the end of the name, including the period.) # type -- the MIME type, taken from the Content-Type: header. # # NOTE: There are two likely and one unlikely place for a filename to # appear in a MIME message: In Content-Disposition: filename, in # Content-Type: name, and in Content-Description. If you are paranoid, # you will use the re_match and re_match_ext functions, which return true # if ANY of these possibilities match. re_match checks the whole name; # re_match_ext checks the extension. See the sample filter below for usage. # %RETURNS: # Nothing # %DESCRIPTION: # This function is called once for each part of a MIME message. # There are many action_*() routines which can decide the fate # of each part; see the mimedefang-filter man page. #*********************************************************************** sub filter { my($entity, $fname, $ext, $type) = @_; return if message_rejected(); # Avoid unnecessary work # Block message/partial parts if (lc($type) eq "message/partial") { md_graphdefang_log('message/partial'); return action_bounce("MIME type message/partial not accepted here"); } if (filter_bad_filename($entity)) { md_graphdefang_log('bad_filename', $fname, $type); return action_drop_with_warning("An attachment named $fname was removed from this document as it\nconstituted a security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n"); } return action_accept(); } #*********************************************************************** # %PROCEDURE: filter_multipart # %ARGUMENTS: # entity -- a Mime::Entity object (see MIME-tools documentation for details) # fname -- the suggested filename, taken from the MIME Content-Disposition: # header. If no filename was suggested, then fname is "" # ext -- the file extension (everything from the last period in the name # to the end of the name, including the period.) # type -- the MIME type, taken from the Content-Type: header. # %RETURNS: # Nothing # %DESCRIPTION: # This is called for multipart "container" parts such as message/rfc822. # You cannot replace the body (because multipart parts have no body), # but you should check for bad filenames. #*********************************************************************** sub filter_multipart { my($entity, $fname, $ext, $type) = @_; return if message_rejected(); # Avoid unnecessary work if (filter_bad_filename($entity)) { md_graphdefang_log('bad_filename', $fname, $type); action_notify_administrator("A MULTIPART attachment of type $type, named $fname was dropped.\n"); return action_drop_with_warning("An attachment of type $type, named $fname was removed from this document as it\nconstituted a security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n"); } # Block message/partial parts if (lc($type) eq "message/partial") { md_graphdefang_log('message/partial'); return action_bounce("MIME type message/partial not accepted here"); } return action_accept(); } #*********************************************************************** # %PROCEDURE: defang_warning # %ARGUMENTS: # oldfname -- the old file name of an attachment # fname -- the new "defanged" name # %RETURNS: # A warning message # %DESCRIPTION: # This function customizes the warning message when an attachment # is defanged. #*********************************************************************** sub defang_warning { my($oldfname, $fname) = @_; return "An attachment named '$oldfname' was converted to '$fname'.\n" . "To recover the file, right-click on the attachment and Save As\n" . "'$oldfname'\n"; } # If SpamAssassin found SPAM, append report. We do it as a separate # attachment of type text/plain sub filter_end { my($entity) = @_; # If you want quarantine reports, uncomment next line # send_quarantine_notifications(); # IMPORTANT NOTE: YOU MUST CALL send_quarantine_notifications() AFTER # ANY PARTS HAVE BEEN QUARANTINED. SO IF YOU MODIFY THIS FILTER TO # QUARANTINE SPAM, REWORK THE LOGIC TO CALL send_quarantine_notifications() # AT THE END!!! # No sense doing any extra work return if message_rejected(); # Spam checks if SpamAssassin is installed if ($Features{"SpamAssassin"}) { if (-s "./INPUTMSG" < 512*1024) { # Only scan messages smaller than 512KB. Larger messages are # unlikely to be spam and SpamAssassin is slow on very large messages. my($hits, $req, $names, $report) = spam_assassin_check(); my($score); if ($hits < 40) { $score = "*" x int($hits); } else { $score = "*" x 40; } # We add a header which looks like this: # X-Spam-Score: 6.8 (******) NAME_OF_TEST,NAME_OF_TEST # The number of asterisks in parens is the integer part # of the spam score clamped to a maximum of 40. # MUA filters can easily be written to trigger on a # minimum number of asterisks... if ($hits >= $req) { action_change_header("X-Spam-Score", "$hits ($score) $names"); md_graphdefang_log('spam', $hits, $RelayAddr); # If you find the SA report useful, add it, I guess... action_add_part($entity, "text/plain", "-suggest", "$report\n", "SpamAssassinReport.txt", "inline"); } else { # Delete any existing X-Spam-Score header? action_delete_header("X-Spam-Score"); } } } # # Check email messages with Rspamd # # my ($hits, $req, $names, $report, $action, $is_spam); # if (-s "./INPUTMSG" < 512*1024) { # ($hits, $req, $names, $report, $action, $is_spam) = rspamd_check(); # md_syslog("Warning", "Action: $action, Spam: $is_spam, Names: $names"); # if ($is_spam eq "true") { # action_change_header("X-Spam-Score", "$hits/$req $names"); # md_syslog("Warning", "Action: $action"); # md_graphdefang_log('spam', $hits, $RelayAddr); # } else { # # Delete any existing X-Spam-Score header? # action_delete_header("X-Spam-Score"); # } # } # I HATE HTML MAIL! If there's a multipart/alternative with both # text/plain and text/html parts, nuke the text/html. Thanks for # wasting our disk space and bandwidth... # If you want to strip out HTML parts if there is a corresponding # plain-text part, uncomment the next line. # remove_redundant_html_parts($entity); md_graphdefang_log('mail_in'); # Deal with malformed MIME. # Some viruses produce malformed MIME messages that are misinterpreted # by mail clients. They also might slip under the radar of MIMEDefang. # If you are worried about this, you should canonicalize all # e-mail by uncommenting the action_rebuild() line. This will # force _all_ messages to be reconstructed as valid MIME. It will # increase the load on your server, and might break messages produced # by marginal software. Your call. # action_rebuild(); } # DO NOT delete the next line, or Perl will complain. 1; mimedefang-3.6/gen-ip-validator.pl000066400000000000000000000021731475763067200172010ustar00rootroot00000000000000#!/usr/bin/perl #*********************************************************************** # # gen-ip-validator.pl # # Generate a random number used to confirm IP address header. # # * This program may be distributed under the terms of the GNU General # * Public License, Version 2. # #*********************************************************************** use constant HAS_OPENSSL_RANDOM => eval { require Crypt::OpenSSL::Random; }; BEGIN { eval{ Crypt::OpenSSL::Random->import }; } use Digest::SHA; sub read_urandom($) { my $len = shift; my $junk; my $in; if (-r "/dev/urandom") { open($in, "<", "/dev/urandom"); read($in, $junk, $len); close($in); } return $junk; } my $rng; my $data; if(HAS_OPENSSL_RANDOM) { for (;;) { $rng .= sprintf "%x\n", rand(0xffffffff); $rng .= read_urandom(64); last if(Crypt::OpenSSL::Random::random_status()); } Crypt::OpenSSL::Random::random_seed($rng); $data = Crypt::OpenSSL::Random::random_bytes(256); } else { $data = read_urandom(256); } my $ctx = Digest::SHA->new; $ctx->add($data); my $rnd = $ctx->hexdigest; print "X-MIMEDefang-Relay-$rnd\n"; mimedefang-3.6/gen_id.c000066400000000000000000000022331475763067200150700ustar00rootroot00000000000000/* * This program may be distributed under the terms of the GNU General * Public License, Version 2. */ #include "mimedefang.h" #include /* Our identifiers are in base-60 */ #define BASE 60 /* Our time-based identifier is 5 base-60 characters. TIMESPAN below is 60^5 */ #define TIMESPAN (BASE*BASE*BASE*BASE*BASE) #define COUNTER_MOD (BASE*BASE) /* Counter incremented each time gen_id is called */ static unsigned int id_counter = 0; /* Mutex to protect id_counter */ static pthread_mutex_t id_counter_mutex = PTHREAD_MUTEX_INITIALIZER; /* This had better be 60 characters long! */ static char const char_map[BASE] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX"; char * gen_mx_id(char *out) { unsigned int counter, time_part; int i; pthread_mutex_lock(&id_counter_mutex); counter = id_counter++; pthread_mutex_unlock(&id_counter_mutex); time_part = ((unsigned int) time(NULL)) % TIMESPAN; for (i=4; i>=0; i--) { out[i] = char_map[time_part % BASE]; time_part /= BASE; } for (i=6; i>=5; i--) { out[i] = char_map[counter % BASE]; counter /= BASE; } out[MX_ID_LEN] = 0; return out; } mimedefang-3.6/install-sh000077500000000000000000000112451475763067200155060ustar00rootroot00000000000000#! /bin/sh # # install - install a program, script, or datafile # This comes from X11R5. # # Calling this script install-sh is preferred over install.sh, to prevent # `make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. # # set DOITPROG to echo to test this script # Don't use :- since 4.3BSD and earlier shells don't like it. doit="${DOITPROG-}" # put in absolute paths if you don't have them in your path; or use env. vars. mvprog="${MVPROG-mv}" cpprog="${CPPROG-cp}" chmodprog="${CHMODPROG-chmod}" chownprog="${CHOWNPROG-chown}" chgrpprog="${CHGRPPROG-chgrp}" stripprog="${STRIPPROG-strip}" rmprog="${RMPROG-rm}" mkdirprog="${MKDIRPROG-mkdir}" transformbasename="" transform_arg="" instcmd="$cpprog" chmodcmd="$chmodprog 0755" chowncmd="" chgrpcmd="" stripcmd="" rmcmd="$rmprog -f" mvcmd="$mvprog" src="" dst="" dir_arg="" while [ x"$1" != x ]; do case $1 in -c) instcmd="$cpprog" shift continue;; -d) dir_arg=true shift continue;; -m) chmodcmd="$chmodprog $2" shift shift continue;; -o) chowncmd="$chownprog $2" shift shift continue;; -g) chgrpcmd="$chgrpprog $2" shift shift continue;; -s) stripcmd="$stripprog" shift continue;; -t=*) transformarg=`echo $1 | sed 's/-t=//'` shift continue;; -b=*) transformbasename=`echo $1 | sed 's/-b=//'` shift continue;; *) if [ x"$src" = x ] then src=$1 else # this colon is to work around a 386BSD /bin/sh bug : dst=$1 fi shift continue;; esac done if [ x"$src" = x ] then echo "install: no input file specified" exit 1 else true fi if [ x"$dir_arg" != x ]; then dst=$src src="" if [ -d $dst ]; then instcmd=: else instcmd=mkdir fi else # Waiting for this to be detected by the "$instcmd $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if [ -f $src -o -d $src ] then true else echo "install: $src does not exist" exit 1 fi if [ x"$dst" = x ] then echo "install: no destination specified" exit 1 else true fi # If destination is a directory, append the input filename; if your system # does not like double slashes in filenames, you may need to add some logic if [ -d $dst ] then dst="$dst"/`basename $src` else true fi fi ## this sed command emulates the dirname command dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` # Make sure that the destination directory exists. # this part is taken from Noah Friedman's mkinstalldirs script # Skip lots of stat calls in the usual case. if [ ! -d "$dstdir" ]; then defaultIFS=' ' IFS="${IFS-${defaultIFS}}" oIFS="${IFS}" # Some sh's can't handle IFS=/ for some reason. IFS='%' set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` IFS="${oIFS}" pathcomp='' while [ $# -ne 0 ] ; do pathcomp="${pathcomp}${1}" shift if [ ! -d "${pathcomp}" ] ; then $mkdirprog "${pathcomp}" else true fi pathcomp="${pathcomp}/" done fi if [ x"$dir_arg" != x ] then $doit $instcmd $dst && if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi else # If we're going to rename the final executable, determine the name now. if [ x"$transformarg" = x ] then dstfile=`basename $dst` else dstfile=`basename $dst $transformbasename | sed $transformarg`$transformbasename fi # don't allow the sed command to completely eliminate the filename if [ x"$dstfile" = x ] then dstfile=`basename $dst` else true fi # Make a temp file name in the proper directory. dsttmp=$dstdir/#inst.$$# # Move or copy the file name to the temp name $doit $instcmd $src $dsttmp && trap "rm -f ${dsttmp}" 0 && # and set any options; do chmod last to preserve setuid bits # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $instcmd $src $dsttmp" command. if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && # Now rename the file to the real destination. $doit $rmcmd -f $dstdir/$dstfile && $doit $mvcmd $dsttmp $dstdir/$dstfile fi && exit 0 mimedefang-3.6/md-mx-ctrl.8.in000066400000000000000000000224061475763067200161650ustar00rootroot00000000000000.\" $Id$ .\"" .TH MD-MX-CTRL 8 "8 February 2005" .UC 4 .SH NAME md-mx-ctrl \- Control mimedefang-multiplexor .SH SYNOPSIS .B md-mx-ctrl \fR[\fIoptions\fR] \fIcommand\fR .SH DESCRIPTION \fBmd-mx-ctrl\fR is a command-line tool for communicating with \fBmimedefang-multiplexor\fR(8). .SH OPTIONS .TP .B \-h Displays usage information. .TP .B \-s \fIpath\fR Specifies the path to the \fBmimedefang-multiplexor\fR socket. If not specified, defaults to @SPOOLDIR@/mimedefang-multiplexor.sock. .TP .B \-i This flag causes \fBmd-mx-ctrl\fR to sit in a loop, reading commands on standard input and printing results to standard output. It is intended for use by a monitoring program such as \fBwatch-mimedefang\fR. .SH COMMANDS The following commands are available: .TP .B status Prints the status of all worker Perl processes in human-readable format. .TP .B rawstatus Prints the status of all worker Perl processes in a format easy to parse by computer. The result is a single line with six words on it. The words are separated by a single space character. Each character in the first word corresponds to a worker, and is "I" for an idle worker, "B" for a busy worker, "S" for a worker which is not running, and "K" for a worker which has been killed, but has not yet exited. A worker is "idle" if there is a running Perl process waiting to do work. "Busy" means the Perl process is currently filtering a message. "S" means there is no associated Perl process with the worker, but one can be started if the load warrants. Finally, "K" means the worker Perl process has been killed, but has yet to terminate. The second word is the total number of messages processed since the multiplexor started up. The third word is the total number of workers which have been activated since the multiplexor started up. (That is, it's a count of the number of times the multiplexor has forked and exec'd the Perl filter.) The fourth word is the size of the queue for request queuing, and the fifth word is the actual number of requests in the queue. The sixth word is the number of seconds elapsed since the multiplexor was started. .TP .B barstatus Prints the status of busy workers and queued requests in a nice "bar chart" format. This lets you keep an eye on things with a script like this: .nf while true ; do md-mx-ctrl barstatus sleep 1 done .fi .TP .B jsonstatus Prints the status of all worker Perl processes in JSON format. .TP .B histo Prints a histogram showing the number of workers that were busy each time a request was processed. A single line is printed for the numbers from 1 up to the maximum number of workers. Each line contains the count of busy workers (1, 2, 3 up to MX_MAXIMUM), a space, and the number of times that many workers were busy when a request was processed. .TP .B load Prints a table showing "load averages" for the last 10 seconds, 1 minute, 5 minutes and 10 minutes. Each row in the table corresponds to a time interval, displayed in the first column. The remaining columns in the table are: \fBMsgs:\fR The number of messages scanned within the row's time interval. \fBMsgs/Sec:\fR The average number of messages scanned per second within the row's time interval. \fBAvg Busy Workers:\fR The average number of busy workers whenever a message was scanned. (If you are processing any mail at all, this number will be at least 1, because there is always 1 busy worker when a message is scanned.) If you have the \fBwatch\fR(1) command on your system, you can keep an eye on the load with this command: .nf watch -n 10 md-mx-ctrl load .fi If you do not have \fBwatch\fR, the following shell script is a less fancy equivalent: .nf #!/bin/sh while true; do clear date md-mx-ctrl load sleep 10 done .fi .TP .B rawload Prints the load averages in computer-readable format. The format consists of twenty-nine space-separated numbers: The first four are integers representing the number of messages scanned in the last 10 seconds, 1 minute, 5 minutes and 10 minutes. The second four are floating-point numbers representing the average number of busy workers in the last 10 seconds, 1 minute, 5 minutes and 10 minutes. The third four are floating-point numbers representing the average time per scan in milliseconds over the last 10 seconds, 1 minute, 5 minutes and 10 minutes. The fourth four are the number of worker activations (new workers started) over the last 10 seconds, 1 minute, 5 minutes and 10 minutes. The fifth four are the number of workers reaped (workers that have exited) over the last 10 seconds, 1 minute, 5 minutes and 10 minutes. The sixth four are the number of busy, idle, stopped and killed workers. The seventh four are the number of messages processed, the number of worker activations, the size of the request queue, and the number of requests actually on the queue. The final number is the number of seconds since the multiplexor was started. .TP .B load-relayok Similar to \fBload\fR, but shows timings for \fBfilter_relay\fR calls. .TP .B load-senderok Similar to \fBload\fR, but shows timings for \fBfilter_sender\fR calls. .TP .B load-recipok Similar to \fBload\fR, but shows timings for \fBfilter_recipient\fR calls. .TP .B rawload-relayok Similar to \fBrawload\fR, but shows timings for \fBfilter_relay\fR calls. Note that the worker activation and reap statistics are present, but always 0. They are only valid in a \fBrawload\fR command. .TP .B rawload-senderok Similar to \fBrawload\fR, but shows timings for \fBfilter_sender\fR calls. Note that the worker activation and reap statistics are present, but always 0. They are only valid in a \fBrawload\fR command. .TP .B rawload-recipok Similar to \fBrawload\fR, but shows timings for \fBfilter_recipient\fR calls. Note that the worker activation and reap statistics are present, but always 0. They are only valid in a \fBrawload\fR command. .TP .B load1 \fInsecs\fR The \fBload1\fR command displays the load for various commands over the last \fInsecs\fR seconds, where \fInsecs\fR is an integer from 10 to 600. The \fBload1\fR command combines the output of \fBload\fR, \fBload-relayok\fR, \fBload-senderokf\fR and \fBload-recipok\fR into one display. You might use the command like this: .nf watch -n 10 md-mx-ctrl load1 60 .fi .TP .B rawload1 \fInsecs\fR Returns the \fBload1\fR data in human-readable format. The result is a line containing twenty-six space-separated numbers: The first three numbers are the number of scans performed in the last \fInsecs\fR seconds, the average number of busy workers when a scan was initiated and the average number of milliseconds per scan. The second three are the same measurements for \fBfilter_relay\fR calls. The third three are the same measurements for \fBfilter_sender\fR calls. The fourth three are the same measurements for \fBfilter_relay\fR calls. The thirteenth through sixteenth numbers are the number of busy, idle, stopped and killed workers, respectively. The seventeenth number is the number of scans since \fBmimedefang-multiplexor\fR was started. The eighteenth number is the number of times a new worker has been activated since program startup. The nineteenth number is the size of the request queue and the twentieth number is the actual number of queued requests. The twenty-first number is the time since program startup and the twenty-second number is a copy of \fInsecs\fR for convenience. The twenty-third through twenty-sixth numbers are the number of workers currently executing a scan, relayok, senderok and recipok command respectively. .TP .B jsonload1 \fInsecs\fR Returns the \fBload1\fR data in JSON format. .TP .B workers Displays a list of workers and their process IDs. Each line of output consists of a worker number, a status (I, B, K, or S), and for idle or busy workers, the process-ID of the worker. For busy workers, the line may contain additional information about what the worker is doing. The command \fBslaves\fR is a deprecated synonym for this command. .TP .B busyworkers Similar to \fBworkers\fR, but only outputs a line for each busy worker. The command \fBbusyslaves\fR is a deprecated synonym for this command. .TP .B workerinfo \fR\fIn\fR Displays information about worker number \fIn\fR. The command \fBslaveinfo\fR is a deprecated synonym for this command. .TP .B reread Forces \fBmimedefang-multiplexor\fR to kill all idle workers, and terminate and restart busy workers when they become idle. This forces a reread of filter rules. .TP .B msgs Prints the total number of messages scanned since the multiplexor started. .SH ADDITIONAL COMMANDS You can supply any other command and arguments to \fBmd-mx-ctrl\fR. It percent-encodes each command-line argument, glues the encoded arguments together with a single space between each, and sends the result to the multiplexor as a command. This allows you to send arbitrary commands to your Perl workers. See the section "EXTENDING MIMEDEFANG" in \fBmimedefang-filter\fR(5) for additional details. .SH PERMISSIONS \fBmd-mx-ctrl\fR uses the multiplexor's socket; therefore, it probably needs to be run as \fIroot\fR or the same user as \fBmimedefang-multiplexor\fR. .SH AUTHOR \fBmd-mx-ctrl\fR was written by Dianne Skoll . The \fBmimedefang\fR home page is \fIhttps://www.mimedefang.org/\fR. .SH SEE ALSO mimedefang.pl(8), mimedefang-filter(5), mimedefang(8), mimedefang-protocol(7), watch-mimedefang(8) mimedefang-3.6/md-mx-ctrl.c000066400000000000000000000463171475763067200156420ustar00rootroot00000000000000/*********************************************************************** * * md-mx-ctrl.c * * Command-line utility for talking to mimedefang-multiplexor directly * * Copyright (C) 2002-2005 by Roaring Penguin Software Inc. * * http://www.roaringpenguin.com * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #include "config.h" #define SMALLBUF 262144 #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif #ifndef AF_LOCAL #define AF_LOCAL AF_UNIX #endif char cmdbuf[SMALLBUF+1]; int read_stdin = 0; FILE *errfp; static int percent_encode(unsigned char *in, unsigned char *out, int outlen); static int process(char const *sock, char const *cmd); /********************************************************************** * %FUNCTION: percent_decode * %ARGUMENTS: * buf -- a buffer with percent-encoded data * %RETURNS: * Nothing * %DESCRIPTION: * Decodes buf IN PLACE. ***********************************************************************/ static void percent_decode(unsigned char *buf) { unsigned char *in = buf; unsigned char *out = buf; unsigned int val; while(*in) { if (*in == '%' && isxdigit(*(in+1)) && isxdigit(*(in+2))) { sscanf((char *) in+1, "%2x", &val); *out++ = (unsigned char) val; in += 3; continue; } *out++ = *in++; } /* Copy terminator */ *out = 0; } /********************************************************************** * %FUNCTION: MXCommand * %ARGUMENTS: * sockname -- multiplexor socket name * cmd -- command to send * buf -- buffer for reply * len -- length of buffer * %RETURNS: * 0 if all went well, -1 on error. * %DESCRIPTION: * Sends a command to the multiplexor and reads the answer back. ***********************************************************************/ static int MXCommand(char const *sockname, char const *cmd, char *buf, int len) { int fd; struct sockaddr_un addr; int nleft, nwritten, nread; int n; fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (fd < 0) { fprintf(errfp, "ERROR MXCommand: socket: %s\n", strerror(errno)); return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; strncpy(addr.sun_path, sockname, sizeof(addr.sun_path) - 1); if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { if (errno == EPERM || errno == EACCES) { fprintf(errfp, "ERROR MXCommand: connect: %s\n", strerror(errno)); } else { fprintf(errfp, "ERROR MXCommand: connect: %s: Is multiplexor running?\n", strerror(errno)); } close(fd); return -1; } nwritten = 0; nleft = strlen(cmd); n = 0; while(nleft) { n = write(fd, cmd+nwritten, nleft); if (n < 0) { if (errno == EINTR) continue; break; } nwritten += n; nleft -= n; } if (n < 0) { fprintf(errfp, "ERROR MXCommand: write: %s: Is multiplexor running?\n", strerror(errno)); close(fd); return -1; } /* Now read the answer */ nread = 0; nleft = len; while(nleft) { n = read(fd, buf+nread, nleft); if (n <= 0) { if (n < 0 && errno == EINTR) continue; break; } nread += n; nleft -= n; } if (n < 0) { fprintf(errfp, "ERROR MXCommand: read: %s: Is multiplexor running?\n", strerror(errno)); close(fd); return -1; } buf[nread] = 0; close(fd); return 0; } static void buildCmd(int argc, char *argv[], char *outbuf, int buflen) { int i; int k; int len = 0; for (i=0; i= buflen-1) { outbuf[buflen-1] = 0; return; } if (i < argc-1) { outbuf[len] = ' '; len++; } } outbuf[len++] = '\n'; outbuf[len] = 0; } static int percent_encode(unsigned char *in, unsigned char *out, int outlen) { unsigned char tmp[8]; int nwritten = 0; unsigned char c; if (outlen <= 0) { return 0; } if (outlen == 1) { *out = 0; return 0; } /* Do real work */ while((c = *in++) != 0) { if (c <= 32 || c > 126 || c == '%' || c == '\\' || c == '\'' || c == '"') { if (nwritten >= outlen-3) { break; } sprintf((char *) tmp, "%%%02X", (unsigned int) c); *out++ = tmp[0]; *out++ = tmp[1]; *out++ = tmp[2]; nwritten += 3; } else { *out++ = c; nwritten++; } if (nwritten >= outlen-1) { break; } } out[nwritten] = 0; return nwritten; } static int doCmd(char const *sock, char const *cmd, int decode) { char ans[SMALLBUF]; size_t l; if (MXCommand(sock, cmd, ans, sizeof(ans)) < 0) { return EXIT_FAILURE; } if (decode) { percent_decode((unsigned char *)ans); } printf("%s", ans); /* If it didn't end with a newline, add one */ l = strlen(ans); if (l > 0) { if (ans[l-1] != '\n') { printf("\n"); } } return EXIT_SUCCESS; } static int doStatus(char const *sock) { char ans[4096]; char *s; int i, l; if (MXCommand(sock, "status\n", ans, sizeof(ans)) < 0) { return EXIT_FAILURE; } if (*ans != 'I' && *ans != 'K' && *ans != 'S' && *ans != 'B') { fprintf(errfp, "ERROR %s", ans); return EXIT_FAILURE; } /* Chop off message and activation count */ s = ans; while (*s && *s != ' ') s++; *s = 0; l = strlen(ans); printf("Max workers: %d\n", l); for (i=0; i= 0) { fprintf(stderr, "\nPossible commands:\n%s", ans); } } int main(int argc, char *argv[]) { int c; char *sock = NULL; errfp = stderr; while ((c = getopt(argc, argv, "hs:i")) != -1) { switch(c) { case 'i': read_stdin = 1; errfp = stdout; break; case 'h': usage(sock); exit(EXIT_SUCCESS); case 's': if (sock) free(sock); sock = strdup(optarg); if (!sock) { fprintf(stderr, "Out of memory\n"); exit(EXIT_FAILURE); } break; default: usage(sock); exit(EXIT_FAILURE); } } if (!read_stdin && !argv[optind]) { usage(sock); exit(EXIT_FAILURE); } if (!sock) { sock = SPOOLDIR "/mimedefang-multiplexor.sock"; } if (read_stdin) { while(fgets(cmdbuf, SMALLBUF, stdin)) { process(sock, cmdbuf); fflush(stdout); } } else { buildCmd(argc-optind, argv+optind, cmdbuf, SMALLBUF); exit(process(sock, cmdbuf)); } exit(EXIT_SUCCESS); } static int process(char const *sock, char const *cmd) { if (!strcmp(cmd, "status\n")) { return doStatus(sock); } else if (!strcmp(cmd, "barstatus\n")) { return doBarStatus(sock); } else if (!strcmp(cmd, "jsonstatus\n")) { return doJsonStatus(sock); } else if (!strcmp(cmd, "reread\n")) { return doCmd(sock, "reread\n", 0); } else if (!strcmp(cmd, "rawstatus\n")) { return doCmd(sock, "status\n", 0); } else if (!strcmp(cmd, "msgs\n")) { return doCmd(sock, "msgs\n", 0); } else if (!strcmp(cmd, "rawload\n")) { return doCmd(sock, "load\n", 0); } else if (!strcmp(cmd, "rawload-relayok\n")) { return doCmd(sock, "load-relayok\n", 0); } else if (!strncmp(cmd, "rawload1 ", 9)) { return doCmd(sock, cmd+3, 0); } else if (!strncmp(cmd, "load1 ", 6)) { return doLoad1(sock, cmd); } else if (!strncmp(cmd, "jsonload1 ", 10)) { return doJsonLoad1(sock, cmd+4); } else if (!strcmp(cmd, "rawload-senderok\n")) { return doCmd(sock, "load-senderok\n", 0); } else if (!strcmp(cmd, "rawload-recipok\n")) { return doCmd(sock, "load-recipok\n", 0); } else if (!strcmp(cmd, "rawhload\n")) { return doCmd(sock, "hload\n", 0); } else if (!strcmp(cmd, "rawhload-relayok\n")) { return doCmd(sock, "hload-relayok\n", 0); } else if (!strcmp(cmd, "rawhload-senderok\n")) { return doCmd(sock, "hload-senderok\n", 0); } else if (!strcmp(cmd, "rawhload-recipok\n")) { return doCmd(sock, "hload-recipok\n", 0); } else if (!strcmp(cmd, "load\n")) { return doLoad(cmd, "Msgs", "Scan", sock); } else if (!strcmp(cmd, "load-relayok\n")) { return doLoad(cmd, "Conn", "Conn", sock); } else if (!strcmp(cmd, "load-senderok\n")) { return doLoad(cmd, "MAIL", "MAIL", sock); } else if (!strcmp(cmd, "load-recipok\n")) { return doLoad(cmd, "RCPT", "RCPT", sock); } else if (!strcmp(cmd, "hload\n")) { return doHLoad(cmd, "Msgs", "Scan", sock); } else if (!strcmp(cmd, "hload-relayok\n")) { return doHLoad(cmd, "Conn", "Conn", sock); } else if (!strcmp(cmd, "hload-senderok\n")) { return doHLoad(cmd, "MAIL", "MAIL", sock); } else if (!strcmp(cmd, "hload-recipok\n")) { return doHLoad(cmd, "RCPT", "RCPT", sock); } else { return doCmd(sock, cmd, 1); } } mimedefang-3.6/milter_cap.c000066400000000000000000000075141475763067200157710ustar00rootroot00000000000000/*********************************************************************** * * milter_cap.c * * Utility functions for checking libmilter capabilities * * Copyright (C) 2006 Roaring Penguin Software Inc. * http://www.roaringpenguin.com * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #include #include "libmilter/mfapi.h" #include "milter_cap.h" /********************************************************************** * %FUNCTION: milter_version_ok * %ARGUMENTS: * None * %RETURNS: * 1 if the libmilter version we're linked against matches that which we * were compiled against; 0 otherwise. * %DESCRIPTION: * This makes a stab at ensuring we're linking to the right library, but * it's not foolproof. ***********************************************************************/ int milter_version_ok(void) { #ifndef MILTER_BUILDLIB_HAS_VERSION /* We can't determine it -- build version is too old */ return 1; #else unsigned int major, minor, plevel; smfi_version(&major, &minor, &plevel); if (major == SM_LM_VRS_MAJOR(SMFI_VERSION) && minor == SM_LM_VRS_MINOR(SMFI_VERSION)) { /* Ignore patchlevel differences */ return 1; } return 0; #endif } /********************************************************************** * %FUNCTION: dump_milter_buildlib_info * %ARGUMENTS: * None * %RETURNS: * Nothing * %DESCRIPTION: * Debugging output -- dumps what we found out about libmilter. ***********************************************************************/ void dump_milter_buildlib_info(void) { #if SMFI_VERSION > 2 printf("%-30s %d.%d.%d (New-Style)\n", "SMFI_VERSION", SM_LM_VRS_MAJOR(SMFI_VERSION), SM_LM_VRS_MINOR(SMFI_VERSION), SM_LM_VRS_PLVL(SMFI_VERSION)); #else printf("%-30s %d (Old-Style)\n", "SMFI_VERSION", SMFI_VERSION); #endif #ifdef MILTER_BUILDLIB_HAS_CHGFROM printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_CHGFROM"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_CHGFROM"); #endif #ifdef MILTER_BUILDLIB_HAS_CHGHDRS printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_CHGHDRS"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_CHGHDRS"); #endif #ifdef MILTER_BUILDLIB_HAS_DATA printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_DATA"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_DATA"); #endif #ifdef MILTER_BUILDLIB_HAS_INSHEADER printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_INSHEADER"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_INSHEADER"); #endif #ifdef MILTER_BUILDLIB_HAS_NEGOTIATE printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_NEGOTIATE"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_NEGOTIATE"); #endif #ifdef MILTER_BUILDLIB_HAS_OPENSOCKET printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_OPENSOCKET"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_OPENSOCKET"); #endif #ifdef MILTER_BUILDLIB_HAS_PROGRESS printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_PROGRESS"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_PROGRESS"); #endif #ifdef MILTER_BUILDLIB_HAS_QUARANTINE printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_QUARANTINE"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_QUARANTINE"); #endif #ifdef MILTER_BUILDLIB_HAS_SETMLREPLY printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_SETMLREPLY"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_SETMLREPLY"); #endif #ifdef MILTER_BUILDLIB_HAS_SETSYMLIST printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_SETSYMLIST"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_SETSYMLIST"); #endif #ifdef MILTER_BUILDLIB_HAS_UNKNOWN printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_UNKNOWN"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_UNKNOWN"); #endif #ifdef MILTER_BUILDLIB_HAS_VERSION printf("%-30s 1\n", "MILTER_BUILDLIB_HAS_VERSION"); #else printf("%-30s 0\n", "MILTER_BUILDLIB_HAS_VERSION"); #endif } mimedefang-3.6/milter_cap.h000066400000000000000000000042511475763067200157710ustar00rootroot00000000000000/*********************************************************************** * * milter_cap.h * * Determine capabilities of libmilter we're building against. * * Copyright (C) 2006 by Roaring Penguin Software Inc. * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #ifndef MILTER_CAP_H #define MILTER_CAP_H 1 #ifndef SMFI_VERSION #error "You must include libmilter/mfapi.h before milter_cap.h" #endif #ifndef SM_LM_VRS_MAJOR #define SM_LM_VRS_MAJOR(v) (((v) & 0x7f000000) >> 24) #endif #ifndef SM_LM_VRS_MINOR #define SM_LM_VRS_MINOR(v) (((v) & 0x007fff00) >> 8) #endif #ifndef SM_LM_VRS_PLVL #define SM_LM_VRS_PLVL(v) ((v) & 0x0000007f) #endif #ifdef SMFIF_QUARANTINE #define MILTER_BUILDLIB_HAS_QUARANTINE 1 #endif /* UGLY hack... it's almost impossible to distinguish between the Sendmail 8.12 and 8.13 mfapi.h header. The only way to know for sure is as follows... */ #ifdef SMFIF_QUARANTINE #ifndef _FFR_QUARANTINE /* We have SMFIR_QUARANTINE without _FFR_QUARANTINE. Must be Sendmail 8.13 or higher */ #define SM813 1 #endif #endif #ifdef SM813 #define MILTER_BUILDLIB_HAS_OPENSOCKET 1 #define MILTER_BUILDLIB_HAS_SETMLREPLY 1 #define MILTER_BUILDLIB_HAS_PROGRESS 1 #define MILTER_BUILDLIB_HAS_INSHEADER 1 #else #ifdef _FFR_SMFI_OPENSOCKET #define MILTER_BUILDLIB_HAS_OPENSOCKET 1 #endif #ifdef _FFR_MULTILINE #define MILTER_BUILDLIB_HAS_SETMLREPLY 1 #endif #ifdef _FFR_SMFI_PROGRESS #define MILTER_BUILDLIB_HAS_PROGRESS 1 #endif #endif /* SM813 */ #if SMFI_VERSION > 2 #define MILTER_BUILDLIB_HAS_VERSION 1 #define MILTER_BUILDLIB_HAS_NEGOTIATE 1 #define MILTER_BUILDLIB_HAS_UNKNOWN 1 #define MILTER_BUILDLIB_HAS_DATA 1 #define MILTER_BUILDLIB_HAS_CHGFROM 1 #endif #if SMFI_VERSION > 1 #define MILTER_BUILDLIB_HAS_CHGHDRS 1 #endif #ifdef SMFIF_CHGFROM #define MILTER_BUILDLIB_HAS_CHGFROM 1 #endif #ifdef SMFIF_ADDRCPT_PAR #define MILTER_BUILDLIB_HAS_ADDRCPT_PAR 1 #endif #ifdef SMFIF_SETSYMLIST #define MILTER_BUILDLIB_HAS_SETSYMLIST 1 #endif extern int milter_version_ok(void); extern void dump_milter_buildlib_info(void); #endif mimedefang-3.6/mimedefang-filter.5.in000066400000000000000000002537541475763067200175710ustar00rootroot00000000000000.\" $Id$ .\" .TH MIMEDEFANG-FILTER 5 "8 February 2005" .UC 4 .SH NAME mimedefang-filter \- Configuration file for MIMEDefang mail filter. .SH DESCRIPTION \fBmimedefang-filter\fR is a Perl fragment that controls how \fBmimedefang.pl\fR disposes of various parts of a MIME message. In addition, it contains some global variable settings that affect the operation of \fBmimedefang.pl\fR. .SH CALLING SEQUENCE Incoming messages are scanned as follows: .PP 1) A temporary working directory is created. It is made the current working directory and the e-mail message is split into parts in this directory. Each part is represented internally as an instance of MIME::Entity. .PP 2) If the file \fB@CONFDIR_EVAL@/mimedefang-filter.pl\fR defines a Perl function called \fBfilter_begin\fR, it is called with a single argument consisting of a MIME::Entity representing the parsed e-mail message. Any return value is ignored. .PP 3) For each \fIleaf\fR part of the mail message, \fBfilter\fR is called with four arguments: \fBentity\fR, a MIME::Entity object; \fBfname\fR, the suggested filename taken from the MIME Content-Disposition header; \fBext\fR, the file extension, and \fBtype\fR, the MIME Content-Type value. For each \fInon-leaf\fR part of the mail message, \fBfilter_multipart\fR is called with the same four arguments as \fBfilter\fR. A non-leaf part of a message is a part that contains nested parts. Such a part has no useful body, but you should \fIstill perform filename checks\fR to check for viruses that use malformed MIME to masquerade as non-leaf parts (like message/rfc822). In general, any action you perform in \fBfilter_multipart\fR applies to the part itself \fIand\fR any contained parts. .PP Note that both \fBfilter\fR and \fBfilter_multipart\fR are optional. If you do not define them, a default function that simply accepts each part is used. .PP 4) After all parts have been processed, the function \fBfilter_end\fR is called if it has been defined. It is passed a single argument consisting of the (possibly modified) MIME::Entity object representing the message about to be delivered. Within \fBfilter_end\fR, you can call functions that modify the message headers body. .PP 5) After \fBfilter_end\fR returns, the function \fBfilter_wrapup\fR is called if it has been defined. It is passed a single argument consisting of the (possibly modified) MIME::Entity object representing the message about to be delivered, including any modifications made in \fBfilter_end\fR. Within \fBfilter_wrapup\fR, you can \fInot\fR call functions that modify the message body, but you can still add or modify message headers. .SH DISPOSITION \fBmimedefang.pl\fR examines each part of the MIME message and chooses a \fIdisposition\fR for that part. (A disposition is selected by calling one of the following functions from \fBfilter\fR and then immediately returning.) Available dispositions are: .TP .B action_accept The part is passed through unchanged. If no disposition function is returned, this is the default. .TP .B action_accept_with_warning The part is passed through unchanged, but a warning is added to the mail message. .TP .B action_drop The part is deleted without any notification to the recipients. .TP .B action_drop_with_warning The part is deleted and a warning is added to the mail message. .TP .B action_replace_with_warning The part is deleted and instead replaced with a text message. .TP .B action_quarantine The part is deleted and a warning is added to the mail message. In addition, a copy of the part is saved on the mail server in the directory @QDIR@ and a notification is sent to the MIMEDefang administrator. .TP .B action_bounce The entire e-mail message is rejected and an error returned to the sender. The intended recipients are not notified. Note that in spite of the name, MIMEDefang does \fInot\fR generate and e-mail a failure notification. Rather, it causes the SMTP server to return a 5\fIXX\fR SMTP failure code. .TP .B action_discard The entire e-mail message is discarded silently. Neither the sender nor the intended recipients are notified. .TP .B action_greylist The e-mail message is greylisted. If it's the first time we see this sender, the email will be tempfailed, when the sender will retry the email will be accepted. .SH CONTROLLING RELAYING You can define a function called \fBfilter_relay\fR in your filter. This lets you reject SMTP connection attempts early on in the SMTP dialog, rather than waiting until the whole message has been sent. Note that for this check to take place, you must use the \-r flag with \fBmimedefang\fR. .PP \fBfilter_relay\fR is passed six arguments: $hostip is the IP address of the relay host (for example, "127.0.0.1"), $hostname is the host name if known (for example, "localhost.localdomain"). If the host name could not be determined, $hostname is $hostip enclosed in square brackets. (That is, ("$hostname" eq "[$hostip]") will be true.) .PP The remaining four arguments to \fBfilter_relay\fR are $port, $myip, $myport and $qid, which contain the client's TCP port, the Sendmail daemon's listening IP address, the Sendmail daemon's listening port, and the Sendmail Queue-ID, respectively. \fINote\fR that the Queue-ID may not yet be available at this stage (for example, Postfix does not allocate a queue-ID this early.) If the Queue-ID is not available, the string NOQUEUE is passed instead. .PP \fBfilter_relay\fR must return a two-element list: ($code, $msg). $msg specifies the text message to use for the SMTP reply, but because of limitations in the Milter API, this message is for documentation purposes only---you cannot set the text of the SMTP message returned to the SMTP client from \fBfilter_relay\fR. $code is a literal string, and can have one of the following values: .TP .B 'REJECT' if the connection should be rejected. .TP .B 'CONTINUE' if the connection should be accepted. .TP .B 'TEMPFAIL' if a temporary failure code should be returned. .TP .B 'DISCARD' if the message should be accepted and silently discarded. .TP .B 'ACCEPT_AND_NO_MORE_FILTERING' if the connection should be accepted \fIand no further filtering done\fR. .PP Earlier versions of MIMEDefang used -1 for TEMPFAIL, 0 for REJECT and 1 for CONTINUE. These values still work, but are deprecated. .PP In the case of REJECT or TEMPFAIL, $msg specifies the text part of the SMTP reply. $msg \fImust not\fR contain newlines. .PP For example, if you wish to reject connection attempts from any machine in the spammer.com domain, you could use this function: .nf sub filter_relay { my ($ip, $name) = @_; if ($name =~ /spammer\\.com$/) { return ('REJECT', "Sorry; spammer.com is blacklisted"); } return ('CONTINUE', "ok"); } .fi .SH FILTERING BY HELO You can define a function called \fBfilter_helo\fR in your filter. This lets you reject connections after the HELO/EHLO SMTP command. Note that for this function to be called, you must use the \-H flag with \fBmimedefang\fR. .PP \fBfilter_helo\fR is passed seven arguments: $ip and $name are the IP address and name of the sending relay, as in \fBfilter_relay\fR. The third argument, $helo, is the argument supplied in the HELO/EHLO command. .PP The remaining four arguments to \fBfilter_helo\fR are $port, $myip, $myport and $qid, which contain the client's TCP port, the Sendmail daemon's listening IP address, the Sendmail daemon's listening port, and the Sendmail Queue-ID, respectively. \fINote\fR that the Queue-ID may not yet be available at this stage (for example, Postfix does not allocate a queue-ID this early.) If the Queue-ID is not available, the string NOQUEUE is passed instead. .PP \fBfilter_helo\fR must return a two-to-five element list: ($code, $msg, $smtp_code, $smtp_dsn, $delay). $code is a return code, with the same meaning as the $code return from \fBfilter_relay\fR. $msg specifies the text message to use for the SMTP reply. If $smtp_code and $smtp_dsn are supplied, they become the SMTP numerical reply code and the enhanced status delivery code (DSN code). If they are not supplied, sensible defaults are used. $delay specifies a delay in seconds; the C milter code will sleep for $delay seconds before returning the reply to Sendmail. $delay defaults to zero. (Note that the delay is implemented in the Milter C code; if you specify a delay of 30 seconds, that doesn't mean a Perl worker is tied up for the duration of the delay. The delay only costs one Milter thread.) .SH FILTERING BY SENDER You can define a function called \fBfilter_sender\fR in your filter. This lets you reject messages from certain senders, rather than waiting until the whole message has been sent. Note that for this check to take place, you must use the \-s flag with \fBmimedefang\fR. .PP \fBfilter_sender\fR is passed four arguments: $sender is the envelope e-mail address of the sender (for example, ""). The address may or may not be surrounded by angle brackets. $ip and $name are the IP address and host name of the SMTP relay. Finally, $helo is the argument to the SMTP "HELO" command. .PP Inside \fBfilter_sender\fR, you can access any ESMTP arguments (such as "SIZE=12345") in the array @ESMTPArgs. Each ESMTP argument occupies one array element. .PP \fBfilter_sender\fR must return a two-to-five element list, with the same meaning as the return value from \fBfilter_helo\fR. .PP For example, if you wish to reject messages from spammer@badguy.com, you could use this function: .nf sub filter_sender { my ($sender, $ip, $hostname, $helo) = @_; if ($sender =~ /^?$/i) { return ('REJECT', 'Sorry; spammer@badguy.com is blacklisted.'); } return ('CONTINUE', "ok"); } .fi .PP As another example, some spammers identify their own machine as your machine in the SMTP "HELO" command. This function rejects a machine claiming to be in the "roaringpenguin.com" domain unless it really is a Roaring Penguin machine: .nf sub filter_sender { my($sender, $ip, $hostname, $helo) = @_; if ($helo =~ /roaringpenguin\.com/i) { if ($ip ne "127.0.0.1" and $ip ne "216.191.236.23" and $ip ne "216.191.236.30") { return('REJECT', "Go away... $ip is not in roaringpenguin.com"); } } return ('CONTINUE', "ok"); } .fi .PP As a third example, you may wish to prevent spoofs by requiring SMTP authentication when email is sent from some email addresses. This function rejects mail from "king@example.com", unless the connecting user properly authenticated as "elvisp". Note that this needs access to the %SendmailMacros global, that is not available in filter_sender until after a call to \fBread_commands_file\fR. .nf sub filter_sender { my($sender, $ip, $hostname, $helo) = @_; read_commands_file(); ### notice: This assumes The King uses authentication without realm! if ($sender =~ /^?$/i and $SendmailMacros{auth_authen} ne "elvisp") { return('REJECT', "Faking mail from the king is not allowed."); } return ('CONTINUE', "ok"); } .fi .SH FILTERING BY RECIPIENT You can define a function called \fBfilter_recipient\fR in your filter. This lets you reject messages to certain recipients, rather than waiting until the whole message has been sent. Note that for this check to take place, you must use the \-t flag with \fBmimedefang\fR. .PP \fBfilter_recipient\fR is passed nine arguments: $recipient is the envelope address of the recipient and $sender is the envelope e-mail address of the sender (for example, ""). The addresses may or may not be surrounded by angle brackets. $ip and $name are the IP address and host name of the SMTP relay. $first is the envelope address of the \fIfirst\fR recipient for this message, and $helo is the argument to the SMTP "HELO" command. The last three arguments, $rcpt_mailer, $rcpt_host and $rcpt_addr are the Sendmail mailer, host and address triple for the recipient address. For example, for local recipients, $rcpt_mailer is likely to be "local", while for remote recipients, it is likely to be "esmtp". .PP Inside \fBfilter_recipient\fR, you can access any ESMTP arguments (such as "NOTIFY=never") in the array @ESMTPArgs. Each ESMTP argument occupies one array element. .PP \fBfilter_recipient\fR must return a two-to-five element list whose interpretation is the same as for \fBfilter_sender\fR. Note, however, that if \fBfilter_recipient\fR returns 'DISCARD', then the entire message for \fIall\fR recipients is discarded. (It doesn't really make sense, but that's how Milter works.) .PP For example, if you wish to reject messages from spammer@badguy.com, unless they are to postmaster@mydomain.com, you could use this function: .nf sub filter_recipient { my ($recipient, $sender, $ip, $hostname, $first, $helo, $rcpt_mailer, $rcpt_host, $rcpt_addr) = @_; if ($sender =~ /^?$/i) { if ($recipient =~ /^?$/i) { return ('CONTINUE', "ok"); } return ('REJECT', 'Sorry; spammer@badguy.com is blacklisted.'); } return ('CONTINUE', "ok"); } .fi .SH INITIALIZATION AND CLEANUP Just before a worker begins processing messages, \fBmimedefang.pl\fR calls the functions \fBfilter_initialize\fR (if it is defined) with no arguments. By the time \fBfilter_initialize\fR is called, all the other initialization (such as setting up syslog facility and priority) has been done. If you are not using an embedded Perl interpreter, then performing an action inside \fBfilter_initialize\fR is practically the same as performing it directly in the filter file, outside any function definition. However, if you are using an embedded Perl interpreter, then anything you call directly from outside a function definition is executed \fIonce only\fR in the parent process. Anything in \fBfilter_initialize\fR is executed \fIonce per worker\fR. If you use any code that opens a descriptor (for example, a connection to a database server), you \fImust\fR run that code inside \fBfilter_initialize\fR and not directly from the filter, because the multiplexor closes all open descriptors when it activates a new worker. From within \fBfilter_initialize\fR a configuration file could be loaded by calling \fBread_config\fR. \fBread_config\fR accepts a configuration file path and it can be used to overwrite global variables. Configuration file format is pure Perl code. When a worker is about to exit, \fBmimedefang.pl\fR calls the function \fBfilter_cleanup\fR (if it is defined) with no arguments. This function can do whatever cleanup you like, such as closing file descriptors and cleaning up long-lived worker resources. The return value from \fBfilter_cleanup\fR becomes the worker's exit status. (You should therefore ensure that \fBfilter_cleanup\fR returns an integer suitable for a process exit status.) .PP If \fBfilter_cleanup\fR takes longer than 10 seconds to run, the worker is sent a SIGTERM signal. If that doesn't kill it (because you're catching signals, perhaps), then a further 10 seconds later, the worker is sent a SIGKILL signal. .SH CONTROLLING PARSING If you define a function called \fBfilter_create_parser\fR taking no arguments, then \fBmimedefang.pl\fR will call it to create a MIME::Parser object for parsing mail messages. \fBFilter_create_parser\fR is expected to return a MIME::Parser object (or an instance of a class derived from MIME::Parser). You can use \fBfilter_create_parser\fR to change the behavior of the MIME::Parser used by \fBmimedefang.pl\fR. If you do not define a \fBfilter_create_parser\fR function, then a built-in version equivalent to this is used: .nf sub filter_create_parser () { my $parser = MIME::Parser->new(); $parser->extract_nested_messages(1); $parser->extract_uuencode(1); $parser->output_to_core(0); $parser->tmp_to_core(0); return $parser; } .fi .SH EXTENDING MIMEDEFANG The man page for \fBmimedefang-protocol\fR(7) lists commands that are passed to workers in server mode (see "SERVER COMMANDS".) You can define a function called \fBfilter_unknown_cmd\fR to extend the set of commands your filter can handle. If you define \fBfilter_unknown_cmd\fR, it is passed the unknown command as a single argument. It should return a list of values as follows: The first element of the list must be either "ok" or "error:" (with the colon.) The remaining arguments are percent-encoded. All the resulting pieces are joined together with a single space between them, and the resulting string passed back as the reply to the multiplexor. For example, the following function will make your filter reply to a "PING" command with "PONG": .nf sub filter_unknown_cmd ($) { my($cmd) = @_; if ($cmd eq "PING") { return("ok", "PONG"); } return("error:", "Unknown command"); } .fi You can test this filter by typing the following as root: .nf md-mx-ctrl PING .fi The response should be: .nf ok PONG .fi If you extend the set of commands using \fBfilter_unknown_cmd\fR, you should make all your commands start with an upper-case letter to avoid clashes with future built-in commands. .SH REJECTING UNKNOWN USERS EARLY A very common mail setup is to have a MIMEDefang machine act as an SMTP proxy, accepting and scanning mail and then relaying it to the real mail server. Unfortunately, this means that the MIMEDefang machine cannot know if a local address is valid or not, and will forward all mail for the appropriate domains. If a mail comes in for an unknown user, the MIMEDefang machine will be forced to generate a bounce message when it tries to relay the mail. .PP It's often desirable to have the MIMEDefang host reply with a "User unknown" SMTP response directly. While this can be done by copying the list of local users to the MIMEDefang machine, MIMEDefang has a built-in function called \fBmd_check_against_smtp_server\fR for querying another relay host: .TP .B md_check_against_smtp_server($sender, $recip, $helo, $server, $port) This function connects to the SMTP server $server and pretends to send mail from $sender to $recip. The return value is always a two-element array. If the RCPT TO: command succeeds, the return value is ("CONTINUE", "OK"). If the RCPT fails with a permanent failure, the return value is ("REJECT", $msg), where $msg is the message from the SMTP server. Any temporary failures, connection errors, etc. result in a return value of ("TEMPFAIL", $msg). The optional argument $port specifies the TCP port to connect to. If it is not supplied, then the default SMTP port of 25 is used. If the server offers STARTTLS support, TLS step-up is attempted. If TLS step-up fails, the check will fall-back to using clear text and log the failure .PP Suppose the machine \fBfilter.domain.tld\fR is filtering mail destined for the real mail server \fBmail.domain.tld\fR. You could have a \fBfilter_recipient\fR function like this: .nf sub filter_recipient { my($recip, $sender, $ip, $host, $first, $helo, $rcpt_mailer, $rcpt_host, $rcpt_addr) = @_; return md_check_against_smtp_server($sender, $recip, "filter.domain.tld", "mail.domain.tld"); } .fi For each RCPT TO: command, MIMEDefang opens an SMTP connection to \fBmail.domain.tld\fR and checks if the command would succeed. .PP Please note that you should only use \fBmd_check_against_smtp_server\fR if your mail server responds with a failure code for nonexistent users at the RCPT TO: level. Also, this function may impose too much overhead if you receive a lot of e-mail, and it will generate lots of useless log entries on the real mail server (because of all the RCPT TO: probes.) It may also significantly increase the load on the real mail server. .SH GLOBAL VARIABLES YOU CAN SET The following Perl global variables should be set in \fBmimedefang-filter\fR: .TP .B $AdminAddress The e-mail address of the MIMEDefang administrator. .TP .B $DaemonAddress The e-mail address from which MIMEDefang-originated notifications come. .TP .B $AddWarningsInline If this variable is set to 0, then all MIMEDefang warnings (such as created by action_quarantine or action_drop_with_warning) are collected together and added in a separate MIME part called WARNING.TXT. If the variable is set to 1, then the warnings are added directly in the first text/plain and text/html parts of the message. If the message does not contain any text/plain or text/html parts, then a WARNING.TXT MIME part is added as before. .TP .B $MaxMIMEParts A message containing many MIME parts can cause MIME::Tools to consume large amounts of memory and bring your system to its knees. If you set $MaxMIMEParts to a positive number, then MIME parsing is terminated for messages with more than that many parts, and the message is bounced. In this case, \fInone\fR of your filter functions is called. By default, $MaxMIMEParts is set to -1, meaning there is no limit on the number of parts in a message. Note that in order to use this variable, you \fImust\fR install the Roaring Penguin patched version of MIME::Tools, version 5.411a-RP-Patched-02 or newer. .TP .B $Stupidity{"NoMultipleInlines"} Set this to 1 if your e-mail is too stupid to display multiple MIME parts in-line. In this case, a nasty hack causes the first part of the original message to appear as an attachment if warning are issued. Mail clients that are not this stupid are Netscape Communicator and Pine. On the other hand, Microsoft Exchange and Microsoft Outlook are indeed this stupid. Perhaps users of those clients should switch. The following global variables may optionally be set. If they are not set, sensible defaults are used: .TP .B $AddApparentlyToForSpamAssassin By default, MIMEDefang tries to pass SpamAssassin a message that looks exactly like one it would receive via procmail. This means adding a Received: header, adding a Message-ID header if necessary, and adding a Return-Path: header. If you set $AddApparentlyToForSpamAssassin to 1, then MIMEDefang also adds an Apparently-To: header with all the envelope recipients before passing the message to SpamAssassin. This lets SpamAssassin detect possibly whitelisted recipient addresses. The default value for $AddApparentlyToForSpamAssassin is 0. .TP .B $SyslogFacility This specifies the logging facility used by mimedefang.pl. By default, it is set to "mail", but you can set it to other possibilites. See the openlog(3) man page for details. You should name facilities as all-lowercase without the leading "LOG_". That is, use "local3", not "LOG_LOCAL3". .TP .B $WarningLocation \fR(default 0) If set to 0 (the default), non-inline warnings are placed first. If you want the warning at the end of the e-mail, set $WarningLocation to -1. .TP .B $DaemonName \fR(default """MIMEDefang"") The full name used when MIMEDefang sends out notifications. .TP .B $AdminName \fR(default """MIMEDefang Administrator"") The full name of the MIMEDefang administrator. .TP .B $SALocalTestsOnly \fR(default 1) If set to 1, SpamAssassin calls will use only local tests. This is the default and recommended setting. This disables Received, RBL and Razor tests in an all or nothing fashion. To use Razor this \fBMUST\fR be set to 0. You can add 'skip_rbl_checks 1' to your SpamAssassin config file if you need to. .TP .B $NotifySenderSubject \fR(default """MIMEDefang Notification"") The subject used when e-mail is sent out by action_notify_sender(). If you set this, you should set it each time you call action_notify_sender() to ensure consistency. .\" fix Emacs highlighting..." .TP .B $NotifyAdministratorSubject \fR(default """MIMEDefang Notification"") The subject used when e-mail is sent out by action_notify_administrator(). If you set this, you should set it each time you call action_notify_administrator() to ensure consistency. .TP .B $QuarantineSubject \fR(default """MIMEDefang Quarantine Report"") The subject used when a quarantine notice is sent to the administrator. If you set this, you should set it each time you call action_quarantine() or action_quarantine_entire_message(). .TP .B $NotifyNoPreamble \fR(default 0) Normally, notifications sent by action_notify_sender() have a preamble warning about message modifications. If you do not want this, set $NotifyNoPreamble to 1. .TP .B $CSSHost \fR(default 127.0.0.1:7777:local) Host and port for the Symantec CarrierScan Server virus scanner. This takes the form \fIip_addr\fR:\fIport\fR:\fIlocal_or_nonlocal\fR. The \fIip_addr\fR and \fIport\fR are the host and port on which CarrierScan Server is listening. If you want to scan local files, append :local to force the use of the AVSCANLOCAL command. If the CarrierScan Server is on another host, append :nonlocal to force the file contents to be sent to the scanner over the socket. .TP .B $SophieSock \fR(default @SPOOLDIR@/sophie) Socket used for Sophie daemon calls within message_contains_virus_sophie and entity_contains_virus_sophie unless a socket is provided by the calling routine. .TP .B $ClamdSock \fR(default @SPOOLDIR@/clamd.sock) Socket used for clamd daemon calls within message_contains_virus_clamd and entity_contains_virus_clamd unless a socket is provided by the calling routine. .TP .B $TrophieSock \fR(default @SPOOLDIR@/trophie) Socket used for Trophie daemon calls within message_contains_virus_trophie and entity_contains_virus_trophie unless a socket is provided by the calling routine. .SH FILTER The heart of \fBmimedefang-filter\fR is the \fBfilter\fR procedure. See the examples that came with MIMEDefang to learn to write a filter. The filter is called with the following arguments: .TP .B $entity The MIME::Entity object. (See the MIME::tools Perl module documentation.) .TP .B $fname The suggested attachment filename, or "" if none was supplied. .TP .B $ext The file extension (all characters from the rightmost period to the end of the filename.) .TP .B $type The MIME type (for example, "text/plain".) .PP The filename is derived as follows: .TP o First, if the Content-Disposition header has a "filename" field, it is used. .TP o Otherwise, if the Content-Type header has a "name" field, it is used. .TP o Otherwise, the Content-Description header value is used. .PP Note that the truly paranoid will check all three fields for matches. The functions \fBre_match\fR and \fBre_match_ext\fR perform regular expression matches on all three of the fields named above, and return 1 if any field matches. See the sample filters for details. The calling sequence is: .nf re_match($entity, "regexp") re_match_ext($entity, "regexp") .fi \fBre_match\fR returns true if any of the fields matches the regexp without regard to case. \fBre_match_ext\fR returns true if the extension in any field matches. An extension is defined as the last dot in a name and all remaining characters. .PP A third function called \fBre_match_in_zip_directory\fR will look inside zip files and return true if any of the file names inside the zip archive match the regular expression. Call it like this: .nf my $bh = $entity->bodyhandle(); my $path = (defined($bh)) ? $bh->path() : undef; if (defined($path) and re_match_in_zip_directory($path, "regexp")) { # Take action... } .fi You should \fInot\fR call \fBre_match_in_zip_directory\fR unless you know that the entity is a zip file attachment. .PP Another function called \fBre_match_in_rar_directory\fR will look inside rar files and return true if any of the file names inside the rar archive match the regular expression. The function is very similar to \fBre_match_in_zip_directory\fR but the unrar binary is required and must be specified in \fB$Features{"unrar"}\fR. .PP Another function called \fBre_match_in_7z_directory\fR will look inside 7zip files and return true if any of the file names inside the 7zip archive match the regular expression. The function is very similar to \fBre_match_in_zip_directory\fR but the 7z binary is required and must be specified in \fB$Features{"7zip"}\fR. .SH GLOBAL VARIABLES SET BY MIMEDEFANG.PL The following global variables are set by \fBmimedefang.pl\fR and are available for use in your filter. All of these variables are always available to filter_begin, filter, filter_multipart and filter_end. In addition, some of them are available in \fBfilter_relay\fR, \fBfilter_sender\fR or \fBfilter_recipient\fR. If this is the case, it will be noted below. .TP .B %Features This hash lets you determine at run-time whether certain functionality is available. This hash is available at all times assuming the detect_and_load_perl_modules() function has been called. The defined features are: $Features{"SpamAssassin"} is 1 if SpamAssassin 1.6 or better is installed; 0 otherwise. $Features{"HTML::Parser"} is 1 if HTML::Parser is installed; 0 otherwise. $Features{"Virus:FPROTD"} is currently always 0. Set it to 1 in your filter file if you have F-Risk's FPROTD scanner earlier than version 6. $Features{"Virus:FPROTD6"} is currently always 0. Set it to 1 in your filter file if you have version 6 of F-Risk's FPROTD scanner. $Features{"Virus:SymantecCSS"} is currently always 0. Set it to 1 in your filter file if you have the Symantec CarrierScan Server virus scanner. $Features{"Virus:NAI"} is the full path to NAI uvscan if it is installed; 0 if it is not. $Features{"Virus:BDC"} is the full path to Bitdefender bdc if it is installed; 0 if it is not. $Features{"Virus:NVCC"} is the full path to Norman Virus Control nvcc if it is installed; 0 if it is not. $Features{"Virus:HBEDV"} is the full path to H+BEDV AntiVir if it is installed; 0 if it is not. $Features{"Virus:VEXIRA"} is the full path to Central Command Vexira if it is installed; 0 if it is not. $Features{"Virus:SOPHOS"} is the full path to Sophos sweep if it is installed; 0 if it is not. $Features{"Virus:SAVSCAN"} is the full path to Sophos savscan if it is installed; 0 if it is not. $Features{"Virus:CLAMAV"} is the full path to Clam AV clamscan if it is installed; 0 if it is not. $Features{"Virus:AVP"} is the full path to AVP AvpLinux if it is installed; 0 if it is not. $Features{"Virus:AVP5"} is the full path to Kaspersky "aveclient" if it is installed; 0 if it is not. $Features{"Virus:CSAV"} is the full path to Command csav if it is installed; 0 if it is not. $Features{"Virus:FSAV"} is the full path to F-Secure fsav if it is installed; 0 if it is not. $Features{"Virus:FPROT"} is the full path to F-Risk f-prot if it is installed; 0 if it is not. $Features{"Virus:FPSCAN"} is the full path to F-Risk fpscan if it is installed; 0 if it is not. $Features{"Virus:SOPHIE"} is the full path to Sophie if it is installed; 0 if it is not. $Features{"Virus:CLAMD"} is the full path to clamd if it is installed; 0 if it is not. $Features{"Virus:CLAMDSCAN"} is the full path to clamdscan if it is installed; 0 if it is not. $Features{"Virus:TROPHIE"} is the full path to Trophie if it is installed; 0 if it is not. $Features{"Virus:NOD32"} is the full path to ESET NOD32 nod32cli if it is installed; 0 if it is not. $Features{"Path:RSPAMC"} is the full path to rspamc(1) if it is installed (deprecated); 0 if it is not. \fBNOTE:\fR Perl-module based features such as SpamAssassin are determined at runtime and may change as these are added and removed. Most Virus features are predetermined at the time of configuration and do not adapt to runtime availability unless changed by the filter rules. .TP .B $CWD This variable holds the working directory for the current message. During filter processing, \fBmimedefang.pl\fR chdir's into this directory before calling any of the filter_ functions. Note that this variable \fIis\fR set correctly in \fBfilter_sender\fR and \fBfilter_recipient\fR, but \fInot\fR in \fBfilter_relay\fR. .TP .B $SuspiciousCharsInHeaders If this variable is true, then \fBmimedefang\fR has discovered suspicious characters in message headers. This might be an exploit for bugs in MIME-parsing routines in some badly-written mail user agents (e.g. Microsoft Outlook.) You should \fIalways\fR drop such messages. .TP .B $SuspiciousCharsInBody If this variable is true, then \fBmimedefang\fR has discovered suspicious characters in the message body. This might be an exploit for bugs in MIME-parsing routines in some badly-written mail user agents (e.g. Microsoft Outlook.) You should \fIalways\fR drop such messages. .TP .B $RelayHostname The host name of the relay. This is the name of the host that is attempting to send e-mail to your host. May be "undef" if the host name could not be determined. This variable is available in \fBfilter_relay\fR, \fBfilter_sender\fR and \fBfilter_recipient\fR in addition to the body filtering functions. .TP .B $RelayAddr The IP address of the sending relay (as a string consisting of four dot-separated decimal numbers.) One potential use of \fB$RelayAddr\fR is to limit mailing to certain lists to people within your organization. This variable is available in \fBfilter_relay\fR, \fBfilter_sender\fR and \fBfilter_recipient\fR in addition to the body filtering functions. .TP .B $Helo The argument given to the SMTP "HELO" command. This variable is available in \fBfilter_sender\fR and \fBfilter_recipient\fR, but \fInot\fR in \fBfilter_relay\fR. .TP .B $Subject The contents of the "Subject:" header. .TP .B $Sender The sender of the e-mail. This variable is set in \fBfilter_sender\fR and \fBfilter_recipient\fR in addition to the body filtering functions. .TP .B @Recipients A list of the recipients. In \fBfilter_recipient\fR, it is set to the single recipient currently under consideration. Or, after calling \fBread_commands_file\fR within \fBfilter_recipient\fR, the current recipient under consideration is in the final position of the array, at \fB$Recipients[-1]\fR, while any previous (and accepted) recipients are at the beginning of the array, that is, in \fB@Recipients[0 .. $#Recipients-1]\fR. .TP .B $MessageID The contents of the "Message-ID:" header if one is present. Otherwise, contains the string "NOQUEUE". .TP .B $QueueID The Sendmail queue identifier if it could be determined. This variable \fIis\fR set correctly in \fBfilter_relay\fR, \fBfilter_helo\fR, \fBfilter_sender\fR and \fBfilter_recipient\fR. Note, however, that Postfix may not allocate a queue ID until \fBfilter_recipient\fR time. If a Queue-ID has not yet been allocated, $QueueID is set to "NOQUEUE". .TP .B $MsgID Set to $QueueID if the queue ID could be determined; otherwise, set to $MessageID. This identifier should be used in logging, because it matches the identifier used by Sendmail to log messages. Note that this variable \fIis\fR set correctly in \fBfilter_sender\fR and \fBfilter_recipient\fR, but it is \fInot\fR available in \fBfilter_relay\fR. .TP .B $VirusScannerMessages Each time a virus-scanning function is called, messages (if any) from the virus scanner are accumulated in this variable. You can use it in filter_end to formulate a notification (if you wish.) .TP .B $VirusName If a virus-scanning function found a virus, this variable will hold the virus name (if it could be determined.) .TP .B $SASpamTester If defined, this is the configured Mail::SpamAssassin object used for mail tests. It may be initialized with a call to \fBspam_assassin_init\fR which also returns it. .TP .B %SendmailMacros This hash contains the values of some Sendmail macros. The hash elements exist only for macros defined by Sendmail. See the Sendmail documentation for the meanings of the macros. By default, \fBmimedefang\fR passes the values of the following macros: ${daemon_name}, ${daemon_port}, ${if_name}, ${if_addr}, $j, $_, $i, ${tls_version}, ${cipher}, ${cipher_bits}, ${cert_subject}, ${cert_issuer}, ${auth_type}, ${auth_authen}, ${auth_ssf}, ${auth_author}, ${mail_mailer}, ${mail_host} and ${mail_addr}. In addition, ${client_port} is set to the client's TCP port. If any macro is not set or not passed to milter, it will be unavailable. To access the value of a macro, use: .nf $SendmailMacros{"macro_name"} .fi Do not place curly brackets around the macro name. This variable is available in \fBfilter_sender\fR and \fBfilter_recipient\fR after a call to \fBread_commands_file\fR. .TP .B @SenderESMTPArgs This array contains all the ESMTP arguments supplied in the MAIL FROM: command. For example: .nf sub print_sender_esmtp_args { foreach (@SenderESMTPArgs) { print STDERR "Sender ESMTP arg: $_\n"; } } .fi .TP .B %RecipientESMTPArgs This hash contains all the ESMTP arguments supplied in each RCPT TO: command. For example: .nf sub print_recip_esmtp_args { foreach my $recip (@Recipients) { foreach(@{$RecipientESMTPArgs{$recip}}) { print STDERR "Recip ESMTP arg for $recip: $_\n"; } } } .fi .TP .B %RecipientMailers This hash contains the Sendmail "mailer-host-address" triple for each recipient. Here's an example of how to use it: .nf sub print_mailer_info { my($recip, $mailer, $host, $addr); foreach $recip (@Recipients) { $mailer = ${RecipientMailers{$recip}}[0]; $host = ${RecipientMailers{$recip}}[1]; $addr = ${RecipientMailers{$recip}}[2]; print STDERR "$recip: mailer=$mailer, host=$host, addr=$addr\\n"; } } .fi In \fBfilter_recipient\fR, this variable by default only contains information on the recipient currently under investigation. Information on all recipients is available after calling \fBread_commands_file\fR. .SH ACTIONS When the filter procedure decides how to dispose of a part, it should call one or more \fBaction_\fR subroutines. The action subroutines are: .TP .B action_accept() Accept the part. .TP .B action_rebuild() \fR Rebuild the mail body, even if \fBmimedefang\fR thinks no changes were made. Normally, \fBmimedefang\fR does not alter a message if no changes were made. \fBaction_rebuild\fR may be used if you make changes to entities directly (by manipulating the MIME::Head, for example.) Unless you call \fBaction_rebuild\fR, \fBmimedefang\fR will be unaware of the changes. Note that all the built-in \fBaction...\fR routines that change a message implicitly call \fBaction_rebuild\fR. .TP .B action_add_header($hdr, $val) Add a header to the message. This can be used in \fBfilter_begin\fR or \fBfilter_end\fR. The $hdr component is the header name \fIwithout the colon\fR, and the $val is the header value. For example, to add the header: .nf X-MyHeader: A nice piece of text .fi use: .nf action_add_header("X-MyHeader", "A nice piece of text"); .fi .TP .B action_change_header($hdr, $val, $index) Changes an existing header in the message. This can be used in \fBfilter_begin\fR or \fBfilter_end\fR. The $hdr parameter is the header name \fIwithout the colon\fR, and $val is the header value. If the header does not exist, then a header with the given name and value is added. The $index parameter is optional; it defaults to 1. If you supply it, then the $index'th occurrence of the header is changed, if there is more than one header with the same name. (This is common with the Received: header, for example.) .TP .B action_insert_header($hdr, $val, $index) Add a header to the message int the specified position $index. A position of 0 specifies that the header should be prepended before existing headers. This can be used in \fBfilter_begin\fR or \fBfilter_end\fR. The $hdr component is the header name \fIwithout the colon\fR, and the $val is the header value. .TP .B action_delete_header($hdr, $index) Deletes an existing header in the message. This can be used in \fBfilter_begin\fR or \fBfilter_end\fR. The $hdr parameter is the header name \fIwithout the colon\fR. The $index parameter is optional; it defaults to 1. If you supply it, then the $index'th occurrence of the header is deleted, if there is more than one header with the same name. .TP .B action_delete_all_headers($hdr) Deletes all headers with the specified name. This can be used in \fBfilter_begin\fR or \fBfilter_end\fR. The $hdr parameter is the header name \fIwithout the colon\fR. .TP .B action_drop() Drop the part. If called from \fBfilter_multipart\fR, drops all contained parts also. .TP .B action_drop_with_warning($msg) Drop the part, but add the warning \fI$msg\fR to the e-mail message. If called from \fBfilter_multipart\fR, drops all contained parts also. .TP .B action_accept_with_warning($msg) Accept the part, but add the warning \fI$msg\fR to the e-mail message. .TP .B action_replace_with_warning($msg) Drop the part and replace it with a text part \fI$msg\fR. If called from \fBfilter_multipart\fR, drops all contained parts also. .TP .B action_replace_with_url($entity, $doc_root, $base_url, $msg, [$cd_data, $salt]) Drop the part, but save it in a unique location under $doc_root. The part is replaced with the text message $msg. The string "_URL_" in $msg is replaced with $base_url/something, that can be used to retrieve the message. You should not use this function in \fBfilter_multipart\fR. This action is intended for stripping large parts out of the message and replacing them to a link on a Web server. Here's how you would use it in filter(): .nf $size = (stat($entity->bodyhandle->path))[7]; if ($size > 1000000) { return action_replace_with_url($entity, "/home/httpd/html/mail_parts", "http://mailserver.company.com/mail_parts", "The attachment was larger than 1,000,000 bytes.\\n" . "It was removed, but may be accessed at this URL:\\n\\n" . "\\t_URL_\\n"); } .fi This example moves attachments greater than 1,000,000 bytes into /home/httpd/html/mail_parts and replaces them with a link. The directory should be accessible via a Web server at http://mailserver.company.com/mail_parts. The generated name is created by performing a SHA1 hash of the part and adding the extension to the ASCII-HEX representation of the hash. If many different e-mails are sent containing an identical large part, only one copy of the part is stored, regardless of the number of senders or recipients. For privacy reasons, you \fBmust\fR turn off Web server indexing in the directory in which you place mail parts, or anyone will be able to read them. If indexing is disabled, an attacker would have to guess the SHA1 hash of a part in order to read it. Optionally, a fifth argument can supply data to be saved into a hidden dot filename based on the generated name. This data can then be read in on the fly by a CGI script or mod_perl module before serving the file to a web client, and used to add information to the response, such as Content-Disposition data. A sixth optional argument, $salt, is mixed in to the SHA1 hash. This salt can be any string and should be kept confidential. The salt is designed to prevent people from guessing whether or not a particular attachment has been received on your server by altering the SHA1 hash calculation. .TP .B action_defang($entity, $name, $fname, $type) Accept the part, but change its name to \fI$name\fR, its suggested filename to \fI$fname\fR and its MIME type to \fI$type\fR. If \fI$name\fR or \fI$fname\fR are "", then \fBmimedefang.pl\fR generates generic names. Do not use this action in \fBfilter_multipart\fR. If you use \fBaction_defang\fR, you must define a subroutine called \fBdefang_warning\fR in your filter. This routine takes two arguments: $oldfname (the original name of an attachment) and $fname (the defanged version.) It should return a message telling the user what happened. For example: .nf sub defang_warning { my($oldfname, $fname) = @_; return "The attachment '$oldfname' was renamed to '$fname'\\n"; } .fi .TP .B action_external_filter($entity, $cmd) Run an external UNIX command \fB$cmd\fR. This command must read the part from the file \fB./FILTERINPUT\fR and leave the result in \fB./FILTEROUTPUT\fR. If the command executes successfully, returns 1, otherwise 0. You can test the return value and call another \fBaction_\fR if the filter failed. Do not use this action in \fBfilter_multipart\fR. .TP .B action_quarantine($entity, $msg) Drop and quarantine the part, but add the warning \fI$msg\fR to the e-mail message. .TP .B action_quarantine_entire_message($msg) Quarantines the entire message in a quarantine directory on the mail server, but does not otherwise affect disposition of the message. If "$msg" is non-empty, it is included in any administrator notification. .TP .B action_sm_quarantine($reason) Quarantines a message \fIin the Sendmail mail queue\fR using the new QUARANTINE facility of Sendmail 8.13. Consult the Sendmail documentation for details about this facility. If you use \fBaction_sm_quarantine\fR with a version of Sendmail that lacks the QUARANTINE facility, \fBmimedefang\fR will log an error message and not quarantine the message. .TP .B action_bounce($reply, $code, $dsn) Reject the entire e-mail message with an SMTP failure code, and the one-line error message \fI$reply\fR. If the optional $code and $dsn arguments are supplied, they specify the numerical SMTP reply code and the extended status code (DSN code). If the codes you supply do not make sense for a bounce, they are replaced with "554" and "5.7.1" respectively. \fBaction_bounce\fR merely makes a note that the message is to be bounced; remaining parts are still processed. If \fBaction_bounce\fR is called for more than one part, the mail is bounced with the message in the final call to \fBaction_bounce\fR. You can profitably call \fBaction_quarantine\fR followed by \fBaction_bounce\fR if you want to keep a copy of the offending part. Note that the message is not bounced immediately; rather, remaining parts are processed and the message is bounced after all parts have been processed. Note that despite its name, \fBaction_bounce\fR does \fInot\fR generate a "bounce message". It merely rejects the message with an SMTP failure code. .B WARNING: \fBaction_bounce()\fR may cause the sending relay to generate spurious bounce messages if the sender address is faked. This is a particular problem with viruses. However, we believe that on balance, it's better to bounce a virus than to silently discard it. It's almost never a good idea to hide a problem. .TP .B action_tempfail($msg, $code, $dsn) Cause an SMTP "temporary failure" code to be returned, so the sending mail relay requeues the message and tries again later. The message $msg is included with the temporary failure code. If the optional $code and $dsn arguments are supplied, they specify the numerical SMTP reply code and the extended status code (DSN code). If the codes you supply do not make sense for a temporary failure, they are replaced with "450" and "4.7.1" respectively. .TP .B action_discard() Silently discard the message, notifying nobody. You can profitably call \fBaction_quarantine\fR followed by \fBaction_discard\fR if you want to keep a copy of the offending part. Note that the message is not discarded immediately; rather, remaining parts are processed and the message is discarded after all parts have been processed. .TP .B action_notify_sender($message) This action sends an e-mail back to the original sender with the indicated message. You may call another action after this one. If \fBaction_notify_sender\fR is called more than once, the messages are accumulated into a single e-mail message -- at most one notification message is sent per incoming message. The message should be terminated with a newline. The notification is delivered in deferred mode; you should run a client-queue runner if you are using Sendmail 8.12. \fINOTE\fR: Viruses often fake the sender address. For that reason, if a virus-scanner has detected a virus, \fBaction_notify_sender\fR is \fIdisabled\fR and will simply log an error message if you try to use it. .TP .B action_notify_administrator($message) This action e-mails the MIMEDefang administrator the supplied message. You may call another action after this one; \fBaction_notify_administrator\fR does not affect mail processing. If \fBaction_notify_administrator\fR is called more than once, the messages are accumulated into a single e-mail message -- at most one notification message is sent per incoming message. The message should be terminated with a newline. The notification is delivered in deferred mode; you should run a client-queue runner if you are using Sendmail 8.12. .TP .B append_text_boilerplate($entity, $boilerplate, $all) This action should \fIonly\fR be called from \fBfilter_end\fR. It appends the text "\\n$boilerplate\\n" to the first text/plain part (if $all is 0) or to \fIall\fR text/plain parts (if $all is 1). .TP .B append_html_boilerplate($entity, $boilerplate, $all) This action should \fIonly\fR be called from \fBfilter_end\fR. It adds the text "\\n$boilerplate\\n" to the first text/html part (if $all is 0) or to \fIall\fR text/html parts (if $all is 1). This function tries to be smart about inserting the boilerplate; it uses HTML::Parser to detect closing tags and inserts the boilerplate before the tag if there is one, or before the tag if there is no . If there is no or tag, it appends the boilerplate to the end of the part. Do not use append_html_boilerplate unless you have installed the HTML::Parser Perl module. Here is an example illustrating how to use the boilerplate functions: .nf sub filter_end { my($entity) = @_; append_text_boilerplate($entity, "Lame text disclaimer", 0); append_html_boilerplate($entity, "Lame HTML disclaimer", 0); } .fi .TP .B action_add_part($entity, $type, $encoding, $data, $fname, $disposition [, $offset]) This action should \fIonly\fR be called from the \fBfilter_end\fR routine. It adds a new part to the message, converting the original message to mutipart if necessary. The function returns the part so that additional mime attributes may be set on it. Here's an example: .nf sub filter_end { my($entity) = @_; action_add_part($entity, "text/plain", "-suggest", "This e-mail does not represent" . "the official policy of FuBar, Inc.\\n", "disclaimer.txt", "inline"); } .fi The $entity parameter \fImust\fR be the argument passed in to \fBfilter_end\fR. The $offset parameter is optional; if omitted, it defaults to -1, which adds the new part at the end. See the MIME::Entity man page and the \fBadd_part\fR member function for the meaning of $offset. Note that \fBaction_add_part\fR tries to be more intelligent than simply calling $entity->add_part. The decision process is as follows: .TP .B o If the top-level entity is multipart/mixed, then the part is simply added. .TP .B o Otherwise, a new top-level multipart/mixed container is generated, and the original top-level entity is made the first part of the multipart/mixed container. The new part is then added to the multipart/mixed container. .TP .B action_add_entity($entity [, $offset]) This is similar to \fBaction_add_part\fR but takes a pre-built MIME::Entity object rather than constructing one based on $type, $encoding, $data, $fname and $disposition arguments. .SH USEFUL ROUTINES \fBmimedefang.pl\fR includes some useful functions you can call from your filter: .TP .B detect_and_load_perl_modules() Unless you \fIreally\fR know what you're doing, this function \fBmust\fR be called first thing in your filter file. It causes \fBmimedefang.pl\fR to detect and load Perl modules such as Mail::SpamAssassin, Net::DNS, etc., and to populate the %Features hash. .TP .B send_quarantine_notifications() This function should be called from \fBfilter_end\fR. If any parts were quarantined, a quarantine notification is sent to the MIMEDefang administrator. Please note that if you do not call \fBsend_quarantine_notifications\fR, then \fIno\fR quarantine notifications are sent. .TP .B get_quarantine_dir() This function returns the full path name of the quarantine directory. If you have not yet quarantined any parts of the message, a quarantine directory is created and its pathname returned. .TP .B change_sender($sender) This function changes the envelope sender to $sender. It can only be called from \fBfilter_begin\fR or any later function. Please note that this function is \fIonly\fR supported with Sendmail/Milter 8.14.0 or newer. It has \fIno effect\fR if you're running older versions. .TP .B add_recipient($recip) This function adds $recip to the list of envelope recipients. A copy of the message (after any modifications by MIMEDefang) will be sent to $recip in addition to the original recipients. Note that \fBadd_recipient\fR does \fInot\fR modify the @Recipients array; it just makes a note to Sendmail to add the recipient. .TP .B delete_recipient($recip) This function deletes $recip from the list of recipients. That person will not receive a copy of the mail. $recip should exactly match an entry in the @Recipients array for delete_recipient() to work. Note that \fBdelete_recipient\fR does \fInot\fR modify the @Recipients array; it just makes a note to Sendmail to delete the recipient. .TP .B resend_message($recip1, $recip2, ...) or .TP .B resend_message(@recips) This function \fIimmediately\fR resends the \fIoriginal, unmodified\fR mail message to each of the named recipients. The sender's address is preserved. Be very careful when using this function, because it resends the \fIoriginal\fR message, which may contain undesired attachments. Also, you should \fInot\fR call this function from filter(), because it resends the message \fIeach time\fR it is called. This may result in multiple copies being sent if you are not careful. Call from filter_begin() or filter_end() to be safe. The function returns true on success, or false if it fails. Note that the resend_message function delivers the mail in deferred mode (using Sendmail's "-odd" flag.) You \fImust\fR run a client-submission queue processor if you use Sendmail 8.12. We recommend executing this command as part of the Sendmail startup sequence: .nf sendmail -Ac -q5m .fi .TP .B remove_redundant_html_parts($entity) This function should only be called from \fBfilter_end\fR. It removes redundant HTML parts from the message. It works by deleting any part of type text/html from the message if (1) it is a sub-part of a multipart/alternative part, and (2) there is another part of type text/plain under the multipart/alternative part. .TP .B replace_entire_message($entity) This function can only be called from \fBfilter_end\fR. It replaces the entire message with $entity, a MIME::Entity object that you have constructed. You can use any of the MIME::Tools functions to construct the entity. .TP .B read_commands_file() This function should only be called from \fBfilter_sender\fR and \fBfilter_recipient\fR. This will read the \fBCOMMANDS\fR file (as described in mimedefang-protocol(7)), and will fill or update the following global variables: $Sender, @Recipients, %RecipientMailers, $RelayAddr, $RealRelayAddr, $RelayHostname, $RealRelayHostname, $QueueID, $Helo, %SendmailMacros. If you do not call \fBread_commands_file\fR, then the only information available in \fBfilter_sender\fR and \fBfilter_recipient\fR is that which is passed as an argument to the function. .TP .B stream_by_domain() \fIDo not use this function unless you have Sendmail 8.12 and locally- submitted e-mail is submitted using SMTP.\fR This function should \fIonly\fR be called at the very beginning of filter_begin(), like this: .nf sub filter_begin { if (stream_by_domain()) { return; } # Rest of filter_begin } .fi stream_by_domain() looks at all the recipients of the message, and if they belong to the same domain (e.g., joe@domain.com, jane@domain.com and sue@domain.com), it returns 0 and sets the global variable $Domain to the domain (domain.com in this example.) If users are in different domains, stream_by_domain() \fIresends\fR the message (once to each domain) and returns 1 For example, if the original recipients are joe@abc.net, jane@xyz.net and sue@abc.net, the original message is resent twice: One copy to joe@abc.net and sue@abc.net, and another copy to jane@xyz.net. Also, any subsequent scanning is canceled (filter() and filter_end() will \fInot\fR be called for the original message) and the message is silently discarded. If you have Sendmail 8.12, then locally-submitted messages are sent via SMTP, and MIMEDefang will be called for each resent message. It is possible to set up Sendmail 8.12 so locally-submitted messages are delivered directly; in this case, stream_by_domain will \fInot\fR work. Using stream_by_domain allows you to customize your filter rules for each domain. If you use the function as described above, you can do this in your filter routine: .nf sub filter { my($entity, $fname, $ext, $type) = @_; if ($Domain eq "abc.com") { # Filter actions for abc.com } elsif ($Domain eq "xyz.com") { # Filter actions for xyz.com } else { # Default filter actions } } .fi You cannot rely on $Domain being set unless you have called stream_by_domain(). .TP .B stream_by_recipient() \fIDo not use this function unless you have Sendmail 8.12 and locally- submitted e-mail is submitted using SMTP.\fR This function should \fIonly\fR be called at the very beginning of filter_begin(), like this: .nf sub filter_begin { if (stream_by_recipient()) { return; } # Rest of filter_begin } .fi If there is more than one recipient, stream_by_recipient() resends the message once to each recipient. That way, you can customize your filter rules on a per-recipient basis. This may increase the load on your mail server considerably. Also, a "recipient" is determined before alias expansion. So "all@mydomain.com" is considered a single recipient, even if Sendmail delivers to a list. If you have Sendmail 8.12, then locally-submitted messages are sent via SMTP, and MIMEDefang will be called for each resent message. It is possible to set up Sendmail 8.12 so locally-submitted messages are delivered directly; in this case, stream_by_recipient() will \fInot\fR work. stream_by_recipient() allows you to customize your filter rules for each recipient in a manner similar to stream_by_domain(). .SH LOGGING .TP .B md_graphdefang_log_enable($facility, $enum_recips) Enables the md_graphdefang_log function (described next). The function logs to syslog using the specified facility. If you omit $facility, it defaults to 'mail'. If you do not call md_graphdefang_log_enable in your filter, then any calls to md_graphdefang_log simply do nothing. If you supply $enum_recips as 1, then a line of logging is output for \fIeach\fR recipient of a mail message. If it is zero, then only a single line is output for each message. If you omit $enum_recips, it defaults to 1. .TP .B md_graphdefang_log($event, $v1, $v2) Logs an event with up to two optional additional parameters. The log message has a specific format useful for graphing tools; the message looks like this: .nf MDLOG,msgid,event,v1,v2,sender,recipient,subj .fi "MDLOG" is literal text. "msgid" is the Sendmail queue identifier. "event" is the event name, and "v1" and "v2" are the additional parameters. "sender" is the sender's e-mail address. "recipient" is the recipient's e-mail address, and "subj" is the message subject. If a message has more than one recipient, md_graphdefang_log may log an event message for \fIeach\fR recipient, depending on how you called md_graphdefang_log_enable. Note that md_graphdefang_log should not be used in filter_relay, filter_sender or filter_recipient. The global variables it relies on are not valid in that context. If you want to log general text strings, \fIdo not\fR use md_graphdefang_log. Instead, use md_syslog (described next). .TP .B md_syslog($level, $msg) Logs the message $msg to syslog, using level $level. The level is a literal string, and should be one of 'err', 'debug', 'warning', \'emerg', 'crit', 'notice' or 'info'. (See syslog(3) for details.) Note that md_syslog does \fInot\fR perform %-subsitutions like syslog(3) does. Depending on your Perl installation, md_syslog boils down to a call to Unix::Syslog::syslog or Sys::Syslog::syslog. See the Unix::Syslog or Sys::Syslog man pages for more details. .TP .B md_openlog($tag, $facility) Sets the tag used in syslog messages to $tag, and sends the logs to the $facility facility. If you do not call md_openlog before you call md_syslog, then it is called implicitly with $tag set to \fBmimedefang.pl\fR and $facility set to \fBmail\fR. .SH RBL LOOKUP FUNCTIONS \fBmimedefang.pl\fR includes the following functions for looking up IP addresses in DNS-based real-time blacklists. Note that the "relay_is_blacklisted" functions are deprecated and may be removed in a future release. Instead, you should use the module Net::DNSBL::Client from CPAN. .TP .B relay_is_blacklisted($relay, $domain) This checks a DNS-based real-time spam blacklist, and returns true if the relay host is blacklisted, or false otherwise. (In fact, the return value is whatever the blacklist returns as a resolved hostname, such as "127.0.0.4") Note that \fBrelay_is_blacklisted\fR uses the built-in \fBgethostbyname\fR function; this is usually quite inefficient and does not permit you to set a timeout on the lookup. Instead, we recommend using one of the other DNS lookup function described in this section. (Note, though, that the other functions require the Perl Net::DNS module, whereas \fBrelay_is_blacklisted\fR does not.) Here's an example of how to use \fBrelay_is_blacklisted\fR: .nf if (relay_is_blacklisted($RelayAddr, "rbl.spamhaus.org")) { action_add_header("X-Blacklist-Warning", "Relay $RelayAddr is blacklisted by Spamhaus"); } .fi .TP .B relay_is_blacklisted_multi($relay, $timeout, $answers_wanted, [$domain1, $domain2, ...], $res) This function is similar to \fBrelay_is_blacklisted\fR, except that it takes a timeout argument (specified in seconds) and an array of domains to check. The function checks all domains in parallel, and is guaranteed to return in \fB$timeout\fR seconds. (Actually, it may take up to one second longer.) The parameters are: $relay -- the IP address you want to look up $timeout -- a timeout in seconds after which the function should return $answers_wanted -- the maximum number of positive answers you care about. For example, if you're looking up an address in 10 different RBLs, but are going to bounce it if it is on four or more, you can set $answers_wanted to 4, and the function returns as soon as four "hits" are discovered. If you set $answers_wanted to zero, then the function does not return early. [$domain1, $domain2, ...] -- a reference to an array of strings, where each string is an RBL domain. $res -- a Net::DNS::Resolver object. This argument is optional; if you do not supply it, then \fBrelay_is_blacklisted_multi\fR constructs its own resolver. The return value is a reference to a hash; the keys of the hash are the original domains, and the corresponding values are either SERVFAIL, NXDOMAIN, or a list of IP addresses in dotted-quad notation. Here's an example: .nf $ans = relay_is_blacklisted_multi($RelayAddr, 8, 0, ["sbl.spamhaus.org", "relays.ordb.org"]); foreach $domain (keys(%$ans)) { $r = $ans->{$domain}; if (ref($r) eq "ARRAY") { # It's an array -- it IS listed in RBL print STDERR "Lookup in $domain yields [ "; foreach $addr (@$r) { print STDERR $addr . " "; } print STDERR "]\\n"; } else { # It is NOT listed in RBL print STDERR "Lookup in $domain yields " . $ans->{$domain} . "\\n"; } } .fi You should compare each of $ans->{$domain} to "SERVFAIL" and "NXDOMAIN" to see if the relay is \fInot\fR listed. Any other return value will be an array of IP addresses indicating that the relay is listed. Any lookup that does not succeed within $timeout seconds has the corresponding return value set to SERVFAIL. .TP .B relay_is_blacklisted_multi_list($relay, $timeout, $answers_wanted, [$domain1, $domain2, ...], $res) This function is similar to \fBrelay_is_blacklisted_multi\fR except that the return value is simply an array of RBL domains in which the relay was listed. .TP .B relay_is_blacklisted_multi_count($relay, $timeout, $answers_wanted, [$domain1, $domain2, ...], $res) This function is similar to \fBrelay_is_blacklisted_multi\fR except that the return value is an integer specifying the number of domains on which the relay was blacklisted. .TP .B md_get_bogus_mx_hosts($domain) This function looks up all the MX records for the specified domain (or A records if there are no MX records) and returns a list of "bogus" IP addresses found amongst the records. A "bogus" IP address is an IP address in a private network (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), the loopback network (127.0.0.0/8), local-link for auto-DHCP (169.254.0.0/16), IPv4 multicast (224.0.0.0/4) or reserved (240.0.0.0/4). .PP Here's how you might use the function in filter_sender: .nf sub filter_sender { my ($sender, $ip, $hostname, $helo) = @_; if ($sender =~ /\@([^>]+)/) { my $domain = $1; my @bogushosts = md_get_bogus_mx_hosts($domain); if (scalar(@bogushosts)) { return('REJECT', "Domain $domain contains bogus MX record(s) " . join(', ', @bogushosts)); } } return ('CONTINUE', 'ok'); } .fi .SH TEST FUNCTIONS \fBmimedefang.pl\fR includes some "test" functions: .TP .B md_version() returns the version of MIMEDefang as a string (for example, "@VERSION@"). .TP .B message_rejected() Returns true if any of \fBaction_tempfail\fR, \fBaction_bounce\fR or \fBaction_discard\fR have been called for this message; returns false otherwise. .PP If you have the Mail::SpamAssassin Perl module installed (see https://spamassassin.apache.org) you may call any of the spam_assassin_* functions. They should only be called from \fBfilter_begin\fR or \fBfilter_end\fR because they operate on the entire message at once. Most functions use an optionally provided config file. If no config file is provided, mimedefang.pl will look for one of four default SpamAssassin preference files. The first of the following found will be used: .TP .B o @CONFDIR_EVAL@/sa-mimedefang.cf .TP .B o @CONFDIR_EVAL@/spamassassin/sa-mimedefang.cf .TP .B o @CONFDIR_EVAL@/spamassassin/local.cf .TP .B o @CONFDIR_EVAL@/spamassassin.cf .PP \fBImportant Note\fR: MIMEDefang does \fInot\fR permit SpamAssassin to modify messages. If you want to tag spam messages with special headers or alter the subject line, you must use MIMEDefang functions to do it. Setting SpamAssassin configuration options to alter messages will not work. .TP .B spam_assassin_is_spam([ $config_file ]) Determine if the current message is SPAM/UCE as determined by SpamAssassin. Compares the score of the message against the threshold score (see below) and returns true if it is. Uses \fBspam_assassin_check\fR below. .TP .B spam_assassin_check([ $config_file ]) This function returns a four-element list of the form ($hits, $required, $tests, $report). $hits is the "score" given to the message by SpamAssassin (higher score means more likely SPAM). $required is the number of hits required before SpamAssassin concludes that the message is SPAM. $tests is a comma-separated list of SpamAssassin test names, and $report is text detailing which tests triggered and their point score. This gives you insight into why SpamAssassin concluded that the message is SPAM. Uses \fBspam_assassin_status\fR below. .TP .B spam_assassin_status([ $config_file ]) This function returns a Mail::SpamAssasin::PerMsgStatus object. Read the SpamAssassin documentation for details about this object. You are responsible for calling the \fBfinish\fR method when you are done with it. Uses \fBspam_assassin_init\fR and \fBspam_assassin_mail\fR below. .TP .B spam_assassin_init([ $config_file ]) This function returns the new global Mail::SpamAssassin object with the specified or default config (outlined above). If the global object is already defined, returns it -- does not change config files! The object can be used to perform other SpamAssassin related functions. .TP .B spam_assassin_mail() This function returns a Mail::SpamAssassin::NoMailAudit object with the current email message contained in it. It may be used to perform other SpamAssassin related functions. .TP .B rspamd_check([ $uri ]) *experimental* This function returns a six-element list of the form ($hits, $required, $tests, $report, $action, $is_spam). $hits is the "score" given to the message by Rspamd (higher score means more likely SPAM). $required is the number of hits required before Rspamd concludes that the message is SPAM. $tests is a list of Rspamd test names, and $report is text detailing which tests triggered and their point score. If JSON and LWP modules are present $report will be a json string; $action is the action that rspamd(8) wants to apply and $is_spam is a boolean value (true/false) that determines if the message is spam or not. This gives you insight into why Rspamd concluded that the message is SPAM. .TP .B md_copy_orig_msg_to_work_dir() Normally, virus-scanners are passed only the unpacked, decoded parts of a MIME message. If you want to pass the original, undecoded message in as well, call \fBmd_copy_orig_msg_to_work_dir\fR \fIprior to\fR calling \fBmessage_contains_virus\fR. .TP .B md_copy_orig_msg_to_work_dir_as_mbox_file() Normally, virus-scanners are passed only the unpacked, decoded parts of a MIME message. If you want to pass the original, undecoded message in as a UNIX-style "mbox" file, call \fBmd_copy_orig_msg_to_work_dir_as_mbox_file\fR \fIprior to\fR calling \fBmessage_contains_virus\fR. The only difference between this function and \fBmd_copy_orig_msg_to_work_dir\fR is that this function prepends a "From_" line to make the message look like a UNIX-style mbox file. This is required for some virus scanners (such as Clam AntiVirus) to recognize the file as an e-mail message. .TP .B message_contains_virus() This function runs \fIevery\fR installed virus-scanner and returns the scanner results. The function should be called in list context; the return value is a three-element list ($code, $category, $action). $code is the actual return code from the virus scanner. $category is a string categorizing the return code: "ok" - no viruses detected. "not-installed" - indicated virus scanner is not installed. "cannot-execute" - for some reason, the scanner could not be executed. "virus" - a virus was found. "suspicious" - a "suspicious" file was found. "interrupted" - scanning was interrupted. "swerr" - an internal scanner software error occurred. $action is a string containing the recommended action: "ok" - allow the message through unmolested. "quarantine" - a virus was detected; quarantine it. "tempfail" - something went wrong; tempfail the message. .TP .B message_contains_virus_trend() .TP .B message_contains_virus_nai() .TP .B message_contains_virus_bdc() .TP .B message_contains_virus_nvcc() .TP .B message_contains_virus_csav() .TP .B message_contains_virus_fsav() .TP .B message_contains_virus_hbedv() .TP .B message_contains_virus_vexira() .TP .B message_contains_virus_sophos() .TP .B message_contains_virus_clamav() .TP .B message_contains_virus_clamdscan() .TP .B message_contains_virus_avp() .TP .B message_contains_virus_avp5() .TP .B message_contains_virus_fprot() .TP .B message_contains_virus_fpscan() .TP .B message_contains_virus_fprotd() .TP .B message_contains_virus_fprotd_v6() .TP .B message_contains_virus_nod32() These functions should be called in \fBlist context\fR. They use the indicated anti-virus software to scan the message for viruses. These functions are intended for use in filter_begin() to make an initial scan of the e-mail message. The supported virus scanners are: .TP .B nai NAI "uvscan" - http://www.nai.com/ .TP .b bdc Bitdefender "bdc" - http://www.bitdefender.com/ .TP .B csav Command Anti-Virus - http://www.commandsoftware.com/ .TP .B fsav F-Secure Anti-Virus - http://www.f-secure.com/ .TP .B hbedv H+BEDV "AntiVir" - http://www.hbedv.com/ .TP .B vexira Vexira "Vexira" - http://www.centralcommand.com/ .TP .B sophos Sophos AntiVirus - http://www.sophos.com/ .TP .B avp Kaspersky AVP and aveclient (AVP5) - http://www.avp.ru/ .TP .B clamav Clam AntiVirus - https://www.clamav.net/ .TP .B f-prot F-RISK F-PROT - http://www.f-prot.com/ .TP .B nod32cli ESET NOD32 - http://www.eset.com/ .TP .B message_contains_virus_carrier_scan([$host]) Connects to the specified host:port:local_or_nonlocal (default \fB$CSSHost\fR), where the Symantec CarrierScan Server daemon is expected to be listening. Return values are the same as the other message_contains_virus functions. .TP .B message_contains_virus_sophie([$sophie_sock]) Connects to the specified socket (default \fB$SophieSock\fR), where the Sophie daemon is expected to be listening. Return values are the same as the other message_contains_virus functions. .TP .B message_contains_virus_clamd([$clamd_sock]) Connects to the specified socket (default \fB$ClamdSock\fR), where the clamd daemon is expected to be listening. Return values are the same as the other message_contains_virus functions. .TP .B message_contains_virus_trophie([$trophie_sock]) Connects to the specified socket (default \fB$TrophieSock\fR), where the Trophie daemon is expected to be listening. Return values are the same as the other message_contains_virus functions. .TP .B entity_contains_virus($entity) This function runs the specified MIME::Entity through \fIevery\fR installed virus-scanner and returns the scanner results. The return values are the same as for \fBmessage_contains_virus()\fR. .TP .B entity_contains_virus_trend($entity) .TP .B entity_contains_virus_nai($entity) .TP .B entity_contains_virus_bdc($entity) .TP .B entity_contains_virus_nvcc($entity) .TP .B entity_contains_virus_csav($entity) .TP .B entity_contains_virus_fsav($entity) .TP .B entity_contains_virus_hbedv($entity) .TP .B entity_contains_virus_sophos($entity) .TP .B entity_contains_virus_clamav($entity) .TP .B entity_contains_virus_clamdscan($entity) .TP .B entity_contains_virus_avp($entity) .TP .B entity_contains_virus_avp5($entity) .TP .B entity_contains_virus_fprot($entity) .TP .B entity_contains_virus_fpscan($entity) .TP .B entity_contains_virus_fprotd($entity) .TP .B entity_contains_virus_fprotd_v6($entity) .TP .B entity_contains_virus_nod32($entity) These functions, meant to be called from filter(), are similar to the message_contains_virus functions except they scan only the current part. They should be called from list context, and their return values are as described for the message_contains_virus functions. .TP .B entity_contains_virus_carrier_scan($entity[, $host]) Connects to the specified host:port:local_or_nonlocal (default \fB$CSSHost\fR), where the Symantec CarrierScan Server daemon is expected to be listening. Return values are the same as the other entity_contains_virus functions. .TP .B entity_contains_virus_sophie($entity[, $sophie_sock]) Connects to the specified socket (default \fB$SophieSock\fR), where the Sophie daemon is expected to be listening. Return values are the same as the other entity_contains_virus functions. .TP .B entity_contains_virus_trophie($entity[, $trophie_sock]) Connects to the specified socket (default \fB$TrophieSock\fR), where the Trophie daemon is expected to be listening. Return values are the same as the other entity_contains_virus functions. .TP .B entity_contains_virus_clamd($entity[, $clamd_sock]) Connects to the specified socket (default \fB$ClamdSock\fR), where the clamd daemon is expected to be listening. Return values are the same as the other entity_contains_virus functions. .SH SMTP FLOW This section illustrates the flow of messages through MIMEDefang. .TP .B 1. INITIAL CONNECTION If you invoked \fBmimedefang\fR with the \fB\-r\fR option and have defined a filter_relay routine, it is called. .TP .B 2. SMTP HELO COMMAND The HELO string is stored internally, but no filter functions are called. .TP .B 3. SMTP MAIL FROM: COMMAND If you invoked \fBmimedefang\fR with the \fB\-s\fR option and have defined a filter_sender routine, it is called. .TP .B 4. SMTP RCPT TO: COMMAND If you invoked \fBmimedefang\fR with the \fB\-t\fR option and have defined a filter_recipient routine, it is called. .TP .B 5. END OF SMTP DATA filter_begin is called. For each MIME part, filter is called. Then filter_end is called. .SH PRESERVING RELAY INFORMATION .PP Most organizations have more than one machine handling internet e-mail. If the primary machine is down, mail is routed to a secondary (or tertiary, etc.) MX server, which stores the mail until the primary MX host comes back up. Mail is then relayed to the primary MX host. .PP Relaying from a secondary to a primary MX host has the unfortunate side effect of losing the original relay's IP address information. MIMEDefang allows you to preserve this information. One way around the problem is to run MIMEDefang on all the secondary MX hosts and use the same filter. However, you may not have control over the secondary MX hosts. If you can persuade the owners of the secondary MX hosts to run MIMEDefang with a simple filter that only preserves relay information and does no other scanning, your primary MX host can obtain relay information and make decisions using $RelayAddr and $RelayHostname. .PP When you configure MIMEDefang, supply the "--with-ipheader" argument to the ./configure script. When you install MIMEDefang, a file called \fB@CONFDIR_EVAL@/mimedefang-ip-key\fR will be created which contains a randomly-generated header name. Copy this file to all of your mail relays. It is important that all of your MX hosts have the \fBsame\fR key. The key should be kept confidential, but it's not disastrous if it leaks out. .PP On your secondary MX hosts, add this line to filter_end: .nf add_ip_validation_header(); .fi .PP \fINote\fR: You should \fIonly\fR add the validation header to mail destined for one of your other MX hosts! Otherwise, the validation header will leak out. .PP When the secondary MX hosts relay to the primary MX host, $RelayAddr and $RelayHostname will be set based on the IP validation header. If MIMEDefang notices this header, it sets the global variable $WasResent to 1. Since you don't want to trust the header unless it was set by one of your secondary MX hosts, you should put this code in filter_begin: .nf if ($WasResent) { if ($RealRelayAddr ne "ip.of.secondary.mx" and $RealRelayAddr ne "ip.of.tertiary.mx") { $RelayAddr = $RealRelayAddr; $RelayHostname = $RealRelayHostname; } } .fi This resets the relay address and hostname to the actual relay address and hostname, unless the message is coming from one of your other MX hosts. .PP On the primary MX host, you should add this in filter_begin: .nf delete_ip_validation_header(); .fi .PP This prevents the validation header from leaking out to recipients. .PP \fINote\fR: The IP validation header works only in message-oriented functions. It (obviously) has no effect on \fBfilter_relay\fR, \fBfilter_sender\fR and \fBfilter_recipient\fR, because no header information is available yet. You must take this into account when writing your filter; you must defer relay-based decisions to the message filter for mail arriving from your other MX hosts. .SH GLOBAL VARIABLE LIFETIME The following list describes the lifetime of global variables (thanks to Tony Nugent for providing this documentation.) If you set a global variable: .TP .B Outside a subroutine in your filter file It is available to all functions, all the time. .TP .B In filter_relay, filter_sender or filter_recipient Not guaranteed to be available to any other function, not even from one filter_recipient call to the next, when receiving a multi-recipient email message. .TP .B In filter_begin Available to filter_begin, filter and filter_end .TP .B In filter Available to filter and filter_end .TP .B In filter_end Available within filter_end .PP The "built-in" globals like $Subject, $Sender, etc. are always available to filter_begin, filter and filter_end. Some are available to filter_relay, filter_sender or filter_recipient, but you should check the documentation of the variable above for details. .SH MAINTAINING STATE .PP There are four basic groups of filtering functions: .TP .B 1 filter_relay .TP .B 2 filter_sender .TP .B 3 filter_recipient .TP .B 4 filter_begin, filter, filter_multipart, filter_end .PP In general, for a given mail message, these groups of functions may be called in completely different Perl processes. Thus, there is \fIno way\fR to maintain state inside Perl between groups of functions. That is, you cannot set a variable in \fBfilter_relay\fR and expect it to be available in \fBfilter_sender\fR, because the \fBfilter_sender\fR invocation might take place in a completely different process. .PP For a given mail message, it \fIis\fR always the case that \fBfilter_begin\fR, \fBfilter\fR, \fBfilter_multipart\fR and \fBfilter_end\fR are called in the same Perl process. Therefore, you can use global variables to carry state among those functions. You should be very careful to initialize such variables in \fBfilter_begin\fR to ensure no data leaks from one message to another. .PP Also for a given mail message, the $CWD global variable holds the message spool directory, and the current working directory is set to $CWD. Therefore, you can store state in files inside $CWD. If \fBfilter_sender\fR stores data in a file inside $CWD, then \fBfilter_recipient\fR can retrieve that data. .PP Since \fBfilter_relay\fR is called directly after a mail connection is established, there is no message context yet, no per-message mimedefang spool directory, and the $CWD global is not set. Therefore, it is not possible to share information from \fBfilter_relay\fR to one of the other filter functions. The only thing that \fBfilter_relay\fR has in common with the other functions are the values in the globals $RelayAddr, and $RelayHostname. These could be used to access per-remote-host information in some database. .PP Inside $CWD, we reserve filenames beginning with upper-case letters for internal MIMEDefang use. If you want to create files to store state, name them beginning with a lower-case letter to avoid clashes with future releases of MIMEDefang. .SH SOCKET MAPS If you have Sendmail 8.13 or later, and have compiled it with the SOCKETMAP option, then you can use a special map type that communicates over a socket with another program (rather than looking up a key in a Berkeley database, for example.) .PP \fBmimedefang-multiplexor\fR implements the Sendmail SOCKETMAP protocol if you supply the \fB\-N\fR option. In that case, you can define a function called \fBfilter_map\fR to implement map lookups. \fBfilter_map\fR takes two arguments: $mapname is the name of the Sendmail map (as given in the K sendmail configuration directive), and $key is the key to be looked up. .PP \fBfilter_map\fR must return a two-element list: ($code, $val) $code can be one of: .TP .B "OK" The lookup was successful. In this case, $val must be the result of the lookup .TP .B "NOTFOUND" The lookup was unsuccessful -- the key was not found. In this case, $val should be the empty string. .TP .B "TEMP" There was a temporary failure of some kind. $val can be an explanatory error message. .TP .B "TIMEOUT" There was a timeout of some kind. $val can be an explanatory error message. .TP .B "PERM" There was a permanent failure. This is \fInot\fR the same as an unsuccessful lookup; it should be used only to indicate a serious misconfiguration. As before, $val can be an explanatory error message. .PP Consider this small example. Here is a minimal Sendmail configuration file: .nf V10/Berkeley Kmysock socket unix:/var/spool/MIMEDefang/map.sock kothersock socket unix:/var/spool/MIMEDefang/map.sock .fi .PP If \fBmimedefang-multiplexor\fR is invoked with the arguments \fB\-N unix:/var/spool/MIMEDefang/map.sock\fR, and the filter defines \fBfilter_map\fR as follows: .nf sub filter_map ($$) { my($mapname, $key) = @_; my $ans; if($mapname ne "mysock") { return("PERM", "Unknown map $mapname"); } $ans = reverse($key); return ("OK", $ans); } .fi Then in Sendmail's testing mode, we see the following: .nf > /map mysock testing123 map_lookup: mysock (testing123) returns 321gnitset (0) > /map othersock foo map_lookup: othersock (foo) no match (69) .fi .PP (The return code of 69 means EX_UNAVAILABLE or Service Unavailable) .PP A real-world example could do map lookups in an LDAP directory or SQL database, or perform other kinds of processing. You can even implement standard Sendmail maps like virtusertable, mailertable, access_db, etc. using SOCKETMAP. .SH TICK REQUESTS .PP If you supply the \fB\-X\fR option to \fBmimedefang-multiplexor\fR, then every so often, a "tick" request is sent to a free worker. If your filter defines a function called \fBfilter_tick\fR, then this function is called with a single argument: the tick type. If you run multiple parallel ticks, then each tick has a type ranging from 0 to \fIn\fR-1, where \fIn\fR is the number of parallel ticks. If you're only running one tick request, then the argument to \fBfilter_tick\fR is always 0. You can use this facility to run periodic tasks from within MIMEDefang. Note, however, that you have no control over which worker is picked to run \fBfilter_tick\fR. Also, at most one \fBfilter_tick\fR call with a particular "type" argument will be active at any time, and if there are no free workers when a tick would occur, the tick is skipped. .SH SUPPORTED VIRUS SCANNERS The following virus scanners are supported by MIMEDefang: .TP .B o Symantec CarrierScan Server (http://www.symantec.com/region/can/eng/product/scs/) .TP .B o Trend Micro vscan (http://www.antivirus.com/) .TP .B o Sophos Sweep (http://www.sophos.com/products/antivirus/savunix.html) .TP .B o H+BEDV AntiVir (http://www.hbedv.com/) .TP .B o Central Command Vexira (http://www.centralcommand.com/) .TP .B o NAI uvscan (http://www.nai.com) .TP .B o Bitdefender bdc (http://www.bitdefender.com) .TP .B o Norman Virus Control (NVCC) (http://www.norman.no/) .TP .B o Command csav (http://www.commandsoftware.com) .TP .B o F-Secure fsav (http://www.f-secure.com) .TP .B o The clamscan and clamdscan command-line scanners and the clamd daemon from Clam AntiVirus (https://www.clamav.net/) .TP .B o Kaspersky Anti-Virus (AVP) (http://www.kaspersky.com/) .TP .B o F-Risk F-Prot (http://www.f-prot.com/) .TP .B o F-Risk F-Prot v6 (http://www.f-prot.com/) .TP .B o F-Risk FPROTD (daemonized version of F-Prot) .TP .B o Symantec CarrierScan Server (http://www.symantec.ca/region/can/eng/product/scs/buymenu.html) .TP .B o Sophie (http://www.vanja.com/tools/sophie/), which uses the libsavi library from Sophos, is supported in daemon-scanning mode. .TP .B o Trophie (http://www.vanja.com/tools/trophie/), which uses the libvsapi library from Trend Micro, is supported in daemon-scanning mode. .TP .B o ESET NOD32 (http://www.eset.com/) .SH AUTHORS \fBmimedefang\fR was written by Dianne Skoll . The \fBmimedefang\fR home page is \fIhttps://www.mimedefang.org/\fR. .SH SEE ALSO mimedefang(8), mimedefang.pl(8), Mail::MIMEDefang(3) mimedefang-3.6/mimedefang-multiplexor.8.in000066400000000000000000000472741475763067200206710ustar00rootroot00000000000000.\" $Id$ .\"" .TH MIMEDEFANG-MULTIPLEXOR 8 "8 February 2005" .UC 4 .SH NAME mimedefang-multiplexor \- Process pool controller for mail filters. .SH SYNOPSIS .B mimedefang-multiplexor \fR[\fIoptions\fR] .SH DESCRIPTION \fBmimedefang-multiplexor\fR manages a pool of Perl processes for scanning e-mail. It is designed to work in conjunction with \fBmimedefang\fR(8) and \fBmimedefang.pl\fR(8). \fBmimedefang-multiplexor\fR opens a UNIX-domain socket and listens for requests for work from \fBmimedefang\fR. As requests come in, \fBmimedefang-multiplexor\fR creates Perl processes as needed to scan mail. The Perl processes are not killed when scanning is completed, but continue to run in a loop. Perl processes are re-used for subsequent e-mail messages. This eliminates the large overhead of starting a new Perl process for each incoming message. To avoid memory leaks, the Perl processes are killed after they have handled some number of scans. .SH OPTIONS .TP .B \-U \fIuser\fR Runs the multiplexor as \fIuser\fR rather than \fIroot\fR. This option is mandatory, and must match the \fB\-U\fR option supplied to \fBmimedefang\fR. .TP .B \-m \fIminWorkers\fR The minimum number of Perl processes to keep running at all times. The default is zero. .TP .B \-x \fImaxWorkers\fR The maximum number of Perl processes to run simultaneously. If a request comes in and all processes are busy, a temporary failure is signalled to the SMTP peer. The default is 2. .TP .B \-r \fImaxRequests\fR The maximum number of requests a given process handles before it is killed and a replacement started. The default is 500. .TP .B \-i \fIidleTime\fR The idle time in seconds after which to kill of excess Perl processes. That is, if the process is idle for longer than this time, and there are more than \fIminWorkers\fR running, the process is killed. Note that this is implemented as a timer which ticks every \fIidleTime\fR seconds; therefore, processes may be idle for up to twice this time before they are killed. The default for \fIidleTime\fR is 300 seconds. .TP .B \-V \fImaxLifetime\fR The maximum lifetime in seconds of a worker before it is killed and a replacement started. The default is -1, which signifies no maximum lifetime. Note that the lifetime check is done only when a worker becomes idle after a request, or every time the idle-timeout check is made. On a very quiet system, workers may live for longer than \fImaxLifetime\fR by as much as \fIidleTime\fR. Note also that the lifetime is measured not from when the worker started running, but from when it was assigned its very first request. A completely-idle worker that has never processed any requests will not be terminated by the \fImaxLifetime\fR setting. .TP .B \-b \fIbusyTime\fR The longest a Perl process is allowed to spend scanning an e-mail before it is declared hung up and killed. The default is 120 seconds. .TP .B \-Z This option specifies that the multiplexor should accept and process "status updates" from busy workers. Note that this consumes one extra file descriptor per worker, plus a small amount of CPU time per status update. .TP .B \-c \fIcmdTime\fR The timeout for communication between \fBmimedefang-multiplexor\fR and \fBmimedefang\fR, or between \fBmimedefang-multiplexor\fR and a Perl scanning process. The default is 10 seconds. This timeout should be kept quite short. .TP .B \-w \fIwaitTime\fR When \fBmimedefang-multiplexor\fR starts the initial workers, or needs to bring the number of running workers up to the number defined by the \fB\-m\fR option, it does not start all the workers at once, because this could overload your server. Instead, it starts one worker every \fIwaitTime\fR seconds. The default value for \fIwaitTime\fR is 3. .TP .B \-W \fIwaitTime\fR If you use this option, \fBmimedefang-multiplexor\fR will \fInever\fR activate a worker until \fIwaitTime\fR seconds have elapsed since the last worker activation. This could result in mail being tempfailed if worker activations do not keep pace with incoming mail. However, it may be preferable to tempfail mail rather than allow the load on your server to spike up too quickly. The default value for this option is 0, meaning that \fBmimedefang-multiplexor\fR will start workers as quickly as necessary to keep up with incoming mail. .TP .B \-z \fIspooldir\fR Set the spool directory to \fIspooldir\fR. If this option is omitted, the spool directory defaults to @SPOOLDIR@. .TP .B \-s \fIpathName\fR The UNIX-domain socket on which \fBmimedefang-multiplexor\fR listens for requests. This should be specified as an absolute pathname. If this option is not supplied, it defaults to mimedefang-multiplexor.sock under the spool directory. .TP .B \-a \fIsocket\fR A socket for listening for requests. This is similar to the \fB\-s\fR socket, except that a restricted set of requests are processed. On this socket, the multiplexor will only process requests asking for status; it will not accept any commands to do scanning or that would consume a worker. See the SOCKET SPECIFICATION section for the format of \fIsocket\fR. .TP .B \-p \fIfileName\fR Causes \fBmimedefang-multiplexor\fR to write its process-ID (after becoming a daemon) to the specified file. The file will be owned by root. .TP .B \-o \fIfileName\fR Causes \fbmimedefang-multiplexor\fR to use \fIfileName\fR as a lock file to avoid multiple instances from running. If you supply \fB\-p\fR but not \fB\-o\fR, then \fbmimedefang-multiplexor\fR constructs a lock file by appending ".lock" to the pid file. However, this is less secure than having a root-owned pid file in a root-owned directory and a lock file writable by the user named by the \fB\-U\fR option. (The lock file must be writable by the \fB\-U\fR user.) .TP .B \-f \fIfilter_path\fR Normally, \fBmimedefang-multiplexor\fR executes a Perl filter script called \fBmimedefang.pl\fR to scan the e-mail. However, you can have it execute any program you like by specifying the full path to the program with the \fB\-f\fR option. This program must obey the protocol documented in \fBmimedefang-protocol\fR(7); see that manual page for details. Note that the \fB-f\fR option does \fInot\fR specify the "filter" to use with \fBmimedefang.pl\fR; instead, it specifies the program for \fBmimedefang-multiplexor\fR to execute. You almost certainly should \fInot\fR use this option unless you wish to replace \fBmimedefang.pl\fR with your own program. .TP .B \-F \fIrules_path\fR Specifies the path to the filter rules. By default, \fB@CONFDIR_EVAL@/mimedefang-filter\fR is used. If you use the \fB\-F\fR option, its value is passed to the underlying Perl filter program using \fB\-f\fR. .TP .B \-l Log certain events, including the output of the Perl workers' standard-error, using syslog. Normally, the multiplexor does not log much information. .TP .B \-d Write debugging information about event-handling code in /var/log/mimedefang-event-debug.log. This is only of use to people debugging \fBmimedefang-multiplexor\fR. .TP .B \-R \fIkbytes\fR Limits the resident-set size of the worker filter processes to \fIkbytes\fR kilobytes. This limit is not supported on all operating systems; it is known to work on Linux. .TP .B \-M \fIkbytes\fR Limits the total memory space of worker filter processes to \fIkbytes\fR kilobytes. This limit is supported on all operating systems which support the setrlimit(2) system call. This should include most modern UNIX systems. We recommend that you monitor your worker filter processes and get a feel for how much memory they use. You should then limit the memory to two or three times the worst-case that you have observed. This can help mitigate denial-of-service attacks which use complicated MIME messages to force \fBmimedefang.pl\fR to consume lots of memory. .TP .B \-h Print usage information and exit. .TP .B \-t \fIfilename\fR Log statistical information to \fIfilename\fR. See the section STATISTICS for more information. .TP .B \-T Log statistical information using \fBsyslog\fR(2). You may use any \fB\-t\fR and \fB\-T\fR together, in which case statistical information is logged in a file and using \fBsyslog\fR. .TP .B \-u Flush the statistics file after every write. Normally, \fBmimedefang-multiplexor\fR does not flush the file; this is the best choice for minimizing disk I/O on a busy mail server. However, if you wish to watch statistics entries in real-time, you should enable flushing. .TP .B \-D Do not fork into the background and become a daemon. Instead, stay in the foreground. Useful mainly for debugging or if you have a supervisory process managing \fBmimedefang-multiplexor\fR. .TP .B \-q \fIqueue_size\fR Normally, if all workers are busy and \fBmimedefang-multiplexor\fR receives another request, it fails it with the error "No free workers." However, if you use the \fB\-q\fR option, then up to \fIqueue_size\fR requests will be queued. As soon as a worker becomes free, the queued requests will be handed off in FIFO order. If the queue is full and another request comes in, then the request is failed with "No free workers". .TP .B \-Q \fIqueue_timeout\fR Queued requests should not stay on the queue indefinitely. If a queued request cannot be processed within \fIqueue_timeout\fR (default 30) seconds of being placed on the queue, it is failed with a "Queued request timed out" message. See the section "QUEUEING REQUESTS" for more discussion. .TP .B \-O \fIsock\fR Listen on a \fInotification socket\fR for connections from \fIlisteners\fR. \fBmimedefang-multiplexor\fR can inform external programs of state changes by sending messages over a notification socket. The external programs connect to this socket and then listen for notifications. See the section SOCKET SPECIFICATION for the format of \fIsock\fR. See the \fBmimedefang-notify\fR(7) man page for details of the notification protocol. .TP .B \-N \fImap_sock\fR Listen on a \fImap socket\fR for Sendmail SOCKETMAP connections. As of Sendmail 8.13, you can define a Sendmail map type that talks to a daemon over a socket. \fBmimedefang-multiplexor\fR implements that protocol; consult the \fBmimedefang-filter\fR(5) man page for detils (see the SOCKET MAPS section). See the section SOCKET SPECIFICATION for the format of \fImap_sock\fR. .TP .B \-I \fIbacklog\fR When \fBmimedefang-multiplexor\fR creates a listening socket, it calculates the "backlog" argument to \fBlisten\fR(2) based on the maximum number of workers. However, you can explicitly set this backlog with the \fB\-I\fR option. Setting the backlog to a high value (around 30-50) may help on a very busy server. If you see mail log messages saying "MXCommand: socket: Connection refused" during busy periods, then that's an indication you need a higher listen backlog. .TP .B \-L \fIinterval\fR Log the worker status every \fIinterval\fR seconds. This logs a line using syslog; the line looks like this: .TP .nf Worker status: Stopped=s Idle=i Busy=b Killed=k Queued=q Msgs=m Activations=a .fi Here, "Stopped" is the number of non-running workers, "Idle" is the number of idle workers, "Busy" is the number of busy workers, "Killed" is the number of killed workers yet to be reaped, "Queued" is the number of queued requests, "Msgs" is the total number of messages processed since the multiplexor began running, and "Activations" is the number of times a Perl process has been started since the multiplexor began running. If you supply an \fIinterval\fR of 0 (which is the default), no periodic status logging is performed. If you supply an \fIinterval\fR of less than 5 seconds, it is silently reset to 5 seconds. .TP \fB\-S \fIfacility\fR Specifies the syslog facility for log messages. The default is \fImail\fR. See \fBopenlog\fR(3) for a list of valid facilities. You can use either the short name ("mail") or long name ("LOG_MAIL") for the facility name. .TP \fB\-E\fR Specifies that the multiplexor should create an embedded Perl interpreter. This can improve performance dramatically. But see the section "EMBEDDING PERL" for more information. .TP \fB\-X\fR \fIn\fR Specifies that the multiplexor should initiate a "tick" request every \fIn\fR seconds. This causes your \fIfilter_tick\fR function (if defined) to be called. Note that you have no control over which worker executes \fIfilter_tick\fR. If all workers are busy when a tick occurs, that tick request is skipped and a warning message is logged. .TP \fB\-P\fR \fIn\fR Specifies that the multiplexor should run \fIn\fR tick requests in parallel. Each tick is run as often as specified with the \fB\-X\fR argument. (If you omit the \fB\-P\fR option, then the multiplexor behaves as if \fB\-P 1\fR had been specified.) If you run parallel ticks, each tick is assigned an integer identifying its "type". The type ranges from 0 to \fIn\fR-1. While there may be as many as \fIn\fR tick requests running at a time, only one tick of each type will be active at any time. .TP \fB\-Y\fR \fIlabel\fR Sets the tag used in the multiplexor's syslog messages to \fIlabel\fR instead of \fBmimedefang-multiplexor\fR. .TP \fB\-G\fR Normally, \fBmimedefang-multiplexor\fR uses a umask of 027 when creating listening sockets. If you would like the sockets to be readable and writeable by the group as well as the owner, supply the \fB\-G\fR option. This causes the umask to be 007 whenever UNIX-domain sockets are created. .TP \fB\-y\fR \fIn\fR Limits the maximum number of concurrent \fBrecipok\fR checks to \fIn\fR on a per-domain basis. The value of \fIn\fR can range from 0 (in which case no limit is applied) to \fImaxWorkers\fR, where \fImaxWorkers\fR is the argument to the \fB\-x\fR option. If \fIn\fR is outside that range, it is ignored (and no limit is applied.) .RS .PP The \fBrecipok\fR command ultimately invokes the \fBfilter_recipient\fR function in your filter. If you are doing recipient verification against servers that may be slow or unreliable, you can use the \fB\-y\fR option to limit the number of concurrent recipient verifications per domain. That way, if one domain's server becomes very slow, it won't consume all available workers for recipient verification. Instead, its RCPT commands will be tempfailed and there will be workers available to handle RCPT commands for other domains. .RE .SH SOCKET SPECIFICATION The \fB\-a\fR, \fB\-N\fR and \fB\-O\fR options takes a socket as an argument. The format of the socket parameter is similar to that of the Sendmail Milter library, and is one of the following: .TP .B /path/to/socket A UNIX-domain socket .TP .B inet:portnum A TCP socket bound to port \fIportnum\fR, but which accepts connections only from the IPv4 loopback address (127.0.0.1). .TP .B inet_any:portnum A TCP socket bound to port \fIportnum\fR which will accept connections from any address. \fIUse inet_any with caution!\fR .TP .B inet6:portnum A TCP socket bound to port \fIportnum\fR listening on the IPv6 loopback address. .TP .B inet6_any:portnum A TCP socket bound to port \fIportnum\fR listening on the IPv6 wildcard address. .SH QUEUEING REQUESTS Normally, if all workers are busy, any additional requests are failed immediately. However, the \fB\-q\fR and \fB\-Q\fR options allow you to queue requests for a short amount of time. This facility is intended to gracefully handle a temporary overload; most of the time, your queue should be empty. Because \fBmimedefang\fR checks the number of free workers when a connection is opened and fails the connection if there are no free workers, the intent of the queue is to allow SMTP transactions that are already underway to continue if there is a slight overload. Any new connections will be failed if all workers are busy, but existing connections are allowed to continue. Queuing requests may improve throughput on extremely busy servers. Note that if you supply the \fB\-q\fR option to \fBmimedefang\fR, then even new connections are allowed to queue. This may improve throughput by keeping the worker utilization higher. The \fB\-R\fR option to \fBmimedefang\fR can be used to reserve a specified number of workers for connections from the loopback address. Using the \fB\-R\fR option has the side-effect of permitting new connections from the loopback address to queue. .SH EMBEDDING PERL Normally, when \fBmimedefang-multiplexor\fR activates a worker, it forks and execs \fBmimedefang.pl\fR. However, if the multiplexor was compiled with embedded Perl support, and you supply the \fB\-E\fR command-line option, the multiplexor works like this: .TP 1 It creates an embedded Perl interpreter, and sources \fBmimedefang.pl\fR with a special command-line argument telling it to read the filter, but not to enter the main loop. .TP 2 Each time a worker is activated, the multiplexor calls fork() and runs the \fBmimedefang.pl\fR main loop. This invokes \fBfilter_initialize\fR and then runs the main loop. .PP On some platforms (for example, Red Hat Linux 7.3 with Perl 5.6.1), it is not safe to destroy and recreate a Perl interpreter without causing a memory leak. On those platforms, if you attempt to reread the filter file (by sending the multiplexor a HUP signal or reread command), the filter will \fInot\fR be re-read, and a message will be logged to syslog. On those platforms, you must kill and restart MIMEDefang if you change the filter file. .PP On most platforms, however, a filter reread is accomplished by destroying and re-creating the embedded interpreter, re-sourcing \fBmimedefang.pl\fR and killing workers as soon as they are idle. .SH STATISTICS With the \fB\-t\fR option, \fBmimedefang-multiplexor\fR logs certain events to a file. This file can be post-processed to gather statistics about the multiplexor. You can use it to tune the number of workers you run, adjust timeouts, and so on. .PP Each line of the file looks like this: .nf YYYY/MM/DD:HH:MM:SS timestamp event key=val key=val... .fi Here, YYYY/MM/DD:HH:MM:SS is the local time of day. Timestamp is the number of seconds since January 1, 1970. Event is the name of an event. The valid events are: .TP .B StartWorker A worker process has been started. .TP .B KillWorker A worker process has been killed. .TP .B ReapWorker A dead worker process has been reaped. It is possible to have a ReapWorker event without a previous KillWorker event if the worker process terminated abnormally. .TP .B StartFilter A worker process has begun filtering an e-mail message. .TP .B EndFilter A worker process has finished filtering an e-mail message. .PP The possible keys in the key=value pairs are: .TP .B worker=\fIn\fR The worker involved in the event. Every worker is identified by a small integer. .TP .B nworkers=\fIn\fR The total number of running workers immediately after the event happened. .TP .B nbusy=\fIn\fR The number of busy workers (workers which are processing an e-mail message) immediately after the event happened. .TP .B reason="\fIstring\fB" The reason for a StartWorker or KillWorker event. (Present only for these events.) .TP .B numRequests=\fIn\fR The number of e-mails processed by the worker. Present only for an EndFilter event. .PP If you send the \fBmimedefang-multiplexor\fR process a SIGHUP signal (kill -1 \fIpid\fR), it closes and reopens the statistics file. This is useful during log file rotation. .PP If you send the \fBmimedefang-multiplexor\fR process a SIGINT signal (kill -INT \fIpid\fR), it terminates all active-but-idle workers. Also, any active-and-busy workers terminate as soon as they finish filtering the current message. This is useful to force a reread of the filter rules file without stopping and restarting Sendmail. .PP If you send the \fBmimedefang-multiplexor\fR process a SIGTERM signal (kill \fIpid\fR), it terminates all workers and exits immediately. .SH AUTHOR \fBmimedefang-mulitplexor\fR was written by Dianne Skoll . The \fBmimedefang\fR home page is \fIhttps://www.mimedefang.org/\fR. .SH SEE ALSO mimedefang.pl(8), mimedefang-filter(5), mimedefang(8), mimedefang-protocol(7) mimedefang-3.6/mimedefang-multiplexor.c000066400000000000000000004310121475763067200203220ustar00rootroot00000000000000/*********************************************************************** * * mimedefang-multiplexor.c * * Main program which manages a pool of e-mail scanning processes. * * Copyright (C) 2001-2005 Roaring Penguin Software Inc. * http://www.roaringpenguin.com * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #include "config.h" #include "event_tcp.h" #include "mimedefang.h" #ifdef HAVE_GETOPT_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_STDINT_H #include #include #define BIG_INT int64_t #define BIG_INT_FMT PRIi64 #elif HAVE_LONG_LONG_INT #define BIG_INT long long #define BIG_INT_FMT "lld" #else #define BIG_INT long #define BIG_INT_FMT "ld" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SETRLIMIT #include static void limit_mem_usage(unsigned long rss, unsigned long as); #endif static char *pidfile = NULL; static char *lockfile = NULL; /* Number of file descriptors to close when forking */ #define CLOSEFDS 256 /* Weird case, but hey... */ #if defined(HAVE_WAIT3) && !defined(HAVE_SETRLIMIT) #include #endif #define STR(x) STR2(x) #define STR2(x) #x #define MAX_CMD_LEN 4096 /* Maximum length of command from mimedefang */ #define MAX_DIR_LEN 511 /* Maximum length of working directory */ #define MAX_QID_LEN 31 /* Maximum length of a Sendmail queue-id */ #define MAX_STATUS_LEN 64 /* Maximum length of status tag */ #define MAX_UNPRIV_CONNS 20 /* Maximum number of simultaneous unprivileged connections */ #define MAX_DOMAIN_LEN 128 /* Maximum length of a domain name for tracking per-domain recipok workers */ #define DOLOG Settings.doSyslog #define WORKERNO(s) ((int) ((s) - AllWorkers)) /* A worker can be in one of four states: Stopped -- Worker has no associated Perl process Idle -- Worker has an associated process, but is not doing work Busy -- Worker is processing a command Killed -- Worker has been killed, but we're waiting for it to exit */ #define STATE_STOPPED 0 #define STATE_IDLE 1 #define STATE_BUSY 2 #define STATE_KILLED 3 #define NUM_WORKER_STATES 4 /* Structure of a worker process */ typedef struct Worker_t { struct Worker_t *next; /* Link in free/busy list */ EventSelector *es; /* Event selector */ pid_t pid; /* Process ID of worker process */ int numRequests; /* Number of requests handled by worker */ int numScans; /* Number of messages scanned */ time_t idleTime; /* Time when worker became idle */ time_t activationTime; /* Time when worker was activated */ time_t firstReqTime; /* Time when worker received its first job */ time_t lastStateChange; /* Time when worker last changed state */ unsigned int activated; /* Activation order */ int workerStdin; /* Worker's stdin descriptor */ int workerStdout; /* Worker's stdout descriptor */ int workerStderr; /* Worker's stderr descriptor */ int workerStatusFD; /* File descriptor for worker status reports */ int clientFD; /* Client file descriptor */ int oom; /* Did worker run out of memory? */ EventTcpState *event; /* Pending event handler */ EventHandler *errHandler; /* Read handler for stderr */ EventHandler *statusHandler; /* Read handler for status descriptor */ EventHandler *termHandler; /* Timer after which we send SIGTERM */ char workdir[MAX_DIR_LEN+1]; /* Working directory for current scan */ char qid[MAX_QID_LEN+1]; /* Current Sendmail queue ID */ char status_tag[MAX_STATUS_LEN]; /* Status tag */ char domain[MAX_DOMAIN_LEN]; /* Current domain for recipok */ int generation; /* Worker's generation */ int state; /* Worker's state */ unsigned int histo; /* Kind of double-duty as histogram value */ int tick_no; /* Which tick are we handling? */ struct timeval start_cmd; /* Time when current command started */ int cmd; /* Which of the 4 commands with history? */ int last_cmd; /* Last command executed */ } Worker; /* A queued request */ typedef struct Request_t { struct Request_t *next; /* Next request in linked list */ EventSelector *es; /* Event selector */ EventHandler *timeoutHandler; /* Time out if we're queued too long */ int fd; /* File descriptor for client communication */ char *cmd; /* Command to send to worker */ } Request; #define MAX_QUEUE_SIZE 128 /* Hard-coded limit */ Request RequestQueue[MAX_QUEUE_SIZE]; int NumQueuedRequests = 0; Request *RequestHead; Request *RequestTail; Worker *AllWorkers; /* Array of all workers */ Worker *Workers[NUM_WORKER_STATES]; /* Lists of workers in each state */ int WorkerCount[NUM_WORKER_STATES]; /* Count of workers in each state */ int Generation = 0; /* Current generation */ int NumMsgsProcessed = 0; /* Number of messages processed since last "msgs" query */ unsigned int Activations = 0; /* Incremented when a worker is activated */ static int Old_NumFreeWorkers = -1; int NumUnprivConnections = 0; static pid_t ParentPid = (pid_t) -1; static char **Env; struct Settings_t { int minWorkers; /* Minimum number of workers to keep running */ int maxWorkers; /* Maximum possible number of workers */ int maxRecipokPerDomain; /* Maximum workers doing recipok for a given domain */ int maxRequests; /* Maximum number of requests per worker */ int maxLifetime; /* Maximum lifetime of a worker in seconds. */ int maxIdleTime; /* Excess workers should be killed after time */ int busyTimeout; /* Timeout after which we kill scanner */ int clientTimeout; /* Timeout for client request/reply */ int slewTime; /* Time to wait between workers' activation */ int waitTime; /* Minimum time to wait between activations */ int doSyslog; /* If true, log various things with syslog */ char const *sockName; /* Socket name for talking to mimedefang */ char const *progPath; /* Program to execute for filter */ char const *statsFile; /* File name for logging statistics */ char const *subFilter; /* Sub-filter to pass to filter */ char const *unprivSockName; /* Socket for unprivileged commands */ char const *spoolDir; /* Spool directory to chdir into */ FILE *statsFP; /* File pointer for stats file */ int statsToSyslog; /* If true, log stats using syslog */ int flushStats; /* If non-zero, flush stats file after write*/ unsigned long maxRSS; /* Maximum RSS for workers (if supported) */ unsigned long maxAS; /* Maximum address space for workers */ int logStatusInterval; /* How often to log status to syslog */ char const *mapSock; /* Socket for Sendmail TCP map requests */ int requestQueueSize; int requestQueueTimeout; int listenBacklog; /* Listen backlog */ int useEmbeddedPerl; /* Use embedded Perl interpreter */ char const *notifySock; /* Socket for notifications */ int tick_interval; /* Do "tick" request every tick_interval s */ int num_ticks; /* How many tick types to cycle through */ char const *syslog_label; /* Syslog label */ int wantStatusReports; /* Do we want status reports from workers? */ int debugWorkerScheduling; /* Log details about worker scheduling */ } Settings; /* Structure for keeping statistics on number of messages processed in last 10 minutes */ #define NO_CMD -2 #define OTHER_CMD -1 #define MIN_CMD 0 #define SCAN_CMD 0 #define RELAYOK_CMD 1 #define SENDEROK_CMD 2 #define RECIPOK_CMD 3 #define MAX_CMD 3 #define NUM_CMDS (MAX_CMD+1) static char *CmdName[] = { "scan", "relayok", "senderok", "recipok" }; /* Not real commands */ #define HISTORY_SECONDS (10*60) #define HISTORY_HOURS 24 typedef struct { time_t first; /* Time at which first entry was made */ time_t last; /* Time at which last entry was made */ int elapsed; /* Seconds or hours since epoch for this bucket */ int count; /* Number of messages processed */ int workers; /* TOTAL number of workers (active workers * count) */ int ms; /* TOTAL scan time in milliseconds */ int activated; /* Number of workers activated */ int reaped; /* Number of workers reaped */ } HistoryBucket; static HistoryBucket history[NUM_CMDS][HISTORY_SECONDS]; static HistoryBucket hourly_history[NUM_CMDS][HISTORY_HOURS]; /* Pipe written on reception of SIGCHLD */ static int Pipe[2] = {-1, -1}; #ifndef HAVE_SIG_ATOMIC_T #define sig_atomic_t int #endif static volatile sig_atomic_t ReapPending = 0; static volatile sig_atomic_t HupPending = 0; static volatile sig_atomic_t IntPending = 0; static volatile sig_atomic_t CharPending = 0; static int DebugEvents = 0; static time_t LastWorkerActivation = (time_t) 0; static time_t TimeOfProgramStart = (time_t) 0; extern int drop_privs(char const *user, uid_t uid, gid_t gid); extern int find_syslog_facility(char const *facility_name); /* Prototypes */ #ifdef HAVE_WAIT3 static void log_worker_resource_usage(Worker *s, struct rusage *usage); #endif extern int make_notifier_socket(EventSelector *es, char const *name); extern void notify_listeners(EventSelector *es, char const *msg); extern void notify_worker_status(EventSelector *es, int workerno, char const *status); extern void notify_worker_state_change(EventSelector *es, int workerno, char const *old_state, char const *new_state); static Worker *findFreeWorker(int cmdno); static void shutDescriptors(Worker *s); static void reapTerminatedWorkers(int killed); static Worker *findWorkerByPid(pid_t pid); static int update_worker_status(Worker *s, char const *buf); static void set_worker_status_from_command(Worker *s, char const *buf); static pid_t activateWorker(Worker *s, char const *reason); static void killWorker(Worker *s, char const *reason); static void terminateWorker(EventSelector *es, int fd, unsigned int flags, void *data); static void nukeWorker(EventSelector *es, int fd, unsigned int flags, void *data); /* List-management functions */ static void unlinkFromList(Worker *s); static void putOnList(Worker *s, int state); static void handleAccept(EventSelector *es, int fd); static void handleUnprivAccept(EventSelector *es, int fd); static void handleCommand(EventSelector *es, int fd, char *buf, int len, int flag, void *data); static void handleWorkerReceivedCommand(EventSelector *es, int fd, char *buf, int len, int flag, void *data); static void handleWorkerReceivedTick(EventSelector *es, int fd, char *buf, int len, int flag, void *data); static void handleWorkerReceivedAnswer(EventSelector *es, int fd, char *buf, int len, int flag, void *data); static void handleWorkerReceivedAnswerFromTick(EventSelector *es, int fd, char *buf, int len, int flag, void *data); static void doScan(EventSelector *es, int fd, char *cmd); static void doWorkerInfo(EventSelector *es, int fd, char *cmd); static void doScanAux(EventSelector *es, int fd, char *cmd, int queueable); static void doStatus(EventSelector *es, int fd); static void doHelp(EventSelector *es, int fd, int unpriv); static void doWorkerReport(EventSelector *es, int fd, int only_busy); static void doLoad(EventSelector *es, int fd, int cmd); static void doLoad1(EventSelector *es, int fd, int back); static void doHourlyLoad(EventSelector *es, int fd, int cmd); static void doHistogram(EventSelector *es, int fd); static void doWorkerCommand(EventSelector *es, int fd, char *cmd); static void doWorkerCommandAux(EventSelector *es, int fd, char *cmd, int queueable); static void checkWorkerForExpiry(Worker *s); static void handlePipe(EventSelector *es, int fd, unsigned int flags, void *data); static void handleWorkerStderr(EventSelector *es, int fd, unsigned int flags, void *data); static void handleWorkerStatusFD(EventSelector *es, int fd, unsigned int flags, void *data); static void childHandler(int sig); static void hupHandler(int sig); static void intHandler(int sig); static void sigterm(int sig); static void newGeneration(void); static void handleIdleTimeout(EventSelector *es, int fd, unsigned int flags, void *data); static void doStatusLog(EventSelector *es, int fd, unsigned int flags, void *data); static void logWorkerReaped(Worker *s, int status); static int queue_request(EventSelector *es, int fd, char *cmd); static int handle_queued_request(void); static void handleRequestQueueTimeout(EventSelector *es, int fd, unsigned int flags, void *data); static void statsReopenFile(void); static void statsLog(char const *event, int workerno, char const *fmt, ...); static void bringWorkersUpToMin(EventSelector *es, int fd, unsigned int flags, void *data); static void scheduleBringWorkersUpToMin(EventSelector *es); static int minScheduled = 0; static void schedule_tick(EventSelector *es, int tick_no); static void handleMapAccept(EventSelector *es, int fd); static void init_history(void); static HistoryBucket *get_history_bucket(int cmd); static HistoryBucket *get_hourly_history_bucket(int cmd); static int get_history_totals(int cmd, time_t now, int back, int *total, int *workers, BIG_INT *ms, int *activated, int *reaped); static int get_hourly_history_totals(int cmd, time_t now, int hours, int *total, int *workers, BIG_INT *ms, int *secs); #define NUM_FREE_WORKERS (WorkerCount[STATE_IDLE] + WorkerCount[STATE_STOPPED]) #define NUM_RUNNING_WORKERS (WorkerCount[STATE_IDLE] + WorkerCount[STATE_BUSY] + WorkerCount[STATE_KILLED]) #define REPORT_FAILURE(msg) do { if (kidpipe[1] >= 0) { write(kidpipe[1], "E" msg, strlen(msg)+1); } else { fprintf(stderr, "%s\n", msg); } } while(0) /********************************************************************** * %FUNCTION: state_name * %ARGUMENTS: * state -- a state number * %RETURNS: * A string representing the name of the state ***********************************************************************/ static char const * state_name(int state) { switch(state) { case STATE_STOPPED: return "Stopped"; case STATE_IDLE: return "Idle"; case STATE_BUSY: return "Busy"; case STATE_KILLED: return "Killed"; } return "Unknown"; } /********************************************************************** * %FUNCTION: state_name_lc * %ARGUMENTS: * state -- a state number * %RETURNS: * A string representing the name of the state in all lower-case ***********************************************************************/ static char const * state_name_lc(int state) { switch(state) { case STATE_STOPPED: return "stopped"; case STATE_IDLE: return "idle"; case STATE_BUSY: return "busy"; case STATE_KILLED: return "killed"; } return "unknown"; } /********************************************************************** * %FUNCTION: reply_to_mimedefang_with_len * %ARGUMENTS: * es -- event selector * fd -- file descriptor * msg -- message to send back * len -- length of message * %RETURNS: * The event associated with the reply, or NULL. * %DESCRIPTION: * Sends a final message back to mimedefang. Closes fd after message has * been sent. ***********************************************************************/ static EventTcpState * reply_to_mimedefang_with_len(EventSelector *es, int fd, char const *msg, int len) { EventTcpState *e; if (len == 0) { /* Nothing to say. */ close(fd); return NULL; } e = EventTcp_WriteBuf(es, fd, msg, len, NULL, Settings.clientTimeout, NULL); if (!e) { if (DOLOG) { syslog(LOG_ERR, "reply_to_mimedefang: EventTcp_WriteBuf failed: %m"); } close(fd); } return e; } /********************************************************************** * %FUNCTION: reply_to_mimedefang * %ARGUMENTS: * es -- event selector * fd -- file descriptor * msg -- message to send back * %RETURNS: * The event associated with the reply, or NULL. * %DESCRIPTION: * Sends a final message back to mimedefang. ***********************************************************************/ EventTcpState * reply_to_mimedefang(EventSelector *es, int fd, char const *msg) { return reply_to_mimedefang_with_len(es, fd, msg, strlen(msg)); } /********************************************************************** * %FUNCTION: findWorkerByPid * %ARGUMENTS: * pid -- Process-ID we're looking for * %RETURNS: * The worker with given pid, or NULL if not found * %DESCRIPTION: * Searches the killed, idle and busy lists for specified worker. ***********************************************************************/ static Worker * findWorkerByPid(pid_t pid) { Worker *s; /* Most likely to be on killed list, so search there first */ s = Workers[STATE_KILLED]; while(s) { if (s->pid == pid) return s; s = s->next; } s = Workers[STATE_IDLE]; while(s) { if (s->pid == pid) return s; s = s->next; } s = Workers[STATE_BUSY]; while(s) { if (s->pid == pid) return s; s = s->next; } return NULL; } /********************************************************************** * %FUNCTION: usage * %ARGUMENTS: * None * %RETURNS: * Nothing (exits) * %DESCRIPTION: * Prints usage information ***********************************************************************/ static void usage(void) { fprintf(stderr, "mimedefang-multiplexor version %s\n", VERSION); fprintf(stderr, "Usage: mimedefang-multiplexor [options]\n"); fprintf(stderr, "Options:\n"); fprintf(stderr, " -h -- Print usage info and exit\n"); fprintf(stderr, " -v -- Print version and exit\n"); fprintf(stderr, " -t filename -- Log statistics to filename\n"); fprintf(stderr, " -p filename -- Write process-ID in filename\n"); fprintf(stderr, " -o file -- Use specified file as a lock file\n"); fprintf(stderr, " -T -- Log statistics to syslog\n"); fprintf(stderr, " -u -- Flush stats file after each write\n"); fprintf(stderr, " -Z -- Accept and process status updates from busy workers\n"); fprintf(stderr, " -U username -- Run as username, not root\n"); fprintf(stderr, " -m minWorkers -- Minimum number of workers\n"); fprintf(stderr, " -x maxWorkers -- Maximum number of workers\n"); fprintf(stderr, " -y recipokPerDom -- Maximum concurrent recipoks per domain\n"); fprintf(stderr, " -r maxRequests -- Maximum number of requests per worker\n"); fprintf(stderr, " -V maxLifetime -- Maximum lifetime of a worker in seconds\n"); fprintf(stderr, " -i idleTime -- Idle time (seconds) for killing excess workers\n"); fprintf(stderr, " -b busyTime -- Busy time (seconds) for killing hung workers\n"); fprintf(stderr, " -c cmdTime -- Request/reply transmission timeout (seconds)\n"); fprintf(stderr, " -w waitTime -- How long to wait between worker activations (seconds)\n"); fprintf(stderr, " -W waitTime -- Absolute minimum to wait between worker activations\n"); fprintf(stderr, " -z dir -- Spool directory\n"); fprintf(stderr, " -s sock -- UNIX-domain socket for communication\n"); fprintf(stderr, " -a u_sock -- Socket for unprivileged communication\n"); fprintf(stderr, " -f /dir/filter -- Specify full path of filter program\n"); fprintf(stderr, " -d -- Debug events in /var/log/mdefang-event-debug.log\n"); fprintf(stderr, " -l -- Log events with syslog\n"); #ifdef HAVE_SETRLIMIT fprintf(stderr, " -R size -- Limit RSS to size kB (if supported on your OS)\n"); fprintf(stderr, " -M size -- Limit memory address space to size kB\n"); #endif fprintf(stderr, " -L interval -- Log worker status every interval seconds\n"); fprintf(stderr, " -S facility -- Set syslog(3) facility\n"); fprintf(stderr, " -N sock -- Listen for Sendmail map requests on sock\n"); fprintf(stderr, " -O sock -- Listen for notification requests on sock\n"); fprintf(stderr, " -q size -- Size of request queue (default 0)\n"); fprintf(stderr, " -Q timeout -- Timeout for queued requests\n"); fprintf(stderr, " -I backlog -- 'backlog' argument for listen on multiplexor socket\n"); fprintf(stderr, " -D -- Do not become a daemon (stay in foreground)\n"); fprintf(stderr, " -X interval -- Run a 'tick' request every interval seconds\n"); fprintf(stderr, " -P n -- Run 'n' parallel tick requests\n"); fprintf(stderr, " -Y label -- Set syslog label to 'label'\n"); fprintf(stderr, " -G -- Make sockets group-writable\n"); #ifdef EMBED_PERL fprintf(stderr, " -E -- Use embedded Perl interpreter\n"); #endif exit(EXIT_FAILURE); } static int set_sigchld_handler(void) { struct sigaction act; /* Set signal handler for SIGCHLD */ act.sa_handler = childHandler; sigemptyset(&act.sa_mask); act.sa_flags = SA_NOCLDSTOP | SA_RESTART; return sigaction(SIGCHLD, &act, NULL); } /********************************************************************** * %FUNCTION: main * %ARGUMENTS: * argc, argv -- usual suspects * %RETURNS: * Nothing -- runs in an infinite loop * %DESCRIPTION: * Main program ***********************************************************************/ int main(int argc, char *argv[], char **env) { int i; int sock, unpriv_sock; int c; int n; int pidfile_fd = -1; int lockfile_fd = -1; char *user = NULL; char *options; int facility = LOG_MAIL; int kidpipe[2] = {-1, -1}; char kidmsg[256]; time_t now; mode_t socket_umask = 077; mode_t file_umask = 077; EventSelector *es; struct sigaction act; struct timeval t; struct passwd *pw = NULL; int nodaemon = 0; /* Record program start time */ TimeOfProgramStart = time(NULL); Env = env; /* Paranoia time */ umask(077); /* Paranoia time II */ if (getuid() != geteuid()) { fprintf(stderr, "ERROR: %s is NOT intended to run suid! Exiting.\n", argv[0]); exit(EXIT_FAILURE); } if (getgid() != getegid()) { fprintf(stderr, "ERROR: %s is NOT intended to run sgid! Exiting.\n", argv[0]); exit(EXIT_FAILURE); } Settings.minWorkers = 0; Settings.maxWorkers = 2; Settings.maxRecipokPerDomain = 0; Settings.maxRequests = 500; Settings.maxLifetime = 0; /* Unlimited */ Settings.maxIdleTime = 300; Settings.busyTimeout = 120; Settings.slewTime = 3; Settings.waitTime = 0; Settings.clientTimeout = 10; Settings.doSyslog = 0; Settings.spoolDir = NULL; Settings.sockName = NULL; Settings.unprivSockName = NULL; Settings.progPath = MIMEDEFANG_PL; Settings.subFilter = NULL; Settings.statsFile = NULL; Settings.statsFP = NULL; Settings.flushStats = 0; Settings.statsToSyslog = 0; Settings.maxRSS = 0; Settings.maxAS = 0; Settings.logStatusInterval = 0; Settings.requestQueueSize = 0; Settings.requestQueueTimeout = 30; Settings.listenBacklog = -1; Settings.useEmbeddedPerl = 0; Settings.notifySock = NULL; Settings.tick_interval = 0; Settings.num_ticks = 1; Settings.mapSock = NULL; Settings.wantStatusReports = 0; Settings.debugWorkerScheduling = 0; #ifndef HAVE_SETRLIMIT options = "GAa:Tt:um:x:y:r:i:b:c:s:hdlf:p:o:w:F:W:U:S:q:Q:I:DEO:X:Y:N:vZP:z:V:"; #else options = "GAa:Tt:um:x:y:r:i:b:c:s:hdlf:p:o:w:F:W:U:S:q:Q:L:R:M:I:DEO:X:Y:N:vZP:z:V:"; #endif while((c = getopt(argc, argv, options)) != -1) { switch(c) { case 'G': socket_umask = 007; file_umask = 027; break; case 'A': Settings.debugWorkerScheduling = 1; break; case 'z': Settings.spoolDir = strdup(optarg); if (!Settings.spoolDir) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'Z': Settings.wantStatusReports = 1; break; case 'a': Settings.unprivSockName = strdup(optarg); if (!Settings.unprivSockName) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'v': printf("mimedefang-multiplexor version %s\n", VERSION); exit(EXIT_SUCCESS); case 'E': #ifdef EMBED_PERL Settings.useEmbeddedPerl = 1; #else fprintf(stderr, "mimedefang-multiplexor compiled without support for embedded perl. Ignoring -E flag.\n"); #endif break; case 'D': nodaemon = 1; break; case 'P': if (sscanf(optarg, "%d", &n) != 1) usage(); if (n < 1) { n = 1; } else if (n > 30) { n = 30; } Settings.num_ticks = n; break; case 'I': if (sscanf(optarg, "%d", &n) != 1) usage(); if (n < 5) { n = 5; } else if (n > 200) { n = 200; } Settings.listenBacklog = n; break; case 'q': if (sscanf(optarg, "%d", &n) != 1) usage(); if (n <= 0) { n = 0; } else if (n > MAX_QUEUE_SIZE) { fprintf(stderr, "%s: Request queue size %d too big (%d max)\n", argv[0], n, MAX_QUEUE_SIZE); exit(EXIT_FAILURE); } Settings.requestQueueSize = n; break; case 'X': if (sscanf(optarg, "%d", &n) != 1) usage(); if (n < 0) { n = 0; } Settings.tick_interval = n; break; case 'Q': if (sscanf(optarg, "%d", &n) != 1) usage(); if (n <= 1) { n = 1; } else if (n > 600) { n = 600; } Settings.requestQueueTimeout = n; break; case 'S': facility = find_syslog_facility(optarg); if (facility < 0) { fprintf(stderr, "%s: Unknown syslog facility %s\n", argv[0], optarg); exit(EXIT_FAILURE); } break; case 'L': if (sscanf(optarg, "%d", &n) != 1) usage(); if (n <= 0) { n = 0; } else if (n < 5) { n = 5; } Settings.logStatusInterval = n; break; case 'R': case 'M': if (sscanf(optarg, "%d", &n) != 1) usage(); if (c == 'R') { Settings.maxRSS = (unsigned long) n; } else { Settings.maxAS = (unsigned long) n; } break; case 'Y': Settings.syslog_label = strdup(optarg); if (!Settings.syslog_label) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'O': Settings.notifySock = strdup(optarg); if (!Settings.notifySock) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'N': Settings.mapSock = strdup(optarg); if (!Settings.mapSock) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'U': /* User to run as */ if (user) { free(user); } user = strdup(optarg); if (!user) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'F': /* Sub-filter */ if (Settings.subFilter) { free((void *) Settings.subFilter); } Settings.subFilter = strdup(optarg); if (!Settings.subFilter) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'W': /* Absolute minimum to wait between each worker's start-up */ if (sscanf(optarg, "%d", &Settings.waitTime) != 1) usage(); if (Settings.waitTime < 0) { Settings.waitTime = 0; } break; case 'w': /* How long to wait between each worker's start-up */ if (sscanf(optarg, "%d", &Settings.slewTime) != 1) usage(); if (Settings.slewTime < 1) { Settings.slewTime = 1; } break; case 'p': /* Write our pid to this file */ if (pidfile != NULL) free(pidfile); pidfile = strdup(optarg); if (!pidfile) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'o': /* Use this as our lock file */ if (lockfile != NULL) free(lockfile); lockfile = strdup(optarg); if (!lockfile) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'f': /* Filter program */ if (optarg[0] != '/') { fprintf(stderr, "%s: -f: You must supply an absolute path for filter program\n", argv[0]); exit(EXIT_FAILURE); } Settings.progPath = strdup(optarg); if (!Settings.progPath) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'u': Settings.flushStats = 1; break; case 't': Settings.statsFile = strdup(optarg); if (!Settings.statsFile) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'T': Settings.statsToSyslog = 1; break; case 'l': Settings.doSyslog = 1; break; case 'd': DebugEvents = 1; break; case 'h': usage(); break; case 'm': if (sscanf(optarg, "%d", &Settings.minWorkers) != 1) usage(); if (Settings.minWorkers < 1) Settings.minWorkers = 1; break; case 'x': if (sscanf(optarg, "%d", &Settings.maxWorkers) != 1) usage(); break; case 'y': if (sscanf(optarg, "%d", &Settings.maxRecipokPerDomain) != 1) usage(); break; case 'r': if (sscanf(optarg, "%d", &Settings.maxRequests) != 1) usage(); if (Settings.maxRequests < 1) Settings.maxRequests = 1; break; case 'V': if (sscanf(optarg, "%d", &Settings.maxLifetime) != 1) usage(); if (Settings.maxLifetime <= 0) { Settings.maxLifetime = -1; } break; case 'i': if (sscanf(optarg, "%d", &Settings.maxIdleTime) != 1) usage(); if (Settings.maxIdleTime < 10) Settings.maxIdleTime = 10; break; case 'b': if (sscanf(optarg, "%d", &Settings.busyTimeout) != 1) usage(); if (Settings.busyTimeout < 10) Settings.busyTimeout = 10; break; case 'c': if (sscanf(optarg, "%d", &Settings.clientTimeout) != 1) usage(); if (Settings.clientTimeout < 10) Settings.clientTimeout = 10; break; case 's': Settings.sockName = strdup(optarg); if (!Settings.sockName) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; default: fprintf(stderr, "\n"); usage(); } } /* Set spooldir, if it's not set */ if (!Settings.spoolDir) { Settings.spoolDir = SPOOLDIR; } /* Set sockName, if it's not set */ if (!Settings.sockName) { Settings.sockName = malloc(strlen(Settings.spoolDir) + strlen("/mimedefang-multiplexor.sock") + 1); if (!Settings.sockName) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } strcpy((char *) Settings.sockName, Settings.spoolDir); strcat((char *) Settings.sockName, "/mimedefang-multiplexor.sock"); } /* Open the pidfile as root. We'll write the pid later on in the grandchild */ if (pidfile) { pidfile_fd = open(pidfile, O_RDWR|O_CREAT, 0666); if (pidfile_fd < 0) { syslog(LOG_ERR, "Could not open PID file %s: %m", pidfile); exit(EXIT_FAILURE); } /* It needs to be world-readable */ fchmod(pidfile_fd, 0644); } /* Drop privileges */ if (user) { pw = getpwnam(user); if (!pw) { fprintf(stderr, "%s: Unknown user '%s'\n", argv[0], user); exit(EXIT_FAILURE); } if (drop_privs(user, pw->pw_uid, pw->pw_gid) < 0) { exit(EXIT_FAILURE); } free(user); } /* Warn */ if (!getuid() || !geteuid()) { fprintf(stderr, "ERROR: You must not run mimedefang-multiplexor as root.\n" "Use the -U option to set a non-root user.\n"); exit(EXIT_FAILURE); } if (chdir(Settings.spoolDir) < 0) { fprintf(stderr, "%s: Unable to chdir(%s): %s\n", argv[0], Settings.spoolDir, strerror(errno)); exit(EXIT_FAILURE); } /* Fix obvious stupidities */ if (Settings.maxWorkers < 1) { Settings.maxWorkers = 1; } if (Settings.minWorkers < 1) { Settings.minWorkers = 1; } if (Settings.minWorkers > Settings.maxWorkers) { Settings.minWorkers = Settings.maxWorkers; } /* Make sure maxRecipokPerDomain is sane */ if (Settings.maxRecipokPerDomain < 0) { Settings.maxRecipokPerDomain = 0; } else if (Settings.maxRecipokPerDomain >= Settings.maxWorkers) { Settings.maxRecipokPerDomain = 0; } /* Daemonize */ if (!nodaemon) { /* Set up a pipe so child can report back when it's happy */ if (pipe(kidpipe) < 0) { perror("pipe"); exit(EXIT_FAILURE); } i = fork(); if (i < 0) { fprintf(stderr, "%s: fork() failed\n", argv[0]); exit(EXIT_FAILURE); } else if (i != 0) { /* parent */ /* Wait for a message from kid */ close(kidpipe[1]); i = read(kidpipe[0], kidmsg, sizeof(kidmsg) - 1); if (i < 0) { fprintf(stderr, "Error reading message from child: %s\n", strerror(errno)); exit(EXIT_FAILURE); } /* Zero-terminate the string */ kidmsg[i] = 0; if (i == 1 && kidmsg[0] == 'X') { /* Child indicated successful startup */ exit(EXIT_SUCCESS); } if (i > 1 && kidmsg[0] == 'E') { /* Child indicated error */ fprintf(stderr, "Error from child: %s\n", kidmsg+1); exit(EXIT_FAILURE); } /* Unknown status from child */ fprintf(stderr, "Unknown reply from child: %s\n", kidmsg); exit(EXIT_FAILURE); } setsid(); signal(SIGHUP, SIG_IGN); i = fork(); if (i < 0) { fprintf(stderr, "%s: fork() failed\n", argv[0]); exit(EXIT_FAILURE); } else if (i != 0) { exit(EXIT_SUCCESS); } } /* Do the locking */ if (pidfile || lockfile) { if ( (lockfile_fd = write_and_lock_pidfile(pidfile, &lockfile, pidfile_fd)) < 0) { REPORT_FAILURE("Cannot lock lockfile: Is another copy running?"); exit(EXIT_FAILURE); } pidfile_fd = -1; } /* Initialize history buckets */ init_history(); /* Initialize queue */ for (i=0; ies = es; s->pid = (pid_t) -1; s->workerStdin = -1; s->workerStdout = -1; s->workerStderr = -1; s->workerStatusFD = -1; s->clientFD = -1; s->oom = 0; s->event = NULL; s->errHandler = NULL; s->statusHandler = NULL; s->termHandler = NULL; s->workdir[0] = 0; s->status_tag[0] = 0; s->domain[0] = 0; s->generation = Generation; s->state = STATE_STOPPED; s->activationTime = (time_t) -1; s->firstReqTime = (time_t) -1; s->lastStateChange = now; s->last_cmd = NO_CMD; } /* Set up the linked list */ for (i=0; i 200) Settings.listenBacklog = 200; } umask(socket_umask); sock = make_listening_socket(Settings.sockName, Settings.listenBacklog, 1); umask(file_umask); if (sock < 0) { if (sock == -2) { REPORT_FAILURE("Argument to -s option must be a UNIX-domain socket, not a TCP socket."); } else { REPORT_FAILURE("Unable to create listening socket."); } if (pidfile) unlink(pidfile); if (lockfile) unlink(lockfile); exit(EXIT_FAILURE); } if(set_cloexec(sock) < 0) { REPORT_FAILURE("Could not set FD_CLOEXEC option on socket"); if (pidfile) unlink(pidfile); if (lockfile) unlink(lockfile); exit(EXIT_FAILURE); } /* Set up an accept loop */ if (!EventTcp_CreateAcceptor(es, sock, handleAccept)) { REPORT_FAILURE("Could not make accept() handler"); if (pidfile) unlink(pidfile); if (lockfile) unlink(lockfile); exit(EXIT_FAILURE); } unpriv_sock = -1; if (Settings.unprivSockName) { /* Relax the umask for the unprivileged socket */ umask(000); unpriv_sock = make_listening_socket(Settings.unprivSockName, Settings.listenBacklog, 0); umask(file_umask); if (unpriv_sock < 0) { REPORT_FAILURE("Unable to create unprivileged listening socket"); if (pidfile) unlink(pidfile); if (lockfile) unlink(lockfile); exit(EXIT_FAILURE); } if(set_cloexec(unpriv_sock) < 0) { REPORT_FAILURE("Could not set FD_CLOEXEC option on socket"); if (pidfile) unlink(pidfile); if (lockfile) unlink(lockfile); exit(EXIT_FAILURE); } if (!EventTcp_CreateAcceptor(es, unpriv_sock, handleUnprivAccept)) { REPORT_FAILURE("Could not make accept() handler"); if (pidfile) unlink(pidfile); if (lockfile) unlink(lockfile); exit(EXIT_FAILURE); } } /* Ignore sigpipe */ signal(SIGPIPE, SIG_IGN); /* Set up pipe for signal handler for worker termination notification*/ if (pipe(Pipe) < 0) { REPORT_FAILURE("pipe() call failed"); if (pidfile) unlink(pidfile); if (lockfile) unlink(lockfile); exit(EXIT_FAILURE); } if((set_cloexec(Pipe[0]) < 0) || (set_cloexec(Pipe[1]) < 0)) { REPORT_FAILURE("Could not set FD_CLOEXEC option on pipe"); if (pidfile) unlink(pidfile); if (lockfile) unlink(lockfile); exit(EXIT_FAILURE); } /* Create event handler for pipe */ if (!Event_AddHandler(es, Pipe[0], EVENT_FLAG_READABLE, handlePipe, NULL)) { REPORT_FAILURE("Could not make pipe handler"); if (pidfile) unlink(pidfile); if (lockfile) unlink(lockfile); exit(EXIT_FAILURE); } /* Close files */ for (i=0; i= 0) { if(set_cloexec(sock) < 0) { syslog(LOG_ERR, "Could not set FD_CLOEXEC option on socket"); close(sock); } if (!EventTcp_CreateAcceptor(es, sock, handleMapAccept)) { syslog(LOG_ERR, "Could not listen for map requests: EventTcp_CreateAcceptor: %m"); close(sock); } } } /* Start the tick handler. All ticks start off at the same time, but should soon get out of sync. */ if (Settings.tick_interval > 0 && Settings.num_ticks > 0) { for (i=0; i 0) { buffer[n] = 0; if (buffer[n-1] == '\n') { buffer[n-1] = 0; } if(s == NULL) { syslog(LOG_ERR, "no data available for current worker"); return; } /* Heuristic... Perl spits this out, I think*/ if (strstr(buffer, "Out of memory!")) { s->oom = 1; } qid = s->qid; if (DOLOG) { /* Split lines into separate syslog calls */ char *str, *nl; str = buffer; while ( (nl = strchr(str, '\n')) ) { *nl = 0; if (*str) { if (qid && *qid) { syslog(LOG_INFO, "%s: Worker %d stderr: %s", qid, WORKERNO(s), str); } else { syslog(LOG_INFO, "Worker %d stderr: %s", WORKERNO(s), str); } } str = nl+1; } if (str && *str) { if (qid && *qid) { syslog(LOG_INFO, "%s: Worker %d stderr: %s", qid, WORKERNO(s), str); } else { syslog(LOG_INFO, "Worker %d stderr: %s", WORKERNO(s), str); } } } } if (n == 0 || (n < 0 && errno != EAGAIN)) { /* EOF or error reading stderr -- close it and cancel handler */ if (n < 0) { if (DOLOG) { qid = s->qid; if (qid && *qid) { syslog(LOG_WARNING, "%s: handleWorkerStderr: Error reading from worker %d's stderr: %m", s->qid, WORKERNO(s)); } else { syslog(LOG_WARNING, "handleWorkerStderr: Error reading from worker %d's stderr: %m", WORKERNO(s)); } } } if(s) { close(s->workerStderr); Event_DelHandler(s->es, s->errHandler); s->errHandler = NULL; s->workerStderr = -1; } } } /********************************************************************** * %FUNCTION: handleWorkerStatusFD * %ARGUMENTS: * es -- event selector * fd -- file descriptor ready for reading * flags -- flags from event-handling code * data -- pointer to worker * %RETURNS: * Nothing * %DESCRIPTION: * Handles a readable status descriptor. Reads data and sets * worker's "status tag" ***********************************************************************/ static void handleWorkerStatusFD(EventSelector *es, int fd, unsigned int flags, void *data) { Worker *s = (Worker *) data; char buffer[64]; int n; int changed = 0; char const *qid; while ( (n=read(fd, buffer, sizeof(buffer)-1)) > 0) { buffer[n] = 0; if (buffer[n-1] == '\n') { buffer[n-1] = 0; } if (update_worker_status(s, buffer)) { changed = 1; } } if (n == 0 || (n < 0 && errno != EAGAIN)) { /* EOF or error reading status FD -- close it and cancel handler */ if (n < 0) { if (DOLOG) { qid = s->qid; if (qid && *qid) { syslog(LOG_WARNING, "%s: handleWorkerStatusFD: Error reading from worker %d's status pipe: %m", s->qid, WORKERNO(s)); } else { syslog(LOG_WARNING, "handleWorkerStatusFD: Error reading from worker %d's status pipe: %m", WORKERNO(s)); } } } if (s != NULL) { close(s->workerStatusFD); Event_DelHandler(s->es, s->statusHandler); s->statusHandler = NULL; s->workerStatusFD = -1; } } if (changed) { if (s != NULL) { notify_worker_status(s->es, WORKERNO(s), s->status_tag); } } } /********************************************************************** * %FUNCTION: update_worker_status * %ARGUMENTS: * s -- a worker * buf -- a buffer of data read from worker's status descriptor * %RETURNS: * True if status was changed; false otherwise. * %DESCRIPTION: * Tucks away line in worker's status area. ***********************************************************************/ static int update_worker_status(Worker *s, char const *buf) { char const *ptr = buf; if (!ptr || !*ptr) { return 0; } while(ptr && *ptr) { /* Only update a busy worker's status -- these updates can come in AFTER worker has exited! */ if (s->state == STATE_BUSY) { snprintf(s->status_tag, sizeof(s->status_tag), "%s", ptr); s->status_tag[MAX_STATUS_LEN-1] = 0; percent_decode(s->status_tag); } /* Scan past next newline */ while (*ptr && *ptr != '\n') ++ptr; if (*ptr == '\n') { ++ptr; } else { return 1; } } return 1; } /********************************************************************** * %FUNCTION: cmd_to_number * %ARGUMENTS: * buf -- a command about to be sent to worker * %RETURNS: * An integer representing the command (SCAN, RELAYOK, SENDEROK or RECIPOK) * or -1 if it isn't any of those commands. ***********************************************************************/ static int cmd_to_number(char const *buf) { if (!strncmp(buf, "relayok ", 8)) { return RELAYOK_CMD; } else if (!strncmp(buf, "senderok ", 9)) { return SENDEROK_CMD; } else if (!strncmp(buf, "recipok ", 8)) { return RECIPOK_CMD; } else if (!strncmp(buf, "scan ", 5)) { return SCAN_CMD; } return OTHER_CMD; } /********************************************************************** * %FUNCTION: set_worker_status_from_command * %ARGUMENTS: * s -- a worker * buf -- a command about to be sent to worker * %RETURNS: * Nothing * %DESCRIPTION: * Sets status tag to command name plus first argument, if any. ***********************************************************************/ static void set_worker_status_from_command(Worker *s, char const *buf) { char const *ptr = buf; char *out = s->status_tag; int len = 0; int space = 0; s->cmd = cmd_to_number(buf); while (*ptr && (*ptr != '\n') && len < MAX_STATUS_LEN - 1) { char c = *ptr++; *out++ = c; len++; if (c == ' ') { space++; if (space == 2) { *(out-1) = 0; break; } } } *out = 0; /* If it was "recipok", set the domain appropriately if we have specified a limit on concurrent recipoks per domain */ if ((s->cmd == RECIPOK_CMD) && (Settings.maxRecipokPerDomain > 0)) { int len = 0; s->domain[0] = 0; out = s->domain; ptr = buf; while(*ptr && (*ptr != '@')) ptr++; if (*ptr == '@') { ptr++; while(*ptr && *ptr != '>' && *ptr != ' ') { *out++ = *ptr++; len++; if (len >= MAX_DOMAIN_LEN - 1) break; } *out = 0; } } percent_decode(s->status_tag); /* Sanitize tag -- ASCII-centric! */ out = s->status_tag; while (*out) { if (*out < ' ' || *out > '~') *out = ' '; ++out; } notify_worker_status(s->es, WORKERNO(s), s->status_tag); } /********************************************************************** * %FUNCTION: handleAccept * %ARGUMENTS: * es -- event selector * fd -- accepted connection * %RETURNS: * Nothing * %DESCRIPTION: * Handles a connection attempt from MIMEDefang. Sets up a command-reader. ***********************************************************************/ static void handleAccept(EventSelector *es, int fd) { if (!EventTcp_ReadBuf(es, fd, MAX_CMD_LEN, '\n', handleCommand, Settings.clientTimeout, 1, NULL)) { if (DOLOG) { syslog(LOG_ERR, "handleAccept: EventTcp_ReadBuf failed: %m"); } close(fd); } } /********************************************************************** * %FUNCTION: handleUnprivAccept * %ARGUMENTS: * es -- event selector * fd -- accepted connection * %RETURNS: * Nothing * %DESCRIPTION: * Handles a connection attempt on the *unprivileged* socket. ***********************************************************************/ static void handleUnprivAccept(EventSelector *es, int fd) { if (NumUnprivConnections >= MAX_UNPRIV_CONNS) { close(fd); return; } /* Non-null arg indicates unprivileged socket! */ if (!EventTcp_ReadBuf(es, fd, MAX_CMD_LEN, '\n', handleCommand, Settings.clientTimeout, 1, &Activations)) { if (DOLOG) { syslog(LOG_ERR, "handleAccept: EventTcp_ReadBuf failed: %m"); } close(fd); return; } NumUnprivConnections++; } /********************************************************************** * %FUNCTION: handleCommand * %ARGUMENTS: * es -- event selector * fd -- connection to MIMEDefang * buf -- buffer of data read from MIMEDefang * len -- amount of data read from MIMEDefang * flag -- flag from reader * data -- if non-NULL, command came from unprivileged socket -- restrict * what we can do. * %RETURNS: * Nothing * %DESCRIPTION: * Handles a MIMEDefang command. ***********************************************************************/ static void handleCommand(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { char answer[MAX_CMD_LEN]; if (data) { NumUnprivConnections--; } if (flag == EVENT_TCP_FLAG_TIMEOUT || flag == EVENT_TCP_FLAG_IOERROR) { if (DOLOG) { syslog(LOG_ERR, "handleCommand: Timeout or error: Flag = %d: %m", flag); } /* Client timeout or error */ close(fd); return; } /* Null-terminate buffer */ if (len) { len--; buf[len] = 0; } /* Remove cr so we can use telnet for unpriv socket */ if (len && (buf[len-1] == '\r')) { len--; buf[len] = 0; } if (len == 4 && !strcmp(buf, "help")) { if (data) { doHelp(es, fd, 1); } else { doHelp(es, fd, 0); } return; } if (len == 4 && !strcmp(buf, "free")) { snprintf(answer, sizeof(answer), "%d\n", NUM_FREE_WORKERS); reply_to_mimedefang(es, fd, answer); return; } if (len == 7 && !strcmp(buf, "version")) { reply_to_mimedefang(es, fd, VERSION); return; } /* "tick" command must ONLY be internally-generated to guarantee that two tick handlers won't be running simultaneously */ if (len == 4 && !strcmp(buf, "tick")) { snprintf(answer, sizeof(answer), "error: External agents may not invoke 'tick'\n"); reply_to_mimedefang(es, fd, answer); return; } if (len == 6 && !strcmp(buf, "status")) { doStatus(es, fd); return; } /* We have to keep the old command for backward-compatibility */ if (len == 6 && !strcmp(buf, "slaves")) { doWorkerReport(es, fd, 0); return; } if (len == 7 && !strcmp(buf, "workers")) { doWorkerReport(es, fd, 0); return; } /* We have to keep the old command for backward-compatibility */ if (len == 10 && !strcmp(buf, "busyslaves")) { doWorkerReport(es, fd, 1); return; } if (len == 11 && !strcmp(buf, "busyworkers")) { doWorkerReport(es, fd, 1); return; } if (len == 4 && !strcmp(buf, "load")) { doLoad(es, fd, SCAN_CMD); return; } if (len > 6 && !strncmp(buf, "load1 ", 6)) { int back; if (sscanf(buf+6, "%d", &back) != 1 || back < 10 || back > 600) { reply_to_mimedefang(es, fd, "error: Invalid 'back' amount (must be 10-600)\n"); return; } doLoad1(es, fd, back); return; } if (len == 12 && !strcmp(buf, "load-relayok")) { doLoad(es, fd, RELAYOK_CMD); return; } if (len == 13 && !strcmp(buf, "load-senderok")) { doLoad(es, fd, SENDEROK_CMD); return; } if (len == 12 && !strcmp(buf, "load-recipok")) { doLoad(es, fd, RECIPOK_CMD); return; } if (len == 5 && !strcmp(buf, "hload")) { doHourlyLoad(es, fd, SCAN_CMD); return; } if (len == 13 && !strcmp(buf, "hload-relayok")) { doHourlyLoad(es, fd, RELAYOK_CMD); return; } if (len == 14 && !strcmp(buf, "hload-senderok")) { doHourlyLoad(es, fd, SENDEROK_CMD); return; } if (len == 13 && !strcmp(buf, "hload-recipok")) { doHourlyLoad(es, fd, RECIPOK_CMD); return; } if (len == 5 && !strcmp(buf, "histo")) { doHistogram(es, fd); return; } if (len == 4 && !strcmp(buf, "msgs")) { snprintf(answer, sizeof(answer), "%d\n", NumMsgsProcessed); reply_to_mimedefang(es, fd, answer); return; } /* We have to keep the old command for backward-compatibility */ if (len > 10 && !strncmp(buf, "slaveinfo ", 10)) { doWorkerInfo(es, fd, buf); return; } if (len > 11 && !strncmp(buf, "workerinfo ", 11)) { doWorkerInfo(es, fd, buf); return; } /* This is an awful hack used by watch-multiple-mimedefangs.tcl. We handle it here so we don't have to waste a worker */ if (len == 19 && !strcmp(buf, "foo_no_such_command")) { reply_to_mimedefang(es, fd, "error: Unknown command\n"); return; } /* Remaining commands are privileged */ if (data) { reply_to_mimedefang(es, fd, "error: Attempt to use privileged command on unprivileged socket\n"); return; } if (len == 6 && !strcmp(buf, "reread")) { newGeneration(); notify_listeners(es, "R\n"); #ifndef SAFE_EMBED_PERL if (Settings.useEmbeddedPerl) { reply_to_mimedefang(es, fd, "Cannot destroy and recreate an embedded Perl interpreter safely on this platform. Filter rules will NOT be reread\n"); return; } #endif reply_to_mimedefang(es, fd, "Forced reread of filter rules\n"); return; } if (len > 5 && !strncmp(buf, "scan ", 5)) { doScan(es, fd, buf); return; } /* Any command other than "scan" is handled generically. */ doWorkerCommand(es, fd, buf); return; } static int worker_request_age(Worker *s) { if (s->firstReqTime == (time_t) -1) { return -1; } return (int) (time(NULL) - s->firstReqTime); } static int worker_age(Worker *s) { if (s->activationTime == (time_t) -1) { return -1; } return (int) (time(NULL) - s->activationTime); } /********************************************************************** * %FUNCTION: doWorkerInfo * %ARGUMENTS: * es -- event selector * fd -- connection to MIMEDefang * cmd -- workerinfo command * %RETURNS: * Nothing * %DESCRIPTION: * Returns detailed information about a specific worker ***********************************************************************/ static void doWorkerInfo(EventSelector *es, int fd, char *cmd) { int workerno; char buf[1024]; Worker *s; if (sscanf(cmd+10, "%d", &workerno) != 1) { reply_to_mimedefang(es, fd, "error: Invalid worker number\n"); return; } if (workerno < 0 || workerno >= Settings.maxWorkers) { reply_to_mimedefang(es, fd, "error: Worker number out of range\n"); return; } s = &AllWorkers[workerno]; snprintf(buf, sizeof(buf), "Worker %d\nState %s\nPID %d\nNumRequests %d\nNumScans %d\nAge %d\nFirstReqAge %d\nLastStateChangeAge %d\nStatusTag %s\n", workerno, state_name(s->state), (int) s->pid, s->numRequests, s->numScans, worker_age(s), worker_request_age(s), (int) (time(NULL) - s->lastStateChange), s->status_tag); reply_to_mimedefang(es, fd, buf); } /********************************************************************** * %FUNCTION: doScan * %ARGUMENTS: * es -- event selector * fd -- connection to MIMEDefang * cmd -- scan command * %RETURNS: * Nothing * %DESCRIPTION: * Sends the scan command to the worker and arranges for answer to be * sent back when scanning is complete. ***********************************************************************/ static void doScan(EventSelector *es, int fd, char *cmd) { int len; /* Add newline back to command */ len = strlen(cmd); if (len < MAX_CMD_LEN-1) { cmd[len+1] = 0; cmd[len] = '\n'; } else { char *answer = "error: Command too long\n"; reply_to_mimedefang(es, fd, answer); if (DOLOG) { syslog(LOG_DEBUG, "doScan: Command too long"); } return; } doScanAux(es, fd, cmd, 1); } static void doScanAux(EventSelector *es, int fd, char *cmd, int queueable) { Worker *s; /* Find a free worker */ s = findFreeWorker(SCAN_CMD); if (!s) { char *answer = "error: No free workers\n"; if (queueable && Settings.requestQueueSize > 0) { if (queue_request(es, fd, cmd)) { /* Successfully queued */ return; } } if (DOLOG) { syslog(LOG_WARNING, "No free workers"); } reply_to_mimedefang(es, fd, answer); return; } if (activateWorker(s, "About to perform scan") == (pid_t) -1) { char *answer = "error: Unable to activate worker\n"; if (DOLOG) { syslog(LOG_ERR, "Unable to activate worker %d", WORKERNO(s)); } reply_to_mimedefang(es, fd, answer); return; } /* Put the worker on the busy list */ putOnList(s, STATE_BUSY); /* Set last_cmd field */ s->last_cmd = SCAN_CMD; /* Update worker status */ set_worker_status_from_command(s, cmd); /* Set worker's clientFD so we can reply */ s->clientFD = fd; /* Set worker's queue ID and working directory */ sscanf(cmd, "scan %" STR(MAX_QID_LEN) "s %" STR(MAX_DIR_LEN) "s", s->qid, s->workdir); s->workdir[MAX_DIR_LEN] = 0; s->qid[MAX_QID_LEN] = 0; /* Set worker's start-of-command time */ gettimeofday(&(s->start_cmd), NULL); /* And tell the worker to go ahead... */ s->event = EventTcp_WriteBuf(es, s->workerStdin, cmd, strlen(cmd), handleWorkerReceivedCommand, Settings.clientTimeout, s); if (!s->event) { if (DOLOG) syslog(LOG_ERR, "doScan: EventTcp_WriteBuf failed: %m"); killWorker(s, "EventTcp_WriteBuf failed"); } else { statsLog("StartFilter", WORKERNO(s), NULL); } } /********************************************************************** * %FUNCTION: doWorkerCommand * %ARGUMENTS: * es -- event selector * fd -- connection to MIMEDefang * cmd -- command * %RETURNS: * Nothing * %DESCRIPTION: * Sends the command to the worker and arranges for answer to be * sent back when command finishes. ***********************************************************************/ static void doWorkerCommand(EventSelector *es, int fd, char *cmd) { int len; /* Add newline back to command */ len = strlen(cmd); if (len < MAX_CMD_LEN-1) { cmd[len+1] = 0; cmd[len] = '\n'; } else { char *answer = "error: Command too long\n"; reply_to_mimedefang(es, fd, answer); if (DOLOG) { syslog(LOG_DEBUG, "doWorkerCommand: Command too long"); } return; } doWorkerCommandAux(es, fd, cmd, 1); } static int at_recipok_limit(char *cmd) { char domain_buf[MAX_DOMAIN_LEN]; char const *ptr = cmd; char *out = domain_buf; int len = 0; int count = 0; Worker *s; while(*ptr && (*ptr != '@')) ptr++; /* No domain? Punt! */ if (*ptr != '@') return 0; ptr++; while(*ptr && *ptr != '>' && *ptr != ' ') { len++; *out++ = *ptr++; if (len >= MAX_DOMAIN_LEN - 1) break; } *out = 0; /* Search active workers doing recipok */ s = Workers[STATE_BUSY]; while(s) { if (s->cmd == RECIPOK_CMD && !strcasecmp(s->domain, domain_buf)) { count++; if (count >= Settings.maxRecipokPerDomain) { if (DOLOG) { syslog(LOG_WARNING, "Hit per-domain recipok limit (%d) for domain %s", Settings.maxRecipokPerDomain, domain_buf); } return 1; } } s = s->next; } return 0; } static void doWorkerCommandAux(EventSelector *es, int fd, char *cmd, int queueable) { Worker *s; char reason[200]; int cmdno; sprintf(reason, "About to execute command '%.100s'", cmd); cmdno = cmd_to_number(cmd); /* If cmdno is RECIPOK_CMD, make sure we are not at per-domain limit */ if ((cmdno == RECIPOK_CMD) && (Settings.maxRecipokPerDomain > 0)) { if (at_recipok_limit(cmd)) { /* TODO: Should we queue? Can't right now because queue-removal logic knows nothing about per-domain recipok limit. */ reply_to_mimedefang(es, fd, "ok -1 Per-domain%20recipok%20limit%20hit;%20please%20try%20again%20later\n"); return; } } /* Find a free worker */ s = findFreeWorker(cmdno); if (!s) { char *answer = "error: No free workers\n"; if (queueable && Settings.requestQueueSize > 0) { if (queue_request(es, fd, cmd)) { /* Successfully queued */ return; } } if (DOLOG) { syslog(LOG_WARNING, "No free workers"); } reply_to_mimedefang(es, fd, answer); return; } if (activateWorker(s, reason) == (pid_t) -1) { char *answer = "error: Unable to activate worker\n"; if (DOLOG) { syslog(LOG_ERR, "Unable to activate worker %d", WORKERNO(s)); } reply_to_mimedefang(es, fd, answer); return; } /* Put the worker on the busy list */ putOnList(s, STATE_BUSY); /* Update last_cmd */ if (cmdno >= 0) { s->last_cmd = cmdno; } /* Update worker's status tag */ set_worker_status_from_command(s, cmd); /* Set worker's clientFD so we can reply */ s->clientFD = fd; /* Null workdir signals not to log EndFilter event */ s->workdir[0] = 0; /* Set the qid */ switch(cmdno) { case SENDEROK_CMD: /* senderok sender ip name helo dir qid */ sscanf(cmd, "senderok %*s %*s %*s %*s %*s %" STR(MAX_QID_LEN) "s", s->qid); s->qid[MAX_QID_LEN] = 0; break; case RECIPOK_CMD: /* recipok recipient sender ip name firstrecip helo dir qid junk */ sscanf(cmd, "recipok %*s %*s %*s %*s %*s %*s %*s %" STR(MAX_QID_LEN) "s", s->qid); s->qid[MAX_QID_LEN] = 0; break; default: s->qid[0] = 0; break; } /* Set worker's start-of-command time */ gettimeofday(&(s->start_cmd), NULL); /* And tell the worker to go ahead... */ s->event = EventTcp_WriteBuf(es, s->workerStdin, cmd, strlen(cmd), handleWorkerReceivedCommand, Settings.clientTimeout, s); if (!s->event) { if (DOLOG) syslog(LOG_ERR, "doWorkerCommand: EventTcp_WriteBuf failed: %m"); killWorker(s, "EventTcp_WriteBuf failed"); } } /********************************************************************** * %FUNCTION: handleWorkerReceivedCommand * %ARGUMENTS: * es -- event selector * fd -- connection to MIMEDefang * buf -- buffer of data read from MIMEDefang * len -- amount of data read from MIMEDefang * flag -- flag from reader * data -- the worker * %RETURNS: * Nothing * %DESCRIPTION: * Called when command has been written to worker. Sets up an event handler * to read the worker's reply ***********************************************************************/ static void handleWorkerReceivedCommand(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { Worker *s = (Worker *) data; /* Event was triggered */ s->event = NULL; if (flag == EVENT_TCP_FLAG_TIMEOUT || flag == EVENT_TCP_FLAG_IOERROR) { /* Error writing to worker */ char *answer = "error: Error talking to worker process\n"; if (DOLOG) { syslog(LOG_ERR, "handleWorkerReceivedCommand(%d): Timeout or error: Flag = %d: %m", WORKERNO(s), flag); } reply_to_mimedefang(es, s->clientFD, answer); /* The reply_to_mimedefang will close clientFD when it's done */ s->clientFD = -1; /* Kill the worker process */ killWorker(s, "Error talking to worker process"); return; } /* Worker has been given the command; now wait for it to reply */ s->event = EventTcp_ReadBuf(es, s->workerStdout, MAX_CMD_LEN, '\n', handleWorkerReceivedAnswer, Settings.busyTimeout, 1, s); if (!s->event) { if (DOLOG) syslog(LOG_ERR, "handleWorkerReceivedCommand: EventTcp_ReadBuf failed: %m"); killWorker(s, "EventTcp_ReadBuf failed"); } } /********************************************************************** * %FUNCTION: handleWorkerReceivedAnswer * %ARGUMENTS: * es -- event selector * fd -- connection to MIMEDefang * buf -- buffer of data read from MIMEDefang * len -- amount of data read from MIMEDefang * flag -- flag from reader * data -- the worker * %RETURNS: * Nothing * %DESCRIPTION: * Called when the worker's answer comes back. ***********************************************************************/ static void handleWorkerReceivedAnswer(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { Worker *s = (Worker *) data; struct timeval now; HistoryBucket *b; /* Event was triggered */ s->event = NULL; /* If nothing was received from worker, send error message back */ if (!len || (flag == EVENT_TCP_FLAG_TIMEOUT)) { if (flag == EVENT_TCP_FLAG_TIMEOUT) { /* Heuristic... */ if (WorkerCount[STATE_BUSY] > 3) { reply_to_mimedefang(es, s->clientFD, "ERR Filter timed out - system may be overloaded " "(consider increasing busy timeout)\n"); /* The reply_to_mimedefang will close clientFD when it's done */ s->clientFD = -1; } else { reply_to_mimedefang(es, s->clientFD, "ERR Filter timed out - check filter rules or system load\n"); /* The reply_to_mimedefang will close clientFD when it's done */ s->clientFD = -1; } } else { if (DOLOG) { if (s->oom) { syslog(LOG_ERR, "Worker %d ran out of memory -- possible DoS attack due to complex MIME?", WORKERNO(s)); } else { syslog(LOG_ERR, "Worker %d died prematurely -- check your filter rules", WORKERNO(s)); } } else { if (s->oom) { syslog(LOG_ERR, "Worker %d ran out of memory -- possible DoS attack due to complex MIME?", WORKERNO(s)); } else { syslog(LOG_ERR, "Worker %d died prematurely -- check your filter rules and use the '-l' flag on mimedefang-multiplexor to see Perl error messages", WORKERNO(s)); } } reply_to_mimedefang(es, s->clientFD, "ERR No response from worker\n"); /* The reply_to_mimedefang will close clientFD when it's done */ s->clientFD = -1; } } else { /* Write the worker's answer back to the client */ reply_to_mimedefang_with_len(es, s->clientFD, buf, len); /* The reply_to_mimedefang will close clientFD when it's done */ s->clientFD = -1; } s->numRequests++; if (s->cmd >= 0 && s->cmd < NUM_CMDS) { long sec_diff, usec_diff; int ms; /* Calculate how many milliseconds the command took */ gettimeofday(&now, NULL); sec_diff = now.tv_sec - s->start_cmd.tv_sec; usec_diff = now.tv_usec - s->start_cmd.tv_usec; if (usec_diff < 0) { usec_diff += 1000000; sec_diff--; } ms = (int) (sec_diff * 1000 + usec_diff / 1000); b = get_history_bucket(s->cmd); b->count++; b->workers += WorkerCount[STATE_BUSY]; b->ms += ms; b = get_hourly_history_bucket(s->cmd); b->count++; b->workers += WorkerCount[STATE_BUSY]; b->ms += ms; /* Only increment NumMsgsProcessed for a "scan" command */ if (s->cmd == SCAN_CMD) { s->numScans++; NumMsgsProcessed++; } } /* If we had a busy timeout, kill the worker */ if (flag == EVENT_TCP_FLAG_TIMEOUT) { notify_listeners(es, "B\n"); killWorker(s, "Busy timeout"); } else { /* Put worker on free list */ putOnList(s, STATE_IDLE); /* Nuke cmd field, just in case */ s->cmd = -1; /* Record time when this worker became idle */ s->idleTime = time(NULL); /* Check worker for expiry */ checkWorkerForExpiry(s); } if (s->workdir[0]) { statsLog("EndFilter", WORKERNO(s), "numRequests=%d", s->numRequests); } } /********************************************************************** * %FUNCTION: unlinkFromList * %ARGUMENTS: * s -- a worker * %RETURNS: * Nothing * %DESCRIPTION: * Unlinks "s" from the list it is currently on. ***********************************************************************/ static void unlinkFromList(Worker *s) { int state = s->state; Worker *prev = NULL; if (!Workers[state]) { /* Panic... worker not on this list */ syslog(LOG_CRIT, "%s worker %d not found on its list!", state_name(s->state), WORKERNO(s)); return; } /* If it's the first on the list, simple */ if (Workers[state] == s) { Workers[state] = s->next; s->next = NULL; return; } /* Somewhere in the middle of the list */ prev = Workers[state]; while (prev->next != s) { if (!prev->next) { /* Panic... worker not on this list */ syslog(LOG_CRIT, "%s worker %d not found on its list!", state_name(s->state), WORKERNO(s)); return; } prev = prev->next; } prev->next = s->next; s->next = NULL; } /********************************************************************** * %FUNCTION: putOnList * %ARGUMENTS: * s -- a worker * state -- state to change to. * %RETURNS: * Nothing * %DESCRIPTION: * Sets s->state to state, and moves s to the Workers[state] list. ***********************************************************************/ static void putOnList(Worker *s, int state) { s->status_tag[0] = 0; if (s->state == state) { /* Already there, nothing to do */ return; } notify_worker_state_change(s->es, WORKERNO(s), state_name(s->state), state_name(state)); /* Adjust counts */ WorkerCount[s->state]--; WorkerCount[state]++; unlinkFromList(s); s->next = Workers[state]; Workers[state] = s; s->state = state; s->lastStateChange = time(NULL); /* Update busy histogram if worker was made busy */ if (state == STATE_BUSY) { AllWorkers[WorkerCount[STATE_BUSY]-1].histo++; /* Also update firstReqTime */ if (s->firstReqTime == (time_t) -1) { s->firstReqTime = s->lastStateChange; } } /* Notify listeners */ if (Settings.notifySock && s->es && Old_NumFreeWorkers != NUM_FREE_WORKERS) { char msg[65]; sprintf(msg, "F %d\n", NUM_FREE_WORKERS); notify_listeners(s->es, msg); } /* If we went to zero, notify with a Z message */ if (NUM_FREE_WORKERS == 0) { notify_listeners(s->es, "Z\n"); } if (Old_NumFreeWorkers == 0 && NUM_FREE_WORKERS != 0) { /* Went from zero to one free worker, notify with a Y message */ notify_listeners(s->es, "Y\n"); } Old_NumFreeWorkers = NUM_FREE_WORKERS; } /********************************************************************** * %FUNCTION: activateWorker * %ARGUMENTS: * s -- a worker * reason -- reason worker is being activated * %RETURNS: * The process-ID of the worker * %DESCRIPTION: * Activates the worker if it is not currently associated with a running * process. ***********************************************************************/ static pid_t activateWorker(Worker *s, char const *reason) { int pin[2], pout[2], perr[2], pstatus[2]; char const *pname; int i; time_t now = (time_t) 0; /* Avoid compiler warning by initializing */ sigset_t sigs; char *sarg; /* Check if it's already active */ if (s->state == STATE_BUSY || s->state == STATE_IDLE) { if (s->pid == (pid_t) -1) { if (DOLOG) { syslog(LOG_ERR, "Argh!!! Worker %d in state %s has pid of -1! Internal error!", WORKERNO(s), state_name(s->state)); putOnList(s, STATE_STOPPED); } } else { return s->pid; } } /* Check if enough time has elapsed */ if (Settings.waitTime) { now = time(NULL); if (LastWorkerActivation && (((int) (now - LastWorkerActivation)) < Settings.waitTime)) { if (DOLOG) { syslog(LOG_DEBUG, "Did not start worker %d: Not enough time elapsed since last worker activation", WORKERNO(s)); } return -1; } } /* Set up pipes */ if (pipe(pin) < 0) { if (DOLOG) syslog(LOG_ERR, "Could not start worker %d: pipe failed: %m", WORKERNO(s)); return -1; } if (pipe(pout) < 0) { if (DOLOG) syslog(LOG_ERR, "Could not start worker %d: pipe failed: %m", WORKERNO(s)); close(pin[0]); close(pin[1]); return -1; } if (pipe(perr) < 0) { if (DOLOG) syslog(LOG_ERR, "Could not start worker %d: pipe failed: %m", WORKERNO(s)); close(pin[0]); close(pin[1]); close(pout[0]); close(pout[1]); return -1; } pstatus[0] = -1; pstatus[1] = -1; if (Settings.wantStatusReports) { if (pipe(pstatus) < 0) { if (DOLOG) syslog(LOG_ERR, "Could not start worker %d: pipe failed: %m", WORKERNO(s)); close(pin[0]); close(pin[1]); close(pout[0]); close(pout[1]); close(perr[0]); close(perr[1]); return -1; } } /* fork and exec */ s->pid = fork(); if (s->pid == (pid_t) -1) { if (DOLOG) syslog(LOG_ERR, "Could not start worker %d: fork failed: %m", WORKERNO(s)); close(pin[0]); close(pin[1]); close(pout[0]); close(pout[1]); close(perr[0]); close(perr[1]); if (pstatus[0] >= 0) close(pstatus[0]); if (pstatus[1] >= 0) close(pstatus[1]); return s->pid; /* Fork failed */ } if (s->pid) { HistoryBucket *b; putOnList(s, STATE_IDLE); /* Record time when this worker became idle */ s->idleTime = time(NULL); /* Record activation time (just copy instead of calling time()) */ s->activationTime = s->idleTime; /* Track activations in history */ b = get_history_bucket(SCAN_CMD); b->activated++; /* In the parent -- return */ close(pin[0]); close(pout[1]); close(perr[1]); if (pstatus[1] >= 0) close(pstatus[1]); s->workerStdin = pin[1]; s->workerStdout = pout[0]; s->workerStderr = perr[0]; s->workerStatusFD = pstatus[0]; s->activated = Activations++; /* Make worker stderr non-blocking */ if (set_nonblocking(s->workerStderr) < 0) { syslog(LOG_ERR, "Could not make worker %d's stderr non-blocking: %m", WORKERNO(s)); } /* Handle anything written to worker's stderr */ s->errHandler = Event_AddHandler(s->es, s->workerStderr, EVENT_FLAG_READABLE, handleWorkerStderr, s); /* Handle anything written to status descriptor */ if (Settings.wantStatusReports) { if (set_nonblocking(s->workerStatusFD) < 0) { syslog(LOG_ERR, "Could not make worker %d's status descriptor non-blocking: %m", WORKERNO(s)); } s->statusHandler = Event_AddHandler(s->es, s->workerStatusFD, EVENT_FLAG_READABLE, handleWorkerStatusFD, s); } else { s->statusHandler = NULL; } s->clientFD = -1; s->numRequests = 0; s->numScans = 0; s->oom = 0; s->generation = Generation; s->last_cmd = NO_CMD; if (DOLOG) { syslog(LOG_INFO, "Starting worker %d (pid %lu) (%d running): %s", WORKERNO(s), (unsigned long) s->pid, NUM_RUNNING_WORKERS, reason); } statsLog("StartWorker", WORKERNO(s), "reason=\"%s\"", reason); if (Settings.waitTime) { LastWorkerActivation = now; } return s->pid; } /* In the child */ /* Reset signal-handling dispositions */ signal(SIGTERM, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); sigemptyset(&sigs); sigprocmask(SIG_SETMASK, &sigs, NULL); /* Set resource limits */ #ifdef HAVE_SETRLIMIT limit_mem_usage(Settings.maxRSS, Settings.maxAS); #endif /* Close unneeded file descriptors */ closelog(); close(pin[1]); close(pout[0]); close(perr[0]); dup2(pin[0], STDIN_FILENO); dup2(pout[1], STDOUT_FILENO); dup2(perr[1], STDERR_FILENO); if (pin[0] != STDIN_FILENO) close(pin[0]); if (pout[1] != STDOUT_FILENO) close(pout[1]); if (perr[1] != STDERR_FILENO) close(perr[1]); if (Settings.wantStatusReports) { dup2(pstatus[1], STDERR_FILENO+1); if (pstatus[1] != STDERR_FILENO+1) close(pstatus[1]); } if (!Settings.wantStatusReports) (void) close(STDERR_FILENO+1); for (i=STDERR_FILENO+2; i<1024; i++) { (void) close(i); } pname = strrchr(Settings.progPath, '/'); if (pname) { pname++; } else { pname = Settings.progPath; } #ifdef EMBED_PERL if (Settings.useEmbeddedPerl) { run_embedded_filter(); term_embedded_interpreter(); deinit_embedded_interpreter(); exit(EXIT_SUCCESS); } #endif if (Settings.wantStatusReports) { sarg = "-serveru"; } else { sarg = "-server"; } if (Settings.subFilter) { execl(Settings.progPath, pname, "-f", Settings.subFilter, sarg, NULL); } else { execl(Settings.progPath, pname, sarg, NULL); } _exit(EXIT_FAILURE); } /********************************************************************** * %FUNCTION: checkWorkerForExpiry * %ARGUMENTS: * s -- a worker to check * %RETURNS: * Nothing * %DESCRIPTION: * If the worker has served too many requests, it is killed. ***********************************************************************/ static void checkWorkerForExpiry(Worker *s) { /* If there is a queued request, don't terminate worker just yet. Allow it to go up to triple maxRequests. Yes, this is a horrible hack. */ if (s->numRequests < Settings.maxRequests * 3) { if (handle_queued_request()) { return; } } if (s->numRequests >= Settings.maxRequests) { char reason[200]; snprintf(reason, sizeof(reason), "Worker has processed %d requests", s->numRequests); killWorker(s, reason); } else if (Settings.maxLifetime > 0 && worker_request_age(s) > Settings.maxLifetime) { char reason[200]; snprintf(reason, sizeof(reason), "Worker has exceeded maximum lifetime of %d seconds", Settings.maxLifetime); killWorker(s, reason); } else if (s->generation < Generation) { killWorker(s, "New generation -- forcing reread of filter rules"); } } /********************************************************************** * %FUNCTION: killWorker * %ARGUMENTS: * s -- a worker to kill * reason -- reason worker is being killed * %RETURNS: * Nothing * %DESCRIPTION: * Kills a worker by closing its pipe. The worker then has 10 seconds * to clean up and exit before we send SIGTERM ***********************************************************************/ static void killWorker(Worker *s, char const *reason) { struct timeval t; int withPrejudice = 0; int age = worker_age(s); int req_age = worker_request_age(s); if (s->state != STATE_KILLED) { if (DOLOG) syslog(LOG_INFO, "Killing %s worker %d (pid %lu) req=%d age=%d req_age=%d: %s", state_name_lc(s->state), WORKERNO(s), (unsigned long) s->pid, s->numRequests, age, req_age, reason); /* In case, for some weird reason, the worker has stopped... */ kill(s->pid, SIGCONT); /* If worker is busy, we kill it with prejudice */ if (s->state == STATE_BUSY) { withPrejudice = 1; kill(s->pid, SIGTERM); } putOnList(s, STATE_KILLED); /* Close stdin so worker sees EOF */ close(s->workerStdin); s->workerStdin = -1; /* Kill any pending event */ if (s->event) { EventTcp_CancelPending(s->event); s->event = NULL; } /* Set up a timer to send SIGTERM if worker doesn't exit in 10 seconds */ t.tv_sec = 10; t.tv_usec = 0; if (withPrejudice) { s->termHandler = Event_AddTimerHandler(s->es, t, nukeWorker, (void *) s); } else { s->termHandler = Event_AddTimerHandler(s->es, t, terminateWorker, (void *) s); } statsLog("KillWorker", WORKERNO(s), "req=%d age=%d reason=\"%s\"", s->numRequests, age, reason); } } /********************************************************************** * %FUNCTION: terminateWorker * %ARGUMENTS: * es -- Event selector * fd -- not used * flags -- ignored * data -- the worker to terminate * %RETURNS: * Nothing * %DESCRIPTION: * Sends a SIGTERM to worker, because it's taken too long to exit. ***********************************************************************/ static void terminateWorker(EventSelector *es, int fd, unsigned int flags, void *data) { struct timeval t; Worker *s = (Worker *) data; s->termHandler = NULL; if (s->pid != (pid_t) -1) { if (DOLOG) { syslog(LOG_INFO, "Worker %d (pid %lu) taking too long to exit; sending SIGTERM", WORKERNO(s), (unsigned long) s->pid); } kill(s->pid, SIGCONT); kill(s->pid, SIGTERM); /* Set up a timer to send SIGKILL if worker doesn't exit in 10 seconds */ t.tv_sec = 10; t.tv_usec = 0; s->termHandler = Event_AddTimerHandler(s->es, t, nukeWorker, (void *) s); } } /********************************************************************** * %FUNCTION: nukeWorker * %ARGUMENTS: * es -- Event selector * fd -- not used * flags -- ignored * data -- the worker to nuke * %RETURNS: * Nothing * %DESCRIPTION: * Sends a SIGKILL to worker, because it's taken too long to exit, in spite * of getting a SIGTERM ***********************************************************************/ static void nukeWorker(EventSelector *es, int fd, unsigned int flags, void *data) { Worker *s = (Worker *) data; s->termHandler = NULL; if (s->pid != (pid_t) -1) { if (DOLOG) { syslog(LOG_INFO, "Worker %d (pid %lu) taking way too long to exit; sending SIGKILL", WORKERNO(s), (unsigned long) s->pid); } kill(s->pid, SIGCONT); kill(s->pid, SIGKILL); } } /********************************************************************** *%FUNCTION: childHandler *%ARGUMENTS: * sig -- signal number *%RETURNS: * Nothing *%DESCRIPTION: * Called by SIGCHLD. Writes 'C' to Pipe to wake up the select * loop and cause reaping of dead sessions ***********************************************************************/ static void childHandler(int sig) { char byte = 'C'; if (!ReapPending) { ReapPending = 1; if (!CharPending) { int errno_save = errno; write(Pipe[1], &byte, 1); errno = errno_save; CharPending = 1;; } } } /********************************************************************** *%FUNCTION: hupHandler *%ARGUMENTS: * sig -- signal number *%RETURNS: * Nothing *%DESCRIPTION: * Called by SIGHUP. Writes 'H' to Pipe to wake up the select * loop and cause closing and reopening of stats descriptor ***********************************************************************/ static void hupHandler(int sig) { char byte = 'H'; if (!HupPending) { HupPending = 1; if (!CharPending) { int errno_save = errno; write(Pipe[1], &byte, 1); errno = errno_save; CharPending = 1;; } } } /********************************************************************** *%FUNCTION: intHandler *%ARGUMENTS: * sig -- signal number *%RETURNS: * Nothing *%DESCRIPTION: * Called by SIGINT. Writes 'I' to Pipe to wake up the select * loop and cause closing and reopening of stats descriptor ***********************************************************************/ static void intHandler(int sig) { char byte = 'I'; if (!IntPending) { IntPending = 1; if (!CharPending) { int errno_save = errno; write(Pipe[1], &byte, 1); errno = errno_save; CharPending = 1;; } } } /********************************************************************** * %FUNCTION: handlePipe * %ARGUMENTS: * es -- event selector (ignored) * fd -- file descriptor which is readable * flags -- ignored * data -- ignored * %RETURNS: * Nothing * %DESCRIPTION: * Simply reads from the pipe; then reaps and/or closes and reopens * stats file. ***********************************************************************/ static void handlePipe(EventSelector *es, int fd, unsigned int flags, void *data) { char buf[64]; sigset_t sigs, osigs; int n; int doreap, dohup, doint; /* The read and the reset of CharPending must be atomic with respect to signal handlers. */ sigemptyset(&sigs); sigaddset(&sigs, SIGCHLD); sigaddset(&sigs, SIGINT); sigaddset(&sigs, SIGHUP); n = sigprocmask(SIG_BLOCK, &sigs, &osigs); if (n < 0) { syslog(LOG_ERR, "sigprocmask failed: %m"); } /* Begin atomic portion */ read(fd, buf, 64); doreap = ReapPending; doint = IntPending; dohup = HupPending; CharPending = 0; ReapPending = 0; IntPending = 0; HupPending = 0; /* End atomic portion */ /* Reset signal mask */ if (n >= 0) { sigprocmask(SIG_SETMASK, &osigs, NULL); } if (doreap) { reapTerminatedWorkers(0); /* Activate new workers if we've fallen below minimum */ if (NUM_RUNNING_WORKERS < Settings.minWorkers) { scheduleBringWorkersUpToMin(es); } } if (dohup) { statsReopenFile(); } if (doint) { newGeneration(); notify_listeners(es, "R\n"); } } /********************************************************************** * %FUNCTION: reapTerminatedWorkers * %ARGUMENTS: * killed -- If true, multiplexor was killed and has killed all workers * %RETURNS: * Nothing * %DESCRIPTION: * Reaps all terminated workers ***********************************************************************/ static void reapTerminatedWorkers(int killed) { pid_t pid; int status; Worker *s; int oldstate; HistoryBucket *b; #ifdef HAVE_WAIT3 struct rusage resource; while ((pid = wait3(&status, WNOHANG, &resource)) > 0) { #else while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { #endif s = findWorkerByPid(pid); if (!s) continue; oldstate = s->state; if (killed) { s->state = STATE_KILLED; } logWorkerReaped(s, status); if (killed) { s->state = oldstate; } #ifdef HAVE_WAIT3 log_worker_resource_usage(s, &resource); #endif s->pid = (pid_t) -1; s->activationTime = (time_t) -1; s->firstReqTime = (time_t) -1; shutDescriptors(s); putOnList(s, STATE_STOPPED); statsLog("ReapWorker", WORKERNO(s), NULL); b = get_history_bucket(SCAN_CMD); b->reaped++; } } /********************************************************************** * %FUNCTION: shutDescriptors * %ARGUMENTS: * s -- a worker * %RETURNS: * Nothing * %DESCRIPTION: * Closes worker's descriptors if necessary ***********************************************************************/ static void shutDescriptors(Worker *s) { char buffer[64]; int n; if (s->workerStdin >= 0) { close(s->workerStdin); s->workerStdin = -1; } if (s->workerStdout >= 0) { close(s->workerStdout); s->workerStdout = -1; } if (s->workerStderr >= 0) { Event_DelHandler(s->es, s->errHandler); s->errHandler = NULL; /* Consume and log any error messages */ while( (n=read(s->workerStderr, buffer, sizeof(buffer)-1)) > 0) { buffer[n] = 0; if (buffer[n-1] == '\n') { buffer[n-1] = 0; } if (DOLOG) { syslog(LOG_INFO, "Worker %d stderr: %s", WORKERNO(s), buffer); } } close(s->workerStderr); s->workerStderr = -1; } if (s->workerStatusFD >= 0) { close(s->workerStatusFD); Event_DelHandler(s->es, s->statusHandler); s->statusHandler = NULL; s->workerStatusFD = -1; } if (s->termHandler) { Event_DelHandler(s->es, s->termHandler); s->termHandler = NULL; } if (s->event) { EventTcp_CancelPending(s->event); s->event = NULL; } if (s->clientFD >= 0) { close(s->clientFD); s->clientFD = -1; } } /********************************************************************** * %FUNCTION: handleIdleTimeout * %ARGUMENTS: * es -- event selector * fd, flags, data -- ignored * %RETURNS: * Nothing * %DESCRIPTION: * Called periodically to check for idle workers that can be killed ***********************************************************************/ static void handleIdleTimeout(EventSelector *es, int fd, unsigned int flags, void *data) { time_t now = time(NULL); struct timeval t; /* Kill idle workers which have been idle for maxIdleTime or alive for more than maxLifetime*/ Worker *s = Workers[STATE_IDLE]; Worker *next; int numAlive = NUM_RUNNING_WORKERS; /* First pass: Kill workers that have exceeded their * lifetimes */ while(s) { next = s->next; if (Settings.maxLifetime > 0 && worker_request_age(s) > Settings.maxLifetime) { char reason[200]; snprintf(reason, sizeof(reason), "Worker has exceeded maximum lifetime of %d seconds", Settings.maxLifetime); numAlive--; killWorker(s, reason); } s = next; } /* Next pass: Kill workers that have been idle for too long */ s = Workers[STATE_IDLE]; while(s && numAlive > Settings.minWorkers) { next = s->next; if ((unsigned long) now - (unsigned long) s->idleTime >= Settings.maxIdleTime) { numAlive--; killWorker(s, "Idle timeout"); } s = next; } /* If there are fewer running workers than Settings.minWorkers, then start some more. */ if (numAlive < Settings.minWorkers) { scheduleBringWorkersUpToMin(es); } /* Reschedule timer */ t.tv_usec = 0; t.tv_sec = Settings.maxIdleTime; Event_AddTimerHandler(es, t, handleIdleTimeout, NULL); } /********************************************************************** * %FUNCTION: doStatusLog * %ARGUMENTS: * es -- event selector * fd, flags, data -- ignored * %RETURNS: * Nothing * %DESCRIPTION: * Called periodically to log worker status (numRunning, etc.) ***********************************************************************/ static void doStatusLog(EventSelector *es, int fd, unsigned int flags, void *data) { struct timeval t; syslog(LOG_INFO, "Worker status: Stopped=%d Idle=%d Busy=%d Killed=%d Queued=%d Msgs=%d Activations=%u", WorkerCount[STATE_STOPPED], WorkerCount[STATE_IDLE], WorkerCount[STATE_BUSY], WorkerCount[STATE_KILLED], NumQueuedRequests, NumMsgsProcessed, Activations); /* Reschedule timer */ t.tv_usec = 0; t.tv_sec = Settings.logStatusInterval; Event_AddTimerHandler(es, t, doStatusLog, NULL); } /********************************************************************** * %FUNCTION: logWorkerReaped * %ARGUMENTS: * s -- worker * status -- termination status * %RETURNS: * Nothing * %DESCRIPTION: * Logs to syslog ***********************************************************************/ static void logWorkerReaped(Worker *s, int status) { int level; char *extra; if (s->state == STATE_KILLED) { level = LOG_DEBUG; extra = ""; } else { level = LOG_ERR; extra = " (WORKER DIED UNEXPECTEDLY)"; if (s->es) { notify_listeners(s->es, "U\n"); } } if (!DOLOG) { return; } if (WIFEXITED(status)) { syslog(level, "Reap: worker %d (pid %lu) exited normally with status %d%s", WORKERNO(s), (unsigned long) s->pid, WEXITSTATUS(status), extra); return; } if (WIFSIGNALED(status)) { if (s->state == STATE_KILLED && (WTERMSIG(status) == SIGTERM || WTERMSIG(status) == SIGKILL)) { syslog(level, "Reap: worker %d (pid %lu) exited due to SIGTERM/SIGKILL as expected.", WORKERNO(s), (unsigned long) s->pid); } else { syslog(level, "Reap: worker %d (pid %lu) exited due to signal %d%s", WORKERNO(s), (unsigned long) s->pid, WTERMSIG(status), extra); } return; } syslog(level, "Reap: worker %d (pid %lu) exited for unknown reason%s", WORKERNO(s), (unsigned long) s->pid, extra); } /********************************************************************** * %FUNCTION: sigterm * %ARGUMENTS: * sig -- signal number * %RETURNS: * Nothing * %DESCRIPTION: * Called when SIGTERM received -- kills workers and exits ***********************************************************************/ static void sigterm(int sig) { int i, j, oneleft; /* Only the parent process should handle SIGTERM */ if (ParentPid != getpid()) { syslog(LOG_WARNING, "Child process received SIGTERM before signal disposition could be reset! Exiting!"); exit(EXIT_FAILURE); } if (pidfile) { unlink(pidfile); } if (lockfile) { unlink(lockfile); } if (DOLOG) { if (sig) { syslog(LOG_INFO, "Received SIGTERM: Stopping workers and terminating"); } } /* Remove our socket so we don't get any more requests */ if (Settings.sockName) { (void) remove(Settings.sockName); } /* Hack...*/ if (Settings.unprivSockName && (Settings.unprivSockName[0] == '/')) { (void) remove(Settings.unprivSockName); } /* First, close descriptors to force EOF on STDIN; then wait up to 10 seconds before sending SIGTERM */ for (i=0; i= Settings.minWorkers) { /* Enough workers, so do nothing */ return; } /* Start a worker */ s = Workers[STATE_STOPPED]; if (s) { snprintf(reason, sizeof(reason), "Bringing workers up to minWorkers (%d)", Settings.minWorkers); if (activateWorker(s, reason) >= 0) { /* Check for and handle queued requests, if there are any */ /* FOR THE FUTURE handle_queued_request(); */ } } /* Reschedule if necessary */ if (NUM_RUNNING_WORKERS < Settings.minWorkers) { scheduleBringWorkersUpToMin(es); } } /********************************************************************** * %FUNCTION: scheduleBringWorkersUpToMin * %ARGUMENTS: * es -- event selector * %RETURNS: * Nothing * %DESCRIPTION: * Schedules a call to bringWorkersUpToMin in slewTime seconds from now. ***********************************************************************/ static void scheduleBringWorkersUpToMin(EventSelector *es) { struct timeval t; /* Do nothing if already scheduled */ if (minScheduled) return; minScheduled = 1; t.tv_usec = 0; t.tv_sec = Settings.slewTime; Event_AddTimerHandler(es, t, bringWorkersUpToMin, NULL); } #ifdef HAVE_SETRLIMIT /********************************************************************** * %FUNCTION: limit_mem_usage * %ARGUMENTS: * rss -- maximum resident-set size in kB * as -- maximum total address space in kB * %RETURNS: * Nothing * %DESCRIPTION: * Calls setrlimit to limit resource usage ***********************************************************************/ static void limit_mem_usage(unsigned long rss, unsigned long as) { int n; struct rlimit lim; #ifdef RLIMIT_RSS if (rss) { /* Convert kb to bytes */ rss *= 1024; lim.rlim_cur = rss; lim.rlim_max = rss; n = setrlimit(RLIMIT_RSS, &lim); if (n < 0) { syslog(LOG_WARNING, "setrlimit(RLIMIT_RSS, %lu) failed: %m", rss); } } #endif if (as) { /* Convert kb to bytes */ as *= 1024; lim.rlim_cur = as; lim.rlim_max = as; #ifdef RLIMIT_AS n = setrlimit(RLIMIT_AS, &lim); if (n < 0) { syslog(LOG_WARNING, "setrlimit(RLIMIT_AS, %lu) failed: %m", as); } #endif #ifdef RLIMIT_DATA n = setrlimit(RLIMIT_DATA, &lim); if (n < 0) { syslog(LOG_WARNING, "setrlimit(RLIMIT_DATA, %lu) failed: %m", as); } #endif } } #endif /* HAVE_SETRLIMIT */ /********************************************************************** * %FUNCTION: doWorkerReport * %ARGUMENTS: * es -- event selector * fd -- socket * only_busy -- if true, only report on busy workers * %RETURNS: * Nothing * %DESCRIPTION: * Prints a list of workers, statuses and pids ***********************************************************************/ static void doWorkerReport(EventSelector *es, int fd, int only_busy) { int len = Settings.maxWorkers * (33 + MAX_STATUS_LEN + 2 + 32) + 1; char *ans = malloc(len); char *ptr = ans; char status = '?'; int i, j; time_t now; int secs; if (!ans) { reply_to_mimedefang(es, fd, "error: Out of memory\n"); return; } *ans = 0; now = time(NULL); for (i=0; istate != STATE_BUSY)) { continue; } switch (s->state) { case STATE_STOPPED: status = 'S'; break; case STATE_IDLE: status = 'I'; break; case STATE_BUSY: status = 'B'; break; case STATE_KILLED: status = 'K'; break; } j = snprintf(ptr, len, "%d %c", i, status); len -= j; ptr += j; if (s->state != STATE_STOPPED) { j = snprintf(ptr, len, " %lu", (unsigned long) s->pid); len -= j; ptr += j; } if (s->state == STATE_BUSY && s->cmd >= 0 && s->cmd < NUM_CMDS) { j = snprintf(ptr, len, " %s", CmdName[s->cmd]); len -= j; ptr += j; } if (s->last_cmd >= 0 && s->last_cmd < NUM_CMDS) { j = snprintf(ptr, len, " last=%s", CmdName[s->last_cmd]); len -= j; ptr += j; } secs = (int) (now - s->lastStateChange); j = snprintf(ptr, len, " ago=%d", secs); len -= j; ptr += j; if (s->status_tag[0]) { j = snprintf(ptr, len, " (%s)", s->status_tag); len -= j; ptr += j; } j = snprintf(ptr, len, "\n"); len -= j; ptr += j; } reply_to_mimedefang(es, fd, ans); free(ans); } /********************************************************************** * %FUNCTION: doStatus * %ARGUMENTS: * es -- event selector * fd -- socket * %RETURNS: * Nothing * %DESCRIPTION: * Prints a status string back. Each char corresponds to a worker; * the chars are: * S -- worker is stopped * I -- worker is idle * B -- worker is busy * K -- worker is killed but not yet reaped. ***********************************************************************/ static void doStatus(EventSelector *es, int fd) { char *ans = malloc(Settings.maxWorkers + 2 + 300); int i; if (!ans) { reply_to_mimedefang(es, fd, "error: Out of memory\n"); return; } for (i=0; i < Settings.maxWorkers; i++) { Worker *s = &AllWorkers[i]; switch (s->state) { case STATE_STOPPED: ans[i] = 'S'; break; case STATE_IDLE: ans[i] = 'I'; break; case STATE_BUSY: ans[i] = 'B'; break; case STATE_KILLED: ans[i] = 'K'; break; default: ans[i] = '?'; } } sprintf(ans + Settings.maxWorkers, " %d %d %d %d %d\n", NumMsgsProcessed, Activations, Settings.requestQueueSize, NumQueuedRequests, (int) (time(NULL) - TimeOfProgramStart)); reply_to_mimedefang(es, fd, ans); free(ans); } /********************************************************************** * %FUNCTION: doHelp * %ARGUMENTS: * es -- event selector * fd -- socket * unpriv -- true on unprivileged socket; print raw help * %RETURNS: * Nothing * %DESCRIPTION: * Prints a help string listing commands the multiplexor accepts. ***********************************************************************/ static void doHelp(EventSelector *es, int fd, int unpriv) { if (unpriv) { reply_to_mimedefang(es, fd, "help -- List available multiplexor commands\n" "free -- Display number of free workers\n" "load -- Display worker load (scans)\n" "rawload -- Display worker load in computer-readable format\n" "load1 secs -- Display worker load in alternate format\n" "jsonload1 secs -- Display worker load in JSON format\n" "rawload1 secs -- Display worker load in computer-readable alternate format\n" "load-relayok -- Display load (relayok requests)\n" "rawload-relayok -- Computer-readable load (relayok requests)\n" "load-senderok -- Display load (senderok requests)\n" "rawload-senderok -- Computer-readable load (senderok requests)\n" "load-recipok -- Display load (recipok requests)\n" "rawload-recipok -- Computer-readable load (recipok requests)\n" "status -- Display worker status\n" "jsonstatus -- Display worker status in JSON format\n" "rawstatus -- Display worker status in computer-readable format\n" "barstatus -- Display worker status as bar graph\n" "histo -- Display histogram of busy workers\n" "msgs -- Display number of messages processed since startup\n" "workers -- Display workers with process-IDs\n" "busyworkers -- Display busy workers with process-IDs\n" "workerinfo n -- Display information about a particular worker\n" "(Analogous hload commands provide hourly information)\n"); } else { reply_to_mimedefang(es, fd, "help -- List available multiplexor commands\n" "status -- Display worker status\n" "jsonstatus -- Display worker status in JSON format\n" "rawstatus -- Display worker status in computer-readable format\n" "barstatus -- Display worker status as bar graph\n" "free -- Display number of free workers\n" "load -- Display worker load (scans)\n" "rawload -- Display worker load in computer-readable format\n" "load1 secs -- Display worker load in alternate format\n" "jsonload1 secs -- Display worker load in JSON format\n" "rawload1 secs -- Display worker load in alternate format\n" "load-relayok -- Display load (relayok requests)\n" "rawload-relayok -- Computer-readable load (relayok requests)\n" "load-senderok -- Display load (senderok requests)\n" "rawload-senderok -- Computer-readable load (senderok requests)\n" "load-recipok -- Display load (recipok requests)\n" "rawload-recipok -- Computer-readable load (recipok requests)\n" "histo -- Display histogram of busy workers\n" "msgs -- Display number of messages processed since startup\n" "reread -- Force a re-read of filter rules\n" "workers -- Display workers with process-IDs\n" "busyworkers -- Display busy workers with process-IDs\n" "workerinfo n -- Display information about a particular worker\n" "scan /path -- Run a scan (do not invoke using md-mx-ctrl)\n" "(Analogous hload commands provide hourly information)\n"); } } static void doLoad1(EventSelector *es, int fd, int back) { char ans[1024]; int msgs[NUM_CMDS], workers[NUM_CMDS], activated, reaped; BIG_INT ms[NUM_CMDS]; double avg[NUM_CMDS], ams[NUM_CMDS]; int counts[NUM_CMDS]; int cmd; time_t now = time(NULL); Worker *s; for (cmd=MIN_CMD; cmd <= MAX_CMD; cmd++) { get_history_totals(cmd, now, back, &msgs[cmd], &workers[cmd], &ms[cmd], &activated, &reaped); if (msgs[cmd]) { avg[cmd] = (double) workers[cmd] / (double) msgs[cmd]; ams[cmd] = (double) ms[cmd] / (double) msgs[cmd]; } else { avg[cmd] = 0.0; ams[cmd] = 0.0; } } memset(counts, 0, sizeof(counts)); s = Workers[STATE_BUSY]; while(s) { if (s->cmd >= MIN_CMD && s->cmd <= MAX_CMD) { counts[s->cmd]++; } s = s->next; } snprintf(ans, sizeof(ans), "%d %f %f %d %f %f %d %f %f %d %f %f %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", msgs[SCAN_CMD], avg[SCAN_CMD], ams[SCAN_CMD], msgs[RELAYOK_CMD], avg[RELAYOK_CMD], ams[RELAYOK_CMD], msgs[SENDEROK_CMD], avg[SENDEROK_CMD], ams[SENDEROK_CMD], msgs[RECIPOK_CMD], avg[RECIPOK_CMD], ams[RECIPOK_CMD], WorkerCount[STATE_BUSY], WorkerCount[STATE_IDLE], WorkerCount[STATE_STOPPED], WorkerCount[STATE_KILLED], NumMsgsProcessed, Activations, Settings.requestQueueSize, NumQueuedRequests, (int)(now - TimeOfProgramStart), back, counts[SCAN_CMD], counts[RELAYOK_CMD], counts[SENDEROK_CMD], counts[RECIPOK_CMD]); reply_to_mimedefang(es, fd, ans); } /********************************************************************** * %FUNCTION: doLoad * %ARGUMENTS: * es -- event selector * fd -- socket * cmd -- which command's history we want * %RETURNS: * Nothing * %DESCRIPTION: * Prints a load string back. This string is a list of numbers: * * msgs_0 msgs_1 msgs_5 msgs_10 * avg_0 avg_1 avg_5 avg_10 * ams_0 ams_1 ams_5 ams_10 * a0 a1 a5 a10 * r0 r1 r5 r10 * busy idle stopped killed * msgs_processed total_activations * request_queue_size num_queued_requests seconds_since_program_start * * Note for below: If N=0, then time frame is last 10 seconds. * msgs_N -- Number of messages processed in last N minutes. * avg_N -- Average number of busy workers when msg processed * ams_N -- Average scan time in milliseconds * aN -- Number of workers activated in last N minutes * rN -- Number of workers reaped in last N minutes * busy idle stopped killed -- Snapshot of # workers in each status * msgs_processed -- Total msgs processed since startup * total_activations -- Total worker activations since startup * request_queue_size -- size of queue * num_queued_requests -- number of requests on queue * seconds_since_program_start -- just what it says! * * NOTE: Activations and reaps (aN, rN) are only valid if cmd == SCAN_CMD ***********************************************************************/ static void doLoad(EventSelector *es, int fd, int cmd) { char ans[1024]; time_t now = time(NULL); int msgs_0, msgs_1, msgs_5, msgs_10, worker_0, worker_1, worker_5, worker_10; BIG_INT ms_0, ms_1, ms_5, ms_10; int a0, a1, a5, a10; /* Activations */ int r0, r1, r5, r10; /* Reaps */ double ams_0, ams_1, ams_5, ams_10; double avg_0, avg_1, avg_5, avg_10; /* Tricky... get slices of history so as not to overlap */ /* start go_back_by results.... */ get_history_totals(cmd, now, 10, &msgs_0, &worker_0, &ms_0, &a0, &r0); get_history_totals(cmd, now-10, 1*60-10, &msgs_1, &worker_1, &ms_1, &a1, &r1); get_history_totals(cmd, now-1*60, 5*60-1*60, &msgs_5, &worker_5, &ms_5, &a5, &r5); get_history_totals(cmd, now-5*60, 10*60-5*60, &msgs_10, &worker_10, &ms_10, &a10, &r10); /* Accumulate partial sums */ msgs_1 += msgs_0; worker_1 += worker_0; ms_1 += ms_0; a1 += a0; r1 += r0; msgs_5 += msgs_1; worker_5 += worker_1; ms_5 += ms_1; a5 += a1; r5 += r1; msgs_10 += msgs_5; worker_10 += worker_5; ms_10 += ms_5; a10 += a5; r10 += r5; if (!msgs_0) avg_0 = 1.0; else avg_0 = (double) worker_0 / (double) msgs_0; if (!msgs_1) avg_1 = 1.0; else avg_1 = (double) worker_1 / (double) msgs_1; if (!msgs_5) avg_5 = 1.0; else avg_5 = (double) worker_5 / (double) msgs_5; if (!msgs_10) avg_10 = 1.0; else avg_10 = (double) worker_10 / (double) msgs_10; if (!msgs_0) ams_0 = 0.0; else ams_0 = (double) ms_0 / (double) msgs_0; if (!msgs_1) ams_1 = 0.0; else ams_1 = (double) ms_1 / (double) msgs_1; if (!msgs_5) ams_5 = 0.0; else ams_5 = (double) ms_5 / (double) msgs_5; if (!msgs_10) ams_10 = 0.0; else ams_10 = (double) ms_10 / (double) msgs_10; snprintf(ans, sizeof(ans), "%d %d %d %d %f %f %f %f %f %f %f %f %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", msgs_0, msgs_1, msgs_5, msgs_10, avg_0, avg_1, avg_5, avg_10, ams_0, ams_1, ams_5, ams_10, a0, a1, a5, a10, r0, r1, r5, r10, WorkerCount[STATE_BUSY], WorkerCount[STATE_IDLE], WorkerCount[STATE_STOPPED], WorkerCount[STATE_KILLED], NumMsgsProcessed, Activations, Settings.requestQueueSize, NumQueuedRequests, (int)(now - TimeOfProgramStart)); reply_to_mimedefang(es, fd, ans); } /********************************************************************** * %FUNCTION: doHourlyLoad * %ARGUMENTS: * es -- event selector * fd -- socket * cmd -- which command's history we want * %RETURNS: * Nothing * %DESCRIPTION: * Prints a load string back. This string is a list of numbers: * * msgs_1 msgs_4 msgs_12 msgs_24 - #msgs in last N hours * avg_1 avg_4 avg_12 avg_24 - Avg workers/cmd last N hours * ams_1 ams_4 ams_12 ams_24 - Avg ms/cmd last N hours * secs_1 secs_4 secs_12 secs_24 - Total elapsed seconds in output ***********************************************************************/ static void doHourlyLoad(EventSelector *es, int fd, int cmd) { int msgs_1, msgs_4, msgs_12, msgs_24; double ams_1, ams_4, ams_12, ams_24; double avg_1, avg_4, avg_12, avg_24; int secs_1, secs_4, secs_12, secs_24; int workers; BIG_INT ms; char ans[1024]; time_t now = time(NULL); get_hourly_history_totals(cmd, now, 1, &msgs_1, &workers, &ms, &secs_1); if (msgs_1) { avg_1 = ((double) workers) / ((double) msgs_1); ams_1 = ((double) ms) / ((double) msgs_1); } else { avg_1 = 0.0; ams_1 = 0.0; } get_hourly_history_totals(cmd, now, 4, &msgs_4, &workers, &ms, &secs_4); if (msgs_4) { avg_4 = ((double) workers) / ((double) msgs_4); ams_4 = ((double) ms) / ((double) msgs_4); } else { avg_4 = 0.0; ams_4 = 0.0; } get_hourly_history_totals(cmd, now, 12, &msgs_12, &workers, &ms, &secs_12); if (msgs_12) { avg_12 = ((double) workers) / ((double) msgs_12); ams_12 = ((double) ms) / ((double) msgs_12); } else { avg_12 = 0.0; ams_12 = 0.0; } get_hourly_history_totals(cmd, now, 24, &msgs_24, &workers, &ms, &secs_24); if (msgs_24) { avg_24 = ((double) workers) / ((double) msgs_24); ams_24 = ((double) ms) / ((double) msgs_24); } else { avg_24 = 0.0; ams_24 = 0.0; } snprintf(ans, sizeof(ans), "%d %d %d %d %f %f %f %f %f %f %f %f %d %d %d %d\n", msgs_1, msgs_4, msgs_12, msgs_24, avg_1, avg_4, avg_12, avg_24, ams_1, ams_4, ams_12, ams_24, secs_1, secs_4, secs_12, secs_24); reply_to_mimedefang(es, fd, ans); } /********************************************************************** * %FUNCTION: doHistogram * %ARGUMENTS: * es -- event selector * fd -- socket * %RETURNS: * Nothing * %DESCRIPTION: * Prints the histogram like this: * 1 num1 * 2 num2 * ....... * N numN * * Each "numI" is the number of times "I" workers were busy. ***********************************************************************/ static void doHistogram(EventSelector *es, int fd) { /* Allow 20 bytes/entry - two 9-digit numbers, a space and a carriage-return */ int i; size_t roomleft = (size_t) (Settings.maxWorkers * 20); char *ans = malloc(roomleft + 1); char *pos = ans; if (!ans) { reply_to_mimedefang(es, fd, "error: Out of memory\n"); return; } for (i=0; i= roomleft) { free(ans); reply_to_mimedefang(es, fd, "error: String too long\n"); return; } pos += count; roomleft -= count; } reply_to_mimedefang(es, fd, ans); free(ans); } /********************************************************************** * %FUNCTION: findFreeWorker * %ARGUMENTS: * cmdno -- the command number. One of: OTHER_CMD, SCAN_CMD, * RELAYOK_CMD, SENDEROK_CMD, or RECIPOK_CMD * %RETURNS: * A pointer to a free worker, or NULL if none found. Prefers to * return a running worker rather than one which needs activation. Also, * prefers to return the worker which has been running the longest since * activation. Also prefers to pick a worker that last ran the same * command as cmdno * %DESCRIPTION: * Finds a free (preferably running) worker. ***********************************************************************/ static Worker * findFreeWorker(int cmdno) { Worker *s = Workers[STATE_IDLE]; Worker *best = s; Worker *best_same_cmd = NULL; while(s) { if (s->activated < best->activated) { best = s; } if (s->last_cmd == cmdno || s->last_cmd == NO_CMD) { if (!best_same_cmd) { best_same_cmd = s; } else if (best_same_cmd->last_cmd != cmdno || s->activated < best_same_cmd->activated) { best_same_cmd = s; } } s = s->next; } if (best_same_cmd) { best = best_same_cmd; } if (!best) { /* No running workers - just pick the first stopped worker */ best = Workers[STATE_STOPPED]; } if (best) { best->status_tag[0] = 0; best->cmd = -1; } if (Settings.debugWorkerScheduling && best) { syslog(LOG_INFO, "Scheduling %s worker %d for cmdno %d (activated=%d, last=%d)", state_name(best->state), WORKERNO(best), cmdno, best->activated, best->last_cmd); } return best; } /********************************************************************** * %FUNCTION: log_worker_resource_usage * %ARGUMENTS: * s -- worker * usage -- struct rusage with worker's resource usage. * %RETURNS: * Nothing * %DESCRIPTION: * Logs worker's resource usage. ***********************************************************************/ #ifdef HAVE_WAIT3 static void log_worker_resource_usage(Worker *s, struct rusage *usage) { if (!DOLOG) return; syslog(LOG_INFO, "Worker %d resource usage: req=%d, scans=%d, user=%d.%03d, " "sys=%d.%03d, nswap=%ld, majflt=%ld, minflt=%ld, " "maxrss=%ld, bi=%ld, bo=%ld", WORKERNO(s), s->numRequests, s->numScans, (int) usage->ru_utime.tv_sec, (int) usage->ru_utime.tv_usec / 1000, (int) usage->ru_stime.tv_sec, (int) usage->ru_stime.tv_usec / 1000, usage->ru_nswap, usage->ru_majflt, usage->ru_minflt, usage->ru_maxrss, usage->ru_inblock, usage->ru_oublock); } #endif static void handleMapAccept(EventSelector *es, int fd); static void got_map_request(EventSelector *es, int fd, char *buf, int len, int flag, void *data); static void handle_worker_received_map_command(EventSelector *es, int fd, char *buf, int len, int flag, void *data); static void receive_worker_map_answer(EventSelector *es, int fd, char *buf, int len, int flag, void *data); static void sent_map_reply(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { if (!EventTcp_ReadNetstring(es, fd, got_map_request, 0, NULL)) { syslog(LOG_ERR, "sent_map_reply: EventTcp_ReadNetstring failed: %m"); close(fd); } } /********************************************************************** * %FUNCTION: reply_to_map_with_len * %ARGUMENTS: * es -- event selector * fd -- file descriptor * msg -- message to send back * len -- length of message * %RETURNS: * The event associated with the reply, or NULL. * %DESCRIPTION: * Sends a final message back to map. ***********************************************************************/ static EventTcpState * reply_to_map_with_len(EventSelector *es, int fd, char const *msg, int len) { EventTcpState *e; e = EventTcp_WriteNetstring(es, fd, msg, len, sent_map_reply, Settings.clientTimeout, NULL); if (!e) { if (DOLOG) { syslog(LOG_ERR, "reply_to_map: EventTcp_WriteBuf failed: %m"); } close(fd); } return e; } /********************************************************************** * %FUNCTION: reply_to_map * %ARGUMENTS: * es -- event selector * fd -- file descriptor * msg -- message to send back * %RETURNS: * The event associated with the reply, or NULL. * %DESCRIPTION: * Sends a final message back to map. ***********************************************************************/ static EventTcpState * reply_to_map(EventSelector *es, int fd, char const *msg) { return reply_to_map_with_len(es, fd, msg, strlen(msg)); } /********************************************************************** * %FUNCTION: handleMapAccept * %ARGUMENTS: * es -- event selector * fd -- accepted connection * %RETURNS: * Nothing * %DESCRIPTION: * Handles a connection attempt for TCP map. Sets up to read requests. ***********************************************************************/ static void handleMapAccept(EventSelector *es, int fd) { if (!EventTcp_ReadNetstring(es, fd, got_map_request, 0, NULL)) { syslog(LOG_ERR, "handleMapAccept: EventTcp_ReadNetstring failed: %m"); close(fd); } } /********************************************************************** * %FUNCTION: got_request * %ARGUMENTS: * es -- event selector * fd -- connection from Sendmail map reader * buf -- map request * len -- length of map request * flag -- flag from reader * data -- ignored * %RETURNS: * Nothing * %DESCRIPTION: * Handles a received map command. ***********************************************************************/ static void got_map_request(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { Worker *s; char *cmd, *oldcmd; char *t; if (flag == EVENT_TCP_FLAG_TIMEOUT || flag == EVENT_TCP_FLAG_IOERROR || flag == EVENT_TCP_FLAG_EOF) { close(fd); return; } /* Chop off comma */ if (len && buf[len-1] == ',') { buf[len-1] = 0; len--; } else { reply_to_map(es, fd, "PERM Netstring missing terminating comma"); return; } /* Convert the command to something we can use */ cmd = malloc(len*3+1 + 4 + 3); if (!cmd) { reply_to_map(es, fd, "TEMP Out of memory"); return; } t = buf; while(*t && (*t != ' ')) t++; if (*t != ' ') { free(cmd); reply_to_map(es, fd, "PERM Invalid request format"); return; } *t = 0; oldcmd = cmd; strcpy(cmd, "map "); cmd += 4; cmd += percent_encode(buf, cmd, len*3+1); *cmd++ = ' '; cmd += percent_encode(t+1, cmd, len*3+1+4 - (cmd - oldcmd)); *cmd++ = '\n'; *cmd = 0; cmd = oldcmd; /* Send the request to a worker */ s = findFreeWorker(OTHER_CMD); if (!s) { free(cmd); reply_to_map(es, fd, "TEMP No free workers"); return; } if (activateWorker(s, "About to handle map request") == (pid_t) -1) { free(cmd); syslog(LOG_WARNING, "map command failed: No free workers"); reply_to_map(es, fd, "TEMP Unable to activate worker"); return; } putOnList(s, STATE_BUSY); s->clientFD = fd; s->workdir[0] = 0; set_worker_status_from_command(s, cmd); s->event = EventTcp_WriteBuf(es, s->workerStdin, cmd, strlen(cmd), handle_worker_received_map_command, Settings.clientTimeout, s); free(cmd); if (!s->event) { if (DOLOG) syslog(LOG_ERR, "got_map_request: EventTcp_WriteBuf failed: %m"); s->clientFD = -1; /* Do not close FD */ killWorker(s, "EventTcp_WriteBuf failed"); reply_to_map(es, fd, "TEMP Could not send command to worker"); } } static void handle_worker_received_map_command(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { Worker *s = (Worker *) data; /* Event was triggered */ s->event = NULL; if (flag == EVENT_TCP_FLAG_TIMEOUT || flag == EVENT_TCP_FLAG_IOERROR) { reply_to_map(es, s->clientFD, "TEMP Error talking to worker process"); s->clientFD = -1; /* Kill the worker process */ killWorker(s, "Error talking to worker process"); return; } /* Worker has been given the command; now wait for it to reply */ s->event = EventTcp_ReadBuf(es, s->workerStdout, MAX_CMD_LEN, '\n', receive_worker_map_answer, Settings.busyTimeout, 1, s); if (!s->event) { if (DOLOG) syslog(LOG_ERR, "handleWorkerReceivedCommand: EventTcp_ReadBuf failed: %m"); reply_to_map(es, s->clientFD, "TEMP Error talking to worker process"); s->clientFD = -1; killWorker(s, "EventTcp_ReadBuf failed"); } } static void receive_worker_map_answer(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { Worker *s = (Worker *) data; s->event = NULL; if (!len || (flag == EVENT_TCP_FLAG_TIMEOUT)) { reply_to_map(es, s->clientFD, "TEMP Busy timeout on worker"); s->clientFD = -1; killWorker(s, "Busy timeout"); return; } /* Remove newline at end */ if (buf[len-1] == '\n') { buf[len-1] = 0; } /* Send the answer back */ percent_decode(buf); reply_to_map(es, s->clientFD, buf); s->clientFD = -1; s->numRequests++; putOnList(s, STATE_IDLE); s->idleTime = time(NULL); checkWorkerForExpiry(s); } static void enqueue_request(Request *slot) { NumQueuedRequests++; slot->next = NULL; if (!RequestHead) { RequestHead = slot; RequestTail = slot; } else { /* Assert that RequestTail != NULL */ RequestTail->next = slot; RequestTail = slot; } } static void dequeue_request(Request *slot) { Request *prev; NumQueuedRequests--; if (slot == RequestHead) { RequestHead = RequestHead->next; if (!RequestHead) { RequestTail = NULL; } /* Safety... */ slot->next = NULL; return; } prev = RequestHead; while (prev->next && prev->next != slot) { prev = prev->next; } if (prev->next == slot) { prev->next = slot->next; if (slot == RequestTail) { RequestTail = prev; } } /* Safety... */ slot->next = NULL; } /********************************************************************** * %FUNCTION: queue_request * %ARGUMENTS: * es -- event selector * fd -- client file descriptor * cmd -- command to queue * %RETURNS: * 1 if request is successfully queued; 0 if not. * %DESCRIPTION: * Queues a request if all workers are temporarily busy. Queue is * handled in FIFO order as workers become free. ***********************************************************************/ int queue_request(EventSelector *es, int fd, char *cmd) { Request *slot = NULL; int i; struct timeval t; if (NumQueuedRequests >= Settings.requestQueueSize) { if (DOLOG) { syslog(LOG_INFO, "Cannot queue request: request queue is full"); } return 0; } /* Find a free request slot */ for (i=0; icmd = strdup(cmd); if (!slot->cmd) { if (DOLOG) { syslog(LOG_ERR, "Cannot queue request: Out of memory!"); } return 0; } t.tv_usec = 0; t.tv_sec = Settings.requestQueueTimeout; slot->timeoutHandler = Event_AddTimerHandler(es, t, handleRequestQueueTimeout, slot); if (!slot->timeoutHandler) { free(slot->cmd); if (DOLOG) { syslog(LOG_ERR, "Cannot queue request: Out of memory!"); } return 0; } slot->fd = fd; slot->es = es; enqueue_request(slot); if (DOLOG) { syslog(LOG_INFO, "All workers are busy: Queueing request (%d queued)", NumQueuedRequests); } return 1; } /********************************************************************** * %FUNCTION: handleRequestQueueTimeout * %ARGUMENTS: * es -- event selector * fd -- ignored * flags -- ignored * data -- the queued request that timed out * %RETURNS: * Nothing * %DESCRIPTION: * Handles a queue timeout -- sends error back to client ***********************************************************************/ static void handleRequestQueueTimeout(EventSelector *es, int fd, unsigned int flags, void *data) { Request *slot = (Request *) data; fd = slot->fd; free(slot->cmd); slot->cmd = NULL; slot->fd = -1; slot->timeoutHandler = NULL; dequeue_request(slot); reply_to_mimedefang(es, fd, "error: Queued request timed out\n"); } /********************************************************************** * %FUNCTION: handle_queued_request * %ARGUMENTS: * None * %RETURNS: * 1 if a queued request is waiting and was passed off to a worker; 0 * otherwise. * %DESCRIPTION: * Checks the queue for pending requests. ***********************************************************************/ static int handle_queued_request(void) { Request *slot = RequestHead; int len; if (!slot) return 0; dequeue_request(slot); Event_DelHandler(slot->es, slot->timeoutHandler); slot->timeoutHandler = NULL; len = strlen(slot->cmd); if (len > 5 && !strncmp(slot->cmd, "scan ", 5)) { doScanAux(slot->es, slot->fd, slot->cmd, 0); } else { doWorkerCommandAux(slot->es, slot->fd, slot->cmd, 0); } slot->es = NULL; slot->fd = -1; free(slot->cmd); slot->cmd = NULL; return 1; } /********************************************************************** * %FUNCTION: do_tick * %ARGUMENTS: * es -- event selector * fd, flags -- ignored * data -- really the tick number in disguise * %RETURNS: * Nothing * %DESCRIPTION: * Called periodically to run a "tick" request. ***********************************************************************/ static void do_tick(EventSelector *es, int fd, unsigned int flags, void *data) { Worker *s; char buffer[128]; /* Ugly */ int tick_no = (int) ((long) data); s = findFreeWorker(OTHER_CMD); if (!s) { if (DOLOG) { syslog(LOG_WARNING, "Tick %d skipped -- no free workers", tick_no); } schedule_tick(es, tick_no); return; } if (activateWorker(s, "About to run tick") == (pid_t) -1) { if (DOLOG) { syslog(LOG_WARNING, "Tick %d skipped -- unable to activate worker %d", tick_no, WORKERNO(s)); } schedule_tick(es, tick_no); return; } putOnList(s, STATE_BUSY); s->clientFD = -1; s->tick_no = tick_no; sprintf(buffer, "tick %d", tick_no); strncpy(s->status_tag, buffer, MAX_STATUS_LEN); s->status_tag[MAX_STATUS_LEN-1] = 0; sprintf(buffer, "tick %d\n", tick_no); s->event = EventTcp_WriteBuf(es, s->workerStdin, buffer, strlen(buffer), handleWorkerReceivedTick, Settings.clientTimeout, s); if (!s->event) { if (DOLOG) { syslog(LOG_WARNING, "Tick %d skipped -- EventTcp_WriteBuf failed: %m", tick_no); } killWorker(s, "EventTcp_WriteBuf failed"); schedule_tick(es, tick_no); } } /********************************************************************** * %FUNCTION: handleWorkerReceivedTick * %ARGUMENTS: * es -- event selector * fd -- not used * buf -- buffer of data read from multiplexor * len -- amount of data read from multiplexor * flag -- flag from reader * data -- the worker * %RETURNS: * Nothing * %DESCRIPTION: * Called when "tick\n" has been written to worker. Sets up an event handler * to read the worker's reply ***********************************************************************/ static void handleWorkerReceivedTick(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { Worker *s = (Worker *) data; /* Event was triggered */ s->event = NULL; if (flag == EVENT_TCP_FLAG_TIMEOUT || flag == EVENT_TCP_FLAG_IOERROR) { if (DOLOG) { syslog(LOG_ERR, "handleWorkerReceivedTick(%d): Timeout or error: Flag = %d: %m", WORKERNO(s), flag); } /* Kill the worker process */ killWorker(s, "Error talking to worker process"); schedule_tick(es, s->tick_no); return; } /* Worker has been given the command; now wait for it to reply */ s->event = EventTcp_ReadBuf(es, s->workerStdout, MAX_CMD_LEN, '\n', handleWorkerReceivedAnswerFromTick, Settings.busyTimeout, 1, s); if (!s->event) { if (DOLOG) syslog(LOG_ERR, "handleWorkerReceivedTick: EventTcp_ReadBuf failed: %m"); killWorker(s, "EventTcp_ReadBuf failed"); schedule_tick(es, s->tick_no); } } /********************************************************************** * %FUNCTION: handleWorkerReceivedAnswerFromTick * %ARGUMENTS: * es -- event selector * fd -- not used * buf -- buffer of data read * len -- amount of data read * flag -- flag from reader * data -- the worker * %RETURNS: * Nothing * %DESCRIPTION: * Called when the worker's answer comes back from the "tick" request ***********************************************************************/ static void handleWorkerReceivedAnswerFromTick(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { Worker *s = (Worker *) data; /* Event was triggered */ s->event = NULL; /* We don't care how the worker replied. */ s->numRequests++; /* If we had a busy timeout, kill the worker */ if (flag == EVENT_TCP_FLAG_TIMEOUT) { killWorker(s, "Busy timeout"); } else { /* Put worker on free list */ putOnList(s, STATE_IDLE); /* Record time when this worker became idle */ s->idleTime = time(NULL); /* Check worker for expiry */ checkWorkerForExpiry(s); } /* And reschedule another tick request */ schedule_tick(es, s->tick_no); } /********************************************************************** * %FUNCTION: schedule_tick * %ARGUMENTS: * es -- event selector * tick_no -- tick number * %RETURNS: * Nothing * %DESCRIPTION: * Schedules a "tick" command for tick_interval seconds in the future, * if tick_interval is non-zero ***********************************************************************/ static void schedule_tick(EventSelector *es, int tick_no) { struct timeval t; if (Settings.tick_interval <= 0) { return; } t.tv_usec = 0; t.tv_sec = Settings.tick_interval; if (!Event_AddTimerHandler(es, t, do_tick, (void *) ((long) tick_no))) { syslog(LOG_CRIT, "Unable to schedule tick handler! Event_AddTimerHandler failed!"); } } /********************************************************************** * %FUNCTION: init_history * %ARGUMENTS: * None * %RETURNS: * Nothing * %DESCRIPTION: * Resets all history bucket entries to zero ***********************************************************************/ static void init_history(void) { memset(history, 0, sizeof(history)); memset(hourly_history, 0, sizeof(hourly_history)); } /********************************************************************** * %FUNCTION: get_history_bucket * %ARGUMENTS: * cmd -- which command's buckets we want * %RETURNS: * A pointer to an initialized history bucket ready for incrementing counters * %DESCRIPTION: * Determines current bucket and initializes it if required. ***********************************************************************/ static HistoryBucket * get_history_bucket(int cmd) { time_t now = time(NULL); int bucket = ((int) now) % HISTORY_SECONDS; HistoryBucket *b = &(history[cmd][bucket]); if (b->elapsed != now) { b->elapsed = now; b->count = 0; b->workers = 0; b->ms = 0; b->activated = 0; b->reaped = 0; } return b; } /********************************************************************** * %FUNCTION: get_hourly_history_bucket * %ARGUMENTS: * cmd -- which command's buckets we want * %RETURNS: * A pointer to an initialized history bucket ready for incrementing counters * %DESCRIPTION: * Determines current bucket and initializes it if required. ***********************************************************************/ static HistoryBucket * get_hourly_history_bucket(int cmd) { time_t now = time(NULL); int hour = ((int) now) / 3600; int bucket = hour % HISTORY_HOURS; HistoryBucket *b = &(hourly_history[cmd][bucket]); if (b->elapsed != hour) { b->first = now; b->elapsed = hour; b->count = 0; b->workers = 0; b->ms = 0; b->activated = 0; b->reaped = 0; } b->last = now; return b; } /********************************************************************** * %FUNCTION: get_history_totals * %ARGUMENTS: * cmd -- which command's buckets we want * now -- current time * back -- how many seconds to go back from current time * total -- set to total count of messages * workers -- set to total worker count. Average number of busy workers/msg * is given by workers / total. * ms -- set to total scan time in milliseconds * activated -- set to number of workers activated * reaped -- set to number of workers reaped * %RETURNS: * 0 if all is OK, -1 otherwise. ***********************************************************************/ static int get_history_totals(int cmd, time_t now, int back, int *total, int *workers, BIG_INT *ms, int *activated, int *reaped) { int start = ((int) now) - back + 1; int end = (int) now; int i, bucket; *total = 0; *workers = 0; *ms = 0; *activated = 0; *reaped = 0; if (back <= 0) return 0; if (back > HISTORY_SECONDS) return -1; for (i = start; i <= end; i++) { bucket = i % HISTORY_SECONDS; if (history[cmd][bucket].elapsed == i) { (*total) += history[cmd][bucket].count; (*workers) += history[cmd][bucket].workers; (*ms) += history[cmd][bucket].ms; (*activated) += history[cmd][bucket].activated; (*reaped) += history[cmd][bucket].reaped; } } return 0; } /********************************************************************** * %FUNCTION: get_hourly_history_totals * %ARGUMENTS: * cmd -- which command's buckets we want * now -- current time * hours -- how many hours to go back (1-24) * total -- set to total count of messages * workers -- set to total worker count. Average number of busy workers/msg * is given by workers / total. * ms -- set to total scan time in milliseconds * secs -- set to actual number of elapsed seconds between first and last * entries * %RETURNS: * 0 if all is OK, -1 otherwise. ***********************************************************************/ static int get_hourly_history_totals(int cmd, time_t now, int hours, int *total, int *workers, BIG_INT *ms, int *secs) { int end = ((int) now) / 3600; int start; int max_sec = -1; int min_sec = -1; int i, bucket; *total = 0; *workers = 0; *ms = 0; *secs = 0; if (hours <= 0) { return 0; } if (hours > 24) { return -1; } start = end - hours + 1; for (i = start; i <= end; i++) { HistoryBucket *b; bucket = i % HISTORY_HOURS; b = &(hourly_history[cmd][bucket]); if (b->elapsed == i) { if (min_sec == -1 || b->first < min_sec) min_sec = b->first; if (b->last > max_sec) max_sec = b->last; (*total) += b->count; (*workers) += b->workers; (*ms) += b->ms; } } if (min_sec > -1) { *secs = (max_sec - min_sec); } return 0; } mimedefang-3.6/mimedefang-notify.7.in000066400000000000000000000105041475763067200175760ustar00rootroot00000000000000.\" $Id$ .\"" .TH MIMEDEFANG-NOTIFY 7 "8 February 2005" .UC 4 .SH NAME mimedefang-notify \- Conventions used by \fBmimedefang-multiplexor\fR(8) to notify an external program of state changes. .SH DESCRIPTION If you supply the \fB\-O\fR option to \fBmimedefang-multiplexor\fR, then it allows external programs to connect to a socket and be notified of certain state changes in the multiplexor. The external programs can react in whatever way they choose to these state changes. The external program that listens for state changes is referred to as a \fIlistener\fR. .SH NOTIFICATION OVERVIEW From the point of view of a listener, notification works like this: 1) The listener connects to a TCP or UNIX-domain socket. 2) The listener informs \fBmimedefang-multiplexor\fR of the \fImessage types\fR it is interested in. 3) The listener loops, reading messages from the socket and reacting to them. .SH MESSAGES Each message from the multiplexor normally consists of a single upper-case letter, possibly followed by a space and some arguments, and then followed by a newline. Two special messages are "*OK" followed by a newline, which is issued when a listener first connects, and "*ERR" followed by some text and a newline, which is issued when an error occurs. The normal messages are: .TP \fBB\fR This message is issued whenever a worker is killed because of a busy timeout. .TP \fBF\fR \fIn\fR This message is issued whenever the number of free workers changes. The parameter \fIn\fR is the number of free workers. .TP \fBR\fR This message is issued whenever someone has forced a filter reread. .TP \fBS\fR \fIn\fR \fInmsg\fR This message is issued whenever worker \fIn\fR's status tag changes. The status tag is a string indicating what the worker is currently doing; the \fB\-Z\fR option to the multiplexor allows the Perl code to update the status tag so you have a good idea what each worker is doing. .TP \fBU\fR This message is issued whenever a worker has died unexpectedly. .TP \fBY\fR This message is issued whenever the number of free workers changes from zero to non-zero. .TP \fBZ\fR This message is issued whenever the number of free workers falls to zero. .SH EXPRESSING INTEREST A listener does not receive any messages until it has \fIexpressed interest\fR in various message types. To express interest, the listener should send a question mark ("?") followed by the types of messages it is interested in, followed by a newline over the socket. For example, a listener interested in the R and F messages would send this line: ?RF A listener interested in every possible message type should send: ?* Once a listener has expressed interest, it may receive messages at any time, and should monitor the socket for messages. Note that a listener \fIalways\fR receives the special messages "*OK" and "*ERR", even if it has not expressed interest in them. .SH EXAMPLE The following Perl script implements a listener that, on Linux, rejects new SMTP connections if all workers are busy, and accepts them again once a worker is free. Existing SMTP connections are not shut down; the system merely refuses new connections if all the workers are busy. This script assumes that you have used the \fB\-O inet:4567\fR option to \fBmimedefang-multiplexor\fR. .nf #!/usr/bin/perl -w # # On Linux, prepare to use this script like this: # /sbin/iptables -N smtp_connect # /sbin/iptables -A INPUT --proto tcp --dport 25 --syn -j smtp_connect # Then run the script as root. use IO::Socket::INET; sub no_free_workers { print STDERR "No free workers!\\n"; system("/sbin/iptables -A smtp_connect -j REJECT"); } sub some_free_workers { print STDERR "Some free workers.\\n"; system("/sbin/iptables -F smtp_connect"); } sub main { my $sock; $sock = IO::Socket::INET->new(PeerAddr => '127.0.0.1', PeerPort => '4567', Proto => 'tcp'); # We are only interested in Y and Z messages print $sock "?YZ\\n"; $sock->flush(); while(<$sock>) { if (/^Z/) { no_free_workers(); } if (/^Y/) { some_free_workers(); } } # EOF from multiplexor?? Better undo firewalling system("/sbin/iptables -F smtp_connect"); } main(); .fi .SH SEE ALSO mimedefang.pl(8), mimedefang(8), mimedefang-multiplexor(8), mimedefang-filter(5) mimedefang-3.6/mimedefang-protocol.7.in000066400000000000000000000334241475763067200201350ustar00rootroot00000000000000.\" $Id$ .\"" .TH MIMEDEFANG-PROTOCOL 7 "8 February 2005" .UC 4 .SH NAME mimedefang-protocol \- Conventions used by \fBmimedefang\fR(8) to communicate with filter programs. .SH DESCRIPTION \fBmimedefang\fR(8) and \fBmimedefang-multiplexor\fR(8) provide a simplified mechanism for hooking scripts and programs into Sendmail's milter API. The milter API is multi-threaded and written in C; \fBmimedefang\fR lets you write single-threaded filters written in the language of your choice. Some of the flexibility and speed of milter is sacrificed, but the ease of writing filters more than compensates for this slight loss. This manual describes how \fBmimedefang\fR communicates with the filter program, and gives you enough information to write your own filter program as a replacement for \fBmimedefang.pl\fR if you wish. .SH PROTOCOL OVERVIEW The protocol is a simple file-based protocol. For each invocation of a filter, \fBmimedefang\fR creates a unique working directory and populates it with files. It calls the filter, which is expected to populate the working directory with more files, which communicate the scan results back to \fBmimedefang\fR. This simple mechanism allows you to easily write filters in scripting languages without worrying about C-level details. .SH FILTER INVOCATION The filter program may be invoked in one of five ways: .TP \fIfilter_prog\fR \fIdirectory\fR If the program is invoked with a single argument which is an absolute path name (called the \fBworking directory\fR, the program is expected to perform filtering in that directory and then exit. .TP \fIfilter_prog\fR \fB\-server\fR If the program is invoked with the single argument \fB\-server\fR, it is expected to run as a server. See SERVER MODE for details. .TP \fIfilter_prog\fR \fB\-serveru\fR If the program is invoked with the single argument \fB\-serveru\fR, it is expected to run as a server. In addition, anything it prints to file descriptor 3 is used to update the "worker status" field in the multiplexor. This lets the filter inform administrators exactly what it is doing. (See the \fB\-Z\fR option to \fBmimedefang-multiplexor\fR.) .TP \fIfilter_prog\fR \fB\-embserver\fR Similar to \fB\-server\fR, but used by the embedded Perl code. The program should run any initialization routines and then exit. The multiplexor will subsequently call the Perl routine \fBdo_main_loop\fR when it is time for the worker to begin running in server mode. .TP \fIfilter_prog\fR \fB\-embserveru\fR Similar to \fB\-embserver\fR with the additional magic of updating the worker status from data written to file descriptor 3. .SH INITIAL FILE LAYOUT When the filter begins a scan, it should change directories to the working directory. In that directory, it will find the following files. .TP .B INPUTMSG A file containing the complete input e-mail message, including headers. .TP .B HEADERS A file containing just the headers, one per line. Headers which are continued over several lines in the original message are collapsed into a single line in this file. .TP .B COMMANDS A file containing a list of commands. Each command is a single letter and may be followed by arguments. Each command is on its own line. .SH THE COMMANDS FILE All commands have their arguments encoded as follows: All characters outside the range 33 to 126 ASCII, as well as the characters "%", "\\", "'" and double-quote, are replaced by a percent sign followed by two hex digits specifying the character's numerical value. The filter must un-escape the arguments when it reads the COMMANDS file. .\""Fix emacs highlighting The commands from the C to Perl filters are: .TP .B S\fIsender\fR The sender of the message. .TP .B s\fIesmtp_arg\fR An ESMTP argument associated with the sender (such as SIZE=54432). There is one \fBs\fR line for each ESMTP argument. .TP .B U\fIsubject\fR The message subject. .TP .B X\fImessage_id\fR The Message-ID. .TP .B R\fIrecipient\fR \fImailer\fR \fIhost\fR \fIaddr\fR A recipient. There is one \fBR\fR line for each recipient. The \fImailer\fR, \fIhost\fR and \fIaddr\fR parts of the line are the values of the Sendmail {rcpt_mailer}, {rcpt_host} and {rcpt_addr} macros if they are available, or "?" if not. .TP .B r\fIesmtp_arg\fR An ESMTP argument associated with the \fImost recent\fR recipient (such as NOTIFY=never). There is one \fBr\fR line for each SMTP argument. .TP .B ! If this command is present, there are suspicious characters in the message headers. .TP .B ? If this command is present, there are suspicious characters in the message body. .TP .B I\fIhost_addr\fR The SMTP relay host's IP address in dotted-quad notation. .TP .B i\fIidentifier\fR An identifier generated by MIMEDefang. On a given host, this identifier is very likely to be unique over a timespan of about 24 years. .TP .B J\fIhost_addr\fR The "real" SMTP relay host's IP address in dotted-quad notation. Multi-stage MIMEDefang relays can use a special IP validation header so that even the innermost MIMEDefang relay can see the "original" relay's IP address. .TP .B H\fIhost_name\fR The SMTP relay host name. .TP .B E\fIargument\fR The argument to the SMTP "EHLO" or "HELO" command. .TP .B Q\fIqid\fR The message's Sendmail queue-ID. .TP .B =\fImacro\fR \fIval\fR Set the value of the specified Sendmail macro to \fIval\fR. Both \fImacro\fR and \fIval\fR are percent-encoded, but the single space character between them is not. .SH FILTER OPERATION When the filter performs a scan, it can make use of all the information in the files mentioned previously. If the filter needs temporary working files, it should create a subdirectory under the working directory for its own use. In this case, you do not have to clean up your working files, because \fBmimedefang\fR deletes the working directory when the filter returns. .SH FINAL FILE LAYOUT The filter communicates the results of the scan back to \fBmimedefang\fR by creating additional files in the working directory. The most important file is called RESULTS, and it contains a list of one-letter, one-line commands back to the filter. As usual, command arguments are percent-escaped. The commands from the filter back to \fBmimedefang\fR are: .TP .B B\fIcode\fR \fIdsn\fR \fIreply_text\fR Bounce (reject) the message with the specified SMTP reply code, DSN code and reply text. .TP .B D Silently discard the message and pretend it was delivered. .TP .B T\fIcode\fR \fIdsn\fR \fIreply_text\fR Return an SMTP temporary failure code with the specified SMTP code, DSN and reply text. .TP .B C Replace the message body. If this command is present, the file NEWBODY must contain the new message body. .TP .B M\fIheader_val\fR Replace the MIME Content-Type header with a new value. Used to change MIME boundaries or convert non-MIME to MIME messages. .TP .B H\fIheader\fR \fIval\fR Add a new header \fIheader\fR with value \fIval\fR. The \fIheader\fR should not contain a colon. Each of \fIheader\fR and \fIval\fR is percent-escaped, but the single space between them is not. .TP .B N\fIheader\fR \fIindex\fR \fIval\fR Adds a new header \fIheader\fR with value \fIval\fR in position \fIindex\fR. An index of zero specifies that the new header should be prepended before all existing headers. .TP .B I\fIheader\fR \fIindex\fR \fIval\fR Replace the \fIindex'th\fR occurrence of \fIheader\fR with value \fIval\fR. The \fIindex\fR is 1-based. The \fIheader\fR should not contain a colon. Each of \fIheader\fR, \fIindex\fR and \fIval\fR is percent-escaped, but the single space between them is not. .TP .B J\fIheader\fR \fIindex\fR Delete the \fIindex\fR'th occurrence of \fIheader\fR. .TP .B R\fIrecip\fR Add a new recipient \fIrecip\fR to the message. .TP .B S\fIrecip\fR Delete \fIrecip\fR from the list of message recipients. .TP .B f\fIsender\fR Change the envelope sender to \fIsender\fR. This is only supported by Sendmail 8.14.0 and higher. .TP .B F Indicate that we have finished issuing commands. Anything after an F line is ignored. .SH SERVER MODE In server mode, \fBmimedefang-multiplexor\fR runs the filter program continuously in a server loop. The filter program reads commands from standard input, and writes results to standard output. The filter program \fImust\fR exit shortly after it sees EOF on its standard input. If it does not exit within 10 seconds, it will be terminated with SIGTERM. If that still does not work, then after a further 10 seconds, it is killed with SIGKILL. .TP .B SERVER COMMANDS All server commands are single line commands. Each command is followed by a space-separated list of arguments; each argument is percent-encoded. The commands defined are: .TP .B ping Elicits a reply of "PONG" from the server. .TP .B scan \fIqueue_id\fR \fIdir\fR Run a scan for the mail identiefied by the Sendmail queue-ID \fIqueue_id\fR in the directory \fIdir\fR. The command is terminated with a newline. The server must write a newline-terminated "ok" if the scan completed successfully, or "error: msg" if something went wrong. .TP .B relayok \fIip_addr\fR \fIhostname\fR \fIclient_port\fR \fIdaemon_ip\fR \fIdaemon_port\fR Test whether or not to accept a connection from the specified host. The server must write "ok 1" if we will accept the connection, or "ok 0 error_message code dsn" if not. It can indicate a temporary failure by writing "ok -1 error_message code dsn". Note that even if the connection is accepted, a later scan can still reject the message based on other criteria. "ip_addr" is the IP address of the relay and "hostname" is the hostname (if it could be determined; otherwise, the IP address in square brackets). .TP .B helook \fIip_addr\fR \fIhostname\fR \fIhelo_string\fR \fIclient_port\fR \fIdaemon_ip\fR \fIdaemon_port\fR Test whether or not to accept the HELO/EHLO command. The server must write "ok 1" if we will accept the mail attempt, or "ok 0 error_message code dsn" if not. "ok -1 error_message code dsn" indicates a temporary failure. \fIhelo_string\fR is the argument to the HELO/EHLO command. The remaining arguments have the same meaning as in \fBrelayok\fR. .TP .B senderok \fIsender_addr\fR \fIip_addr\fR \fIhostname\fR \fIhelo_string\fR \fIdir\fR \fIqueue_id\fR [\fIesmtp_args\fR...] Test whether or not to accept mail from the specified sender. The server must write "ok 1" if we will accept the mail attempt, or "ok 0 error_message code dsn" if not. "ok -1 error_message code dsn" indicates a temporary failure. Note that even if the sender is accepted, a later scan can still reject the message based on other criteria. "sender_addr" is the sender's e-mail address. The "ip_addr" and "hostname" arguments are as in \fBrelayok\fR. "helo_string" is the argument to the SMTP HELO/EHLO command. "dir" is the MIMEDefang spool directory, and "queue_id" is the Sendmail queue identifier. The optional "esmtp_args" are space-separated, percent-encoded ESMTP arguments supplied with the MAIL FROM: command. .TP .B recipok \fIrecip_addr\fR \fIsender_addr\fR \fIip_addr\fR \fIhostname\fR \fIfirst_recip\fR \fIhelo_string\fR \fIdir\fR \fIqueue_id\fR [\fIesmtp_args\fR...] Test whether or not to accept mail for the specified recipient. The server must write "ok 1" if we will accept it, or "ok 0 error_message code dsn" if not. ok -1 error_message code dsn" indicates a temporary-failure. "recip_addr" is the argument to the RCPT TO: command, and "first_recip" is the argument to the first RCPT TO: command for this message. Other arguments are as in \fBsenderok\fR. .TP .B map \fImap_name\fR \fIkey\fR If you are using a map socket (the \fB\-N\fR option to \fBmimedefang-multiplexor\fR), then the server should look up the key \fIkey\fR in the map \fImap_name\fR. The server should print a single line to standard output. The first word on the line should be one of OK, NOTFOUND, TEMP, TIMEOUT or PERM, indicating a successful lookup, absence of the key, a temporary failure, a timeout or a permanent failure, respectively. This should be followed by a space and a percent-encoded string representing the value of the key (if it was found) or an optional error message (if something went wrong.) .TP .B tick \fIband\fR The filter should run \fBfilter_tick\fR with the specified \fIband\fR argument. It should print a single line to standard output: tock \fIband\fR .TP .B Additional Commands The filter can define a function \fBfilter_unknown_cmd\fR that can extend the list of server commands. If you do this, make sure all of your commands start with an upper-case letter to avoid conflicts if more built-in commands are defined in the future. .TP .B SERVER REPLIES The reply codes are: .TP .B ok [\fIreturn_code\fR] [\fIparameters\fR] The operation completed successfully. Some operations have an associated return code, and possibly other parameters as well. See the source code for the gory details. .TP .B error: \fIMessage\fR The operation failed. \fIMessage\fR may give additional details. .PP In server mode, you should \fInot\fR write anything to standard output except reply codes, or the multiplexor will become confused. You should \fInot\fR terminate the program in server mode; simply echo an \fBerror:\fR reply and return to the server loop. When you send a reply code back to the multiplexor, be sure to terminate it with a newline, and to flush standard output. If your program uses the Standard I/O library, standard output may not be flushed immediately, and \fBmimedefang-multiplexor\fR will wait forever for the filter's reply, and eventually kill the filter on the assumption it has hung up. In server mode, if the filter program receives a SIGINT signal, it must terminate. This is used by \fBmimedefang-multiplexor\fR to terminate workers after they have processed a given number of e-mail messages. .SH AUTHOR \fBmimedefang\fR was written by Dianne Skoll . The \fBmimedefang\fR home page is \fIhttps://www.mimedefang.org/\fR. .SH SEE ALSO mimedefang.pl(8), mimedefang(8), mimedefang-multiplexor(8), mimedefang-filter(5) mimedefang-3.6/mimedefang-release.pl.in000066400000000000000000000144301475763067200201550ustar00rootroot00000000000000#!@PERL@ # -*- Perl -*- ###################################################################### # mimedefang-release -d directory # release a message from quarantine directory # # This program was derived from fang.pl, # original script available on contrib/ directory # # * This program may be distributed under the # terms of the GNU General Public License, # Version 2. # ###################################################################### =head1 NAME mimedefang-release - a tool to release quarantined email messages =head1 DESCRIPTION mimedefang-release(8) is a tool that permits to release quarantined messages or to attach the messages to a new email message. =head1 SYNOPSIS mimedefang-release [options] ... =head1 OPTIONS -a enable attach mode, the released email will be sent as an attachment to the user. -h display the help -d path to the quarantined directory, it can be an absolute path or relative to MIMEDefang quarantine spool directory. -s set a custom subject for the email, this option is valid only in attach mode. -S specify an smtp server, in this mode the quarantined email will be delivered to the original user without modifications. -t enable TLS when delivering the email in smtp mode. -z compress the quarantined email using Archive::Zip. this option is valid only in attach mode. =head1 EXAMPLES mbox mode: mimedefang-release -s "Message Released" -a -z -d 2023-04-16-14/qdir-2023-04-16-14.36.05-001 smtp mode: mimedefang-release -S 192.168.0.254 -d 2023-04-16-14/qdir-2023-04-16-14.36.05-001 =head1 AUTHOR mimedefang-release(8) was written by Giovanni Bechis L<>. The mimedefang home page is L. =head1 SEE ALSO mimedefang.pl(8), mimedefang-filter(5), mimedefang(8), mimedefang-protocol(7), watch-mimedefang(8) =cut use strict; use warnings; use Carp; use Getopt::Std; use File::Temp qw ( :POSIX ); use MIME::Entity; use Mail::MIMEDefang; use constant HAS_NET_SMTP => eval { require Net::SMTP; }; init_globals(); detect_and_load_perl_modules(); $Features{'Path:SENDMAIL'} = '@SENDMAILPROG@'; $Features{'Path:QUARANTINEDIR'} = '@QDIR@'; my %opts = (); getopts('ahd:s:S:tz', \%opts); if (exists $opts{'?'} || exists $opts{'h'}) { print "mimedefang-release -d [ -a ] [ -s ] [ -S ] [ -t ] [ -z ]"; exit 0; } my $qdir = $opts{'d'}; $qdir //= ''; if($qdir !~ /^\//) { $qdir = $Features{'Path:QUARANTINEDIR'} . '/' . $qdir; } unless (-d $qdir and -f "$qdir/ENTIRE_MESSAGE" and -f "$qdir/RECIPIENTS" and -f "$qdir/SENDER" and -f "$qdir/HEADERS") { croak("$qdir is not a valid directory!"); } my $attach = 0; if(defined $opts{'a'}) { $attach = 1; } my $subject; if(defined $opts{'s'}) { $subject = $opts{'s'}; } my $smtp; if(defined $opts{'S'}) { if(not HAS_NET_SMTP) { croak("Net::SMTP is required to use smtp mode"); } $smtp = $opts{'S'}; } my $tls = 0; if(defined $opts{'t'}) { $tls = 1; } my $zip = 0; if(defined $opts{'z'} and $Features{"Archive::Zip"} eq 1) { $zip = 1; } elsif((not defined $Features{"Archive::Zip"}) or ($Features{"Archive::Zip"} eq 0)) { croak("-z option needs Archive::Zip Perl module"); } if($zip and not $attach) { croak("-z option cannot be used without -a option"); } if(($zip or $attach) and defined $smtp) { croak("-z and -a options cannot be used with -S option"); } if($tls and not $smtp) { croak("-t option cannot be used without -S option"); } my $rc = make_message(qdir => $qdir, subject => $subject, zip => $zip); exit $rc; sub make_message { my (%params) = @_; my $dir = $params{qdir}; my $subj = $params{subject}; my $use_zip = $params{zip}; my @to; my $toh; open($toh, "<", "$dir/RECIPIENTS") || croak("Can't read $dir/RECIPIENTS: $!"); while (<$toh>) { chomp; push @to,$_; } close($toh); my ($from, $fromh); open($fromh, "<", "$dir/SENDER") || croak("Can't read $dir/SENDER: $!"); $from = <$fromh>; close($fromh); my ($date, $headerh, $subject); open($headerh, "<", "$dir/HEADERS") || croak("Can't read $dir/HEADERS: $!"); while (<$headerh>) { chomp; SWITCH: { /Date:/ && do { s/Date: //; $date = $_; }; /Subject:/ && do { s/Subject: //; $subject = $_; }; } } close($headerh); # use subject passed as parameter if(defined($subj)) { $subject = $subj; } my $folderh; opendir($folderh, "$dir") || Error("Can't list $dir: $!"); my @files = readdir($folderh); close($folderh); my $msg = MIME::Entity->build( From => $DaemonAddress, To => join(',',@to), Subject => $subject, Data => "A quarantined message has been released,\nyou can find the original message in the attached file.", Type => 'multipart/mixed', ); my $tmpzip; if($attach) { if($use_zip) { my $zipfile = Archive::Zip->new(); my $file_member = $zipfile->addFile("$dir/ENTIRE_MESSAGE", 'released-message.eml'); $tmpzip = tmpnam(); # this code burps an ugly message if it fails, but that's redirected elsewhere # AZ_OK is a constant exported by Archive::Zip my $az_ok; eval '$az_ok = AZ_OK'; my $status = $zipfile->writeToFileNamed($tmpzip); croak "error while compressing file $tmpzip" if $status != $az_ok; $msg->attach( Type => 'application/zip', Path => $tmpzip, Filename => 'released-message.zip', ); } else { $msg->attach( Type => 'message/rfc822', Path => "$dir/ENTIRE_MESSAGE", Filename => 'released-message.eml', ); } if(send_mail($DaemonAddress, $DaemonName, join(',', @to), $msg->stringify)) { unlink($tmpzip) if ($zip); return 1; } else { unlink($tmpzip) if ($zip); return 0; } } elsif(defined $smtp) { my $srv = Net::SMTP->new($smtp, SSL => $tls, ); open my $fh, '<', "$dir/ENTIRE_MESSAGE" or croak("Cannot open quarantined message on directory $dir"); local $/ = undef; my $entire_message = <$fh>; close $fh; $srv->mail($from); if($srv->to(join(',',@to))) { $srv->data(); $srv->datasend($entire_message); $srv->dataend(); } $srv->quit(); } else { croak("Invalid options, at least -S or -a options must be used"); } } mimedefang-3.6/mimedefang.8.in000066400000000000000000000246011475763067200162740ustar00rootroot00000000000000.\" $Id$ .\"" .TH MIMEDEFANG 8 "8 February 2005" .UC 4 .SH NAME mimedefang \- Sendmail MIME mail filter .SH SYNOPSIS .B mimedefang prcap .B mimedefang \fR-p \fIconnection\fR -m \fImx_socket_name\fR -U \fIuser\fR [\fIoptions\fR] .SH DESCRIPTION \fBmimedefang\fR is a filter built around Sendmail 8.11's \fImilter\fR API for mail filters. It collects each incoming message and runs a filter on the message. This is useful for deleting attachments which may be a security risk on poorly-designed systems like Microsoft Windows. \fBmimedefang\fR does not actually run the Perl filter; instead, it communicates with \fBmimedefang-multiplexor\fR(8), which manages a pool of persistent Perl processes. See the \fBmimedefang-multiplexor\fR man page for additional information. .SH OPTIONS If you invoke \fBmimedefang\fR with the single argument \fBprcap\fR, it prints information about the version of Milter it is linked against and exits. Otherwise, you should invoke \fBmimedefang\fR as shown in the second line of the SYNOPSIS. .TP .B \-U \fIuser\fR Runs \fBmimedefang\fR as \fIuser\fR rather than \fIroot\fR. The \fIuser\fR argument must match the argument to \fBmimedefang-multiplexor\fR's \fB\-U\fR option as well. .TP .B \-y If the \fB\-y\fR command-line option is given, MIMEDefang will call smfi_setsymlist to set the list of macros it wants. \fIThis function leaked memory in versions of Sendmail prior to 8.14.4\fR so by default we do not call it. If you are running an older version of sendmail, you should explicitly set the list of macros you want in the Sendmail configuration file. .TP .B \-z \fIspooldir\fR Set the spool directory to \fIspooldir\fR. If this option is omitted, the spool directory defaults to @SPOOLDIR@. .TP .B \-p \fIconnection\fR The \fB\-p\fR switch is required and specifies the \fImilter\fR connection type. Typically, you should run \fBmimedefang\fR on the same computer as \fBsendmail\fR. Therefore, you should use a UNIX-domain socket for the connection type. The suggested value for the \fB\-p\fR switch is \fBmimedefang.sock\fR under the spool directory. .TP .B \-m \fImx_socket_name\fR Specifies the socket for communicating with \fBmimedefang-multiplexor\fR(8). The \fImx_socket_name\fR specifies the path of the UNIX-domain socket. See \fBmimedefang-multiplexor\fR(8) for details. .TP .B \-b \fIbacklog\fR Sets the "backlog" argument to the \fBlisten\fR(2) system call to \fIbacklog\fR. If this option is omitted, then the operating-system default backlog is used. .TP \fB\-G\fR Normally, \fBmimedefang\fR uses a umask of 077 when creating the milter socket and files. If you would like the socket to be readable and writeable by the group and files to be group-readable, supply the \fB\-G\fR option. This causes the umask to be 007 whenever UNIX-domain sockets are created and 027 whenever files are created. \fINote\fR: if your milter library is too old to have the smfi_opensocket() function, the \fB\-G\fR option causes \fBmimedefang\fR to use a umask of 007 throughout its execution. Note that by default, @SPOOLDIR@ is created with mode 0700. If you use the \fB\-G\fR option, you probably should change the mode to 0750. .TP .B \-d The \fB\-d\fR switch causes \fBmimedefang\fR \fInot\fR to delete the temporary spool files it creates for incoming messages. This is for debugging purposes only and should \fInever\fR be used on a production mail server. .TP .B \-r Causes \fBmimedefang\fR to perform a relay check before processing any messages. It calls into a user-supplied Perl function called \fBfilter_relay\fR with the IP address and host name of the sending relay. (See \fBmimedefang-filter\fR(5) for details.) .TP .B \-H Causes \fBmimedefang\fR to perform a HELO check before processing any messages. It calls into a user-supplied Perl function called \fBfilter_helo\fR with the IP address and host name of the sending relay, and the HELO argument. (See \fBmimedefang-filter\fR(5) for details.) .TP .B \-s Causes \fBmimedefang\fR to perform a sender check before processing the message body. It calls into a user-supplied Perl function called \fBfilter_sender\fR with the envelope address of the sender. (See \fBmimedefang-filter\fR(5) for details.) .TP .B \-t Causes \fBmimedefang\fR to perform recipient checks before processing the message body. It calls into a user-supplied Perl function called \fBfilter_recipient\fR with the envelope address of each recipient. (See \fBmimedefang-filter\fR(5) for details.) .TP .B \-q Permits the multiplexor to queue new connections. See the section QUEUEING REQUESTS in the mimedefang-multiplexor man page. Note that this option and the \fB\-R\fR option are mutually-exclusive. If you supply \fB\-q\fR, then \fB\-R\fR is ignored. .TP .B \-k Causes \fBmimedefang\fR \fInot\fR to delete working directories if a filter fails. This lets you obtain the message which caused the filter to fail and determine what went wrong. \fBmimedefang\fR logs the directory containing the failed message using syslog. .TP .B \-P \fIfileName\fR Causes \fBmimedefang\fR to write its process-ID (after becoming a daemon) to the specified file. The file will be owned by root. .TP .B \-o \fIfileName\fR Causes \fbmimedefang\fR to use \fIfileName\fR as a lock file to avoid multiple instances from running. If you supply \fB\-P\fR but not \fB\-o\fR, then \fbmimedefang\fR constructs a lock file by appending ".lock" to the pid file. However, this is less secure than having a root-owned pid file in a root-owned directory and a lock file writable by the user named by the \fB\-U\fR option. (The lock file must be writable by the \fB\-U\fR user.) .TP .B \-R \fInum\fR Normally, \fBmimedefang\fR tempfails a new SMTP connection if there are no free workers. Supplying the \fB\-R\fR \fInum\fR option makes \fBmimedefang\fR tempfail new connections if there are fewer than \fInum\fR free workers, \fIunless\fR the connection is from the local host. This allows you to favour connections from localhost so your clientmqueue doesn't build up. Note that supplying \fB\-R 0\fR is subtly different from omitting the option; in this case, \fBmimedefang\fR permits new connections from localhost to queue, but not connections from other hosts (unless you also supply the \fB\-q\fR option.) The purpose of the \fB\-R\fR option is to reserve resources for clientmqueue runs. Otherwise, on a very busy mail server, clientmqueue runs can starve for a long time, leading to delays for locally-generated or streamed mail. We recommend using a small number for \fInum\fR; probably no more than 3 or 10% of the total number of workers (whichever is smaller.) Note that this option and the \fB\-q\fR option are mutually-exclusive. If you supply \fB\-q\fR, then \fB\-R\fR is ignored. .TP .B \-C Conserve file descriptors by opening and closing disk files more often. (Disk files are never held open across Milter callbacks.) While this shortens the length of time a file descriptor is open, it also leaves more opportunities for the open to fail. We do not recommend the use of this flag except on very busy systems that exhibit failures due to a shortage of file descriptors. .TP .B \-T Causes \fBmimedefang\fR to log the run-time of the Perl filter using syslog. .TP .B \-x \fIstring\fR Add \fIstring\fR as the content of the X-Scanned-By: header. If you set \fIstring\fR to the empty string (i.e. \-x ""), then no X-Scanned-By: header will be added. .TP .B \-X Do not add an X-Scanned-By: header. Specifying \-X is equivalent to specifying \-x "". .TP .B \-D Do not fork into the background and become a daemon. Instead, stay in the foreground. Useful mainly for debugging or if you have a supervisory process managing \fBmimedefang\fR. .TP .B \-M This option is obsolete; it is accepted for backward-compatibility, but is ignored. .TP .B \-N Normally, \fBmimedefang\fR sees all envelope recipients, even ones that Sendmail knows to be invalid. If you don't want Sendmail to perform a milter callback for recipients it knows to be invalid, invoke \fBmimedefang\fR with the \-N flag. \fIPlease note that this flag only works with Sendmail and Milter 8.14.0 and newer. It has no effect if you're running an older version of Sendmail or Milter.\fR .TP \-S \fIfacility\fR Specifies the syslog facility for log messages. The default is \fImail\fR. See \fBopenlog\fR(3) for a list of valid facilities. You can use either the short name ("mail") or long name ("LOG_MAIL") for the facility name. .TP \-a \fImacro\fR Pass the value of the specified Sendmail macro through to the Perl filter. You can repeat the \-a option to write more macros than the built-in defaults. Note that in addition to asking \fBmimedefang\fR to pass the macro value to the filter, you must configure Sendmail to pass the macro through to \fBmimedefang\fR using the confMILTER_MACROS_ENVFROM definition in Sendmail's m4 configuration file. .TP .B \-c Strip "bare" carriage-returns (CR) characters from the message body. A bare CR should never appear in an e-mail message. Older versions of \fBmimedefang\fR used to strip them out automatically, but now they are left in by default. The \fB\-c\fR option enables the older behavior. .TP .B \-h Print usage information and exit. .SH OPERATION When \fBmimedefang\fR starts, it connects to \fBsendmail\fR using the \fImilter\fR API. (See the Sendmail 8.11 documentation.) For each incoming message, \fBmimedefang\fR creates a temporary directory and saves information in the directory. At various phases during the SMTP conversation, \fBmimedefang\fR communicates with \fBmimedefang-multiplexor\fR to perform various operations. \fBmimedefang-multiplexor\fR manages a pool of persistent Perl processes that actually perform the mail scanning operations. .PP When a Perl process scans an e-mail, the temporary spool directory contains certain files; details of the communication protocol between \fBmimedefang\fR and the Perl script are in \fBmimedefang-protocol\fR(7). .SH WARNINGS \fBmimedefang\fR does violence to the flow of e-mail. The Perl filter is quite picky and assumes that MIME e-mail messages are well-formed. While I have tried to make the script safe, I take \fIno responsibility\fR for lost or mangled e-mail messages or any security holes this script may introduce. .SH AUTHOR \fBmimedefang\fR was written by Dianne Skoll . The \fBmimedefang\fR home page is \fIhttp://www.mimedefang.org/\fR. .SH SEE ALSO mimedefang.pl(8), mimedefang-filter(5), mimedefang-multiplexor(8), mimedefang-protocol(7), mimedefang-release(8) mimedefang-3.6/mimedefang.c000066400000000000000000002475011475763067200157500ustar00rootroot00000000000000/*********************************************************************** * * mimedefang.c * * C interface to the attachment-filter program for stripping or altering * MIME attachments in incoming Sendmail connections. * * Copyright (C) 2000-2005 Roaring Penguin Software Inc. * http://www.roaringpenguin.com * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * * This program was derived from the sample mail filter included in * libmilter/README in the Sendmail 8.11 distribution. ***********************************************************************/ #define _DEFAULT_SOURCE 1 #define MAX_ML_LINES 31 #ifdef HAVE_SOCKLEN_T typedef socklen_t md_socklen_t; #else typedef int md_socklen_t; #endif #include "config.h" #include "mimedefang.h" #include "dynbuf.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include "libmilter/mfapi.h" #include "milter_cap.h" #include #include /* Solaris does not define AF_LOCAL */ #ifndef AF_LOCAL #define AF_LOCAL AF_UNIX #endif #ifdef ENABLE_DEBUGGING #include #define DEBUG(x) x extern void *malloc_debug(void *, size_t, char const *fname, int); extern char *strdup_debug(void *, char const *, char const *, int); extern void free_debug(void *, void *, char const *, int); #undef malloc #undef strdup #undef free #define malloc_with_log(x) malloc_debug(ctx, x, __FILE__, __LINE__) #define strdup_with_log(x) strdup_debug(ctx, x, __FILE__, __LINE__) #define malloc(x) malloc_debug(ctx, x, __FILE__, __LINE__) #define strdup(x) strdup_debug(ctx, x, __FILE__, __LINE__) #define free(x) free_debug(ctx, x, __FILE__, __LINE__) #else #define DEBUG(x) (void) 0 #endif /* If we don't have inet_ntop, we need to protect inet_ntoa with a mutex */ #ifndef HAVE_INET_NTOP static pthread_mutex_t ntoa_mutex = PTHREAD_MUTEX_INITIALIZER; #endif extern int find_syslog_facility(char const *facility_name); #define DEBUG_ENTER(func) DEBUG(syslog(LOG_DEBUG, "%p: %s(%d): ENTER %s", ctx, __FILE__, __LINE__, func)) #define DEBUG_EXIT(func, ret) DEBUG(syslog(LOG_DEBUG, "%p: %s(%d): EXIT %s: %s", ctx, __FILE__, __LINE__, func, ret)) #define SCAN_BODY "MIMEDefang " VERSION /* Call a Milter smfi_xxxx function, but syslog if it doesn't return * MI_SUCCESS */ #define MD_SMFI_TRY(func, args) do { if (func args != MI_SUCCESS) syslog(LOG_WARNING, "%s: %s returned MI_FAILURE", data->qid, #func); } while (0) char *scan_body = NULL; static char *pidfile = NULL; static char *lockfile = NULL; #define KEY_FILE CONFDIR "/mimedefang-ip-key" /* In debug mode, we do not delete working directories. */ int DebugMode = 0; /* Conserve file descriptors by reopening files in each callback */ int ConserveDescriptors = 0; /* Log "eom" run-times */ int LogTimes = 0; /* Show me rejected recipients? (Sendmail 8.14.0 and higher only */ int SeeRejectedRecipients = 1; /* Default Backlog for "listen" */ static int Backlog = -1; /* Strip out bare CR characters? */ static int StripBareCR = 0; /* Allow new connections to queue? */ static int AllowNewConnectionsToQueue = 0; /* Is it OK to call smfi_setsymlist? */ static int setsymlist_ok = 0; /* Run as this user */ static char *user = NULL; extern int drop_privs(char const *user, uid_t uid, gid_t gid); /* NOQUEUE */ static char *NOQUEUE = "NOQUEUE"; /* My IP address */ static char *MyIPAddress = NULL; /* "Equivalent-to-loopback" address */ static char *EquivToLoopback = NULL; /* Header name for validating IP addresses */ static char ValidateHeader[256]; /* Additional Sendmail macros to pass along */ /* Note that libmilter has a hard limit of 50 and we include 20 in StandardSendmailMacros */ #define MAX_ADDITIONAL_SENDMAIL_MACROS 30 /* Standard Sendmail macros */ /* We can't make it char const * because libmilter is not const-correct. */ static char *StandardSendmailMacros[] = { "_", "auth_authen", "auth_author", "auth_ssf", "auth_type", "cert_issuer", "cert_subject", "cipher", "cipher_bits", "daemon_name", "daemon_port", "i", "if_addr", "if_name", "j", "mail_addr", "mail_host", "mail_mailer", "tls_version", "verify", "rcpt_addr", "rcpt_host", "rcpt_mailer", /* End of macros MUST be marked with NULL! */ NULL }; static char *AdditionalMacros[MAX_ADDITIONAL_SENDMAIL_MACROS]; static int NumAdditionalMacros = 0; /* Keep track of private data -- file name and fp for writing e-mail body */ struct privdata { char *hostname; /* Name of connecting host */ char *hostip; /* IP address of connecting host */ unsigned int hostport; /* Port of connecting host */ char *myip; /* My IP address, from Sendmail macro */ unsigned int daemon_port; /* Daemon port from Sendmail macro */ char *sender; /* Envelope sender */ char *firstRecip; /* Address of first recipient */ char *dir; /* Work directory */ char *heloArg; /* HELO argument */ char *qid; /* Queue ID */ unsigned char qid_written; /* Have we written qid to COMMANDS? */ int fd; /* File for message body */ int headerFD; /* File for message headers */ int cmdFD; /* File for commands */ int numContentTypeHeaders; /* How many Content-Type headers have we seen? */ int seenMimeVersionHeader; /* True if there was a MIME-Version header */ unsigned char validatePresent; /* Saw a relay-address validation header */ unsigned char suspiciousBody; /* Suspicious characters in message body? */ unsigned char lastWasCR; /* Last char of body chunk was CR? */ unsigned char filterFailed; /* Filter failed */ }; static void set_queueid(SMFICTX *ctx); static void append_macro_value(dynamic_buffer *dbuf, SMFICTX *ctx, char *macro); static void append_mx_command(dynamic_buffer *dbuf, char cmd, char const *buf); static int write_dbuf(dynamic_buffer *dbuf, int fd, struct privdata *data, char const *filename); static void append_percent_encoded(dynamic_buffer *dbuf, char const *buf); static int safe_append_header(dynamic_buffer *dbuf, char *str); static sfsistat cleanup(SMFICTX *ctx); static sfsistat mfclose(SMFICTX *ctx); static int do_sm_quarantine(SMFICTX *ctx, char const *reason); static void remove_working_directory(SMFICTX *ctx, struct privdata *data); static char const *SpoolDir = NULL; static char const *NoDeleteDir = NULL; static char const *MultiplexorSocketName = NULL; static int set_reply(SMFICTX *ctx, char const *first, char const *code, char const *dsn, char const *reply); #define DATA ((struct privdata *) smfi_getpriv(ctx)) /* Size of chunk when replacing body */ #define CHUNK 4096 /* Number of file descriptors to close when forking */ #define CLOSEFDS 256 /* Do relay check? */ static int doRelayCheck = 0; /* Do HELO check? */ static int doHeloCheck = 0; /* Do sender check? */ static int doSenderCheck = 0; /* Do recipient check? */ static int doRecipientCheck = 0; /* Keep directories around if multiplexor fails? */ static int keepFailedDirectories = 0; /* Number of scanning workers reserved for connection from loopback */ static int workersReservedForLoopback = -1; static void set_dsn(SMFICTX *ctx, char *buf2, int code); #define NO_DELETE_NAME "/DO-NOT-DELETE-WORK-DIRS" #ifdef ENABLE_DEBUGGING /********************************************************************** *%FUNCTION: handle_sig *%ARGUMENTS: * s -- signal number *%RETURNS: * Nothing *%DESCRIPTION: * Handler for SIGSEGV and SIGBUS -- logs a message and returns -- hopefully, * we'll get a nice core dump the second time around ***********************************************************************/ static void handle_sig(int s) { syslog(LOG_ERR, "WHOA, NELLY! Caught signal %d -- this is bad news. Core dump at 11.", s); /* Default is terminate and core. */ signal(s, SIG_DFL); /* Return and probably cause core dump */ } #endif /********************************************************************** * %FUNCTION: get_fd * %ARGUMENTS: * data -- our struct privdata * fname -- filename to open for writing. Relative to work directory * sample_fd -- the "sample" fd from "data", if we're not conserving. * %RETURNS: * A file descriptor open for writing, or -1 on failure * %DESCRIPTION: * If we are NOT conserving file descriptors, simply returns sample_fd. * If we ARE conserving file descriptors, opens fname for writing. ***********************************************************************/ static int get_fd(struct privdata *data, char const *fname, int sample_fd) { char buf[SMALLBUF]; if (sample_fd >= 0 && !ConserveDescriptors) return sample_fd; snprintf(buf, SMALLBUF, "%s/%s", data->dir, fname); sample_fd = open(buf, O_CREAT|O_APPEND|O_RDWR, 0640); if (sample_fd < 0) { syslog(LOG_WARNING, "%s: Could not open %s/%s: %m", data->qid, data->dir, fname); } return sample_fd; } /********************************************************************** * %FUNCTION: put_fd * %ARGUMENTS: * fd -- file descriptor to close * %RETURNS: * -1 if descriptor was closed; fd otherwise. * %DESCRIPTION: * If we are NOT conserving file descriptors, simply returns fd. * If we ARE conserving file descriptors, closes fd and returns -1. ***********************************************************************/ static int put_fd(int fd) { if (!ConserveDescriptors) return fd; closefd(fd); return -1; } /********************************************************************** * %FUNCTION: do_reply * %ARGUMENTS: * ctx -- filter context * code -- SMTP three-digit code * dsn -- SMTP DSN status notification code * reply -- text message (MAY BE MODIFIED!) * %RETURNS: * Nothing * %DESCRIPTION: * Called if we KNOW reply contains carriage-returns. * Sets the SMTP reply code and message. If smfi_setmlreply is available, * call it. Otherwise, call smfi_setreply after changing carriage-returns * to spaces. ***********************************************************************/ static sfsistat do_reply(SMFICTX *ctx, char const *code, char const *dsn, char *reply) { char *s; char *lines[MAX_ML_LINES+1]; int i; /* If there are carriage returns and we don't have smfi_setmlreply, change them to spaces and call smfi_setreply */ #ifndef MILTER_BUILDLIB_HAS_SETMLREPLY for (s = reply; *s; s++) { if (*s == '\n') *s = ' '; } return smfi_setreply(ctx, (char *) code, (char *) dsn, reply); #else /* Split on carriage-returns and pass to smfi_setmlreply */ for (i=0; i= 0, accept or queue connections from localhost */ if (!AllowNewConnectionsToQueue) { int is_local; int required_workers; int n = MXCheckFreeWorkers(MultiplexorSocketName, NULL); if (n < 0) { syslog(LOG_WARNING, "mfconnect: Error communicating with multiplexor"); DEBUG_EXIT("mfconnect", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (!sa) { is_local = 1; } else { is_local = is_localhost(sa); } if (workersReservedForLoopback >= 0) { if (is_local) { required_workers = 0; } else { required_workers = workersReservedForLoopback; } } else { required_workers = 0; } if (n <= required_workers) { if (workersReservedForLoopback < 0 || ! is_local) { syslog(LOG_WARNING, "mfconnect: No free workers: Need %d, found %d", required_workers+1, n); DEBUG_EXIT("mfconnect", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } } } data = malloc_with_log(sizeof *data); if (!data) { DEBUG_EXIT("mfconnect", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } data->hostname = NULL; data->hostip = NULL; data->hostport = 0; data->myip = NULL; data->daemon_port = 0; data->sender = NULL; data->firstRecip = NULL; data->dir = NULL; data->heloArg = NULL; data->qid_written = 0; data->qid = NOQUEUE; data->fd = -1; data->headerFD = -1; data->cmdFD = -1; data->numContentTypeHeaders = 0; data->seenMimeVersionHeader = 0; data->validatePresent = 0; data->suspiciousBody = 0; data->lastWasCR = 0; data->filterFailed = 0; /* Save private data */ if (smfi_setpriv(ctx, data) != MI_SUCCESS) { free(data); /* Can't hurt... */ smfi_setpriv(ctx, NULL); syslog(LOG_WARNING, "Unable to set private data pointer: smfi_setpriv failed"); DEBUG_EXIT("mfconnect", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (hostname) { data->hostname = strdup_with_log(hostname); if (!data->hostname) { cleanup(ctx); DEBUG_EXIT("mfconnect", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } } else { data->hostname = NULL; } /* Port */ if (sa) { #ifdef AF_INET6 if (sa->sa_family == AF_INET6) { data->hostport = ntohs(in6sa->sin6_port); } #endif if (sa->sa_family == AF_INET) { data->hostport = ntohs(insa->sin_port); } } /* Padding -- should be big enough for IPv6 addresses */ if (!sa) { data->hostip = NULL; } else { data->hostip = malloc_with_log(65); if (!data->hostip) { cleanup(ctx); DEBUG_EXIT("mfconnect", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } #ifdef HAVE_INET_NTOP #ifdef AF_INET6 if (sa->sa_family == AF_INET6) { tmp = inet_ntop(AF_INET6, &in6sa->sin6_addr, data->hostip, 65); /* Convert IPv6-mapped IPv4 address to pure IPv4. That is: ::ffff:xxx.yyy.zzz.www to simply xxx.yyy.zzz.www */ if (tmp) { if (IN6_IS_ADDR_V4MAPPED(&in6sa->sin6_addr) || IN6_IS_ADDR_V4COMPAT(&in6sa->sin6_addr)) { if (strchr(data->hostip, '.')) { char const *lastcolon = strrchr(data->hostip, ':'); char *dst = data->hostip; while(lastcolon) { lastcolon++; *dst++ = *lastcolon; if (!*lastcolon) break; } } } } } else #endif if (sa->sa_family == AF_INET) { tmp = inet_ntop(AF_INET, &insa->sin_addr, data->hostip, 65); } else if (sa->sa_family == AF_LOCAL) { tmp = "127.0.0.1"; strcpy(data->hostip, tmp); } else { syslog(LOG_WARNING, "Unknown address family %d", (int) sa->sa_family); cleanup(ctx); DEBUG_EXIT("mfconnect", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } #else pthread_mutex_lock(&ntoa_mutex); tmp = inet_ntoa(insa->sin_addr); if (tmp) strncpy(data->hostip, tmp, 64); pthread_mutex_unlock(&ntoa_mutex); #endif if (!tmp) { syslog(LOG_WARNING, "inet_ntoa or inet_ntop failed: %m"); cleanup(ctx); DEBUG_EXIT("mfconnect", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } data->hostip[64] = 0; } /* Is host IP equivalent to loopback? If so, replace with 127.0.0.1 */ /* (We have enough room because data->hostip can hold 64 chars + nul) */ if (data->hostip && EquivToLoopback && !strcmp(data->hostip, EquivToLoopback)) { strcpy(data->hostip, "127.0.0.1"); } data->dir = NULL; data->fd = -1; data->headerFD = -1; data->cmdFD = -1; data->suspiciousBody = 0; data->lastWasCR = 0; /* Get my IP address */ me = smfi_getsymval(ctx, "{if_addr}"); if (me && *me && MyIPAddress && !strcmp(me, MyIPAddress)) { data->myip = MyIPAddress; } else if (me && *me && strcmp(me, "127.0.0.1")) { data->myip = strdup_with_log(me); } else { /* Sigh... use our computed address */ data->myip = MyIPAddress; } /* Get my port */ if (!data->daemon_port) { me = smfi_getsymval(ctx, "{daemon_port}"); if (me && *me) { sscanf(me, "%u", &(data->daemon_port)); } } /* Try grabbing the Queue ID */ set_queueid(ctx); if (doRelayCheck) { char buf2[SMALLBUF]; int n = MXRelayOK(MultiplexorSocketName, buf2, data->hostip, data->hostname, data->hostport, data->myip, data->daemon_port, data->qid); if (n == MD_REJECT) { /* Can't call smfi_setreply from connect callback */ /* set_dsn(ctx, buf2, 5); */ /* We reject connections from this relay */ cleanup(ctx); DEBUG_EXIT("mfconnect", "SMFIS_REJECT"); return SMFIS_REJECT; } if (n <= MD_TEMPFAIL) { /* Can't call smfi_setreply from connect callback */ /* set_dsn(ctx, buf2, 4); */ cleanup(ctx); DEBUG_EXIT("mfconnect", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (n == MD_ACCEPT_AND_NO_MORE_FILTERING) { /* Can't call smfi_setreply from connect callback */ /* set_dsn(ctx, buf2, 2); */ cleanup(ctx); DEBUG_EXIT("mfconnect", "SMFIS_ACCEPT"); return SMFIS_ACCEPT; } if (n == MD_DISCARD) { /* Can't call smfi_setreply from connect callback */ /* set_dsn(ctx, buf2, 2); */ cleanup(ctx); DEBUG_EXIT("mfconnect", "SMFIS_DISCARD"); return SMFIS_DISCARD; } } DEBUG_EXIT("mfconnect", "SMFIS_CONTINUE"); return SMFIS_CONTINUE; } /********************************************************************** * %FUNCTION: helo * %ARGUMENTS: * ctx -- Milter context * helohost -- argument to "HELO" or "EHLO" SMTP command * %RETURNS: * SMFIS_CONTINUE * %DESCRIPTION: * Stores the HELO argument in the private data area ***********************************************************************/ static sfsistat helo(SMFICTX *ctx, char *helohost) { struct privdata *data = DATA; DEBUG_ENTER("helo"); if (!data) { DEBUG_EXIT("helo", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (data->heloArg) { free(data->heloArg); data->heloArg = NULL; } data->heloArg = strdup_with_log(helohost); /* Try grabbing the Queue ID */ set_queueid(ctx); if (doHeloCheck) { char buf2[SMALLBUF]; int n = MXHeloOK(MultiplexorSocketName, buf2, data->hostip, data->hostname, data->heloArg, data->hostport, data->myip, data->daemon_port, data->qid); if (n == MD_REJECT) { set_dsn(ctx, buf2, 5); cleanup(ctx); DEBUG_EXIT("helo", "SMFIS_REJECT"); return SMFIS_REJECT; } if (n <= MD_TEMPFAIL) { set_dsn(ctx, buf2, 4); cleanup(ctx); DEBUG_EXIT("helo", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (n == MD_ACCEPT_AND_NO_MORE_FILTERING) { set_dsn(ctx, buf2, 2); cleanup(ctx); DEBUG_EXIT("helo", "SMFIS_ACCEPT"); return SMFIS_ACCEPT; } if (n == MD_DISCARD) { set_dsn(ctx, buf2, 2); cleanup(ctx); DEBUG_EXIT("helo", "SMFIS_DISCARD"); return SMFIS_DISCARD; } if (n == MD_CONTINUE) { /* Called only in case we need to delay */ set_dsn(ctx, buf2, 2); } } DEBUG_EXIT("helo", "SMFIS_CONTINUE"); return SMFIS_CONTINUE; } /********************************************************************** *%FUNCTION: envfrom *%ARGUMENTS: * ctx -- Sendmail filter mail context * from -- list of arguments to "MAIL FROM:" SMTP command. *%RETURNS: * SMFIS_TEMPFAIL or SMFIS_CONTINUE *%DESCRIPTION: * Allocates a private data structure for tracking this message, and * opens a temporary file for saving message body. ***********************************************************************/ static sfsistat envfrom(SMFICTX *ctx, char **from) { struct privdata *data; int i; char buffer[SMALLBUF]; char buf2[SMALLBUF]; char **macro; dynamic_buffer dbuf; char mxid[MX_ID_LEN+1]; DEBUG_ENTER("envfrom"); /* Generate the MIMEDefang ID */ (void) gen_mx_id(mxid); /* Get the private context */ data = DATA; if (!data) { syslog(LOG_WARNING, "envfrom: Unable to obtain private data from milter context"); DEBUG_EXIT("envfrom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* Set the Queue ID if it hasn't yet been set */ set_queueid(ctx); /* Copy sender */ if (data->sender) { free(data->sender); } data->sender = strdup_with_log(from[0]); if (!data->sender) { cleanup(ctx); DEBUG_EXIT("envfrom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* Old data lying around? */ if (data->firstRecip) { free(data->firstRecip); data->firstRecip = NULL; } /* Make the working directory */ if (!data->qid || data->qid == NOQUEUE) { /* No queue ID, so use the mxid */ snprintf(buffer, SMALLBUF, "%s/mdefang-%s", SpoolDir, mxid); } else { /* We have a queue ID, so use it */ snprintf(buffer, SMALLBUF, "%s/mdefang-%s", SpoolDir, data->qid); } if (mkdir(buffer, 0750) != 0) { /* Could not create temp. directory */ syslog(LOG_WARNING, "%s: Could not create directory %s: %m", data->qid, buffer); cleanup(ctx); DEBUG_EXIT("envfrom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (data->fd >= 0) closefd(data->fd); if (data->headerFD >= 0) closefd(data->headerFD); if (data->cmdFD >= 0) closefd(data->cmdFD); if (data->dir) { /* Clean data->dir up if it's still lying around */ if (access(data->dir, R_OK) == 0) { (void) rm_r(data->qid, data->dir); } free(data->dir); data->dir = NULL; } data->fd = -1; data->headerFD = -1; data->cmdFD = -1; data->validatePresent = 0; data->filterFailed = 0; data->numContentTypeHeaders = 0; data->seenMimeVersionHeader = 0; data->dir = strdup_with_log(buffer); if (!data->dir) { /* Don't forget to clean up directory... */ rmdir(buffer); cleanup(ctx); DEBUG_EXIT("envfrom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* Create the HEADERS file. Even if we get a wacky e-mail without * any headers, we want an empty HEADERS file lying around */ data->headerFD = get_fd(data, "HEADERS", data->headerFD); if (data->headerFD >= 0) { data->headerFD = put_fd(data->headerFD); } /* Initialize the dynamic buffer for writing COMMANDS file */ dbuf_init(&dbuf); /* Generate an ID */ append_mx_command(&dbuf, 'i', mxid); /* Write the sender */ append_mx_command(&dbuf, 'S', from[0]); /* Write ESMTP args */ for (i=1; from[i]; i++) { append_mx_command(&dbuf, 's', from[i]); } /* Write the standard macros */ macro = StandardSendmailMacros; while (*macro) { append_macro_value(&dbuf, ctx, *macro); macro++; } /* Fake client_port: We don't get the macro, but we have the connection info cached in our private data area. */ dbuf_putc(&dbuf, '='); append_percent_encoded(&dbuf, "client_port"); dbuf_putc(&dbuf, ' '); { char portstring[32]; snprintf(portstring, sizeof(portstring), "%u", data->hostport); append_percent_encoded(&dbuf, portstring); } dbuf_putc(&dbuf, '\n'); /* Write any additional macros requested by user */ for (i=0; imyip && (data->myip != MyIPAddress)) { free(data->myip); data->myip = NULL; } if (data->qid && data->qid != NOQUEUE) { append_mx_command(&dbuf, 'Q', data->qid); data->qid_written = 1; } /* Write host name and host IP */ append_mx_command(&dbuf, 'H', data->hostname); append_mx_command(&dbuf, 'I', data->hostip); /* Write HELO value */ if (data->heloArg) { append_mx_command(&dbuf, 'E', data->heloArg); } /* Now actually dump the dbuf contents to COMMANDS */ /* Open command file */ data->cmdFD = get_fd(data, "COMMANDS", data->cmdFD); if (data->cmdFD < 0) { dbuf_free(&dbuf); cleanup(ctx); DEBUG_EXIT("envfrom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (write_dbuf(&dbuf, data->cmdFD, data, "COMMANDS") < 0) { dbuf_free(&dbuf); cleanup(ctx); DEBUG_EXIT("envfrom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } dbuf_free(&dbuf); data->cmdFD = put_fd(data->cmdFD); if (doSenderCheck) { int n = MXSenderOK(MultiplexorSocketName, buf2, (char const **) from, data->hostip, data->hostname, data->heloArg, data->dir, data->qid); if (n == MD_REJECT) { set_dsn(ctx, buf2, 5); /* We reject connections from this sender */ cleanup(ctx); DEBUG_EXIT("envfrom", "SMFIS_REJECT"); return SMFIS_REJECT; } if (n <= MD_TEMPFAIL) { set_dsn(ctx, buf2, 4); cleanup(ctx); DEBUG_EXIT("envfrom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (n == MD_ACCEPT_AND_NO_MORE_FILTERING) { set_dsn(ctx, buf2, 2); cleanup(ctx); DEBUG_EXIT("envfrom", "SMFIS_ACCEPT"); return SMFIS_ACCEPT; } if (n == MD_DISCARD) { set_dsn(ctx, buf2, 2); cleanup(ctx); DEBUG_EXIT("envfrom", "SMFIS_DISCARD"); return SMFIS_DISCARD; } if (n == MD_CONTINUE) { /* Called only in case we need to delay */ set_dsn(ctx, buf2, 2); } } DEBUG_EXIT("envfrom", "SMFIS_CONTINUE"); return SMFIS_CONTINUE; } /********************************************************************** *%FUNCTION: mf_data *%ARGUMENTS: * ctx -- Sendmail filter mail context *%RETURNS: * Standard milter reply code *%DESCRIPTION: * Does a post-DATA callback ***********************************************************************/ #ifdef MILTER_BUILDLIB_HAS_DATA static sfsistat mf_data(SMFICTX *ctx) { return SMFIS_CONTINUE; } #endif /********************************************************************** *%FUNCTION: mf_unknown *%ARGUMENTS: * ctx -- Sendmail filter mail context *%RETURNS: * Standard milter reply code *%DESCRIPTION: * Does a post-DATA callback ***********************************************************************/ #ifdef MILTER_BUILDLIB_HAS_UNKNOWN static sfsistat mf_unknown(SMFICTX *ctx, char const *arg) { return SMFIS_CONTINUE; } #endif /********************************************************************** *%FUNCTION: mf_negotiate *%ARGUMENTS: * ctx -- Sendmail filter mail context * Remaining args -- crappy args as described in libmilter docs. *%RETURNS: * Standard milter reply code *%DESCRIPTION: * Negotiates protocol options ***********************************************************************/ #ifdef MILTER_BUILDLIB_HAS_NEGOTIATE static sfsistat mf_negotiate(SMFICTX *ctx, unsigned long f0, unsigned long f1, unsigned long f2, unsigned long f3, unsigned long *pf0, unsigned long *pf1, unsigned long *pf2, unsigned long *pf3) { dynamic_buffer dbuf; char **macroname; int done_one = 0; int i; *pf0 = f0; *pf1 = 0; *pf2 = 0; *pf3 = 0; if (f1 & SMFIP_RCPT_REJ) { if (SeeRejectedRecipients) { *pf1 |= SMFIP_RCPT_REJ; } } /* Don't want leading spaces */ *pf1 &= (~SMFIP_HDR_LEADSPC); /*** libmilter 8.14.3 leaked memory, so don't use smfi_setsymlist unless invoked with -y option ***/ if (!setsymlist_ok) { return SMFIS_CONTINUE; } /* Send along the list of macros we want */ dbuf_init(&dbuf); for (macroname = StandardSendmailMacros; *macroname; macroname++) { if (done_one) { dbuf_putc(&dbuf, ' '); } if (strlen(*macroname) > 1) { dbuf_putc(&dbuf, '{'); } dbuf_puts(&dbuf, *macroname); if (strlen(*macroname) > 1) { dbuf_putc(&dbuf, '}'); } done_one = 1; } for (i=0, macroname=AdditionalMacros; i 1) { dbuf_putc(&dbuf, '{'); } dbuf_puts(&dbuf, *macroname); if (strlen(*macroname) > 1) { dbuf_putc(&dbuf, '}'); } done_one = 1; } i = 1; if (smfi_setsymlist(ctx, SMFIM_CONNECT, DBUF_VAL(&dbuf)) != MI_SUCCESS) i = 0; if (smfi_setsymlist(ctx, SMFIM_HELO, DBUF_VAL(&dbuf)) != MI_SUCCESS) i = 0; if (smfi_setsymlist(ctx, SMFIM_ENVFROM, DBUF_VAL(&dbuf)) != MI_SUCCESS) i = 0; if (smfi_setsymlist(ctx, SMFIM_ENVRCPT, DBUF_VAL(&dbuf)) != MI_SUCCESS) i = 0; if (smfi_setsymlist(ctx, SMFIM_DATA, DBUF_VAL(&dbuf)) != MI_SUCCESS) i = 0; if (smfi_setsymlist(ctx, SMFIM_EOM, DBUF_VAL(&dbuf)) != MI_SUCCESS) i = 0; if (smfi_setsymlist(ctx, SMFIM_EOH, DBUF_VAL(&dbuf)) != MI_SUCCESS) i = 0; dbuf_free(&dbuf); if (!i) { syslog(LOG_INFO, "smfi_setsymlist() failed"); } return SMFIS_CONTINUE; } #endif /********************************************************************** *%FUNCTION: rcptto *%ARGUMENTS: * ctx -- Sendmail filter mail context * to -- list of arguments to each RCPT_TO *%RETURNS: * SMFIS_TEMPFAIL or SMFIS_CONTINUE *%DESCRIPTION: * Saves recipient data ***********************************************************************/ static sfsistat rcptto(SMFICTX *ctx, char **to) { struct privdata *data = DATA; char ans[SMALLBUF]; sfsistat retcode = SMFIS_CONTINUE; char const *rcpt_mailer, *rcpt_host, *rcpt_addr; int i; dynamic_buffer dbuf; DEBUG_ENTER("rcptto"); if (!data) { syslog(LOG_WARNING, "rcptto: Unable to obtain private data from milter context"); DEBUG_EXIT("rcptto", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* Apparently, Postfix offers an option to set the "i" macro at rcptto time */ set_queueid(ctx); if (data->qid && data->qid != NOQUEUE) { if (!data->qid_written) { /* Write this out separately; the write below may be skipped */ data->cmdFD = get_fd(data, "COMMANDS", data->cmdFD); if (data->cmdFD >= 0) { dbuf_init(&dbuf); append_mx_command(&dbuf, 'Q', data->qid); if (write_dbuf(&dbuf, data->cmdFD, data, "COMMANDS") >= 0) { data->qid_written = 1; } dbuf_free(&dbuf); data->cmdFD = put_fd(data->cmdFD); } } } rcpt_mailer = smfi_getsymval(ctx, "{rcpt_mailer}"); if (!rcpt_mailer || !*rcpt_mailer) rcpt_mailer = "?"; rcpt_host = smfi_getsymval(ctx, "{rcpt_host}"); if (!rcpt_host || !*rcpt_host) rcpt_host = "?"; rcpt_addr = smfi_getsymval(ctx, "{rcpt_addr}"); if (!rcpt_addr || !*rcpt_addr) rcpt_addr = "?"; /* Recipient check if enabled */ if (doRecipientCheck) { int n; /* If this is first recipient, copy it */ if (!data->firstRecip) { data->firstRecip = strdup_with_log(to[0]); if (!data->firstRecip) { DEBUG_EXIT("rcptto", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } } n = MXRecipientOK(MultiplexorSocketName, ans, (char const **) to, data->sender, data->hostip, data->hostname, data->firstRecip, data->heloArg, data->dir, data->qid, rcpt_mailer, rcpt_host, rcpt_addr); if (n == MD_REJECT) { /* We reject to this recipient */ set_dsn(ctx, ans, 5); DEBUG_EXIT("rcptto", "SMFIS_REJECT"); return SMFIS_REJECT; } if (n <= MD_TEMPFAIL) { set_dsn(ctx, ans, 4); DEBUG_EXIT("rcptto", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (n == MD_ACCEPT_AND_NO_MORE_FILTERING) { set_dsn(ctx, ans, 2); cleanup(ctx); DEBUG_EXIT("rcptto", "SMFIS_ACCEPT"); return SMFIS_ACCEPT; } if (n == MD_DISCARD) { set_dsn(ctx, ans, 2); cleanup(ctx); DEBUG_EXIT("rcptto", "SMFIS_DISCARD"); return SMFIS_DISCARD; } if (n == MD_CONTINUE) { /* Called only in case we need to delay */ set_dsn(ctx, ans, 2); } } /* Write recipient line, only for recipients we accept! */ dbuf_init(&dbuf); dbuf_putc(&dbuf, 'R'); append_percent_encoded(&dbuf, to[0]); dbuf_putc(&dbuf, ' '); append_percent_encoded(&dbuf, rcpt_mailer); dbuf_putc(&dbuf, ' '); append_percent_encoded(&dbuf, rcpt_host); dbuf_putc(&dbuf, ' '); append_percent_encoded(&dbuf, rcpt_addr); dbuf_putc(&dbuf, '\n'); /* Write ESMTP args */ for (i=1; to[i]; i++) { append_mx_command(&dbuf, 'r', to[i]); } /* Now flush out to cmdFD */ data->cmdFD = get_fd(data, "COMMANDS", data->cmdFD); if (data->cmdFD < 0) { dbuf_free(&dbuf); cleanup(ctx); DEBUG_EXIT("rcptto", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (write_dbuf(&dbuf, data->cmdFD, data, "COMMANDS") < 0) { dbuf_free(&dbuf); cleanup(ctx); DEBUG_EXIT("rcptto", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } dbuf_free(&dbuf); data->cmdFD = put_fd(data->cmdFD); DEBUG_EXIT("rcptto", "SMFIS_CONTINUE or SMFIS_ACCEPT"); return retcode; } /********************************************************************** *%FUNCTION: header *%ARGUMENTS: * ctx -- Sendmail filter mail context * headerf -- Header field name * headerv -- Header value *%RETURNS: * SMFIS_TEMPFAIL or SMFIS_CONTINUE *%DESCRIPTION: * Writes the header to the temporary file ***********************************************************************/ static sfsistat header(SMFICTX *ctx, char *headerf, char *headerv) { struct privdata *data = DATA; int suspicious = 0; int write_header = 1; dynamic_buffer dbuf; DEBUG_ENTER("header"); if (!data) { syslog(LOG_WARNING, "header: Unable to obtain private data from milter context"); DEBUG_EXIT("header", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* Check for multiple content-type headers */ if (!strcasecmp(headerf, "content-type")) { data->numContentTypeHeaders++; /* If more than one content-type header, only write the first one to ensure reliable interpretation by filter! */ if (data->numContentTypeHeaders > 1) write_header = 0; } else if (!strcasecmp(headerf, "mime-version")) { /* We have seen a MIME-Version: header? */ data->seenMimeVersionHeader = 1; } if (write_header) { /* Write the header to the message file */ dbuf_init(&dbuf); suspicious = safe_append_header(&dbuf, headerf); dbuf_puts(&dbuf, ": "); suspicious |= safe_append_header(&dbuf, headerv); dbuf_putc(&dbuf, '\n'); data->fd = get_fd(data, "INPUTMSG", data->fd); if (data->fd < 0) { dbuf_free(&dbuf); cleanup(ctx); DEBUG_EXIT("header", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (write_dbuf(&dbuf, data->fd, data, "INPUTMSG") < 0) { dbuf_free(&dbuf); cleanup(ctx); DEBUG_EXIT("header", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } dbuf_free(&dbuf); data->fd = put_fd(data->fd); } /* Remove embedded newlines and save to our HEADERS file */ chomp(headerf); chomp(headerv); if (write_header) { data->headerFD = get_fd(data, "HEADERS", data->headerFD); if (data->headerFD < 0) { cleanup(ctx); DEBUG_EXIT("header", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } dbuf_init(&dbuf); dbuf_puts(&dbuf, headerf); dbuf_puts(&dbuf, ": "); dbuf_puts(&dbuf, headerv); dbuf_putc(&dbuf, '\n'); if (write_dbuf(&dbuf, data->headerFD, data, "HEADERS") < 0) { dbuf_free(&dbuf); cleanup(ctx); DEBUG_EXIT("header", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } dbuf_free(&dbuf); data->headerFD = put_fd(data->headerFD); } dbuf_init(&dbuf); if (suspicious) { append_mx_command(&dbuf, '!', NULL); } /* Check for subject -- special case */ if (!strcasecmp(headerf, "subject")) { append_mx_command(&dbuf, 'U', headerv); } else if (!strcasecmp(headerf, "message-id")) { append_mx_command(&dbuf, 'X', headerv); } /* Check for validating IP header. If found, write a J line to the file to reset the SMTP host address */ if (ValidateHeader[0] && !strcmp(headerf, ValidateHeader)) { /* Make sure it looks like an IP address, though... */ int n, a, b, c, d; char ipaddr[32]; n = sscanf(headerv, "%d.%d.%d.%d", &a, &b, &c, &d); if (n == 4 && a >= 0 && a <= 255 && b >= 0 && b <= 255 && c >= 0 && c <= 255 && d >= 0 && d <= 255) { sprintf(ipaddr, "%d.%d.%d.%d", a, b, c, d); append_mx_command(&dbuf, 'J', ipaddr); data->validatePresent = 1; } } if (DBUF_LEN(&dbuf)) { data->cmdFD = get_fd(data, "COMMANDS", data->cmdFD); if (data->cmdFD < 0) { dbuf_free(&dbuf); cleanup(ctx); DEBUG_EXIT("header", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (write_dbuf(&dbuf, data->cmdFD, data, "COMMANDS") < 0) { dbuf_free(&dbuf); cleanup(ctx); DEBUG_EXIT("header", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } data->cmdFD = put_fd(data->cmdFD); } dbuf_free(&dbuf); DEBUG_EXIT("header", "SMFIS_CONTINUE"); return SMFIS_CONTINUE; } /********************************************************************** *%FUNCTION: eoh *%ARGUMENTS: * ctx -- Sendmail filter mail context *%RETURNS: * SMFIS_TEMPFAIL or SMFIS_CONTINUE *%DESCRIPTION: * Writes a blank line to indicate the end of headers. ***********************************************************************/ static sfsistat eoh(SMFICTX *ctx) { struct privdata *data = DATA; DEBUG_ENTER("eoh"); if (!data) { syslog(LOG_WARNING, "eoh: Unable to obtain private data from milter context"); DEBUG_EXIT("eoh", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* Set the Queue ID if it hasn't yet been set */ set_queueid(ctx); /* We can close headerFD to save a descriptor */ if (data->headerFD >= 0 && closefd(data->headerFD) < 0) { data->headerFD = -1; syslog(LOG_WARNING, "%s: Error closing header descriptor: %m", data->qid); cleanup(ctx); DEBUG_EXIT("eoh", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } data->headerFD = -1; data->suspiciousBody = 0; data->lastWasCR = 0; data->fd = get_fd(data, "INPUTMSG", data->fd); if (data->fd < 0) { cleanup(ctx); DEBUG_EXIT("eoh", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* Write blank line separating headers from body */ if (writestr(data->fd, "\n") != 1) { cleanup(ctx); DEBUG_EXIT("eoh", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } data->fd = put_fd(data->fd); DEBUG_EXIT("eoh", "SMFIS_CONTINUE"); return SMFIS_CONTINUE; } /********************************************************************** *%FUNCTION: body *%ARGUMENTS: * ctx -- Sendmail filter mail context * text -- a chunk of text from the mail body * len -- length of chunk *%RETURNS: * SMFIS_TEMPFAIL or SMFIS_CONTINUE *%DESCRIPTION: * Writes a chunk of the body to the temporary file ***********************************************************************/ static sfsistat body(SMFICTX *ctx, u_char *text, size_t len) { struct privdata *data = DATA; char buf[4096]; int nsaved = 0; DEBUG_ENTER("body"); if (!data) { syslog(LOG_WARNING, "body: Unable to obtain private data from milter context"); DEBUG_EXIT("body", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* Set the Queue ID if it hasn't yet been set */ set_queueid(ctx); /* Write to file and scan body for suspicious characters */ if (len) { u_char *s = text; size_t n; /* If last was CR, and this is not LF, suspicious! */ if (data->lastWasCR && *text != '\n') { data->suspiciousBody = 1; if (!StripBareCR) { /* Do not suppress bare CR's. Only suppress those followed by LF */ buf[nsaved++] = '\r'; } } data->lastWasCR = 0; data->fd = get_fd(data, "INPUTMSG", data->fd); if (data->fd < 0) { cleanup(ctx); DEBUG_EXIT("body", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } for (n=0; nlastWasCR = 1; continue; } else if (*(s+1) != '\n') { data->suspiciousBody = 1; if (StripBareCR) { /* Suppress ALL CR's */ continue; } } else { /* Suppress the CR immediately preceding a LF */ continue; } } /* Write char */ if (nsaved == sizeof(buf)) { if (writen(data->fd, buf, nsaved) < 0) { syslog(LOG_WARNING, "%s: writen failed: %m line %d", data->qid, __LINE__); cleanup(ctx); DEBUG_EXIT("body", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } nsaved = 0; } buf[nsaved++] = *s; /* Embedded NULL's are cause for concern */ if (!*s) { data->suspiciousBody = 1; } } /* Flush buffer */ if (nsaved) { if (writen(data->fd, buf, nsaved) < 0) { syslog(LOG_WARNING, "%s: writen failed: %m line %d", data->qid, __LINE__); cleanup(ctx); DEBUG_EXIT("body", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } } data->fd = put_fd(data->fd); } DEBUG_EXIT("body", "SMFIS_CONTINUE"); return SMFIS_CONTINUE; } /********************************************************************** *%FUNCTION: remove_carriage_return *%ARGUMENTS: * str -- pointer to string to modify *%DESCRIPTION: * removes carriage returns from a string, header values should not * contain "\r" chars ***********************************************************************/ static void remove_carriage_return(char *str) { char t = '\r'; int i,j; i = 0; while(i < strlen(str)) { if (str[i] == t) { for (j = i; j < strlen(str); j++) { str[j] = str[j+1]; } } else { i++; } } } /********************************************************************** *%FUNCTION: eom *%ARGUMENTS: * ctx -- Sendmail filter mail context *%RETURNS: * SMFIS_TEMPFAIL or SMFIS_CONTINUE *%DESCRIPTION: * This is where all the action happens. Called at end of message, it * runs the Perl scanner which may or may not ask for the body to be * replaced. ***********************************************************************/ static sfsistat eom(SMFICTX *ctx) { char buffer[SMALLBUF]; char result[SMALLBUF]; char *rbuf, *rptr, *eptr; int seen_F = 0; int res_fd; int n; struct privdata *data = DATA; int r; int problem = 0; int fd; int j; char chunk[CHUNK]; char *hdr, *val, *count; char *code, *dsn, *reply; struct stat statbuf; dynamic_buffer dbuf; struct timespec start, finish, diff; int rejecting; DEBUG_ENTER("eom"); if (LogTimes) { #ifdef HAVE_CLOCK_MONOTONIC if(clock_gettime(CLOCK_MONOTONIC, &start) == -1) { syslog(LOG_INFO, "%s: Error in clock_gettime", data->qid); } #else if(clock_gettime(CLOCK_REALTIME, &start) == -1) { syslog(LOG_INFO, "%s: Error in clock_gettime", data->qid); } #endif } /* Close output file */ if (!data) { syslog(LOG_WARNING, "eom: Unable to obtain private data from milter context"); DEBUG_EXIT("eom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } dbuf_init(&dbuf); /* Set the Queue ID if it hasn't yet been set */ set_queueid(ctx); if (!data->qid_written && data->qid && (data->qid != NOQUEUE)) { append_mx_command(&dbuf, 'Q', data->qid); data->qid_written = 1; } /* Signal suspicious body chars */ if (data->suspiciousBody) { append_mx_command(&dbuf, '?', NULL); } /* Signal end of command file */ append_mx_command(&dbuf, 'F', NULL); data->cmdFD = get_fd(data, "COMMANDS", data->cmdFD); if (data->cmdFD < 0) { dbuf_free(&dbuf); cleanup(ctx); DEBUG_EXIT("eom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } if (write_dbuf(&dbuf, data->cmdFD, data, "COMMANDS") < 0) { dbuf_free(&dbuf); cleanup(ctx); DEBUG_EXIT("eom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } dbuf_free(&dbuf); /* All the fd's are closed unconditionally -- no need for put_fd */ if (data->fd >= 0 && (closefd(data->fd) < 0)) problem = 1; if (data->headerFD >= 0 && (closefd(data->headerFD) < 0)) problem = 1; if (data->cmdFD >= 0 && (closefd(data->cmdFD) < 0)) problem = 1; data->fd = -1; data->headerFD = -1; data->cmdFD = -1; if (problem) { cleanup(ctx); DEBUG_EXIT("eom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } data->suspiciousBody = 0; data->lastWasCR = 0; /* Run the filter */ if (MXScanDir(MultiplexorSocketName, data->qid, data->dir) < 0) { data->filterFailed = 1; cleanup(ctx); DEBUG_EXIT("eom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* Read the results file */ snprintf(buffer, SMALLBUF, "%s/RESULTS", data->dir); res_fd = open(buffer, O_RDONLY); if (res_fd < 0) { syslog(LOG_WARNING, "%s: Filter did not create RESULTS file", data->qid); data->filterFailed = 1; cleanup(ctx); DEBUG_EXIT("eom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* Slurp in the entire RESULTS file in one go... */ if (fstat(res_fd, &statbuf) < 0) { syslog(LOG_WARNING, "%s: Unable to stat RESULTS file: %m", data->qid); closefd(res_fd); cleanup(ctx); data->filterFailed = 1; DEBUG_EXIT("eom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* If file is unreasonable big, forget it! */ if (statbuf.st_size > BIGBUF - 1) { syslog(LOG_WARNING, "%s: RESULTS file is unreasonably large - %ld byes; max is %d bytes", data->qid, (long) statbuf.st_size, BIGBUF-1); closefd(res_fd); cleanup(ctx); data->filterFailed = 1; DEBUG_EXIT("eom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* RESULTS files are typically pretty small and will fit into our */ /* SMALLBUF-sized buffer. However, we'll allocate up to BIGBUF bytes */ /* for weird, large RESULTS files. */ if (statbuf.st_size < SMALLBUF) { rbuf = result; } else { rbuf = malloc(statbuf.st_size + 1); if (!rbuf) { syslog(LOG_WARNING, "%s: Unable to allocate memory for RESULTS data", data->qid); closefd(res_fd); cleanup(ctx); DEBUG_EXIT("eom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } } /* Slurp in the file */ n = readn(res_fd, rbuf, statbuf.st_size); if (n < 0) { syslog(LOG_WARNING, "%s: Error reading RESULTS file: %m", data->qid); closefd(res_fd); if (rbuf != result) free(rbuf); cleanup(ctx); DEBUG_EXIT("eom", "SMFIS_TEMPFAIL"); return SMFIS_TEMPFAIL; } /* Done with descriptor -- close it. */ closefd(res_fd); rbuf[n] = 0; /* Make a pass through the RESULTS file to see if mail will be rejected or discarded */ rejecting = 0; rptr = rbuf; while (rptr && *rptr) { if (*rptr == 'T' || *rptr == 'D' || *rptr == 'B') { /* We are tempfailing, discarding or bouncing the message */ rejecting = 1; break; } /* Move to start of next line */ while (*rptr && (*rptr != '\n')) { rptr++; } if (*rptr == '\n') { rptr++; } } /* Now process the commands in the results file */ for (rptr = rbuf, eptr = rptr ; rptr && *rptr; rptr = eptr) { /* Get end-of-line character */ while (*eptr && (*eptr != '\n')) { eptr++; } /* Check line length */ if (eptr - rptr >= SMALLBUF-1) { syslog(LOG_WARNING, "%s: Overlong line in RESULTS file - %d chars (max %d)", data->qid, (int) (eptr - rptr), SMALLBUF-1); cleanup(ctx); MD_SMFI_TRY(set_reply, (ctx, "5", "554", "5.4.0" , "Overlong line in RESULTS file")); r = SMFIS_REJECT; goto bail_out; } if (*eptr == '\n') { *eptr = 0; eptr++; } else { eptr = NULL; } switch(*rptr) { case 'B': /* Bounce */ syslog(LOG_DEBUG, "%s: Bouncing because filter instructed us to", data->qid); split_on_space3(rptr+1, &code, &dsn, &reply); percent_decode(code); percent_decode(dsn); percent_decode(reply); MD_SMFI_TRY(set_reply, (ctx, "5", code, dsn, reply)); cleanup(ctx); r = SMFIS_REJECT; goto bail_out; case 'D': /* Discard */ syslog(LOG_DEBUG, "%s: Discarding because filter instructed us to", data->qid); cleanup(ctx); r = SMFIS_DISCARD; goto bail_out; case 'T': /* Tempfail */ syslog(LOG_DEBUG, "%s: Tempfailing because filter instructed us to", data->qid); split_on_space3(rptr+1, &code, &dsn, &reply); percent_decode(code); percent_decode(dsn); percent_decode(reply); MD_SMFI_TRY(set_reply, (ctx, "4", code, dsn, reply)); cleanup(ctx); r = SMFIS_TEMPFAIL; goto bail_out; case 'C': if (!rejecting) { snprintf(buffer, SMALLBUF, "%s/NEWBODY", data->dir); fd = open(buffer, O_RDONLY); if (fd < 0) { syslog(LOG_WARNING, "%s: Could not open %s for reading: %m", data->qid, buffer); closefd(fd); cleanup(ctx); data->filterFailed = 1; r = SMFIS_TEMPFAIL; goto bail_out; } while ((j=read(fd, chunk, CHUNK)) > 0) { MD_SMFI_TRY(smfi_replacebody, (ctx, (unsigned char *) chunk, j)); } close(fd); } break; case 'M': if (!rejecting) { /* New content-type header */ percent_decode(rptr+1); if (strlen(rptr+1) > 0) { MD_SMFI_TRY(smfi_chgheader, (ctx, "Content-Type", 1, rptr+1)); } if (!data->seenMimeVersionHeader) { /* No MIME-Version: header. Add one. */ MD_SMFI_TRY(smfi_chgheader, (ctx, "MIME-Version", 1, "1.0")); } } break; case 'H': /* Add a header */ if (!rejecting) { split_on_space(rptr+1, &hdr, &val); if (hdr && val) { percent_decode(hdr); percent_decode(val); remove_carriage_return(val); MD_SMFI_TRY(smfi_addheader, (ctx, hdr, val)); } } break; case 'N': /* Insert a header in position count */ if (!rejecting) { split_on_space3(rptr + 1, &hdr, &count, &val); if (hdr && val && count) { percent_decode(hdr); percent_decode(count); percent_decode(val); remove_carriage_return(val); if (sscanf(count, "%d", &j) != 1 || j < 0) { j = 0; /* 0 means add header at the top */ } #ifdef SMFIR_INSHEADER MD_SMFI_TRY(smfi_insheader, (ctx, j, hdr, val)); #else syslog(LOG_WARNING, "%s: No smfi_insheader; using smfi_addheader instead.", data->qid); MD_SMFI_TRY(smfi_addheader, (ctx, hdr, val)); #endif } } break; case 'I': /* Change a header */ if (!rejecting) { split_on_space3(rptr+1, &hdr, &count, &val); if (hdr && val && count) { percent_decode(hdr); percent_decode(count); percent_decode(val); remove_carriage_return(val); if (sscanf(count, "%d", &j) != 1 || j < 1) { j = 1; } MD_SMFI_TRY(smfi_chgheader, (ctx, hdr, j, val)); } } break; case 'J': /* Delete a header */ if (!rejecting) { split_on_space(rptr+1, &hdr, &count); if (hdr && count) { percent_decode(hdr); percent_decode(count); if (sscanf(count, "%d", &j) != 1 || j < 1) { j = 1; } MD_SMFI_TRY(smfi_chgheader, (ctx, hdr, j, NULL)); } } break; case 'R': /* Add a recipient */ if (!rejecting) { percent_decode(rptr+1); MD_SMFI_TRY(smfi_addrcpt, (ctx, rptr+1)); } break; case 'Q': /* Quarantine a message using Sendmail's facility */ percent_decode(rptr+1); MD_SMFI_TRY(do_sm_quarantine, (ctx, rptr+1)); break; case 'f': /* Change the "from" address */ #ifdef MILTER_BUILDLIB_HAS_CHGFROM if (!rejecting) { percent_decode(rptr+1); MD_SMFI_TRY(smfi_chgfrom, (ctx, rptr+1, NULL)); } #else syslog(LOG_WARNING, "%s: change_sender called, but this version of libmilter does not support CHGFROM", data->qid); #endif break; case 'S': /* Delete a recipient */ if (!rejecting) { percent_decode(rptr+1); MD_SMFI_TRY(smfi_delrcpt, (ctx, rptr+1)); } break; case 'F': seen_F = 1; /* We're done */ break; default: syslog(LOG_WARNING, "%s: Unknown command '%c' in RESULTS file", data->qid, *rptr); } if (*rptr == 'F') break; } if (!seen_F) { syslog(LOG_ERR, "%s: RESULTS file did not finish with 'F' line: Tempfailing", data->qid); r = SMFIS_TEMPFAIL; goto bail_out; } if (scan_body && *scan_body && !rejecting) { if (data->myip) { snprintf(buffer, SMALLBUF, "%s on %s", scan_body, data->myip); buffer[SMALLBUF-1] = 0; MD_SMFI_TRY(smfi_addheader, (ctx, "X-Scanned-By", buffer)); } else { MD_SMFI_TRY(smfi_addheader, (ctx, "X-Scanned-By", scan_body)); } } /* Delete first validation header if it was present */ if (ValidateHeader[0] && data->validatePresent) { MD_SMFI_TRY(smfi_chgheader, (ctx, ValidateHeader, 1, NULL)); } /* Delete any excess Content-Type headers and log */ if (data->numContentTypeHeaders > 1) { syslog(LOG_WARNING, "%s: WARNING: %d Content-Type headers found -- deleting all but first", data->qid, data->numContentTypeHeaders); for (j=2; j<=data->numContentTypeHeaders; j++) { MD_SMFI_TRY(smfi_chgheader, (ctx, "Content-Type", j, NULL)); } } r = cleanup(ctx); bail_out: if (rbuf != result) free(rbuf); if (LogTimes) { long msec_diff; #ifdef HAVE_CLOCK_MONOTONIC if(clock_gettime(CLOCK_MONOTONIC, &finish) != -1) { timespecsub(&finish, &start, &diff); // convert to milliseconds msec_diff = diff.tv_nsec / 1000000; syslog(LOG_INFO, "%s: Filter time is %ldms", data->qid, msec_diff); } else { syslog(LOG_INFO, "%s: Filter time cannot be calculated", data->qid); } #else if(clock_gettime(CLOCK_REALTIME, &finish) != -1) { timespecsub(&finish, &start, &diff); // convert to milliseconds msec_diff = diff.tv_nsec / 1000000; syslog(LOG_INFO, "%s: Filter time is %ldms", data->qid, msec_diff); } else { syslog(LOG_INFO, "%s: Filter time cannot be calculated", data->qid); } #endif } DEBUG(syslog(LOG_DEBUG, "%p: %s(%d): EXIT %s: %d", ctx, __FILE__, __LINE__, "eom", r)); return r; } /********************************************************************** *%FUNCTION: mfclose *%ARGUMENTS: * ctx -- Sendmail filter mail context *%RETURNS: * SMFIS_ACCEPT *%DESCRIPTION: * Called when connection is closed. ***********************************************************************/ static sfsistat mfclose(SMFICTX *ctx) { struct privdata *data = DATA; DEBUG_ENTER("mfclose"); cleanup(ctx); if (data) { if (data->fd >= 0) closefd(data->fd); if (data->headerFD >= 0) closefd(data->headerFD); if (data->cmdFD >= 0) closefd(data->cmdFD); if (data->dir) free(data->dir); if (data->hostname) free(data->hostname); if (data->hostip) free(data->hostip); if (data->myip && data->myip != MyIPAddress) free(data->myip); if (data->sender) free(data->sender); if (data->firstRecip) free(data->firstRecip); if (data->heloArg) free(data->heloArg); if (data->qid && data->qid != NOQUEUE) free(data->qid); free(data); } smfi_setpriv(ctx, NULL); DEBUG_EXIT("mfclose", "SMFIS_CONTINUE"); return SMFIS_CONTINUE; } /********************************************************************** *%FUNCTION: mfabort *%ARGUMENTS: * ctx -- Sendmail filter mail context *%RETURNS: * SMFIS_TEMPFAIL or SMFIS_CONTINUE *%DESCRIPTION: * Called if current message is aborted. Just cleans up. ***********************************************************************/ static sfsistat mfabort(SMFICTX *ctx) { return cleanup(ctx); } /********************************************************************** *%FUNCTION: cleanup *%ARGUMENTS: * ctx -- Sendmail filter mail context *%RETURNS: * SMFIS_TEMPFAIL or SMFIS_CONTINUE *%DESCRIPTION: * Cleans up temporary files. ***********************************************************************/ static sfsistat cleanup(SMFICTX *ctx) { sfsistat r = SMFIS_CONTINUE; struct privdata *data = DATA; DEBUG_ENTER("cleanup"); if (!data) { DEBUG_EXIT("cleanup", "SMFIS_CONTINUE"); return r; } if (data->fd >= 0 && (closefd(data->fd) < 0)) { syslog(LOG_ERR, "%s: Failure in cleanup line %d: %m", data->qid, __LINE__); r = SMFIS_TEMPFAIL; } data->fd = -1; if (data->headerFD >= 0 && (closefd(data->headerFD) < 0)) { syslog(LOG_ERR, "%s: Failure in cleanup line %d: %m", data->qid, __LINE__); r = SMFIS_TEMPFAIL; } data->headerFD = -1; if (data->cmdFD >= 0 && (closefd(data->cmdFD) < 0)) { syslog(LOG_ERR, "%s: Failure in cleanup line %d: %m", data->qid, __LINE__); r = SMFIS_TEMPFAIL; } data->cmdFD = -1; remove_working_directory(ctx, data); if (data->dir) { free(data->dir); data->dir = NULL; } if (data->sender) { free(data->sender); data->sender = NULL; } if (data->firstRecip) { free(data->firstRecip); data->firstRecip = NULL; } /* Do NOT free qid here; we need it for logging filter times */ DEBUG_EXIT("cleanup", (r == SMFIS_TEMPFAIL ? "SMFIS_TEMPFAIL" : "SMFIS_CONTINUE")); return r; } static struct smfiDesc filterDescriptor = { "MIMEDefang-" VERSION, /* Filter name */ SMFI_VERSION, /* Version code */ #if SMFI_VERSION >= 2 SMFIF_ADDHDRS|SMFIF_CHGBODY|SMFIF_ADDRCPT|SMFIF_DELRCPT|SMFIF_CHGHDRS #ifdef SMFIF_QUARANTINE |SMFIF_QUARANTINE #endif #ifdef MILTER_BUILDLIB_HAS_CHGFROM |SMFIF_CHGFROM #endif , #elif SMFI_VERSION == 1 /* We can: add a header and may alter body and add/delete recipients*/ SMFIF_MODHDRS|SMFIF_MODBODY|SMFIF_ADDRCPT|SMFIF_DELRCPT, #endif mfconnect, /* connection */ helo, /* HELO */ envfrom, /* MAIL FROM: */ rcptto, /* RCPT TO: */ header, /* Called for each header */ eoh, /* Called at end of headers */ body, /* Called for each body chunk */ eom, /* Called at end of message */ mfabort, /* Called on abort */ mfclose /* Called on connection close */ #ifdef MILTER_BUILDLIB_HAS_UNKNOWN , mf_unknown /* xxfi_unknown */ #endif #ifdef MILTER_BUILDLIB_HAS_DATA , mf_data /* xxfi_data */ #endif #ifdef MILTER_BUILDLIB_HAS_NEGOTIATE , mf_negotiate /* xxfi_negotiate */ #endif }; /********************************************************************** * %FUNCTION: usage * %ARGUMENTS: * None * %RETURNS: * Nothing (exits) * %DESCRIPTION: * Prints usage information ***********************************************************************/ static void usage(void) { fprintf(stderr, "mimedefang version %s\n", VERSION); fprintf(stderr, "Usage: mimedefang [options]\n"); fprintf(stderr, "Options:\n"); fprintf(stderr, " -h -- Print usage info and exit\n"); fprintf(stderr, " -v -- Print version and exit\n"); fprintf(stderr, " -y -- Invoke smfi_setsymlist to set macro list\n"); fprintf(stderr, " -m /path -- Use /path as UNIX-domain socket for multiplexor\n"); fprintf(stderr, " -p /path -- Path to UNIX-domain socket for sendmail communication\n"); fprintf(stderr, " -U user -- Run as user instead of root\n"); fprintf(stderr, " -d -- Enable debugging (do not remove spool files)\n"); fprintf(stderr, " -k -- Do not remove spool files if filter fails\n"); fprintf(stderr, " -z dir -- Spool directory\n"); fprintf(stderr, " -r -- Do relay check before processing body\n"); fprintf(stderr, " -s -- Do sender check before processing body\n"); fprintf(stderr, " -t -- Do recipient checks before processing body\n"); fprintf(stderr, " -q -- Allow new connections to be queued by multiplexor\n"); fprintf(stderr, " -P file -- Write process-ID of daemon to specified file\n"); fprintf(stderr, " -o file -- Use specified file as a lock file\n"); fprintf(stderr, " -T -- Log filter times to syslog\n"); fprintf(stderr, " -b n -- Set listen() backlog to n\n"); fprintf(stderr, " -C -- Try very hard to conserve file descriptors\n"); fprintf(stderr, " -x string -- Add string as X-Scanned-By header\n"); fprintf(stderr, " -X -- Do not add X-Scanned-By header\n"); fprintf(stderr, " -D -- Do not become a daemon (stay in foreground)\n"); fprintf(stderr, " -S facility -- Set syslog(3) facility\n"); fprintf(stderr, " -a macro -- Pass additional Sendmail macro\n"); fprintf(stderr, " -L ip.addr -- Specify 'equivalent-to-loopback' address\n"); fprintf(stderr, " -H -- Do HELO checks before processing any messages\n"); fprintf(stderr, " -c -- Strip bare characters from message body\n"); fprintf(stderr, " -R num -- Reserve num workers for connections from localhost\n"); fprintf(stderr, " -G -- Make sockets group-writable and files group-readable\n"); fprintf(stderr, " -N -- Do not pass rejected recipients to milter (Sendmail\n"); fprintf(stderr, " 8.14.0 and newer only.)\n"); exit(EXIT_FAILURE); } #define REPORT_FAILURE(msg) do { if (kidpipe[1] >= 0) { write(kidpipe[1], "E" msg, strlen(msg)+1); } else { fprintf(stderr, "%s\n", msg); } } while(0) /********************************************************************** * %FUNCTION: main * %ARGUMENTS: * argc, argv -- the usual suspects * %RETURNS: * Whatever smfi_main returns * %DESCRIPTION: * Main program ***********************************************************************/ int main(int argc, char **argv) { int c; int mx_alive; pid_t i; struct passwd *pw = NULL; FILE *fp; int facility = LOG_MAIL; int nodaemon = 0; char buf[SMALLBUF]; int got_p_option = 0; char *sockfile = NULL; int kidpipe[2]; char kidmsg[256]; int pidfile_fd = -1; int lockfile_fd = -1; int rc; int j; mode_t socket_umask = 077; mode_t file_umask = 077; #ifdef ENABLE_DEBUGGING /* Keep debugging malloc macros happy... */ void *ctx = NULL; #endif /* If first arg is "prcap", just print milter capabilities and quit */ if (argc == 2 && !strcmp(argv[1], "prcap")) { dump_milter_buildlib_info(); exit(0); } /* Paranoia time */ umask(077); /* Paranoia time II */ if (getuid() != geteuid()) { fprintf(stderr, "ERROR: %s is NOT intended to run suid! Exiting.\n", argv[0]); exit(EXIT_FAILURE); } if (getgid() != getegid()) { fprintf(stderr, "ERROR: %s is NOT intended to run sgid! Exiting.\n", argv[0]); exit(EXIT_FAILURE); } MyIPAddress = NULL; EquivToLoopback = NULL; /* Determine my IP address */ if (gethostname(buf, sizeof(buf)) >= 0) { struct hostent *he = gethostbyname(buf); struct in_addr in; if (he && he->h_addr) { memcpy(&in.s_addr, he->h_addr, sizeof(in.s_addr)); #ifdef HAVE_INET_NTOP if (inet_ntop(AF_INET, &in.s_addr, buf, sizeof(buf))) { if (*buf) MyIPAddress = strdup_with_log(buf); } #else { char *s = inet_ntoa(in); if (s && *s) MyIPAddress = strdup_with_log(s); } #endif } else { syslog(LOG_WARNING, "Could not determine my own IP address! Ensure that %s has an entry in /etc/hosts or the DNS", buf); fprintf(stderr, "Could not determine my own IP address! Ensure that %s has an entry in /etc/hosts or the DNS\n", buf); } } /* Process command line options */ while ((c = getopt(argc, argv, "GNCDHL:MP:o:R:S:TU:Xa:b:cdhkm:p:qrstvx:z:y")) != -1) { switch (c) { case 'y': setsymlist_ok = 1; break; case 'G': socket_umask = 007; file_umask = 027; break; case 'N': #ifdef MILTER_BUILDLIB_HAS_NEGOTIATE SeeRejectedRecipients = 0; #else fprintf(stderr, "-N option only available with Sendmail/Milter 8.14.0 and higher... ignoring\n"); #endif break; case 'R': sscanf(optarg, "%d", &workersReservedForLoopback); if (workersReservedForLoopback < -1) { workersReservedForLoopback = -1; } break; case 'H': doHeloCheck = 1; break; case 'c': StripBareCR = 1; break; case 'z': SpoolDir = strdup(optarg); if (!SpoolDir) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'q': AllowNewConnectionsToQueue = 1; break; case 'b': sscanf(optarg, "%d", &Backlog); if (Backlog < 5) Backlog = 5; break; case 'C': ConserveDescriptors = 1; break; case 'v': printf("mimedefang version %s\n", VERSION); exit(0); case 'D': nodaemon = 1; break; case 'a': if (strlen(optarg) > 200) { fprintf(stderr, "%s: Macro name too long: %s\n", argv[0], optarg); exit(EXIT_FAILURE); } if (NumAdditionalMacros == MAX_ADDITIONAL_SENDMAIL_MACROS) { fprintf(stderr, "%s: Too many Sendmail macros (max %d)\n", argv[0], MAX_ADDITIONAL_SENDMAIL_MACROS); exit(EXIT_FAILURE); } AdditionalMacros[NumAdditionalMacros] = strdup(optarg); if (!AdditionalMacros[NumAdditionalMacros]) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } NumAdditionalMacros++; break; case 'S': facility = find_syslog_facility(optarg); if (facility < 0) { fprintf(stderr, "%s: Unknown syslog facility %s\n", argv[0], optarg); exit(EXIT_FAILURE); } break; case 'M': /* Ignore. This once set protectMkdirWithMutex, which has been removed. */ break; case 'X': if (scan_body) { fprintf(stderr, "%s: Cannot use multiple '-X' options\n", argv[0]); exit(EXIT_FAILURE); } scan_body = strdup(""); if (!scan_body) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'L': if (EquivToLoopback) { fprintf(stderr, "%s: Cannot use multiple '-L' options\n", argv[0]); exit(EXIT_FAILURE); } EquivToLoopback = strdup(optarg); if (!EquivToLoopback) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'x': if (scan_body) { fprintf(stderr, "%s: Cannot use multiple '-x' options\n", argv[0]); exit(EXIT_FAILURE); } scan_body = strdup(optarg); if (!scan_body) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'T': LogTimes = 1; break; case 'U': /* User to run as */ if (user) { fprintf(stderr, "%s: Cannot use multiple '-U' options\n", argv[0]); exit(EXIT_FAILURE); } user = strdup(optarg); if (!user) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'o': /* Use this as our lock file */ if (lockfile != NULL) { fprintf(stderr, "%s: Cannot use multiple '-o' options\n", argv[0]); exit(EXIT_FAILURE); } lockfile = strdup(optarg); if (!lockfile) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'P': /* Write our pid to this file */ if (pidfile != NULL) { fprintf(stderr, "%s: Cannot use multiple '-P' options\n", argv[0]); exit(EXIT_FAILURE); } pidfile = strdup(optarg); if (!pidfile) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'k': keepFailedDirectories = 1; break; case 's': doSenderCheck = 1; break; case 'r': doRelayCheck = 1; break; case 't': doRecipientCheck = 1; break; case 'h': usage(); break; case 'm': /* Multiplexor */ if (MultiplexorSocketName) { fprintf(stderr, "%s: Cannot use multiple '-m' options\n", argv[0]); exit(EXIT_FAILURE); } MultiplexorSocketName = strdup(optarg); if (!MultiplexorSocketName) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } break; case 'd': DebugMode = 1; break; case 'p': if (optarg == NULL || *optarg == '\0') { fprintf(stderr, "%s: Illegal conn: %s\n", argv[0], optarg); exit(EXIT_FAILURE); } if (sockfile) free(sockfile); sockfile = strdup(optarg); if (!sockfile) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } got_p_option = 1; /* Remove socket from file system if it's a local socket */ (void) remove_local_socket(optarg); if (smfi_setconn(optarg) != MI_SUCCESS) { fprintf(stderr, "%s: Could not open connection %s: %s", argv[0], optarg, strerror(errno)); exit(EXIT_FAILURE); } break; default: usage(); break; } } /* Set SpoolDir if it wasn't set on command line */ if (!SpoolDir) { SpoolDir = SPOOLDIR; } if (!NoDeleteDir) { NoDeleteDir = malloc(strlen(SpoolDir) + strlen(NO_DELETE_NAME) + 1); if (!NoDeleteDir) { fprintf(stderr, "%s: Out of memory\n", argv[0]); exit(EXIT_FAILURE); } strcpy((char *) NoDeleteDir, SpoolDir); strcat((char *) NoDeleteDir, NO_DELETE_NAME); } if (!scan_body) { scan_body = SCAN_BODY; } if (Backlog > 0) { smfi_setbacklog(Backlog); } if (!got_p_option) { fprintf(stderr, "%s: You must use the `-p' option.\n", argv[0]); exit(EXIT_FAILURE); } if (!MultiplexorSocketName) { fprintf(stderr, "%s: You must use the `-m' option.\n", argv[0]); exit(EXIT_FAILURE); } /* Open the pidfile as root. We'll write the pid later on in the grandchild */ if (pidfile) { pidfile_fd = open(pidfile, O_RDWR|O_CREAT, 0666); if (pidfile_fd < 0) { syslog(LOG_ERR, "Could not open PID file %s: %m", pidfile); exit(EXIT_FAILURE); } /* It needs to be world-readable */ fchmod(pidfile_fd, 0644); } /* Look up user */ if (user) { pw = getpwnam(user); if (!pw) { fprintf(stderr, "%s: Unknown user `%s'", argv[0], user); exit(EXIT_FAILURE); } if (drop_privs(user, pw->pw_uid, pw->pw_gid) < 0) { fprintf(stderr, "%s: Could not drop privileges: %s", argv[0], strerror(errno)); exit(EXIT_FAILURE); } free(user); } /* Warn */ if (!getuid() || !geteuid()) { fprintf(stderr, "ERROR: You must not run mimedefang as root.\n" "Use the -U option to set a non-root user.\n"); exit(EXIT_FAILURE); } if (chdir(SpoolDir) < 0) { fprintf(stderr, "%s: Unable to chdir(%s): %s\n", argv[0], SpoolDir, strerror(errno)); exit(EXIT_FAILURE); } /* Read key file if present */ fp = fopen(KEY_FILE, "r"); if (fp) { if(fgets(ValidateHeader, sizeof(ValidateHeader), fp) != NULL) { fclose(fp); chomp(ValidateHeader); } else { ValidateHeader[0] = 0; } } else { if(errno != ENOENT) { fprintf(stderr, "%s: cannot read key file %s, error: %s\n", argv[0], KEY_FILE, strerror(errno)); } ValidateHeader[0] = 0; } if (smfi_register(filterDescriptor) == MI_FAILURE) { fprintf(stderr, "%s: smfi_register failed\n", argv[0]); exit(EXIT_FAILURE); } (void) closelog(); /* Daemonize */ if (!nodaemon) { /* Set up a pipe so child can report back when it's happy */ if (pipe(kidpipe) < 0) { perror("pipe"); exit(EXIT_FAILURE); } i = fork(); if (i < 0) { fprintf(stderr, "%s: fork() failed\n", argv[0]); exit(EXIT_FAILURE); } else if (i != 0) { /* parent */ close(kidpipe[1]); /* Wait for a message from kid */ i = read(kidpipe[0], kidmsg, sizeof(kidmsg) - 1); if (i < 0) { fprintf(stderr, "Error reading message from child: %s\n", strerror(errno)); exit(EXIT_FAILURE); } /* Zero-terminate the string */ kidmsg[i] = 0; if (i == 1 && kidmsg[0] == 'X') { /* Child indicated successful startup */ exit(EXIT_SUCCESS); } if (i > 1 && kidmsg[0] == 'E') { /* Child indicated error */ fprintf(stderr, "Error from child: %s\n", kidmsg+1); exit(EXIT_FAILURE); } /* Unknown status from child */ fprintf(stderr, "Unknown reply from child: %s\n", kidmsg); exit(EXIT_FAILURE); } /* In the child */ close(kidpipe[0]); setsid(); signal(SIGHUP, SIG_IGN); i = fork(); if (i < 0) { REPORT_FAILURE("fork() failed"); exit(EXIT_FAILURE); } else if (i != 0) { exit(EXIT_SUCCESS); } } else { /* nodaemon */ kidpipe[0] = -1; kidpipe[1] = -1; } /* In the actual daemon */ for (j=0; j= 0) { mx_alive = 1; break; } /* Sleep for 200ms */ select(0, NULL, NULL, NULL, &sleeptime); } if (mx_alive) { syslog(LOG_INFO, "Multiplexor alive - entering main loop"); } else { /* Signal the waiting parent */ REPORT_FAILURE("Multiplexor socket did not appear. Exiting."); if (pidfile) unlink(pidfile); if (lockfile) unlink(lockfile); exit(EXIT_FAILURE); } /* Tell the waiting parent that everything is A-OK */ if (kidpipe[1] >= 0) { write(kidpipe[1], "X", 1); close(kidpipe[1]); } rc = (int) smfi_main(); if (pidfile) { unlink(pidfile); } if (lockfile) { unlink(lockfile); } if (sockfile) { remove_local_socket(sockfile); } return rc; } /********************************************************************** * %FUNCTION: append_macro_value * %ARGUMENTS: * ctx -- Sendmail milter context * macro -- name of a macro * %RETURNS: * Nothing * %DESCRIPTION: * Sends a command to Perl code to set a macro value ***********************************************************************/ static void append_macro_value(dynamic_buffer *dbuf, SMFICTX *ctx, char *macro) { struct privdata *data; char *val; char buf[256]; data = DATA; if (!data) return; if (*macro && *(macro+1)) { /* Longer than 1 char -- use curlies */ snprintf(buf, sizeof(buf), "{%s}", macro); val = smfi_getsymval(ctx, buf); } else { val = smfi_getsymval(ctx, macro); } if (!val) return; dbuf_putc(dbuf, '='); append_percent_encoded(dbuf, macro); dbuf_putc(dbuf, ' '); append_percent_encoded(dbuf, val); dbuf_putc(dbuf, '\n'); } /********************************************************************** * %FUNCTION: remove_working_directory * %ARGUMENTS: * data -- our private data * %RETURNS: * Nothing * %DESCRIPTION: * Removes working directory if appropriate. ***********************************************************************/ static void remove_working_directory(SMFICTX *ctx, struct privdata *data) { if (!data || !data->dir || !*(data->dir)) return; /* Don't remove if in debug mode or various other reasons */ if (DebugMode) { syslog(LOG_INFO, "%s: Not cleaning up %s because of command-line `-d' flag", data->qid, data->dir); return; } if (access(NoDeleteDir, F_OK) == 0) { syslog(LOG_INFO, "%s: Not cleaning up %s because of %s", data->qid, (data->dir ? data->dir : ""), NoDeleteDir); return; } if (keepFailedDirectories && data->filterFailed) { syslog(LOG_WARNING, "%s: Filter failed. Message kept in %s", data->qid, data->dir); return; } if (rm_r(data->qid, data->dir) < 0) { syslog(LOG_ERR, "%s: failed to clean up %s: %m", data->qid, data->dir); } } /********************************************************************** * %FUNCTION: set_dsn * %ARGUMENTS: * data -- our private data area * ctx -- Milter context * buf2 -- return from a relay/sender/filter check. Consists of * space-separated "reply code dsn sleep_amount" list. * num -- 0, 4 or 5 -- if 4 or 5, we use the code and dsn in set_reply. * %RETURNS: * Nothing * %DESCRIPTION: * Sets SMTP reply and possibly delays ***********************************************************************/ static void set_dsn(SMFICTX *ctx, char *buf2, int num) { char *reply, *code, *dsn, *sleepstr; if (*buf2) { split_on_space4(buf2, &reply, &code, &dsn, &sleepstr); percent_decode(code); percent_decode(dsn); percent_decode(reply); percent_decode(sleepstr); do_delay(sleepstr); if (num == 4 || num == 5) { struct privdata *data = DATA; if (num == 5) { MD_SMFI_TRY(set_reply, (ctx, "5", code, dsn, reply)); } else { MD_SMFI_TRY(set_reply, (ctx, "4", code, dsn, reply)); } } } } /********************************************************************** * %FUNCTION: do_sm_quarantine * %ARGUMENTS: * ctx -- Milter context * reason -- reason for quarantine * %RETURNS: * Whatever smfi_quarantine returns * %DESCRIPTION: * Quarantines a message using Sendmail's quarantine facility, if supported. ***********************************************************************/ static int do_sm_quarantine(SMFICTX *ctx, char const *reason) { #ifdef SMFIF_QUARANTINE return smfi_quarantine(ctx, (char *) reason); #else syslog(LOG_WARNING, "smfi_quarantine not supported: Requires Sendmail 8.13.0 or later"); return MI_FAILURE; #endif } /********************************************************************** * %FUNCTION: append_percent_encoded * %ARGUMENTS: * dbuf -- dynamic buffer to append to * buf -- a buffer * %RETURNS: * Nothing * %DESCRIPTION: * Appends a percent-encoded version of "buf" to dbuf. ***********************************************************************/ static void append_percent_encoded(dynamic_buffer *dbuf, char const *buf) { char pbuf[16]; unsigned char const *ubuf = (unsigned char const *) buf; unsigned int c; while ((c = *ubuf++) != 0) { if (c <= 32 || c > 126 || c == '%') { sprintf(pbuf, "%%%02X", c); dbuf_puts(dbuf, pbuf); } else { dbuf_putc(dbuf, c); } } } /********************************************************************** * %FUNCTION: append_mx_command * %ARGUMENTS: * dbuf -- dynamic buffer to append to * cmd -- the command to write. A single character. * buf -- command arguments * %RETURNS: * Nothing * %DESCRIPTION: * Appends a command to dynamic buffer ***********************************************************************/ static void append_mx_command(dynamic_buffer *dbuf, char cmd, char const *buf) { dbuf_putc(dbuf, cmd); if (buf) { append_percent_encoded(dbuf, buf); } dbuf_putc(dbuf, '\n'); } /********************************************************************** * %FUNCTION: safe_append_header * %ARGUMENTS: * dbuf -- dynamic buffer to append to * str -- a string value * %RETURNS: * 0 if header seems OK; 1 if suspicious character found. * %DESCRIPTION: * Writes "str" to dbuf with the following changes: * CR -> written as space ***********************************************************************/ static int safe_append_header(dynamic_buffer *dbuf, char *str) { int suspicious = 0; for(; *str; str++) { /* Do not write \r to header file -- convert to space */ if (*str == '\r') { if (*(str+1) != '\n') { suspicious = 1; dbuf_putc(dbuf, ' '); continue; } } dbuf_putc(dbuf, *str); } return suspicious; } static int write_dbuf(dynamic_buffer *dbuf, int fd, struct privdata *data, char const *filename) { int i; i = writen(fd, DBUF_VAL(dbuf), DBUF_LEN(dbuf)); if (i == DBUF_LEN(dbuf)) { return 0; } syslog(LOG_WARNING, "%s: Unable to write %d bytes to file %s (ret = %d): %m", data->qid, DBUF_LEN(dbuf), filename, i); return -1; } /********************************************************************** *%FUNCTION: set_queueid *%ARGUMENTS: * ctx -- Sendmail filter mail context *%RETURNS: * Nothing. *%DESCRIPTION: * Obtains the Sendmail "i" macro and sets the privata data->qid * string to the value of the macro, if its value could be obtained. ***********************************************************************/ static void set_queueid(SMFICTX *ctx) { struct privdata *data = DATA; char const *queueid; /* Get value of "i" macro */ queueid = smfi_getsymval(ctx, "i"); if (!queueid) { /* Macro not set - nothing we can do. */ return; } /* If qid is already set and is the same as what we have, do nothing */ if (data->qid && !strcmp(data->qid, queueid)) { return; } /* If qid is already set, free it */ if (data->qid && data->qid != NOQUEUE) { free(data->qid); data->qid_written = 0; } data->qid = strdup_with_log(queueid); if (!data->qid) { data->qid = NOQUEUE; } } mimedefang-3.6/mimedefang.h000066400000000000000000000101201475763067200157360ustar00rootroot00000000000000/*********************************************************************** * * mimedefang.h * * External declarations and defines. * * Copyright (C) 2002-2005 by Roaring Penguin Software Inc. * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #ifndef MIMEDEFANG_H #define MIMEDEFANG_H 1 #define SMALLBUF 16384 #define BIGBUF 65536 /* Identifier is 7 chars long: 5 time plus 2 counter */ #define MX_ID_LEN 7 #include #include #include /* For struct sockaddr */ extern void chomp(char *str); extern int percent_encode(char const *in, char *out, int outlen); extern void percent_decode(char *buf); extern int MXCheckFreeWorkers(char const *sockname, char const *qid); extern int MXScanDir(char const *sockname, char const *qid, char const *dir); extern int MXCommand(char const *sockname, char const *cmd, char *buf, int len, char const *qid); extern int MXRelayOK(char const *sockname, char *msg, char const *ip, char const *name, unsigned int port, char const *myip, unsigned int daemon_port, char const *qid); extern int MXHeloOK(char const *sockname, char *msg, char const *ip, char const *name, char const *helo, unsigned int port, char const *myip, unsigned int daemon_port, char const *qid); extern int MXSenderOK(char const *sockname, char *msg, char const **sender_argv, char const *ip, char const *name, char const *helo, char const *dir, char const *qid); extern int MXRecipientOK(char const *sockname, char *msg, char const **recip_argv, char const *sender, char const *ip, char const *name, char const *firstRecip, char const *helo, char const *dir, char const *qid, char const *rcpt_mailer, char const *rcpt_host, char const *rcpt_addr); extern int safeWriteHeader(int fd, char *str); extern void split_on_space(char *buf, char **first, char **rest); extern void split_on_space3(char *buf, char **first, char **second, char **rest); extern void split_on_space4(char *buf, char **first, char **second, char **third, char **rest); extern void *malloc_with_log(size_t s); extern char *strdup_with_log(char const *s); extern int rm_r(char const *qid, char const *dir); extern int writen(int fd, char const *buf, size_t len); extern int readn(int fd, void *buf, size_t count); extern int writestr(int fd, char const *buf); extern int closefd(int fd); extern int validate_smtp_code(char const *code, char const *first); extern int validate_smtp_dsn(char const *dsn, char const *first); extern int make_listening_socket(char const *str, int backlog, int must_be_unix); extern void do_delay(char const *sleepstr); extern int is_localhost(struct sockaddr *); extern int remove_local_socket(char const *str); extern int write_and_lock_pidfile(char const *pidfile, char **lockfile, int fd); #ifdef EMBED_PERL extern int make_embedded_interpreter(char const *progPath, char const *subFilter, int wantStatusReports, char **env); extern void init_embedded_interpreter(int, char **, char **); extern void deinit_embedded_interpreter(void); extern void term_embedded_interpreter(void); extern void run_embedded_filter(void); extern void dump_milter_buildlib_info(void); #endif #ifndef timespecsub #define timespecsub(tsp, usp, vsp) \ do { \ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ if ((vsp)->tv_nsec < 0) { \ (vsp)->tv_sec--; \ (vsp)->tv_nsec += 1000000000L; \ } \ } while (0) #endif extern char *gen_mx_id(char *); /* Magic return values */ #define MD_TEMPFAIL -1 #define MD_REJECT 0 #define MD_CONTINUE 1 #define MD_ACCEPT_AND_NO_MORE_FILTERING 2 #define MD_DISCARD 3 #endif mimedefang-3.6/mimedefang.pl.8.in000066400000000000000000000042701475763067200167060ustar00rootroot00000000000000.\" $Id$ .\"" .TH MIMEDEFANG.PL 8 "8 February 2005" .UC 4 .SH NAME mimedefang.pl \- Perl script to scan MIME messages. .SH SYNOPSIS .B mimedefang.pl [-f \fIfilter\fR] \fIdir\fR .SH DESCRIPTION \fBmimedefang.pl\fR is a Perl script designed to work with \fBmimedefang\fR(8). It takes a single argument which is a directory which should contain files laid out as described in \fBmimedefang\fR(8). .SH OPTIONS .TP .B \-f \fIfilter\fR Specifies the name of the file containing the filter. If this option is omitted, the default filter \fB@CONFDIR_EVAL@/mimedefang-filter\fR is used. .SH OPERATION \fBmimedefang.pl\fR evaluates the file \fB@CONFDIR_EVAL@/mimedefang-filter\fR as a Perl fragment. This file should define the \fBfilter\fR procedure. For each part of a MIME message, \fBmimedefang.pl\fR calls \fBfilter\fR and disposes of the part as instructed by the filter. The various modes of disposition are described in \fBmimedefang-filter\fR(5). .SH TESTING FILTERS You are \fIstrongly\fR recommended to test your filter before installing it in \fB@CONFDIR_EVAL@/mimedefang-filter\fR. To test the filter, save it in a file (e.g. \fBtest-filter\fR) and run this command: .nf mimedefang.pl -f test-filter -test .fi This tests the filter for syntactic correctness. If it passes, you can install it as a production filter. (Note that the test tests only for correct Perl syntax; it doesn't make sure your filter does something sensible.) .SH MISCELLANEOUS OPTIONS There are a few other ways to invoke mimedefang.pl: .nf mimedefang.pl -features .fi prints a list of detected optional Perl modules. The output looks something like this: .nf SpamAssassin: yes .fi .PP .nf mimedefang.pl -validate .fi calls the function filter_validate, if it is defined in your filter. filter_validate should return an integer; this becomes the exit code. If filter_validate does not exist, an error message is printed and \fBmimedefang.pl\fR exits with an exit code of 1. .SH AUTHOR \fBmimedefang.pl\fR was written by Dianne Skoll . The \fBmimedefang\fR home page is \fIhttps://www.mimedefang.org/\fR. .SH SEE ALSO mimedefang(8), mimedefang-filter(5), mimedefang-protocol(7), mimedefang-release(8) mimedefang-3.6/mimedefang.pl.in000077500000000000000000001176631475763067200165560ustar00rootroot00000000000000#!@PERL@ # -*- Perl -*- #*********************************************************************** # # mimedefang.pl # # Perl scanner which parses MIME messages and filters or removes # objectionable attachments. # # Copyright (C) 2000-2005 Roaring Penguin Software Inc. # # This program may be distributed under the terms of the GNU General # Public License, Version 2. # # This program was derived from the sample program "mimeexplode" # in the MIME-Tools Perl module distribution. # #*********************************************************************** use strict; use warnings; # Move site library directory ahead of default library directory in @INC. # That's so we can sanely package our own version of MIME::Base64 that # won't conflict with the built-in one on RPM-based platforms. use lib '@PERLINSTALLSITELIB@'; use FindBin; use lib "$FindBin::Bin/modules/lib"; require 5.008; package main; use Mail::MIMEDefang; use Mail::MIMEDefang::MIME; use Mail::MIMEDefang::Net; use Mail::MIMEDefang::Mail; use Mail::MIMEDefang::RFC2822; use Mail::MIMEDefang::Utils; use Mail::MIMEDefang::Actions; use Mail::MIMEDefang::Antispam; use Carp; use Socket; use IO::Socket; use IO::Select; use IO::Handle; use IO::File; use MIME::Tools 5.410 (); use MIME::Words qw(:all); use Digest::SHA; use Time::Local; use MIME::Parser; use Sys::Hostname; use File::Spec qw (); use Errno qw(ENOENT EACCES); undef $SASpamTester; undef $PrivateMyHostName; undef @VirusScannerMessageRoutines; undef @VirusScannerEntityRoutines; $VirusScannerRoutinesInitialized = 0; $SALocalTestsOnly = 1; $DoStatusTags = 0; $Features{'Virus:AVP'} = ('@AVP@' ne '/bin/false' ? '@AVP@' : 0); $Features{'Virus:AVP5'} = ('@AVP5@' ne '/bin/false' ? '@AVP5@' : 0); $Features{'Virus:KAVSCANNER'} = ('@KAVSCANNER@' ne '/bin/false' ? '@KAVSCANNER@' : 0); $Features{'Virus:CLAMAV'} = ('@CLAMSCAN@' ne '/bin/false' ? '@CLAMSCAN@' : 0); $Features{'Virus:CLAMD'} = ('@CLAMD@' ne '/bin/false' ? '@CLAMD@' : 0); $Features{'Virus:CLAMDSCAN'} = ('/bin/false' ne '/bin/false' ? '/bin/false' : 0); $Features{'Virus:FPROT'} = ('@FPROT@' ne '/bin/false' ? '@FPROT@' : 0); $Features{'Virus:FPSCAN'} = ('@FPSCAN@' ne '/bin/false' ? '@FPSCAN@' : 0); $Features{'Virus:FSAV'} = ('@FSAV@' ne '/bin/false' ? '@FSAV@' : 0); $Features{'Virus:HBEDV'} = ('@HBEDV@' ne '/bin/false' ? '@HBEDV@' : 0); $Features{'Virus:VEXIRA'} = ('@VEXIRA@' ne '/bin/false' ? '@VEXIRA@' : 0); $Features{'Virus:NAI'} = ('@NAI@' ne '/bin/false' ? '@NAI@' : 0); $Features{'Virus:BDC'} = ('@BDC@' ne '/bin/false' ? '@BDC@' : 0); $Features{'Virus:NVCC'} = ('@NVCC@' ne '/bin/false' ? '@NVCC@' : 0); $Features{'Virus:SymantecCSS'} = 0; # Ditto $Features{'Virus:FPROTD'} = 0; $Features{'Virus:FPROTD6'} = 0; $Features{'Virus:SOPHIE'} = ('@SOPHIE@' ne '/bin/false' ? '@SOPHIE@' : 0); $Features{'Virus:SOPHOS'} = ('@SOPHOS@' ne '/bin/false' ? '@SOPHOS@' : 0); $Features{'Virus:SAVSCAN'} = ('@SAVSCAN@' ne '/bin/false' ? '@SAVSCAN@' : 0); $Features{'Virus:TREND'} = ('@TREND@' ne '/bin/false' ? '@TREND@' : 0); $Features{'Virus:TROPHIE'} = ('@TROPHIE@' ne '/bin/false' ? '@TROPHIE@' : 0); $Features{'Virus:CSAV'} = ('@CSAV@' ne '/bin/false' ? '@CSAV@' : 0); $Features{'Virus:NOD32'} = ('@NOD32@' ne '/bin/false' ? '@NOD32@' : 0); $Features{'Path:SENDMAIL'} = '@SENDMAILPROG@'; $Features{'Path:QUARANTINEDIR'} = '@QDIR@'; $Features{'Path:SPOOLDIR'} = '@SPOOLDIR@'; $Features{'Path:CONFDIR'} = '@CONFDIR_EVAL@'; $Features{'Path:CLAMDCONF'} = '@CONFDIR_EVAL@/MD-clamdscan.conf'; $Features{"Path:RSPAMC"} = ('@RSPAMC@' ne '/bin/false' ? '@RSPAMC@' : 0); # Not in server mode by default $ServerMode = 0; # Don't add Apparently-To: header for SpamAssassin $AddApparentlyToForSpamAssassin = 0; # Don't add warnings inline (add a MIME part instead) $AddWarningsInline = 0; # M$ Exchange or Outlook cannot display multiple Inline: parts $Stupidity{"NoMultipleInlines"} = 0; # Warning goes at beginning $WarningLocation = 0; # No limit to complexity of MIME messages $MaxMIMEParts = -1; # Cache the timzone calculation $CachedTimezone = ""; # Syslog facility is "mail" $SyslogFacility = "mail"; undef $GraphDefangSyslogFacility; $URL = 'https://mimedefang.org/enduser/'; $CSSHost = "127.0.0.1:7777:local"; $FprotdHost = "127.0.0.1:10200"; $Fprotd6Host = "127.0.0.1:10200"; $SophieSock = '@SPOOLDIR@/sophie'; $ClamdSock = '@SPOOLDIR@/clamd.sock'; $TrophieSock = '@SPOOLDIR@/trophie'; #*********************************************************************** # %PROCEDURE: md_copy_orig_msg_to_work_dir # %ARGUMENTS: # None # %DESCRIPTION: # Copies original INPUTMSG file into work directory for virus-scanning # %RETURNS: # 1 on success, 0 on failure. #*********************************************************************** sub md_copy_orig_msg_to_work_dir { return if (!in_message_context("md_copy_orig_msg_to_work_dir")); return copy_or_link("INPUTMSG", "Work/INPUTMSG"); } #*********************************************************************** # %PROCEDURE: fatal # %ARGUMENTS: # msg -- message # %RETURNS: # Nothing # %DESCRIPTION: # Logs an error and (if we are not in server mode) exits. #*********************************************************************** sub fatal { my($msg) = @_; md_syslog('err', "$msg"); if (!$ServerMode) { croak($msg); } else { print_and_flush("error: $msg"); } } #*********************************************************************** # %PROCEDURE: get_host_name # %ARGUMENTS: # None # %RETURNS: # Local host name, if it could be determined. #*********************************************************************** sub get_host_name { return Mail::MIMEDefang::Net::get_host_name($PrivateMyHostName); } sub rfc2822_date { return Mail::MIMEDefang::RFC2822::rfc2822_date($CachedTimezone); } sub header_timezone { my($now) = @_; return Mail::MIMEDefang::RFC2822::header_timezone($CachedTimezone, $now); } #*********************************************************************** # %PROCEDURE: gen_msgid_header # %ARGUMENTS: # None # %RETURNS: # A string like this: "Message-ID: \n" # %DESCRIPTION: # Generates RFC2822-compliant Message-ID headers. #*********************************************************************** sub gen_msgid_header { # Generate a "random" message ID that looks # similar to sendmail's for SpamAssassin comparing # Received / MessageID QueueID return Mail::MIMEDefang::RFC2822::gen_msgid_header($QueueID, get_host_name()); } #*********************************************************************** # %PROCEDURE: stream_by_recipient # %ARGUMENTS: # None # %RETURNS: # True if message was resent; false if it was for only a single user # %DESCRIPTION: # If there is more than one recipient, re-send the message once per # recipient. # MAKE SURE your sendmail is set up to use # /etc/mail/submit.cf. # # Use this # ONLY from filter_begin() and ONLY if you have Sendmail 8.12 or newer, # and ONLY if locally-submitted mail goes via SMTP. #*********************************************************************** sub stream_by_recipient { return 0 if (!in_message_context("stream_by_recipient")); if ($#Recipients <= 0) { # Only one recipient (or none??) return 0; } foreach my $recip (@Recipients) { if (!resend_message_one_recipient($recip)) { md_syslog('crit', 'stream_by_recipient: COULD NOT RESEND MESSAGE - PLEASE INVESTIGATE'); action_bounce("Unable to stream message"); # We return 1 to avoid rest of filter return 1; } } $TerminateAndDiscard = 1; return 1; } #*********************************************************************** # %PROCEDURE: stream_by_domain # %ARGUMENTS: # None # %RETURNS: # True if message was resent; false if it was for only a single domain. # %DESCRIPTION: # Checks each recipient. If recipients are in more than one domain # (foo@abc.com, foo@xyz.com), the message is re-sent (once per domain), # action_discard() is called, and scanning terminates. Use this # ONLY from filter_begin() and ONLY if you have Sendmail 8.12 or newer, # and ONLY if locally-submitted mail goes via SMTP. #*********************************************************************** sub stream_by_domain { my(%Domains, $dom, $nkeys); return 0 if (!in_message_context("stream_by_domain")); # Grab list of domains of recipients foreach my $recip (@Recipients) { $dom = $recip; # Remove angle brackets $dom =~ s/[<>]//g; # Get domain $dom =~ s/.*\@//; if (!defined($Domains{$dom})) { $Domains{$dom} = [ $recip ]; } else { push( @{ $Domains{$dom} }, $recip); } $Domain = $dom; } $nkeys = keys(%Domains); if ($nkeys > 1) { # More than one domain. Cancel and resend foreach my $key (keys %Domains) { if (!resend_message(@{$Domains{$key}})) { md_syslog('crit', 'stream_by_domain: COULD NOT RESEND MESSAGE - PLEASE INVESTIGATE'); action_bounce("Unable to stream message"); # We return 1 to avoid rest of filter return 1; } } $TerminateAndDiscard = 1; return 1; } return 0; } #*********************************************************************** # %PROCEDURE: main # %ARGUMENTS: # workdir -- directory to "chdir" to and do all work in. # msg -- file containing MIME message # %RETURNS: # 0 if parse went well; non-zero otherwise. # %DESCRIPTION: # Main program. Splits the MIME message up and then reconstructs it. #*********************************************************************** sub main { my($Filter); my($workdir); $Filter = '@CONFDIR_EVAL@/mimedefang-filter'; $DoStatusTags = 0; my($ip, $name, $sender, $recip, $firstRecip, $helo, $map, $key); # Check for "-f filter-file" option if ($#ARGV >= 2) { if ($ARGV[0] eq "-f") { $Filter = $ARGV[1]; shift @ARGV; shift @ARGV; } } if ($#ARGV != 0) { md_syslog('warning', "Usage: mimedefang.pl [-f filter] workdir | -server | -test | -features | -validate"); print STDERR "Usage: mimedefang.pl [-f filter] workdir | -server | -test | -features | -validate\n"; return 1; } $ValidateIPHeader = ""; my $in; if (open($in, '<', '@CONFDIR_EVAL@/mimedefang-ip-key')) { $ValidateIPHeader = <$in>; chomp($ValidateIPHeader); close($in); } # These are set unconditionally; filter() can change them. $NotifySenderSubject = "MIMEDefang Notification"; $NotifyAdministratorSubject = "MIMEDefang Notification"; $QuarantineSubject = "MIMEDefang Quarantine Report"; $NotifyNoPreamble = 0; # Load the filter init_globals(); if ($ValidateIPHeader ne "" and $ValidateIPHeader !~ /^X-MIMEDefang-Relay/) { md_syslog('err', "Invalid value for mimedefang-ip-key: $ValidateIPHeader"); $ValidateIPHeader = ""; } if (! -r $Filter) { md_syslog('err', "Cannot read filter $Filter: Check permissions. mimedefang.pl will not work."); } # Special-case /dev/null so we can invoke without # a filter for test purposes. unless ($Filter eq '/dev/null') { require $Filter; } # In case it wasn't done in filter... won't hurt to do it again detect_and_load_perl_modules(); # Load Antivirus code only if needed if (Mail::MIMEDefang::detect_antivirus_support()) { use Mail::MIMEDefang::Antivirus; } # Backward-compatibility if (defined($Administrator)) { $AdminAddress = $Administrator; md_syslog('warning', 'Variable $Administrator is deprecated. Use $AdminAddress instead'); } # Defaults $AdminName = 'MIMEDefang Administrator' unless defined($AdminName); $AdminAddress = 'postmaster@localhost' unless defined($AdminAddress); $DaemonName = 'MIMEDefang' unless defined($DaemonName); $DaemonAddress = 'mailer-daemon@localhost' unless defined($DaemonAddress); $SALocalTestsOnly = 1 unless defined($SALocalTestsOnly); if (!defined($GeneralWarning)) { $GeneralWarning = "WARNING: This e-mail has been altered by MIMEDefang. Following this\n" . "paragraph are indications of the actual changes made. For more\n" . "information about your site's MIMEDefang policy, contact\n" . "$AdminName <$AdminAddress>. For more information about MIMEDefang, see:\n\n" . " $URL\n\n"; } # check dir $workdir = $ARGV[0]; if ($workdir eq "-test") { printf("Filter $Filter seems syntactically correct.\n"); exit(0); } if ($workdir eq "-validate") { if (defined(&filter_validate)) { exit(filter_validate()); } print STDERR "ERROR: You must define a function called filter_validate in your filter\nto use the -validate argument.\n"; exit(1); } if ($workdir eq "-features") { # Print available features my($ans); # Print MIMEDefang version my $ver = md_version(); print("MIMEDefang version $ver\n\n"); # Print the features we have first foreach my $thing (sort keys %Features) { my($feat); $feat = $Features{$thing}; $ans = $feat ? "yes" : "no"; if ($ans eq "yes") { if ($feat ne "1") { printf("%-30s: %s\n", $thing, "yes ($feat)"); } else { printf("%-30s: %s\n", $thing, "yes"); } } } # And now print the ones we don't have foreach my $thing (sort keys %Features) { my($feat); $feat = $Features{$thing}; $ans = $feat ? "yes" : "no"; if ($ans eq "no") { printf("%-30s: %s\n", $thing, "no"); } } # And print Perl module versions print("\n"); my($version); foreach my $thing (qw(Archive::Zip Digest::SHA HTML::Parser IO::Socket MIME::Base64 MIME::Tools MIME::Words Mail::Mailer Mail::SpamAssassin Net::DNS Unix::Syslog )) { unless (eval "require $thing") { printf("%-30s: missing\n", $thing); next; } $version = $thing->VERSION(); $version = "UNKNOWN" unless defined($version); printf("%-30s: Version %s\n", $thing, $version); } exit(0); } my $enter_main_loop; if ($workdir eq "-server") { $ServerMode = 1; $enter_main_loop = 1; } elsif ($workdir eq "-serveru") { $ServerMode = 1; $enter_main_loop = 1; $DoStatusTags = 1; } elsif ($workdir eq "-embserver") { $ServerMode = 1; $enter_main_loop = 0; } elsif ($workdir eq "-embserveru") { $ServerMode = 1; $DoStatusTags = 1; $enter_main_loop = 0; } else { $ServerMode = 0; } if (!$ServerMode) { chdir($Features{'Path:SPOOLDIR'}); if (defined(&filter_initialize)) { filter_initialize(); } init_globals(); do_scan($workdir); exit(0); } do_main_loop() if $enter_main_loop; } sub do_main_loop { init_status_tag(); chdir($Features{'Path:SPOOLDIR'}); if(defined(&filter_initialize)) { filter_initialize(); } # Infinite server loop... well, not quite infinite; we stop on EOF # from STDIN. while (my $line = ) { chomp $line; # Clear out vars so they aren't used by filter_begin, etc. init_globals(); # Change to spool dir -- ignore error chdir($Features{'Path:SPOOLDIR'}); my ($cmd, @args) = map { percent_decode($_) } split(/\s+/, $line); $cmd = lc $cmd; no strict 'refs'; my $cmd_handler = *{"handle_${cmd}"}; use strict 'refs'; if (defined(&{'handle_' . $cmd})) { no strict 'refs'; &{'handle_' . $cmd}(@args); use strict 'refs'; } else { unknown_command_handler( $cmd, @args ); } } # EOF on STDIN... time to bye-bye... if(defined(&filter_cleanup)) { exit(filter_cleanup()); } exit(0); } # This is the only command handler not named handle_XXXXX for two reasons: # 1) We don't want someone to pass in a command named 'unknown_command' and # get this handler. # 2) This handler takes $cmd as first argument, whereas the others do not get # their own name passed down as the first arg. sub unknown_command_handler { my ($cmd, @args) = @_; if(!defined(&filter_unknown_cmd)) { print_and_flush('error: Unknown command'); return; } my ($code, @list) = filter_unknown_cmd($cmd, @args); $code = "error:" if($code ne "ok" and $code ne "error:"); my $reply = join(' ', map { percent_encode($_) } ($code, @list) ); print_and_flush($reply); } sub handle_ping { print_and_flush('PONG'); } sub handle_scan { my ($dummyqid, $workdir) = @_; # EVIL FOLLOWS. AVERT YOUR EYES. # File::Spec::Unix caches $ENV{'TMPDIR'}. # We want to force it to cache it BEFORE # we muck about with the env. variable, # otherwise code that uses File::Spec->tmpfile # will fail when our transient $workdir/tmp is # deleted. Horrible. # FORCE File::Spec to cache a reasonable tmpfile File::Spec->tmpdir(); my $old_tmpdir; mkdir("$workdir/tmp"); if(-d "$workdir/tmp") { $old_tmpdir = $ENV{'TMPDIR'}; $ENV{'TMPDIR'} = "$workdir/tmp"; } else { $old_tmpdir = undef; } do_scan($workdir); # If we set TMPDIR to $workdir/tmp, reset it # here. if(exists($ENV{'TMPDIR'}) && $ENV{'TMPDIR'} eq "$workdir/tmp") { if($old_tmpdir) { $ENV{'TMPDIR'} = $old_tmpdir; } else { delete($ENV{'TMPDIR'}); } } chdir($Features{'Path:SPOOLDIR'}); } sub handle_map { my ($map, $key) = @_; if(!defined(&filter_map)) { md_syslog('err', "No filter_map function defined"); print_and_flush('PERM No filter_map function defined'); return; } my ($code, $val) = filter_map($map, $key); if( $code ne "OK" and $code ne "NOTFOUND" and $code ne "TEMP" and $code ne "TIMEOUT" and $code ne "PERM") { md_syslog('err', "Invalid code from filter_map: $code"); print_and_flush('PERM Invalid code from filter_map: ' . percent_encode($code)); return; } print_and_flush("$code " . percent_encode($val)); } #*********************************************************************** # %PROCEDURE: handle_tick # %ARGUMENTS: # Tick value (integer) # %DESCRIPTION: # May be called periodically by multiplexor; runs filter_tick routine # if it exists. # %RETURNS: # Nothing #*********************************************************************** sub handle_tick { my ($tick_no) = @_; $tick_no ||= 0; if(defined(&filter_tick)) { filter_tick($tick_no); print_and_flush("tock $tick_no"); } else { print_and_flush("error: tick $tick_no: filter_tick undefined"); } } #*********************************************************************** # %PROCEDURE: handle_relayok # %ARGUMENTS: # hostip -- IP address of relay host # hostname -- name of relay host # port -- client port # myip -- my IP address # myport -- my listening port # %RETURNS: # Nothing, but prints "ok 1" if we accept connection, "ok 0" if not. #*********************************************************************** sub handle_relayok { my ($hostip, $hostname, $port, $myip, $myport, $qid) = @_; if(!defined(&filter_relay)) { send_filter_answer('CONTINUE', "ok", "filter_relay", "host $hostip ($hostname)"); return; } # Set up globals $RelayAddr = $hostip; $RelayHostname = $hostname; $QueueID = $qid; $MsgID = $qid; my ($ok, $msg, $code, $dsn, $delay) = filter_relay($hostip, $hostname, $port, $myip, $myport, $qid); send_filter_answer($ok, $msg, "filter_relay", "host $hostip ($hostname)", $code, $dsn, $delay); } #*********************************************************************** # %PROCEDURE: handle_helook # %ARGUMENTS: # ip -- IP address of relay host # name -- name of relay host # helo -- arg to SMTP HELO command # port -- client port # myip -- my IP address # myport -- my listening port # %RETURNS: # Nothing, but prints "ok 1" if we accept connections from this host. # "ok 0" if not. #*********************************************************************** sub handle_helook { my ($ip, $name, $helo, $port, $myip, $myport, $qid) = @_; if(!defined(&filter_helo)) { send_filter_answer('CONTINUE', "ok", "filter_helo", "helo $helo"); return; } # Set up globals $RelayAddr = $ip; $RelayHostname = $name; $Helo = $helo; $QueueID = $qid; $MsgID = $qid; my ($ok, $msg, $code, $dsn, $delay) = filter_helo($ip, $name, $helo, $port, $myip, $myport, $qid); send_filter_answer($ok, $msg, "filter_helo", "helo $helo", $code, $dsn, $delay); } #*********************************************************************** # %PROCEDURE: handle_senderok # %ARGUMENTS: # sender -- e-mail address of sender # ip -- IP address of relay host # name -- name of relay host # helo -- arg to SMTP HELO command # %RETURNS: # Nothing, but prints "ok 1" if we accept message from this sender, # "ok 0" if not. #*********************************************************************** sub handle_senderok { my ($sender, $ip, $name, $helo); ($sender, $ip, $name, $helo, $CWD, $QueueID, @ESMTPArgs) = @_; if(!defined(&filter_sender)) { send_filter_answer('CONTINUE', "ok", "filter_sender", "sender $sender"); return; } if (!chdir($CWD)) { send_filter_answer('TEMPFAIL', "could not chdir($CWD): $!", "filter_sender", "sender $sender"); } # Set up additional globals $MsgID = $QueueID; $Sender = $sender; $RelayAddr = $ip; $RelayHostname = $name; $Helo = $helo; my ($ok, $msg, $code, $dsn, $delay) = filter_sender($sender, $ip, $name, $helo); send_filter_answer($ok, $msg, "filter_sender", "sender $sender", $code, $dsn, $delay); chdir($Features{'Path:SPOOLDIR'}); } #*********************************************************************** # %PROCEDURE: handle_recipok # %ARGUMENTS: # recipient -- e-mail address of recipient # sender -- e-mail address of sender # ip -- IP address of relay host # name -- name of relay host # firstRecip -- first recipient of message # helo -- arg to SMTP HELO command # %RETURNS: # Nothing, but prints "ok 1" if we accept message to this recipient, # "ok 0" if not. #*********************************************************************** sub handle_recipok { my ($recipient, $sender, $ip, $name, $firstRecip, $helo, $rcpt_mailer, $rcpt_host, $rcpt_addr); ($recipient, $sender, $ip, $name, $firstRecip, $helo, $CWD, $QueueID, $rcpt_mailer, $rcpt_host, $rcpt_addr, @ESMTPArgs) = @_; $MsgID = $QueueID; if(!defined(&filter_recipient)) { send_filter_answer('CONTINUE', "ok", "filter_recipient", "recipient $recipient"); return; } if (!chdir($CWD)) { send_filter_answer('TEMPFAIL', "could not chdir($CWD): $!", "filter_recipient", "recipient $recipient"); } # Set up additional globals @Recipients = ($recipient); $Sender = $sender; $RelayAddr = $ip; $RelayHostname = $name; $Helo = $helo; $RecipientMailers{$recipient} = [ $rcpt_mailer, $rcpt_host, $rcpt_addr ]; my ($ok, $msg, $code, $dsn, $delay) = filter_recipient($recipient, $sender, $ip, $name, $firstRecip, $helo, $rcpt_mailer, $rcpt_host, $rcpt_addr); send_filter_answer($ok, $msg, "filter_recipient", "recipient $recipient", $code, $dsn, $delay); chdir($Features{'Path:SPOOLDIR'}); } #*********************************************************************** # %PROCEDURE: do_scan # %ARGUMENTS: # workdir -- working directory to scan # %RETURNS: # 0 if parse went well; non-zero otherwise. # %DESCRIPTION: # Scan a message in working directory. #*********************************************************************** sub do_scan { my($workdir) = @_; if (!chdir($workdir)) { fatal("Cannot chdir($workdir): $!"); return -1; } $CWD = $workdir; # Read command file push_status_tag("Reading COMMANDS"); read_commands_file('need_F') or return -1; pop_status_tag(); # We're processing a message $InMessageContext = 1; # Set message ID if ($QueueID ne "") { $MsgID = $QueueID; } elsif ($MessageID ne "") { $MsgID = $MessageID; } else { $MsgID = "NOQUEUE"; } if ($QueueID eq "") { $QueueID = "NOQUEUE"; } if ($MessageID eq "") { $MessageID = "NOQUEUE"; } my($file) = "INPUTMSG"; # Create a subdirectory for storing all the actual message data my($msgdir) = "Work"; if (!mkdir($msgdir, 0750)) { fatal("Cannot mkdir($msgdir): $!"); return -1; } my $entity; my $parser; if (defined(&filter_create_parser)) { $parser = filter_create_parser(); if (!defined($parser) || !$parser->isa('MIME::Parser')) { $parser = builtin_create_parser(); } } else { $parser = builtin_create_parser(); } my $filer = MIME::Parser::FileInto->new($msgdir); # Don't trust any filenames from the message. $filer->ignore_filename(1); $parser->filer($filer); # Parse the input stream: if (not -r $file) { fatal("couldn't open $file: $!"); signal_complete(); return -1; } if ($MaxMIMEParts > 0) { $parser->max_parts($MaxMIMEParts); } push_status_tag("Parsing Message"); $entity = $parser->parse_open($file); pop_status_tag(); if (!defined($entity) && $MaxMIMEParts > 0) { # Message is too complex; bounce it action_bounce("Message contained too many MIME parts. We do not accept such complicated messages."); signal_unchanged(); signal_complete(); return; } if (!$entity) { fatal("Couldn't parse MIME in $file: $!"); signal_complete(); return -1; } # Make entity multipart my ($code); $code = $entity->make_multipart(); $WasMultiPart = ($code eq 'ALREADY'); # If there are multiple Subject: lines, delete all but the first if ($SubjectCount > 1) { md_syslog('warning', "Message contains $SubjectCount Subject: headers. Deleting all but the first"); for (my $i=$SubjectCount; $i > 1; $i--) { action_delete_header("Subject", $i); } } # Call pre-scan filter if defined if (defined(&filter_begin)) { push_status_tag("In filter_begin"); filter_begin($entity); pop_status_tag(); # If stream_by_domain tells us to discard, do so... if ($TerminateAndDiscard) { write_result_line("D", ""); signal_unchanged(); md_syslog('debug', "filter_begin set TerminateAndDiscard flag. Don't panic; it's most likely a message being streamed."); signal_complete(); return; } } # Now rebuild the message! my($boundary); my($rebuilt); my($rebuilt_flat); # Prepare rebuilt container. # We don't want a deep copy here, so do some trickery... my @parts; # Save parts @parts = $entity->parts; # Clear them out prior to deep copy $entity->parts([]); # "Deep" copy (ha ha...) $rebuilt = $entity->dup; # And restore parts to original $entity->parts(\@parts); # Rebuild $InFilterContext = 1; push_status_tag("In rebuild loop"); map { rebuild_entity($rebuilt, $_) } $entity->parts; pop_status_tag(); if ($#Warnings >= 0) { my $didSomething = 0; my $html_warning; $Changed = 1; if ($AddWarningsInline) { my $warning = $GeneralWarning . join("\n", @Warnings); my $ruler = "=" x 75; $html_warning = $warning; $html_warning =~ s/&/&/g; $html_warning =~ s//>/g; $didSomething = 1 if append_text_boilerplate($rebuilt, "$ruler\n$warning", 0); $didSomething = 1 if append_html_boilerplate($rebuilt, "
\n
\n$html_warning
", 0); } if (!$didSomething) { # HACK for Micro$oft "LookOut!" if ($WasMultiPart && $Stupidity{"NoMultipleInlines"} && $WarningLocation == 0) { # Descend into first leaf my($msg) = $rebuilt; my(@parts) = $msg->parts; while($#parts >= 0) { $msg = $parts[0]; @parts = $msg->parts; } my($head) = $msg->head; my($type) = $msg->mime_type; if (lc($head->mime_type) eq "text/plain") { $head->mime_attr("Content-Type.name" => "MESSAGE.TXT"); $head->mime_attr("Content-Disposition" => "inline"); $head->mime_attr("Content-Disposition.filename" => "MESSAGE.TXT"); $head->mime_attr("Content-Description" => "MESSAGE.TXT"); } } my $warns = $GeneralWarning . join("\n", @Warnings); $WarningCounter++; action_add_part($rebuilt, "text/plain", "-suggest", $warns, "warning$WarningCounter.txt", "inline", $WarningLocation); } } $InFilterContext = 0; # Call post-scan filter if defined if (defined(&filter_end)) { $InFilterEnd = 1; push_status_tag("In filter_end"); filter_end($rebuilt); pop_status_tag(); $InFilterEnd = 0; } if ($Rebuild && defined($FilterEndReplacementEntity)) { $rebuilt = $FilterEndReplacementEntity; undef $FilterEndReplacementEntity; } if ($Changed || $Rebuild) { my $fh = IO::File->new("NEWBODY", '>:'); if (not $fh) { fatal("Can't open NEWBODY: $!"); signal_complete(); return -1; } # Add any parts inserted by action_add_part $rebuilt = process_added_parts($rebuilt); # Trim out useless multiparts. FIXME: Make this optional? while ((lc($rebuilt->head->mime_type) eq "multipart/mixed" || lc($rebuilt->head->mime_type) eq "multipart/alternative") && $rebuilt->parts == 1 && defined($rebuilt->parts(0))) { $rebuilt->make_singlepart(); } push_status_tag("Writing new body"); $rebuilt->print_body($fh); pop_status_tag(); $fh->close; # Write new content-type header in case we've changed the type. my $ct = $rebuilt->head->get('Content-Type'); if (!defined($ct)) { my $type; $type = $rebuilt->mime_type; $boundary = $rebuilt->head->multipart_boundary; if (defined($boundary)) { $ct = "$type; boundary=\"$boundary\""; } else { $ct = "$type"; } } if (defined($ct)) { chomp($ct); write_result_line("M", $ct); } # Write out all the other MIME headers associated with the rebuilt # entity. my($hdr); foreach my $tag (grep {/^content-/i} $rebuilt->head->tags) { # Already done content-type next if ($tag =~ /^content-type$/i); if ($tag =~ /^content-transfer-encoding$/i) { # If it is now multipart, but wasn't before, we will # delete any content-transfer-encoding header. if ($rebuilt->head->mime_type =~ m+^multipart/+i && !$WasMultiPart) { next; } } $hdr = $rebuilt->head->get($tag); if (defined($hdr) && $hdr ne "") { chomp($hdr); action_change_header($tag, $hdr); } } # If it is now multipart, but wasn't before, delete # content-transfer-encoding header. if ($rebuilt->head->mime_type =~ m+^multipart/+i && !$WasMultiPart) { action_delete_header("Content-Transfer-Encoding"); } signal_changed(); } else { signal_unchanged(); } # Call filter_wrapup if defined if (defined(&filter_wrapup)) { $InFilterWrapUp = 1; push_status_tag("In filter_wrapup"); filter_wrapup($rebuilt); pop_status_tag(); $InFilterWrapUp = 0; } signal_complete(); return 0; } #*********************************************************************** # %PROCEDURE: replace_entire_message # %ARGUMENTS: # e -- a MIME::Entity # %RETURNS: # 1 on success; 0 on failure. # %DESCRIPTION: # Replaces entire message with $e # %PRECONDITIONS: # Can only be called from filter_end #*********************************************************************** sub replace_entire_message { my($e) = @_; return 0 unless in_filter_end("replace_entire_message"); if (!defined($e)) { md_syslog('err', "Call to replace_entire_message with undefined argument"); return 0; } if (ref($e) ne "MIME::Entity") { md_syslog('err', "Call to replace_entire_message with argument that is not of type MIME::Entity"); return 0; } $FilterEndReplacementEntity = $e; $Rebuild = 1; return 1; } #*********************************************************************** # %PROCEDURE: send_filter_answer # %ARGUMENTS: # ok -- 1 = accept, 0 = reject, -1 = tmpfail # msg -- if non-blank, additional message # who -- one of "filter_sender", "filter_relay" or "filter_recipient" # what -- the address or host being adjusted # code -- SMTP reply code # dsn -- DSN code # delay -- number of seconds C code should delay before returning # %RETURNS: # Nothing # %DESCRIPTION: # Sends an answer back for filter_relay, filter_sender and filter_recipient #*********************************************************************** sub send_filter_answer { my($ok, $msg, $who, $what, $code, $dsn, $delay) = @_; my($num_ok); $num_ok = 0; # Did we get an integer? $delay = 0 unless (defined($delay) and $delay =~ /^\d+$/); if ($ok =~ /^-?\d+$/) { $num_ok = $ok; } $msg = "?" if (!defined($msg) or ($msg eq "")); if ($ok eq 'ACCEPT_AND_NO_MORE_FILTERING') { md_syslog('debug', "$who said ACCEPT_AND_NO_MORE_FILTERING: No further filtering for this message"); $code = 250 unless (defined($code) and $code =~ /^2\d\d$/); $dsn = "2.1.0" unless (defined($dsn) and $dsn =~ /^2\.\d{1,3}\.\d{1,3}$/); $msg = percent_encode($msg); $code = percent_encode($code); $dsn = percent_encode($dsn); print_and_flush("ok 2 $msg $code $dsn $delay"); } elsif ($ok eq 'DISCARD') { $code = 250 unless (defined($code) and $code =~ /^2\d\d$/); $dsn = "2.1.0" unless (defined($dsn) and $dsn =~ /^2\.\d{1,3}\.\d{1,3}$/); $msg = percent_encode($msg); $code = percent_encode($code); $dsn = percent_encode($dsn); md_syslog('info', "$who said DISCARD: Discarding this message"); print_and_flush("ok 3 $msg $code $dsn $delay"); } elsif (($ok eq 'CONTINUE') or ($num_ok > 0)) { $code = 250 unless (defined($code) and $code =~ /^2\d\d$/); $dsn = "2.1.0" unless (defined($dsn) and $dsn =~ /^2\.\d{1,3}\.\d{1,3}$/); $msg = percent_encode($msg); $code = percent_encode($code); $dsn = percent_encode($dsn); print_and_flush("ok 1 $msg $code $dsn $delay"); } elsif (($ok eq 'TEMPFAIL') or ($num_ok < 0)) { md_syslog('debug', "$who tempfailed $what"); $code = 451 unless (defined($code) and $code =~ /^4\d\d$/); $dsn = "4.3.0" unless (defined($dsn) and $dsn =~ /^4\.\d{1,3}\.\d{1,3}$/); $msg = percent_encode($msg); $code = percent_encode($code); $dsn = percent_encode($dsn); print_and_flush("ok -1 $msg $code $dsn $delay"); } else { $code = 554 unless (defined($code) and $code =~ /^5\d\d$/); $dsn = "5.7.1" unless (defined($dsn) and $dsn =~ /^5\.\d{1,3}\.\d{1,3}$/); md_syslog('debug', "$who rejected $what"); $msg = percent_encode($msg); $code = percent_encode($code); $dsn = percent_encode($dsn); print_and_flush("ok 0 $msg $code $dsn $delay"); } } #*********************************************************************** # %PROCEDURE: md_graphdefang_log_enable # %ARGUMENTS: # SyslogFacility -- (optional) The Syslog facility to which mimedefang # should log messages when md_graphdefang_log() is called. If # this variable is not passed in, a default value # of 'mail' will be used. # EnumerateRecipients -- (optional) Whether or not to output a syslog # line for each recipient of a spam message or only # once per incoming message. Disabling this will # reduce the entries to syslog but will reduce # statistical granularity on a per user basis. # # %RETURNS: # Nothing # %DESCRIPTION: # This is called to enable Mimedefang logging when the md_graphdefang_log() # subroutine is called. The $SyslogFacility name should be known # to syslog on the machine on which Mimedefang is running. #*********************************************************************** sub md_graphdefang_log_enable { $GraphDefangSyslogFacility = shift; $EnumerateRecipients = shift; # If we don't have a SyslogFacility from the user, # use the system default $GraphDefangSyslogFacility = $SyslogFacility unless defined($GraphDefangSyslogFacility); # By default, we want md_graphdefang_log to output a syslog line for each # recipient. This is useful for per user spam statistics. # i.e. How many spam messages were received by foo@bar.com? $EnumerateRecipients = 1 unless defined($EnumerateRecipients); } #*********************************************************************** # %PROCEDURE: add_ip_validation_header # %ARGUMENTS: # None # %RETURNS: # 1 if header was added; 0 otherwise # %DESCRIPTION: # Adds an IP address validation header to preserve relay info. #*********************************************************************** sub add_ip_validation_header { if ($ValidateIPHeader eq "") { md_syslog('warning', 'add_ip_validation_header called, but no validation header available. Check permissions on @CONFDIR_EVAL@/mimedefang-ip-key'); return 0; } action_add_header($ValidateIPHeader, $RelayAddr); return 1; } #*********************************************************************** # %PROCEDURE: delete_ip_validation_header # %ARGUMENTS: # None # %RETURNS: # 1 if header was deleted; 0 otherwise # %DESCRIPTION: # Deletes IP address validation header. #*********************************************************************** sub delete_ip_validation_header { if ($ValidateIPHeader eq "") { md_syslog('warning', 'delete_ip_validation_header called, but no validation header available. Check permissions on @CONFDIR_EVAL@/mimedefang-ip-key'); return 0; } action_delete_all_headers($ValidateIPHeader); return 1; } =over 4 =item read_config(file_path) Loads a config file where global variables can be stored. =back =cut #*********************************************************************** # %PROCEDURE: read_config # %ARGUMENTS: # configuration file path # %RETURNS: # return 1 if configuration file cannot be loaded; 0 otherwise # %DESCRIPTION: # loads a configuration file to overwrite global variables values #*********************************************************************** # Derivative work from amavisd-new read_config_file($$) # Copyright (C) 2002-2018 Mark Martinec sub read_config { my($config_file) = @_; $config_file = File::Spec->rel2abs($config_file); my(@stat_list) = stat($config_file); # symlinks-friendly my $errn = @stat_list ? 0 : 0+$!; my $owner_uid = $stat_list[4]; my $msg; if ($errn == ENOENT) { $msg = "does not exist" } elsif ($errn) { $msg = "is inaccessible: $!" } elsif (-d _) { $msg = "is a directory" } elsif (-S _ || -b _ || -c _) { $msg = "is not a regular file or pipe" } elsif ($owner_uid) { $msg = "should be owned by root (uid 0)" } if (defined $msg) { md_syslog("crit", "Config file \"$config_file\" $msg"); return 1; } if (defined(do $config_file)) {} return 0; } =over 4 =item rebuild_entity Method that descends through input entity and rebuilds an output entity. The various parts of the input entity may be modified (or even deleted). =back =cut #*********************************************************************** # %PROCEDURE: rebuild_entity # %ARGUMENTS: # out -- output entity to hold rebuilt message # in -- input message # %RETURNS: # Nothing useful # %DESCRIPTION: # Descends through input entity and rebuilds an output entity. The # various parts of the input entity may be modified (or even deleted) #*********************************************************************** sub rebuild_entity { my($out, $in) = @_; my @parts = $in->parts; my($type) = $in->mime_type; $type =~ tr/A-Z/a-z/; my($body) = $in->bodyhandle; my($fname) = takeStabAtFilename($in); $fname = "" unless defined($fname); my $extension = ""; $extension = $1 if $fname =~ /(\.[^.]*)$/; # If no Content-Type: header, add one if (!$in->head->mime_attr('content-type')) { $in->head->mime_attr('Content-Type', $type); } if (!defined($body)) { $Action = "accept"; if (defined(&filter_multipart)) { push_status_tag("In filter_multipart routine"); filter_multipart($in, $fname, $extension, $type); pop_status_tag(); } if ($Action eq "drop") { $Changed = 1; return 0; } if ($Action eq "replace") { $Changed = 1; $out->add_part($ReplacementEntity); return 0; } my($subentity); $subentity = $in->dup; $subentity->parts([]); $out->add_part($subentity); map { rebuild_entity($subentity, $_) } @parts; } else { # This is where we call out to the user filter. Get some useful # info to pass to the filter # Default action is to accept the part $Action = "accept"; if (defined(&filter)) { push_status_tag("In filter routine"); filter($in, $fname, $extension, $type); pop_status_tag(); } # If action is "drop", just drop it silently; if ($Action eq "drop") { $Changed = 1; return 0; } # If action is "replace", replace it with $ReplacementEntity; if ($Action eq "replace") { $Changed = 1; $out->add_part($ReplacementEntity); return 0; } # Otherwise, accept it $out->add_part($in); } } exit(&main) unless caller; #------------------------------------------------------------ 1; mimedefang-3.6/modules/000077500000000000000000000000001475763067200151475ustar00rootroot00000000000000mimedefang-3.6/modules/lib/000077500000000000000000000000001475763067200157155ustar00rootroot00000000000000mimedefang-3.6/modules/lib/Mail/000077500000000000000000000000001475763067200165775ustar00rootroot00000000000000mimedefang-3.6/modules/lib/Mail/MIMEDefang.pm000066400000000000000000001167611475763067200210050ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang - email filtering milter =head1 DESCRIPTION Mail::MIMEDefang is a framework for filtering e-mail. It uses Sendmail's "Milter" API, some C glue code, and some Perl code to let you write high-performance mail filters in Perl. People use MIMEDefang to: Block viruses Block or tag spam Remove HTML mail parts Add boilerplate disclaimers to outgoing mail Remove or alter attachments Replace attachments with URL's Implement sophisticated access controls. You're limited only by your imagination. If you can think of it and code it in Perl, you can do it with MIMEDefang. =head1 METHODS =over 4 =cut package Mail::MIMEDefang; ## no critic (TestingAndDebugging::RequireUseStrict) use warnings; no warnings qw(once uninitialized); require Exporter; use Carp; use Errno qw(ENOENT EACCES); use File::Spec; use IO::File; use MIME::Entity; use MIME::WordDecoder; use Socket; our @ISA = qw(Exporter); our @EXPORT; our @EXPORT_OK; our $VERSION = '3.6'; @EXPORT = qw{ $AddWarningsInline @StatusTags $Action $Administrator $AdminName $AdminAddress $DoStatusTags $Changed $CSSHost $DaemonAddress $DaemonName $DefangCounter $Domain $EntireMessageQuarantined $MessageID $Rebuild $QuarantineCount $QuarantineSubdir $QueueID $MsgID $MIMEDefangID $RelayAddr $WasResent $RelayHostname $RealRelayAddr $RealRelayHostname $ReplacementEntity $Sender $ServerMode $Subject $SubjectCount $ClamdSock $SophieSock $TrophieSock $Helo @ESMTPArgs @SenderESMTPArgs %RecipientESMTPArgs $TerminateAndDiscard $URL $VirusName $CurrentVirusScannerMessage @AddedParts $VirusScannerMessages $WarningLocation $WasMultiPart $CWD $FprotdHost $Fprotd6Host $NotifySenderSubject $NotifyAdministratorSubject $ValidateIPHeader $QuarantineSubject $SALocalTestsOnly $NotifyNoPreamble %Actions %Stupidity @FlatParts @Recipients @Warnings %Features $SyslogFacility $GraphDefangSyslogFacility $MaxMIMEParts $InMessageContext $InFilterContext $PrivateMyHostName $EnumerateRecipients $InFilterEnd $FilterEndReplacementEntity $AddApparentlyToForSpamAssassin $WarningCounter @VirusScannerMessageRoutines @VirusScannerEntityRoutines $VirusScannerRoutinesInitialized %SendmailMacros %RecipientMailers $CachedTimezone $InFilterWrapUp $SuspiciousCharsInHeaders $SuspiciousCharsInBody $GeneralWarning $HTMLFoundEndBody $HTMLBoilerplate $SASpamTester $results_fh init_globals print_and_flush detect_and_load_perl_modules init_status_tag push_status_tag pop_status_tag signal_changed signal_unchanged md_syslog md_graphdefang_log write_result_line in_message_context in_filter_context in_filter_wrapup in_filter_end percent_decode percent_encode percent_encode_for_graphdefang send_mail send_multipart_mail send_quarantine_notifications signal_complete send_admin_mail md_version set_status_tag read_commands_file }; @EXPORT_OK = qw{ detect_antivirus_support }; #*********************************************************************** # %PROCEDURE: md_version # %ARGUMENTS: # None # %RETURNS: # MIMEDefang version #*********************************************************************** sub md_version { return $VERSION; } =item init_globals Initialize global variables used across MIMEDefang instance and filter. =cut sub init_globals { my ($self, @params) = @_; $CWD = $Features{'Path:SPOOLDIR'}; $InMessageContext = 0; $InFilterEnd = 0; $InFilterContext = 0; $InFilterWrapUp = 0; undef $FilterEndReplacementEntity; $Action = ""; $Changed = 0; $DefangCounter = 0; $Domain = ""; $MIMEDefangID = ""; $MsgID = "NOQUEUE"; $MessageID = "NOQUEUE"; $Helo = ""; $QueueID = "NOQUEUE"; $QuarantineCount = 0; $Rebuild = 0; $EntireMessageQuarantined = 0; $QuarantineSubdir = ""; $RelayAddr = ""; $RealRelayAddr = ""; $WasResent = 0; $RelayHostname = ""; $RealRelayHostname = ""; $Sender = ""; $Subject = ""; $SubjectCount = 0; $SuspiciousCharsInHeaders = 0; $SuspiciousCharsInBody = 0; $TerminateAndDiscard = 0; $VirusScannerMessages = ""; $VirusName = ""; $WasMultiPart = 0; $WarningCounter = 0; undef %Actions; undef %SendmailMacros; undef %RecipientMailers; undef %RecipientESMTPArgs; undef @FlatParts; undef @Recipients; undef @Warnings; undef @AddedParts; undef @StatusTags; undef @ESMTPArgs; undef @SenderESMTPArgs; undef $results_fh; } =item print_and_flush(text) Prints to stdout and flush buffer. =cut sub print_and_flush { local $| = 1; print($_[0], "\n"); } =item md_openlog(tag, facility) Initialize e syslog object using Sys::Syslog or Unix::Syslog as appropriate. =item md_syslog(facility, msg) Prints a message to syslog(3) using the specified facility =cut { # Reworked detection/usage of Sys::Syslog or Unix::Syslog as # appropriate is mostly borrowed from Log::Syslog::Abstract, to which # I'd love to convert at some point. my $_syslogsub = undef; my $_openlogsub = undef; my $_fac_map = undef; #*********************************************************************** # %PROCEDURE: md_openlog # %ARGUMENTS: # tag -- syslog tag ("mimedefang.pl") # facility -- Syslog facility as a string # %RETURNS: # Nothing # %DESCRIPTION: # Opens a log using either Unix::Syslog or Sys::Syslog #*********************************************************************** sub md_openlog { my ($tag, $facility) = @_; local $@; if( ! defined $_openlogsub ) { # Try Unix::Syslog first, then Sys::Syslog eval { require Unix::Syslog; }; if(!$@) { Unix::Syslog->import(qw( :macros )); ($_openlogsub, $_syslogsub) = _wrap_for_unix_syslog(); } else { eval { require Sys::Syslog; }; if(!$@) { Sys::Syslog->import(); ($_openlogsub, $_syslogsub) = _wrap_for_sys_syslog(); } else { croak q{Unable to detect either Unix::Syslog or Sys::Syslog}; } } } return $_openlogsub->($tag, 'pid,ndelay', $facility); } #*********************************************************************** # %PROCEDURE: md_syslog # %ARGUMENTS: # facility -- Syslog facility as a string # msg -- message to log # %RETURNS: # Nothing # %DESCRIPTION: # Calls syslog, either in Sys::Syslog or Unix::Syslog package #*********************************************************************** sub md_syslog { my ($facility, $msg) = @_; if(!$_syslogsub) { md_openlog('mimedefang.pl', $SyslogFacility); } if (defined $MsgID && $MsgID ne 'NOQUEUE') { return $_syslogsub->($facility, '%s', $MsgID . ': ' . $msg); } else { return $_syslogsub->($facility, '%s', $msg); } } sub _wrap_for_unix_syslog { my $openlog = sub { my ($id, $flags, $facility) = @_; croak q{first argument must be an identifier string} unless defined $id; croak q{second argument must be flag string} unless defined $flags; croak q{third argument must be a facility string} unless defined $facility; return Unix::Syslog::openlog( $id, _convert_flags( $flags ), _convert_facility( $facility ) ); }; my $syslog = sub { my $facility = shift; return Unix::Syslog::syslog( _convert_facility( $facility ), @_); }; return ($openlog, $syslog); } sub _wrap_for_sys_syslog { my $openlog = sub { # Debian Stretch version is 0.33_01...dammit! my $ver = $Sys::Syslog::VERSION; $ver =~ s/_.*//; if( $ver < 0.16 ) { # Older Sys::Syslog versions still need # setlogsock(). RHEL5 still ships with 0.13 :( Sys::Syslog::setlogsock([ 'unix', 'tcp', 'udp' ]); } return Sys::Syslog::openlog(@_); }; my $syslog = sub { return Sys::Syslog::syslog(@_); }; return ($openlog, $syslog); } sub _convert_flags { my($flags) = @_; my $flag_map = { pid => Unix::Syslog::LOG_PID(), ndelay => Unix::Syslog::LOG_NDELAY(), }; my $num = 0; foreach my $thing (split(/,/, $flags)) { next unless exists $flag_map->{$thing}; $num |= $flag_map->{$thing}; } return $num; } sub _convert_facility { my($facility) = @_; my $num = 0; foreach my $thing (split(/\|/, $facility)) { if (!defined($_fac_map) || !exists($_fac_map->{$thing})) { $_fac_map->{$thing} = _fac_to_num($thing); } next unless defined $_fac_map->{$thing}; $num |= $_fac_map->{$thing}; } return $num; } my %special = ( error => 'err', panic => 'emerg', ); # Some of the Unix::Syslog 'macros' tag exports aren't # constants, so we need to ignore them if found. my %blacklisted = map { $_ => 1 } qw(mask upto pri makepri fac); sub _fac_to_num { my ($thing) = @_; local $@; return if exists $blacklisted{$thing}; $thing = $special{$thing} if exists $special{$thing}; $thing = 'LOG_' . uc($thing); return unless grep { $_ eq $thing } @ {$Unix::Syslog::EXPORT_TAGS{macros} }; return eval "Unix::Syslog::$thing()"; ## no critic } } =item md_graphdefang_log This is called to log events that occur during mimedefang processing. It should be called from mimedefang-filter with appropriate event names and values. Possible examples: C C C If you need to log UTF-8 strings you can call the sub as: C =cut #*********************************************************************** # %PROCEDURE: md_graphdefang_log # %ARGUMENTS: # event -- The name of the event that is being logged. Examples # include virus, spam, mail, etc. # value1 -- (optional) A value associated with the event being logged. # value2 -- (optional) A value associated with the event being logged. # utf8_decode -- (optional) A boolean value that indicates if we want to # decode UTF-8 encoded strings # %RETURNS: # Nothing # %DESCRIPTION: # This is called to log events that occur during mimedefang processing. # It should be called from mimedefang-filter with appropriate # event names and values. Possible examples: # md_graphdefang_log('virus',$VirusName,$filename); # md_graphdefang_log('spam',$hits); # md_graphdefang_log('spam',$hits, undef, 1); # md_graphdefang_log('bad_filename',$filename,$extension); #*********************************************************************** sub md_graphdefang_log { return unless defined($GraphDefangSyslogFacility); return if (!in_message_context("md_graphdefang_log")); my $event = shift; my $value1 = shift; my $value2 = shift; my $utf8_decode = shift; $value1 = "" unless defined($value1); $value2 = "" unless defined($value2); $utf8_decode = 0 unless defined($utf8_decode); my $lcsender = percent_encode_for_graphdefang(lc($Sender)); # Make values safe for graphdefang my $id = percent_encode_for_graphdefang($MsgID); my $subj; if($utf8_decode eq 1) { eval { no warnings 'utf8'; $subj = mime_to_perl_string($Subject); $event = mime_to_perl_string($event); $value1 = mime_to_perl_string($value1); $value2 = mime_to_perl_string($value2); $subj =~ s/\P{Print}//g; $event =~ s/\P{Print}//g; $value1 =~ s/\P{Print}//g; $value2 =~ s/\P{Print}//g; if (utf8::is_utf8($subj)) { utf8::encode($subj); } if (utf8::is_utf8($event)) { utf8::encode($event); } if (utf8::is_utf8($value1)) { utf8::encode($value1); } if (utf8::is_utf8($value2)) { utf8::encode($value2); } }; } else { $subj = percent_encode_for_graphdefang($Subject); $event = percent_encode_for_graphdefang($event); $value1 = percent_encode_for_graphdefang($value1); $value2 = percent_encode_for_graphdefang($value2); } if ($EnumerateRecipients || scalar(@Recipients) == 1) { foreach my $recipient (@Recipients) { my $lcrecipient = percent_encode_for_graphdefang(lc($recipient)); md_syslog("$GraphDefangSyslogFacility|info","MDLOG,$id," . "$event,$value1,$value2,$lcsender," . "$lcrecipient,$subj"); } } else { my $lcrecipient = "rcpts=" . scalar(@Recipients); $lcrecipient = percent_encode_for_graphdefang($lcrecipient); md_syslog("$GraphDefangSyslogFacility|info","MDLOG,$id," . "$event,$value1,$value2,$lcsender," . "$lcrecipient,$subj"); } } =item detect_and_load_perl_modules Automatically detect and load Perl modules needed for some features like SpamAssassin, rbl checks, zip file listing and HTML parsing. =cut # Detect these Perl modules at run-time. Can explicitly prevent # loading of these modules by setting $Features{"xxx"} = 0; # # You can turn off ALL auto-detection by setting # $Features{"AutoDetectPerlModules"} = 0; sub detect_and_load_perl_modules() { my ($use_sa, $use_html, $use_zip, $use_dns); local $@; if (!defined($Features{"AutoDetectPerlModules"}) or $Features{"AutoDetectPerlModules"}) { if (!defined($Features{"SpamAssassin"}) or ($Features{"SpamAssassin"} eq 1)) { eval { require Mail::SpamAssassin; }; if($@) { $use_sa = 0; } else { Mail::SpamAssassin->import(); $use_sa = 1; } } $Features{"SpamAssassin"} = $use_sa; if (!defined($Features{"HTML::Parser"}) or ($Features{"HTML::Parser"} eq 1)) { eval { require HTML::Parser; }; if($@) { $use_html = 0; } else { HTML::Parser->import(); $use_html = 1; } } $Features{"HTML::Parser"} = $use_html; if (!defined($Features{"Archive::Zip"}) or ($Features{"Archive::Zip"} eq 1)) { eval { require Archive::Zip; }; if($@) { $use_zip = 0; } else { Archive::Zip->import(); $use_zip = 1; } } $Features{"Archive::Zip"} = $use_zip; if (!defined($Features{"Net::DNS"}) or ($Features{"Net::DNS"} eq 1)) { eval { require Net::DNS; }; if($@) { $use_dns = 0; } else { Net::DNS->import(); $use_dns = 1; } } $Features{"Net::DNS"} = $use_dns; if(exists &Mail::MIMEDefang::Actions::md_init) { Mail::MIMEDefang::Actions::md_init(); } if(exists &Mail::MIMEDefang::Antispam::md_init) { Mail::MIMEDefang::Antispam::md_init(); } if(exists &Mail::MIMEDefang::Antivirus::md_init) { Mail::MIMEDefang::Antivirus::md_init(); } if(exists &Mail::MIMEDefang::Mail::md_init) { Mail::MIMEDefang::Mail::md_init(); } if(exists &Mail::MIMEDefang::MIME::md_init) { Mail::MIMEDefang::MIME::md_init(); } if(exists &Mail::MIMEDefang::Net::md_init) { Mail::MIMEDefang::Net::md_init(); } if(exists &Mail::MIMEDefang::RFC2822::md_init) { Mail::MIMEDefang::RFC2822::md_init(); } if(exists &Mail::MIMEDefang::Utils::md_init) { Mail::MIMEDefang::Utils::md_init(); } } } =item detect_antivirus_support Check if antivirus support should be loaded by looking at %Features =cut # Detect if antivirus support should be enabled sub detect_antivirus_support() { return 1 if (!defined $Features{"AutoDetectPerlModules"}); foreach my $k ( keys %Features ) { if($k =~ /^Virus\:/) { if($Features{$k} ne 0) { return 1; } } } return 0; } =item init_status_tag Open the status file descriptor =cut # Try to open the status descriptor sub init_status_tag { return unless $DoStatusTags; if(open(STATUS_HANDLE, ">&=", 3)) { ## no critic STATUS_HANDLE->autoflush(1); } else { $DoStatusTags = 0; } } =item set_status_tag(depth, tag) Sets the status tag for this worker inside the multiplexor. =cut #*********************************************************************** # %PROCEDURE: set_status_tag # %ARGUMENTS: # nest_depth -- nesting depth # tag -- status tag # %DESCRIPTION: # Sets the status tag for this worker inside the multiplexor. # %RETURNS: # Nothing #*********************************************************************** sub set_status_tag { return unless $DoStatusTags; my ($depth, $tag) = @_; $tag ||= ''; if($tag eq '') { print STATUS_HANDLE "\n"; return; } $tag =~ s/[^[:graph:]]/ /g; if(defined($MsgID) and ($MsgID ne "NOQUEUE")) { print STATUS_HANDLE percent_encode("$depth: $tag $MsgID") . "\n"; } else { print STATUS_HANDLE percent_encode("$depth: $tag") . "\n"; } } =item push_status_tag(tag) Updates status tag inside multiplexor and pushes onto stack. =cut #*********************************************************************** # %PROCEDURE: push_status_tag # %ARGUMENTS: # tag -- tag describing current status # %DESCRIPTION: # Updates status tag inside multiplexor and pushes onto stack. # %RETURNS: # Nothing #*********************************************************************** sub push_status_tag { return unless $DoStatusTags; my ($tag) = @_; push(@StatusTags, $tag); if($tag ne '') { $tag = "> $tag"; } set_status_tag(scalar(@StatusTags), $tag); } =item pop_status_tag Pops previous status of stack and sets tag in multiplexor. =cut #*********************************************************************** # %PROCEDURE: pop_status_tag # %ARGUMENTS: # None # %DESCRIPTION: # Pops previous status of stack and sets tag in multiplexor. # %RETURNS: # Nothing #*********************************************************************** sub pop_status_tag { return unless $DoStatusTags; pop @StatusTags; my $tag = $StatusTags[0] || 'no_tag'; set_status_tag(scalar(@StatusTags), "< $tag"); } =item percent_encode(str) Encode a string with unsafe chars as "%XY" where X and Y are hex digits. =cut #*********************************************************************** # %PROCEDURE: percent_encode # %ARGUMENTS: # str -- a string, possibly with newlines and control characters # %RETURNS: # A string with unsafe chars encoded as "%XY" where X and Y are hex # digits. For example: # "foo\r\nbar\tbl%t" ==> "foo%0D%0Abar%09bl%25t" #*********************************************************************** sub percent_encode { my($str) = @_; $str =~ s/([^\x21-\x7e]|[%\\'"])/sprintf("%%%02X", unpack("C", $1))/ge; #" Fix emacs highlighting... return $str; } =item percent_encode_for_graphdefang(str) Encode a string with unsafe chars as "%XY" where X and Y are hex digits. Quotes or spaces are not encoded but commas are encoded. =cut #*********************************************************************** # %PROCEDURE: percent_encode_for_graphdefang # %ARGUMENTS: # str -- a string, possibly with newlines and control characters # %RETURNS: # A string with unsafe chars encoded as "%XY" where X and Y are hex # digits. For example: # "foo\r\nbar\tbl%t" ==> "foo%0D%0Abar%09bl%25t" # This differs slightly from percent_encode because we don't encode # quotes or spaces, but we do encode commas. #*********************************************************************** sub percent_encode_for_graphdefang { my($str) = @_; $str =~ s/([^\x20-\x7e]|[%\\,])/sprintf("%%%02X", unpack("C", $1))/ge; #" Fix emacs highlighting... return $str; } =item percent_decode(str) Decode a string previously encoded by percent_encode(). =cut #*********************************************************************** # %PROCEDURE: percent_decode # %ARGUMENTS: # str -- a string encoded by percent_encode # %RETURNS: # The decoded string. For example: # "foo%0D%0Abar%09bl%25t" ==> "foo\r\nbar\tbl%t" #*********************************************************************** sub percent_decode { my($str) = @_; $str =~ s/%([0-9A-Fa-f]{2})/pack("C", hex($1))/ge; return $str; } =item write_result_line ( $cmd, @args ) Writes a result line to the RESULTS file. $cmd should be a one-letter command for the RESULTS file @args are the arguments for $cmd, if any. They will be percent_encode()'ed before being written to the file. Returns 0 or 1 and an optional warning message. =cut sub write_result_line { my $cmd = shift; # Do nothing if we don't yet have a dedicated working directory if ($CWD eq $Features{'Path:SPOOLDIR'}) { md_syslog('warning', "write_result_line called before working directory established"); return; } my $line = $cmd . join ' ', map { percent_encode($_) } @_; if (!$results_fh) { $results_fh = IO::File->new('>>RESULTS'); if (!$results_fh) { croak("Could not open RESULTS file: $!"); } } # We have a 16kb limit on the length of lines in RESULTS, including # trailing newline and null used in the milter. So, we limit $cmd + # $args to 16382 bytes. if( length $line > 16382 ) { md_syslog( 'warning', "Cannot write line over 16382 bytes long to RESULTS file; truncating. Original line began with: " . substr $line, 0, 40); $line = substr $line, 0, 16382; } print $results_fh "$line\n" or croak "Could not write RESULTS line: $!"; return; } =item signal_unchanged Tells mimedefang C program message has not been altered. =cut #*********************************************************************** # %PROCEDURE: signal_unchanged # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Tells mimedefang C program message has not been altered (does nothing...) #*********************************************************************** sub signal_unchanged { } =item signal_changed Tells mimedefang C program message has been altered. =cut #*********************************************************************** # %PROCEDURE: signal_changed # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Tells mimedefang C program message has been altered. #*********************************************************************** sub signal_changed { write_result_line("C", ""); } =item in_message_context(name) Returns 1 if we are processing a message; 0 otherwise. =cut #*********************************************************************** # %PROCEDURE: in_message_context # %ARGUMENTS: # name -- a string to syslog if we are not in a message context # %RETURNS: # 1 if we are processing a message; 0 otherwise. Returns 0 if # we're in filter_relay, filter_sender or filter_recipient #*********************************************************************** sub in_message_context { my($name) = @_; return 1 if ($InMessageContext); md_syslog('warning', "$name called outside of message context"); return 0; } =item in_filter_wrapup(name) Returns 1 if we are not in filter wrapup; 0 otherwise. =cut #*********************************************************************** # %PROCEDURE: in_filter_wrapup # %ARGUMENTS: # name -- a string to syslog if we are in filter wrapup # %RETURNS: # 1 if we are not in filter wrapup; 0 otherwise. #*********************************************************************** sub in_filter_wrapup { my($name) = @_; if ($InFilterWrapUp) { md_syslog('warning', "$name called inside filter_wrapup context"); return 1; } return 0; } =item in_filter_context Returns 1 if we are inside filter or filter_multipart, 0 otherwise. =cut #*********************************************************************** # %PROCEDURE: in_filter_context # %ARGUMENTS: # name -- a string to syslog if we are not in a filter context # %RETURNS: # 1 if we are inside filter or filter_multipart, 0 otherwise. #*********************************************************************** sub in_filter_context { my($name) = @_; return 1 if ($InFilterContext); md_syslog('warning', "$name called outside of filter context"); return 0; } =item in_filter_end(name) Returns 1 if we are inside filter_end 0 otherwise. =cut #*********************************************************************** # %PROCEDURE: in_filter_end # %ARGUMENTS: # name -- a string to syslog if we are not in filter_end # %RETURNS: # 1 if we are inside filter_end 0 otherwise. #*********************************************************************** sub in_filter_end { my($name) = @_; return 1 if ($InFilterEnd); md_syslog('warning', "$name called outside of filter_end"); return 0; } =item send_quarantine_notifications Sends quarantine notification message, if anything was quarantined. =cut #*********************************************************************** # %PROCEDURE: send_quarantine_notifications # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Sends quarantine notification message, if anything was quarantined #*********************************************************************** sub send_quarantine_notifications { # If there are quarantined parts, e-mail a report if ($QuarantineCount > 0 || $EntireMessageQuarantined) { my($body); my $hostname = Mail::MIMEDefang::Net::get_host_name(); if ($QuarantineCount >= 1) { $body .= "An e-mail had $QuarantineCount part"; $body .= "s" if ($QuarantineCount != 1); } else { $body .= "An e-mail message was"; } $body .= " quarantined in the directory\n"; $body .= "$QuarantineSubdir on " . Mail::MIMEDefang::Net::get_host_name() . ".\n\n"; $body .= "The sender was '$Sender'.\n\n" if defined($Sender); $body .= "The Sendmail queue identifier was $QueueID.\n\n" if ($QueueID ne "NOQUEUE"); $body .= "The relay machine was $RelayHostname ($RelayAddr).\n\n"; if ($EntireMessageQuarantined) { $body .= "The entire message was quarantined in $QuarantineSubdir/ENTIRE_MESSAGE\n\n"; } foreach my $recip (@Recipients) { $body .= "Recipient: $recip\n"; } my $donemsg = 0; my $in; my $i; for ($i=0; $i<=$QuarantineCount; $i++) { if (open($in, "<", "$QuarantineSubdir/MSG.$i")) { if (!$donemsg) { $body .= "Quarantine Messages:\n"; $donemsg = 1; } while(<$in>) { $body .= $_; } close($in); } } if ($donemsg) { $body .= "\n"; } if (open($in, "<", "$QuarantineSubdir/HEADERS")) { $body .= "\n----------\nHere are the message headers:\n"; while(<$in>) { $body .= $_; } close($in); } for ($i=1; $i<=$QuarantineCount; $i++) { if (open($in, "<", "$QuarantineSubdir/PART.$i.HEADERS")) { $body .= "\n----------\nHere are the headers for quarantined part $i:\n"; while(<$in>) { $body .= $_; } close($in); } } if ($#Warnings >= 0) { $body .= "\n----------\nHere are the warning details:\n\n"; $body .= "@Warnings"; } my $mime = MIME::Entity->build( From => "\"$DaemonName\" <$DaemonAddress>", To => "\"$AdminName\" <$AdminAddress>", Subject => $QuarantineSubject, Encoding => 'quoted-printable', Data => [$body], Type => "text/plain"); $mime->head->add('Auto-Submitted', 'auto-generated'); $mime->head->add('Precedence', 'bulk'); send_mail($DaemonAddress, $DaemonName, $AdminAddress, $mime->stringify); } } =item signal_complete Tells mimedefang C program Perl filter has finished successfully. Also mails any quarantine notifications and sender notifications. =cut #*********************************************************************** # %PROCEDURE: signal_complete # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Tells mimedefang C program Perl filter has finished successfully. # Also mails any quarantine notifications and sender notifications. #*********************************************************************** sub signal_complete { # Send notification to sender, if required if ($Sender ne '<>' && -r "NOTIFICATION") { my $body = ""; unless($NotifyNoPreamble) { $body .= "An e-mail you sent with message-id $MessageID\n"; $body .= "was modified by our mail scanning software.\n\n"; $body .= "The recipients were:"; foreach my $recip (@Recipients) { $body .= " $recip"; } $body .= "\n\n"; } my $notify; if (open($notify, "<", "NOTIFICATION")) { unless($NotifyNoPreamble) { $body .= "Here are the details of the modification:\n\n"; } while(<$notify>) { $body .= $_; } close($notify); } my $mime = MIME::Entity->build( From => "\"$DaemonName\" <$DaemonAddress>", To => $Sender, Subject => $NotifySenderSubject, Encoding => 'quoted-printable', Data => [$body], Type => "text/plain"); $mime->head->add('Auto-Submitted', 'auto-generated'); $mime->head->add('Precedence', 'bulk'); send_mail($DaemonAddress, $DaemonName, $Sender, $mime->stringify); } # Send notification to administrator, if required my $adm_notify; if (-r "ADMIN_NOTIFICATION") { my $body = ""; if (open($adm_notify, "<", "ADMIN_NOTIFICATION")) { $body .= join('', <$adm_notify>); close($adm_notify); send_admin_mail($NotifyAdministratorSubject, $body); } } # Syslog some info if any actions were taken my($msg) = ""; my($num); foreach my $key (sort keys(%Actions)) { $num = $Actions{$key}; $msg .= " $key=$num"; } if ($msg ne "") { md_syslog('debug', "filter: $msg"); } write_result_line("F", ""); if ($results_fh) { $results_fh->close() or croak("Could not close RESULTS file: $!"); undef $results_fh; } if ($ServerMode) { print_and_flush('ok'); } } =item send_mail(fromAddr, fromFull, recipient, body, deliverymode) Sends a mail message using Sendmail. Invokes Sendmail without involving the shell, so that shell metacharacters won't cause security problems. Delivery mode parameter is the optional sendmail delivery mode arg (default "-odd"). =cut #*********************************************************************** # %PROCEDURE: send_mail # %ARGUMENTS: # fromAddr -- address of sender # fromFull -- full name of sender # recipient -- address of recipient # body -- mail message (including headers) newline-terminated # deliverymode -- optional sendmail delivery mode arg (default "-odd") # %RETURNS: # Nothing # %DESCRIPTION: # Sends a mail message using Sendmail. Invokes Sendmail without involving # the shell, so that shell metacharacters won't cause security problems. #*********************************************************************** sub send_mail { my($fromAddr, $fromFull, $recipient, $body, $deliverymode) = @_; $deliverymode = "-odd" unless defined($deliverymode); if ($deliverymode ne "-odb" && $deliverymode ne "-odq" && $deliverymode ne "-odd" && $deliverymode ne "-odi") { $deliverymode = "-odd"; } my($pid); # Fork and exec for safety instead of involving shell my $ch; $pid = open($ch, "|-"); if (!defined($pid)) { md_syslog('err', "Cannot fork to run sendmail"); return; } if ($pid) { # In the parent -- pipe mail message to the child print $ch $body; close($ch); return; } # In the child -- invoke Sendmail # Direct stdout to stderr, or we will screw up communication with # the multiplexor.. open(STDOUT, ">&", \*STDERR); my(@cmd); if ($fromAddr ne "") { push(@cmd, "-f$fromAddr"); } else { push(@cmd, "-f<>"); } if ($fromFull ne "") { push(@cmd, "-F$fromFull"); } push(@cmd, $deliverymode); push(@cmd, "-Ac"); push(@cmd, "-oi"); push(@cmd, "--"); push(@cmd, $recipient); # In curlies to silence Perl warning... my $sm; $sm = $Features{'Path:SENDMAIL'}; { exec($sm, @cmd); } # exec failed! md_syslog('err', "Could not exec $sm: $!"); exit(1); # NOTREACHED } =item send_multipart_mail(fromAddr, fromName, recipient, subject, body_text, body_html, extra_headers) Sends a multipart mail message using Sendmail. Invokes Sendmail without involving the shell, so that shell metacharacters won't cause security problems. =cut #*********************************************************************** # %PROCEDURE: send_multipart_mail # %ARGUMENTS: # fromAddr -- address of sender # fromName -- full name of sender # recipient -- address of recipient # subject -- email subject # body_text -- text message newline-terminated # body_html -- html message newline-terminated # extra_headers -- optional extra headers to add to the email message # %RETURNS: # Nothing # %DESCRIPTION: # Sends a mail message using Sendmail. Invokes Sendmail without involving # the shell, so that shell metacharacters won't cause security problems. #*********************************************************************** sub send_multipart_mail { my ($fromAddr, $fromName, $recipient, $subject, $body_text, $body_html, $extra_headers) = @_; my $body; my @bset = ('A' .. 'Z'); my $boundary = join '' => map $bset[rand @bset], 1 .. 10; if($fromAddr =~ /^\<(.*)\>$/) { $fromAddr = $1; } if (defined $fromAddr and $fromAddr ne '') { my $mime = MIME::Entity->build( From => "\"$fromName\" <$fromAddr>", To => $recipient, Subject => $subject, Type => "multipart/alternative"); $mime->attach(Data => $body_text, Type => "text/plain"); $mime->attach(Data => $body_html, Type => "text/html"); $mime->head->add('Auto-Submitted', 'auto-generated'); $mime->head->add('Precedence', 'bulk'); foreach (keys %{$extra_headers}) { $mime->head->add($_, $extra_headers->{$_}); } send_mail($fromAddr, $fromName, $recipient, $mime->stringify); } else { md_syslog("Warning", "send_mail_multipart called with empty \"fromAddr\" parameter"); } } =item send_admin_mail(subject, body) Sends a mail message to the administrator =cut #*********************************************************************** # %PROCEDURE: send_admin_mail # %ARGUMENTS: # subject -- mail subject # body -- mail message (without headers) newline-terminated # %RETURNS: # Nothing # %DESCRIPTION: # Sends a mail message to the administrator #*********************************************************************** sub send_admin_mail { my ($subject, $body) = @_; my $mail; my $mime = MIME::Entity->build( To => "\"$AdminName\" <$AdminAddress>", From => "$DaemonName <$DaemonAddress>", Subject => $subject, Encoding => 'quoted-printable', Data => [$body], Type => "text/plain"); $mime->head->add('Auto-Submitted', 'auto-generated'); $mime->head->add('Precedence', 'bulk'); send_mail($DaemonAddress, $DaemonName, $AdminAddress, $mime->stringify); } =item read_commands_file() This function should only be called from C and C. This will read the C file (as described in L), and will fill or update the following global variables: $Sender, @Recipients, %RecipientMailers, $RelayAddr, $RealRelayAddr, $RelayHostname, $RealRelayHostname, $QueueID, $Helo, %SendmailMacros. If you do not call C, then the only information available in C and C is that which is passed as an argument to the function. =cut #*********************************************************************** # %PROCEDURE: read_commands_file # %ARGUMENTS: # needf - if true, will return an error when no closing "F" was found. # (optional, default is false). needf should not be set when # called from within filter_relay, filter_sender, filter_recipient. # %RETURNS: # true if parse went well, # false otherwise # %DESCRIPTION: # Parses the COMMANDS file, and sets these global variables based # upon the contents of that file: # $Sender # @Recipients # %RecipientMailers # $SuspiciousCharsInHeaders # $SuspiciousCharsInBody # $RelayAddr # $RealRelayAddr # $WasResent # $RelayHostname # $RealRelayHostname # $QueueID # $Subject # $MessageID # $Helo # %SendmailMacros # #*********************************************************************** sub read_commands_file { my $needF = shift; $needF = 0 unless defined($needF); my $in; if (!open($in, "<", "COMMANDS")) { fatal("Cannot open COMMANDS file from mimedefang: $!"); return 0; } my($cmd, $arg, $rawcmd, $rawarg, $seenF); # Save current recipient if called from filter_recipient my @tmp_recipients = @Recipients; @Recipients = (); $seenF = 0; my $recent_recip = ""; while(<$in>) { chomp; $rawcmd = $_; $cmd = percent_decode($rawcmd); $arg = substr($cmd, 1); $cmd = substr($cmd, 0, 1); $rawarg = substr($rawcmd, 1); if ($cmd eq "S") { $Sender = $arg; } elsif ($cmd eq "s") { push(@SenderESMTPArgs, $arg); } elsif ($cmd eq "F") { $seenF = 1; last; } elsif ($cmd eq "R") { my($recip, $rcpt_mailer, $rcpt_host, $rcpt_addr); ($recip, $rcpt_mailer, $rcpt_host, $rcpt_addr) = split(' ', $rawarg); $rcpt_mailer = "?" unless (defined($rcpt_mailer) and ($rcpt_mailer ne "")); $rcpt_host = "?" unless (defined($rcpt_host) and ($rcpt_host ne "")); $rcpt_addr = "?" unless (defined($rcpt_addr) and ($rcpt_addr ne "")); $recip = percent_decode($recip); $rcpt_mailer = percent_decode($rcpt_mailer); $rcpt_host = percent_decode($rcpt_host); $rcpt_addr = percent_decode($rcpt_addr); push(@Recipients, $recip); $RecipientMailers{$recip} = [$rcpt_mailer, $rcpt_host, $rcpt_addr]; $recent_recip = $recip; } elsif ($cmd eq "r") { push (@{$RecipientESMTPArgs{$recent_recip}}, $arg); } elsif ($cmd eq "!") { $SuspiciousCharsInHeaders = 1; } elsif ($cmd eq "?") { $SuspiciousCharsInBody = 1; } elsif ($cmd eq "I") { $RelayAddr = $arg; $RealRelayAddr = $arg; } elsif ($cmd eq "J") { $WasResent = 1; $RelayAddr = $arg; my($iaddr, $iname); $iaddr = inet_aton($RelayAddr); $iname = gethostbyaddr($iaddr, AF_INET); if (defined($iname)) { $RelayHostname = $iname; } else { $RelayHostname = "[$RelayAddr]"; } } elsif ($cmd eq "H") { $RelayHostname = $arg; $RealRelayHostname = $arg; } elsif ($cmd eq "Q") { $QueueID = $arg; } elsif ($cmd eq "U") { $SubjectCount++; if ($SubjectCount > 1) { md_syslog('warning', "Message contains more than one Subject: header: $Subject --> $arg"); } else { $Subject = $arg; } } elsif ($cmd eq "X") { $MessageID = $arg; } elsif ($cmd eq "E") { $Helo = $arg; } elsif ($cmd eq "=") { my($macro, $value); ($macro, $value) = split(' ', $rawarg); $value = "" unless defined($value); $macro = "" unless defined($macro); if ($macro ne "") { $macro = percent_decode($macro); $value = percent_decode($value); $SendmailMacros{$macro} = $value; } } elsif ($cmd eq "i") { $MIMEDefangID = $arg; } else { md_syslog('warning', "Unknown command $cmd from mimedefang"); } } close($in); if ( $needF && !$seenF ) { md_syslog('err', "COMMANDS file from mimedefang did not terminate with 'F' -- check disk space in spool directory"); fatal("COMMANDS file did not end with F"); return 0; } push @Recipients, @tmp_recipients; return 1; } =back =head2 SEE ALSO L L L L L L L L L L L L =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/000077500000000000000000000000001475763067200204335ustar00rootroot00000000000000mimedefang-3.6/modules/lib/Mail/MIMEDefang/Actions.pm000066400000000000000000001040411475763067200223710ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::Actions - actions methods for email filters =head1 DESCRIPTION Mail::MIMEDefang::Actions are a set of methods that can be called from F to accept or reject the email message. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::Actions; use strict; use warnings; use Digest::SHA; use Mail::MIMEDefang; use Mail::MIMEDefang::MIME; use Mail::MIMEDefang::Utils; require Exporter; our @ISA = qw(Exporter); our @EXPORT; our @EXPORT_OK; @EXPORT = qw{ action_rebuild action_notify_sender action_insert_header action_drop action_bounce action_accept action_defang action_discard action_add_part action_tempfail action_add_header action_add_entity action_quarantine action_quarantine_entire_message action_change_header action_delete_header action_external_filter get_quarantine_dir action_sm_quarantine action_greylist action_accept_with_warning action_notify_administrator action_replace_with_warning action_replace_with_url action_drop_with_warning action_delete_all_headers message_rejected process_added_parts add_recipient delete_recipient change_sender }; =item action_rebuild Sets a flag telling MIMEDefang to rebuild message even if it is unchanged. =cut #*********************************************************************** # %PROCEDURE: action_rebuild # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Sets a flag telling MIMEDefang to rebuild message even if it is # unchanged. #*********************************************************************** sub action_rebuild { return unless (in_message_context("action_rebuild") && !in_filter_wrapup("action_rebuild")); $Rebuild = 1; } =item action_add_entity Makes a note to add a part to the message. Parts are *actually* added at the end, which lets us correctly handle non-multipart messages or multipart/foo where "foo" != "mixed". Sets the rebuild flag. =cut #*********************************************************************** # %PROCEDURE: action_add_entity # %ARGUMENTS: # entity -- the mime entity to add (must be pre-built) # location -- (optional) location at which to add part (default -1 = end) # %RETURNS: # The entity object for the new part # %DESCRIPTION: # Makes a note to add a part to the message. Parts are *actually* added # at the end, which lets us correctly handle non-multipart messages or # multipart/foo where "foo" != "mixed". Sets the rebuild flag. #*********************************************************************** sub action_add_entity { my($entity, $offset) = @_; return unless (in_message_context("action_add_part") && !in_filter_wrapup("action_add_part")); $offset = -1 unless defined($offset); push(@AddedParts, [$entity, $offset]); action_rebuild(); return $entity; } =item action_add_part Makes a note to add a part to the message. Parts are *actually* added at the end, which lets us correctly handle non-multipart messages or multipart/foo where "foo" != "mixed". Sets the rebuild flag. =cut #*********************************************************************** # %PROCEDURE: action_add_part # %ARGUMENTS: # entity -- the mime entity # type -- the mime type # encoding -- see MIME::Entity(8) # data -- the data for the part # fname -- file name # disposition -- content-disposition header # location -- (optional) location at which to add part (default -1 = end) # %RETURNS: # The entity object for the new part # %DESCRIPTION: # Makes a note to add a part to the message. Parts are *actually* added # at the end, which lets us correctly handle non-multipart messages or # multipart/foo where "foo" != "mixed". Sets the rebuild flag. #*********************************************************************** sub action_add_part { my ($entity) = shift; my ($type) = shift; my ($encoding) = shift; my ($data) = shift; my ($fname) = shift; my ($disposition) = shift; my ($offset) = shift; return unless (in_message_context("action_add_part") && !in_filter_wrapup("action_add_part")); $offset = -1 unless defined($offset); my ($part); my $charset = undef; if (utf8::is_utf8($data)) { utf8::encode($data); $charset = 'utf-8'; } $part = MIME::Entity->build(Type => $type, Top => 0, 'X-Mailer' => undef, Encoding => $encoding, Charset => $charset, Data => ["$data"]); defined ($fname) && $part->head->mime_attr("Content-Type.name" => $fname); defined ($disposition) && $part->head->mime_attr("Content-Disposition" => $disposition); defined ($fname) && $part->head->mime_attr("Content-Disposition.filename" => $fname); return action_add_entity($part, $offset); } =item process_added_parts Actually adds requested parts to entity. Ensures that entity is of type multipart/mixed. =cut #*********************************************************************** # %PROCEDURE: process_added_parts # %ARGUMENTS: # rebuilt -- rebuilt entity # %RETURNS: # A new entity with parts added # %DESCRIPTION: # Actually adds requested parts to entity. Ensures that entity is # of type multipart/mixed #*********************************************************************** sub process_added_parts { my($rebuilt) = @_; my($entity); # If no parts to add, do nothing return $rebuilt if ($#AddedParts < 0); # Make sure we have a multipart/mixed container if (lc($rebuilt->head->mime_type) ne "multipart/mixed") { $entity = MIME::Entity->build(Type => "multipart/mixed", 'X-Mailer' => undef); $entity->add_part($rebuilt); } else { $entity = $rebuilt; } foreach my $thing (@AddedParts) { $entity->add_part($thing->[0], $thing->[1]); } return $entity; } =item action_insert_header Makes a note for milter to insert a header in the message in the specified position. May not be supported on all versions of Sendmail; on unsupported versions, the C milter falls back to action_add_header. =cut #*********************************************************************** # %PROCEDURE: action_insert_header # %ARGUMENTS: # header -- header name (eg: X-My-Header) # value -- header value (eg: any text goes here) # position -- where to place it (eg: 0 [default] to make it first) # %RETURNS: # Nothing # %DESCRIPTION: # Makes a note for milter to insert a header in the message in the # specified position. May not be supported on all versions of Sendmail; # on unsupported versions, the C milter falls back to action_add_header. #*********************************************************************** sub action_insert_header { my($header, $value, $pos) = @_; $pos = 0 unless defined($pos); write_result_line('N', $header, $pos, $value); } =item action_add_header Makes a note for milter to add a header to the message. =cut #*********************************************************************** # %PROCEDURE: action_add_header # %ARGUMENTS: # header -- header name (eg: X-My-Header) # value -- header value (eg: any text goes here) # %RETURNS: # Nothing # %DESCRIPTION: # Makes a note for milter to add a header to the message. #*********************************************************************** sub action_add_header { my($header, $value) = @_; write_result_line('H', $header, $value); } =item action_change_header Makes a note for milter to change a header in the message. =cut #*********************************************************************** # %PROCEDURE: action_change_header # %ARGUMENTS: # header -- header name (eg: X-My-Header) # value -- header value (eg: any text goes here) # index -- index of header to change (default 1) # %RETURNS: # Nothing # %DESCRIPTION: # Makes a note for milter to change a header in the message. #*********************************************************************** sub action_change_header { my($header, $value, $idx) = @_; return if (!in_message_context("action_change_header")); $idx = 1 unless defined($idx); write_result_line('I', $header, $idx, $value); } =item action_delete_header Makes a note for milter to delete a header in the message. =cut #*********************************************************************** # %PROCEDURE: action_delete_header # %ARGUMENTS: # header -- header name (eg: X-My-Header) # index -- index of header to delete (default 1) # %RETURNS: # Nothing # %DESCRIPTION: # Makes a note for milter to delete a header in the message. #*********************************************************************** sub action_delete_header { my($header, $idx) = @_; return if (!in_message_context("action_delete_header")); $idx = 1 unless defined($idx); write_result_line('J', $header, $idx); } =item action_delete_all_headers Makes a note for milter to delete all instances of header. =cut #*********************************************************************** # %PROCEDURE: action_delete_all_headers # %ARGUMENTS: # header -- header name (eg: X-My-Header) # %RETURNS: # Nothing # %DESCRIPTION: # Makes a note for milter to delete all instances of header. #*********************************************************************** sub action_delete_all_headers { my($header) = @_; return 0 if (!in_message_context("action_delete_all_headers")); my($count, $len, $orig_header); $orig_header = $header; $len = length($header) + 1; $header .= ":"; $header = lc($header); my $hdrs; return unless(open($hdrs, "<", "HEADERS")); $count = 0; while(<$hdrs>) { if (lc(substr($_, 0, $len)) eq $header) { $count++; } } close($hdrs); # Delete in REVERSE order, in case Sendmail updates # its count as headers are deleted... paranoid but safe. while ($count > 0) { action_delete_header($orig_header, $count); $count--; } return 1; } =item action_accept Makes a note for milter to accept the current part. =cut #*********************************************************************** # %PROCEDURE: action_accept # %ARGUMENTS: # Ignored # %RETURNS: # Nothing # %DESCRIPTION: # Makes a note to accept the current part. #*********************************************************************** sub action_accept { return 0 if (!in_filter_context("action_accept")); $Action = "accept"; return 1; } =item action_accept_with_warning Makes a note for milter to accept the current part, but add a warning to the message. =cut #*********************************************************************** # %PROCEDURE: action_accept_with_warning # %ARGUMENTS: # msg -- warning message # %RETURNS: # Nothing # %DESCRIPTION: # Makes a note to accept the current part, but add a warning to the # message. #*********************************************************************** sub action_accept_with_warning { my($msg) = @_; return 0 if (!in_filter_context("action_accept_with_warning")); $Actions{'accept_with_warning'}++; $Action = "accept"; push(@Warnings, "$msg\n"); return 1; } =item message_rejected Method that returns True if message has been rejected (with action_bounce or action_tempfail), false otherwise. =cut #*********************************************************************** # %PROCEDURE: message_rejected # %ARGUMENTS: # None # %RETURNS: # True if message has been rejected (with action_bounce or action_tempfail); # false otherwise. #*********************************************************************** sub message_rejected { return 0 if (!in_message_context("message_rejected")); return (defined($Actions{'tempfail'}) || defined($Actions{'bounce'}) || defined($Actions{'discard'})); } =item action_drop Makes a note for milter to drop the current part without any warning. =cut #*********************************************************************** # %PROCEDURE: action_drop # %ARGUMENTS: # Ignored # %RETURNS: # Nothing # %DESCRIPTION: # Makes a note to drop the current part without any warning. #*********************************************************************** sub action_drop { return 0 if (!in_filter_context("action_drop")); $Actions{'drop'}++; $Action = "drop"; return 1; } =item action_drop_with_warning Makes a note for milter to drop the current part and add a warning to the message. =cut #*********************************************************************** # %PROCEDURE: action_drop_with_warning # %ARGUMENTS: # msg -- warning message # %RETURNS: # Nothing # %DESCRIPTION: # Makes a note to drop the current part and add a warning to the message #*********************************************************************** sub action_drop_with_warning { my($msg) = @_; return 0 if (!in_filter_context("action_drop_with_warning")); $Actions{'drop_with_warning'}++; $Action = "drop"; push(@Warnings, "$msg\n"); return 1; } =item action_replace_with_warning Makes a note for milter to drop the current part and replace it with a warning. =cut #*********************************************************************** # %PROCEDURE: action_replace_with_warning # %ARGUMENTS: # msg -- warning message # %RETURNS: # Nothing # %DESCRIPTION: # Makes a note to drop the current part and replace it with a warning #*********************************************************************** sub action_replace_with_warning { my($msg) = @_; return 0 if (!in_filter_context("action_replace_with_warning")); $Actions{'replace_with_warning'}++; $Action = "replace"; $WarningCounter++; $ReplacementEntity = MIME::Entity->build(Top => 0, Type => "text/plain", Encoding => "-suggest", Disposition => "inline", Filename => "warning$WarningCounter.txt", 'X-Mailer' => undef, Data => [ "$msg\n" ]); return 1; } =item action_defang Makes a note for milter to defang the current part by changing its name, filename and possibly MIME type. =cut #*********************************************************************** # %PROCEDURE: action_defang # %ARGUMENTS: # entity -- current part # name -- suggested name for defanged part # fname -- suggested filename for defanged part # type -- suggested MIME type for defanged part # %RETURNS: # Nothing # %DESCRIPTION: # Makes a note to defang the current part by changing its name, filename # and possibly MIME type. #*********************************************************************** sub action_defang { $Changed = 1; my($entity, $name, $fname, $type) = @_; return 0 if (!in_filter_context("action_defang")); $name = "" unless defined($name); $fname = "" unless defined($fname); $type = "application/octet-stream" unless defined($type); $Actions{'defang'}++; my($head) = $entity->head; my($oldfname) = takeStabAtFilename($entity); my($defang); if ($name eq "" || $fname eq "") { $defang = make_defanged_name(); } $name = $defang if ($name eq ""); $fname = $defang if ($fname eq ""); my($warning); if (defined(&defang_warning)) { $warning = defang_warning($oldfname, $fname); } else { $warning = "An attachment named '$oldfname'"; $warning .= " was converted to '$fname'.\n"; $warning .= "To recover the file, click on the attachment and Save As\n'$oldfname' in order to access it.\n"; } $entity->effective_type($type); $head->replace("Content-Type", $type); $head->mime_attr("Content-Type.name" => $name); $head->mime_attr("Content-Disposition.filename" => $fname); $head->mime_attr("Content-Description" => $fname); action_accept_with_warning("$warning"); return 1; } =item action_external_filter Pipes the part through the UNIX command $cmd, and replaces the part with the result of running the filter. =cut #*********************************************************************** # %PROCEDURE: action_external_filter # %ARGUMENTS: # entity -- current part # cmd -- UNIX command to run # %RETURNS: # 1 on success, 0 otherwise. # %DESCRIPTION: # Pipes the part through the UNIX command $cmd, and replaces the # part with the result of running the filter. #*********************************************************************** sub action_external_filter { my($entity, $cmd) = @_; return 0 if (!in_filter_context("action_external_filter")); # Copy the file my($body) = $entity->bodyhandle; if (!defined($body)) { return 0; } if (!defined($body->path)) { return 0; } unless(copy_or_link($body->path, "FILTERINPUT")) { md_syslog('err', "Could not open FILTERINPUT: $!"); return(0); } # Run the filter my($status) = system($cmd); # Filter failed if non-zero exit if ($status % 255) { md_syslog('err', "External filter exited with non-zero status $status"); return 0; } # If filter didn't produce FILTEROUTPUT, do nothing return 1 if (! -r "FILTEROUTPUT"); # Rename FILTEROUTPUT over original path unless (rename("FILTEROUTPUT", $body->path)) { md_syslog('err', "Could not rename FILTEROUTPUT to path: $!"); return(0); } $Changed = 1; $Actions{'external_filter'}++; return 1; } =item action_quarantine Makes a note for milter to drop the current part, emails the MIMEDefang administrator a notification, and quarantines the part in the quarantine directory. =cut #*********************************************************************** # %PROCEDURE: action_quarantine # %ARGUMENTS: # entity -- current part # msg -- warning message # %RETURNS: # Nothing # %DESCRIPTION: # Similar to action_drop_with_warning, but e-mails the MIMEDefang # administrator a notification, and quarantines the part in the # quarantine directory. #*********************************************************************** sub action_quarantine { my($entity, $msg) = @_; return 0 if (!in_filter_context("action_quarantine")); $Action = "drop"; push(@Warnings, "$msg\n"); # Can't handle path-less bodies my($body) = $entity->bodyhandle; if (!defined($body)) { return 0; } if (!defined($body->path)) { return 0; } get_quarantine_dir(); if ($QuarantineSubdir eq "") { # Could not create quarantine directory return 0; } $Actions{'quarantine'}++; $QuarantineCount++; # Save the part copy_or_link($body->path, "$QuarantineSubdir/PART.$QuarantineCount.BODY"); # Save the part's headers my $out; if (open($out, ">", "$QuarantineSubdir/PART.$QuarantineCount.HEADERS")) { print $out $entity->head->stringify; close($out); } # Save the messages if (open($out, ">", "$QuarantineSubdir/MSG.$QuarantineCount")) { print $out "$msg\n"; close($out); } return 1; } =item action_sm_quarantine Asks Sendmail to quarantine message in mqueue using Sendmail's smfi_quarantine facility. =cut #*********************************************************************** # %PROCEDURE: action_sm_quarantine # %ARGUMENTS: # reason -- reason for quarantine # %RETURNS: # Nothing # %DESCRIPTION: # Asks Sendmail to quarantine message in mqueue using Sendmail's # smfi_quarantine facility. #*********************************************************************** sub action_sm_quarantine { my($reason) = @_; return if (!in_message_context("action_sm_quarantine")); $Actions{'sm_quarantine'} = 1; write_result_line("Q", $reason); } =item get_quarantine_dir Method that returns the configured quarantine directory. =cut sub get_quarantine_dir { # If quarantine dir has already been made, return it. if ($QuarantineSubdir ne "") { return $QuarantineSubdir; } my($counter) = 0; my($tries); my($success) = 0; my($tm); $tm = time_str(); my $hour = hour_str(); my $hour_dir = sprintf("%s/%s", $Features{'Path:QUARANTINEDIR'}, $hour); mkdir($hour_dir, 0750); if (! -d $hour_dir) { return ""; } do { $counter++; $QuarantineSubdir = sprintf("%s/%s/qdir-%s-%03d", $Features{'Path:QUARANTINEDIR'}, $hour, $tm, $counter); if (mkdir($QuarantineSubdir, 0750)) { $success = 1; } } while(!$success && ($tries++ < 1000)); if (!$success) { $QuarantineSubdir = ""; return ""; } # Write the sender and recipient info my ($in, $out); if (open($out, ">", "$QuarantineSubdir/SENDER")) { print $out "$Sender\n"; close($out); } if (open($out, ">", "$QuarantineSubdir/SENDMAIL-QID")) { print $out "$QueueID\n"; close($out); } if (open($out, ">", "$QuarantineSubdir/RECIPIENTS")) { foreach my $s (@Recipients) { print $out "$s\n"; } close($out); } # Copy message headers if (open($out, ">", "$QuarantineSubdir/HEADERS")) { if (open($in, "<", "HEADERS")) { while(my $line = <$in>) { print $out $line; } close($in); } close($out); } return $QuarantineSubdir; } =item action_greylist($dbh, $sender, $recipient, $ip, $min_retry, $max_retry) $dbh is a DBI handle connected to the greylist database. $dbh object should be initialized in filter_initialize sub. $min_delay and $max_delay are the minimum and maximum retry delays respectively, those parameters are optional (default values are 300 and 14400 seconds). If an SMTP client tries to deliver email faster, it will continue to be greylisted. $ip, $sender and $recipient are used to identify a unique connection. If it waits longer, it will begin the greylisting test from scratch. $ip is the IP address of the connecting SMTP client, to greylist an entire subnet you can pass the subnet instead. In C sub, the database connection should be closed. Returns "tempfail" if a new sender sends the email from a new ip address, "continue" if the email is allowed to pass or "reject" if the email has been greylisted for too much time. =cut #*********************************************************************** # %PROCEDURE: action_greylist # %ARGUMENTS: # dbh -- db handle # sender -- sender's email # recipient -- recipient's email # ip -- ip address or subnet of the sender # min_retry -- minimum retry time # max_retry -- maximum retry time # %RETURNS: # continue, reject or tempfail # %DESCRIPTION: # Returns "tempfail" if a new sender sends the email from a new ip address, # "continue" if the email is allowed to pass or "reject" if the email has been # greylisted for too much time. #*********************************************************************** sub action_greylist { my ($dbh, $sender, $recipient, $ip, $min_retry, $max_retry) = @_; $min_retry //= 300; $max_retry //= (4*3600); my $now = time; # skip greylisting if ip address has passed greylisting in the last 31 days my $res = $dbh->selectrow_hashref(q{SELECT sender_host_ip FROM greylist WHERE sender = ? AND recipient = ? AND sender_host_ip = ? AND last_received >= ? AND known_ip = 1}, undef, $sender, $recipient, $ip, ($now - 31*86400)); if ($res) { # update received date $dbh->do(q{UPDATE greylist SET known_ip=1, last_received=? WHERE sender = ? AND recipient = ? AND sender_host_ip = ?}, undef, $now, $sender, $recipient, $ip); md_syslog('Warning', "[greylist] Skip greylisting for sender $sender, sender is in our skiplist"); return "continue"; } $res = $dbh->selectrow_hashref(q{SELECT last_received FROM greylist WHERE sender = ? AND recipient = ? AND sender_host_ip = ?}, undef, $sender, $recipient, $ip); if ($res) { if ($now - $res->{last_received} > $max_retry) { # message arrived too late $dbh->do(q{DELETE FROM greylist WHERE sender = ? AND recipient = ? AND sender_host_ip = ?}, undef, $sender, $recipient, $ip); md_syslog('Warning', "[greylist] Reject sender $sender, email has arrived too late"); return "reject"; } if ($now - $res->{last_received} < $min_retry) { # message arrived too soon md_syslog('Warning', "[greylist] Tempfail sender $sender, email has arrived too soon"); return "tempfail"; } # Ip can continue and we can add it to skip-list $dbh->do(q{UPDATE greylist SET known_ip=1, last_received=? WHERE sender = ? AND recipient = ? AND sender_host_ip = ?}, undef, $now, $sender, $recipient, $ip); md_syslog('Warning', "[greylist] Sender $sender has been added to our skiplist"); return "continue"; } $dbh->do(q{INSERT INTO greylist(sender_host_ip, sender, recipient, first_received, last_received, known_ip) VALUES(?, ?, ?, ?, ?, ?)}, undef, $ip, $sender, $recipient, $now, $now, 0); md_syslog('Warning', "[greylist] Tempfail sender $sender, please come back later"); return "tempfail"; } =item action_quarantine_entire_message Method that puts a copy of the entire message in the quarantine directory. =cut #*********************************************************************** # %PROCEDURE: action_quarantine_entire_message # %ARGUMENTS: # msg -- quarantine message (optional) # %RETURNS: # Nothing # %DESCRIPTION: # Puts a copy of the entire message in the quarantine directory. #*********************************************************************** sub action_quarantine_entire_message { my($msg) = @_; return 0 if (!in_message_context("action_quarantine_entire_message")); # If no parts have yet been quarantined, create the quarantine subdirectory # and write useful info there get_quarantine_dir(); if ($QuarantineSubdir eq "") { # Could not create quarantine directory return 0; } # Don't copy message twice if ($EntireMessageQuarantined) { return 1; } $Actions{'quarantine_entire_message'}++; my $out; if (defined($msg) && ($msg ne "")) { if (open($out, ">", "$QuarantineSubdir/MSG.0")) { print $out "$msg\n"; close($out); } } $EntireMessageQuarantined = 1; copy_or_link("INPUTMSG", "$QuarantineSubdir/ENTIRE_MESSAGE"); return 1; } =item action_bounce Method that Causes the SMTP transaction to fail with an SMTP 554 failure code and the specified reply text. If code or DSN are omitted or invalid, use 554 and 5.7.1. =cut #*********************************************************************** # %PROCEDURE: action_bounce # %ARGUMENTS: # reply -- SMTP reply text (eg: "Not allowed, sorry") # code -- SMTP reply code (eg: 554) # DSN -- DSN code (eg: 5.7.1) # %RETURNS: # Nothing # %DESCRIPTION: # Causes the SMTP transaction to fail with an SMTP 554 failure code and the # specified reply text. If code or DSN are omitted or invalid, # use 554 and 5.7.1. #*********************************************************************** sub action_bounce { my($reply, $code, $dsn) = @_; return 0 if (!in_message_context("action_bounce")); $reply = "Forbidden for policy reasons" unless (defined($reply) and ($reply ne "")); $code = 554 unless (defined($code) and $code =~ /^5\d\d$/); $dsn = "5.7.1" unless (defined($dsn) and $dsn =~ /^5\.\d{1,3}\.\d{1,3}$/); write_result_line('B', $code, $dsn, $reply); $Actions{'bounce'}++; return 1; } =item action_discard Method that causes the entire message to be silently discarded without notifying anyone. =cut #*********************************************************************** # %PROCEDURE: action_discard # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Causes the entire message to be silently discarded without without # notifying anyone. #*********************************************************************** sub action_discard { return 0 if (!in_message_context("action_discard")); write_result_line("D", ""); $Actions{'discard'}++; return 1; } =item action_notify_sender Method that sends an email to the sender containing the $msg. =cut #*********************************************************************** # %PROCEDURE: action_notify_sender # %ARGUMENTS: # msg -- a message to send # %RETURNS: # Nothing # %DESCRIPTION: # Causes an e-mail to be sent to the sender containing $msg #*********************************************************************** sub action_notify_sender { my($msg) = @_; return 0 if (!in_message_context("action_notify_sender")); if ($Sender eq '<>') { md_syslog('err', "Skipped action_notify_sender: Sender = <>"); return 0; } if ($VirusName ne "") { md_syslog('err', "action_notify_sender disabled when virus is detected"); return 0; } my $notify; if (open($notify, ">>", "NOTIFICATION")) { print $notify $msg; close($notify); $Actions{'notify_sender'}++; return 1; } md_syslog('err', "Could not create NOTIFICATION file: $!"); return 0; } =item action_notify_administrator Method that sends an email to MIMEDefang administrator containing the $msg. =cut #*********************************************************************** # %PROCEDURE: action_notify_administrator # %ARGUMENTS: # msg -- a message to send # %RETURNS: # Nothing # %DESCRIPTION: # Causes an e-mail to be sent to the MIMEDefang administrator # containing $msg #*********************************************************************** sub action_notify_administrator { my($msg) = @_; if (!$InMessageContext) { send_admin_mail($NotifyAdministratorSubject, $msg); return 1; } my $adm_notify; if (open($adm_notify, ">>", "ADMIN_NOTIFICATION")) { print $adm_notify $msg; close($adm_notify); $Actions{'notify_administrator'}++; return 1; } md_syslog('err', "Could not create ADMIN_NOTIFICATION file: $!"); return 0; } =item action_tempfail Method that sends a temporary failure with a 4.x.x SMTP code. If code or DSN are omitted or invalid, use 451 and 4.3.0. =cut #*********************************************************************** # %PROCEDURE: action_tempfail # %ARGUMENTS: # reply -- the text reply # code -- SMTP reply code (eg: 451) # DSN -- DSN code (eg: 4.3.0) # %RETURNS: # Nothing # %DESCRIPTION: # Tempfails the message with a 4.x.x SMTP code. If code or DSN are # omitted or invalid, use 451 and 4.3.0. #*********************************************************************** sub action_tempfail { my($reply, $code, $dsn) = @_; return 0 if (!in_message_context("action_tempfail")); $reply = "Try again later" unless (defined($reply) and ($reply ne "")); $code = 451 unless (defined($code) and $code =~ /^4\d\d$/); $dsn = "4.3.0" unless (defined($dsn) and $dsn =~ /^4\.\d{1,3}\.\d{1,3}$/); write_result_line('T', $code, $dsn, $reply); $Actions{'tempfail'}++; return 1; } =item add_recipient Signals to MIMEDefang to add a recipient to the envelope. =cut #*********************************************************************** # %PROCEDURE: add_recipient # %ARGUMENTS: # recip -- recipient to add # %RETURNS: # 0 on failure, 1 on success. # %DESCRIPTION: # Signals to MIMEDefang to add a recipient to the envelope. #*********************************************************************** sub add_recipient { my($recip) = @_; write_result_line("R", $recip); return 1; } =item delete_recipient Signals to MIMEDefang to delete a recipient from the envelope. =cut #*********************************************************************** # %PROCEDURE: delete_recipient # %ARGUMENTS: # recip -- recipient to delete # %RETURNS: # 0 on failure, 1 on success. # %DESCRIPTION: # Signals to MIMEDefang to delete a recipient from the envelope. #*********************************************************************** sub delete_recipient { my($recip) = @_; write_result_line("S", $recip); return 1; } =item change_sender Signals to MIMEDefang to change the envelope sender. =cut #*********************************************************************** # %PROCEDURE: change_sender # %ARGUMENTS: # sender -- new envelope sender # %RETURNS: # 0 on failure, 1 on success. # %DESCRIPTION: # Signals to MIMEDefang to change the envelope sender. Only works on # Sendmail 8.14.0 and higher, but no feedback is given to Perl caller! #*********************************************************************** sub change_sender { my($sender) = @_; write_result_line("f", $sender); return 1; } =item action_replace_with_url Method that places the part in doc_root/{sha1_of_part}.ext and replaces it with a text/plain part giving the URL for pickup. =cut #*********************************************************************** # %PROCEDURE: action_replace_with_url # %ARGUMENTS: # entity -- part to replace # doc_root -- document root in which to place file # base_url -- base URL for retrieving document # msg -- message to replace document with. The string "_URL_" is # replaced with the actual URL of the part. # cd_data -- optional Content-Disposition filename data to save # salt -- optional salt to add to SHA1 hash. # %RETURNS: # 1 on success, 0 on failure # %DESCRIPTION: # Places the part in doc_root/{sha1_of_part}.ext and replaces it with # a text/plain part giving the URL for pickup. #*********************************************************************** sub action_replace_with_url { my($entity, $doc_root, $base_url, $msg, $cd_data, $salt) = @_; my($ctx); my($path); my($fname, $ext, $name, $url); my $extension = ""; return 0 unless in_filter_context("action_replace_with_url"); return 0 unless defined($entity->bodyhandle); $path = $entity->bodyhandle->path; return 0 unless defined($path); $ctx = Digest::SHA->new; $ctx->addfile($path); $ctx->add($salt) if defined($salt); $fname = takeStabAtFilename($entity); $fname = "" unless defined($fname); $extension = $1 if ($fname =~ /(\.[^.]*)$/); # Use extension if it is .[alpha,digit,underscore] $extension = "" unless ($extension =~ /^\.[A-Za-z0-9_]*$/); # Filename to save $name = $ctx->hexdigest . $extension; $fname = $doc_root . "/" . $name; $url = $base_url . "/" . $name; if (-r $fname) { # If file exists, then this is either a duplicate or someone # has defeated SHA1. Just update the mtime on the file. my($now); $now = time; utime($now, $now, $fname); } else { copy_or_link($path, $fname) or return 0; # In case umask is whacked... chmod 0644, $fname; } # save optional Content-Disposition data my $cdf; if (defined($cd_data) and ($cd_data ne "")) { if (open $cdf, ">", "$doc_root/.$name") { print $cdf $cd_data; close $cdf; chmod 0644, "$doc_root/.$name"; } } $msg =~ s/_URL_/$url/g; action_replace_with_warning($msg); return 1; } =back =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/Antispam.pm000066400000000000000000000356431475763067200225600ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::Antispam - antispam related methods for email filters =head1 DESCRIPTION Mail::MIMEDefang::Antispam are a set of methods that can be called from F to check email messages with antispam softwares. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::Antispam; use strict; use warnings; use Carp; use Mail::MIMEDefang; use Mail::MIMEDefang::Utils; require Exporter; our @ISA = qw(Exporter); our @EXPORT; our @EXPORT_OK; @EXPORT = qw(spam_assassin_init spam_assassin_mail spam_assassin_check spam_assassin_status spam_assassin_is_spam md_spamc_init md_spamc_check rspamd_check); =item spam_assassin_is_spam Method that scans a mmessage using SpamAssassin and returns True if the email message has been detected as spam. =cut #*********************************************************************** # %PROCEDURE: spam_assassin_is_spam # %ARGUMENTS: # config -- optional configuration file # %RETURNS: # 1 if SpamAssassin thinks current message is SPAM; 0 otherwise # or if message could not be opened. # %DESCRIPTION: # Scans message using SpamAssassin (http://www.spamassassin.org) #*********************************************************************** sub spam_assassin_is_spam { my($hits, $req, $tests, $report) = spam_assassin_check(@_); return if (!defined($hits)); return ($hits >= $req); } =item spam_assassin_check Method that scans a message using SpamAssassin and returns an array of four elements, =over 4 =item * Weight of message ('hits') =item * Number of hits required before SA considers a message spam =item * Comma separated list of symbolic test names that were triggered =item * A 'report' string, detailing tests that failed and their weights =back =cut #*********************************************************************** # %PROCEDURE: spam_assassin_check # %ARGUMENTS: # config -- optional spamassassin config file # %RETURNS: # An array of four elements, # Weight of message ('hits') # Number of hits required before SA considers a message spam # Comma separated list of symbolic test names that were triggered # A 'report' string, detailing tests that failed and their weights # %DESCRIPTION: # Scans message using SpamAssassin (http://www.spamassassin.org) #*********************************************************************** sub spam_assassin_check { my($status) = spam_assassin_status(@_); return if (!defined($status)); my $hits = $status->get_hits; my $req = $status->get_required_hits(); my $tests = $status->get_names_of_tests_hit(); my $report = $status->get_report(); $status->finish(); return ($hits, $req, $tests, $report); } =item spam_assassin_status Method that scans a mmessage using SpamAssassin and returns a C object. The caller is responsible for calling the C method. =cut #*********************************************************************** # %PROCEDURE: spam_assassin_status # %ARGUMENTS: # config -- optional spamassassin config file # %RETURNS: # A Mail::SpamAssassin:PerMsgStatus object. # CALLER IS RESPONSIBLE FOR CALLING finish() # %DESCRIPTION: # Scans message using SpamAssassin (http://www.spamassassin.org) #*********************************************************************** sub spam_assassin_status { my $object = spam_assassin_init(@_); return unless $object; my $mail = spam_assassin_mail(); return unless $mail; my $status; push_status_tag("Running SpamAssassin"); $status = $object->check($mail); $mail->finish(); pop_status_tag(); return $status; } =item spam_assassin_init Initialize Apache SpamAssassin and returns a C object. =cut #*********************************************************************** # %PROCEDURE: spam_assassin_init # %ARGUMENTS: # config -- optional spamassassin config file # %RETURNS: # A Mail::SpamAssassin object. # %DESCRIPTION: # Scans message using SpamAssassin (http://www.spamassassin.org) #*********************************************************************** sub spam_assassin_init { my ($config) = @_; unless ($Features{"SpamAssassin"}) { md_syslog('err', "Attempt to call SpamAssassin function, but SpamAssassin is not installed."); return; } if (!defined($SASpamTester)) { if (!defined($config)) { if (-r $Features{'Path:CONFDIR'} . '/sa-mimedefang.cf') { $config = $Features{'Path:CONFDIR'} . '/sa-mimedefang.cf'; } elsif (-r $Features{'Path:CONFDIR'} . '/spamassassin/sa-mimedefang.cf') { $config = $Features{'Path:CONFDIR'} . '/spamassassin/sa-mimedefang.cf'; } elsif (-r $Features{'Path:CONFDIR'} . '/spamassassin/local.cf') { $config = $Features{'Path:CONFDIR'} . '/spamassassin/local.cf'; } else { $config = $Features{'Path:CONFDIR'} . '/spamassassin.cf'; } } push_status_tag("Creating SpamAssasin Object"); my $sa_args = { local_tests_only => $SALocalTestsOnly, dont_copy_prefs => 1, userprefs_filename => $config, user_dir => $Features{'Path:QUARANTINEDIR'}, }; $SASpamTester = Mail::SpamAssassin->new( $sa_args ); pop_status_tag(); } return $SASpamTester; } =item spam_assassin_mail Method that calls SpamAssassin and returns a C object. =cut #*********************************************************************** # %PROCEDURE: spam_assassin_mail # %ARGUMENTS: # none # %RETURNS: # A Mail::SpamAssassin::Message object #*********************************************************************** sub spam_assassin_mail { unless ($Features{"SpamAssassin"}) { md_syslog('err', "Attempt to call SpamAssassin function, but SpamAssassin is not installed."); return; } open(my $in, "<", "./INPUTMSG") or return; my @msg = <$in>; close($in); # Synthesize a "Return-Path" and "Received:" header my @sahdrs; push (@sahdrs, "Return-Path: $Sender\n"); push (@sahdrs, split(/^/m, synthesize_received_header())); if ($AddApparentlyToForSpamAssassin and ($#Recipients >= 0)) { push(@sahdrs, "Apparently-To: " . join(", ", @Recipients) . "\n"); } unshift (@msg, @sahdrs); if (!defined($SASpamTester)) { spam_assassin_init(@_); return unless $SASpamTester; } return $SASpamTester->parse(\@msg); } =item md_spamc_init Initialize Apache SpamAssassin and returns a C object. C and C subs should be used only with Apache SpamAssassin starting from version 4.0.1. =over 4 The sub returns a Mail::SpamAssassin::Client object. =back Optional parameters are SpamAssassin host, SpamAssassin port, the username to pass to SpamAssassin server and the maximum size of the email message. =cut #*********************************************************************** # %PROCEDURE: md_spamc_init # %ARGUMENTS: # SpamAssassin host -- defaults to localhost # SpamAssassin port -- defaults to 783 # SpamAssassin user # SpamAssassin maximum email size # %RETURNS: # A Mail::SpamAssassin::Client object. # %DESCRIPTION: # Scans message using SpamAssassin (http://www.spamassassin.org) #*********************************************************************** sub md_spamc_init { my ($host, $port, $spamc_user, $spamc_max_size) = @_; local $@; my $spamc; eval { require Mail::SpamAssassin::Client; $spamc = 1; }; if($@) { $spamc = 0; } else { Mail::SpamAssassin::Client->import(); } if($spamc eq 0) { md_syslog('err', "Attempt to call Apache SpamAssassin function, but Apache SpamAssassin is not installed."); return; } $host //= 'localhost'; $port //= 783; $spamc_max_size //= 0; $spamc_user //= getpwuid($<); my $client; eval { local $SIG{__WARN__} = sub { my $warn = $_[0]; $warn =~ s/\n//g; md_syslog("Warning", "md_spamc_init: $warn"); }; $client = Mail::SpamAssassin::Client->new({ host => $host, port => $port, max_size => $spamc_max_size, username => $spamc_user}); }; return $client; } =item md_spamc_check Method that scans the message using SpamAssassin Perl client and returns an array of four elemets: =over 4 =item * Weight of message ('score') =item * Number of hits required before Apache SpamAssassin considers a message spam =item * A 'report' string, detailing tests that failed and their weights =item * A flag explaining if the email is a spam message or not (true/false). =back Required parameters is a Mail::SpamAssassin::Client object initialized by calling C sub. =cut #*********************************************************************** # %PROCEDURE: md_spamc_check # %ARGUMENTS: # A Mail::SpamAssassin::Client object as returned by md_spamc_init # %RETURNS: # An array of four elements, # Weight of message ('score') # Number of hits required before Apache SpamAssassin considers a message spam # A 'report' string, detailing tests that failed and their weights # A flag is_spam true/false # The sub returns undef if the connection fails # %DESCRIPTION: # Scans message using Apache SpamAssassin (https://spamassassin.apache.org) #*********************************************************************** sub md_spamc_check { my ($saobj) = @_; if((not defined $saobj) or (ref($saobj) ne 'Mail::SpamAssassin::Client')) { md_syslog("Warning", "md_spamc_check: SpamAssassin client not initialized"); return; } open(my $in, "<", "./INPUTMSG") or return; my @msg = <$in>; close($in); # Synthesize a "Return-Path" and "Received:" header my @sahdrs; push (@sahdrs, "Return-Path: $Sender\n"); push (@sahdrs, split(/^/m, synthesize_received_header())); if ($AddApparentlyToForSpamAssassin and ($#Recipients >= 0)) { push(@sahdrs, "Apparently-To: " . join(", ", @Recipients) . "\n"); } unshift (@msg, @sahdrs); my $msg = join('', @msg); my $result; eval { local $SIG{__WARN__} = sub { my $warn = $_[0]; $warn =~ s/\n//g; md_syslog("Warning", "md_spamc_check: $warn"); }; $result = $saobj->spam_report($msg); }; if(defined $result and (ref($result) eq 'HASH')) { return ($result->{score}, $result->{threshold}, $result->{report}, $result->{isspam}); } else { return; } } =item rspamd_check Method that scans the message using Rspamd and returns an array of six elemets: =over 4 =item * Weight of message ('hits') =item * Number of hits required before Rspamd considers a message spam =item * Comma separated list of symbolic test names that were triggered =item * A 'report' string, detailing tests that failed and their weights or a Json report if JSON and LWP modules are present =item * An action that should be applied to the email =item * A flag explaining if the email is a spam message or not (true/false). =back An optional rspamd url can be passed to the method, its default value is http://127.0.0.1:11333. =cut #*********************************************************************** # %PROCEDURE: rspamd_check # %ARGUMENTS: # an Rspamd url -- defaults to http://127.0.0.1:11333 # %RETURNS: # An array of six elements, # Weight of message ('hits') # Number of hits required before Rspamd considers a message spam # Comma separated list of symbolic test names that were triggered # A 'report' string, detailing tests that failed and their weights # or a Json report if JSON and LWP modules are present # An action that should be applied to the email # A flag is_spam true/false # %DESCRIPTION: # Scans message using Rspamd (http://rspamd.org) #*********************************************************************** sub rspamd_check { my ($uri) = @_; my $rp; my ($hits, $req, $action, $is_spam); my $tests = ''; my $report = ''; $uri = 'http://127.0.0.1:11333' if not defined $uri; # Check if required modules are available local $@; my $rspamc; eval { require JSON; require LWP::UserAgent; $rspamc = 1; }; if($@) { $rspamc = 0; } else { JSON->import(); LWP::UserAgent->import(); } unless ($Features{"Path:RSPAMC"} or $rspamc = 1) { md_syslog('err', "Attempt to call Rspamd function, but Rspamd is not installed or JSON and LWP modules not available."); return; } # forking method is deprecated if($Features{"Path:RSPAMC"} and ($rspamc eq 0)) { md_syslog("Warning", "Using fork method to check Rspamd server (deprecated)"); $rspamc = 0; } if($rspamc eq 1) { my $ua = LWP::UserAgent->new; $ua->agent("MIMEDefang"); # slurp the mail message open my $fh, '<', "./INPUTMSG" or return; local $/; my $mail = <$fh>; close $fh; my $req = HTTP::Request->new(POST => "$uri/checkv2"); $req->content("$mail"); my $res = $ua->request($req); if ($res->is_success) { my $json = JSON->new->allow_nonref; my $rspamd_res = $json->decode( $res->content ); $hits = $rspamd_res->{score}; $req = $rspamd_res->{required_score}; $action = $rspamd_res->{action}; my %sym = %{$rspamd_res->{symbols}}; foreach my $test ( keys %sym ) { $tests .= $sym{$test}->{name} . " (" . $sym{$test}->{score} . "), "; } $tests =~ s/, $//; if($hits >= $req) { $is_spam = "true"; } else { $is_spam = "false"; } $report = $res->content; return ($hits, $req, $tests, $report, $action, $is_spam); } else { md_syslog("Warning", "Cannot connect to Rspamd server, the message will be temporarily delayed"); return (0, 0, '', '', "soft reject", "false"); } } else { my @rs = ($Features{"Path:RSPAMC"}, "./INPUTMSG"); if ( -f $Features{"Path:RSPAMC"} ) { open(my $rspamd_pipe, "-|", @rs) || croak "can't open rspamc: $!"; while(<$rspamd_pipe>) { $rp = $_; { if($rp =~ /Action: (.*)/) { $action = $1; } } { if($rp =~ /Spam: (.*)/) { $is_spam = $1; } } { if($rp =~ /Score: (.*) \/ (.*)/) { $hits = $1; $req = $2; } } { if($rp =~ /Symbol: (.*)/) { $tests .= $1 . ", "; } } $report .= $rp . "\n"; } $tests =~ s/\, $//; close($rspamd_pipe); } } return ($hits, $req, $tests, $report, $action, $is_spam); } =back =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/Antivirus.pm000066400000000000000000002777271475763067200230030ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::Antivirus - Antivirus interface methods for email filters =head1 DESCRIPTION Mail::MIMEDefang::Antivirus are a set of methods that can be called from F to scan with installed antivirus software the email message. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::Antivirus; use strict; use warnings; use IO::Socket; use Mail::MIMEDefang; require Exporter; our @ISA = qw(Exporter); our @EXPORT; our @EXPORT_OK; @EXPORT = qw(message_contains_virus entity_contains_virus initialize_virus_scanner_routines run_virus_scanner entity_contains_virus_avp entity_contains_virus_bdc entity_contains_virus_nai entity_contains_virus_avp5 entity_contains_virus_csav entity_contains_virus_fsav entity_contains_virus_clamd entity_contains_virus_fprot entity_contains_virus_hbedv entity_contains_virus_clamav entity_contains_virus_fprotd entity_contains_virus_fpscan entity_contains_virus_clamdscan entity_contains_virus_fprotd_v6 entity_contains_virus_kavscanner entity_contains_virus_carrier_scan message_contains_virus_avp message_contains_virus_bdc message_contains_virus_nai message_contains_virus_avp5 message_contains_virus_csav message_contains_virus_fsav message_contains_virus_clamd message_contains_virus_fprot message_contains_virus_hbedv message_contains_virus_nod32 message_contains_virus_clamav message_contains_virus_fprotd message_contains_virus_fpscan message_contains_virus_clamdscan message_contains_virus_fprotd_v6 message_contains_virus_kavscanner interpret_avp_code interpret_bdc_code interpret_nai_code interpret_avp5_code interpret_csav_code interpret_fsav_code interpret_nvcc_code interpret_fprot_code interpret_hbedv_code interpret_nod32_code interpret_sweep_code interpret_trend_code interpret_clamav_code interpret_clamav_code interpret_fpscan_code interpret_vexira_code interpret_savscan_code scan_file_using_fprotd_v6 scan_file_using_carrier_scan ); =item message_contains_virus Method that scans a message using every installed virus scanner. =cut #*********************************************************************** # %PROCEDURE: message_contains_virus # %ARGUMENTS: # None # %RETURNS: # ($code, $category, $action) -- standard virus-scanner return values. # %DESCRIPTION: # Scans message using *every single* installed virus scanner. #*********************************************************************** sub message_contains_virus { my($code, $category, $action); $code = 0; $category = 'ok'; $action = 'ok'; initialize_virus_scanner_routines(); if (!@VirusScannerMessageRoutines) { return (wantarray ? (0, 'ok', 'ok') : 0); } my ($scode, $scat, $sact); push_status_tag("Running virus scanner"); foreach my $scanner (@VirusScannerMessageRoutines) { ($scode, $scat, $sact) = &$scanner(); if ($scat eq "virus") { return (wantarray ? ($scode, $scat, $sact) : $scode); } if ($scat ne "ok") { $code = $scode; $category = $scat; $action = $sact; } } pop_status_tag(); return (wantarray ? ($code, $category, $action) : $code); } =item entity_contains_virus Method that scans a C part using every installed virus scanner. =cut #*********************************************************************** # %PROCEDURE: entity_contains_virus # %ARGUMENTS: # e -- a MIME::Entity # %RETURNS: # ($code, $category, $action) -- standard virus-scanner return values. # %DESCRIPTION: # Scans entity using *every single* installed virus scanner. #*********************************************************************** sub entity_contains_virus { my($e) = @_; my($code, $category, $action); $code = 0; $category = 'ok'; $action = 'ok'; initialize_virus_scanner_routines(); if (!@VirusScannerEntityRoutines) { return (wantarray ? (0, 'ok', 'ok') : 0); } my ($scode, $scat, $sact); push_status_tag("Running virus scanner"); foreach my $scanner (@VirusScannerEntityRoutines) { ($scode, $scat, $sact) = &$scanner($e); if ($scat eq "virus") { return (wantarray ? ($scode, $scat, $sact) : $scode); } if ($scat ne "ok") { $code = $scode; $category = $scat; $action = $sact; } } pop_status_tag(); return (wantarray ? ($code, $category, $action) : $code); } =item initialize_virus_scanner_routines Method that sets C<@VirusScannerMessageRoutines> and C<@VirusScannerEntityRoutines> to arrays of virus-scanner routines to call, based on installed scanners. =cut #*********************************************************************** # %PROCEDURE: initialize_virus_scanner_routines # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Sets @VirusScannerMessageRoutines and @VirusScannerEntityRoutines # to arrays of virus-scanner routines to call, based on installed # scanners. #*********************************************************************** sub initialize_virus_scanner_routines { if ($VirusScannerRoutinesInitialized) { return; } $VirusScannerRoutinesInitialized = 1; # The daemonized scanners first if ($Features{'Virus:CLAMD'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_clamd; push @VirusScannerEntityRoutines, \&entity_contains_virus_clamd; } if ($Features{'Virus:CLAMDSCAN'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_clamdscan; push @VirusScannerEntityRoutines, \&entity_contains_virus_clamdscan; } if ($Features{'Virus:SOPHIE'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_sophie; push @VirusScannerEntityRoutines, \&entity_contains_virus_sophie; } if ($Features{'Virus:TROPHIE'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_trophie; push @VirusScannerEntityRoutines, \&entity_contains_virus_trophie; } if ($Features{'Virus:SymantecCSS'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_carrier_scan; push @VirusScannerEntityRoutines, \&entity_contains_virus_carrier_scan; } if ($Features{'Virus:FPROTD'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_fprotd; push @VirusScannerEntityRoutines, \&entity_contains_virus_fprotd; } if ($Features{'Virus:FPROTD6'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_fprotd_v6; push @VirusScannerEntityRoutines, \&entity_contains_virus_fprotd_v6; } if ($Features{'Virus:AVP5'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_avp5; push @VirusScannerEntityRoutines, \&entity_contains_virus_avp5; } if ($Features{'Virus:KAVSCANNER'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_kavscanner; push @VirusScannerEntityRoutines, \&entity_contains_virus_kavscanner; } # Finally the command-line scanners if ($Features{'Virus:CLAMAV'} && ! $Features{'Virus:CLAMD'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_clamav; push @VirusScannerEntityRoutines, \&entity_contains_virus_clamav; } if ($Features{'Virus:AVP'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_avp; push @VirusScannerEntityRoutines, \&entity_contains_virus_avp; } if ($Features{'Virus:NAI'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_nai; push @VirusScannerEntityRoutines, \&entity_contains_virus_nai; } if ($Features{'Virus:FPROT'} && !$Features{'Virus:FPROTD'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_fprot; push @VirusScannerEntityRoutines, \&entity_contains_virus_fprot; } if ($Features{'Virus:FPSCAN'} && !$Features{'Virus:FPROTD6'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_fpscan; push @VirusScannerEntityRoutines, \&entity_contains_virus_fpscan; } if ($Features{'Virus:CSAV'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_csav; push @VirusScannerEntityRoutines, \&entity_contains_virus_csav; } if ($Features{'Virus:FSAV'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_fsav; push @VirusScannerEntityRoutines, \&entity_contains_virus_fsav; } if ($Features{'Virus:HBEDV'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_hbedv; push @VirusScannerEntityRoutines, \&entity_contains_virus_hbedv; } if ($Features{'Virus:BDC'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_bdc; push @VirusScannerEntityRoutines, \&entity_contains_virus_bdc; } if ($Features{'Virus:NVCC'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_nvcc; push @VirusScannerEntityRoutines, \&entity_contains_virus_nvcc; } if ($Features{'Virus:VEXIRA'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_vexira; push @VirusScannerEntityRoutines, \&entity_contains_virus_vexira; } if ($Features{'Virus:SOPHOS'} && ! $Features{'Virus:SOPHIE'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_sophos; push @VirusScannerEntityRoutines, \&entity_contains_virus_sophos; } if ($Features{'Virus:SAVSCAN'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_savscan; push @VirusScannerEntityRoutines, \&entity_contains_virus_savscan; } if ($Features{'Virus:TREND'} && ! $Features{'Virus:TROPHIE'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_trend; push @VirusScannerEntityRoutines, \&entity_contains_virus_trend; } if ($Features{'Virus:NOD32'}) { push @VirusScannerMessageRoutines, \&message_contains_virus_nod32; push @VirusScannerEntityRoutines, \&entity_contains_virus_nod32; } } #*********************************************************************** # %PROCEDURE: entity_contains_virus_nai # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by NAI uvscan; 0 otherwise. # %DESCRIPTION: # Runs the NAI Virus Scan program on the entity. (http://www.nai.com) #*********************************************************************** sub entity_contains_virus_nai { unless ($Features{'Virus:NAI'}) { md_syslog('err', "NAI Virus Scan not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run uvscan my($code, $category, $action) = run_virus_scanner($Features{'Virus:NAI'} . " --mime --noboot --secure --allole $path 2>&1", "Found"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # UVScan return codes return (wantarray ? interpret_nai_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_nai # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the NAI Virus Scan program on the working directory #*********************************************************************** sub message_contains_virus_nai { unless ($Features{'Virus:NAI'}) { md_syslog('err', "NAI Virus Scan not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run uvscan my($code, $category, $action) = run_virus_scanner($Features{'Virus:NAI'} . " --noboot --secure --mime --allole ./Work 2>&1", "Found"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # UVScan return codes return (wantarray ? interpret_nai_code($code) : $code); } sub interpret_nai_code { # Info from Anthony Giggins my($code) = @_; # OK return ($code, 'ok', 'ok') if ($code == 0); # Driver integrity check failed return ($code, 'swerr', 'tempfail') if ($code == 2); # "A general problem occurred" -- idiot Windoze programmers... return ($code, 'swerr', 'tempfail') if ($code == 6); # Could not find a driver return ($code, 'swerr', 'tempfail') if ($code == 8); # Scanner tried to clean a file, but it failed return ($code, 'swerr', 'tempfail') if ($code == 12); # Virus found if ($code == 13) { # Sigh... stupid NAI can't have a standard message. Go through # hoops to get virus name. my $cm = $CurrentVirusScannerMessage; $cm =~ s/ !+//; $cm =~ s/!+//; if ($VirusName eq "") { $VirusName = "EICAR-Test" if ($cm =~ m/Found: EICAR test file/i); } if ($VirusName eq "") { $VirusName = $1 if ($cm =~ m/^\s+Found the (\S+) .*virus/i); } if ($VirusName eq "") { $VirusName = $1 if ($cm =~ m/Found the (.*) trojan/i); } if ($VirusName eq "") { $VirusName = $1 if ($cm =~ m/Found .* or variant (.*)/i); } $VirusName = "unknown-NAI-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # Self-check failed return ($code, 'swerr', 'tempfail') if ($code == 19); # User quit using --exit-on-error return ($code, 'interrupted', 'tempfail') if ($code == 102); # Unknown exit code return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_bdc # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by Bitdefender; 0 otherwise. # %DESCRIPTION: # Runs the Bitdefender program on the entity. (http://www.bitdefender.com) #*********************************************************************** sub entity_contains_virus_bdc { unless($Features{'Virus:BDC'}) { md_syslog('err', "Bitdefender not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } if (! ($path =~ m+^/+)) { $path = $CWD . "/" . $path; } # Run bdc my($code, $category, $action) = run_virus_scanner($Features{'Virus:BDC'} . " $path --mail 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_bdc_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_bdc # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the Bitdefender program on the working directory #*********************************************************************** sub message_contains_virus_bdc { unless($Features{'Virus:BDC'}) { md_syslog('err', "Bitdefender not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run bdc my($code, $category, $action) = run_virus_scanner($Features{'Virus:BDC'} . " $CWD/Work --mail --arc 2>&1"); return (wantarray ? interpret_bdc_code($code) : $code); } sub interpret_bdc_code { my($code) = @_; # OK return ($code, 'ok', 'ok') if ($code == 0); # If code is not 0 or 1, it's an internal error return ($code, 'swerr', 'tempfail') if ($code != 1); # Code is 1 -- virus found. $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/(?:suspected|infected)\: (\S+)/); $VirusName = "unknown-BDC-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_csav # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by Command Anti-Virus # %DESCRIPTION: # Runs the Command Anti-Virus program. (http://www.commandsoftware.com) #*********************************************************************** sub entity_contains_virus_csav { unless($Features{'Virus:CSAV'}) { md_syslog('err', "Command Anti-Virus not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run csav my($code, $category, $action) = run_virus_scanner($Features{'Virus:CSAV'} . " $path 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # csav return codes return (wantarray ? interpret_csav_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_csav # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the Command Anti-Virus program on the working directory #*********************************************************************** sub message_contains_virus_csav { unless($Features{'Virus:CSAV'}) { md_syslog('err', "Command Anti-Virus not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run csav my($code, $category, $action) = run_virus_scanner($Features{'Virus:CSAV'} . " ./Work 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # csav return codes return (wantarray ? interpret_csav_code($code) : $code); } sub interpret_csav_code { my($code) = @_; # OK return ($code, 'ok', 'ok') if ($code == 50); # Interrupted return ($code, 'interrupted', 'tempfail') if ($code == 5); # Out of memory return ($code, 'swerr', 'tempfail') if ($code == 101); # Suspicious files found if ($code == 52) { $VirusName = 'suspicious'; return ($code, 'suspicious', 'quarantine'); } # Found a virus if ($code == 51) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/infec.*\: (\S+)/i); $VirusName = "unknown-CSAV-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # Found a virus and disinfected if ($code == 53) { $VirusName = "unknown-CSAV-virus disinfected"; return ($code, 'virus', 'quarantine'); } # Unknown exit code return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_fsav # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by F-Secure Anti-Virus # %DESCRIPTION: # Runs the F-Secure Anti-Virus program. (http://www.f-secure.com) #*********************************************************************** sub entity_contains_virus_fsav { unless($Features{'Virus:FSAV'}) { md_syslog('err', "F-Secure Anti-Virus not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run fsav my($code, $category, $action) = run_virus_scanner($Features{'Virus:FSAV'} . " --dumb --mime $path 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # fsav return codes return (wantarray ? interpret_fsav_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_fsav # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the F-Secure Anti-Virus program on the working directory #*********************************************************************** sub message_contains_virus_fsav { unless($Features{'Virus:FSAV'}) { md_syslog('err', "F-Secure Anti-Virus not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run fsav my($code, $category, $action) = run_virus_scanner($Features{'Virus:FSAV'} . " --dumb --mime ./Work 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # fsav return codes return (wantarray ? interpret_fsav_code($code) : $code); } sub interpret_fsav_code { # Info from David Green my($code) = @_; # OK return ($code, 'ok', 'ok') if ($code == 0); # Abnormal termination return ($code, 'swerr', 'tempfail') if ($code == 1); # Self-test failed return ($code, 'swerr', 'tempfail') if ($code == 2); # Found a virus if ($code == 3 or $code == 6) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/infec.*\: (\S+)/i); $VirusName = "unknown-FSAV-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # Interrupted return ($code, 'interrupted', 'tempfail') if ($code == 5); # Out of memory return ($code, 'swerr', 'tempfail') if ($code == 7); # Suspicious files found if ($code == 8) { $VirusName = 'suspicious'; return ($code, 'suspicious', 'quarantine'); } # Unknown exit code return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: scan_file_using_fprotd_v6 # %ARGUMENTS: # fname -- name of file to scan # host -- host and port on which FPROTD version 6 is listening, # eg 127.0.0.1:7777 # %RETURNS: # A (code, category, action) triplet. Sets VirusName if virus found. # %DESCRIPTION: # Asks FPROTD version 6 to scan a file. #*********************************************************************** sub scan_file_using_fprotd_v6 { my($fname, $hname) = @_; $hname ||= $Fprotd6Host; my($host, $port) = split(/:/, $hname); $host ||= '127.0.0.1'; $port ||= 10200; my $connect_timeout = 10; my $read_timeout = 60; # Convert path to absolute if (! ($fname =~ m+^/+)) { my($cwd); chomp($cwd = `pwd`); $fname = $cwd . "/" . $fname; } my $sock = IO::Socket::INET->new( PeerAddr => $host, PeerPort => $port, Timeout => $connect_timeout); unless (defined $sock) { md_syslog('warning', "Could not connect to FPROTD6 on $host: $!"); return (999, 'cannot-execute', 'tempfail'); } if (!$sock->print("SCAN --scanlevel=2 --archive=2 --heurlevel=2 --adware --applications FILE $fname\n") || !$sock->flush()) { md_syslog('warning', "Error writing to FPROTD6 on $host: $!"); $sock->close(); return (999, 'cannot-execute', 'tempfail'); } my $s = IO::Select->new($sock); if (!$s->can_read($read_timeout)) { $sock->close(); md_syslog('warning', "Timeout reading from FPROTD6 daemon on $host"); return (999, 'cannot-execute', 'tempfail'); } my $resp = $sock->getline(); $sock->close(); if (!$resp) { md_syslog('warning', "Did not get response from FPROTD6 on $host while scanning $fname"); return (999, 'cannot-execute', 'tempfail'); } my ($code, $desc, $name); unless (($code, $desc, $name) = $resp =~ /\A(\d+)\s<(.*?)>\s(.*)\Z/) { md_syslog('warning', "Failed to parse response from FPROTD6 for $fname: $resp"); return (999, 'cannot-execute', 'tempfail'); } # Clean up $desc $desc =~ s/\A(?:contains infected objects|infected):\s//i; # Our output should contain: # 1) A code. The code is a bitmask of: # bit num Meaning # 0 1 At least one virus-infected object was found (and remains). # 1 2 At least one suspicious (heuristic match) object was found (and remains). # 2 4 Interrupted by user. (SIGINT, SIGBREAK). # 3 8 Scan restriction caused scan to skip files (maxdepth directories, maxdepth archives, exclusion list, etc). # 4 16 Platform error (out of memory, real I/O errors, insufficient file permission etc.). # 5 32 Internal engine error (whatever the engine fails at) # 6 64 At least one object was not scanned (encrypted file, unsupported/unknown compression method, corrupted or invalid file). # 7 128 At least one object was disinfected (clean now) (treat same as virus for File::VirusScan) # # 2) The description, including virus name # # 3) The item name, incl. member of archive etc. We ignore # this for now. if($code & (1 | 2 | 128)) { $VirusName = $desc; $VirusName ||= 'unknown-FPROTD6-virus'; return ($code, 'virus', 'quarantine'); } elsif($code & 4) { md_syslog('warning', 'FPROTD6 scanning interrupted by user'); return ($code, 'interrupted', 'tempfail'); } elsif($code & 16) { md_syslog('warning', 'FPROTD6 platform error'); return ($code, 'swerr', 'tempfail'); } elsif($code & 32) { md_syslog('warning', 'FPROTD6 internal engine error'); return ($code, 'swerr', 'tempfail'); } return(0, 'ok', 'ok'); } #*********************************************************************** # %PROCEDURE: scan_file_using_carrier_scan # %ARGUMENTS: # fname -- name of file to scan # host -- host and port on which Carrier Scan is listening, eg 127.0.0.1:7777 # Can optionally have :local or :nonlocal appended to force # AVSCANLOCAL or AVSCAN # %RETURNS: # A (code, category, action) triplet. Sets VirusName if virus found. # %DESCRIPTION: # Asks Symantec CarrierScan Server to scan a file. #*********************************************************************** sub scan_file_using_carrier_scan { my($fname, $hname) = @_; my($host, $port, $local) = split(/:/, $hname); # If not specified, use local scanning for 127.0.0.1, remote for # any other. unless(defined($local)) { if ($host =~ /^127\.0\.0\.1/) { $local = 1; } else { $local = 0; } } # Convert from strings if ($local eq "local") { $local = 1; } if ($local eq "nonlocal") { $local = 0; } $port = 7777 unless defined($port); # Convert path to absolute if (! ($fname =~ m+^/+)) { my($cwd); chomp($cwd = `pwd`); $fname = $cwd . "/" . $fname; } my $sock = IO::Socket::INET->new("$host:$port"); my ($line); unless (defined $sock) { md_syslog('warning', "Could not connect to CarrierScan Server on $host: $!"); return (999, 'cannot-execute', 'tempfail'); } # Read first line of reply from socket chomp($line = $sock->getline); $line =~ s/\r//g; unless ($line =~ /^220/) { md_syslog('warning', "Unexpected reply $line from CarrierScan Server"); $sock->close; return (999, 'swerr', 'tempfail'); } # Next line must be version chomp($line = $sock->getline); $line =~ s/\r//g; unless ($line eq "2") { md_syslog('warning', "Unexpected version $line from CarrierScan Server"); $sock->close; return(999, 'swerr', 'tempfail'); } # Cool; send our stuff! my $in; if ($local) { if (!$sock->print("Version 2\nAVSCANLOCAL\n$fname\n")) { $sock->close; return (999, 'swerr', 'tempfail'); } } else { my ($size); my ($chunk); my ($chunksize, $nread); $size = (stat($fname))[7]; unless(defined($size)) { md_syslog('warning', "Cannot stat $fname: $!"); $sock->close; return(999, 'swerr', 'tempfail'); } if (!$sock->print("Version 2\nAVSCAN\n$fname\n$size\n")) { $sock->close; return (999, 'swerr', 'tempfail'); } unless(open($in, "<", "$fname")) { md_syslog('warning', "Cannot open $fname: $!"); $sock->close; return(999, 'swerr', 'tempfail'); } while ($size > 0) { if ($size < 8192) { $chunksize = $size; } else { $chunksize = 8192; } $nread = read($in, $chunk, $chunksize); unless(defined($nread)) { md_syslog('warning', "Error reading $fname: $!"); $sock->close; close($in); return(999, 'swerr', 'tempfail'); } last if ($nread == 0); if (!$sock->print($chunk)) { $sock->close; close($in); return (999, 'swerr', 'tempfail'); } $size -= $nread; } if ($size > 0) { md_syslog('warning', "Error reading $fname: $!"); $sock->close; close($in); return(999, 'swerr', 'tempfail'); } } if (!$sock->flush) { $sock->close; close($in); return (999, 'swerr', 'tempfail'); } # Get reply from server chomp($line = $sock->getline); $line =~ s/\r//g; unless ($line =~ /^230/) { md_syslog('warning', "Unexpected response to AVSCAN or AVSCANLOCAL command: $line"); $sock->close; close($in); return(999, 'swerr', 'tempfail'); } # Get infection status chomp($line = $sock->getline); $line =~ s/\r//g; if ($line == 0) { $sock->close; close($in); return (0, 'ok', 'ok'); } # Skip definition date and version, infection count and filename chomp($line = $sock->getline); # Definition date chomp($line = $sock->getline); # Definition version chomp($line = $sock->getline); # Infection count (==1) chomp($line = $sock->getline); # Filename # Get virus name chomp($line = $sock->getline); $line =~ s/\r//g; close($in); $sock->close; $VirusName = $line; return (1, 'virus', 'quarantine'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_carrier_scan # %ARGUMENTS: # entity -- a MIME entity # host (optional) -- Symantec CarrierScan host:port # %RETURNS: # Usual virus status # %DESCRIPTION: # Scans the entity using Symantec CarrierScan #*********************************************************************** sub entity_contains_virus_carrier_scan { my($entity) = shift; my($host) = $CSSHost; $host = shift if (@_ > 0); $host = '127.0.0.1:7777:local' if (!defined($host)); if (!defined($entity->bodyhandle)) { return (wantarray ? (0, 'ok', 'ok') : 0); } if (!defined($entity->bodyhandle->path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } return scan_file_using_carrier_scan($entity->bodyhandle->path, $host); } sub entity_contains_virus_fprotd_v6 { my($entity, $host) = @_; $host ||= $Fprotd6Host; if (!defined($entity->bodyhandle)) { return (wantarray ? (0, 'ok', 'ok') : 0); } if (!defined($entity->bodyhandle->path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } return scan_file_using_fprotd_v6($entity->bodyhandle->path, $host); } sub message_contains_virus_fprotd_v6 { my($host) = @_; $host ||= $Fprotd6Host; my $dir; if (!opendir($dir, "./Work")) { md_syslog('err', "message_contains_virus_fprotd_v6: Could not open ./Work directory: $!"); return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Scan all files in Work my(@files); @files = grep { -f "./Work/$_" } readdir($dir); closedir($dir); my($code, $category, $action); foreach my $file (@files) { ($code, $category, $action) = scan_file_using_fprotd_v6("Work/$file", $host); if ($code != 0) { return (wantarray ? ($code, $category, $action) : $code); } } return (0, 'ok', 'ok'); } #*********************************************************************** # %PROCEDURE: message_contains_virus_carrier_scan # %ARGUMENTS: # host (optional) -- Symantec CarrierScan host:port # %RETURNS: # Usual virus status # %DESCRIPTION: # Scans the entity using Symantec CarrierScan #*********************************************************************** sub message_contains_virus_carrier_scan { my($host) = $CSSHost; $host = shift if (@_ > 0); $host = '127.0.0.1:7777:local' if (!defined($host)); my $dir; if (!opendir($dir, "./Work")) { md_syslog('err', "message_contains_virus_carrier_scan: Could not open ./Work directory: $!"); return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Scan all files in Work my(@files); @files = grep { -f "./Work/$_" } readdir($dir); closedir($dir); my($code, $category, $action); foreach my $file (@files) { ($code, $category, $action) = scan_file_using_carrier_scan("Work/$file", $host); if ($code != 0) { return (wantarray ? ($code, $category, $action) : $code); } } return (0, 'ok', 'ok'); } #*********************************************************************** # %PROCEDURE: item_contains_virus_fprotd # %ARGUMENTS: # item -- a file or directory # host (optional) -- Fprotd host and base port. # %RETURNS: # Usual virus status # %DESCRIPTION: # Scans the entity using Fprotd scanning daemon #*********************************************************************** sub item_contains_virus_fprotd { my $item = shift; my ($host) = $FprotdHost; $host = shift if (@_ > 0); $host = '127.0.0.1' if (!defined($host)); my $baseport = 10200; if($host =~ /(.*):(.*)/ ) { $host = $1; $baseport = $2; } md_syslog('info', "Scan '$item' via F-Protd \@$host:$baseport"); # The F-Prot demon cannot scan directories, but files only # hence, we recurse any directories manually if(-d $item) { my @result; $host .= ":$baseport"; foreach my $entry (glob("$item/*")) { @result = &item_contains_virus_fprotd($entry, $host); last if $result[0] != 0; } return (wantarray ? @result : $result[0]); } # Default error message when reaching end of function my $errmsg = "Could not connect to F-Prot Daemon at $host:$baseport"; # Try 5 ports in order to find an active scanner; they may change the port # when they find and spawn an updated demon executable SEARCH_DEMON: foreach my $port ($baseport..($baseport+4)) { my $sock = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port); if (defined $sock) { # The arguments (following the '?' sign in the HTTP request) # are the same as for the command line F-Prot, the additional # -remote-dtd suppresses the useless XML DTD prefix if (!$sock->print("GET $item?-dumb%20-archive%20-packed%20-remote-dtd HTTP/1.0\n\n")) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 999); } if (!$sock->flush) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 999); } # Fetch HTTP Header ## Maybe dropped, if no validation checks are to be made while(my $output = $sock->getline) { if($output =~ /^\s*$/) { last; # break line for XML content #### Below here: Validating the protocol #### If the protocol is not recognized, it's assumed that the #### endpoint is not an F-Prot demon, hence, #### the next port is probed. } elsif($output =~ /^HTTP(.*)/) { my $h = $1; next SEARCH_DEMON unless $h =~ m!/1\.0\s+200\s!; } elsif($output =~ /^Server:\s*(\S*)/) { next SEARCH_DEMON if $1 !~ /^fprotd/; } } # Parsing XML results my $xml = HTML::TokeParser->new($sock); my $t = $xml->get_tag('fprot-results'); unless($t) { # This is an essential tag --> assume a broken demon $errmsg = 'Demon did not return tag'; last SEARCH_DEMON; } if($t->[1]{'version'} ne '1.0') { $errmsg = "Incompatible F-Protd results version: " . $t->[1]{'version'}; last SEARCH_DEMON; } my $curText; # temporarily accumulated information my $virii = ''; # name(s) of virus(es) found my $code; # overall exit code my $msg = ''; # accumulated message of virus scanner while( $t = $xml->get_token ) { my $tag = $t->[1]; if($t->[0] eq 'S') { # Start tag # Accumulate the information temporarily # into $curText until the tag is found my $text = $xml->get_trimmed_text; # $tag 'filename' of no use in MIMEDefang if($tag eq 'name') { $virii .= (length $virii ? " " : "" ) . $text; $curText .= "Found the virus: '$text'\n"; } elsif($tag eq 'accuracy' || $tag eq 'disinfectable' || $tag eq 'message') { $curText .= "\t$tag: $text\n"; } elsif($tag eq 'error') { $msg .= "\nError: $text\n"; } elsif($tag eq 'summary') { $code = $t->[2]{'code'} if defined $t->[2]{'code'}; } } elsif($t->[0] eq 'E') { # End tag if($tag eq 'detected') { # move the cached information to the # accumulated message $msg .= "\n$curText" if $curText; undef $curText; } elsif($tag eq 'fprot-results') { last; # security check } } } $sock->close; ## Check the exit code (man f-protd) ## NOTE: These codes are different from the ones of the command line version! # 0 Not scanned, unable to handle the object. # 1 Not scanned due to an I/O error. # 2 Not scanned, as the scanner ran out of memory. # 3 X The object is not of a type the scanner knows. This # may either mean it was misidentified or that it is # corrupted. # 4 X The object was valid, but encrypted and could not # be scanned. # 5 Scanning of the object was interrupted. # 7 X The object was identified as an "innocent" object. # 9 X The object was successfully scanned and nothing was # found. # 11 The object is infected. # 13 The object was disinfected. unless(defined $code) { $errmsg = "No summary code found"; last SEARCH_DEMON; } if($code < 3 # I/O error, unable to handle, out of mem # any filesystem error less than zero || $code == 5) { # interrupted ## assume this a temporary failure $errmsg = "Scan error #$code: $msg"; last SEARCH_DEMON; } if($code > 10) { # infected; (disinfected: Should never happen!) # Add the accumulated information $VirusScannerMessages .= $msg; if ( length $virii ) { $VirusName = $virii; } elsif ( $msg =~ /^\tmessage:\s+(\S.*)/m ) { $VirusName = $1; } else { # no virus name found, log message returned by fprot $msg =~ s/\s+/ /g; md_syslog('info', qq[$MsgID: cannot extract virus name from f-prot: "$msg"]); $VirusName = "unknown"; } return (wantarray ? (1, 'virus', 'quarantine') : 1); } ###### These codes are left to be handled: # 3 X The object is not of a type the scanner knows. This # may either mean it was misidentified or that it is # corrupted. # 4 X The object was valid, but encrypted and could not # be scanned. # 7 X The object was identified as an "innocent" object. # 9 X The object was successfully scanned and nothing was # 9 is trivial; 7 is probably trivial # 4 & 3 we can't do anything really, because if the attachment # is some unknown archive format, the scanner wouldn't had known # this issue anyway, hence, I consider it "clean" return (wantarray ? (0, 'ok', 'ok') : 0); } } # Could not connect to daemon or some error occurred during the # communication with it $errmsg =~ s/\s*\.*\s*\n+\s*/\. /g; md_syslog('err', "$errmsg"); return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_fprotd # %ARGUMENTS: # entity -- a MIME entity # host (optional) -- F-Prot Demon host:port # %RETURNS: # 1 if entity contains a virus as reported by F-Prot Demon # %DESCRIPTION: # Invokes the F-Prot daemon (http://www.frisk.org/) on # the entity. #*********************************************************************** sub entity_contains_virus_fprotd { my ($entity) = shift; if (!defined($entity->bodyhandle)) { return (wantarray ? (0, 'ok', 'ok') : 0); } if (!defined($entity->bodyhandle->path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } my $path = $entity->bodyhandle->path; # If path is not absolute, add cwd if (! ($path =~ m+^/+)) { $path = $CWD . "/" . $path; } return item_contains_virus_fprotd($path, $_[0]); } #*********************************************************************** # %PROCEDURE: message_contains_virus_fprotd # %ARGUMENTS: # host (optional) -- F-Prot Demon host:port # %RETURNS: # 1 if entity contains a virus as reported by F-Prot Demon # %DESCRIPTION: # Invokes the F-Prot daemon (http://www.frisk.org/) on # the entire message. #*********************************************************************** sub message_contains_virus_fprotd { return item_contains_virus_fprotd ("$CWD/Work", $_[0]); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_hbedv # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by H+BEDV Antivir; 0 otherwise. # %DESCRIPTION: # Runs the H+BEDV Antivir program on the entity. (http://www.hbedv.com) #*********************************************************************** sub entity_contains_virus_hbedv { unless($Features{'Virus:HBEDV'}) { md_syslog('err', "H+BEDV not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run antivir my($code, $category, $action) = run_virus_scanner($Features{'Virus:HBEDV'} . " --allfiles -z -rs $path 2>&1", "!Virus!|>>>|VIRUS:|ALERT:"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_hbedv_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_hbedv # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the H+BEDV Antivir program on the working directory #*********************************************************************** sub message_contains_virus_hbedv { unless($Features{'Virus:HBEDV'}) { md_syslog('err', "H+BEDV not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run antivir my($code, $category, $action) = run_virus_scanner($Features{'Virus:HBEDV'} . " --allfiles -z -rs ./Work 2>&1", "!Virus!|>>>|VIRUS:|ALERT:"); return (wantarray ? interpret_hbedv_code($code) : $code); } sub interpret_hbedv_code { # Based on info from Nels Lindquist, updated by # Thorsten Schlichting my($code) = @_; # OK return ($code, 'ok', 'ok') if ($code == 0); # Virus or virus in memory if ($code == 1 || $code == 2 || $code == 3) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/ALERT: \[(\S+)/ or $CurrentVirusScannerMessage =~ /!Virus! \S+ (\S+)/ or $CurrentVirusScannerMessage =~ m/VIRUS: file contains code of the virus '(\S+)'/); $VirusName = "unknown-HBEDV-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # All other codes should not happen md_syslog('err', "Unknown HBEDV Virus scanner return code: $code"); return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_vexira # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by Vexira; 0 otherwise. # %DESCRIPTION: # Runs the Vexira program on the entity. (http://www.centralcommand.com) #*********************************************************************** sub entity_contains_virus_vexira { unless($Features{'Virus:VEXIRA'}) { md_syslog('err', "Vexira not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run vexira my($code, $category, $action) = run_virus_scanner($Features{'Virus:VEXIRA'} . " -qqq --log=/dev/null --all-files -as $path 2>&1", ": (virus|iworm|macro|mutant|sequence|trojan) "); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_vexira_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_vexira # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the Vexira program on the working directory #*********************************************************************** sub message_contains_virus_vexira { unless($Features{'Virus:VEXIRA'}) { md_syslog('err', "Vexira not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run vexira my($code, $category, $action) = run_virus_scanner($Features{'Virus:VEXIRA'} . " -qqq --log=/dev/null --all-files -as ./Work 2>&1", ": (virus|iworm|macro|mutant|sequence|trojan) "); return (wantarray ? interpret_vexira_code($code) : $code); } sub interpret_vexira_code { # http://www.centralcommand.com/ts/dl/pdf/scanner_en_vexira.pdf my($code) = @_; # OK or new file type we don't understand return ($code, 'ok', 'ok') if ($code == 0 or $code == 9); # Password-protected ZIP or corrupted file if ($code == 3 or $code == 5) { $VirusName = 'vexira-password-protected-zip'; return ($code, 'suspicious', 'quarantine'); } # Virus if ($code == 1 or $code == 2) { $VirusName = $2 if ($CurrentVirusScannerMessage =~ m/: (virus|iworm|macro|mutant|sequence|trojan) (\S+)/); $VirusName = "unknown-Vexira-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # All other codes should not happen return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_sophos # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by Sophos Sweep # %DESCRIPTION: # Runs the Sophos Sweep program on the entity. #*********************************************************************** sub entity_contains_virus_sophos { unless($Features{'Virus:SOPHOS'}) { md_syslog('err', "Sophos Sweep not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run antivir my($code, $category, $action) = run_virus_scanner($Features{'Virus:SOPHOS'} . " -f -mime -all -archive -ss $path 2>&1", "(>>> Virus)|(Password)|(Could not check)"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_sweep_code($code) : $code); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_savscan # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by Sophos Savscan # %DESCRIPTION: # Runs the Sophos Savscan program on the entity. #*********************************************************************** sub entity_contains_virus_savscan { unless($Features{'Virus:SAVSCAN'}) { md_syslog('err', "Sophos Savscan not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run antivir my($code, $category, $action) = run_virus_scanner($Features{'Virus:SAVSCAN'} . " -f -mime -all -cab -oe -tnef -archive -ss $path 2>&1", "(>>> Virus)|(Password)|(Could not check)"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_savscan_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_sophos # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the Sophos Sweep program on the working directory #*********************************************************************** sub message_contains_virus_sophos { unless($Features{'Virus:SOPHOS'}) { md_syslog('err', "Sophos Sweep not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run antivir my($code, $category, $action) = run_virus_scanner($Features{'Virus:SOPHOS'} . " -f -mime -all -archive -ss ./Work 2>&1", "(>>> Virus)|(Password)|(Could not check)"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_sweep_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_savscan # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the Sophos Savscan program on the working directory #*********************************************************************** sub message_contains_virus_savscan { unless($Features{'Virus:SAVSCAN'}) { md_syslog('err', "Sophos Savscan not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run antivir my($code, $category, $action) = run_virus_scanner($Features{'Virus:SAVSCAN'} . " -f -mime -all -cab -oe -tnef -archive -ss ./Work 2>&1", "(>>> Virus)|(Password)|(Could not check)"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_savscan_code($code) : $code); } sub interpret_sweep_code { # Based on info from Nicholas Brealey my($code) = @_; # OK return ($code, 'ok', 'ok') if ($code == 0); # Interrupted return ($code, 'interrupted', 'tempfail') if ($code == 1); # This is technically an error code, but Sophos chokes # on a lot of M$ docs with this code, so we let it through... return (0, 'ok', 'ok') if ($code == 2); # Virus if ($code == 3) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/^\s*>>> Virus '(\S+)'/); $VirusName = "unknown-Sweep-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # Unknown code return ($code, 'swerr', 'tempfail'); } sub interpret_savscan_code { # Based on info from Nicholas Brealey my($code) = @_; # OK return ($code, 'ok', 'ok') if ($code == 0); # Interrupted return ($code, 'interrupted', 'tempfail') if ($code == 1); # This is technically an error code, but Sophos chokes # on a lot of M$ docs with this code, so we let it through... return (0, 'ok', 'ok') if ($code == 2); # Virus if ($code == 3) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/^\s*>>> Virus '(\S+)'/); $VirusName = "unknown-Savscan-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # Unknown code return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_clamav # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by clamav # %DESCRIPTION: # Runs the clamav program on the entity. #*********************************************************************** sub entity_contains_virus_clamav { unless ($Features{'Virus:CLAMAV'}) { md_syslog('err', "clamav not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run clamscan my($code, $category, $action) = run_virus_scanner($Features{'Virus:CLAMAV'} . " --stdout --no-summary --infected $path 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_clamav_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_clamav # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the clamscan program on the working directory #*********************************************************************** sub message_contains_virus_clamav { unless ($Features{'Virus:CLAMAV'}) { md_syslog('err', "clamav not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run clamscan my($code, $category, $action) = run_virus_scanner($Features{'Virus:CLAMAV'} . " -r --stdout --no-summary --infected ./Work 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_clamav_code($code) : $code); } sub interpret_clamav_code { my($code) = @_; # From info obtained from: # clamscan(1) # OK return ($code, 'ok', 'ok') if ($code == 0); # virus found if ($code == 1) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/: (.+) FOUND/); $VirusName = "unknown-Clamav-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # other codes return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_clamdscan # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by clamdscan # %DESCRIPTION: # Runs the clamdscan program on the entity. #*********************************************************************** sub entity_contains_virus_clamdscan { unless ($Features{'Virus:CLAMDSCAN'}) { md_syslog('err', "clamav not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run clamdscan my($code, $category, $action) = run_virus_scanner($Features{'Virus:CLAMDSCAN'} . " -c " . $Features{'Path:CLAMDCONF'} . " --no-summary --infected --fdpass --stream $path 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_clamav_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_clamdscan # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the clamdscan program on the working directory #*********************************************************************** sub message_contains_virus_clamdscan { unless ($Features{'Virus:CLAMDSCAN'}) { md_syslog('err', "clamav not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run clamdscan my($code, $category, $action) = run_virus_scanner($Features{'Virus:CLAMDSCAN'} . " -c " . $Features{'Path:CLAMDCONF'} . " --no-summary --infected --fdpass --stream ./Work 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_clamav_code($code) : $code); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_avp5 # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by Kaspersky 5.x # %DESCRIPTION: # Runs the Kaspersky 5.x aveclient program on the entity. #*********************************************************************** sub entity_contains_virus_avp5 { unless ($Features{'Virus:AVP5'}) { md_syslog('err', "Kaspersky aveclient not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run aveclient my($code, $category, $action) = run_virus_scanner($Features{'Virus:AVP5'} . " -s -p /var/run/aveserver $path 2>&1","INFECTED"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_avp5_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_avp5 # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the Kaspersky 5.x aveclient program on the working directory #*********************************************************************** sub message_contains_virus_avp5 { unless ($Features{'Virus:AVP5'}) { md_syslog('err', "Kaspersky aveclient not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run aveclient my($code, $category, $action) = run_virus_scanner($Features{'Virus:AVP5'} . " -s -p /var/run/aveserver $CWD/Work/* 2>&1","INFECTED"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_avp5_code($code) : $code); } sub interpret_avp5_code { my($code) = @_; # From info obtained from: # man aveclient (/opt/kav/man/aveclient.8) # OK return ($code, 'ok', 'ok') if ($code == 0); # Scan incomplete return ($code, 'interrupted', 'tempfail') if ($code == 1); # "modified or damaged virus" = 2; virus = 4 if ($code == 2 or $code == 4) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/INFECTED (\S+)/); $VirusName = "unknown-AVP5-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # "suspicious" object found if ($code == 3) { $VirusName = 'suspicious'; return ($code, 'suspicious', 'quarantine'); } # Disinfected ?? return ($code, 'ok', 'ok') if ($code == 5); # Viruses deleted ?? return ($code, 'ok', 'ok') if ($code == 6); # AVPLinux corrupt or infected return ($code, 'swerr', 'tempfail') if ($code == 7); # Corrupt objects found -- treat as suspicious if ($code == 8) { $VirusName = 'suspicious'; return ($code, 'suspicious', 'quarantine'); } # Anything else shouldn't happen return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_kavscanner # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by Kaspersky kavscanner # %DESCRIPTION: # Runs the Kaspersky kavscanner program on the entity. #*********************************************************************** sub entity_contains_virus_kavscanner { unless ($Features{'Virus:KAVSCANNER'}) { md_syslog('err', "Kaspersky kavscanner not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run kavscanner my($code, $category, $action) = run_virus_scanner($Features{'Virus:KAVSCANNER'} . " -e PASBME -o syslog -i0 $path 2>&1", "INFECTED"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_kavscanner_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_kavscanner # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the Kaspersky 5.x aveclient program on the working directory #*********************************************************************** sub message_contains_virus_kavscanner { unless ($Features{'Virus:KAVSCANNER'}) { md_syslog('err', "Kaspersky aveclient not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run kavscanner my($code, $category, $action) = run_virus_scanner($Features{'Virus:KAVSCANNER'} . " -e PASBME -o syslog -i0 $CWD/Work/* 2>&1", "INFECTED"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_kavscanner_code($code) : $code); } sub interpret_kavscanner_code { my($code) = @_; # From info obtained from: # man kavscanner (/opt/kav/man/kavscanner.8) # OK return ($code, 'ok', 'ok') if ($code == 0 or $code == 5 or $code == 10); # Password-protected ZIP if ($code == 9) { $VirusName = 'kavscanner-password-protected-zip'; return ($code, 'suspicious', 'quarantine'); } # Virus or suspicious TODO: Set virus name if ($code == 20 or $code == 21 or $code == 25) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/INFECTED (\S+)/); $VirusName = 'unknown-kavscanner-virus' if $VirusName eq ""; if ($code == 20) { return ($code, 'suspicious', 'quarantine'); } else { return ($code, 'virus', 'quarantine'); } } # Something else return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_avp # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by AVP AvpLinux # %DESCRIPTION: # Runs the AvpLinux program on the entity. #*********************************************************************** sub entity_contains_virus_avp { unless ($Features{'Virus:AVP'}) { md_syslog('err', "AVP AvpLinux not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($is_daemon); $is_daemon = ($Features{'Virus:AVP'} =~ /kavdaemon$/); my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run antivir my($code, $category, $action); if ($is_daemon) { # If path is not absolute, add cwd if (! ($path =~ m+^/+)) { $path = $CWD . "/" . $path; } ($code, $category, $action) = run_virus_scanner($Features{'Virus:AVP'} . " $CWD -o{$path} -dl -Y -O- -K -I0 -WU=$CWD/DAEMON.RPT 2>&1", "infected"); } else { ($code, $category, $action) = run_virus_scanner($Features{'Virus:AVP'} . " -Y -O- -K -I0 $path 2>&1", "infected"); } if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_avp_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_avp # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the AVP AvpLinux program on the working directory #*********************************************************************** sub message_contains_virus_avp { unless ($Features{'Virus:AVP'}) { md_syslog('err', "AVP AvpLinux not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($is_daemon); $is_daemon = ($Features{'Virus:AVP'} =~ /kavdaemon$/); # Run antivir my($code, $category, $action); if ($is_daemon) { ($code, $category, $action) = run_virus_scanner($Features{'Virus:AVP'} . " $CWD -o{$CWD/Work} -dl -Y -O- -K -I0 -WU=$CWD/DAEMON.RPT 2>&1", "infected"); } else { ($code, $category, $action) = run_virus_scanner($Features{'Virus:AVP'} . " -Y -O- -K -I0 ./Work 2>&1", "infected"); } if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_avp_code($code) : $code); } sub interpret_avp_code { my($code) = @_; # From info obtained from: # http://sm.msk.ru/patches/violet-avp-sendmail-11.4.patch # and from Steve Ladendorf # OK return ($code, 'ok', 'ok') if ($code == 0); # Scan incomplete return ($code, 'interrupted', 'tempfail') if ($code == 1); # "modified or damaged virus" = 2; virus = 4 if ($code == 2 or $code == 4) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/infected\: (\S+)/); $VirusName = "unknown-AVP-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # "suspicious" object found if ($code == 3) { $VirusName = 'suspicious'; return ($code, 'suspicious', 'quarantine'); } # Disinfected ?? return ($code, 'ok', 'ok') if ($code == 5); # Viruses deleted ?? return ($code, 'ok', 'ok') if ($code == 6); # AVPLinux corrupt or infected return ($code, 'swerr', 'tempfail') if ($code == 7); # Corrupt objects found -- treat as suspicious if ($code == 8) { $VirusName = 'suspicious'; return ($code, 'suspicious', 'quarantine'); } # Anything else shouldn't happen return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_fprot # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by FRISK F-Prot; 0 otherwise. # %DESCRIPTION: # Runs the F-PROT program on the entity. (http://www.f-prot.com) #*********************************************************************** sub entity_contains_virus_fprot { unless ($Features{'Virus:FPROT'}) { md_syslog('err', "F-RISK FPROT not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run f-prot my($code, $category, $action) = run_virus_scanner($Features{'Virus:FPROT'} . " -DUMB -ARCHIVE -PACKED $path 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # f-prot return codes return (wantarray ? interpret_fprot_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_fprot # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the F-RISK f-prot program on the working directory #*********************************************************************** sub message_contains_virus_fprot { unless ($Features{'Virus:FPROT'}) { md_syslog('err', "F-RISK f-prot not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run f-prot my($code, $category, $action) = run_virus_scanner($Features{'Virus:FPROT'} . " -DUMB -ARCHIVE -PACKED ./Work 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # f-prot return codes return (wantarray ? interpret_fprot_code($code) : $code); } sub interpret_fprot_code { # Info from my($code) = @_; # OK return ($code, 'ok', 'ok') if ($code == 0); # Unrecoverable error (Missing DAT, etc) return ($code, 'swerr', 'tempfail') if ($code == 1); # Driver integrity check failed return ($code, 'swerr', 'tempfail') if ($code == 2); # Virus found if ($code == 3) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/Infection\: (\S+)/); $VirusName = "unknown-FPROT-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # Reserved for now. Treat as an error return ($code, 'swerr', 'tempfail') if ($code == 4); # Abnormal termination (scan didn't finish) return ($code, 'swerr', 'tempfail') if ($code == 5); # At least one virus removed - Should not happen as we aren't # requesting disinfection ( at least in this version). return ($code, 'swerr', 'tempfail') if ($code == 6); # Memory error return ($code, 'swerr', 'tempfail') if ($code == 7); # Something suspicious was found, but not recognized virus # ( uncomment the one your paranoia dictates :) ). # return ($code, 'virus', 'quarantine') if ($code == 8); return ($code, 'ok', 'ok') if ($code == 8); # Unknown exit code return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_fpscan # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by FRISK F-Prot; 0 otherwise. # %DESCRIPTION: # Runs the F-PROT program on the entity. (http://www.f-prot.com) #*********************************************************************** sub entity_contains_virus_fpscan { unless ($Features{'Virus:FPSCAN'}) { md_syslog('err', "F-RISK fpscan not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run f-prot my($code, $category, $action) = run_virus_scanner($Features{'Virus:FPSCAN'} . " --report --archive=5 --scanlevel=4 --heurlevel=3 $path 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # f-prot return codes return (wantarray ? interpret_fpscan_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_fpscan # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the F-RISK f-prot program on the working directory #*********************************************************************** sub message_contains_virus_fpscan { unless ($Features{'Virus:FPSCAN'}) { md_syslog('err', "F-RISK fpscan not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run f-prot my($code, $category, $action) = run_virus_scanner($Features{'Virus:FPSCAN'} . " --report --archive=5 --scanlevel=4 --heurlevel=3 ./Work 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # f-prot return codes return (wantarray ? interpret_fpscan_code($code) : $code); } sub interpret_fpscan_code { # Info from my($code) = @_; # Set to 1 to mark heuristic matches as a virus my $heuristic_virus = 0; # OK return ($code, 'ok', 'ok') if ($code == 0); # bit 1 (1) ==> At least one virus-infected object was found (and # remains). if ($code & 0b1) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/^\[Found\s+[^\]]*\]\s+<([^ \t\(>]*)/m); $VirusName = "unknown-FPSCAN-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } if ($heuristic_virus and $code & 0b10) { return ($code, 'virus', 'quarantine'); } # bit 3 (4) ==> Interrupted by user (SIGINT, SIGBREAK). if ($code & 0b100) { return ($code, 'swerr', 'tempfail'); } # bit 4 (8) ==> Scan restriction caused scan to skip files # (maxdepth directories, maxdepth archives, # exclusion list, etc). if ($code & 0b1000) { return ($code, 'swerr', 'tempfail'); } # bit 5 (16) ==> Platform error (out of memory, real I/O errors, # insufficient file permission etc.) if ($code & 0b10000) { return ($code, 'swerr', 'tempfail'); } # bit 6 (32) ==> Internal engine error (whatever the engine fails # at) if ($code & 0b100000) { return ($code, 'swerr', 'tempfail'); } # bit 7 (64) ==> At least one object was not scanned (encrypted # file, unsupported/unknown compression method, # corrupted or invalid file). if ($code & 0b1000000) { return ($code, 'swerr', 'tempfail'); } # bit 8 (128) ==> At least one object was disinfected (clean now). # Should not happen as we aren't requesting disinfection ( at least # in this version). if ($code & 0b10000000) { return ($code, 'swerr', 'tempfail'); } # bit 2 (2) ==> At least one suspicious (heuristic match) object # was found (and remains). if ($code & 0b10) { # ( uncomment the one your paranoia dictates :) ). return ($code, 'ok', 'ok'); } # Unknown exit code, this should never happen return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_trend # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by Trend Micro vscan # %DESCRIPTION: # Runs the vscan program on the entity. #*********************************************************************** sub entity_contains_virus_trend { unless ($Features{'Virus:TREND'}) { md_syslog('err', "TREND vscan not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run antivir my($code, $category, $action) = run_virus_scanner($Features{'Virus:TREND'} . " -za -a $path 2>&1", "Found "); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_trend_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_trend # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the Trend vscan program on the working directory #*********************************************************************** sub message_contains_virus_trend { unless ($Features{'Virus:TREND'}) { md_syslog('err', "TREND Filescanner or Interscan not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run vscan my($code, $category, $action) = run_virus_scanner($Features{'Virus:TREND'} . " -za -a ./Work/* 2>&1", "Found "); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_trend_code($code) : $code); } sub interpret_trend_code { my($code) = @_; # From info obtained from: # http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/amavis/amavis/README.scanners # OK return ($code, 'ok', 'ok') if ($code == 0); # virus found if ($code >= 1 and $code < 10) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/^\*+ Found virus (\S+)/); $VirusName = "unknown-Trend-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # Anything else shouldn't happen return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_nvcc # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by Norman Virus Control(NVCC) # %DESCRIPTION: # Runs the NVCC Anti-Virus program. (http://www.norman.no/) #*********************************************************************** sub entity_contains_virus_nvcc { unless($Features{'Virus:NVCC'}) { md_syslog('err', "Norman Virus Control (NVCC) not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = shift; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run nvcc my($code, $category, $action) = run_virus_scanner($Features{'Virus:NVCC'} . " -u -c $path 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # nvcc return codes return (wantarray ? interpret_nvcc_code($code) : ($code==1 || $code==2)); } #*********************************************************************** # %PROCEDURE: message_contains_virus_nvcc # %ARGUMENTS: # Nothing # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Runs the NVCC Anti-Virus program on the working directory. # (http://www.norman.no/) #*********************************************************************** sub message_contains_virus_nvcc { unless($Features{'Virus:NVCC'}) { md_syslog('err', "Norman Virus Control (NVCC) not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run nvcc my($code, $category, $action) = run_virus_scanner($Features{'Virus:NVCC'} . " -u -c -s ./Work 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } # nvcc return codes return (wantarray ? interpret_nvcc_code($code) : ($code==1 || $code==2)); } sub interpret_nvcc_code { my($code) = shift; # OK return (0, 'ok', 'ok') if ($code == 0); # Found a virus if ($code == 1 or $code == 2 or $code == 14) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/Possible virus[^']*'(\S+)'$/); #' Emacs highlighting goes nuts with unbalanced single-quote... $VirusName = "unknown-NVCC-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # Corrupt files/archives found -- treat as suspicious if ($code == 11) { $VirusName = 'NVCC-suspicious-code-11'; return ($code, 'suspicious', 'quarantine'); } # No scan area given or something went wrong return ($code, 'swerr', 'tempfail'); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_sophie # %ARGUMENTS: # entity -- a MIME entity # sophie_sock (optional) -- Sophie socket path # %RETURNS: # 1 if entity contains a virus as reported by Sophie # %DESCRIPTION: # Invokes the Sophie daemon (http://www.vanja.com/tools/sophie/) # on the entity. #*********************************************************************** sub entity_contains_virus_sophie { my ($entity) = shift; my ($sophie_sock) = $SophieSock; $sophie_sock = shift if (@_ > 0); return (wantarray ? (999, 'swerr', 'tempfail') : 1) if (!defined($sophie_sock)); if (!defined($entity->bodyhandle)) { return (wantarray ? (0, 'ok', 'ok') : 0); } if (!defined($entity->bodyhandle->path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } my $sock = IO::Socket::UNIX->new(Peer => $sophie_sock); if (defined $sock) { my $path = $entity->bodyhandle->path; # If path is not absolute, add cwd if (! ($path =~ m+^/+)) { $path = $CWD . "/" . $path; } if (!$sock->print("$path\n")) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } if (!$sock->flush) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } my($output); if (!$sock->sysread($output,256)) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } if (!$sock->close) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } if ($output =~ /^0/) { return (wantarray ? (0, 'ok', 'ok') : 0); } elsif ($output =~ /^1/) { $VirusName = "Unknown-sophie-virus"; $VirusName = $1 if $output =~ /^1:(.*)$/; $VirusScannerMessages .= "Sophie found the $VirusName virus.\n"; return (wantarray ? (1, 'virus', 'quarantine') : 1); } elsif ($output =~ /^-1/) { my $errmsg = "unknown status"; $errmsg = "$1" if $output =~ /^-1:(.*)$/; md_syslog('err', "entity_contains_virus_sophie: $errmsg ($path)"); $VirusScannerMessages .= "Sophie error: $errmsg\n"; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } else { md_syslog('err', "entity_contains_virus_sophie: unknown response - $output ($path)"); $VirusScannerMessages .= "Sophie error: unknown response - $output\n"; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } } # Could not connect to daemon md_syslog('err', "Could not connect to Sophie Daemon at $sophie_sock"); return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999); } #*********************************************************************** # %PROCEDURE: message_contains_virus_sophie # %ARGUMENTS: # sophie_sock (optional) -- Sophie socket path # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Invokes the Sophie daemon (http://www.vanja.com/tools/sophie/) # on the entire message. #*********************************************************************** sub message_contains_virus_sophie { my ($sophie_sock) = $SophieSock; $sophie_sock = shift if (@_ > 0); return (wantarray ? (999, 'swerr', 'tempfail') : 1) if (!defined($sophie_sock)); my $sock = IO::Socket::UNIX->new(Peer => $sophie_sock); if (defined $sock) { if (!$sock->print("$CWD/Work\n")) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } if (!$sock->flush) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } my($output, $ans); $ans = $sock->sysread($output, 256); if (!defined($ans)) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } if (!$sock->close) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } if ($output =~ /^0/) { return (wantarray ? (0, 'ok', 'ok') : 0); } elsif ($output =~ /^1/) { $VirusName = "Unknown-sophie-virus"; $VirusName = $1 if $output =~ /^1:(.*)$/; $VirusScannerMessages .= "Sophie found the $VirusName virus.\n"; return (wantarray ? (1, 'virus', 'quarantine') : 1); } elsif ($output =~ /^-1/) { my $errmsg = "unknown status"; $errmsg = "$1" if $output =~ /^-1:(.*)$/; md_syslog('err', "message_contains_virus_sophie: $errmsg ($CWD/Work)"); $VirusScannerMessages .= "Sophie error: $errmsg\n"; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } else { md_syslog('err', "message_contains_virus_sophie: unknown response - $output ($CWD/Work)"); $VirusScannerMessages .= "Sophie error: unknown response - $output\n"; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } } # Could not connect to daemon md_syslog('err', "Could not connect to Sophie Daemon at $sophie_sock"); return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_clamd # %ARGUMENTS: # entity -- a MIME entity # clamd_sock (optional) -- clamd socket path # %RETURNS: # 1 if entity contains a virus as reported by clamd # %DESCRIPTION: # Invokes the clamd daemon (http://www.clamav.net/) # on the entity. #*********************************************************************** sub entity_contains_virus_clamd { my ($entity) = shift; my ($clamd_sock) = $ClamdSock; $clamd_sock = shift if (@_ > 0); if (!defined($entity->bodyhandle)) { return (wantarray ? (0, 'ok', 'ok') : 0); } if (!defined($entity->bodyhandle->path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } my $sock = IO::Socket::UNIX->new(Peer => $clamd_sock); if (defined $sock) { my $path = $entity->bodyhandle->path; # If path is not absolute, add cwd if (! ($path =~ m+^/+)) { $path = $CWD . "/" . $path; } if (!$sock->print("SCAN $path\n")) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } if (!$sock->flush) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 1); } my($output, $ans); $ans = $sock->sysread($output,256); $sock->close; if (!defined($ans) || !$ans) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } if ($output =~ /: (.+) FOUND/) { $VirusScannerMessages .= "clamd found the $1 virus.\n"; $VirusName = $1; return (wantarray ? (1, 'virus', 'quarantine') : 1); } elsif ($output =~ /: (.+) ERROR/) { my $err_detail = $1; md_syslog('err', "Clamd returned error: $err_detail"); # If it's a zip module failure, try falling back on clamscan. # This is despicable, but it might work if ($err_detail =~ /(?:zip module failure|not supported data format)/i && $Features{'Virus:CLAMAV'}) { my ($code, $category, $action) = run_virus_scanner($Features{'Virus:CLAMAV'} . " -r --unzip --unrar --stdout --no-summary --infected $CWD/Work 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } md_syslog('info', "Falling back on clamscan --unzip --unrar because of Zip module failure in clamd"); return (wantarray ? interpret_clamav_code($code) : $code); } return (wantarray ? (999, 'swerr', 'tempfail') : 1); } return (wantarray ? (0, 'ok', 'ok') : 0); } # Could not connect to daemon md_syslog('err', "Could not connect to clamd Daemon at $clamd_sock"); return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999); } #*********************************************************************** # %PROCEDURE: message_contains_virus_clamd # %ARGUMENTS: # clamd_sock (optional) -- clamd socket path # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Invokes the clamd daemon (http://www.clamav.net/) # on the entire message. #*********************************************************************** sub message_contains_virus_clamd { my ($clamd_sock) = $ClamdSock; $clamd_sock = shift if (@_ > 0); return (wantarray ? (999, 'swerr', 'tempfail') : 1) if (!defined($clamd_sock)); my ($output,$sock); # PING/PONG test to make sure clamd is alive $sock = IO::Socket::UNIX->new(Peer => $clamd_sock); if (!defined($sock)) { md_syslog('err', "Could not connect to clamd daemon at $clamd_sock"); return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999); } my $s = IO::Select->new(); $s->add($sock); if (!$s->can_write(30)) { $sock->close; md_syslog('err', "Timeout writing to clamd daemon at $clamd_sock"); return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999); } $sock->print("PING"); $sock->flush; if (!$s->can_read(60)) { $sock->close; md_syslog('err', "Timeout reading from clamd daemon at $clamd_sock"); return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999); } # Free up memory used by IO::Select object undef $s; $sock->sysread($output,256); $sock->close; chomp($output); if (! defined($output) || $output ne "PONG") { md_syslog('err', "clamd is not responding"); return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999); } # open up a socket and scan each file in ./Work $sock = IO::Socket::UNIX->new(Peer => $clamd_sock); if (defined $sock) { if (!$sock->print("SCAN $CWD/Work\n")) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 999); } if (!$sock->flush) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 999); } my $ans; $ans = $sock->sysread($output,256); $sock->close; if (!defined($ans) || !$ans) { return (wantarray ? (999, 'swerr', 'tempfail') : 999); } if ($output =~ /: (.+) FOUND/) { $VirusScannerMessages .= "clamd found the $1 virus.\n"; $VirusName = $1; return (wantarray ? (1, 'virus', 'quarantine') : 1); } elsif ($output =~ /: (.+) ERROR/) { my $err_detail = $1; md_syslog('err', "Clamd returned error: $err_detail"); # If it's a zip module failure, try falling back on clamscan. # This is despicable, but it might work if ($err_detail =~ /(?:zip module failure|not supported data format)/i && $Features{'Virus:CLAMAV'}) { my ($code, $category, $action) = run_virus_scanner($Features{'Virus:CLAMAV'} . " -r --unzip --unrar --stdout --no-summary --infected $CWD/Work 2>&1"); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } md_syslog('info', "Falling back on clamscan --unzip --unrar because of Zip module failure in clamd"); return (wantarray ? interpret_clamav_code($code) : $code); } return (wantarray ? (999, 'swerr', 'tempfail') : 999); } } else { # Could not connect to daemon md_syslog('err', "Could not connect to clamd daemon at $clamd_sock"); return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999); } # No errors, no infected files were found return (wantarray ? (0, 'ok', 'ok') : 0); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_trophie # %ARGUMENTS: # entity -- a MIME entity # trophie_sock (optional) -- Trophie socket path # %RETURNS: # 1 if entity contains a virus as reported by Trophie # %DESCRIPTION: # Invokes the Trophie daemon (http://www.vanja.com/tools/trophie/) # on the entity. #*********************************************************************** sub entity_contains_virus_trophie { my ($entity) = shift; my ($trophie_sock) = $TrophieSock; $trophie_sock = shift if (@_ > 0); return (wantarray ? (999, 'swerr', 'tempfail') : 1) if (!defined($trophie_sock)); if (!defined($entity->bodyhandle)) { return (wantarray ? (0, 'ok', 'ok') : 0); } if (!defined($entity->bodyhandle->path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } my $sock = IO::Socket::UNIX->new(Peer => $trophie_sock); if (defined $sock) { my $path = $entity->bodyhandle->path; # If path is not absolute, add cwd if (! ($path =~ m+^/+)) { $path = $CWD . "/" . $path; } if (!$sock->print("$path\n")) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 999); } if (!$sock->flush) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 999); } my($output); $sock->sysread($output, 256); $sock->close; if ($output =~ /^1:(.*)$/) { $VirusScannerMessages .= "Trophie found the $1 virus.\n"; $VirusName = $1; return (wantarray ? (1, 'virus', 'quarantine') : 1); } return (wantarray ? (0, 'ok', 'ok') : 0); } # Could not connect to daemon md_syslog('err', "Could not connect to Trophie Daemon at $trophie_sock"); return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999); } #*********************************************************************** # %PROCEDURE: message_contains_virus_trophie # %ARGUMENTS: # trophie_sock (optional) -- Trophie socket path # %RETURNS: # 1 if any file in the working directory contains a virus # %DESCRIPTION: # Invokes the Trophie daemon (http://www.vanja.com/tools/trophie/) # on the entire message. #*********************************************************************** sub message_contains_virus_trophie { my ($trophie_sock) = $TrophieSock; $trophie_sock = shift if (@_ > 0); return (wantarray ? (999, 'swerr', 'tempfail') : 1) if (!defined($trophie_sock)); my $sock = IO::Socket::UNIX->new(Peer => $trophie_sock); if (defined $sock) { if (!$sock->print("$CWD/Work\n")) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 999); } if (!$sock->flush) { $sock->close; return (wantarray ? (999, 'swerr', 'tempfail') : 999); } my($output); $sock->sysread($output, 256); $sock->close; if ($output =~ /^1:(.*)$/) { $VirusScannerMessages .= "Trophie found the $1 virus.\n"; $VirusName = $1; return (wantarray ? (1, 'virus', 'quarantine') : 1); } return (wantarray ? (0, 'ok', 'ok') : 0); } # Could not connect to daemon md_syslog('err', "Could not connect to Trophie Daemon at $trophie_sock"); return (wantarray ? (999, 'cannot-execute', 'tempfail') : 999); } #*********************************************************************** # %PROCEDURE: entity_contains_virus_nod32 # %ARGUMENTS: # entity -- a MIME entity # %RETURNS: # 1 if entity contains a virus as reported by NOD32; 0 otherwise. # %DESCRIPTION: # Runs Eset NOD32 program on the entity. (http://www.eset.com) #*********************************************************************** sub entity_contains_virus_nod32 { unless($Features{'Virus:NOD32'}) { md_syslog('err', "NOD32 not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } my($entity) = @_; my($body) = $entity->bodyhandle; if (!defined($body)) { return (wantarray ? (0, 'ok', 'ok') : 0); } # Get filename my($path) = $body->path; if (!defined($path)) { return (wantarray ? (999, 'swerr', 'tempfail') : 1); } # Run NOD32 my($code, $category, $action) = run_virus_scanner($Features{'Virus:NOD32'} . " --subdir $path 2>&1", "virus=\"([^\"]+)\""); if ($action ne 'proceed') { return (wantarray ? ($code, $category, $action) : $code); } return (wantarray ? interpret_nod32_code($code) : $code); } #*********************************************************************** # %PROCEDURE: message_contains_virus_nod32 # %ARGUMENTS: # Nothing # %RETURNS: # 1 or 2 if any file in the working directory contains a virus # %DESCRIPTION: # Runs Eset NOD32 program on the working directory #*********************************************************************** sub message_contains_virus_nod32 { unless($Features{'Virus:NOD32'}) { md_syslog('err', "NOD32 not installed on this system"); return (wantarray ? (1, 'not-installed', 'tempfail') : 1); } # Run NOD32 my($code, $category, $action) = run_virus_scanner($Features{'Virus:NOD32'} . " --subdir ./Work 2>&1", "virus=\"([^\"]+)\""); return (wantarray ? interpret_nod32_code($code) : $code); } sub interpret_nod32_code { my($code) = @_; # OK return ($code, 'ok', 'ok') if ($code == 0); # 1 or 2 -- virus found if ($code == 1 || $code == 2) { $VirusName = $1 if ($CurrentVirusScannerMessage =~ m/virus=\"([^"]*)/); $VirusName = "unknown-NOD32-virus" if $VirusName eq ""; return ($code, 'virus', 'quarantine'); } # error return ($code, 'swerr', 'tempfail'); } =item run_virus_scanner Method that runs a virus scanner, collecting output in C<$VirusScannerMessages>. =cut #*********************************************************************** # %PROCEDURE: run_virus_scanner # %ARGUMENTS: # cmdline -- command to run # match -- regular expression to match (default ".*") # %RETURNS: # A three-element list: (exitcode, category, recommended_action) # exitcode is actual exit code from scanner # category is either "cannot-execute" or "ok" # recommended_action is either "tempfail" or "proceed" # %DESCRIPTION: # Runs a virus scanner, collecting output in $VirusScannerMessages #*********************************************************************** sub run_virus_scanner { my($cmd, $match) = @_; return (999, 'wrong-context', 'tempfail') if (!in_message_context("run_virus_scanner")); my($retcode); my($msg) = ""; $CurrentVirusScannerMessage = ""; $match = ".*" unless defined($match); my $scanner; unless (open($scanner, "-|", "$cmd")) { $msg = "Unable to execute $cmd: $!"; md_syslog('err', "run_virus_scanner: $msg"); $VirusScannerMessages .= "$msg\n"; $CurrentVirusScannerMessage = $msg; return (999, 'cannot-execute', 'tempfail'); } while(<$scanner>) { $msg .= $_ if /$match/i; } close($scanner); $retcode = $? / 256; # Some daemons are instructed to save output in a file my $report; if (open($report, "<", "DAEMON.RPT")) { while(<$report>) { $msg .= $_ if /$match/i; } close($report); unlink("DAEMON.RPT"); } $VirusScannerMessages .= $msg; $CurrentVirusScannerMessage = $msg; return ($retcode, 'ok', 'proceed'); } =back =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/Authres.pm000066400000000000000000000065711475763067200224150ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::Authres - Authentication Results interface for MIMEDefang =head1 DESCRIPTION Mail::MIMEDefang::Authres is a module used to add Authentication Results headers from F. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::Authres; use strict; use warnings; require Exporter; use Mail::MIMEDefang::DKIM; use Mail::MIMEDefang::Net; use Mail::MIMEDefang::SPF; our @ISA = qw(Exporter); our @EXPORT; our @EXPORT_OK; @EXPORT = qw(md_authres); =item md_authres Returns a mail Authentication-Results header value. The method accepts the following parameters: =over 4 =item C<$email> The email address of the sender =item C<$relayip> The relay ip address =item C<$serverdomain> The domain name of the server where MIMEDefang is running on =item C<$helo> (optional) The MTA helo server name =back =cut sub md_authres { my ($spfmail, $relayip, $serverdomain, $helo) = @_; my $dkimoldver = 1; if(not defined $spfmail and not defined $relayip and not defined $serverdomain) { md_syslog('err', "Cannot calculate Authentication-Results header without email address, relay ip and server domain name"); return; } if (version->parse(Mail::DKIM->VERSION) > version->parse(1.2)) { $dkimoldver = 0; } my ($authres, $spfres, $helo_spfres); my ($dkimres, $dkimdom, $ksize, $dkimpk) = md_dkim_verify(); my ($spfcode, $spfexpl, $helo_spfcode, $helo_spfexpl) = md_spf_verify($spfmail, $relayip, $helo); if((defined $spfcode) or ((defined $dkimpk) and ($ksize > 0))) { # Mail::DKIM::ARC::Signer v0.54 doesn't correctly parse Authentication-Results headers, # add a workaround to make md_arc_sign work with our own headers. if($dkimoldver) { $authres = "$serverdomain;"; } else { $authres = "$serverdomain (MIMEDefang);"; } if(defined $dkimpk) { my $dkimb = substr($dkimpk, 0, 8); if($ksize > 0) { $authres .= "\r\n\tdkim=$dkimres ($ksize-bit key) header.d=$dkimdom"; if(defined($dkimb)) { $authres .= " header.b=$dkimb"; } $authres .= ";"; } } if(defined $spfcode) { if($spfcode eq 'fail') { $authres .= "\r\n\tspf=" . $spfcode . " (domain of $spfmail does not designate $relayip as permitted sender) smtp.mailfrom=$spfmail;"; } elsif($spfcode eq 'pass') { $authres .= "\r\n\tspf=" . $spfcode . " (domain of $spfmail designates $relayip as permitted sender) smtp.mailfrom=$spfmail;"; } elsif($spfcode eq 'none') { $authres .= "\r\n\tspf=" . $spfcode . " (domain of $spfmail doesn't specify if $relayip is a permitted sender) smtp.mailfrom=$spfmail;"; } } if(defined $helo_spfcode) { if($helo_spfcode eq 'fail') { $authres .= "\r\n\tspf=" . $helo_spfcode . " (domain of $helo does not designate $relayip as permitted sender) smtp.helo=$helo;"; } elsif($helo_spfcode eq 'pass') { $authres .= "\r\n\tspf=" . $helo_spfcode . " (domain of $helo designates $relayip as permitted sender) smtp.helo=$helo;"; } elsif($helo_spfcode eq 'none') { $authres .= "\r\n\tspf=" . $helo_spfcode . " (domain of $helo doesn't specify if $relayip is a permitted sender) smtp.helo=$helo;"; } } $authres =~ s/\r//gs; return $authres; } return; } =back =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/DKIM.pm000066400000000000000000000121701475763067200215160ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::DKIM - DKIM interface for MIMEDefang =head1 DESCRIPTION Mail::MIMEDefang::DKIM is a module with a set of DKIM related methods called from F to operate with DKIM. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::DKIM; use strict; use warnings; require Exporter; use Mail::DKIM::Signer; use Mail::DKIM::Verifier; use Mail::MIMEDefang; our @ISA = qw(Exporter); our @EXPORT; our @EXPORT_OK; @EXPORT = qw(md_dkim_sign md_dkim_verify); sub _md_signer_policy { my $dkim = shift; use Mail::DKIM::DkSignature; my $sig = Mail::DKIM::Signature->new( Algorithm => $dkim->algorithm, Method => $dkim->method, Headers => $dkim->headers, Domain => $dkim->domain, Selector => $dkim->selector, ); $dkim->add_signature($sig); return; } =item md_dkim_sign Returns a mail header and the DKIM signature for the message. The method accepts the following parameters: =over 4 =item C<$keyfile> The path to the private DKIM key =item C<$algorithm> The algorithm to be used to sign the message, by default is 'rsa-sha1' =item C<$method> The method used to sign the message, by default is 'relaxed/simple' =item C<$domain> The domain to be used when signing the message, by default it's autodetected =item C<$selector> The selector to be used when signing the message, by default it's 'default' =item C<$headers> The headers to sign, by default the headers are: From Sender Reply-To Subject Date Message-ID To Cc MIME-Version Content-Type Content-Transfer-Encoding Content-ID Content-Description Resent-Date Resent-From Resent-Sender Resent-To Resent-cc Resent-Message-ID In-Reply-To References List-Id List-Help List-Unsubscribe List-Subscribe List-Post List-Owner List-Archive =item C<$wrap> Option to enable or disable DKIM header lines wrap (enabled by default). =back =cut sub md_dkim_sign { my ($keyfile, $algorithm, $method, $domain, $selector, $headers, $wrap) = @_; $algorithm = defined $algorithm ? $algorithm : 'rsa-sha1'; $method = defined $method ? $method : 'relaxed/simple'; $selector = defined $selector ? $selector : 'default'; $wrap //= 1; eval { if($wrap) { require Mail::DKIM::TextWrap; Mail::DKIM::TextWrap->import(); } }; my ($fh); if(not -f $keyfile) { md_syslog('err', "Could not open private DKIM key in md_dkim_sign: $!"); return; } my $dkim = Mail::DKIM::Signer->new( Policy => \&_md_signer_policy, Algorithm => $algorithm, Method => $method, Domain => $domain, Selector => $selector, KeyFile => $keyfile, Headers => $headers, ); unless (open($fh, '<', "./INPUTMSG")) { md_syslog('err', "Could not open INPUTMSG in md_dkim_sign: $!"); return; } # or read an email and pass it into the signer, one line at a time while (<$fh>) { # remove local line terminators s/\015?\012$//; # use SMTP line terminators $dkim->PRINT("$_\015\012"); } $dkim->CLOSE; close($fh); my $signature = $dkim->signature->as_string; # canonicalize newlines and trim trailing newline $signature =~ s/\015(?=\012)//gs; $signature =~ s/\012$//; if($signature =~ /^(.*):\s(.*)$/s) { return ($1, $2); } return; } =item md_dkim_verify Verifies the DKIM signature of an email, this method has no parameters. The first return value can be "pass", "fail", "invalid", "temperror" or "none". In case of multiple signatures, the "best" result will be returned. Best is defined as "pass", followed by "fail", "invalid", and "none". The second return value is the domain that has applied the signature. The third return value is the size of the DKIM public key. The forth return value is the value of the "b" tag of the DKIM signature. =back =cut sub md_dkim_verify { my $dkim = Mail::DKIM::Verifier->new(); my $fh; unless (open($fh, '<', "./INPUTMSG")) { md_syslog('err', "Could not open INPUTMSG in md_dkim_verify: $!"); return; } eval { my $warn = 0; local $SIG{__WARN__} = sub { if($warn eq 0) { md_syslog("Warning", "md_dkim_verify: cannot parse DKIM signature"); } $warn++; }; while (<$fh>) { # remove local line terminators s/\015?\012$//; # use SMTP line terminators $dkim->PRINT("$_\015\012"); } $dkim->CLOSE; }; close($fh); my $key_size; $key_size = eval { my $pk = $dkim->signature->get_public_key; $pk && $pk->cork && $pk->cork->size * 8 }; if(defined $dkim->signature and defined $key_size) { return ($dkim->result, $dkim->signature->domain, $key_size, $dkim->signature->get_tag('b')); } else { return ($dkim->result, undef, 0, undef); } } 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/DKIM/000077500000000000000000000000001475763067200211575ustar00rootroot00000000000000mimedefang-3.6/modules/lib/Mail/MIMEDefang/DKIM/ARC.pm000066400000000000000000000077241475763067200221340ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::DKIM::ARC - ARC interface for MIMEDefang =head1 DESCRIPTION Mail::MIMEDefang::DKIM::ARC is a module with a set of ARC related methods called from F to operate with ARC signatures. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::DKIM::ARC; use strict; use warnings; require Exporter; use Mail::DKIM::ARC::Signer; use Mail::DKIM::TextWrap; use Mail::MIMEDefang; our @ISA = qw(Exporter); our @EXPORT; our @EXPORT_OK; @EXPORT = qw(md_arc_sign); =item md_arc_sign Returns an hash with mail headers and the ARC signature for the message. If ARC sign fails the hash will contain an error message. The method accepts the following parameters: =over 4 =item C<$keyfile> The path to the private ARC key =item C<$algorithm> The algorithm to be used to sign the message, by default is 'rsa-sha256' =item C<$chain> The cv= value for the Arc-Seal header. "ar" means to copy it from an Authentication-Results header, or use none if there isn't one. =item C<$domain> The domain to be used when signing the message. =item C<$srvid> The authserv-id in the Authentication-Results headers, defaults to Domain. =item C<$selector> The selector to be used when signing the message, by default it's 'default' =item C<$headers> The headers to sign, by default the headers are: From Sender Reply-To Subject Date Message-ID To Cc MIME-Version Content-Type Content-Transfer-Encoding Content-ID Content-Description Resent-Date Resent-From Resent-Sender Resent-To Resent-cc Resent-Message-ID In-Reply-To References List-Id List-Help List-Unsubscribe List-Subscribe List-Post List-Owner List-Archive =back =cut sub md_arc_sign { my ($keyfile, $algorithm, $chain, $domain, $srvid, $selector, $headers) = @_; $algorithm = defined $algorithm ? $algorithm : 'rsa-sha256'; $selector = defined $selector ? $selector : 'default'; $srvid = defined $srvid ? $srvid : $domain; my (%headers, %err, $fh, $h, $v); if(not -f $keyfile) { md_syslog('err', "Could not open private ARC key in md_arc_sign: $!"); return; } if(not defined $chain) { md_syslog('err', "Could not ARC sign a message without specifying a chain"); return; } if(not defined $domain) { md_syslog('err', "Could not ARC sign a message without specifying a domain"); return; } my $arc = Mail::DKIM::ARC::Signer->new( Algorithm => $algorithm, Chain => $chain, Domain => $domain, SrvId => $srvid, Selector => $selector, KeyFile => $keyfile, Headers => $headers, ); unless (open($fh, '<', "./INPUTMSG")) { md_syslog('err', "Could not open INPUTMSG in md_arc_sign: $!"); return; } eval { local $SIG{__WARN__} = sub { my $warn = $_[0]; $warn =~ s/\n//g; $warn =~ s/\bat .{10,100} line \d+\.//g; if($warn =~ /message not signed/) { md_syslog("Warning", "md_arc_sign: cannot ARC sign a message that is not DKIM signed"); } else { md_syslog("Warning", "md_arc_sign: $warn"); } }; # or read an email and pass it into the signer, one line at a time while (<$fh>) { # remove local line terminators chomp; s/\015$//; # use SMTP line terminators $arc->PRINT("$_\015\012"); } }; $arc->CLOSE; close($fh); if($arc->result eq "sealed") { my @pre_headers = $arc->as_strings(); foreach my $arc_h ( @pre_headers ) { if($arc_h =~ /^(.*):\s(.*)$/s) { $h = $1; $v = $2; $v =~ s/\r//gs; $headers{$h} = $v; } } return %headers; } else { $err{error} = $arc->{details}; return %err; } return; } =back =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/MIME.pm000066400000000000000000000332661475763067200215320ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::MIME - MIME objects interface methods for email filters =head1 DESCRIPTION Mail::MIMEDefang::MIME are a set of methods that can be called from F to operate on MIME objects. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::MIME; use strict; use warnings; require Exporter; use MIME::Parser; use MIME::Words qw(:all); use Mail::MIMEDefang; our @ISA = qw(Exporter); our @EXPORT; our @EXPORT_OK; @EXPORT = qw(builtin_create_parser find_part append_to_part takeStabAtFilename remove_redundant_html_parts append_to_html_part append_html_boilerplate append_text_boilerplate collect_parts anonymize_uri); sub builtin_create_parser { my $parser = MIME::Parser->new(); $parser->extract_nested_messages(1); $parser->extract_uuencode(1); $parser->output_to_core(0); $parser->tmp_to_core(0); return $parser; } =item collect_parts Method that adds parts to the array C<@FlatParts> for flattening. =cut #*********************************************************************** # %PROCEDURE: collect_parts # %ARGUMENTS: # entity -- root entity to rebuild # skip_pgp_mime -- If true, skip multipart/signed and multipart/encrypted # parts # %RETURNS: # Nothing # %DESCRIPTION: # Adds parts to the array @FlatParts for flattening. #*********************************************************************** sub collect_parts { my($entity, $skip_pgp_mime) = @_; my(@parts) = $entity->parts; if ($#parts >= 0) { if (! $skip_pgp_mime || (lc($entity->head->mime_type) ne "multipart/signed" and lc($entity->head->mime_type) ne "multipart/encrypted")) { foreach my $part (@parts) { collect_parts($part, $skip_pgp_mime); } } } else { push(@FlatParts, $entity); } } =item takeStabAtFilename ( $entity ) Makes a guess at a filename for the attachment. Calls MIME::Head's recommended_filename() method, which tries 'Content-Disposition.filename'and if not found, 'Content-Type.name'. Returns a MIME-decoded filename, or a blank string if none found. =cut sub takeStabAtFilename { my ($entity) = @_; my $guess = $entity->head->recommended_filename(); if( defined $guess ) { return scalar( decode_mimewords( $guess ) ); } return ''; } =item find_part Method that returns the first MIME entity of type C<$content_type>, C if none exists. =cut #*********************************************************************** # %PROCEDURE: find_part # %ARGUMENTS: # entity -- root MIME part # content_type -- desired MIME content type # skip_pgp_mime -- If true, do not descend into multipart/signed or # multipart/encrypted parts # %RETURNS: # First MIME entity of type "$content_type"; undef if none exists. #*********************************************************************** sub find_part { my($entity, $content_type, $skip_pgp_mime) = @_; my(@parts); my($ans); if (!($entity->is_multipart)) { if (lc($entity->head->mime_type) eq lc($content_type)) { return $entity; } else { return; } } if ($skip_pgp_mime && (lc($entity->head->mime_type) eq "multipart/signed" or lc($entity->head->mime_type) eq "multipart/encrypted")) { return; } @parts = $entity->parts; foreach my $part (@parts) { $ans = find_part($part, $content_type, $skip_pgp_mime); return $ans if defined($ans); } return; } =item append_to_part Method that appends text to C<$part> =cut #*********************************************************************** # %PROCEDURE: append_to_part # %ARGUMENTS: # part -- a mime entity # msg -- text to append to the entity # %RETURNS: # 1 on success; 0 on failure. # %DESCRIPTION: # Appends text to $part #*********************************************************************** sub append_to_part { my($part, $boilerplate) = @_; return 0 unless defined($part->bodyhandle); my($path) = $part->bodyhandle->path; return 0 unless (defined($path)); return 0 unless (open(my $OUT, ">>", "$path")); print $OUT "\n$boilerplate\n"; close($OUT); $Changed = 1; return 1; } =item remove_redundant_html_parts Method that rebuilds the email message without redundant HTML parts. That is, if a multipart/alternative entity contains text/plain and text/html parts, the text/html part will be removed. =cut #*********************************************************************** # %PROCEDURE: remove_redundant_html_parts # %ARGUMENTS: # e -- entity # %RETURNS: # Nothing # %DESCRIPTION: # Rebuilds $e without redundant HTML parts. That is, if # a multipart/alternative entity contains text/plain and text/html # parts, we nuke the text/html part. #*********************************************************************** sub remove_redundant_html_parts { my($e) = @_; return 0 unless in_filter_end("remove_redundant_html_parts"); my(@parts) = $e->parts; my($type) = lc($e->mime_type); # Don't recurse into multipart/signed or multipart/encrypted return 0 if ($type eq "multipart/signed" or $type eq "multipart/encrypted"); my(@keep); my($didsomething); $didsomething = 0; my($have_text_plain); if ($type eq "multipart/alternative" && $#parts >= 0) { # First look for a text/plain part $have_text_plain = 0; foreach my $part (@parts) { $type = lc($part->mime_type); if ($type eq "text/plain") { $have_text_plain = 1; last; } } # If we have a text/plain part, delete any text/html part if ($have_text_plain) { foreach my $part (@parts) { $type = lc($part->mime_type); if ($type ne "text/html") { push(@keep, $part); } else { $didsomething = 1; } } if ($didsomething) { $e->parts(\@keep); @parts = @keep; $Changed = 1; } } } if ($#parts >= 0) { foreach my $part (@parts) { $didsomething = 1 if (remove_redundant_html_parts($part)); } } return $didsomething; } # HTML parser callbacks sub html_echo { my($p, $text) = @_; $p->{ofh}->print($text); } sub html_end { my($p, $text) = @_; if (!$HTMLFoundEndBody) { if ($text =~ m+<\s*/body+i) { $p->{ofh}->print("$HTMLBoilerplate\n"); $HTMLFoundEndBody = 1; } } if (!$HTMLFoundEndBody) { if ($text =~ m+<\s*/html+i) { $p->{ofh}->print("$HTMLBoilerplate\n"); $HTMLFoundEndBody = 1; } } $p->{ofh}->print($text); } =item append_to_html_part Method that appends text to the spicified mime part, but does so by parsing HTML and adding the text before or tags. =cut #*********************************************************************** # %PROCEDURE: append_to_html_part # %ARGUMENTS: # part -- a mime entity (of type text/html) # msg -- text to append to the entity # %RETURNS: # 1 on success; 0 on failure. # %DESCRIPTION: # Appends text to $part, but does so by parsing HTML and adding the # text before or #*********************************************************************** sub append_to_html_part { my($part, $boilerplate) = @_; my ($ifh, $ofh); if (!$Features{"HTML::Parser"}) { md_syslog('warning', "Attempt to call append_to_html_part, but HTML::Parser Perl module not installed"); return 0; } return 0 unless defined($part->bodyhandle); my($path) = $part->bodyhandle->path; return 0 unless (defined($path)); return 0 unless (open($ifh, "<", "$path")); if (!open($ofh, ">", "$path.tmp")) { close($ifh); return(0); } $HTMLFoundEndBody = 0; $HTMLBoilerplate = $boilerplate; my($p); $p = HTML::Parser->new(api_version => 3, default_h => [\&html_echo, "self,text"], end_h => [\&html_end, "self,text"]); $p->{ifh} = $ifh; $p->{ofh} = $ofh; $p->unbroken_text(1); $p->parse_file($ifh); if (!$HTMLFoundEndBody) { print $ofh "\n$boilerplate\n"; } close($ifh); close($ofh); # Rename the path return 0 unless rename($path, "$path.old"); unless (rename("$path.tmp", $path)) { rename ("$path.old", $path); return 0; } unlink "$path.old"; $Changed = 1; return 1; } =item append_text_boilerplate Method that appends text to text/plain part or parts. =cut #*********************************************************************** # %PROCEDURE: append_text_boilerplate # %ARGUMENTS: # msg -- root MIME entity. # boilerplate -- boilerplate text to append # all -- if 1, append to ALL text/plain parts. If 0, append only to # FIRST text/plain part. # %RETURNS: # 1 if text was appended to at least one part; 0 otherwise. # %DESCRIPTION: # Appends text to text/plain part or parts. #*********************************************************************** sub append_text_boilerplate { my($msg, $boilerplate, $all) = @_; my($part); if (!$all) { $part = find_part($msg, "text/plain", 1); if (defined($part)) { if (append_to_part($part, $boilerplate)) { $Actions{'append_text_boilerplate'}++; return 1; } } return 0; } @FlatParts = (); my($ok) = 0; collect_parts($msg, 1); foreach my $part (@FlatParts) { if (lc($part->head->mime_type) eq "text/plain") { if (append_to_part($part, $boilerplate)) { $ok = 1; $Actions{'append_text_boilerplate'}++; } } } return $ok; } =item append_html_boilerplate Method that appends text to text/html part or parts. It tries to be clever and inserts the text before the tag to be able of being seen. =cut #*********************************************************************** # %PROCEDURE: append_html_boilerplate # %ARGUMENTS: # msg -- root MIME entity. # boilerplate -- boilerplate text to append # all -- if 1, append to ALL text/html parts. If 0, append only to # FIRST text/html part. # %RETURNS: # 1 if text was appended to at least one part; 0 otherwise. # %DESCRIPTION: # Appends text to text/html part or parts. Tries to be clever and # insert the text before the tag so it has a hope in hell of # being seen. #*********************************************************************** sub append_html_boilerplate { my($msg, $boilerplate, $all) = @_; my($part); if (!$all) { $part = find_part($msg, "text/html", 1); if (defined($part)) { if (append_to_html_part($part, $boilerplate)) { $Actions{'append_html_boilerplate'}++; return 1; } } return 0; } @FlatParts = (); my($ok) = 0; collect_parts($msg, 1); foreach my $part (@FlatParts) { if (lc($part->head->mime_type) eq "text/html") { if (append_to_html_part($part, $boilerplate)) { $ok = 1; $Actions{'append_html_boilerplate'}++; } } } return $ok; } sub _anonymize_text_uri { my ($part) = @_; return 0 unless defined($part->bodyhandle); my($path) = $part->bodyhandle->path; my $npath = $path . '.tmp'; return 0 unless (defined($path)); my $body = $part->bodyhandle; # If there's no body, then we can't add return 0 unless $body; my $ifh = $body->open('r'); return 0 unless $ifh; my $ofh; if (!open($ofh, '>', $npath)) { $ifh->close(); return 0; } my $line; my $nline; while (defined($line = $ifh->getline())) { if($line =~ /https?\:\/\/.{3,512}\/(.{1,30})?(\&|\?)utm([_a-z0-9=]+)/) { my @params = split(/(\?|\&|\s+)/, $line); foreach my $p ( @params ) { if($p =~ /(\?|\&)?utm_.{1,20}\=.{1,64}/) { next; } else { $nline .= $p; } } $nline =~ s/(\&{2,}|\?{2,}|\n)//g; $ofh->print($nline); } else { $ofh->print($line); } } $ifh->close(); $ofh->close(); # Rename over the old path return 1 if rename($npath, $path); # Rename failed unlink($npath); $Changed = 1; return 0; } sub html_utm_filter { my($p, $text) = @_; my $nline; if($text =~ /https?\:\/\/.{3,512}\/(.{1,30})?(\&|\?)utm([_a-z0-9=]+)/) { my @params = split(/(\?|\&|\s+|\>|\"|\')/, $text); foreach my $par ( @params ) { if($par =~ /(\?|\&)?utm_.{1,20}\=.{1,64}/) { next; } else { $nline .= $par; } } $nline =~ s/(\&{2,}|\?{2,}|\n)//g; $p->{ofh}->print($nline); } else { $p->{ofh}->print($text); } } sub _anonymize_html_uri { my ($part) = @_; if (!$Features{"HTML::Parser"}) { md_syslog('warning', "Attempt to call append_to_html_part, but HTML::Parser Perl module not installed"); return 0; } my ($ifh, $ofh); return 0 unless defined($part->bodyhandle); my($path) = $part->bodyhandle->path; return 0 unless (defined($path)); return 0 unless (open($ifh, "<", "$path")); if (!open($ofh, ">", "$path.tmp")) { close($ifh); return(0); } my $p; $p = HTML::Parser->new(api_version => 3, default_h => [\&html_utm_filter, "self,text"], end_h => [\&html_echo, "self,text"]); $p->{ifh} = $ifh; $p->{ofh} = $ofh; $p->unbroken_text(1); $p->parse_file($ifh); close($ifh); close($ofh); # Rename the path return 0 unless rename($path, "$path.old"); unless (rename("$path.tmp", $path)) { rename ("$path.old", $path); return 0; } unlink "$path.old"; $Changed = 1; return 1; } =item anonymize_uri Anonymize urls by removing all utm_* parameters, takes the message part as parameter and returns a boolean value if the sub succeeded or not. =cut sub anonymize_uri { my ($msg) = @_; @FlatParts = (); my($ok) = 0; collect_parts($msg, 1); foreach my $part (@FlatParts) { if (lc($part->head->mime_type) =~ /text\/html/) { if (_anonymize_html_uri($part)) { $ok = 1; } } elsif (lc($part->head->mime_type) =~ /text\/plain/) { if (_anonymize_text_uri($part)) { $ok = 1; } } } return $ok; } =back =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/Mail.pm000066400000000000000000000426011475763067200216560ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::Mail - Mail and SMTP related methods for email filters =head1 DESCRIPTION Mail::MIMEDefang::Mail are a set of methods that can be called from F to send email messages or to run SMTP checks. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::Mail; use strict; use warnings; use IO::Socket::SSL; use Mail::MIMEDefang; use Mail::MIMEDefang::MIME; use Mail::MIMEDefang::Utils; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw( resend_message_one_recipient resend_message_specifying_mode resend_message pretty_print_mail md_check_against_smtp_server get_smtp_extensions get_smtp_return_code ); =item resend_message_one_recipient Method that re-sends the message as if it came from original sender to a single recipient. =cut #*********************************************************************** # %PROCEDURE: resend_message_one_recipient # %ARGUMENTS: # recip -- a single recipient # deliverymode -- optional sendmail delivery mode arg (default "-odd") # %RETURNS: # True on success; false on failure. # %DESCRIPTION: # Re-sends the message (as if it came from original sender) to # a single recipient. #*********************************************************************** sub resend_message_one_recipient { my($recip, $deliverymode) = @_; return resend_message_specifying_mode($deliverymode, [ $recip ]); } =item resend_message_specifying_mode Method that re-sends the message as if it came from original sender to a list of recipients. =cut #*********************************************************************** # %PROCEDURE: resend_message_specifying_mode # %ARGUMENTS: # deliverymode -- delivery mode # recipients -- reference to list of recipients to resend message to. # %RETURNS: # True on success; false on failure. # %DESCRIPTION: # Re-sends the message (as if it came from original sender) to # a list of recipients. #*********************************************************************** sub resend_message_specifying_mode { my($deliverymode, $recips) = @_; return 0 if (!in_message_context("resend_message_specifying_mode")); $deliverymode = "-odd" unless defined($deliverymode); if ($deliverymode ne "-odb" && $deliverymode ne "-odq" && $deliverymode ne "-odd" && $deliverymode ne "-odi") { $deliverymode = "-odd"; } # Fork and exec for safety instead of involving shell my $pid = open(my $child, "|-"); if (!defined($pid)) { md_syslog('err', "Cannot fork to resend message"); return 0; } my $in; if ($pid) { # In the parent -- pipe mail message to the child unless (open($in, "<", "INPUTMSG")) { md_syslog('err', "Could not open INPUTMSG in resend_message: $!"); return 0; } # Preserve relay's IP address if possible... if ($ValidateIPHeader =~ /^X-MIMEDefang-Relay/) { print $child "$ValidateIPHeader: $RelayAddr\n" } # Synthesize a Received: header print $child synthesize_received_header(); # Copy message over while(my $line = <$in>) { print $child $line; } close($in); if (!close($child)) { if ($!) { md_syslog('err', "sendmail failure in resend_message: $!"); } else { md_syslog('err', "sendmail non-zero exit status in resend_message: $?"); } return 0; } return 1; } # In the child -- invoke Sendmail # Direct stdout to stderr, or we will screw up communication with # the multiplexor.. open(STDOUT, ">&", \*STDERR); my(@cmd); if ($Sender eq "") { push(@cmd, "-f<>"); } else { push(@cmd, "-f$Sender"); } push(@cmd, $deliverymode); push(@cmd, "-Ac"); push(@cmd, "-oi"); push(@cmd, "--"); push @cmd, @$recips; # In curlies to silence Perl warning... my $sm; $sm = $Features{'Path:SENDMAIL'}; { exec($sm, @cmd); } # exec failed! md_syslog('err', "Could not exec $sm: $!"); exit(1); # NOTREACHED } =item resend_message Method that re-sends the message as if it came from original sender to a list of recipients. =cut #*********************************************************************** # %PROCEDURE: resend_message # %ARGUMENTS: # recipients -- list of recipients to resend message to. # %RETURNS: # True on success; false on failure. # %DESCRIPTION: # Re-sends the message (as if it came from original sender) to # a list of recipients. #*********************************************************************** sub resend_message { return 0 if (!in_message_context("resend_message")); my(@recips); @recips = @_; return resend_message_specifying_mode("-odd", \@recips); } =item pretty_print_mail Method that makes a pretty-printed version of the e-mail body no longer than size characters. =cut #*********************************************************************** # %PROCEDURE: pretty_print_mail # %ARGUMENTS: # e -- a MIME::Entity object # size -- maximum size of value to return in characters # chunk -- optional; used in recursive calls only. Do not supply as arg. # depth -- used in recursive calls only. Do not supply as arg. # %RETURNS: # A "pretty-printed" version of the e-mail body # %DESCRIPTION: # Makes a pretty-printed version of the e-mail body no longer than size # characters. This odd-looking function is used by CanIt... #*********************************************************************** sub pretty_print_mail { my($e, $size, $chunk, $depth) = @_; $chunk = "" unless defined($chunk); $depth = 0 unless defined($depth); my(@parts) = $e->parts; my($type) = $e->mime_type; my($fname) = takeStabAtFilename($e); $fname = "; filename=$fname" if ($fname ne ""); my($spaces) = " " x $depth; $chunk .= "\n$spaces" . "[Part: ${type}${fname}]\n\n"; if ($#parts >= 0) { foreach my $part (@parts) { $chunk = pretty_print_mail($part, $size, $chunk, $depth+1); last if (length($chunk) >= $size); } } else { return $chunk unless ($type =~ m+^text/+); my($body) = $e->bodyhandle; return $chunk unless (defined($body)); my($path) = $body->path; return $chunk unless (defined($path)); return $chunk unless (open(my $in, "<", "$path")); while (<$in>) { $chunk .= $_; last if (length($chunk) >= $size); } close($in); } return $chunk; } =item get_smtp_return_code Method that reads return codes from SMTP server, returns a four-element list:(retval, code, dsn, text), where code is a 3-digit SMTP code. Retval is 'CONTINUE', 'TEMPFAIL' or 'REJECT'. =cut #*********************************************************************** # %PROCEDURE: get_smtp_return_code # %ARGUMENTS: # sock -- a socket connected to an SMTP server # recip -- the recipient we're inquring about # server -- the server we're querying # %RETURNS: # A four-element list:(retval, code, dsn, text), # where code is a 3-digit SMTP code. # Retval is 'CONTINUE', 'TEMPFAIL' or 'REJECT'. # %DESCRIPTION: # Reads return codes from SMTP server #*********************************************************************** sub get_smtp_return_code { my($sock, $recip, $server) = @_; my($line, $code, $text, $retval, $dsn); while (defined ($line = $sock->getline())) { # Chew up all trailing white space, including CR $line =~ s/\s+$//; if (($line =~ /^\d\d\d$/) or ($line =~ /^\d\d\d\s/)) { $line =~ /^(\d\d\d)\s*(.*)$/; $code = $1; $text = $2; # Check for DSN if ($text =~ /^(\d\.\d{1,3}\.\d{1,3})\s+(.*)$/) { $dsn = $1; $text = $2; } else { $dsn = ""; } if ($code =~ /^[123]/) { $retval = 'CONTINUE'; } elsif ($code =~ /^4/) { md_syslog('info', "get_smtp_return_code: for $recip on $server returned $code $dsn $text"); $retval = 'TEMPFAIL'; } elsif ($code =~ /^5/) { md_syslog('info', "get_smtp_return_code: for $recip on $server returned $code $dsn $text"); $retval = 'REJECT'; } else { md_syslog('warning', "get_smtp_return_code: Invalid SMTP reply code $code from server $server for $recip"); $retval = 'TEMPFAIL'; } return ($retval, $code, $dsn, $text); } } my $msg; if( defined $line ) { $msg = "get_smtp_return_code: Invalid response [$line] from SMTP server"; md_syslog('info', "get_smtp_return_code: Check for $recip on $server returned invalid response [$line]"); } else { $msg = "get_smtp_return_code: Empty response from SMTP server"; md_syslog('info', "get_smtp_return_code: for $recip on $server returned an empty response"); } return ('TEMPFAIL', "451", "4.3.0", $msg ); } =item get_smtp_extensions Method that checks SMTP server's supported extensions. It expects EHLO to have been sent already (artifact of get_smtp_return_code). The sub returns a four-element list:(retval, code, dsn, exts) =over 4 =item * retval is 'CONTINUE', 'TEMPFAIL', or 'REJECT'. =item * code is a 3-digit SMTP code. =item * dsn is an extended SMTP status code =item * exts is a hash of EXTNAME-EEXTOPTS =back =cut #*********************************************************************** # %PROCEDURE: get_smtp_extensions # %ARGUMENTS: # sock -- a socket connected to an SMTP server # server -- the server we're querying # %RETURNS: # A four-element list:(retval, code, dsn, exts) # retval is 'CONTINUE', 'TEMPFAIL', or 'REJECT'. # code is a 3-digit SMTP code. # dsn is an extended SMTP status code # exts is a hash of EXTNAME->EXTOPTS # %DESCRIPTION: # Checks SMTP server's supported extensions. # Expects EHLO to have been sent already (artifact of cribbing get_smtp_return_code) #*********************************************************************** sub get_smtp_extensions { my($sock, $server) = @_; my($ext, $msg, $delim, $line, $code, $text, $retval, $dsn); my %exts; my $LineNum=0; $delim='-'; while ( ($delim eq '-' ) && (defined ($line = $sock->getline()))) { # Chew up all trailing white space, including CR $line =~ s/\s+$//; # Line can be: # '[45]xy $ERROR' Failure. Don't really care why. # '250-hostname' Initial line in multi-line response # '250 hostname' ONLY line in successful response # '250-$EXTNAME $EXTOPTS' Advertisement of extension with options # '250 $EXTNAME $EXTOPTS' Advertisement of extension with options (Final line) $line =~ m/([245][0-9][0-9])([- ])([^ ]+) *(.*)/ or return ('TEMPFAIL', "451", "4.3.0", "$server said: $line"); $code=$1; $delim=$2; $ext=$3; $text=$4; # uncomment to debug parsing # md_syslog('debug',"get_smtp_extensions: line $LineNum: code=$code, delim=$delim, ext=$ext, text=$text"); if ( $LineNum == 0 ) { $exts{'hostname'} = $3; $LineNum++; next; } $exts{$ext} = $text; $LineNum++; } $code =~ m/2../ and return ('CONTINUE', "$code", "2.5.0", %exts ); $code =~ m/4../ and return ('TEMPFAIL', "$code", "4.0.0", %exts ); $code =~ m/5../ and return ('REJECT', "$code", "5.0.0", %exts ); } =item md_check_against_smtp_server Method that verifies a recipient against another SMTP server by issuing a HELO / MAIL FROM: / RCPT TO: / QUIT sequence. The method accepts the following parameters: =over 4 =item * sender e-mail address =item * recipient e-mail address =item * helo string to put in "HELO" command =item * SMTP server to try. =item * optional: Port to connect on (defaults to 25) =back The method returns: =over 4 =item * ('CONTINUE', "OK") if recipient is OK =item * ('TEMPFAIL', "err") if temporary failure =item * ('REJECT', "err") if recipient is not OK. =back =cut #*********************************************************************** # %PROCEDURE: md_check_against_smtp_server # %ARGUMENTS: # sender -- sender e-mail address # recip -- recipient e-mail address # helo -- string to put in "HELO" command # server -- SMTP server to try. # port -- optional: Port to connect on (defaults to 25) # %RETURNS: # ('CONTINUE', "OK") if recipient is OK # ('TEMPFAIL', "err") if temporary failure # ('REJECT', "err") if recipient is not OK. # %DESCRIPTION: # Verifies a recipient against another SMTP server by issuing a # HELO / MAIL FROM: / RCPT TO: / QUIT sequence #*********************************************************************** sub md_check_against_smtp_server { my($sender, $recip, $helo, $server, $port) = @_; my($code, $text, $dsn, $retval); $port = 'smtp(25)' unless defined($port); # Add angle-brackets if needed if (!($sender =~ /^<.*>$/)) { $sender = "<$sender>"; } if (!($recip =~ /^<.*>$/)) { $recip = "<$recip>"; } # Set SSL_startHandshake to start in plain mode, # SSL_verify_mode to SSL_VERIFY_NONE to make the check work # with self-signed certificates and SSL_hostname for SNI my $sock; my $plaintext = 0; $sock = IO::Socket::SSL->new(PeerAddr => $server, SSL_startHandshake => 0, SSL_verify_mode => SSL_VERIFY_NONE, SSL_hostname => "$server", SSL_version => 'SSLv23', SSL_cipher_list => 'ALL', PeerPort => $port, Proto => 'tcp', Timeout => 25); if (!defined($sock)) { # fallback to plaintext if SSL connection doesn't succeed md_syslog('warning', 'Falling back to plaintext connection'); $sock = IO::Socket::INET->new(PeerAddr => $server, PeerPort => $port, Proto => 'tcp', Timeout => 25); $plaintext = 1; if(!defined($sock)) { return ('TEMPFAIL', "Could not connect to other SMTP server $server, error=$!"); } } ($retval, $code, $dsn, $text) = get_smtp_return_code($sock, $recip, $server); if ($retval ne 'CONTINUE') { $sock->print("QUIT\r\n"); $sock->flush(); # Swallow return value get_smtp_return_code($sock, $recip, $server); $sock->close(); return ($retval, $text, $code, $dsn); } # If the banner contains our host name, there's a loop! # However, don't check if $server is explicitly 127.0.0.1 # because presumably that indicates the caller knows # what he or she is doing. if ($server ne '127.0.0.1' && $server ne '::1') { my $host_expr = quotemeta(Mail::MIMEDefang::Net::get_host_name()); if ($text =~ /^$host_expr\b/) { $sock->print("QUIT\r\n"); $sock->flush(); # Swallow return value get_smtp_return_code($sock, $recip, $server); $sock->close(); return('REJECT', "Verification server loop! Trying to verify $recip against myself!", 554, '5.4.6'); } } $sock->print("EHLO $helo\r\n"); $sock->flush(); my %exts; my $ext; ($retval, $code, $dsn, %exts) = get_smtp_extensions($sock, $recip, $server); if (($plaintext eq 1) or ($retval ne 'CONTINUE')) { $sock->print("HELO $helo\r\n"); } else { # Uncomment to debug (and/or uncomment similar line in get_smtp_extensions) # foreach $ext ( keys %exts ) { # md_syslog('debug',"md_check_against_smtp_server extension: $ext $exts{$ext}"); # } if (exists $exts{'STARTTLS'}) { # send STARTTLS command and read response $sock->print("STARTTLS\r\n"); ($retval, $code, $dsn, $text) = get_smtp_return_code($sock, $recip, $server); if ($retval ne 'CONTINUE') { $sock->print("QUIT\r\n"); $sock->flush(); # Swallow return value get_smtp_return_code($sock, $recip, $server); $sock->close(); return ($retval, $text, $code, $dsn); } # if response was successful we can upgrade the socket to SSL now: if ( $sock->connect_SSL ) { md_syslog('debug',"md_check_against_smtp_server: start_SSL succeeded!"); # send inside EHLO $sock->print("EHLO $helo\r\n"); } else { #back off from using STARTTLS $sock->stop_SSL; no warnings 'once'; md_syslog('debug',"md_check_against_smtp_server: $server offers STARTTLS but fails with error $IO::Socket::SSL::SSL_ERROR. Falling back to plaintext..."); $sock->print("EHLO $helo\r\n"); } } else { md_syslog('debug',"md_check_against_smtp_server: STARTTLS not available"); $sock->print("RSET\r\n"); $sock->flush(); # Swallow return value get_smtp_return_code($sock, $recip, $server); $sock->print("EHLO $helo\r\n"); } } # At this point we've either sent a fallback HELO, fallback EHLO, or internal EHLO. # so, get the code... ($retval, $code, $dsn, $text) = get_smtp_return_code($sock, $recip, $server); if ($retval ne 'CONTINUE') { $sock->print("QUIT\r\n"); $sock->flush(); # Swallow return value get_smtp_return_code($sock, $recip, $server); $sock->close(); return ($retval, $text, $code, $dsn); } md_syslog('debug',"md_check_against_smtp_server: Checking sender $sender"); $sock->print("MAIL FROM:$sender\r\n"); $sock->flush(); ($retval, $code, $dsn, $text) = get_smtp_return_code($sock, $recip, $server); if ($retval ne 'CONTINUE') { $sock->print("QUIT\r\n"); $sock->flush(); # Swallow return value get_smtp_return_code($sock, $recip, $server); $sock->close(); return ($retval, $text, $code, $dsn); } md_syslog('debug',"md_check_against_smtp_server: Checking recipient $recip"); $sock->print("RCPT TO:$recip\r\n"); $sock->flush(); ($retval, $code, $dsn, $text) = get_smtp_return_code($sock, $recip, $server); $sock->print("QUIT\r\n"); $sock->flush(); # Swallow return value get_smtp_return_code($sock, $recip, $server); $sock->close(); return ($retval, $text, $code, $dsn); } =back =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/Net.pm000066400000000000000000000410521475763067200215210ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::Net - Network related methods for email filters =head1 DESCRIPTION Mail::MIMEDefang::Net are a set of methods that can be called from F to call network related services. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::Net; use strict; use warnings; use Socket; use IO::Select; use Net::DNS; use Sys::Hostname; use Mail::MIMEDefang; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(expand_ipv6_address reverse_ip_address_for_rbl relay_is_blacklisted email_is_blacklisted relay_is_blacklisted_multi relay_is_blacklisted_multi_count relay_is_blacklisted_multi_list is_public_ip4_address is_public_ip6_address md_get_bogus_mx_hosts get_mx_ip_addresses); our @EXPORT_OK = qw(get_host_name get_ptr_record md_init); sub md_init { my $digest_md5 = 0; my $digest_sha = 0; local $@; if (!defined($Features{"Digest::MD5"}) or ($Features{"Digest::MD5"} eq 1)) { eval { require Digest::MD5; $digest_md5 = 1; }; if($@) { $digest_md5 = 0; } else { Digest::MD5->import(qw(md5_hex)); } } if (!defined($Features{"Digest::SHA"}) or ($Features{"Digest::SHA"} eq 1)) { eval { require Digest::SHA; $digest_sha = 1; }; if($@) { $digest_sha = 0; } else { Digest::SHA->import(qw(sha1_hex)); } } $Features{"Digest::MD5"} = $digest_md5; $Features{"Digest::SHA"} = $digest_sha; } =item expand_ipv6_address Method that returns an IPv6 address with all zero fields explicitly expanded, any field shorter than 4 hex digits will be padded with zeros. =cut #*********************************************************************** # %PROCEDURE: expand_ipv6_address # %ARGUMENTS: # addr -- an IPv6 address # %RETURNS: # An IPv6 address with all zero fields explicitly expanded, and # any field shorter than 4 hex digits padded out with zeros. #*********************************************************************** sub expand_ipv6_address { my ($addr) = @_; return '0000:0000:0000:0000:0000:0000:0000:0000' if ($addr eq '::'); if ($addr =~ /::/) { # Do nothing if more than one pair of colons return $addr if ($addr =~ /::.*::/); # Make sure we don't begin or end with :: $addr = "0000$addr" if $addr =~ /^::/; $addr .= '0000' if $addr =~ /::$/; # Count number of colons my $colons = ($addr =~ tr/:/:/); if ($colons < 8) { my $missing = ':' . ('0000:' x (8 - $colons)); $addr =~ s/::/$missing/; } } # Pad short fields return join(':', map { (length($_) < 4 ? ('0' x (4-length($_)) . $_) : $_) } (split(/:/, $addr))); } =item reverse_ip_address_for_rbl Method that returns the ip address in the appropriately-reversed format used for RBL lookups. =cut #*********************************************************************** # %PROCEDURE: reverse_ip_address_for_rbl # %ARGUMENTS: # addr -- an IPv4 or IPv6 address # %RETURNS: # The appropriately-reversed address for RBL lookups. #*********************************************************************** sub reverse_ip_address_for_rbl { my ($addr) = @_; if ($addr =~ /:/) { $addr = expand_ipv6_address($addr); $addr =~ s/://g; return join('.', reverse(split(//, $addr))); } return join('.', reverse(split(/\./, $addr))); } =item relay_is_blacklisted Method that returns the result of the lookup (eg 127.0.0.2). Parameters are the ip address of the relay host and the domain of the rbl server. =cut #*********************************************************************** # %PROCEDURE: relay_is_blacklisted # %ARGUMENTS: # addr -- IP address of relay host. # domain -- domain of blacklist server (eg: inputs.orbz.org) # %RETURNS: # The result of the lookup (eg 127.0.0.2) #*********************************************************************** sub relay_is_blacklisted { my($addr, $domain) = @_; $addr = reverse_ip_address_for_rbl($addr) . ".$domain"; my $hn = gethostbyname($addr); return 0 unless defined($hn); if (defined $hn) { return inet_ntoa($hn); } # Hostname is defined, but false -- return 1 instead. return 1; } =item email_is_blacklisted Method that returns the result of the lookup (eg 127.0.0.2). Parameters are an email address, the domain of the hashbl server, and the type of hashing (MD5 or SHA1). =cut #*********************************************************************** # %PROCEDURE: email_is_blacklisted # %ARGUMENTS: # email -- email address to check in hashbl. # domain -- domain of blacklist server (eg: ebl.msbl.org) # hash_type -- type of hash: MD5/SHA1 # %RETURNS: # The result of the lookup (eg 127.0.0.2) #*********************************************************************** sub email_is_blacklisted { my($email, $domain, $hash_type) = @_; my $hashed; if($Features{'Digest::MD5'} eq 1 and uc($hash_type) eq 'MD5') { $hashed = md5_hex($email); } elsif($Features{'Digest::SHA'} eq 1 and uc($hash_type) eq 'SHA1') { $hashed = sha1_hex($email); } else { md_syslog("Warning", "Invalid or unsupported hash type in email_is_blacklisted call"); return 0; } my $addr = $hashed . ".$domain"; my $hn = gethostbyname($addr); return 0 unless defined($hn); if (defined $hn) { return inet_ntoa($hn); } # Hostname is defined, but false -- return 1 instead. return 1; } #*********************************************************************** # %PROCEDURE: get_host_name # %ARGUMENTS: # None # %RETURNS: # Local host name, if it could be determined. #*********************************************************************** sub get_host_name { my ($PrivateMyHostName) = @_; # Use cached value if we have it return $PrivateMyHostName if defined($PrivateMyHostName); # Otherwise execute "hostname" $PrivateMyHostName = hostname; $PrivateMyHostName = "localhost" unless defined($PrivateMyHostName); # Now make it FQDN my($fqdn) = gethostbyname($PrivateMyHostName); $PrivateMyHostName = $fqdn if (defined $fqdn) and length($fqdn) > length($PrivateMyHostName); return $PrivateMyHostName; } =item is_public_ip4_address $ip_addr Returns true if $ip_addr is a publicly-routable IPv4 address, false otherwise =cut sub is_public_ip4_address { my ($addr) = @_; my @octets = split(/\./, $addr); # Sanity check: Return false if it's not an IPv4 address return 0 unless (scalar(@octets) == 4); foreach my $octet (@octets) { return 0 if ($octet !~ /^\d+$/); return 0 if ($octet > 255); } # 10.0.0.0 to 10.255.255.255 return 0 if ($octets[0] == 10); # 172.16.0.0 to 172.31.255.255 return 0 if ($octets[0] == 172 && $octets[1] >= 16 && $octets[1] <= 31); # 192.168.0.0 to 192.168.255.255 return 0 if ($octets[0] == 192 && $octets[1] == 168); # Loopback return 0 if ($octets[0] == 127); # Local-link for auto-DHCP return 0 if ($octets[0] == 169 && $octets[1] == 254); # IPv4 multicast return 0 if ($octets[0] >= 224 && $octets[0] <= 239); # Class E ("Don't Use") return 0 if ($octets[0] >= 240 && $octets[0] <= 247); # 0.0.0.0 and 255.255.255.255 are bogus return 0 if ($octets[0] == 0 && $octets[1] == 0 && $octets[2] == 0 && $octets[3] == 0); return 0 if ($octets[0] == 255 && $octets[1] == 255 && $octets[2] == 255 && $octets[3] == 255); return 1; } =item is_public_ip6_address $ip_addr Returns true if $ip_addr is a publicly-routable IPv6 address, false otherwise =cut sub is_public_ip6_address { my ($addr) = @_; my @octets = split(/\:/, $addr); # Unique-local address return 0 if $octets[0] =~ /^fd/i; # Link-local address return 0 if $octets[0] =~ /^fe80/i; return 1; } =item get_mx_ip_addresses $domain [$resolver_object] Get IP addresses of all MX hosts for given domain. If there are no MX hosts, then return A records. =cut sub get_mx_ip_addresses { my($domain, $res) = @_; my @results; unless ($Features{"Net::DNS"}) { md_syslog('err', "Attempted to call get_mx_ip_addresses, but Perl module Net::DNS is not installed"); return(@results); } if (!defined($res)) { $res = Net::DNS::Resolver->new; $res->defnames(0); } my $packet = $res->query($domain, 'MX'); if (!defined($packet) || $packet->header->rcode eq 'SERVFAIL' || $packet->header->rcode eq 'NXDOMAIN' || !defined($packet->answer)) { # No MX records; try A records $packet = $res->query($domain, 'A'); if (!defined($packet) || $packet->header->rcode eq 'SERVFAIL' || $packet->header->rcode eq 'NXDOMAIN' || !defined($packet->answer)) { return (@results); } } foreach my $item ($packet->answer) { if ($item->type eq 'MX') { # Weird MX record of "." or "" # host -t mx yahoo.com.pk for example if ($item->exchange eq '' || $item->exchange eq '.' || $item->exchange eq '0' || $item->exchange eq '0 ' || $item->exchange eq '0 .' || $item->exchange eq '0.') { push(@results, '0.0.0.0'); next; } # If it LOOKS like an IPv4 address, don't do # an A lookup if ($item->exchange =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.?$/) { my ($a, $b, $c, $d) = ($1, $2, $3, $4); if ($a <= 255 && $b <= 255 && $c <= 255 && $d <= 255) { push(@results, "$a.$b.$c.$d"); next; } } my $packet2 = $res->query($item->exchange, 'A'); next unless defined($packet2); next if $packet2->header->rcode eq 'SERVFAIL'; next if $packet2->header->rcode eq 'NXDOMAIN'; next unless defined($packet2->answer); foreach my $item2 ($packet2->answer) { if ($item2->type eq 'A') { push(@results, $item2->address); } } } elsif ($item->type eq 'A') { push(@results, $item->address); } } return (@results); } =item md_get_bogus_mx_hosts $domain Returns a list of "bogus" IP addresses that are in $domain's list of MX records. A "bogus" IP address is loopback/private/multicast/etc. =cut sub md_get_bogus_mx_hosts { my ($domain) = @_; my @bogus_hosts = (); my @mx = get_mx_ip_addresses($domain); foreach my $mx (@mx) { if (!is_public_ip4_address($mx)) { push(@bogus_hosts, $mx); } } return @bogus_hosts; } =item get_ptr_record $ip_address [$resolver_object] Get PTR record for given IP address. =cut sub get_ptr_record { my($ip, $res) = @_; unless ($Features{"Net::DNS"}) { md_syslog('err', "Attempted to call get_ptr_record, but Perl module Net::DNS is not installed"); return; } if (!defined($res)) { $res = Net::DNS::Resolver->new; $res->defnames(0); } my $packet = $res->query(reverse_ip_address_for_rbl($ip) . ".in-addr.arpa", 'PTR'); if (!defined($packet) || $packet->header->rcode eq 'SERVFAIL' || $packet->header->rcode eq 'NXDOMAIN' || !defined($packet->answer)) { return; } my $answer = ($packet->answer)[0]; if(defined $answer) { my $res = $answer->rdstring; $res =~ s/\.$//; return $res; } return; } =item relay_is_blacklisted_multi Method that rerurns a hash table with one entry per original domain. Entries in hash will be: C<{ $domain = $return }>, where $return is one of SERVFAIL, NXDOMAIN or a list of IP addresses as a dotted-quad. =cut #*********************************************************************** # %PROCEDURE: relay_is_blacklisted_multi # %ARGUMENTS: # addr -- IP address of relay host. # timeout -- number of seconds after which to time out # answers_wanted -- if positive, return as soon as this many positive answers # have been received. # domains -- an array of domains to check # res (optional) -- A Net::DNS::Resolver object. If you don't pass # one in, we'll generate one and use it. # %RETURNS: # A hash table with one entry per original domain. Entries in hash # will be: # { $domain => $return }, where $return is one of SERVFAIL, NXDOMAIN or # a list of IP addresses as a dotted-quad. #*********************************************************************** sub relay_is_blacklisted_multi { my($addr, $timeout, $answers_wanted, $domains, $res) = @_; my $sock; my $ans = {}; my $positive_answers = 0; foreach my $domain (@{$domains}) { $ans->{$domain} = 'SERVFAIL'; } unless ($Features{"Net::DNS"}) { md_syslog('err', "Attempted to call relay_is_blacklisted_multi, but Perl module Net::DNS is not installed"); return $ans; } push_status_tag("Doing RBL Lookup"); my %sock_to_domain; # Reverse the address $addr = reverse_ip_address_for_rbl($addr); # If user did not pass in a Net::DNS::Resolver object, generate one. unless (defined($res and (UNIVERSAL::isa($res, "Net::DNS::Resolver")))) { $res = Net::DNS::Resolver->new; $res->defnames(0); } my $sel = IO::Select->new(); # Send out the queries foreach my $domain (@{$domains}) { $sock = $res->bgsend("$addr.$domain", 'A'); $sock_to_domain{$sock} = $domain; $sel->add($sock); } # Now wait for them to come back. my $terminate = time() + $timeout; while (time() <= $terminate) { my $expire = $terminate - time(); # Avoid fractional wait for select which gets truncated. # So we may end up timing out after 1 extra second... no big deal $expire = 1 if ($expire < 1); my @ready; @ready = $sel->can_read($expire); foreach my $rsock (@ready) { my $pack = $res->bgread($rsock); $sel->remove($sock); my $sdomain = $sock_to_domain{$rsock}; undef($rsock); my($rr, $rcode); $rcode = $pack->header->rcode; if ($rcode eq "SERVFAIL" or $rcode eq "NXDOMAIN") { $ans->{$sdomain} = $rcode; next; } my $got_one = 0; foreach my $rr ($pack->answer) { if ($rr->type eq 'A') { $got_one = 1; if ($ans->{$sdomain} eq "SERVFAIL") { $ans->{$sdomain} = (); } push(@{$ans->{$sdomain}}, $rr->address); } } $positive_answers++ if ($got_one); } last if ($sel->count() == 0 or ($answers_wanted > 0 and $positive_answers >= $answers_wanted)); } pop_status_tag(); return $ans; } =item relay_is_blacklisted_multi_count Method that returns a number indicating how many RBLs the host was blacklisted in. =cut #*********************************************************************** # %PROCEDURE: relay_is_blacklisted_multi_count # %ARGUMENTS: # addr -- IP address of relay host. # timeout -- number of seconds after which to time out # answers_wanted -- if positive, return as soon as this many positive answers # have been received. # domains -- an array of domains to check # res (optional) -- A Net::DNS::Resolver object. If you don't pass # one in, we'll generate one and use it. # %RETURNS: # A number indicating how many RBLs the host was blacklisted in. #*********************************************************************** sub relay_is_blacklisted_multi_count { my($addr, $timeout, $answers_wanted, $domains, $res) = @_; my $ans = relay_is_blacklisted_multi($addr, $timeout, $answers_wanted, $domains, $res); my $count = 0; foreach my $domain (keys(%$ans)) { my $r = $ans->{$domain}; if (ref($r) eq "ARRAY" and $#{$r} >= 0) { $count++; } } return $count; } =item relay_is_blacklisted_multi_list Method that returns an array indicating the domains in which the relay is blacklisted. =cut #*********************************************************************** # %PROCEDURE: relay_is_blacklisted_multi_list # %ARGUMENTS: # addr -- IP address of relay host. # timeout -- number of seconds after which to time out # answers_wanted -- if positive, return as soon as this many positive answers # have been received. # domains -- an array of domains to check # res (optional) -- A Net::DNS::Resolver object. If you don't pass # one in, we'll generate one and use it. # %RETURNS: # An array indicating the domains in which the relay is blacklisted. #*********************************************************************** sub relay_is_blacklisted_multi_list { my($addr, $timeout, $answers_wanted, $domains, $res) = @_; my $ans = relay_is_blacklisted_multi($addr, $timeout, $answers_wanted, $domains, $res); my $result = []; foreach my $domain (keys(%$ans)) { my $r = $ans->{$domain}; if (ref($r) eq "ARRAY" and $#{$r} >= 0) { push @$result, $domain; } } # If in list context, return the array. Otherwise, return # array reference. return (wantarray ? @$result : $result); } =back =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/RFC2822.pm000066400000000000000000000066051475763067200217300ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::RFC2822 - Dates related methods for email filters =head1 DESCRIPTION Mail::MIMEDefang::RFC2822 are a set of methods that can be called from F to create RFC2822 formatted dates. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::RFC2822; use strict; use warnings; use Time::Local; use Mail::MIMEDefang; use Mail::MIMEDefang::Net; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(gen_date_msgid_headers); our @EXPORT_OK = qw(header_timezone rfc2822_date gen_msgid_header); =item gen_date_msgid_headers Method that generates RFC2822 compliant Date and Message-ID headers. =cut #*********************************************************************** # %PROCEDURE: gen_date_msgid_headers # %ARGUMENTS: # None # %RETURNS: # A string like this: "Date: \nMessage-ID: \n" # %DESCRIPTION: # Generates RFC2822-compliant Date and Message-ID headers. #*********************************************************************** sub gen_date_msgid_headers { my ($msgid_header) = @_; if(not defined $msgid_header) { $msgid_header = gen_msgid_header($QueueID, Mail::MIMEDefang::Net::get_host_name($PrivateMyHostName)); } return "Date: " . rfc2822_date() . "\n" . $msgid_header; } =item rfc2822_date Method that returns an RFC2822 formatted date. =cut sub rfc2822_date { my ($CachedTimezone) = @_; my $now = time(); my ($ss, $mm, $hh, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($now); return sprintf("%s, %02d %s %04d %02d:%02d:%02d %s", (qw( Sun Mon Tue Wed Thu Fri Sat ))[$wday], $mday, (qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ))[$mon], $year + 1900, $hh, $mm, $ss, header_timezone($CachedTimezone, $now) ); } =item header_timezone Method that returns an RFC2822 compliant timezone header. =cut sub header_timezone { my ($CachedTimezone, $now) = @_; return $CachedTimezone if ((defined $CachedTimezone) and ($CachedTimezone ne "")); my($sec, $min, $hr, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($now); my $a = timelocal($sec, $min, $hr, $mday, $mon, $year); my $b = timegm($sec, $min, $hr, $mday, $mon, $year); my $c = ($b - $a) / 60; $hr = int(abs($c) / 60); $min = abs($c) - 60 * $hr; if ($c >= 0) { $CachedTimezone = sprintf("+%02d%02d", $hr, $min); } else { $CachedTimezone = sprintf("-%02d%02d", $hr, $min); } return $CachedTimezone; } =item gen_msgid_header Method that generates RFC2822 compliant Message-ID headers. =cut #*********************************************************************** # %PROCEDURE: gen_msgid_header # %ARGUMENTS: # None # %RETURNS: # A string like this: "Message-ID: \n" # %DESCRIPTION: # Generates RFC2822-compliant Message-ID headers. #*********************************************************************** sub gen_msgid_header { my ($QueueID, $hostname) = @_; my ($ss, $mm, $hh, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time); # Generate a "random" message ID that looks # similiar to sendmail's for SpamAssassin comparing # Received / MessageID QueueID return sprintf("Message-ID: <%04d%02d%02d%02d%02d.%s\@%s>\n", $year + 1900, $mon + 1, $mday, $hh, $mm, ($QueueID eq 'NOQUEUE' ? rand() : $QueueID), $hostname ); } =back =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/SPF.pm000066400000000000000000000054531475763067200214300ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::SPF - Sender Policy Framework interface for MIMEDefang =head1 DESCRIPTION Mail::MIMEDefang::SPF is a module used to check for Sender Policy Framework headers from F. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::SPF; use strict; use warnings; require Exporter; use Mail::SPF; our @ISA = qw(Exporter); our @EXPORT; our @EXPORT_OK; @EXPORT = qw(md_spf_verify); =item md_spf_verify Returns code and explanation of Sender Policy Framework check. Possible return code values are: "pass", "fail", "softfail", "neutral", "none", "error", "permerror", "temperror", "invalid" The method accepts the following parameters: =over 4 =item C<$email> The email address of the sender =item C<$relayip> The relay ip address =item C<$helo> (optional) The MTA helo server name =back =cut sub md_spf_verify { my ($spfmail, $relayip, $helo) = @_; if(not defined $spfmail and not defined $relayip) { md_syslog('err', "Cannot check SPF without email address and relay ip"); return; } # RFC 4408 defines the maximum number of terms (mechanisms and modifiers) per SPF check that perform DNS look-ups # to 10. # In practice 10 lookups are not enough for some domains. my $spf_server = Mail::SPF::Server->new(max_dns_interactive_terms => 20); my ($spfres, $helo_spfres); $spfmail =~ s/^$//; if(defined $spfmail and $spfmail ne '') { if($spfmail =~ /(.*)\+(?:.*)\@(.*)/) { $spfmail = $1 . '@' . $2; } my $request; eval { local $SIG{__WARN__} = sub { my $warn = $_[0]; $warn =~ s/\n//g; $warn =~ s/\bat .{10,100} line \d+\.//g; md_syslog("Warning", "md_spf_verify: $warn"); }; $request = Mail::SPF::Request->new( scope => 'mfrom', identity => $spfmail, ip_address => $relayip, ); $spfres = $spf_server->process($request); }; } else { return ('invalid', 'Invalid mail from parameter'); } if(defined $helo) { my $helo_request; eval { local $SIG{__WARN__} = sub { my $warn = $_[0]; $warn =~ s/\n//g; $warn =~ s/\bat .{10,100} line \d+\.//g; md_syslog("Warning", "md_spf_verify: $warn"); }; $helo_request = Mail::SPF::Request->new( scope => 'helo', identity => $helo, ip_address => $relayip, ); $helo_spfres = $spf_server->process($helo_request); }; } if(defined $helo) { return ($spfres->code, $spfres->local_explanation, $helo_spfres->code, $helo_spfres->local_explanation); } else { return ($spfres->code, $spfres->local_explanation); } } =back =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/Unit.pm000066400000000000000000000040521475763067200217110ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::Unit - Methods used by MIMEDefang regression tests =head1 DESCRIPTION Mail::MIMEDefang::Unit are a set of methods that are called from MIMEDefang regression tests. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::Unit; use strict; use warnings; use Test::Class; use base qw( Test::Class ); use Net::SMTP; use Test::Most; =item include_mimedefang Method that includes F code without running anything. =cut # This bit of evil is how we pull in MIMEDefang's .pl code without running anything. sub include_mimedefang : Test(startup) { no warnings 'redefine'; local *CORE::GLOBAL::exit = sub { }; local @ARGV = (); do './mimedefang.pl.in'; use warnings 'redefine'; } =item smtp_mail Method which sends a test email and returns SMTP replies. =cut sub smtp_mail { my ($from, $to, $filemail) = @_; my $messages = ''; return 0 if not -f $filemail; open my $fh, '<', $filemail or return 0; my $mailcnt = do { local $/; <$fh> }; close($fh); my @email = split(/\@/, $from); my $smtp = Net::SMTP->new("localhost", Hello => $email[1], Timeout => 10, Debug => 0, ); return "Connection error" if not defined $smtp; $smtp->mail(''); $smtp->to("$to\n"); $messages .= $smtp->message(); $smtp->data(); $messages .= $smtp->message(); $smtp->datasend($mailcnt); $messages .= $smtp->message(); $smtp->dataend; $messages .= $smtp->message(); $smtp->quit; $messages .= $smtp->message(); undef $mailcnt; undef $smtp; undef $fh; return $messages; } =item get_abs_path Method which returns the absolute path of a file by reading $PATH. =cut sub get_abs_path { my $prog = shift; my $full_path; for my $dir (split(':', $ENV{PATH})) { $full_path = "$dir/$prog"; if (-x $full_path) { return $full_path; } } return; } =back =cut 1; mimedefang-3.6/modules/lib/Mail/MIMEDefang/Utils.pm000066400000000000000000000375071475763067200221050ustar00rootroot00000000000000# # This program may be distributed under the terms of the GNU General # Public License, Version 2. # =head1 NAME Mail::MIMEDefang::Utils - Support methods used internally or by email filters =head1 DESCRIPTION Mail::MIMEDefang::Utils are a set of methods that can be called from F or by other methods. =head1 METHODS =over 4 =cut package Mail::MIMEDefang::Utils; use strict; use warnings; use Carp; use MIME::Words qw(:all); use Mail::MIMEDefang; use Mail::MIMEDefang::RFC2822; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(time_str date_str hour_str synthesize_received_header copy_or_link re_match re_match_ext re_match_in_rar_directory re_match_in_zip_directory re_match_in_7zip_directory re_match_in_tgz_directory md_copy_orig_msg_to_work_dir_as_mbox_file read_results); our @EXPORT_OK = qw(md_init gen_mx_id); =item time_str Method that returns a string representing the current time. =cut #*********************************************************************** # %PROCEDURE: time_str # %ARGUMENTS: # None # %RETURNS: # The current time in the form: "YYYY-MM-DD-HH:mm:ss" # %DESCRIPTION: # Returns a string representing the current time. #*********************************************************************** sub time_str { my($sec, $min, $hour, $mday, $mon, $year, $junk); ($sec, $min, $hour, $mday, $mon, $year, $junk) = localtime(time()); return sprintf("%04d-%02d-%02d-%02d.%02d.%02d", $year + 1900, $mon+1, $mday, $hour, $min, $sec); } =item hour_str Method that returns a string representing the current hour. =cut #*********************************************************************** # %PROCEDURE: hour_str # %ARGUMENTS: # None # %RETURNS: # The current time in the form: "YYYY-MM-DD-HH" # %DESCRIPTION: # Returns a string representing the current time. #*********************************************************************** sub hour_str { my($sec, $min, $hour, $mday, $mon, $year, $junk); ($sec, $min, $hour, $mday, $mon, $year, $junk) = localtime(time()); return sprintf('%04d-%02d-%02d-%02d', $year+1900, $mon+1, $mday, $hour); } =item synthesize_received_header Method that synthesizes a valid Received: header to reflect re-mailing. Needed by Apache SpamAssassin to correctly parse email messages. =cut #*********************************************************************** # %PROCEDURE: synthesize_received_header # %ARGUMENTS: # None # %RETURNS: # A "Received:" header for current message # %DESCRIPTION: # Synthesizes a valid Received: header to reflect re-mailing. #*********************************************************************** sub synthesize_received_header { my($hdr); my($hn) = $SendmailMacros{"if_name"}; my($auth) = $SendmailMacros{"auth_authen"}; my($tls_version) = $SendmailMacros{"tls_version"}; my $strdate = Mail::MIMEDefang::RFC2822::rfc2822_date($CachedTimezone); $hn = Mail::MIMEDefang::Net::get_host_name() unless (defined($hn) and ($hn ne "")); my $relay = $RealRelayHostname; if($relay eq "") { $relay = Mail::MIMEDefang::Net::get_ptr_record($RealRelayAddr); } if ((defined $relay) and ($relay ne "[$RealRelayAddr]")) { $hdr = "Received: from $Helo ($relay [$RealRelayAddr])\n"; } else { $hdr = "Received: from $Helo ([$RealRelayAddr])\n"; } if($auth) { $hdr .= "\tby $hn (envelope-sender $Sender) (MIMEDefang) with ESMTP"; if($tls_version) { $hdr .= "S"; } $hdr .= "A id $MsgID"; } else { $hdr .= "\tby $hn (envelope-sender $Sender) (MIMEDefang) with ESMTP"; if($tls_version) { $hdr .= "S"; } $hdr .= " id $MsgID"; } if ($#Recipients != 0) { $hdr .= "; "; } else { $hdr .= "\n\tfor " . $Recipients[0] . "; "; } $hdr .= $strdate . "\n"; return $hdr; } =item copy_or_link Method that copies a file if it fails to create an hard link to the original file. =cut #*********************************************************************** # %PROCEDURE: copy_or_link # %ARGUMENTS: # src -- source filename # dest -- destination filename # %RETURNS: # 1 on success; 0 on failure. # %DESCRIPTION: # Copies a file: First, attempts to make a hard link. If that fails, # reads the file and copies the data. #*********************************************************************** sub copy_or_link { my($src, $dst) = @_; return 1 if link($src, $dst); # Link failed; do it the hard way my ($in,$out); open($in, "<", "$src") or return 0; if (!open($out, ">", "$dst")) { close($in); return 0; } my($n, $string); while (($n = read($in, $string, 4096)) > 0) { print $out $string; } close($in); close($out); return 1; } =item read_results Method that extracts an array of command, key, values from RESULTS file, needed for regression tests. =cut #*********************************************************************** # %PROCEDURE: read_results # %ARGUMENTS: # %RETURNS: # array of extracted from RESULTS file. # %DESCRIPTION: # Extracts an array of command, key, values from RESULTS file, # needed for regression tests #*********************************************************************** sub read_results { my ($line, @res); my $fh = IO::File->new('./RESULTS', "r"); while($line = <$fh>) { if($line =~ /([A-Z]{1})([A-Z-]+)\s([^\s]+)\s(.+)/i) { push(@res, [$1, $2, percent_decode($3), percent_decode($4)]); } elsif($line =~ /([A-Z]{1})([A-Z-]+)\s(.+)/i) { push(@res, [$1, $2, percent_decode($3)]); } } undef $fh; return @res; } =item re_match Method that returns 1 if either Content-Disposition.filename or Content-Type.name matches the regexp; 0 otherwise. =cut #*********************************************************************** # %PROCEDURE: re_match # %ARGUMENTS: # entity -- a MIME entity # regexp -- a regular expression # %RETURNS: # 1 if either of Content-Disposition.filename or Content-Type.name # matches regexp; 0 otherwise. Matching is # case-insensitive # %DESCRIPTION: # A helper function for filter. #*********************************************************************** sub re_match { my($entity, $regexp) = @_; my($head) = $entity->head; my($guess) = $head->mime_attr("Content-Disposition.filename"); if (defined($guess)) { $guess = decode_mimewords($guess); return 1 if $guess =~ /$regexp/i; } $guess = $head->mime_attr("Content-Type.name"); if (defined($guess)) { $guess = decode_mimewords($guess); return 1 if $guess =~ /$regexp/i; } return 0; } =item re_match_ext Method that returns 1 if the EXTENSION part of either Content-Disposition.filename or Content-Type.name matches regexp; 0 otherwise. =cut #*********************************************************************** # %PROCEDURE: re_match_ext # %ARGUMENTS: # entity -- a MIME entity # regexp -- a regular expression # %RETURNS: # 1 if the EXTENSION part of either of Content-Disposition.filename or # Content-Type.name matches regexp; 0 otherwise. # Matching is case-insensitive. # %DESCRIPTION: # A helper function for filter. #*********************************************************************** sub re_match_ext { my($entity, $regexp) = @_; my($ext); my($head) = $entity->head; my($guess) = $head->mime_attr("Content-Disposition.filename"); if (defined($guess)) { $guess = decode_mimewords($guess); return 1 if (($guess =~ /(\.[^.]*)$/) && ($1 =~ /$regexp/i)); } $guess = $head->mime_attr("Content-Type.name"); if (defined($guess)) { $guess = decode_mimewords($guess); return 1 if (($guess =~ /(\.[^.]*)$/) && ($1 =~ /$regexp/i)); } return 0; } =item re_match_in_rar_directory Method that returns 1 if the EXTENSION part of any file in the rar archive matches regexp. To enable the check $Features{'unrar'} should be enabled. =cut #*********************************************************************** # %PROCEDURE: re_match_in_rar_directory # %ARGUMENTS: # fname -- name of RAR file # regexp -- a regular expression # %RETURNS: # 1 if the EXTENSION part of any file in the rar archive matches regexp # Matching is case-insensitive. # %DESCRIPTION: # A helper function for filter. #*********************************************************************** sub re_match_in_rar_directory { my($rarname, $regexp) = @_; my ($rf, $beginmark, $file); my @unrar_args = ("unrar", "l", "-c-", "-p-", "-idcdp", $rarname); unless ($Features{"unrar"}) { md_syslog('err', "Attempted to use re_match_in_rar_directory, but unrar binary is not installed."); return 0; } if ( -f $rarname ) { open(my $unrar_pipe, "-|", @unrar_args) || croak "can't open @unrar_args|: $!"; while(<$unrar_pipe>) { $rf = $_; if ( $beginmark and ( $rf !~ /^\-\-\-/ ) ) { $rf =~ /(?:[12]\d{3}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01]))\s(?:\d+\:\d+)\s+(.*)/; $file = $1; return 1 if ((defined $file) and ($file =~ /$regexp/i)); } last if ( $beginmark and ( $rf !~ /^\-\-\-/ ) ); $beginmark = 1 if ( $rf =~ /^\-\-\-/ ); } close($unrar_pipe); } return 0; } =item re_match_in_7zip_directory Method that returns 1 if the EXTENSION part of any file in the 7zip archive matches regexp. To enable the check $Features{'7zip'} should be enabled. =cut #*********************************************************************** # %PROCEDURE: re_match_in_7zip_directory # %ARGUMENTS: # fname -- name of 7zip file # regexp -- a regular expression # %RETURNS: # 1 if the EXTENSION part of any file in the 7zip archive matches regexp # Matching is case-insensitive. # %DESCRIPTION: # A helper function for filter. #*********************************************************************** sub re_match_in_7zip_directory { my($zname, $regexp) = @_; my ($rf, $beginmark, $file); my @unz_args = ("7za", "l", $zname); unless ($Features{"7zip"}) { md_syslog('err', "Attempted to use re_match_in_7zip_directory, but 7zip binary is not installed."); return 0; } if ( -f $zname ) { open(my $unz_pipe, "-|", @unz_args) || croak "can't open @unz_args|: $!"; while(<$unz_pipe>) { $rf = $_; if ( $beginmark and ( $rf !~ /^\-\-\-/ ) ) { $rf =~ /(?:[0-9-]+)\s+(?:[0-9\:]+)\s+(?:[\.[A-Za-z]+)\s+(?:[0-9]+)\s+(?:[0-9]+)\s+(.*)/; $file = $1; return 1 if ((defined $file) and ($file =~ /$regexp/i)); } last if ( $beginmark and ( $rf !~ /^\-\-\-/ ) ); $beginmark = 1 if ( $rf =~ /^\-\-\-/ ); } close($unz_pipe); } return 0; } =item re_match_in_tgz_directory Method that returns 1 if the EXTENSION part of any file in the tgz archive matches regexp. To enable the check $Features{'tar'} should be enabled. =cut #*********************************************************************** # %PROCEDURE: re_match_in_tgz_directory # %ARGUMENTS: # fname -- name of tgz file # regexp -- a regular expression # %RETURNS: # 1 if the EXTENSION part of any file in the tgz archive matches regexp # Matching is case-insensitive. # %DESCRIPTION: # A helper function for filter. #*********************************************************************** sub re_match_in_tgz_directory { my($zname, $regexp) = @_; my ($rf, $beginmark, $file); my @unz_args; if($zname =~ /\.tar/) { @unz_args = ("tar", "tvf", $zname); } elsif($zname =~ /\.tar\.bz2/) { @unz_args = ("tar", "jtvf", $zname); } elsif($zname =~ /\.(?:tar\.gz|tgz)/) { @unz_args = ("tar", "ztvf", $zname); } else { md_syslog('err', "Attempted to use re_match_in_tgz_directory, but filename $zname is not supported."); return 0; } unless ($Features{"tar"}) { md_syslog('err', "Attempted to use re_match_in_tgz_directory, but tar binary is not installed."); return 0; } if ( -f $zname ) { open(my $tar_pipe, "-|", @unz_args) || croak "can't open @unz_args|: $!"; while(<$tar_pipe>) { if(/(?:[a-z-]+)\s+(?:.*)\s+(?:\d+)\s+(?:\d+\-\d+\-\d+)\s+(?:\d+\:\d+)\s+(.*)/) { $file = $1; return 1 if ((defined $file) and ($file =~ /$regexp/i)); } } close($tar_pipe); } return 0; } =item re_match_in_zip_directory Method that returns 1 if the EXTENSION part of any file in the zip archive matches regexp. =cut #*********************************************************************** # %PROCEDURE: re_match_in_zip_directory # %ARGUMENTS: # fname -- name of ZIP file # regexp -- a regular expression # %RETURNS: # 1 if the EXTENSION part of any file in the zip archive matches regexp # Matching is case-insensitive. # %DESCRIPTION: # A helper function for filter. #*********************************************************************** sub dummy_zip_error_handler {} ; sub md_init { local $@; my $use_zip = 0; if (!defined($Features{"Archive::Zip"}) or ($Features{"Archive::Zip"} eq 1)) { eval { require Archive::Zip; }; if($@) { $use_zip = 0; } else { Archive::Zip->import(qw(:ERROR_CODES)); $use_zip = 1; } } $Features{"Archive::Zip"} = $use_zip; } sub re_match_in_zip_directory { my($zipname, $regexp) = @_; unless ($Features{"Archive::Zip"}) { md_syslog('err', "Attempted to use re_match_in_zip_directory, but Perl module Archive::Zip is not installed."); return 0; } my $zip = Archive::Zip->new(); # Prevent carping about errors Archive::Zip::setErrorHandler(\&dummy_zip_error_handler); if ($zip->read($zipname) == AZ_OK()) { foreach my $member ($zip->members()) { my $file = $member->fileName(); return 1 if ($file =~ /$regexp/i); } } return 0; } use strict 'subs'; =item md_copy_orig_msg_to_work_dir_as_mbox_file Method that copies original INPUTMSG file into work directory for virus-scanning as a valid mbox file. =cut #*********************************************************************** # %PROCEDURE: md_copy_orig_msg_to_work_dir_as_mbox_file # %ARGUMENTS: # None # %DESCRIPTION: # Copies original INPUTMSG file into work directory for virus-scanning # as a valid mbox file (adds the "From $Sender mumble..." stuff.) # %RETURNS: # 1 on success, 0 on failure. #*********************************************************************** sub md_copy_orig_msg_to_work_dir_as_mbox_file { return if (!in_message_context("md_copy_orig_msg_to_work_dir_as_mbox_file")); my ($in, $out); open($in, "<", "INPUTMSG") or return 0; unless (open($out, ">", "Work/INPUTMBOX")) { close($in); return 0; } # Remove angle-brackets for From_ line my $s = $Sender; $s =~ s/^$//; print $out "From $s " . Mail::MIMEDefang::RFC2822::rfc2822_date($CachedTimezone) . "\n"; my($n, $string); while (($n = read($in, $string, 4096)) > 0) { print $out $string; } close($in); close($out); return 1; } =item gen_mx_id Method that generates a random indentifier used by MIMEDefang to create temporary files. =cut #*********************************************************************** # %PROCEDURE: gen_mx_id # %ARGUMENTS: # None # %DESCRIPTION: # Generates a random indentifier # %RETURNS: # the random string. #*********************************************************************** sub gen_mx_id { my @out; my $counter; # Our identifiers are in base-60 use constant BASE => 60; # Our time-based identifier is 5 base-60 characters. use constant TIMESPAN => 60*60*60*60*60; # Our identifier is 7 chars long use constant MX_ID_LEN => 7; $counter++; my @char_map = split(//, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX"); my $time_part = time % TIMESPAN; for (my $i=4; $i>=0; $i--) { $out[$i] = $char_map[$time_part % BASE]; $time_part /= BASE; } for (my $i=6; $i>=5; $i--) { $out[$i] = $char_map[$counter % BASE]; $counter /= BASE; } $out[MX_ID_LEN - 1] = 0; return join('', @out); } =back =cut 1; mimedefang-3.6/notifier.c000066400000000000000000000224751475763067200154740ustar00rootroot00000000000000/*********************************************************************** * * notifier.c * * Notification routines for multiplexor * * Copyright (C) 2002-2005 Roaring Penguin Software Inc. * http://www.roaringpenguin.com * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #include "config.h" #include "event_tcp.h" #include "mimedefang.h" #include #include #include #define MAX_LISTENERS 5 #define MAX_MSGLEN 256 #define NUM_MSG_TYPES 26 /* Message types range from 'A' to 'Z' */ typedef struct Listener_t { int fd; char msg[MAX_MSGLEN+1]; char msg_types[NUM_MSG_TYPES]; EventTcpState *read_ev; EventTcpState *write_ev; } Listener; static int ListenersInitialized = 0; static void handle_notifier_accept(EventSelector *es, int fd); extern EventTcpState *reply_to_mimedefang(EventSelector *es, int fd, char const *msg); static void notify(EventSelector *es, Listener *l, char const *msg); static void notify_complete(EventSelector *es, int fd, char *buf, int len, int flag, void *data); static void handle_notify_input(EventSelector *es, int fd, char *buf, int len, int flag, void *data); static void close_listener(Listener *l); static EventTcpState *setup_read_event(Listener *l, EventSelector *es); void notify_listeners(EventSelector *es, char const *msg); Listener listeners[MAX_LISTENERS]; /********************************************************************** * %FUNCTION: make_notifier_socket * %ARGUMENTS: * es -- an EventSelector * name -- name of socket to create * %DESCRIPTION: * Makes a listening socket for programs that want notifications to connect to * %RETURNS: * 0 on success; -1 on failure. ***********************************************************************/ int make_notifier_socket(EventSelector *es, char const *name) { int sock; int i; sock = make_listening_socket(name, 5, 0); if (sock < 0) { return -1; } if(set_cloexec(sock) < 0) { syslog(LOG_ERR, "Could not set FD_CLOEXEC option on socket"); close(sock); return -1; } if (!EventTcp_CreateAcceptor(es, sock, handle_notifier_accept)) { syslog(LOG_ERR, "Could not listen for notifier requests: EventTcp_CreateAcceptor: %m"); close(sock); return -1; } /* Initialize listeners */ for (i=0; ifd = fd; memset(l->msg_types, 0, NUM_MSG_TYPES); l->msg[0] = 0; l->write_ev = NULL; /* Try making a read event */ if (!setup_read_event(l, es)) { return; } /* Issue banner */ notify(es, l, "*OK\n"); } /********************************************************************** * %FUNCTION: close_listener * %ARGUMENTS: * l -- listener who needs closing * %DESCRIPTION: * Closes a listener * %RETURNS: * Nothing ***********************************************************************/ static void close_listener(Listener *l) { if (l->read_ev) { EventTcp_CancelPending(l->read_ev); l->read_ev = NULL; } if (l->write_ev) { EventTcp_CancelPending(l->write_ev); l->write_ev = NULL; } l->msg[0] = 0; if (l->fd >= 0) { close(l->fd); l->fd = -1; } } /********************************************************************** * %FUNCTION: notify * %ARGUMENTS: * es -- an EventSelector * l -- listener who needs notification * msg -- message to send to listener * %DESCRIPTION: * Sends a message to listener. * %RETURNS: * Nothing ***********************************************************************/ static void notify(EventSelector *es, Listener *l, char const *msg) { EventTcpState *e; size_t len = strlen(msg); if (l->fd < 0) { return; } /* Are we interested in this message type? */ if (*msg >= 'A' && *msg <= 'Z' && !l->msg_types[(*msg)-'A']) { return; } /* If busy: Save message for later! */ if (l->write_ev) { /* If room to append, append it; otherwise replace all messages */ if (strlen(l->msg) + len <= MAX_MSGLEN) { strcat(l->msg, msg); return; } if (strlen(msg) > MAX_MSGLEN) { syslog(LOG_ERR, "notify failed: message size is bigger then allowed"); close_listener(l); return; } strcpy(l->msg, msg); return; } e = EventTcp_WriteBuf(es, l->fd, msg, strlen(msg), notify_complete, 10, l); if (!e) { syslog(LOG_ERR, "notify failed: EventTcp_WriteBuf: %m"); close_listener(l); return; } l->write_ev = e; } /********************************************************************** * %FUNCTION: notify_complete * %ARGUMENTS: * es -- an EventSelector * fd -- file descriptor * buf -- buffer that was written to listener * len -- amount of data writeen to listener * flag -- flag indicating what happened * data -- the listener * %DESCRIPTION: * Called when a message has been sent to the listener * %RETURNS: * Nothing ***********************************************************************/ static void notify_complete(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { Listener *l = (Listener *) data; l->write_ev = NULL; if (flag == EVENT_TCP_FLAG_TIMEOUT || flag == EVENT_TCP_FLAG_IOERROR) { close_listener(l); return; } if (l->msg[0]) { /* Queued message -- send it now */ notify(es, l, l->msg); l->msg[0] = 0; } } static EventTcpState * setup_read_event(Listener *l, EventSelector *es) { l->read_ev = EventTcp_ReadBuf(es, l->fd, MAX_MSGLEN, '\n', handle_notify_input, 0, 1, l); if (!l->read_ev) { reply_to_mimedefang(es, l->fd, "*ERR Unable to make reader event\n"); l->fd = -1; close_listener(l); } return l->read_ev; } /********************************************************************** * %FUNCTION: handle_notify_input * %ARGUMENTS: * es -- an EventSelector * fd -- file descriptor * buf -- buffer that was written to listener * len -- amount of data writeen to listener * flag -- flag indicating what happened * data -- the listener * %DESCRIPTION: * Called when a message has been sent *from* the listener * %RETURNS: * Nothing ***********************************************************************/ static void handle_notify_input(EventSelector *es, int fd, char *buf, int len, int flag, void *data) { Listener *l = (Listener *) data; char *s; l->read_ev = NULL; if (flag == EVENT_TCP_FLAG_TIMEOUT || flag == EVENT_TCP_FLAG_IOERROR) { close_listener(l); return; } /* EOF? */ if (!len) { close_listener(l); return; } /* Null-terminate buffer */ buf[len-1] = 0; len--; /* Only accept "?" command */ if (buf[0] != '?') { setup_read_event(l, es); return; } memset(l->msg_types, 0, NUM_MSG_TYPES); s = buf+1; while(*s) { if (*s >= 'A' && *s <= 'Z') { l->msg_types[(*s) - 'A'] = 1; } if (*s == '*') { memset(l->msg_types, 1, NUM_MSG_TYPES); } s++; } setup_read_event(l, es); } /********************************************************************** * %FUNCTION: notify_listeners * %ARGUMENTS: * es -- event selector * msg -- message to send to all listeners * %DESCRIPTION: * Notifies all listeners of a message * %RETURNS: * Nothing ***********************************************************************/ void notify_listeners(EventSelector *es, char const *msg) { int i; if (!ListenersInitialized) return; for (i=0; i= 0) { notify(es, &listeners[i], msg); } } } /********************************************************************** * %FUNCTION: notify_worker_status * %ARGUMENTS: * es -- event selector * workerno -- worker number * msg -- worker's new status * %DESCRIPTION: * Notifies all listeners of a message * %RETURNS: * Nothing ***********************************************************************/ void notify_worker_status(EventSelector *es, int workerno, char const *msg) { char buf[256]; if (!ListenersInitialized) return; if (!msg || !*msg) { return; } snprintf(buf, sizeof(buf), "S %d %.200s\n", workerno, msg); notify_listeners(es, buf); } void notify_worker_state_change(EventSelector *es, int workerno, char const *old_state, char const *new_state) { char buf[256]; if (!ListenersInitialized) return; snprintf(buf, sizeof(buf), "S %d StateChange %s -> %s\n", workerno, old_state, new_state); notify_listeners(es, buf); } mimedefang-3.6/redhat/000077500000000000000000000000001475763067200147465ustar00rootroot00000000000000mimedefang-3.6/redhat/mimedefang-init.in000066400000000000000000000234711475763067200203420ustar00rootroot00000000000000#!/bin/bash ########################################################################## # Copyright @2002, Roaring Penguin Software Inc. All rights reserved. # # * This program may be distributed under the terms of the GNU General # * Public License, Version 2. # # Project : MIMEDefang # Component : redhat/mimedefang-init # Author : Michael McLagan # Creation : 30-Apr-2002 13:42 # Description : This is the init script for the RedHat RPM. It lives # in /etc/rc.d/init.d as mimedefang and is called by # init during system startup. # # Uses redhat/mimedefang-sysconfig (/etc/sysconfig/mimedefang) # to set various variables used as parameters to start the # mimedefang and mimedefang-multiplexor daemons. # # Based on init scripts provided by RedHat and others. # # mimedefang should be started before sendmail and stopped # after sendmail. The values in the chkconfig: line below # are based on those in the default (RedHat issued) sendmail # script as /etc/rc.d/init.d/sendmail (80 30) # ########################################################################## # These comments are used by chkconfig and supporting programs # # chkconfig: - 79 31 # description: mimedefang is a sendmail milter designed to perform virus \ # scans on incoming mail messages. # processname: mimedefang # config: @CONFDIR_EVAL@/mimedefang-filter # pidfile: /var/run/mimedefang.pid ### BEGIN INIT INFO # Provides: mimedefang # Required-Start: $local_fs $network $named $remote_fs $syslog $time # Required-Stop: $local_fs $network $named $remote_fs $syslog $time # Default-Start: # Default-Stop: # Short-Description: Start and stop mimedefang. # Description: MIMEDefang is a framework for filtering e-mail. It uses # Sendmail's "Milter" API, some C glue code, and some Perl code to let you # write high-performance mail filters in Perl. ### END INIT INFO # Source function library. . /etc/rc.d/init.d/functions # Source networking configuration. . /etc/sysconfig/network RETVAL=0 prog="mimedefang" # Check that networking is up. [ ${NETWORKING} = "no" ] && exit 0 # Is the program executable? We search in /usr/bin and /usr/local/bin # so that both RPM and normal installation methods work fine. if [ -x /usr/bin/$prog ] ; then PROGDIR=/usr/bin elif [ -x /usr/local/bin/$prog ] ; then PROGDIR=/usr/local/bin else exit 0 fi # Source configuration if [ -f /etc/sysconfig/$prog ] ; then . /etc/sysconfig/$prog fi # Make sure reqired vars are set SOCKET=${SOCKET:=@SPOOLDIR@/$prog.sock} MX_SOCKET=${MX_SOCKET:=@SPOOLDIR@/$prog-multiplexor.sock} # These lines keep SpamAssassin happy. Not needed if you # aren't using SpamAssassin. HOME=@SPOOLDIR@ export HOME # Locale should be set to "C" for generating valid date headers LC_ALL=C export LC_ALL start() { # Lets not run twice if [ -f /var/lock/subsys/$prog ]; then RETVAL=2 return $RETVAL fi # Check mimedefang-filter syntax configtest RETVAL=$? if [ $RETVAL != 0 ] ; then return $RETVAL fi # Since @SPOOLDIR@ might be tmpfs, ensure that it is properly # initialized. chown defang:defang @SPOOLDIR@ restorecon -R @SPOOLDIR@ >/dev/null 2>&1 if [ ! -d @SPOOLDIR@/.razor ]; then mkdir @SPOOLDIR@/.razor chown defang:defang @SPOOLDIR@/.razor chmod 0750 @SPOOLDIR@/.razor fi if [ ! -L @SPOOLDIR@/.razor/razor-agent.log ]; then # The Razor2 log is mostly useless, and we can't change its location. # In order to prevent it from filling up the spool, we just link it to # /dev/null. ln -sf /dev/null @SPOOLDIR@/.razor/razor-agent.log chown -h defang:defang @SPOOLDIR@/.razor/razor-agent.log fi echo -n "Starting $prog-multiplexor: " [ -e $MX_SOCKET ] && rm -f $MX_SOCKET # Tricky stuff below... "echo -E" won't work, hence the two-step. daemon $PROGDIR/$prog-multiplexor -p /var/run/$prog-multiplexor.pid \ -o @SPOOLDIR@/$prog-multiplexor.lock \ $([ -n "$FILTER" ] && echo "-f $FILTER") \ $([ -n "$SYSLOG_FACILITY" ] && echo "-S $SYSLOG_FACILITY") \ $([ -n "$SUBFILTER" ] && echo "-F $SUBFILTER") \ $([ -n "$MX_MINIMUM" ] && echo "-m $MX_MINIMUM") \ $([ -n "$MX_MAXIMUM" ] && echo "-x $MX_MAXIMUM") \ $([ -n "$MX_RECIPOK_PERDOMAIN_LIMIT" ] && echo "-y $MX_RECIPOK_PERDOMAIN_LIMIT") \ $([ -n "$MX_USER" ] && echo "-U $MX_USER") \ $([ -n "$MX_IDLE" ] && echo "-i $MX_IDLE") \ $([ -n "$MX_BUSY" ] && echo "-b $MX_BUSY") \ $([ -n "$MX_QUEUE_SIZE" ] && echo "-q $MX_QUEUE_SIZE") \ $([ -n "$MX_QUEUE_TIMEOUT" ] && echo "-Q $MX_QUEUE_TIMEOUT") \ $([ -n "$MX_REQUESTS" ] && echo "-r $MX_REQUESTS") \ $([ -n "$MX_MAP_SOCKET" ] && echo "-N $MX_MAP_SOCKET") \ $([ -n "$MX_WORKER_DELAY" ] && echo "-w $MX_WORKER_DELAY") \ $([ -n "$MX_MIN_WORKER_DELAY" ] && echo "-W $MX_MIN_WORKER_DELAY") \ $([ -n "$MX_LOG_WORKER_STATUS_INTERVAL" ] && echo "-L $MX_LOG_WORKER_STATUS_INTERVAL") \ $([ -n "$MX_MAX_RSS" ] && echo "-R $MX_MAX_RSS") \ $([ -n "$MX_MAX_AS" ] && echo "-M $MX_MAX_AS") \ $([ -n "$MX_MAX_LIFETIME" ] && echo "-V $MX_MAX_LIFETIME") \ $([ "$MX_EMBED_PERL" = "yes" ] && (echo -n "-"; echo "E")) \ $([ "$MX_LOG" = "yes" ] && echo "-l") \ $([ "$MX_STATS" = "yes" ] && echo "-t /var/log/mimedefang/stats") \ $([ "$MX_STATUS_UPDATES" = "yes" ] && echo "-Z") \ $([ "$MX_STATS" = "yes" -a "$MX_FLUSH_STATS" = "yes" ] && echo "-u") \ $([ -n "$MX_TICK_REQUEST" ] && echo "-X $MX_TICK_REQUEST") \ $([ -n "$MX_TICK_PARALLEL" ] && echo "-P $MX_TICK_PARALLEL") \ $([ "$MX_STATS_SYSLOG" = "yes" ] && echo "-T") \ $([ "$MD_ALLOW_GROUP_ACCESS" = "yes" ] && echo "-G") \ $([ -n "$MX_NOTIFIER" ] && echo "-O $MX_NOTIFIER") \ -s $MX_SOCKET echo # Start daemon echo -n "Starting $prog: " [ -e $SOCKET ] && rm -f $SOCKET # NOTE: You should limit the stack size on Linux, or # thread-creation will fail on a very busy server. ulimit -s 2048 daemon $PROGDIR/$prog -P /var/run/$prog.pid \ -o @SPOOLDIR@/$prog.lock \ -m $MX_SOCKET \ $([ -n "$LOOPBACK_RESERVED_CONNECTIONS" ] && echo "-R $LOOPBACK_RESERVED_CONNECTIONS") \ $([ -n "$MX_USER" ] && echo "-U $MX_USER") \ $([ -n "$SYSLOG_FACILITY" ] && echo "-S $SYSLOG_FACILITY") \ $([ "$USE_SETSYMLIST" = "yes" ] && echo "-y") \ $([ "$LOG_FILTER_TIME" = "yes" ] && echo "-T") \ $([ "$MX_RELAY_CHECK" = "yes" ] && echo "-r") \ $([ "$MX_HELO_CHECK" = "yes" ] && echo "-H") \ $([ "$MX_SENDER_CHECK" = "yes" ] && echo "-s") \ $([ "$MX_RECIPIENT_CHECK" = "yes" ] && echo "-t") \ $([ "$KEEP_FAILED_DIRECTORIES" = "yes" ] && echo "-k") \ $([ "$MD_ALLOW_GROUP_ACCESS" = "yes" ] && echo "-G") \ $([ "$MD_SKIP_BAD_RCPTS" = "yes" ] && echo "-N") \ $([ -n "$MD_EXTRA" ] && echo "$MD_EXTRA") \ $([ "$ALLOW_NEW_CONNECTIONS_TO_QUEUE" = "yes" ] && echo "-q") \ -p $SOCKET RETVAL=$? echo [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog return $RETVAL } configtest () { echo -n "Checking filter syntax: " $PROGDIR/mimedefang.pl $([ -n "$SUBFILTER" ] && echo "-f $SUBFILTER") -test > @SPOOLDIR@/configtest.out 2>&1 if [ $? != 0 ] ; then echo "FAILED." echo "" cat @SPOOLDIR@/configtest.out return 1 fi rm -f @SPOOLDIR@/configtest.out echo "OK" return 0 } stop() { # If we're not running, there's nothing to do if [ ! -f /var/lock/subsys/$prog ]; then RETVAL=2 return $RETVAL fi # Stop daemon echo -n "Shutting down $prog: " killproc $prog RETVAL=$? echo [ -e $SOCKET ] && rm -f $SOCKET [ -f /var/run/$prog.pid ] && rm -f /var/run/$prog.pid # Stop daemon echo -n "Shutting down $prog-multiplexor: " killproc $prog-multiplexor echo [ -e $MX_SOCKET ] && rm -f $MX_SOCKET if [ "$1" = "wait" ] ; then printf "Waiting for daemons to exit" WAITPID="" test -f /var/run/$prog.pid && WAITPID=`cat /var/run/$prog.pid` test -f /var/run/$prog-multiplexor.pid && WAITPID="$WAITPID `cat /var/run/$prog-multiplexor.pid`" n=0 while [ -n "$WAITPID" ] ; do W2="" for pid in $WAITPID ; do if kill -0 $pid > /dev/null 2>&1 ; then W2="$W2 $pid" fi done printf "." n=`expr $n + 1` test $n -eq 30 && kill -KILL $WAITPID > /dev/null 2>&1 test $n -eq 60 && break WAITPID=$W2 sleep 1 done echo "" fi [ -f /var/run/$prog-multiplexor.pid ] && rm -f /var/run/$prog-multiplexor.pid [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog return $RETVAL } # See how we were called. case "$1" in start) start RETVAL=$? ;; stop) stop $2 RETVAL=$? ;; restart) stop wait start RETVAL=$? ;; condrestart) if [ -f /var/lock/subsys/$prog ]; then stop wait start RETVAL=$? fi ;; status) status $prog RETVAL=$? status $prog-multiplexor [ $RETVAL -eq 0 ] && RETVAL=$? if [ $RETVAL = 0 -a -x $PROGDIR/md-mx-ctrl ] ; then $PROGDIR/md-mx-ctrl -s $MX_SOCKET barstatus fi ;; configtest) configtest RETVAL=$? ;; reread|reload) if [ -x $PROGDIR/md-mx-ctrl ] ; then $PROGDIR/md-mx-ctrl -s $MX_SOCKET reread > /dev/null 2>&1 RETVAL=$? if [ $RETVAL = 0 ] ; then echo "Told $prog-multiplexor to force reread of filter rules." else echo "Could not communicate with $prog-multiplexor" fi else if [ -r /var/run/$prog-multiplexor.pid ] ; then kill -INT `cat /var/run/$prog-multiplexor.pid` RETVAL=$? if [ $RETVAL = 0 ] ; then echo "Told $prog-multiplexor to force reread of filter rules." else echo "Could not signal $prog-multiplexor" fi else RETVAL=1 echo "Could not find process-ID of $prog-multiplexor" fi fi ;; *) echo "Usage: $0 {start|stop|restart|condrestart|reread|reload|status|configtest}" exit 1 esac exit $RETVAL mimedefang-3.6/redhat/mimedefang-spec.in000066400000000000000000000216211475763067200203240ustar00rootroot00000000000000########################################################################## # Copyright @2002, Roaring Penguin Software Inc. All rights reserved. # # * This program may be distributed under the terms of the GNU General # * Public License, Version 2. # # Project : MIMEDefang # Component : redhat/mimedefang.spec # Author : Michael McLagan # Creation : 30-Apr-2002 12:25 # Description : This is the spec file for building the RedHat RPM # distribution SRC and i386 files # # Current Revision: # # $Source$ # $Revision$ # $Author$ # $Date$ # # Revision History: # # $Log$ # Revision 1.30 2004/09/19 19:55:28 dfs # Add sa-mimedefang.cf to example. # # Revision 1.29 2004/09/01 21:22:52 dfs # Fixed bug. # # Revision 1.28 2004/08/09 22:28:06 dfs # Fixed spec so as not to disable service on upgrade. # # Revision 1.27 2004/07/15 17:13:43 dfs # Move sa-mimedefang.cf into /etc/mail instead of /etc/mail/spamassassin # # Revision 1.26 2004/02/24 14:41:08 dfs # Loosened spool permissions to make it world-readable. # Improved spec file to allow detection of AV software at build time. # # Revision 1.25 2003/06/04 14:12:24 dfs # Took out noarch. # # Revision 1.24 2003/06/04 14:03:33 dfs # Copy pid files into /var/run to keep Red Hat killproc() happy. # # Revision 1.23 2003/06/04 13:39:33 dfs # Split out contrib into a separate package. # # Revision 1.22 2002/10/25 14:01:51 dfs # Build RPM with --disable-anti-virus # # Revision 1.21 2002/09/25 11:28:13 dfs # Fixed spec. # # Revision 1.20 2002/08/26 03:48:40 dfs # Install logrotate file # # Revision 1.19 2002/08/26 03:15:40 dfs # Take ip key out! # # Revision 1.18 2002/08/26 03:13:52 dfs # Better RPM file. # # Revision 1.17 2002/08/26 03:10:00 dfs # Better RPM packaging. # # Revision 1.16 2002/06/21 14:50:27 dfs # Fixed spec file. # # Revision 1.15 2002/06/11 12:33:14 dfs # Fixed typo. # # Revision 1.14 2002/06/03 14:26:14 dfs # Fixups for sysconfdir. # # Revision 1.13 2002/05/29 18:12:15 dfs # Put pid files and sockets in /var/spool/MIMEDefang instead of /var/run # # Revision 1.12 2002/05/23 19:08:00 dfs # Fixed spec file to make log directory. # # Revision 1.11 2002/05/15 13:39:02 dfs # Added README.NONROOT # # Revision 1.10 2002/05/14 16:19:14 dfs # Patch from Michael McLagan # # Revision 1.9 2002/05/13 20:32:03 dfs # More spec fixes. # # Revision 1.8 2002/05/13 20:20:07 dfs # Fixed spec file. # # Revision 1.7 2002/05/10 13:46:43 dfs # Backward compatibility with Michael McLagan's RPM setup. # # Revision 1.6 2002/05/10 11:30:24 dfs # Updated spec. # # Revision 1.5 2002/05/09 20:30:42 dfs # Changed spool dir paths back. # # Revision 1.4 2002/05/09 20:26:47 dfs # Fixed typo # # Revision 1.3 2002/05/09 20:24:31 dfs # Fixed bug in spec. # # Revision 1.2 2002/05/09 20:22:09 dfs # Revert spec to our style. # # Revision 1.1 2002/05/09 20:18:05 dfs # Merge Michael McLagan's patch. # # Revision 1.7 2002/05/08 16:56:58 dfs # Added /etc/mail/spamassassin to spec. # # Revision 1.6 2002/05/06 15:23:31 dfs # Update for 2.10. # # Revision 1.5 2002/05/03 14:24:24 dfs # Merge packaging patches. # Fixed typo. # Made default value for -n 10. # ########################################################################## %define dir_spool /var/spool/MIMEDefang %define dir_quarantine /var/spool/MD-Quarantine %define dir_log /var/log/mimedefang %define user defang %define with_antivirus 0 %global debug_package %{nil} %{?_with_antivirus: %{expand: %%define with_antivirus 1}} %{?_without_antivirus: %{expand: %%define with_antivirus 0}} Summary: Email filtering application using sendmail's milter interface Name: mimedefang Version: #VERSION# Release: #RELEASE# License: GPL Group: Networking/Mail Source0: https://mimedefang.org/static/%{name}-%{version}%{?prerelease}.tar.gz Source1: https://mimedefang.org/static/%{name}-%{version}%{?prerelease}.tar.gz.asc Url: https://mimedefang.org Vendor: MIMEDefang Buildroot: %{_tmppath}/%{name}-root Requires: perl-Digest-SHA perl-Digest-MD5 perl-MIME-tools perl-MailTools perl-Unix-Syslog BuildRequires: sendmail-milter-devel >= 8.12.0 BuildRequires: autoconf > 2.55 %description MIMEDefang is an e-mail filter program which works with Sendmail 8.11 and later. MIMEDefang filters all e-mail messages sent via SMTP. MIMEDefang splits multi-part MIME messages into their components and potentially deletes or modifies the various parts. It then reassembles the parts back into an e-mail message and sends it on its way. There are some caveats you should be aware of before using MIMEDefang. MIMEDefang potentially alters e-mail messages. This breaks a "gentleman's agreement" that mail transfer agents do not modify message bodies. This could cause problems, for example, with encrypted or signed messages. Deleting attachments could cause a loss of information. Recipients must be aware of this possibility, and must be willing to explain to senders exactly why they cannot mail certain types of files. You must have the willingness of your e-mail users to commit to security, or they will complain loudly about MIMEDefang. %prep %setup -q -n %{name}-%{version}%{?prerelease} autoconf %configure --prefix=%{_prefix} \ --mandir=%{_mandir} \ --with-milterlib=%{_libdir} \ --sysconfdir=/etc \ --disable-check-perl-modules \ --with-spooldir=%{dir_spool} \ --with-quarantinedir=%{dir_quarantine} \ %if %{with_antivirus} --with-user=%{user} %else --with-user=%{user} \ --disable-anti-virus %endif %build make DONT_STRIP=1 %install rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/%{dir_log} make DESTDIR=$RPM_BUILD_ROOT \ INSTALL='install -p' INSTALL_STRIP_FLAG='' install-redhat # Turn off execute bit on scripts in contrib find contrib -type f -print0 | xargs -0 chmod a-x %clean HERE=`pwd` cd .. rm -rf $HERE rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root) %doc Changelog README* examples SpamAssassin %dir %{dir_spool} %dir %{dir_log} %dir %{dir_quarantine} %{_bindir}/* %{perl_vendorlib}/* %{_mandir}/* %config(noreplace) /etc/mail/mimedefang-filter %config(noreplace) /etc/mail/sa-mimedefang.cf %config(noreplace) /etc/mail/sa-mimedefang.cf.example %config(noreplace) /etc/sysconfig/%{name} /etc/rc.d/init.d/%{name} /etc/logrotate.d/%{name} %pre # Backward-compatibility if test -d /var/spool/mimedefang -a ! -d /var/spool/MIMEDefang ; then mv /var/spool/mimedefang /var/spool/MIMEDefang || true fi if test -d /var/spool/quarantine -a ! -d /var/spool/MD-Quarantine ; then mv /var/spool/quarantine /var/spool/MD-Quarantine || true fi # Add user useradd -M -r -d %{dir_spool} -s /bin/false -c "MIMEDefang User" %{user} > /dev/null 2>&1 || true %post # Tighten permissions chown %{user} %{dir_spool} chgrp %{user} %{dir_spool} chmod 750 %{dir_spool} chown %{user} %{dir_quarantine} chgrp %{user} %{dir_quarantine} chmod 750 %{dir_quarantine} chown %{user} %{dir_log} chgrp %{user} %{dir_log} chmod 755 %{dir_log} cat << EOF In order to complete the installation of mimedefang, you will need to add the following line to your sendmail mc file: INPUT_MAIL_FILTER(\`mimedefang', \`S=unix:/var/spool/MIMEDefang/mimedefang.sock, F=T, T=S:1m;R:1m;E:5m') Use the sendmail-cf package to rebuild your /etc/mail/sendmail.cf file and restart your sendmail daemon. EOF %if 0%{?rhel} > 6 || 0%{?fedora} > 23 %systemd_preun %{name}.service %else /sbin/chkconfig --add mimedefang %endif %preun %if 0%{?rhel} > 6 || 0%{?fedora} > 23 %systemd_preun %{name}.service %else if [ $1 -eq 0 ]; then /sbin/service %{name} stop > /dev/null 2>&1 || : /sbin/chkconfig --del %{name} fi %endif %package contrib Summary: Contributed software that works with MIMEDefang Version: #VERSION# Release: #RELEASE# Group: Networking/Mail %description contrib This package contains contributed software that works with MIMEDefang, such as the graphdefang graphing package and a sample filter. %files contrib %defattr(-,root,root) %doc contrib %changelog * Sun Apr 16 2023 Giovanni Bechis - update Perl module requirements * Wed May 29 2002 Dianne Skoll - Put pid files and sockets in /var/spool/MIMEDefang so we can drop privileges early. * Wed May 15 2002 Dianne Skoll - Change log directory to /var/log/mimedefang/ to more easily accommodate -U flag. * Tue May 14 2002 Michael McLagan - Fixed preinstall script * Thu May 09 2002 Dianne Skoll - Install SpamAssassin config file - Changed spool dir to /var/spool/MIMEDefang and quarantine dir to /var/spool/MD-Quarantine * Thu May 09 2002 Michael McLagan - Modified to build beta releases * Fri May 03 2002 Michael McLagan - Updated to 2.9 * Tue Apr 30 2002 Michael McLagan Initial version 2.8 mimedefang-3.6/redhat/mimedefang-sysconfig.in000066400000000000000000000213471475763067200214030ustar00rootroot00000000000000#!/bin/bash ########################################################################## # Copyright @2002, Roaring Penguin Software Inc. All rights reserved. # # * This program may be distributed under the terms of the GNU General # * Public License, Version 2. # # Project : MIMEDefang # Component : redhat/mimedefang-sysconfig # Author : Michael McLagan # Creation : 02-May-2002 14:17 # Description : This is the configuration file for the RedHat RPM # init script. It lives in /etc/sysconfig as mimedefang # # Documents all variables used by the init script and gives # recommended values. For boolean variables, any value other # than 'yes' (all lower case) means NO. # # Current Revision: # # $Source$ # $Revision$ # $Author$ # $Date$ # # Revision History: # # $Log$ # Revision 1.10 2006/01/20 03:18:46 dfs # Updated init scripts to understand -R option to mimedefang. # # Revision 1.9 2006/01/17 23:05:44 dfs # Added filter_helo and supporting infrastructure. # # Revision 1.8 2005/10/14 16:16:54 dfs # Add "-z" option to mimedefang and mimedefang-multiplexor to set the # spool directory. # # Revision 1.7 2005/02/08 17:10:33 dfs # Added ALLOW_NEW_CONNECTIONS_TO_QUEUE variable in init script. # Added -q option to mimedefang. # # Revision 1.6 2004/10/28 20:31:21 dfs # Add MX_STATUS_UPDATES variable to shell script. # # Revision 1.5 2004/06/21 18:46:03 dfs # Add MX_MAP_SOCKET variable in init scripts. # # Revision 1.4 2004/02/23 16:43:23 dfs # Added MX_NOTIFIER option in sysconfig and init script. # # Revision 1.3 2003/11/10 14:57:16 dfs # Added MX_EMBED_PERL parameter. # # Revision 1.2 2003/08/06 02:09:58 dfs # Increased timeouts. # # Revision 1.1 2003/07/23 18:27:35 dfs # Made Red Hat files configurable. # # Revision 1.22 2003/07/23 18:13:52 dfs # Added MD_EXTRA setting. # # Revision 1.21 2003/07/02 16:35:43 dfs # Cleanups in preparation for 2.35 release. # # Revision 1.20 2003/06/20 18:03:14 dfs # Added queue stuff to init scripts. # # Revision 1.19 2003/05/27 14:50:21 dfs # Default quarantine dir is /var/spool/MD-Quarantine # Removed support for non-multiplexor operation. # # Revision 1.18 2003/04/21 16:27:46 dfs # Added SYSLOG_FACILITY to init scripts. # Fixed typo. # # Revision 1.17 2003/03/05 15:30:28 dfs # Added -L option # # Revision 1.16 2002/12/03 17:55:37 dfs # Minor tweaks. # # Revision 1.15 2002/09/18 15:47:49 dfs # Updated init scripts for "-T" option. # # Revision 1.14 2002/08/26 03:20:10 dfs # Set user to defang in RPM # # Revision 1.13 2002/06/13 14:58:28 dfs # Updated changelog. # # Revision 1.12 2002/06/11 13:36:10 dfs # Update scripts for recipient checks. # # Revision 1.11 2002/05/29 18:12:15 dfs # Put pid files and sockets in /var/spool/MIMEDefang instead of /var/run # # Revision 1.10 2002/05/15 12:55:02 dfs # Infrastructure for running multiplexor as non-root. # # Revision 1.9 2002/05/10 12:36:53 dfs # Changed MX_WORKER_RATE to MX_WORKER_DELAY; added MX_MIN_WORKER_DELAY. # # Revision 1.8 2002/05/08 16:55:17 dfs # Updated redhat init scripts for subfilter. # # Revision 1.7 2002/05/06 18:36:08 dfs # Added MX_WORKER_RATE to init scripts. # # Revision 1.6 2002/05/06 14:04:04 dfs # Cleanups. # # Revision 1.5 2002/05/03 14:24:24 dfs # Merge packaging patches. # Fixed typo. # Made default value for -n 10. # ########################################################################## # The spool directory # SPOOLDIR=@SPOOLDIR@ # The socket used by mimedefang to communicate with sendmail # SOCKET=$SPOOLDIR/mimedefang.sock # If you want to log messages like "Filter time is 156ms" to syslog, # uncomment the next line # LOG_FILTER_TIME=yes # Run the multiplexor and filters as this user, not root. RECOMMENDED MX_USER=defang # If you want to keep spool directories around if the filter fails, # set the next one to yes # KEEP_FAILED_DIRECTORIES=no # If "yes", turn on the multiplexor relay checking function # MX_RELAY_CHECK=no # If "yes", turn on the multiplexor HELO checking function # MX_HELO_CHECK=no # If "yes", turn on the multiplexor sender checking function # MX_SENDER_CHECK=no # If "yes", turn on the multiplexor recipient checking function # MX_RECIPIENT_CHECK=no # Ask for filter_tick to be called every 60 seconds # MX_TICK_REQUEST=60 # Run three tick bands # MX_TICK_PARALLEL=3 # Set to yes if you want the milter and multiplexor sockets # to be group-writable. This also makes files created by mimedefang # group-readable. MD_ALLOW_GROUP_ACCESS=no # Set to yes if you want the multiplexor to log events to syslog MX_LOG=yes # Number of workers reserved for connections from loopback. Use -1 # for default behaviour, 0 to allow loopback connections to queue, # or >0 to reserve workers for loopback connections LOOPBACK_RESERVED_CONNECTIONS=-1 # Set to path name of UNIX-domain socket if you want to use MIMEDefang # with Sendmail's SOCKETMAP map type # MX_MAP_SOCKET=$SPOOLDIR/map.sock # Set to yes if you want to use an embedded Perl interpreter # MX_EMBED_PERL=yes # Set to the syslog facility. Also set $SyslogFacility in your filter # SYSLOG_FACILITY=mail # The multiplexor does not start all workers at the same time. Instead, # it starts one worker every MX_WORKER_DELAY seconds when the system is idle. # (If the system is busy, the multiplexor starts workers as incoming mail # requires attention.) # MX_WORKER_DELAY=3 # The next setting is an absolute limit on worker activation. The multiplexor # will NEVER activate a worker within MX_MIN_WORKER_DELAY seconds of another. # The default of zero means that the multiplexor will activate workers as # quickly as necessary to keep up with incoming mail. # MX_MIN_WORKER_DELAY=0 # Set to yes if you want the multiplexor to log stats in # /var/log/mimdefang/md-stats The /var/log/mimedefang directory # must exist and be writable by the user you're running MIMEDefang as. # MX_STATS=no # Set to yes if you want the stats file flushed after each entry # MX_FLUSH_STATS=no # Set to yes if you want the multiplexor to log stats to syslog # MX_STATS_SYSLOG=no # The socket used by the multiplexor # MX_SOCKET=$SPOOLDIR/mimedefang-multiplexor.sock # Maximum # of requests a process handles # MX_REQUESTS=200 # Minimum number of processes to keep. The default of 0 is probably # too low; we suggest 2 instead. MX_MINIMUM=2 # If you want to allow the multiplexor to queue new connections when # all workers are busy, set this to yes ALLOW_NEW_CONNECTIONS_TO_QUEUE=yes # Maximum number of processes to run (mail received while this many # processes are running is rejected with a temporary failure, so be # wary of how many emails you receive at a time). This applies only # if you DO use the multiplexor. The default value of 2 is probably # too low; we suggest 10 instead MX_MAXIMUM=10 # Uncomment to log worker status; it will be logged every # MX_LOG_WORKER_STATUS_INTERVAL seconds # MX_LOG_WORKER_STATUS_INTERVAL=30 # Uncomment next line to have busy workers send status updates to the # multiplexor. NOTE: Consumes one extra file descriptor per worker, plus # a bit of CPU time. # MX_STATUS_UPDATES=yes # Limit worker processes' resident-set size to this many kilobytes. Default # is unlimited. # MX_MAX_RSS=10000 # Limit total size of worker processes' memory space to this many kilobytes. # Default is unlimited. # MX_MAX_AS=30000 # If you want to use the "notification" facility, set the appropriate port. # See the mimedefang-notify man page for details. # MX_NOTIFIER=inet:4567 # Number of seconds a process should be idle before checking for # minimum number and killed # MX_IDLE=300 # Limit number of concurrent recipok requests on a per-domain basis. # 0 means no limit MX_RECIPOK_PERDOMAIN_LIMIT=0 # Number of seconds a process is allowed to scan an email before it is # considered dead. The default is 30 seconds; we suggest 600. MX_BUSY=600 # Multiplexor queue size -- default is 0 (no queueing) # MX_QUEUE_SIZE=10 # Multiplexor queue timeout -- default is 30 seconds # MX_QUEUE_TIMEOUT=30 # Any extra arguments to mimedefang # MD_EXTRA="-a auth_author" # setting USE_SETSYMLIST=yes adds the "-y" command-line # USE_SETSYMLIST=yes # SUBFILTER specifies which filter rules file to use # SUBFILTER=@CONFDIR_EVAL@/mimedefang-filter # I question why I'm including this as I see no real need for it # but in the interests of a flexible implementation, here goes! # # DO NOT CHANGE THIS VARIABLE LIGHTLY!! # # The filtering program used by mimedefang or the multiplexor. This is # *NOT* a replacement for @CONFDIR_EVAL@/mimedefang-filter!!! It replaces # the entire mimedefang perl program, /usr/bin/mimedefang.pl and allows # a unique filtering agent to be used. Read mimedefang-protocol man # page VERY carefully before you attempt to change this! # # FILTER=/usr/bin/mimedefang.pl mimedefang-3.6/rm_r.c000066400000000000000000000052271475763067200146100ustar00rootroot00000000000000/*********************************************************************** * * rm_r.c * * Implementation in C of recursive deletion of directory (rm -r dir) * * Copyright (C) 2002-2005 Roaring Penguin Software Inc. * http://www.roaringpenguin.com * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #include "config.h" #include "mimedefang.h" #include #include #include #include #include #include #include #include #include #ifdef ENABLE_DEBUGGING extern void *malloc_debug(void *, size_t, char const *fname, int); extern char *strdup_debug(void *, char const *, char const *, int); extern void free_debug(void *, void *, char const *, int); #undef malloc #undef strdup #undef free #define malloc(x) malloc_debug(ctx, x, __FILE__, __LINE__) #define strdup(x) strdup_debug(ctx, x, __FILE__, __LINE__) #define free(x) free_debug(ctx, x, __FILE__, __LINE__) #define malloc_with_log(x) malloc_debug(ctx, x, __FILE__, __LINE__) #define strdup_with_log(x) strdup_debug(ctx, x, __FILE__, __LINE__) #endif /********************************************************************** * %FUNCTION: rm_r * %ARGUMENTS: * dir -- directory or file name * %RETURNS: * -1 on error, 0 otherwise. * %DESCRIPTION: * Deletes dir and recursively deletes contents ***********************************************************************/ int rm_r(char const *qid, char const *dir) { char buf[SMALLBUF]; struct stat sbuf; DIR *d; struct dirent *entry; int retcode = 0; int errno_save; if (!qid || !*qid) { qid = "NOQUEUE"; } if (lstat(dir, &sbuf) < 0) { syslog(LOG_WARNING, "%s: lstat(%s) failed: %m", qid, dir); return -1; } if (!S_ISDIR(sbuf.st_mode)) { /* Not a directory - just unlink */ if (unlink(dir) < 0) { syslog(LOG_WARNING, "%s: unlink(%s) failed: %m", qid, dir); return -1; } return 0; } d = opendir(dir); if (!d) { errno_save = errno; syslog(LOG_WARNING, "%s: opendir(%s) failed: %m", qid, dir); errno = errno_save; return -1; } while((entry = readdir(d)) != NULL) { if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { continue; } snprintf(buf, sizeof(buf), "%s/%s", dir, entry->d_name); if (rm_r(qid, buf) < 0) { retcode = -1; } } closedir(d); if (rmdir(dir) < 0) { syslog(LOG_WARNING, "%s: rmdir(%s) failed: %m", qid, dir); return -1; } return retcode; } mimedefang-3.6/script/000077500000000000000000000000001475763067200150035ustar00rootroot00000000000000mimedefang-3.6/script/mimedefang-util.in000077500000000000000000000115641475763067200204140ustar00rootroot00000000000000#!@PERL@ #*********************************************************************** # # mimedefang-util # # Utility script for message structure debugging # # * This program may be distributed under the terms of the GNU General # * Public License, Version 2. # #*********************************************************************** use strict; use warnings; use Getopt::Long; use Pod::Usage; use MIME::Parser; use MIME::Words qw( decode_mimewords ); use File::Temp qw( tempdir ); =head1 NAME mimedefang-util -- Utility script for message structure debugging =head1 SYNOPSIS # Print a "pretty" version of an input message mimedefang-util --prettyprint < input.msg # Print the message structure mimedefang-util --structure < input.msg # Parse and re-MIME the message mimedefang-util --dump < input.msg =head1 DESCRIPTION This script provides some debug tools formerly provided as part of mimedefang.pl =head1 OPTIONS =over 4 =item B<--prettyprint> Parses a mail message from standard input and reformats it in a "pretty" format on standard output. All text/* parts are printed directly, and non-text parts are described without printing their content. =item B<--structure> Parses a mail message from standard input, and outputs a description of the MIME tree to standard output. =item B<--dump> Parses a mail message from standard input, and dumps the parsed message back out again to standard output. =item B<--data-dump> Parses a mail message from standard input, and dumps the parsed message back out again to standard output using Data::Dumper =item B<--help> This help =item B<--man> Full manpage =back =head1 LICENSE AND COPYRIGHT Copyright (C) 2010 Roaring Penguin Software Inc. This program may be distributed under the terms of the GNU General Public License, Version 2. =cut my ($prettyprint, $structure, $dump) = undef; my %actions; my $result = GetOptions( 'prettyprint' => sub { $actions{prettyprint} = 1; }, 'structure' => sub { $actions{structure} = 1; }, 'dump' => sub { $actions{dump} = 1; }, 'data-dump' => sub { $actions{datadump} = 1; }, 'help' => sub { pod2usage(-exitval => 0, -verbose => 1) }, 'man' => sub { pod2usage(-exitval => 0, -verbose => 2) }, ); if( keys(%actions) > 1 ) { pod2usage( -message => 'Only one of --prettyprint, --structure, --data-dump or --dump may be specified' ); } if( keys(%actions) < 1 ) { pod2usage( -message => 'One of --prettyprint, --structure, --data-dump or --dump must be specified' ); } my $tmpdir = tempdir( CLEANUP => 1 ); my $parser = MIME::Parser->new(); my $filer = MIME::Parser::FileInto->new( $tmpdir ); $filer->ignore_filename(1); $parser->filer( $filer); $parser->extract_nested_messages(1); $parser->extract_uuencode(1); $parser->output_to_core(0); $parser->tmp_to_core(0); my $entity = $parser->parse(\*STDIN); if (!$entity) { die qq{Could not parse MIME: $!\n}; } if( $actions{'datadump'}) { use Data::Dumper; print Dumper($entity); } elsif( $actions{'dump'} ) { $entity->print(\*STDOUT); } elsif( $actions{'structure'} ) { print_entity_structure( $entity, 0 ); } elsif( $actions{'prettyprint'} ) { print $entity->stringify_header, "\n", pretty_print_mail( $entity, 16384 ); } exit(0); sub print_entity_structure { my ($in, $level) = @_; my ($type) = $in->mime_type; my @parts = $in->parts; $type =~ tr/A-Z/a-z/; my ($disposition) = $in->head->mime_attr("Content-Disposition"); my ($body) = $in->bodyhandle; my $fname = $in->head->recommended_filename(); if($fname) { $fname = decode_mimewords($fname); } else { $fname = ''; } my ($extension) = ""; $extension = $1 if($fname =~ /(\.[^.]*)$/); $disposition = "inline" unless defined($disposition); print " " x $level; if(!defined($body)) { print "non-leaf: type=$type; fname=$fname; disp=$disposition\n"; map { print_entity_structure($_, $level + 1) } @parts; } else { print "leaf: type=$type; fname=$fname; disp=$disposition\n"; } } sub pretty_print_mail { my ($e, $size, $chunk, $depth) = @_; $chunk = "" unless defined($chunk); $depth = 0 unless defined($depth); my (@parts) = $e->parts; my ($type) = $e->mime_type; my $fname = $entity->head->recommended_filename(); if($fname) { $fname = decode_mimewords($fname); } else { $fname = ''; } $fname = "; filename=$fname" if($fname ne ""); my ($spaces) = " " x $depth; $chunk .= "\n$spaces" . "[Part: ${type}${fname}]\n\n"; if($#parts >= 0) { my ($part); foreach $part (@parts) { $chunk = pretty_print_mail($part, $size, $chunk, $depth + 1); last if(length($chunk) >= $size); } } else { return $chunk unless ($type =~ m+^text/+); my ($body) = $e->bodyhandle; return $chunk unless (defined($body)); my ($path) = $body->path; return $chunk unless (defined($path)); return $chunk unless (open(IN, "<$path")); while () { $chunk .= $_; last if(length($chunk) >= $size); } close(IN); } return $chunk; } mimedefang-3.6/syslog-fac.c000066400000000000000000000047101475763067200157140ustar00rootroot00000000000000/*********************************************************************** * * syslog-fac.c * * Defined the function find_syslog_facility * * Copyright (C) 2003-2005 Roaring Penguin Software Inc. * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #include #include /********************************************************************** * %FUNCTION: find_syslog_facility * %ARGUMENTS: * facility_name -- name of a syslog facility, like "mail", "news", etc. * %RETURNS: * The numerical facility value, or -1 if not found. ***********************************************************************/ int find_syslog_facility(char const *facility_name) #define RETURN_SYSLOG_FACILITY(nm, sym) \ if (!strcasecmp(facility_name, nm) || !strcasecmp(facility_name, #sym)) \ return (sym) { #ifdef LOG_AUTH RETURN_SYSLOG_FACILITY("auth", LOG_AUTH); #endif #ifdef LOG_AUTHPRIV RETURN_SYSLOG_FACILITY("authpriv", LOG_AUTHPRIV); #endif #ifdef LOG_CRON RETURN_SYSLOG_FACILITY("cron", LOG_CRON); #endif #ifdef LOG_DAEMON RETURN_SYSLOG_FACILITY("daemon", LOG_DAEMON); #endif #ifdef LOG_FTP RETURN_SYSLOG_FACILITY("ftp", LOG_FTP); #endif #ifdef LOG_KERN RETURN_SYSLOG_FACILITY("kern", LOG_KERN); #endif #ifdef LOG_LPR RETURN_SYSLOG_FACILITY("lpr", LOG_LPR); #endif #ifdef LOG_MAIL RETURN_SYSLOG_FACILITY("mail", LOG_MAIL); #endif #ifdef LOG_NEWS RETURN_SYSLOG_FACILITY("news", LOG_NEWS); #endif #ifdef LOG_AUTH RETURN_SYSLOG_FACILITY("security", LOG_AUTH); #endif #ifdef LOG_SYSLOG RETURN_SYSLOG_FACILITY("syslog", LOG_SYSLOG); #endif #ifdef LOG_USER RETURN_SYSLOG_FACILITY("user", LOG_USER); #endif #ifdef LOG_UUCP RETURN_SYSLOG_FACILITY("uucp", LOG_UUCP); #endif #ifdef LOG_LOCAL0 RETURN_SYSLOG_FACILITY("local0", LOG_LOCAL0); #endif #ifdef LOG_LOCAL1 RETURN_SYSLOG_FACILITY("local1", LOG_LOCAL1); #endif #ifdef LOG_LOCAL2 RETURN_SYSLOG_FACILITY("local2", LOG_LOCAL2); #endif #ifdef LOG_LOCAL3 RETURN_SYSLOG_FACILITY("local3", LOG_LOCAL3); #endif #ifdef LOG_LOCAL4 RETURN_SYSLOG_FACILITY("local4", LOG_LOCAL4); #endif #ifdef LOG_LOCAL5 RETURN_SYSLOG_FACILITY("local5", LOG_LOCAL5); #endif #ifdef LOG_LOCAL6 RETURN_SYSLOG_FACILITY("local6", LOG_LOCAL6); #endif #ifdef LOG_LOCAL7 RETURN_SYSLOG_FACILITY("local7", LOG_LOCAL7); #endif return -1; } #undef RETURN_SYSLOG_FACILITY mimedefang-3.6/systemd-units/000077500000000000000000000000001475763067200163275ustar00rootroot00000000000000mimedefang-3.6/systemd-units/mimedefang-multiplexor.service000066400000000000000000000047511475763067200243760ustar00rootroot00000000000000[Unit] Description=MIMEDefang E-mail Filter (Multiplexor) Documentation=man:mimedefang-multiplexor(8) After=remote-fs.target After=systemd-journald-dev-log.socket PartOf=mimedefang.service [Service] Type=simple Restart=on-failure TimeoutStopSec=30s KillMode=mixed # Locale should be set to "C" for generating valid date headers Environment=LC_ALL=C MX_BUSY=600 MX_LOG=yes MX_MAXIMUM=10 MX_MINIMUM=2 MX_SOCKET=/var/spool/MIMEDefang/mimedefang-multiplexor.sock MX_USER=defang EnvironmentFile=-/etc/default/mimedefang EnvironmentFile=-/etc/sysconfig/mimedefang # This can be removed with MIMEDefang 2.82, but is required for older versions: # SuccessExitStatus=1 ExecStart=/bin/sh -c 'HOME=${SPOOLDIR:=/var/spool/MIMEDefang} \ exec /usr/bin/mimedefang-multiplexor -D \ `[ "$MX_EMBED_PERL" = "yes" ] && echo "-E"` \ `[ -n "$SPOOLDIR" ] && echo "-z $SPOOLDIR"` \ `[ -n "$FILTER" ] && echo "-f $FILTER"` \ `[ -n "$SYSLOG_FACILITY" ] && echo "-S $SYSLOG_FACILITY"` \ `[ -n "$SUBFILTER" ] && echo "-F $SUBFILTER"` \ `[ -n "$MX_MAX_LIFETIME" ] && echo "-V $MX_MAX_LIFETIME"` \ `[ -n "$MX_MINIMUM" ] && echo "-m $MX_MINIMUM"` \ `[ -n "$MX_MAXIMUM" ] && echo "-x $MX_MAXIMUM"` \ `[ -n "$MX_MAP_SOCKET" ] && echo "-N $MX_MAP_SOCKET"` \ `[ -n "$MX_LOG_SLAVE_STATUS_INTERVAL" ] && echo "-L $MX_LOG_SLAVE_STATUS_INTERVAL"` \ `[ -n "$MX_USER" ] && echo "-U $MX_USER"` \ `[ -n "$MX_IDLE" ] && echo "-i $MX_IDLE"` \ `[ -n "$MX_BUSY" ] && echo "-b $MX_BUSY"` \ `[ -n "$MX_REQUESTS" ] && echo "-r $MX_REQUESTS"` \ `[ -n "$MX_SLAVE_DELAY" ] && echo "-w $MX_SLAVE_DELAY"` \ `[ -n "$MX_MIN_SLAVE_DELAY" ] && echo "-W $MX_MIN_SLAVE_DELAY"` \ `[ -n "$MX_MAX_RSS" ] && echo "-R $MX_MAX_RSS"` \ `[ -n "$MX_MAX_AS" ] && echo "-M $MX_MAX_AS"` \ `[ "$MX_LOG" = "yes" ] && echo "-l"` \ `[ "$MX_STATS" = "yes" ] && echo "-t /var/log/mimedefang/stats"` \ `[ "$MX_STATS" = "yes" -a "$MX_FLUSH_STATS" = "yes" ] && echo "-u"` \ `[ "$MX_STATS_SYSLOG" = "yes" ] && echo "-T"` \ `[ "$MD_ALLOW_GROUP_ACCESS" = "yes" ] && echo "-G"` \ `[ "$MX_STATUS_UPDATES" = "yes" ] && echo "-Z"` \ `[ -n "$MX_QUEUE_SIZE" ] && echo "-q $MX_QUEUE_SIZE"` \ `[ -n "$MX_QUEUE_TIMEOUT" ] && echo "-Q $MX_QUEUE_TIMEOUT"` \ `[ -n "$MX_NOTIFIER" ] && echo "-O $MX_NOTIFIER"` \ `[ -n "$MX_RECIPOK_PERDOMAIN_LIMIT" ] && echo "-y $MX_RECIPOK_PERDOMAIN_LIMIT"` \ -s $MX_SOCKET' ExecStartPre=/bin/rm -f $MX_SOCKET ExecStopPost=/bin/rm -f $MX_SOCKET ExecReload=/bin/kill -INT $MAINPID mimedefang-3.6/systemd-units/mimedefang.service000066400000000000000000000037151475763067200220130ustar00rootroot00000000000000[Unit] Description=MIMEDefang E-mail Filter Documentation=man:mimedefang(8) Before=multi-user.target Before=postfix.service Before=sendmail.service After=remote-fs.target After=systemd-journald-dev-log.socket BindsTo=mimedefang-multiplexor.service After=mimedefang-multiplexor.service PropagatesReloadTo=mimedefang-multiplexor.service [Service] Type=simple Restart=on-failure TimeoutStopSec=30s # LC_ALL=C may not be necessary for mimedefang, but it is for # mimedefang-multiplexor, so upstream prefers it here also to be consistent. Environment=LC_ALL=C MX_SOCKET=/var/spool/MIMEDefang/mimedefang-multiplexor.sock MX_USER=defang SOCKET=/var/spool/MIMEDefang/mimedefang.sock EnvironmentFile=-/etc/default/mimedefang EnvironmentFile=-/etc/sysconfig/mimedefang ExecStartPre=/bin/rm -f $SOCKET ExecStart=/bin/sh -c 'exec /usr/bin/mimedefang -D \ `[ -n "$LOOPBACK_RESERVED_CONNECTIONS" ] && echo "-R $LOOPBACK_RESERVED_CONNECTIONS"` \ -m $MX_SOCKET \ `[ "$USE_SETSYMLIST" = "yes" ] && echo "-y"` \ `[ -n "$SPOOLDIR" ] && echo "-z $SPOOLDIR"` \ `[ -n "$MX_USER" ] && echo "-U $MX_USER"` \ `[ -n "$SYSLOG_FACILITY" ] && echo "-S $SYSLOG_FACILITY"` \ `[ "$MX_RELAY_CHECK" = "yes" ] && echo "-r"` \ `[ "$MX_HELO_CHECK" = "yes" ] && echo "-H"` \ `[ "$MX_SENDER_CHECK" = "yes" ] && echo "-s"` \ `[ "$MX_RECIPIENT_CHECK" = "yes" ] && echo "-t"` \ `[ "$KEEP_FAILED_DIRECTORIES" = "yes" ] && echo "-k"` \ `[ "$MD_EXTRA" != "" ] && echo $MD_EXTRA` \ `[ "$MD_SKIP_BAD_RCPTS" = "yes" ] && echo "-N"` \ "`[ -n "$X_SCANNED_BY" ] && \ ( [ "$X_SCANNED_BY" = "-" ] && \ echo "-X" || echo "-x$X_SCANNED_BY" )`" \ `[ "$MD_ALLOW_GROUP_ACCESS" = "yes" ] && echo "-G"` \ `[ "$ALLOW_NEW_CONNECTIONS_TO_QUEUE" = "yes" ] && echo "-q"` \ -p $SOCKET' ExecStopPost=/bin/rm -f $SOCKET # Make this service eligible for a reload, so we can propagate it to # mimedefang-multiplexor.service. ExecReload=/bin/true [Install] WantedBy=multi-user.target mimedefang-3.6/t/000077500000000000000000000000001475763067200137425ustar00rootroot00000000000000mimedefang-3.6/t/actions.t000066400000000000000000000035761475763067200156020ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::Utils; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use Mail::MIMEDefang; use Mail::MIMEDefang::Actions; sub t_get_quarantine_dir : Test(1) { init_globals(); # Set up temporary dir system('rm', '-rf', 't/tmp'); mkdir('t/tmp', 0755); $Features{'Path:QUARANTINEDIR'} = 't/tmp'; like(get_quarantine_dir, qr{.*\/(?:[0-9]{4})\-(?:[0-9]{2})\-(?:[0-9]{2})\-(?:[0-9]{2})\/qdir\-(?:[0-9]{4})\-(?:[0-9]{2})\-(?:[0-9]{2})\-(?:[0-9]{2})\.(?:[0-9]{2})\.(?:[0-9]{2})\-(?:[0-9]{3})}); system('rm', '-rf', 't/tmp'); } sub t_replace_with_warning : Test(1) { # Set up temporary dir system('rm', '-rf', 't/tmp'); mkdir('t/tmp', 0755); # Make a parser my $parser = MIME::Parser->new(); $parser->extract_nested_messages(1); $parser->extract_uuencode(1); $parser->output_to_core(0); $parser->tmp_to_core(0); my $filer = MIME::Parser::FileInto->new('t/tmp'); $filer->ignore_filename(1); $parser->filer($filer); $InFilterContext = 'action_replace_with_warning'; my $entity = $parser->parse_open("t/data/uri.eml"); my $ret; $ret = action_replace_with_warning('Warning'); is($ret, 1); system('rm', '-rf', 't/tmp'); } sub t_replace_with_url : Test(1) { # Set up temporary dir system('rm', '-rf', 't/tmp'); mkdir('t/tmp', 0755); # Make a parser my $parser = MIME::Parser->new(); $parser->extract_nested_messages(1); $parser->extract_uuencode(1); $parser->output_to_core(0); $parser->tmp_to_core(0); my $filer = MIME::Parser::FileInto->new('t/tmp'); $filer->ignore_filename(1); $parser->filer($filer); $InFilterContext = 'action_replace_with_url'; my $entity = $parser->parse_open("t/data/uri.eml"); my $ret; $ret = action_replace_with_url($entity, 't/tmp', 't/tmp', 'get file at _URL_'); is($ret, 1); system('rm', '-rf', 't/tmp'); } __PACKAGE__->runtests(); mimedefang-3.6/t/antispam.t000066400000000000000000000027011475763067200157430ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::Antispam; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use Mail::MIMEDefang; use Mail::MIMEDefang::Antispam; use File::Copy; sub md_spamc : Test(1) { SKIP: { if ( -f "/.dockerenv" or (defined $ENV{GITHUB_ACTIONS}) ) { skip "Spamd test disabled on Docker", 1 } my $spamd = Mail::MIMEDefang::Unit::get_abs_path('spamd'); if(not defined $spamd or not -f $spamd) { skip "Spamd binary not found", 1 } init_globals(); system("$spamd -L -s stderr -p 7830 -d"); copy('t/data/gtube.eml', './INPUTMSG'); my $spamc = md_spamc_init('127.0.0.1', 7830); my ($score, $hits, $report, $flag) = md_spamc_check($spamc); is($flag, 'True'); unlink('./INPUTMSG'); system("pkill spamd"); } } sub md_rspamd : Test(1) { SKIP: { if ( -f "/.dockerenv" or (defined $ENV{GITHUB_ACTIONS}) ) { skip "Spamd test disabled on Docker", 1 } my $rspamd = Mail::MIMEDefang::Unit::get_abs_path('rspamd'); if(not defined $rspamd or not -f $rspamd) { skip "Rspamd binary not found", 1 } init_globals(); system("$rspamd -u $ENV{USER} -c t/data/rspamd.conf 1>/dev/null 2>&1"); copy('t/data/gtube.eml', './INPUTMSG'); my ($hits, $req, $tests, $report, $action, $is_spam) = rspamd_check(); is($is_spam, 'true'); unlink('./INPUTMSG'); system("pkill rspamd"); } } __PACKAGE__->runtests(); mimedefang-3.6/t/arc.t000066400000000000000000000026201475763067200146740ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::DKIM::ARC; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use Mail::MIMEDefang; use Mail::MIMEDefang::DKIM::ARC; use File::Copy; sub arc_sign : Test(7) { copy('t/data/arc1.eml', './INPUTMSG'); my %headers = md_arc_sign('t/data/dkim.pem', 'rsa-sha256', 'none', 'testing.dkim.org', undef, 'selector'); like($headers{"ARC-Seal"}, qr/i=1; a=rsa\-sha256; cv=none; d=testing\.dkim\.org; s=selector; t=/); like($headers{"ARC-Message-Signature"}, qr/i=1; a=rsa\-sha256; c=relaxed\/relaxed; d=/); like($headers{"ARC-Authentication-Results"}, qr/i=1; testing\.dkim\.org; header\.From=mickey\@dkim\.org; dkim=pass/); unlink('./INPUTMSG'); undef %headers; copy('t/data/arc2.eml', './INPUTMSG'); %headers = md_arc_sign('t/data/dkim.pem', 'rsa-sha256', 'none', 'sa-test.spamassassin.org', undef, 't0768'); like($headers{"ARC-Seal"}, qr/i=1; a=rsa\-sha256; cv=none; d=sa-test\.spamassassin\.org; s=/); like($headers{"ARC-Message-Signature"}, qr/i=1; a=rsa\-sha256; c=relaxed\/relaxed; d=/); like($headers{"ARC-Authentication-Results"}, qr/i=1; sa-test.spamassassin.org;/); copy('t/data/arc-wrong.eml', './INPUTMSG'); %headers = md_arc_sign('t/data/dkim.pem', 'rsa-sha256', 'none', 'sa-test.spamassassin.org', undef, 't0768'); is($headers{"ARC-Seal"}, undef); unlink('./INPUTMSG'); } __PACKAGE__->runtests(); mimedefang-3.6/t/authres.t000066400000000000000000000030301475763067200155760ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::Authres; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use Mail::MIMEDefang; use Mail::MIMEDefang::Authres; use File::Copy; sub t_md_authres : Test(3) { SKIP: { if ( (not defined $ENV{'NET_TEST'}) or ($ENV{'NET_TEST'} ne 'yes' )) { skip "Net test disabled", 1 } copy('t/data/dkim1.eml', './INPUTMSG'); my $header = md_authres('test@sa-test.spamassassin.org', '1.2.3.4', 'sa-test.spamassassin.org'); like($header, qr{sa\-test\.spamassassin\.org(?:\s\(MIMEDefang\))?;(?:.*)\s+dkim=pass \(768\-bit key\) header\.d=sa-test\.spamassassin\.org header\.b=oRxHoP0Y;(?:.*)\s+spf=none \(domain of test\@sa\-test\.spamassassin\.org doesn't specify if 1\.2\.3\.4 is a permitted sender\) smtp\.mailfrom=test\@sa\-test\.spamassassin\.org;}); copy('t/data/spf1.eml', './INPUTMSG'); $header = md_authres('test@dnsbltest.spamassassin.org', '64.142.3.173', 'dnsbltest.spamassassin.org', 'dnsbltest.spamassassin.org'); like($header, qr{dnsbltest\.spamassassin\.org(?:\s\(MIMEDefang\))?;(?:.*)\s+\s+spf=pass \(domain of test\@dnsbltest\.spamassassin\.org designates 64\.142\.3\.173 as permitted sender\) smtp\.mailfrom=test\@dnsbltest\.spamassassin\.org;}); like($header, qr{dnsbltest\.spamassassin\.org(?:\s\(MIMEDefang\))?;(?:.*)\s+\s+spf=pass \(domain of dnsbltest\.spamassassin\.org designates 64\.142\.3\.173 as permitted sender\) smtp\.helo=dnsbltest\.spamassassin\.org;}); unlink('./INPUTMSG'); }; } __PACKAGE__->runtests(); mimedefang-3.6/t/core.t000066400000000000000000000031571475763067200150650ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::core; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use Mail::MIMEDefang; use File::Copy; sub t_init_globals1 : Test(1) { $::main::Changed = 1; is($::main::Changed, 1); } sub t_init_globals2 : Test(1) { $::main::Changed = 1; init_globals(); is($::main::Changed, 0); } sub t_read_config : Test(1) { init_globals(); no warnings qw(redefine once); local *::md_syslog = sub { note $_[1] }; use warnings qw(redefine once); my $conf_fname = "t/data/md.conf"; SKIP: { skip "read_config test must be run as root", 1 unless ($< eq 0); my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($conf_fname); # Set correct owner to config file chown(0, 0, $conf_fname); ::main::read_config($conf_fname); is($SALocalTestsOnly, 0); # Set back original owner chown($uid, $gid, $conf_fname); }; } sub t_detect_and_load_perl_modules : Test(1) { my $dnsver; detect_and_load_perl_modules(); $dnsver = Net::DNS->version; like($dnsver, qr/([0-9]+)\.([0-9]+)/, "Net::DNS correctly loaded, version is $dnsver"); } sub t_mimedefang_version : Test(1) { like(md_version(), qr/[0-9]\.[0-9]{1,2}/); } sub t_read_commands_file : Test(5) { copy('t/data/COMMANDS', './COMMANDS'); init_globals(); read_commands_file(); is($Sender, '<15522-813-61658-597@mail.example.com>'); is($RelayAddr, '1.2.3.4'); is($Subject, 'Subject'); is($QueueID, '46QDY4CT972760'); is($SendmailMacros{load_avg}, 1); unlink('./COMMANDS'); } __PACKAGE__->runtests(); mimedefang-3.6/t/critic.t000066400000000000000000000006031475763067200154030ustar00rootroot00000000000000use strict; use warnings; use Test::More; if (not $ENV{TEST_AUTHOR}) { my $msg = 'Author test. Set $ENV{TEST_AUTHOR} to a true value to run.'; plan( skip_all => $msg ); } eval { require Test::Perl::Critic; }; if ($@) { my $msg = 'Test::Perl::Critic required to criticise code'; plan( skip_all => $msg ); } else { Test::Perl::Critic->import; } all_critic_ok('modules'); mimedefang-3.6/t/data/000077500000000000000000000000001475763067200146535ustar00rootroot00000000000000mimedefang-3.6/t/data/COMMANDS000066400000000000000000000010041475763067200157720ustar00rootroot00000000000000icQdy59i S<15522-813-61658-597@mail.example.com> sBODY=8BITMIME =_ [1.2.3.4] =daemon_name MTA =daemon_port 25 =i 46QDY4CT972760 =if_addr 192.168.0.172 =if_name s1.domain.com =j s1.domain.com =mail_addr 15522-813-61658-597@mail.example.com =mail_host mail.example.com. =mail_mailer esmtp =client_port 33870 =load_avg 1 =mail_host mail.example.com. =mail_addr 15522-813-61658-597@example.com Q46QDY4CT972760 USubject H[1.2.3.4] I1.2.3.4 Egamma.example.com R esmtp [junior.example.net] user@domain.com mimedefang-3.6/t/data/arc-wrong.eml000066400000000000000000000036421475763067200172560ustar00rootroot00000000000000From mickey@dkim.org Mon Oct 3 23:34:13 2005 Return-Path: Received: from sj-iport-5.cisco.com (sj-iport-5.cisco.com [171.68.10.87]) by testing.dkim.org (8.12.11/8.12.10) with ESMTP id j946YDQp007981 for ; Mon, 3 Oct 2005 23:34:13 -0700 Received: from sj-core-4.cisco.com ([171.68.223.138]) by sj-iport-5.cisco.com with ESMTP; 04 Oct 2005 00:32:01 -0700 X-IronPort-AV: i="3.97,171,1125903600"; d="scan'208"; a="216807210:sNHT21216320" Received: from localhost.localdomain ([171.71.17.133]) by sj-core-4.cisco.com (8.12.10/8.12.6) with ESMTP id j947Vwuk003169 for ; Tue, 4 Oct 2005 00:31:58 -0700 (PDT) Message-Id: <200510040731.j947Vwuk003169@sj-core-4.cisco.com> Date: Thu, 31 May 2001 07:13:26 -0700 (PDT) From: mickey@dkim.org To: andy.zipper@enron.com, david.berberian@enron.com, rex.shelby@enron.com, s..palmer@enron.com, david.forster@enron.com, marc.eichmann@enron.com Subject: GM - Very Promising Beginning Cc: john.pavetto@enron.com, leonardo.pacheco@enron.com Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-bcc: X-Folder: \ExMerge - Zipper, Andy\origination X-Origin: ZIPPER-A X-FileName: andy zipper 6-26-02.PST Status: RO Lines: 29 X-PMX-Version: 4.7.1.128075 X-from-outside-Cisco: [171.71.17.133] DKIM-Signature: a=rsa-sha1; q=dns; l=86; t=1128407653; x=-1019075996; c=nowsp; s=testing1234; h=Subject:From:To:Cc:Date:Content-Type:Content-Transfer-Encoding:MIME-Version:X-DKIM; d=dkim.org; i=mickey@dkim.org; z=From:mickey@dkim.org| Subject:GM=20-=20Very=20Promising=20Beginning; b=arNPPGvmpIRG9XQP8qDp1L9AW8pSAphkFmLlN4fMjOHHfdU1vsWfBOzWgcAcBkvSnlYBvpRK 7W1xBb+SOrBCvQ==; Authentication-Results: gc.mta2vsmtp.cc.prd.sparkpost smtp.user=test; auth=pass (PLAIN) Team I think this is good news. We can cover this with Derrick on our conference call this morning. -John mimedefang-3.6/t/data/arc1.eml000066400000000000000000000036251475763067200162060ustar00rootroot00000000000000From mickey@dkim.org Mon Oct 3 23:34:13 2005 Return-Path: Received: from sj-iport-5.cisco.com (sj-iport-5.cisco.com [171.68.10.87]) by testing.dkim.org (8.12.11/8.12.10) with ESMTP id j946YDQp007981 for ; Mon, 3 Oct 2005 23:34:13 -0700 Received: from sj-core-4.cisco.com ([171.68.223.138]) by sj-iport-5.cisco.com with ESMTP; 04 Oct 2005 00:32:01 -0700 X-IronPort-AV: i="3.97,171,1125903600"; d="scan'208"; a="216807210:sNHT21216320" Received: from localhost.localdomain ([171.71.17.133]) by sj-core-4.cisco.com (8.12.10/8.12.6) with ESMTP id j947Vwuk003169 for ; Tue, 4 Oct 2005 00:31:58 -0700 (PDT) Message-Id: <200510040731.j947Vwuk003169@sj-core-4.cisco.com> Date: Thu, 31 May 2001 07:13:26 -0700 (PDT) From: mickey@dkim.org To: andy.zipper@enron.com, david.berberian@enron.com, rex.shelby@enron.com, s..palmer@enron.com, david.forster@enron.com, marc.eichmann@enron.com Subject: GM - Very Promising Beginning Cc: john.pavetto@enron.com, leonardo.pacheco@enron.com Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-bcc: X-Folder: \ExMerge - Zipper, Andy\origination X-Origin: ZIPPER-A X-FileName: andy zipper 6-26-02.PST Status: RO Lines: 29 X-PMX-Version: 4.7.1.128075 X-from-outside-Cisco: [171.71.17.133] DKIM-Signature: a=rsa-sha1; q=dns; l=86; t=1128407653; x=-1019075996; c=nowsp; s=testing1234; h=Subject:From:To:Cc:Date:Content-Type:Content-Transfer-Encoding:MIME-Version:X-DKIM; d=dkim.org; i=mickey@dkim.org; z=From:mickey@dkim.org| Subject:GM=20-=20Very=20Promising=20Beginning; b=arNPPGvmpIRG9XQP8qDp1L9AW8pSAphkFmLlN4fMjOHHfdU1vsWfBOzWgcAcBkvSnlYBvpRK 7W1xBb+SOrBCvQ==; Authentication-Results: testing.dkim.org; header.From=mickey@dkim.org; dkim=pass ( message from dkim.org verified; ); Team I think this is good news. We can cover this with Derrick on our conference call this morning. -John mimedefang-3.6/t/data/arc2.eml000066400000000000000000000015311475763067200162010ustar00rootroot00000000000000Authentication-Results: sa-test.spamassassin.org; dkim=pass (768-bit key) header.d=sa-test.spamassassin.org header.b=oRxHoP0Y; spf=none (domain of test@sa-test.spamassassin.org doesn't specify if 1.2.3.4 is a permitted sender) smtp.mailfrom=test@sa-test.spamassassin.org; DKIM-Signature: v=1; a=rsa-sha1; d=sa-test.spamassassin.org; h=from:to :subject:message-id:date:mime-version:content-type; s=t0768; bh= vxHXq7bMZ9+UHGuKBsbQKsDHmmk=; b=oRxHoP0YN5LfqwKiqM0iFIeG6J0odmtG bttw9VPWjmTouQl15+EEPNfmWNyKqbXYSs0BbmfkWZuYHSf56lj1F89xqbf62w2h cnelgVK2HSbyD8kzbAAGR/yPWamnRhne From: SpamAssassin Test To: undisclosed-recipients:; Subject: test message 1 Message-ID: <4A294538.10002@spamassassin.org> Date: Mon, 08 Jun 2009 12:00:00 +0000 MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii testing mimedefang-3.6/t/data/dkim.pem000066400000000000000000000032171475763067200163050ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAnJ0bxiTvcfrp5iLPBojr1JcKoPCOnwSEG6plWCSWyri2BQzv Y/yNeoFjXqCVaJq8DqfvC9Y7IgpeN3AsPwevoBeZ8n/CXuzP/1NHVvRZKQi7Qv4S qeUzxEBjBiUeaT0uwNwbOGFtI2gwz6/aU1qo+eyGqmsBV3fdKFSARluhDcdo9IRE xob03priQUg9GyQirrKBJRVMUYdAJjXAB6tGIVme7u0uZU9j2OMPgIxdZ0PAKD7z Lam87Ys8LmD7NeJuBuWHSMyDaJBsKPfRyqLh6KwfnKR1b6o6wS8G/buH9MXwIJIM vDETHgdx8SdT16cgam4vmS3tOE31xTb1cizB5wIDAQABAoIBAQCAx+fIvSJv9UVU Shc2ZmyVuUWyJV0BgXMoVKtVGXEtbUmvSX2oGEo2DCMr8pwpEePFoW29rAGf8iqb J5AanDVcTUv3zfqEl/JJH6+v3ulaBqYFZmrubVxt6gwDR46ENx001UlEyUg3LzvD 6/R5SjlcmZP6mlbWF+gxe5qnQD1k1+Ux3Q+FS4nTcx9tXcHppRgubxZYK1Py2Z58 N5dPZu17G9nkvp+PTCPzHSuQuG/eZ34SuGA/WpFNrS/uDP+Dt3UnKx6bq/j4+2jA gvEQf0yd2PJbsNRu5CJxMTGWA8M/MPoGhp4OOpCwali8ctFyKMJfxJ5Obdv/GYfR nxs/IhLxAoGBAMpvuzz7iuzvoMxYsYBgb17BbgppChfFt2HoXhLh72aEi9CN9Rx4 cBw+Z/8xKbCqdP3nURPw5ubIBlP871SxDhm6bqVypLhydHIoeCERekmzYPfevfQJ qOYoaX3gxDEZGpZtBTDsNVZewQXOu1ArBfbrVPliDPObQw2NE0i+nw6TAoGBAMYN hSPxM4n+yCKxwP2pLonSa+rFkx04gr5WqvVKwevb4hXvrbl7fZrt5viRao7CJnCS 7cNOYZoSVau0dy6Yjtrk4B+9f33xbG5PwZcS3R1Y8Vo1C3QgirmmnbbCSKakPYsg iAA+fn4wYxmWYbm3FsenhRMMO1QOjSPjeRet/T/dAoGALc+xMLDAFLR50CS2LpYk Pe5KJ8LzjMOqp1Z9i4pjsfCG73Bgqsu6mzmoMUD/7UH17cueNoQKzOkxPWp9As56 oWnmRjy2eCM2pOrxnJIm+WzFofJfTE2vUj6M+PaXj4Dd2nLbfOHHhD83MUhCbXb/ B4zkEveUdMCLIRLu7URuHE8CgYEAr57Ziw+dhiZ5TdqVqdlVvTzsGRjt5SFxPlsX 6b9Kyl0G1DDeufU5YiLHh29bMBAVigmf8GoWvkq30TSL4rPBKXV/EGY3IXTtk7wB jHdbv2RZUIZtC6K3oPyQKbmKNsq5EB/euWixPjCmD9B8A/TIrG5t4IAeM+FbYpGZ lVfhk7kCgYA3CKHfOn3hD5en3JlLNg2Vx7K2RPkcliP39Zm3AnBvrTHDTjFtQoo1 QN1r0NUEMkKQkR6GXsnky7VonMST80+UyTmXEC8/z3mUz729rzpvaXFXIkX6y2+E 3c9FUESQmmpdsUOyXnR06ghiuS7GVFbMSuWr6cQwYCKGc66c7jCM/g== -----END RSA PRIVATE KEY----- mimedefang-3.6/t/data/dkim1.eml000066400000000000000000000010661475763067200163620ustar00rootroot00000000000000DKIM-Signature: v=1; a=rsa-sha1; d=sa-test.spamassassin.org; h=from:to :subject:message-id:date:mime-version:content-type; s=t0768; bh= vxHXq7bMZ9+UHGuKBsbQKsDHmmk=; b=oRxHoP0YN5LfqwKiqM0iFIeG6J0odmtG bttw9VPWjmTouQl15+EEPNfmWNyKqbXYSs0BbmfkWZuYHSf56lj1F89xqbf62w2h cnelgVK2HSbyD8kzbAAGR/yPWamnRhne From: SpamAssassin Test To: undisclosed-recipients:; Subject: test message 1 Message-ID: <4A294538.10002@spamassassin.org> Date: Mon, 08 Jun 2009 12:00:00 +0000 MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii testing mimedefang-3.6/t/data/dkim2.eml000066400000000000000000000011741475763067200163630ustar00rootroot00000000000000DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d= sa-test.spamassassin.org; h=from:to:subject:message-id:date :mime-version:content-type; s=t0768; t=1244419200; bh=15pFrAvOGi +eHKJgB6psh6iIBCbvYSuhPj+wQn6C7Ss=; b=mIrAui3j2XnmatMmIz4LaV02wt OY7v3zJAq+pIdOjyhqgSehCByD0/+DJWjOHXFjul0ZLSqMd+M+13ZQo/5Z0OValm FTva44EC7CPc1ZvZn7S9WpDyBW5pz7qGD4d8q3 From: SpamAssassin Test FAKE To: undisclosed-recipients:; Subject: test message (should fail) 2 Message-ID: <4A294538.10002@spamassassin.org> Date: Mon, 08 Jun 2009 12:00:00 +0000 MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii testing mimedefang-3.6/t/data/dkim3.eml000066400000000000000000000024461475763067200163670ustar00rootroot00000000000000From <> Mon Mar 20 16:54:54 2023 Received: by mail-lf1-f48.google.com with SMTP id ul9c9qx1lkrfqsc.1 for ; Sat, 18 Mar 2023 12:26:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; t=1679167567; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id; X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; t=1679167567; h=to:subject:message-id:date:from:mime-version:x-gm-message-state :from:to:cc:subject:date:message-id; X-Gm-Message-State: AO0yUKUzvHoQsVBm2mz1wVjzihmblQvHMjhIifdYuMXCTLhTpVqE4e7p cMXv56hLA5ZamE4RzoJPeiwwnF7naq7FOzOI40dOADh3 X-Google-Smtp-Source: AK7set/YDx3WjJC5IPvgtHk4rv9y9aKRM2cijiODqXOjIE9RpwegJFw3pVGvrVXRgCzN6R2oaHb5bfQWRIN67OQ32ZU= X-Received: by 2002:ac2:5591:0:b0:4dd:98c6:ee3 with SMTP id v17-20020ac25591000000b004dd98c60ee3mr5520795lfg.4.1679167567740; Sat, 18 Mar 2023 12:26:07 -0700 (PDT) MIME-Version: 1.0 From: Welcome Date: Mon, 20 Mar 2023 17:54:38 +0100 Message-ID: Subject: user: You may be on our list. To: user Content-Type: text/html; Test mimedefang-3.6/t/data/dkim_sig.txt000066400000000000000000000007631475763067200172100ustar00rootroot00000000000000v=1; a=rsa-sha1; c=relaxed/simple; d=example.org; h=from :to:subject:mime-version:content-type; s=default; bh=MAUU+6+KlDJ QzCqO71VN08xBlF4=; b=O1Fi9OF6Z/TEaNWjJNQtFYElD30NEkPk7kfgbyt52KM /T5f/rqX4tIazFvOuJRLhHqav/ZqYcrp2HtuO5XfVSt54ysMSU4aMbXC19akJlv2 G+rtNYpXz6qhlwC+crYA01k0PUrfBeLTiigl+ofMJKNzkZmjB3QisxMKkgIZ1Kgo SFMyuNiKCD24FimsjkgpLA4n2CYjyIU6oixVSb5TZGOFGCVlpks1Jjgo1x5ZJCEp O7YDi1J5t+oiQdaZjnXKNLBm4IqTtaB2yQIeuVZnPHmzq7bO5jQEojQZm7oZox54 5U/fCjl5tG52YaiTd1gyH5MJrFb0y+0pCUnjqaeHTEA==mimedefang-3.6/t/data/dkim_sig2.txt000066400000000000000000000010101475763067200172540ustar00rootroot00000000000000v=1; a=rsa-sha256; c=relaxed/simple; d=example.com; h= from:to:subject:mime-version:content-type; s=selector; bh=xWkDNG dajbyHmdMPMgjYBPaKILYNC1EIC1dFBnpM8Ck=; b=FvlYrU75ffEDsAcwK1pOj0 Lk7hrJXfw3KE/lyK4pqaeQlzwGt9b/Xg5ZxLJvtiCjQ2JECjYXtxdk1ZjVBMxvGH OQQ4lv6dN4KJy7jKdlLwn1SH5959shaRSPl4sje73C1c22RTMwdU7N5xbi6uRyR6 Zmyc6MIkmo/WL48uQHIKOftP5MR16hrmeDqXrwxYDS+DwNatC9CO0Bt8c86rBSI1 KG7H5Lry35yV0Fl9K7IxQUnvI/eloyNsEeDqkIXTZckYTPceA4g6l9Stc3i3XYJN pnMUcH8imjsmBmjpc5coe3sTsCiJwO8bcnWmgmArQsWmmSevz1PCs+kg5zpSrlFg ==mimedefang-3.6/t/data/dkim_sig2a.txt000066400000000000000000000007611475763067200174310ustar00rootroot00000000000000v=1; a=rsa-sha256; c=relaxed; d=example.com; h=from:to:subject:mime-version:content-type; s=selector; bh=xWkDNGdajbyHmdMPMgjYBPaKILYNC1EIC1dFBnpM8Ck=; b=ZA2V0LNWS/UHPfOfMfkzZPxjydsnBJSo/ouxA2+WQp/UwbCQauA4ar4glykCTU1xoHu02BAtI/Wpw1oOjVHrzU5FBm2mASzfWekabD3OaMcb0w/iyy7h0oNduPGcMqF62v/ZFeo7SH+s4T6qiC1suWJOh1O4bP+3GkutQH66i8aWRCkRM9IcfS3eyLIMcssa+oudLho2bs0LDe+HjcCGQDlSipHcnj+bRkuXkutb2C7AdM5nzCn9iGTO8qWvK3P3Es/Uw3ICuUml+kgd0sKtE7rT6TR1gmT9WeExPrr7Ob2gwWx7vWRR0GDiKBLM0KMLVeOn0q8tW1cQXBVixrKGGA==mimedefang-3.6/t/data/exe.eml000066400000000000000000000011441475763067200161330ustar00rootroot00000000000000Date: Sun, 26 Jan 2025 15:19:27 +0100 From: Giovanni To: Giovanni Subject: Test .exe Message-ID: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="TMN48Ckxl6bFmFWo" Content-Disposition: inline Content-Length: 278 Lines: 14 --TMN48Ckxl6bFmFWo Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Test --TMN48Ckxl6bFmFWo Content-Type: application/octet-stream Content-Disposition: attachment; filename="test.exe" Content-Transfer-Encoding: quoted-printable MZ=0A --TMN48Ckxl6bFmFWo-- mimedefang-3.6/t/data/gtube.eml000066400000000000000000000004371475763067200164640ustar00rootroot00000000000000From: Sender To: Recipient Subject: GTUBE MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii; format=flowed XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X Generic Test for Unsolicited Bulk Email mimedefang-3.6/t/data/md.conf000066400000000000000000000000271475763067200161210ustar00rootroot00000000000000$SALocalTestsOnly = 0; mimedefang-3.6/t/data/mimedefang-test-filter000066400000000000000000000334551475763067200211440ustar00rootroot00000000000000# -*- Perl -*- #*********************************************************************** # # mimedefang-filter # # Suggested minimum-protection filter for Microsoft Windows clients, plus # SpamAssassin checks if SpamAssassin is installed. # # Copyright (C) 2002 Roaring Penguin Software Inc. # # This program may be distributed under the terms of the GNU General # Public License, Version 2. # # $Id$ #*********************************************************************** #*********************************************************************** # Set administrator's e-mail address here. The administrator receives # quarantine messages and is listed as the contact for site-wide # MIMEDefang policy. A good example would be 'defang-admin@mydomain.com' #*********************************************************************** $AdminAddress = 'postmaster@localhost'; $AdminName = "MIMEDefang Administrator's Full Name"; #*********************************************************************** # Set the e-mail address from which MIMEDefang quarantine warnings and # user notifications appear to come. A good example would be # 'mimedefang@mydomain.com'. Make sure to have an alias for this # address if you want replies to it to work. #*********************************************************************** $DaemonAddress = 'mimedefang@localhost'; #*********************************************************************** # If you set $AddWarningsInline to 1, then MIMEDefang tries *very* hard # to add warnings directly in the message body (text or html) rather # than adding a separate "WARNING.TXT" MIME part. If the message # has no text or html part, then a separate MIME part is still used. #*********************************************************************** $AddWarningsInline = 0; #*********************************************************************** # To enable syslogging of virus and spam activity, add the following # to the filter: # md_graphdefang_log_enable(); # You may optionally provide a syslogging facility by passing an # argument such as: md_graphdefang_log_enable('local4'); If you do this, be # sure to setup the new syslog facility (probably in /etc/syslog.conf). # An optional second argument causes a line of output to be produced # for each recipient (if it is 1), or only a single summary line # for all recipients (if it is 0.) The default is 1. # Comment this line out to disable logging. #*********************************************************************** md_graphdefang_log_enable('mail', 1); #*********************************************************************** # Uncomment this to block messages with more than 50 parts. This will # *NOT* work unless you're using Roaring Penguin's patched version # of MIME tools, version MIME-tools-5.411a-RP-Patched-02 or later. # # WARNING: DO NOT SET THIS VARIABLE unless you're using at least # MIME-tools-5.411a-RP-Patched-02; otherwise, your filter will fail. #*********************************************************************** # $MaxMIMEParts = 50; #*********************************************************************** # Set various stupid things your mail client does below. #*********************************************************************** # Set the next one if your mail client cannot handle multiple "inline" # parts. $Stupidity{"NoMultipleInlines"} = 0; # Detect and load Perl modules detect_and_load_perl_modules(); # This procedure returns true for entities with bad filenames. sub filter_bad_filename { my($entity) = @_; my($bad_exts, $re); # Bad extensions $bad_exts = '(ade|adp|app|asd|asf|asx|bas|bat|chm|cmd|com|cpl|crt|dll|exe|fxp|hlp|hta|hto|inf|ini|ins|isp|jse?|lib|lnk|mdb|mde|msc|msi|msp|mst|ocx|pcd|pif|prg|reg|scr|sct|sh|shb|shs|sys|url|vb|vbe|vbs|vcs|vxd|wmd|wms|wmz|wsc|wsf|wsh|\{[^\}]+\})'; # Do not allow: # - CLSIDs {foobarbaz} # - bad extensions (possibly with trailing dots) at end $re = '\.' . $bad_exts . '\.*$'; return 1 if (re_match($entity, $re)); # Look inside ZIP files if (re_match($entity, '\.zip$') and $Features{"Archive::Zip"}) { my $bh = $entity->bodyhandle(); if (defined($bh)) { my $path = $bh->path(); if (defined($path)) { return re_match_in_zip_directory($path, $re); } } } return 0; } #*********************************************************************** # %PROCEDURE: filter_begin # %ARGUMENTS: # $entity -- the parsed MIME::Entity # %RETURNS: # Nothing # %DESCRIPTION: # Called just before e-mail parts are processed #*********************************************************************** sub filter_begin { my($entity) = @_; # ALWAYS drop messages with suspicious chars in headers if ($SuspiciousCharsInHeaders) { md_graphdefang_log('suspicious_chars'); action_quarantine_entire_message("Message quarantined because of suspicious characters in headers"); # Do NOT allow message to reach recipient(s) return action_discard(); } # Copy original message into work directory as an "mbox" file for # virus-scanning md_copy_orig_msg_to_work_dir_as_mbox_file(); # Scan for viruses if any virus-scanners are installed my($code, $category, $action) = message_contains_virus(); # Lower level of paranoia - only looks for actual viruses $FoundVirus = ($category eq "virus"); # Higher level of paranoia - takes care of "suspicious" objects # $FoundVirus = ($action eq "quarantine"); if ($FoundVirus) { md_graphdefang_log('virus', $VirusName, $RelayAddr); md_syslog('warning', "Discarding because of virus $VirusName"); return action_discard(); } if ($action eq "tempfail") { action_tempfail("Problem running virus-scanner"); md_syslog('warning', "Problem running virus scanner: code=$code, category=$category, action=$action"); } } #*********************************************************************** # %PROCEDURE: filter # %ARGUMENTS: # entity -- a Mime::Entity object (see MIME-tools documentation for details) # fname -- the suggested filename, taken from the MIME Content-Disposition: # header. If no filename was suggested, then fname is "" # ext -- the file extension (everything from the last period in the name # to the end of the name, including the period.) # type -- the MIME type, taken from the Content-Type: header. # # NOTE: There are two likely and one unlikely place for a filename to # appear in a MIME message: In Content-Disposition: filename, in # Content-Type: name, and in Content-Description. If you are paranoid, # you will use the re_match and re_match_ext functions, which return true # if ANY of these possibilities match. re_match checks the whole name; # re_match_ext checks the extension. See the sample filter below for usage. # %RETURNS: # Nothing # %DESCRIPTION: # This function is called once for each part of a MIME message. # There are many action_*() routines which can decide the fate # of each part; see the mimedefang-filter man page. #*********************************************************************** sub filter { my($entity, $fname, $ext, $type) = @_; return if message_rejected(); # Avoid unnecessary work # Block message/partial parts if (lc($type) eq "message/partial") { md_graphdefang_log('message/partial'); return action_bounce("MIME type message/partial not accepted here"); } if (filter_bad_filename($entity)) { md_graphdefang_log('bad_filename', $fname, $type); return action_drop_with_warning("An attachment named $fname was removed from this document as it\nconstituted a security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n"); } return action_accept(); } #*********************************************************************** # %PROCEDURE: filter_multipart # %ARGUMENTS: # entity -- a Mime::Entity object (see MIME-tools documentation for details) # fname -- the suggested filename, taken from the MIME Content-Disposition: # header. If no filename was suggested, then fname is "" # ext -- the file extension (everything from the last period in the name # to the end of the name, including the period.) # type -- the MIME type, taken from the Content-Type: header. # %RETURNS: # Nothing # %DESCRIPTION: # This is called for multipart "container" parts such as message/rfc822. # You cannot replace the body (because multipart parts have no body), # but you should check for bad filenames. #*********************************************************************** sub filter_multipart { my($entity, $fname, $ext, $type) = @_; return if message_rejected(); # Avoid unnecessary work if (filter_bad_filename($entity)) { md_graphdefang_log('bad_filename', $fname, $type); action_notify_administrator("A MULTIPART attachment of type $type, named $fname was dropped.\n"); return action_drop_with_warning("An attachment of type $type, named $fname was removed from this document as it\nconstituted a security hazard. If you require this document, please contact\nthe sender and arrange an alternate means of receiving it.\n"); } # Block message/partial parts if (lc($type) eq "message/partial") { md_graphdefang_log('message/partial'); return action_bounce("MIME type message/partial not accepted here"); } return action_accept(); } #*********************************************************************** # %PROCEDURE: defang_warning # %ARGUMENTS: # oldfname -- the old file name of an attachment # fname -- the new "defanged" name # %RETURNS: # A warning message # %DESCRIPTION: # This function customizes the warning message when an attachment # is defanged. #*********************************************************************** sub defang_warning { my($oldfname, $fname) = @_; return "An attachment named '$oldfname' was converted to '$fname'.\n" . "To recover the file, right-click on the attachment and Save As\n" . "'$oldfname'\n"; } # If SpamAssassin found SPAM, append report. We do it as a separate # attachment of type text/plain sub filter_end { my($entity) = @_; # If you want quarantine reports, uncomment next line # send_quarantine_notifications(); # IMPORTANT NOTE: YOU MUST CALL send_quarantine_notifications() AFTER # ANY PARTS HAVE BEEN QUARANTINED. SO IF YOU MODIFY THIS FILTER TO # QUARANTINE SPAM, REWORK THE LOGIC TO CALL send_quarantine_notifications() # AT THE END!!! # No sense doing any extra work return if message_rejected(); # Spam checks if SpamAssassin is installed if ($Features{"SpamAssassin"}) { if (-s "./INPUTMSG" < 100*1024) { # Only scan messages smaller than 100kB. Larger messages # are extremely unlikely to be spam, and SpamAssassin is # dreadfully slow on very large messages. my($hits, $req, $names, $report) = spam_assassin_check(); my($score); if ($hits < 40) { $score = "*" x int($hits); } else { $score = "*" x 40; } # We add a header which looks like this: # X-Spam-Score: 6.8 (******) NAME_OF_TEST,NAME_OF_TEST # The number of asterisks in parens is the integer part # of the spam score clamped to a maximum of 40. # MUA filters can easily be written to trigger on a # minimum number of asterisks... if ($hits >= 100) { return action_bounce(); } elsif ($hits >= $req) { action_change_header("X-Spam-Score", "$hits ($score) $names"); md_graphdefang_log('spam', $hits, $RelayAddr); # If you find the SA report useful, add it, I guess... action_add_part($entity, "text/plain", "-suggest", "$report\n", "SpamAssassinReport.txt", "inline"); } else { # Delete any existing X-Spam-Score header? action_delete_header("X-Spam-Score"); } } } # Check email message with Rspamd my ($hits, $req, $names, $report, $action, $is_spam); if (-s "./INPUTMSG" < 100*1024) { ($hits, $req, $names, $report, $action, $is_spam) = rspamd_check(); if(defined $action or defined $is_spam or defined $names) { md_syslog("Warning", "Action: $action, Spam: $is_spam, Names: $names"); } if ($is_spam eq "true") { action_change_header("X-Spam-Score", "$hits/$req $names"); md_syslog("Warning", "Action: $action"); md_graphdefang_log('spam', $hits, $RelayAddr); } else { # Delete any existing X-Spam-Score header? action_delete_header("X-Spam-Score"); } } # I HATE HTML MAIL! If there's a multipart/alternative with both # text/plain and text/html parts, nuke the text/html. Thanks for # wasting our disk space and bandwidth... # If you want to strip out HTML parts if there is a corresponding # plain-text part, uncomment the next line. # remove_redundant_html_parts($entity); md_graphdefang_log('mail_in'); # Deal with malformed MIME. # Some viruses produce malformed MIME messages that are misinterpreted # by mail clients. They also might slip under the radar of MIMEDefang. # If you are worried about this, you should canonicalize all # e-mail by uncommenting the action_rebuild() line. This will # force _all_ messages to be reconstructed as valid MIME. It will # increase the load on your server, and might break messages produced # by marginal software. Your call. # action_rebuild(); } # DO NOT delete the next line, or Perl will complain. 1; mimedefang-3.6/t/data/multipart.eml000066400000000000000000000011411475763067200173700ustar00rootroot00000000000000From: Foo To: Bar Subject: multipart email Content-Type: multipart/mixed; boundary="foo" MIME-Version: 1.0 --foo Content-Type: multipart/alternative; boundary="bar" --bar Content-Type: text/plain; charset="UTF-8" plaintext part --bar Content-Type: text/html; charset="UTF-8" Foo

HTML part

--bar-- --foo Content-Type: application/octet-stream Content-Disposition: attachment; filename="wow.bin" Content-Transfer-Encoding: base64 d293Cg== --foo- mimedefang-3.6/t/data/rspamd.conf000066400000000000000000000022711475763067200170120ustar00rootroot00000000000000# System V init adopted top level configuration # Please don't modify this file as your changes might be overwritten with # the next update. # # You can modify '$LOCAL_CONFDIR/rspamd.conf.local.override' to redefine # parameters defined on the top level # # You can modify '$LOCAL_CONFDIR/rspamd.conf.local' to add # parameters defined on the top level # # For specific modules or configuration you can also modify # '$LOCAL_CONFDIR/local.d/file.conf' - to add your options or rewrite defaults # '$LOCAL_CONFDIR/override.d/file.conf' - to override the defaults # # See https://rspamd.com/doc/tutorials/writing_rules.html for details .include "$CONFDIR/common.conf" logging { type = "file"; filename = "/tmp/rspamd.log"; .include "$CONFDIR/logging.inc" .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/logging.inc" .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/logging.inc" } worker "normal" { bind_socket = "localhost:11333"; .include "$CONFDIR/worker-normal.inc" .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/worker-normal.inc" .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/worker-normal.inc" } mimedefang-3.6/t/data/spf1.eml000066400000000000000000000023701475763067200162250ustar00rootroot00000000000000Return-Path: Received: from dnsbltest.spamassassin.org (dnsbltest.spamassassin.org [64.142.3.173]) by amgod.boxhost.net (Postfix) with SMTP id B9B2931016D for ; Tue, 10 Feb 2004 18:18:49 +0000 (GMT) Received: by proxy.google.com with SMTP id so1951389 for ; Tue, 10 Feb 2004 10:14:01 -0800 (PST) Received: by abbulk2 with SMTP id mr733125; Tue, 10 Feb 2004 10:14:01 -0800 (PST) Message-ID: <1076436841.67074.8fa05ccdc458abe5.1446041b@persist.google.com> Date: Tue, 10 Feb 2004 10:14:01 -0800 (PST) From: newsalerts-noreply@dnsbltest.spamassassin.org To: jm-google-news-alerts@jmason.org Subject: Google News Alert - spamassassin MIME-Version: 1.0 Content-Type: text/plain; charset="ISO-8859-1"; SWSOFT Unveils Plesk 7, Deployed by 1&1 Web Host Industry Review - USA ... The software also features a newly designed Windows XP-like user interface, is equipped SpamAssassin, an open source anti-spam tool, and includes "Application ... See all stories on this topic: mimedefang-3.6/t/data/tgz.eml000066400000000000000000000026051475763067200161610ustar00rootroot00000000000000Message-ID: <2e238d52-0f98-cdea-3e9d-dda625df5a08@example.com> Date: Thu, 5 May 2022 14:50:09 +0200 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.8.0 Content-Language: en-US To: me@example.com From: You Subject: Test Content-Type: multipart/mixed; boundary="------------62ATfdiTB07G9boOgpgkRqfH" This is a multi-part message in MIME format. --------------62ATfdiTB07G9boOgpgkRqfH Content-Type: multipart/alternative; boundary="------------02Tj3lGNeKJ1fcXPjVb9IeWa" --------------02Tj3lGNeKJ1fcXPjVb9IeWa Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit Test --------------02Tj3lGNeKJ1fcXPjVb9IeWa Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 7bit Test --------------02Tj3lGNeKJ1fcXPjVb9IeWa-- --------------62ATfdiTB07G9boOgpgkRqfH Content-Type: application/x-compressed-tar; name="test.tgz" Content-Disposition: attachment; filename="test.tgz" Content-Transfer-Encoding: base64 H4sIAAAAAAAAA+3SPQrCQBCG4a09xZxAdrN/59liIosSwawhxzdBAjaiTRDhfZoPZqb4imk6 tqPOanZkFymENV2O9jWfXDYuRJuy71JY5q7L0Ruxe5ba3MdWbiLmVK9TGYb67u7T/k/15ayy PoD09aKHX9cBAAAAAAAAAAAAAAAAAHzpARIuElQAKAAA --------------62ATfdiTB07G9boOgpgkRqfH-- mimedefang-3.6/t/data/uri-html.eml000066400000000000000000000006041475763067200171130ustar00rootroot00000000000000From: Foo To: Bar Subject: multipart email MIME-Version: 1.0 Content-Type: text/html; charset=us-ascii; format=flowed Foo

test

mimedefang-3.6/t/data/uri.eml000066400000000000000000000004431475763067200161520ustar00rootroot00000000000000From: Sender To: Recipient Subject: Uri UTM MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii; format=flowed Click on this http://www.example.com/My_Blog/mypage.aspx?id=123&utm_source=twitterfeed&utm_medium=twitter#c123456 url mimedefang-3.6/t/data/zip.eml000066400000000000000000000026421475763067200161600ustar00rootroot00000000000000Message-ID: <2e238d52-0f98-cdea-3e9d-dda625df5a08@example.com> Date: Thu, 5 May 2022 14:50:09 +0200 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.8.0 Content-Language: en-US To: me@example.com From: You Subject: Test Content-Type: multipart/mixed; boundary="------------62ATfdiTB07G9boOgpgkRqfH" This is a multi-part message in MIME format. --------------62ATfdiTB07G9boOgpgkRqfH Content-Type: multipart/alternative; boundary="------------02Tj3lGNeKJ1fcXPjVb9IeWa" --------------02Tj3lGNeKJ1fcXPjVb9IeWa Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit Test --------------02Tj3lGNeKJ1fcXPjVb9IeWa Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 7bit Test --------------02Tj3lGNeKJ1fcXPjVb9IeWa-- --------------62ATfdiTB07G9boOgpgkRqfH Content-Type: application/zip; name="test.zip" Content-Disposition: attachment; filename="test.zip" Content-Transfer-Encoding: base64 UEsDBAoAAAAAADN2pVTGNbk7BQAAAAUAAAAIABwAdGVzdC50eHRVVAkAA+HHc2Lhx3NidXgL AAEE6AMAAAToAwAAdGVzdApQSwECHgMKAAAAAAAzdqVUxjW5OwUAAAAFAAAACAAYAAAAAAAB AAAApIEAAAAAdGVzdC50eHRVVAUAA+HHc2J1eAsAAQToAwAABOgDAABQSwUGAAAAAAEAAQBO AAAARwAAAAAA --------------62ATfdiTB07G9boOgpgkRqfH-- mimedefang-3.6/t/dates.t000066400000000000000000000027571475763067200152420ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::dates; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use POSIX; sub header_timezone_works : Test(3) { local $ENV{TZ} = 'UTC'; is(::main::header_timezone(time), '+0000', 'Got header timezone for TZ=UTC'); # Note: we use America/Regina here as the province of Saskatchewan does # not observe DST. $::main::CachedTimezone = ''; local $ENV{TZ} = 'America/Regina'; is(::main::header_timezone(time), '-0600', 'Got header timezone for TZ=America/Regina'); $::main::CachedTimezone = '-1000'; is(::main::header_timezone(time), '-1000', 'cache gets used'); $::main::CachedTimezone = ''; } sub rfc2822_date_works : Test(1) { my $now = time(); no warnings 'redefine'; no warnings 'once'; local *time = sub { return $now; }; my $want = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($now)); is(::main::rfc2822_date(), $want, 'Got correct RFC 2822 date'); } sub gen_msgid_header_works : Test(1) { no warnings 'once'; local $::main::QueueID = 'wookie'; like(::main::gen_msgid_header(), qr/Message-ID: <\d{12}\.wookie\@[-a-zA-Z0-9\.]+>\n/, 'Got Message-ID header in correct format'); } sub t_time_str: Test(1) { # XXX use a better regexp like(Mail::MIMEDefang::Utils::time_str(), qr/[0-9]{4}\-[0-9]{2}\-[0-9]{2}\-[0-9]{2}\.[0-9]{2}\.[0-9]{2}/); } sub t_hour_str: Test(1) { # XXX use a better regexp like(Mail::MIMEDefang::Utils::hour_str(), qr/[0-9]{4}\-[0-9]{2}\-[0-9]{2}\-[0-9]{2}/); } __PACKAGE__->runtests(); mimedefang-3.6/t/dkim.t000066400000000000000000000047711475763067200150640ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::DKIM; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use Mail::MIMEDefang; use Mail::MIMEDefang::DKIM; use File::Copy; sub dkim_sign : Test(5) { copy('t/data/uri.eml', './INPUTMSG'); my ($correct_signature, $dkim_sig, $dkim_sig_notw, $header); # disable DKIM TextWrap, must be the first call # otherwise Mail::DKIM::TextWrap will be loaded and cannot be disabled open(my $fd, '<', 't/data/dkim_sig2a.txt') or die("Cannot open signature file: $!"); while(<$fd>) { local $/; $correct_signature .= $_; } close($fd); ($header, $dkim_sig_notw) = md_dkim_sign('t/data/dkim.pem', 'rsa-sha256', 'relaxed', 'example.com', 'selector', undef, 0); is($dkim_sig_notw, $correct_signature); if($dkim_sig_notw =~ /(?) { local $/; $correct_signature .= $_; } close($fd); ($header, $dkim_sig) = md_dkim_sign('t/data/dkim.pem'); is($dkim_sig, $correct_signature); if($dkim_sig =~ /(?) { local $/; $correct_signature .= $_; } close($fd); ($header, $dkim_sig) = md_dkim_sign('t/data/dkim.pem', 'rsa-sha256', 'relaxed/simple', 'example.com', 'selector'); is($dkim_sig, $correct_signature); undef $correct_signature; unlink('./INPUTMSG'); } sub dkim_verify : Test(5) { my ($result, $domain, $ksize); SKIP: { if ( (not defined $ENV{'NET_TEST'}) or ($ENV{'NET_TEST'} ne 'yes' )) { skip "Net test disabled", 1 } copy('t/data/dkim1.eml', './INPUTMSG'); ($result, $domain, $ksize) = md_dkim_verify(); is($result, "pass"); is($ksize, 768); unlink('./INPUTMSG'); copy('t/data/dkim2.eml', './INPUTMSG'); ($result, $domain, $ksize) = md_dkim_verify(); is($result, "fail"); unlink('./INPUTMSG'); copy('t/data/dkim3.eml', './INPUTMSG'); ($result, $domain, $ksize) = md_dkim_verify(); like($result, qr/fail|invalid/); unlink('./INPUTMSG'); copy('t/data/spf1.eml', './INPUTMSG'); ($result, $domain, $ksize) = md_dkim_verify(); is($result, "none"); unlink('./INPUTMSG'); }; } __PACKAGE__->runtests(); mimedefang-3.6/t/dockerPostfix.sh000066400000000000000000000024071475763067200171250ustar00rootroot00000000000000#!/bin/sh echo "Building MIMEDefang inside Docker..." VER=$(perl -I modules/lib -e 'use Mail::MIMEDefang; print Mail::MIMEDefang::md_version();') dnf install -y initscripts 1>/dev/null 2>&1 dnf remove -y --noautoremove mimedefang 1>/dev/null 2>&1 make distclean 1>/dev/null 2>&1 ./configure 1>/dev/null 2>&1 make distro 1>/dev/null 2>&1 mkdir -p ~/rpmbuild/SOURCES mkdir -p ~/rpmbuild/BUILD cp mimedefang-${VER}.tar.gz ~/rpmbuild/SOURCES perl -p -e "s/#VERSION#/${VER}/;s/#RELEASE#/1/;s/#BETA#//g" < redhat/mimedefang-spec.in >redhat/mimedefang.spec rm -rf ~/rpmbuild/RPMS/x86_64/mimedefang-* 1>/dev/null 2>&1 rpmbuild -bb redhat/mimedefang.spec 1>/dev/null 2>&1 dnf -y install ~/rpmbuild/RPMS/x86_64/mimedefang-* 1>/dev/null 2>&1 cp t/data/mimedefang-test-filter /etc/mail/mimedefang-filter . /etc/rc.d/init.d/functions daemon /usr/bin/mimedefang-multiplexor -m 4 -x 10 -y 1 -U defang -l -d -s /var/spool/MIMEDefang/mimedefang-multiplexor.sock daemon /usr/bin/mimedefang -m /var/spool/MIMEDefang/mimedefang-multiplexor.sock -y -U defang -q -T -p inet:10997 postfix start chown root t/data/md.conf mkdir -p /root/.spamassassin touch /root/.spamassassin/user_prefs rm -f /var/spool/mail/defang* echo "Starting regression tests inside Docker..." make test NET_TEST=yes SMTP_TEST=yes mimedefang-3.6/t/dockerSendmail.sh000066400000000000000000000027071475763067200172300ustar00rootroot00000000000000#!/bin/sh echo "Building MIMEDefang inside Docker..." VER=$(perl -I modules/lib -e 'use Mail::MIMEDefang; print Mail::MIMEDefang::md_version();') dnf install -y initscripts 1>/dev/null 2>&1 dnf remove -y --noautoremove mimedefang 1>/dev/null 2>&1 make distclean 1>/dev/null 2>&1 ./configure 1>/dev/null 2>&1 make distro 1>/dev/null 2>&1 mkdir -p ~/rpmbuild/SOURCES mkdir -p ~/rpmbuild/BUILD cp mimedefang-${VER}.tar.gz ~/rpmbuild/SOURCES perl -p -e "s/#VERSION#/${VER}/;s/#RELEASE#/1/;s/#BETA#//g" < redhat/mimedefang-spec.in >redhat/mimedefang.spec rm -rf ~/rpmbuild/RPMS/x86_64/mimedefang-* 1>/dev/null 2>&1 rpmbuild -bb redhat/mimedefang.spec 1>/dev/null 2>&1 dnf -y install ~/rpmbuild/RPMS/x86_64/mimedefang-* 1>/dev/null 2>&1 cp t/data/mimedefang-test-filter /etc/mail/mimedefang-filter . /etc/rc.d/init.d/functions daemon /usr/bin/mimedefang-multiplexor -m 4 -x 10 -y 1 -U defang -l -d -s /var/spool/MIMEDefang/mimedefang-multiplexor.sock daemon /usr/bin/mimedefang -m /var/spool/MIMEDefang/mimedefang-multiplexor.sock -y -U defang -q -T -p inet:10997 # Add our test domain name to hosts(5) to make sendmail(8) start faster H=`tail -n1 /etc/hosts` cp /etc/hosts ~/hosts.new sed -i "s/$H/$H example.com/" ~/hosts.new cp -f ~/hosts.new /etc/hosts sendmail -bd chown root t/data/md.conf mkdir -p /root/.spamassassin touch /root/.spamassassin/user_prefs rm -f /var/spool/mail/defang* echo "Starting regression tests inside Docker..." make test NET_TEST=yes SMTP_TEST=yes mimedefang-3.6/t/headers.t000066400000000000000000000051371475763067200155500ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::headers; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use Mail::MIMEDefang; use Mail::MIMEDefang::Actions; use Mail::MIMEDefang::Utils; sub delete_header_ok : Test(2) { my @results; # Lie about being in message context so these tests can work $InMessageContext = 1; action_delete_header('X-Header'); undef $results_fh; @results = Mail::MIMEDefang::Utils::read_results(); cmp_deeply( \@results, [ ['J', 'X-Header', 1 ] ], 'action_delete_header() wrote correct J line'); unlink('./RESULTS') if -f './RESULTS'; action_delete_header('X-Header', 2); undef $results_fh; @results = Mail::MIMEDefang::Utils::read_results(); cmp_deeply( \@results, [ [ 'J', 'X-Header', 2 ] ], 'action_delete_header() wrote correct J line'); unlink('./RESULTS') if -f './RESULTS'; } sub add_header_ok : Test(2) { my @results; # Lie about being in message context so these tests can work $InMessageContext = 1; action_add_header('X-Header', 'some content'); undef $results_fh; @results = Mail::MIMEDefang::Utils::read_results(); cmp_deeply( \@results, [ ['H', 'X-Header', 'some content' ] ], 'action_add_header() wrote correct H line'); action_add_header('X-Other', '42'); undef $results_fh; @results = Mail::MIMEDefang::Utils::read_results(); cmp_deeply( \@results, [ ['H', 'X-Header', 'some content' ], ['H', 'X-Other', 42] ], 'action_add_header() wrote correct H line'); unlink('./RESULTS') if -f './RESULTS'; } sub change_header_ok : Test(3) { my @results; # Lie about being in message context so these tests can work $InMessageContext = 1; action_change_header('X-Header', 'some content'); undef $results_fh; @results = Mail::MIMEDefang::Utils::read_results(); cmp_deeply( \@results, [ ['I', 'X-Header', 1, 'some content' ] ], 'action_change_header() wrote correct I line'); action_change_header('Received', 'position 3', 3); undef $results_fh; @results = Mail::MIMEDefang::Utils::read_results(); cmp_deeply( \@results, [ ['I', 'X-Header', 1, 'some content' ], ['I', 'Received', 3, 'position 3'] ], 'action_add_header() wrote correct I line'); undef @results; unlink('./RESULTS') if -f './RESULTS'; action_change_header('Subject', 'đŸŒ¸đŸ‘€ OK'); undef $results_fh; @results = Mail::MIMEDefang::Utils::read_results(); cmp_deeply( \@results, [ ['I', 'Subject', 1, 'đŸŒ¸đŸ‘€ OK' ] ], 'action_change_header() wrote correct I line'); unlink('./RESULTS') if -f './RESULTS'; } __PACKAGE__->runtests(); mimedefang-3.6/t/mime.t000066400000000000000000000045561475763067200150700ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::MIME; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use HTML::Parser; use MIME::Parser; use MIME::Entity; use Mail::MIMEDefang; use Mail::MIMEDefang::MIME; sub uri_utm_text : Test(1) { # Set up temporary dir system('rm', '-rf', 't/tmp'); mkdir('t/tmp', 0755); # Make a parser my $parser = MIME::Parser->new(); $parser->extract_nested_messages(1); $parser->extract_uuencode(1); $parser->output_to_core(0); $parser->tmp_to_core(0); my $filer = MIME::Parser::FileInto->new('t/tmp'); $filer->ignore_filename(1); $parser->filer($filer); my $entity = $parser->parse_open("t/data/uri.eml"); if(anonymize_uri($entity)) { is($entity->bodyhandle->as_string(), 'Click on this http://www.example.com/My_Blog/mypage.aspx?id=123 url'); } else { fail("uri_utm_text"); } system('rm', '-rf', 't/tmp'); } sub uri_utm_html : Test(1) { init_globals(); $Features{"HTML::Parser"} = 1; # Set up temporary dir system('rm', '-rf', 't/tmp'); mkdir('t/tmp', 0755); # Make a parser my $parser = MIME::Parser->new(); $parser->extract_nested_messages(1); $parser->extract_uuencode(1); $parser->output_to_core(0); $parser->tmp_to_core(0); my $filer = MIME::Parser::FileInto->new('t/tmp'); $filer->ignore_filename(1); $parser->filer($filer); my $entity = $parser->parse_open("t/data/uri-html.eml"); if(anonymize_uri($entity)) { like($entity->bodyhandle->as_string(), qr,test,); } else { fail("uri_utm_html"); } system('rm', '-rf', 't/tmp'); } sub t_takeStabAtFilename : Test(1) { init_globals(); $Features{"HTML::Parser"} = 1; # Set up temporary dir system('rm', '-rf', 't/tmp'); mkdir('t/tmp', 0755); # Make a parser my $parser = MIME::Parser->new(); $parser->extract_nested_messages(1); $parser->extract_uuencode(1); $parser->output_to_core(0); $parser->tmp_to_core(0); my $filer = MIME::Parser::FileInto->new('t/tmp'); $filer->ignore_filename(1); $parser->filer($filer); my $entity = $parser->parse_open("t/data/zip.eml"); my $res; foreach my $p ( $entity->parts() ) { if($p->head->recommended_filename()) { $res = takeStabAtFilename($p); like($res, qr/test\.zip/); } } system('rm', '-rf', 't/tmp'); } __PACKAGE__->runtests(); mimedefang-3.6/t/net.t000066400000000000000000000055221475763067200147210ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::Net; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use Sys::Hostname; use Mail::MIMEDefang; use Mail::MIMEDefang::Net; init_globals; $Features{"Net::DNS"} = 1; sub t_expand_ipv6_address : Test(1) { my $ipv6 = expand_ipv6_address('2a00:1450:4009:816::200e'); is($ipv6, '2a00:1450:4009:0816:0000:0000:0000:200e'); } sub t_get_host_name : Test(2) { my $hostname = hostname; my $host = ::main::get_host_name($hostname); like($host, qr/$hostname\.*/); $host = ::main::get_host_name(undef); like($host, qr/$hostname\.*/); } sub t_ipv4_public_ip : Test(2) { my $ip_priv = '172.16.0.1'; my $ip_pub = '212.212.212.212'; is(is_public_ip4_address($ip_priv), 0); is(is_public_ip4_address($ip_pub), 1); } sub t_ipv6_public_ip : Test(2) { my $ip_priv = 'fe80::354f:365c:422e:6ae'; my $ip_pub = '2001:460:1e1f:ddc::1'; is(is_public_ip6_address($ip_priv), 0); is(is_public_ip6_address($ip_pub), 1); } sub t_reverse_ip : Test(2) { my $ipv4 = '192.168.0.2'; my $ipv6 = 'fe80::1121:34db:fb39:a64e'; is(reverse_ip_address_for_rbl($ipv4), '2.0.168.192'); is(reverse_ip_address_for_rbl($ipv6), 'e.4.6.a.9.3.b.f.b.d.4.3.1.2.1.1.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f'); } sub t_get_ptr_record : Test(1) { my $ipv4 = '1.1.1.1'; is(Mail::MIMEDefang::Net::get_ptr_record($ipv4), 'one.one.one.one'); } sub t_relay_is_blacklisted_multi : Test(1) { my @rbl; $rbl[0] = "dnsbltest.spamassassin.org"; my $relayip = "144.137.3.98"; SKIP: { if ( (not defined $ENV{'NET_TEST'}) or ($ENV{'NET_TEST'} ne 'yes' )) { skip "Net test disabled", 1 } detect_and_load_perl_modules(); my $res = relay_is_blacklisted_multi($relayip, 10, 1, \@rbl); is($res->{"dnsbltest.spamassassin.org"}[0], "127.0.0.2"); } } sub t_relay_is_blacklisted : Test(1) { my $rbl = "dnsbltest.spamassassin.org"; my $relayip = "144.137.3.98"; SKIP: { if ( (not defined $ENV{'NET_TEST'}) or ($ENV{'NET_TEST'} ne 'yes' )) { skip "Net test disabled", 1 } detect_and_load_perl_modules(); my $ret = relay_is_blacklisted($relayip, $rbl); is($ret, "127.0.0.2"); } } sub t_email_is_blacklisted : Test(1) { my $rbl = "hashbltest2.spamassassin.org"; my $email = 'hustl.er@gmail.com'; SKIP: { if ( (not defined $ENV{'NET_TEST'}) or ($ENV{'NET_TEST'} ne 'yes' )) { skip "Net test disabled", 1 } detect_and_load_perl_modules(); my $ret = email_is_blacklisted($email, $rbl, 'md5'); is($ret, "127.0.0.2"); } } sub t_md_get_bogus_mx_hosts : Test(1) { my $domain = 'multihomed.dnsbltest.spamassassin.org'; my @res = md_get_bogus_mx_hosts($domain); is(scalar @res, 1); } sub t_get_mx_ip_addresses : Test(1) { my $domain = 'mimedefang.org'; my @res = get_mx_ip_addresses($domain); is(scalar @res, 2); } __PACKAGE__->runtests(); mimedefang-3.6/t/relay.t000066400000000000000000000067501475763067200152530ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::filter_relay; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; sub create_filter : Test(setup) { no warnings qw(once); *::main::filter_relay = sub { my($hostip, $hostname) = @_; # $response can be: # # ’REJECT’ # if the connection should be rejected. # # ’CONTINUE’ # if the connection should be accepted. # # ’TEMPFAIL’ # if a temporary failure code should be returned. # # ’DISCARD’ # if the message should be accepted and silently discarded. # # ’ACCEPT_AND_NO_MORE_FILTERING’ # if the connection should be accepted and no further filtering done. # # Earlier versions of MIMEDefang used -1 for TEMPFAIL, 0 for REJECT and 1 for CONTINUE. These values still # work, but are deprecated. # # In the case of REJECT or TEMPFAIL, $msg specifies the text part of the SMTP reply. $msg must not contain # newlines. # # my $response = ""; my $message = ""; my $code = ""; my $dsn = ""; my $delay = 0; if ($hostname =~ m/^reject/) { $response = 'REJECT'; $message = "reject"; if ($hostip =~ m/^192/) { $code = 555; $dsn = "5.5.5"; } } if ($hostname =~ m/^continue/) { $response = 'CONTINUE'; $message = "continue"; if ($hostip =~ m/^192/) { $code = 222; $dsn = "2.2.2"; } } if ($hostname =~ m/^tempfail/) { $response = 'TEMPFAIL'; $message = "tempfail"; if ($hostip =~ m/^192/) { $code = 444; $dsn = "4.4.4"; } } if ($hostname =~ m/^discard/) { $response = 'DISCARD'; $message = "discard"; if ($hostip =~ m/^192/) { $code = 299; $dsn = "2.99.99"; } } if ($hostname =~ m/^accept/) { $response = 'ACCEPT_AND_NO_MORE_FILTERING'; $message = "accept"; if ($hostip =~ m/^192/) { $code = 288; $dsn = "2.8.8"; } } if (!($hostip =~ m/^192/)) { $code = 999; $dsn = "9.9.9"; $delay = 9; } return ($response, $message, $code, $dsn, $delay); }; } sub reject : Test(4) { my ($self) = @_; $self->relay_test("192.168.1.1", 0, "reject", 555, "5.5.5", 0); $self->relay_test("10.10.10.10", 0, "reject", 554, "5.7.1", 9); } sub continue : Test(4) { my ($self) = @_; $self->relay_test("192.168.1.1", 1, "continue", 222, "2.2.2", 0); $self->relay_test("10.10.10.10", 1, "continue", 250, "2.1.0", 9); } sub tempfail : Test(4) { my ($self) = @_; $self->relay_test("192.168.1.1", -1, "tempfail", 444, "4.4.4", 0); $self->relay_test("10.10.10.10", -1, "tempfail", 451, "4.3.0", 9); } sub discard : Test(4) { my ($self) = @_; $self->relay_test("192.168.1.1", 3, "discard", 299, "2.99.99", 0); $self->relay_test("10.10.10.10", 3, "discard", 250, "2.1.0", 9); } sub accept_and_no_more_filtering : Test(4) { my ($self) = @_; $self->relay_test("192.168.1.1", 2, "accept", 288, "2.8.8", 0); $self->relay_test("10.10.10.10", 2, "accept", 250, "2.1.0", 9); } sub relay_test { my ($self, $ip, $action, $msg, $code, $dsn, $delay) = @_; my @answer; no warnings qw(redefine once); local *::md_syslog = sub { note $_[1] }; local *::main::print_and_flush = sub { @answer = split(/\s+/,$_[0]); }; use warnings qw(redefine once); lives_ok { ::main::handle_relayok( $ip, "$msg.com" ) } 'handle_relayok lives'; cmp_deeply( \@answer, [ 'ok', $action, $msg, $code, $dsn, $delay ], 'handle_relayok called send_filter_answer with expected arguments') or diag(explain(\@answer)); } __PACKAGE__->runtests(); mimedefang-3.6/t/sender.t000066400000000000000000000101501475763067200154040ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::filter_sender; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; sub create_filter : Test(setup) { no warnings qw(once); *::main::filter_sender = sub { my($sender, $hostip, $hostname, $helo) = @_; # $response can be: # # ’REJECT’ # if the connection should be rejected. # # ’CONTINUE’ # if the connection should be accepted. # # ’TEMPFAIL’ # if a temporary failure code should be returned. # # ’DISCARD’ # if the message should be accepted and silently discarded. # # ’ACCEPT_AND_NO_MORE_FILTERING’ # if the connection should be accepted and no further filtering done. # # Earlier versions of MIMEDefang used -1 for TEMPFAIL, 0 for REJECT and 1 for CONTINUE. These values still # work, but are deprecated. # # In the case of REJECT or TEMPFAIL, $msg specifies the text part of the SMTP reply. $msg must not contain # newlines. # # my $response = ""; my $message = ""; my $code = ""; my $dsn = ""; my $delay = 0; if ($sender =~ m/^reject/) { $response = 'REJECT'; $message = "reject"; if ($helo =~ m/^test/) { $code = 555; $dsn = "5.5.5"; } } if ($sender =~ m/^continue/) { $response = 'CONTINUE'; $message = "continue"; if ($helo =~ m/^test/) { $code = 222; $dsn = "2.2.2"; } } if ($sender =~ m/^tempfail/) { $response = 'TEMPFAIL'; $message = "tempfail"; if ($helo =~ m/^test/) { $code = 444; $dsn = "4.4.4"; } } if ($sender =~ m/^discard/) { $response = 'DISCARD'; $message = "discard"; if ($helo =~ m/^test/) { $code = 299; $dsn = "2.99.99"; } } if ($sender =~ m/^accept/) { $response = 'ACCEPT_AND_NO_MORE_FILTERING'; $message = "accept"; if ($helo =~ m/^test/) { $code = 288; $dsn = "2.8.8"; } } if ($helo =~ m/^default/) { $code = 999; $dsn = "9.9.9"; $delay = 9; } if ($::main::ESMTPArgs[1] ne 'esmtp2') { $message = "ESMTP args failed."; } return ($response, $message, $code, $dsn, $delay); }; } sub reject : Test(4) { my ($self) = @_; $self->sender_test('reject@foo.com', "192.168.1.1", "foo2.com", "test.org", 0, "reject", 555, "5.5.5", 0); $self->sender_test('reject@foo.com', "10.10.10.10", "foo2.com", "default.org", 0, "reject", 554, "5.7.1", 9); } sub tempfail : Test(4) { my ($self) = @_; $self->sender_test('tempfail@foo.com', "192.168.1.1", "foo2.com", "test.org", -1, "tempfail", 444, "4.4.4", 0); $self->sender_test('tempfail@foo.com', "10.10.10.10", "foo2.com", "default.org", -1, "tempfail", 451, "4.3.0", 9); } sub continue : Test(4) { my ($self) = @_; $self->sender_test('continue@foo.com', "192.168.1.1", "foo2.com", "test.org", 1, "continue", 222, "2.2.2", 0); $self->sender_test('continue@foo.com', "10.10.10.10", "foo2.com", "default.org", 1, "continue", 250, "2.1.0", 9); } sub discard : Test(4) { my ($self) = @_; $self->sender_test('discard@foo.com', "192.168.1.1", "foo2.com", "test.org", 3, "discard", 299, "2.99.99", 0); $self->sender_test('discard@foo.com', "10.10.10.10", "foo2.com", "default.org", 3, "discard", 250, "2.1.0", 9); } sub accept_and_no_more_filtering : Test(4) { my ($self) = @_; $self->sender_test('accept@foo.com', "192.168.1.1", "foo2.com", "test.org", 2, "accept", 288, "2.8.8", 0); $self->sender_test('accept@foo.com', "10.10.10.10", "foo2.com", "default.org", 2, "accept", 250, "2.1.0", 9); } sub sender_test { my ($self, $sender, $ip, $host, $helo, $action, $msg, $code, $dsn, $delay) = @_; my @answer; no warnings qw(redefine once); local *::md_syslog = sub { note $_[1] }; local *::main::print_and_flush = sub { @answer = split(/\s+/,$_[0]); }; use warnings qw(redefine once); lives_ok { ::main::handle_senderok( $sender, $ip, $host, $helo, Cwd::cwd(), '242', qw( esmtp1 esmtp2 ) ) } 'handle_senderok lives'; cmp_deeply( \@answer, [ 'ok', $action, $msg, $code, $dsn, $delay ], 'handle_senderok called send_filter_answer with expected arguments') or diag(explain(\@answer)); } __PACKAGE__->runtests(); mimedefang-3.6/t/smtp.t000066400000000000000000000023551475763067200151170ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::Smtp; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; sub t0_smtp_sa : Test(1) { SKIP: { if ( (not defined $ENV{'SMTP_TEST'}) or ($ENV{'SMTP_TEST'} ne 'yes' )) { skip "Smtp test disabled", 1 } my $from = 'defang'; my $to = 'defang'; my $filemail = "t/data/gtube.eml"; my $ret = Mail::MIMEDefang::Unit::smtp_mail($from, $to, $filemail); like($ret, qr/5\.7\.1 /); }; } sub t1_smtp : Test(2) { SKIP: { if ( (not defined $ENV{'SMTP_TEST'}) or ($ENV{'SMTP_TEST'} ne 'yes' )) { skip "Smtp test disabled", 1 } my $from = 'defang'; my $to = 'defang'; my $filemail = "t/data/multipart.eml"; my $ret = Mail::MIMEDefang::Unit::smtp_mail($from, $to, $filemail); sleep 5; like($ret, qr/2\.0\.0 /); $filemail = "t/data/exe.eml"; $ret = Mail::MIMEDefang::Unit::smtp_mail($from, $to, $filemail); sleep 5; my $warning = 0; if(open(my $fh, '<', '/var/spool/mail/defang')) { while(my $line = <$fh>) { if($line =~ /An attachment named test.exe was removed/) { $warning = 1; } } close $fh; } is($warning, 1); }; } __PACKAGE__->runtests(); mimedefang-3.6/t/spf.t000066400000000000000000000021551475763067200147220ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::SPF; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use Mail::MIMEDefang::SPF; sub t_md_spf_verify : Test(5) { SKIP: { if ( (not defined $ENV{'NET_TEST'}) or ($ENV{'NET_TEST'} ne 'yes' )) { skip "Net test disabled", 1 } my ($spf_code, $spf_expl, $helo_spf_code, $helo_spf_expl) = md_spf_verify('newsalerts-noreply@dnsbltest.spamassassin.org', '1.2.3.4', 'dnsbltest.spamassassin.org'); is($spf_code, 'fail'); ($spf_code, $spf_expl, $helo_spf_code, $helo_spf_expl) = md_spf_verify('newsalerts-noreply@dnsbltest.spamassassin.org', '65.214.43.157', 'dnsbltest.spamassassin.org'); is($spf_code, 'neutral'); is($helo_spf_code, 'neutral'); ($spf_code, $spf_expl, $helo_spf_code, $helo_spf_expl) = md_spf_verify('dnsbltest.spamassassin.org', '65.214.43.157', 'dnsbltest.spamassassin.org'); is($spf_code, 'neutral'); ($spf_code, $spf_expl, $helo_spf_code, $helo_spf_expl) = md_spf_verify('', '65.214.43.157', 'dnsbltest.spamassassin.org'); is($spf_code, 'invalid'); }; } __PACKAGE__->runtests(); mimedefang-3.6/t/utils.t000066400000000000000000000114321475763067200152700ustar00rootroot00000000000000package Mail::MIMEDefang::Unit::Utils; use strict; use warnings; use lib qw(modules/lib); use base qw(Mail::MIMEDefang::Unit); use Test::Most; use MIME::Parser; use Mail::MIMEDefang; use Mail::MIMEDefang::Utils; use constant HAS_ARCHIVEZIP => eval { require Archive::Zip; }; sub t_percent_encode : Test(1) { my $pe = percent_encode("foo\r\nbar\tbl%t"); is($pe, "foo%0D%0Abar%09bl%25t"); } sub t_percent_decode : Test(1) { my $pd = percent_decode("foo%0D%0Abar%09bl%25t"); is($pd, "foo\r\nbar\tbl%t"); } sub t_synthetize : Test(4) { init_globals(); $Helo = "test.example.com"; my $hn = $Helo; $SendmailMacros{"if_name"} = $Helo; $RealRelayAddr = "1.2.3.4"; $Sender = 'me@example.com'; my $header = synthesize_received_header(); like($header, qr/Received\: from $Helo \(\[$RealRelayAddr\]\)\n\tby $hn \(envelope-sender $Sender\) \(MIMEDefang\) with ESMTP id NOQUEUE/); $SendmailMacros{"tls_version"} = "1.2"; $header = synthesize_received_header(); like($header, qr/Received\: from $Helo \(\[$RealRelayAddr\]\)\n\tby $hn \(envelope-sender $Sender\) \(MIMEDefang\) with ESMTPS id NOQUEUE/); undef $SendmailMacros{"tls_version"}; $SendmailMacros{"auth_authen"} = "user"; $header = synthesize_received_header(); like($header, qr/Received\: from $Helo \(\[$RealRelayAddr\]\)\n\tby $hn \(envelope-sender $Sender\) \(MIMEDefang\) with ESMTPA id NOQUEUE/); $SendmailMacros{"auth_authen"} = "user"; $SendmailMacros{"tls_version"} = "1.2"; $header = synthesize_received_header(); like($header, qr/Received\: from $Helo \(\[$RealRelayAddr\]\)\n\tby $hn \(envelope-sender $Sender\) \(MIMEDefang\) with ESMTPSA id NOQUEUE/); } sub t_re_match : Test(2) { my ($done, @parts); my $parser = new MIME::Parser; $parser->output_to_core(1); my $entity = $parser->parse_open("t/data/multipart.eml"); my $bad_exts = '(bin|exe|\{[^\}]+\})'; my $re = '\.' . $bad_exts . '\.*$'; @parts = $entity->parts(); foreach my $part (@parts) { if($part->head->mime_encoding eq "base64") { $done = re_match($part, $re); is($done, 1); } else { $done = re_match($part, $re); is($done, 0); } } } sub t_re_match_ext : Test(2) { my ($done, @parts); my $parser = new MIME::Parser; $parser->output_to_core(1); my $entity = $parser->parse_open("t/data/multipart.eml"); @parts = $entity->parts(); foreach my $part (@parts) { if($part->head->mime_encoding eq "base64") { $done = re_match_ext($part, "\.bin"); is($done, 1); $done = re_match_ext($part, "\.txt"); is($done, 0); } } } sub t_re_match_zip : Test(2) { my ($done, @parts); SKIP: { skip "Archive::Zip is needed for this test to work", 1 unless (HAS_ARCHIVEZIP); # Set up temporary dir system('rm', '-rf', 't/tmp'); mkdir('t/tmp', 0755); # Make a parser my $parser = MIME::Parser->new(); $parser->extract_nested_messages(1); $parser->extract_uuencode(1); $parser->output_to_core(0); $parser->tmp_to_core(0); my $filer = MIME::Parser::FileInto->new('t/tmp'); $filer->ignore_filename(1); $parser->filer($filer); detect_and_load_perl_modules(); my $entity = $parser->parse_open("t/data/zip.eml"); @parts = $entity->parts(); foreach my $part (@parts) { my $bh = $part->bodyhandle(); if (defined($bh)) { my $path = $bh->path(); if (defined($path)) { $done = re_match_in_zip_directory($path, "\.bin"); is($done, 0); $done = re_match_in_zip_directory($path, "\.txt"); is($done, 1); } } } system('rm', '-rf', 't/tmp'); }; } sub t_re_match_tgz_directory : Test(2) { my ($done, @parts); $Features{'tar'} = '/usr/bin/tar'; SKIP: { skip "tar(1) is needed for this test to work", 1 unless -f $Features{'tar'}; # Set up temporary dir system('rm', '-rf', 't/tmp'); mkdir('t/tmp', 0755); # Make a parser my $parser = MIME::Parser->new(); $parser->extract_nested_messages(1); $parser->extract_uuencode(1); $parser->output_to_core(0); $parser->tmp_to_core(0); my $filer = MIME::Parser::FileInto->new('t/tmp'); $filer->ignore_filename(1); $parser->filer($filer); detect_and_load_perl_modules(); my $entity = $parser->parse_open("t/data/tgz.eml"); @parts = $entity->parts(); foreach my $part (@parts) { my $bh = $part->bodyhandle(); if (defined($bh)) { my $path = $bh->path(); if (defined($path)) { $done = re_match_in_tgz_directory($path, "\.exe"); is($done, 1); $done = re_match_in_tgz_directory($path, "\.txt"); is($done, 0); } } } system('rm', '-rf', 't/tmp'); }; } sub t_gen_mx_id : Test(1) { my $str = Mail::MIMEDefang::Utils::gen_mx_id(); is(length($str), 7); } __PACKAGE__->runtests(); mimedefang-3.6/utils.c000066400000000000000000001033131475763067200150040ustar00rootroot00000000000000/*********************************************************************** * * utils.c * * Utility functions for MIMEDefang * * Copyright (C) 2002-2005 Roaring Penguin Software Inc. * http://www.roaringpenguin.com * * This program may be distributed under the terms of the GNU General * Public License, Version 2. * ***********************************************************************/ #define _DEFAULT_SOURCE 1 #include "config.h" #include "mimedefang.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_STDINT_H #include #endif #ifdef ENABLE_DEBUGGING extern void *malloc_debug(void *, size_t, char const *fname, int); extern char *strdup_debug(void *, char const *, char const *, int); extern void free_debug(void *, void *, char const *, int); #undef malloc #undef strdup #undef free #define malloc(x) malloc_debug(ctx, x, __FILE__, __LINE__) #define strdup(x) strdup_debug(ctx, x, __FILE__, __LINE__) #define free(x) free_debug(ctx, x, __FILE__, __LINE__) #define malloc_with_log(x) malloc_debug(ctx, x, __FILE__, __LINE__) #define strdup_with_log(x) strdup_debug(x, __FILE__, __LINE__) #endif #ifndef HAVE_UINT32_T /* On these machines, punt to unsigned int */ typedef unsigned int uint32_t; #endif #ifndef AF_LOCAL #define AF_LOCAL AF_UNIX #endif #ifndef SUN_LEN #define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + strlen ((ptr)->sun_path)) #endif #ifndef INADDR_LOOPBACK #define INADDR_LOOPBACK 0x7f000001 #endif static int percent_encode_command(int term_with_newline, char *out, int outlen, ...); /********************************************************************** * %FUNCTION: split_on_space * %ARGUMENTS: * buf -- input buffer * first -- set to first word * rest -- set to everything following a space, or NULL if no space * %RETURNS: * Nothing * %DESCRIPTION: * Splits a line on whitespace. ***********************************************************************/ void split_on_space(char *buf, char **first, char **rest) { *first = buf; *rest = NULL; while(*buf && !isspace(*buf)) buf++; if (*buf && isspace(*buf)) { *buf = 0; *rest = buf+1; } } /********************************************************************** * %FUNCTION: split_on_space3 * %ARGUMENTS: * buf -- input buffer * first -- set to first word * second -- set to second word or NULL * rest -- set to everything following a space, or NULL if no space * %RETURNS: * Nothing * %DESCRIPTION: * Splits a line on whitespace. ***********************************************************************/ void split_on_space3(char *buf, char **first, char **second, char **rest) { *first = buf; *second = NULL; *rest = NULL; while(*buf && !isspace(*buf)) buf++; if (*buf && isspace(*buf)) { *buf = 0; *second = buf+1; buf++; while(*buf && !isspace(*buf)) buf++; if (*buf && isspace(*buf)) { *buf = 0; *rest = buf+1; } } } /********************************************************************** * %FUNCTION: split_on_space4 * %ARGUMENTS: * buf -- input buffer * first -- set to first word * second -- set to second word or NULL * third -- set to third word or NULL * rest -- set to everything following a space, or NULL if no space * %RETURNS: * Nothing * %DESCRIPTION: * Splits a line on whitespace. ***********************************************************************/ void split_on_space4(char *buf, char **first, char **second, char **third, char **rest) { *first = buf; *second = NULL; *third = NULL; *rest = NULL; while(*buf && !isspace(*buf)) buf++; if (*buf && isspace(*buf)) { *buf = 0; *second = buf+1; buf++; while(*buf && !isspace(*buf)) buf++; if (*buf && isspace(*buf)) { *buf = 0; *third = buf+1; buf++; while(*buf && !isspace(*buf)) buf++; if (*buf && isspace(*buf)) { *buf = 0; *rest = buf+1; } } } } #ifndef ENABLE_DEBUGGING /********************************************************************** * %FUNCTION: malloc_with_log * %ARGUMENTS: * size -- amount of memory to allocate * %RETURNS: * Allocated memory * %DESCRIPTION: * Calls malloc, but syslogs an error on failure to allocate memory. ***********************************************************************/ void * malloc_with_log(size_t s) { void *p = malloc(s); if (!p) { syslog(LOG_WARNING, "Failed to allocate %lu bytes of memory", (unsigned long) s); } return p; } /********************************************************************** * %FUNCTION: strdup_with_log * %ARGUMENTS: * s -- string to strdup * %RETURNS: * A copy of s in malloc'd memory. * %DESCRIPTION: * Calls strdup, but syslogs an error on failure to allocate memory. ***********************************************************************/ char * strdup_with_log(char const *s) { char *p = strdup(s); if (!p) { syslog(LOG_WARNING, "Failed to allocate %d bytes of memory in strdup", (int) strlen(s)+1); } return p; } #endif /********************************************************************** *%FUNCTION: chomp *%ARGUMENTS: * str -- a string *%RETURNS: * Nothing *%DESCRIPTION: * Removes newlines and carriage-returns (if any) from str ***********************************************************************/ void chomp(char *str) { char *s, *t; s = str; for (t=str; *t; t++) { if (*t == '\n' || *t == '\r') continue; *s++ = *t; } *s = 0; } /********************************************************************** * %FUNCTION: MXCommand * %ARGUMENTS: * sockname -- multiplexor socket name * cmd -- command to send * buf -- buffer for reply * len -- length of buffer * qid -- Sendmail queue identifier * %RETURNS: * 0 if all went well, -1 on error. * %DESCRIPTION: * Sends a command to the multiplexor and reads the answer back. ***********************************************************************/ int MXCommand(char const *sockname, char const *cmd, char *buf, int len, char const *qid) { int fd; struct sockaddr_un addr; int nread; int n; if (!qid || !*qid) { qid = "NOQUEUE"; } fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (fd < 0) { syslog(LOG_ERR, "%s: MXCommand: socket: %m", qid); return MD_TEMPFAIL; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; strncpy(addr.sun_path, sockname, sizeof(addr.sun_path) - 1); if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { syslog(LOG_ERR, "%s: MXCommand: connect: %m: Is multiplexor running?", qid); close(fd); return MD_TEMPFAIL; } n = writestr(fd, cmd); if (n < 0) { syslog(LOG_ERR, "%s: MXCommand: write: %m: Is multiplexor running?", qid); close(fd); return MD_TEMPFAIL; } /* Now read the answer */ nread = readn(fd, buf, len-1); if (nread < 0) { syslog(LOG_ERR, "%s: MXCommand: read: %m: Is multiplexor running?", qid); close(fd); return MD_TEMPFAIL; } buf[nread] = 0; /* If we read a full buffer, read to EOF to maintain synchronizaion */ if (nread == len-1) { char slop[SMALLBUF]; if (readn(fd, slop, SMALLBUF) > 0) { syslog(LOG_WARNING, "%s: MXCommand: Overlong reply from multiplexor was truncated!", qid); /* Read all the way to EOF */ while (readn(fd, slop, SMALLBUF) > 0); } } close(fd); return 0; } /********************************************************************** * %FUNCTION: MXCheckFreeWorkers * %ARGUMENTS: * sockname -- MX socket name * %RETURNS: * >0 if there are free workers, 0 if all workers are busy, -1 if there * was an error. * %DESCRIPTION: * Queries multiplexor for number of free workers. ***********************************************************************/ int MXCheckFreeWorkers(char const *sockname, char const *qid) { char ans[SMALLBUF]; int workers; if (MXCommand(sockname, "free\n", ans, SMALLBUF-1, qid) < 0) return MD_TEMPFAIL; if (sscanf(ans, "%d", &workers) != 1) return MD_TEMPFAIL; return workers; } /********************************************************************** * %FUNCTION: MXScanDir * %ARGUMENTS: * sockname -- MX socket name * qid -- Sendmail queue ID * dir -- directory to scan * %RETURNS: * 0 if scanning succeeded; -1 if there was an error. * %DESCRIPTION: * Asks multiplexor to initiate a scan. ***********************************************************************/ int MXScanDir(char const *sockname, char const *qid, char const *dir) { char cmd[SMALLBUF]; char ans[SMALLBUF]; int len; if (!qid || !*qid) { qid = "NOQUEUE"; } if (percent_encode_command(1, cmd, sizeof(cmd), "scan", qid, dir, NULL) < 0) { return MD_TEMPFAIL; } if (MXCommand(sockname, cmd, ans, SMALLBUF-1, qid) < 0) return MD_TEMPFAIL; if (!strcmp(ans, "ok\n")) return 0; len = strlen(ans); if (len > 0 && ans[len-1] == '\n') ans[len-1] = 0; syslog(LOG_ERR, "%s: Error from multiplexor: %s", qid, ans); return MD_TEMPFAIL; } /********************************************************************** * %FUNCTION: percent_decode * %ARGUMENTS: * buf -- a buffer with percent-encoded data * %RETURNS: * Nothing * %DESCRIPTION: * Decodes buf IN PLACE. ***********************************************************************/ void percent_decode(char *buf) { unsigned char *in = (unsigned char *) buf; unsigned char *out = (unsigned char *) buf; unsigned int val; if (!buf) { return; } while(*in) { if (*in == '%' && isxdigit(*(in+1)) && isxdigit(*(in+2))) { sscanf((char *) in+1, "%2x", &val); *out++ = (unsigned char) val; in += 3; continue; } *out++ = *in++; } /* Copy terminator */ *out = 0; } /********************************************************************** * %FUNCTION: percent_encode * %ARGUMENTS: * in -- input buffer to encode * out -- output buffer to place encoded data * outlen -- number of chars in output buffer. * %RETURNS: * Number of chars written, not including trailing NULL. Ranges from * 0 to outlen-1 * %DESCRIPTION: * Encodes "in" into "out", writing at most (outlen-1) chars. Then writes * trailing 0. ***********************************************************************/ int percent_encode(char const *in, char *out, int outlen) { unsigned char tmp[8]; int nwritten = 0; unsigned char c; unsigned char const *uin = (unsigned char const *) in; unsigned char *uout = (unsigned char *) out; if (outlen <= 0) { return 0; } if (outlen == 1) { *uout = 0; return 0; } /* Do real work */ while((c = *uin++) != 0) { if (c <= 32 || c > 126 || c == '%' || c == '\\' || c == '\'' || c == '"') { if (nwritten >= outlen-3) { break; } sprintf((char *) tmp, "%%%02X", (unsigned int) c); *uout++ = tmp[0]; *uout++ = tmp[1]; *uout++ = tmp[2]; nwritten += 3; } else { *uout++ = c; nwritten++; } if (nwritten >= outlen-1) { break; } } *uout = 0; return nwritten; } /********************************************************************** * %FUNCTION: percent_encode_command * %ARGUMENTS: * term_with_newline -- if true, terminate with "\n\0". Otherwise, just "\0" * out -- output buffer * outlen -- length of output buffer * args -- arguments. Each one is percent-encoded and space-separated from * previous. * %RETURNS: * 0 if everything fits; -1 otherwise. * %DESCRIPTION: * Writes a series of space-separated, percent-encoded words to a buffer. ***********************************************************************/ static int percent_encode_command(int term_with_newline, char *out, int outlen, ...) { va_list ap; int spaceleft = outlen-2; int first = 1; int len; char *arg; if (outlen < 2) return MD_TEMPFAIL; va_start(ap, outlen); while ((arg = va_arg(ap, char *)) != NULL) { if (first) { first = 0; } else { if (spaceleft <= 0) { va_end(ap); return MD_TEMPFAIL; } *out++ = ' '; spaceleft--; } len = percent_encode(arg, out, spaceleft); spaceleft -= len; out += len; } va_end(ap); if (term_with_newline) *out++ = '\n'; *out = 0; return 0; } /********************************************************************** * %FUNCTION: munch_mx_return * %ARGUMENTS: * ans -- answer from multiplexor * msg -- buffer for holding error message, at least SMALLBUF chars * qid -- Sendmail queue ID * %RETURNS: * 1 if it's OK to accept connections from this host; 0 if not, -1 if error. * If connection is rejected, error message *may* be set. ***********************************************************************/ static int munch_mx_return(char *ans, char *msg, char const *qid) { size_t len; if (!qid || !*qid) { qid = "NOQUEUE"; } if (!strcmp(ans, "ok -1\n")) return MD_TEMPFAIL; if (!strcmp(ans, "ok 1\n")) return MD_CONTINUE; if (!strcmp(ans, "ok 2\n")) return MD_ACCEPT_AND_NO_MORE_FILTERING; if (!strcmp(ans, "ok 3\n")) return MD_DISCARD; if (!strcmp(ans, "ok 0\n")) return MD_REJECT; chomp(ans); /* If rejection message is supplied, set failure code and return 0 */ len = strlen(ans); if (len >= 6 && !strncmp(ans, "ok 0 ", 5)) { strcpy(msg, ans+5); return MD_REJECT; } if (len >= 7 && !strncmp(ans, "ok -1 ", 6)) { strcpy(msg, ans+6); return MD_TEMPFAIL; } if (len >= 6 && !strncmp(ans, "ok 1 ", 5)) { strcpy(msg, ans+5); return MD_CONTINUE; } if (len >= 6 && !strncmp(ans, "ok 2 ", 5)) { strcpy(msg, ans+5); return MD_ACCEPT_AND_NO_MORE_FILTERING; } if (len >= 6 && !strncmp(ans, "ok 3 ", 5)) { strcpy(msg, ans+5); return MD_DISCARD; } if (len > 0 && ans[len-1] == '\n') ans[len-1] = 0; syslog(LOG_ERR, "%s: Error from multiplexor: %s", qid, ans); return MD_TEMPFAIL; } /********************************************************************** * %FUNCTION: MXRelayOK * %ARGUMENTS: * sockname -- multiplexor socket name * msg -- buffer for holding error message, at least SMALLBUF chars * ip -- relay IP address * name -- relay name * port -- client port * myip -- My IP address, if known. * daemon_port -- Listening port * qid -- Queue ID * %RETURNS: * 1 if it's OK to accept connections from this host; 0 if not, -1 if error. * If connection is rejected, error message *may* be set. ***********************************************************************/ int MXRelayOK(char const *sockname, char *msg, char const *ip, char const *name, unsigned int port, char const *myip, unsigned int daemon_port, char const *qid) { char cmd[SMALLBUF]; char ans[SMALLBUF]; char port_string[65]; char daemon_port_string[65]; snprintf(port_string, sizeof(port_string), "%u", port); snprintf(daemon_port_string, sizeof(daemon_port_string), "%u", daemon_port); *msg = 0; if (!ip || !*ip) { ip = "UNKNOWN"; } if (!name || !*name) { name = ip; } if (!myip || !*myip) { myip = "UNKNOWN"; } if (!qid || !*qid) { qid = "NOQUEUE"; } if (percent_encode_command(1, cmd, sizeof(cmd), "relayok", ip, name, port_string, myip, daemon_port_string, qid, NULL) < 0) { return MD_TEMPFAIL; } if (MXCommand(sockname, cmd, ans, SMALLBUF-1, NULL) < 0) return MD_TEMPFAIL; return munch_mx_return(ans, msg, NULL); } /********************************************************************** * %FUNCTION: MXHeloOK * %ARGUMENTS: * sockname -- multiplexor socket name * msg -- buffer for holding error message, at least SMALLBUF chars * ip -- IP address of client * name -- resolved name of client * helo -- the helo string * port -- client port * myip -- My IP address, if known. * daemon_port -- Listening port * qid -- Queue ID * %RETURNS: * 1 if it's OK to accept messages from this sender; 0 if not, -1 if error or * we should tempfail. ***********************************************************************/ int MXHeloOK(char const *sockname, char *msg, char const *ip, char const *name, char const *helo, unsigned int port, char const *myip, unsigned int daemon_port, char const *qid) { char cmd[SMALLBUF]; char ans[SMALLBUF]; char port_string[65]; char daemon_port_string[65]; snprintf(port_string, sizeof(port_string), "%u", port); snprintf(daemon_port_string, sizeof(daemon_port_string), "%u", daemon_port); *msg = 0; if (!ip || !*ip) { ip = "UNKNOWN"; } if (!name || !*name) { name = ip; } if (!helo) { helo = "UNKNOWN"; } if (!myip || !*myip) { myip = "UNKNOWN"; } if (!qid || !*qid) { qid = "NOQUEUE"; } if (percent_encode_command(1, cmd, sizeof(cmd), "helook", ip, name, helo, port_string, myip, daemon_port_string, qid, NULL) < 0) { return MD_TEMPFAIL; } if (MXCommand(sockname, cmd, ans, SMALLBUF-1, NULL) < 0) return MD_TEMPFAIL; return munch_mx_return(ans, msg, NULL); } /********************************************************************** * %FUNCTION: MXSenderOK * %ARGUMENTS: * sockname -- socket name * msg -- buffer of at least SMALLBUF size for error message * sender_argv -- args from sendmail. sender_argv[0] is sender; rest are * ESMTP args. * ip -- sending relay's IP address * name -- sending relay's host name * helo -- argument to "HELO/EHLO" (may be NULL) * dir -- MIMEDefang working directory * qid -- Sendmail queue identifier * %RETURNS: * 1 if it's OK to accept messages from this sender; 0 if not, -1 if error or * we should tempfail. * If message is rejected, error message *may* be set. ***********************************************************************/ int MXSenderOK(char const *sockname, char *msg, char const **sender_argv, char const *ip, char const *name, char const *helo, char const *dir, char const *qid) { char cmd[SMALLBUF]; char ans[SMALLBUF]; int l, l2, i; char const *sender = sender_argv[0]; *msg = 0; if (!sender || !*sender) { sender = "UNKNOWN"; } if (!ip || !*ip) { ip = "UNKNOWN"; } if (!name || !*name) { name = ip; } if (!helo) { helo = "UNKNOWN"; } if (percent_encode_command(0, cmd, sizeof(cmd)-1, "senderok", sender, ip, name, helo, dir, qid, NULL) < 0) { return MD_TEMPFAIL; } /* Append ESMTP args */ l = strlen(cmd); for (i=1; sender_argv[i]; i++) { percent_encode(sender_argv[i], ans, sizeof(ans)); l2 = strlen(ans) + 1; if (l + l2 < sizeof(cmd)-1) { strcat(cmd, " "); strcat(cmd, ans); l += l2; } else { break; } } /* Add newline */ strcat(cmd, "\n"); if (MXCommand(sockname, cmd, ans, SMALLBUF-1, qid) < 0) return MD_TEMPFAIL; return munch_mx_return(ans, msg, qid); } /********************************************************************** * %FUNCTION: MXRecipientOK * %ARGUMENTS: * sockname -- multiplexor socket name * msg -- buffer of at least SMALLBUF size for error messages * recip_argv -- recipient e-mail address and ESMTP args * sender -- sender's e-mail address * ip -- sending relay's IP address * name -- sending relay's host name * firstRecip -- first recipient of the message * helo -- argument to "HELO/EHLO" (may be NULL) * dir -- MIMEDefang working directory * qid -- Sendmail queue identifier * rcpt_mailer -- the "mailer" part of the triple for RCPT TO address * rcpt_host -- the "host" part of the triple for RCPT TO address * rcpt_addr -- the "addr" part of the triple for RCPT TO address * %RETURNS: * 1 if it's OK to accept messages to this recipient; 0 if not, -1 if error. * If recipient is rejected, error message *may* be set. ***********************************************************************/ int MXRecipientOK(char const *sockname, char *msg, char const **recip_argv, char const *sender, char const *ip, char const *name, char const *firstRecip, char const *helo, char const *dir, char const *qid, char const *rcpt_mailer, char const *rcpt_host, char const *rcpt_addr) { char cmd[SMALLBUF]; char ans[SMALLBUF]; int i, l, l2; char const *recipient = recip_argv[0]; *msg = 0; if (!recipient || !*recipient) { recipient = "UNKNOWN"; } if (!sender || !*sender) { sender = "UNKNOWN"; } if (!ip || !*ip) { ip = "UNKNOWN"; } if (!name || !*name) { name = ip; } if (!firstRecip || !*firstRecip) { firstRecip = "UNKNOWN"; } if (!helo) { helo = "UNKNOWN"; } if (percent_encode_command(0, cmd, sizeof(cmd), "recipok", recipient, sender, ip, name, firstRecip, helo, dir, qid, rcpt_mailer, rcpt_host, rcpt_addr, NULL) < 0) { return MD_TEMPFAIL; } /* Append ESMTP args */ l = strlen(cmd); for (i=1; recip_argv[i]; i++) { percent_encode(recip_argv[i], ans, sizeof(ans)); l2 = strlen(ans) + 1; if (l + l2 < sizeof(cmd)-1) { strcat(cmd, " "); strcat(cmd, ans); l += l2; } else { break; } } /* Add newline */ strcat(cmd, "\n"); if (MXCommand(sockname, cmd, ans, SMALLBUF-1, qid) < 0) return MD_TEMPFAIL; return munch_mx_return(ans, msg, qid); } /********************************************************************** * %FUNCTION: writen * %ARGUMENTS: * fd -- file to write to * buf -- buffer to write * len -- length to write * %RETURNS: * Number of bytes written, or -1 on error * %DESCRIPTION: * Writes exactly "len" bytes from "buf" to file descriptor fd ***********************************************************************/ int writen(int fd, char const *buf, size_t len) { int r; int nleft = len; while(nleft) { r = write(fd, buf, nleft); if (r > 0) { nleft -= r; buf += r; continue; } if (r == 0) { /* Shouldn't happen! */ errno = EIO; return MD_TEMPFAIL; } if (r < 0) { if (errno == EINTR || errno == EAGAIN) { continue; } } return r; } return len; } /********************************************************************** * %FUNCTION: writestr * %ARGUMENTS: * fd -- file to write to * buf -- null-terminated string to write * %RETURNS: * Number of bytes written, or -1 on error * %DESCRIPTION: * Writes the string in "buf" to fd. ***********************************************************************/ int writestr(int fd, char const *buf) { return writen(fd, buf, strlen(buf)); } /********************************************************************** * %FUNCTION: readn * %ARGUMENTS: * fd -- file descriptor to read from * buf -- buffer to read into * count -- number of bytes to read * %RETURNS: * The number of bytes actually read, or -1 on error * %DESCRIPTION: * Attempts to read exactly "count" bytes from a descriptor. ***********************************************************************/ int readn(int fd, void *buf, size_t count) { size_t num_read = 0; char *c = (char *) buf; while (count) { int n = read(fd, c, count); if (n == 0) { /* EOF */ return num_read; } if (n < 0) { /* Error */ if (errno == EINTR || errno == EAGAIN) { continue; } return n; } num_read += n; count -= n; c += n; } return num_read; } /********************************************************************** * %FUNCTION: closefd * %ARGUMENTS: * fd -- file to close * %RETURNS: * Whatever close(2) returns * %DESCRIPTION: * Closes fd, handling EINTR ***********************************************************************/ int closefd(int fd) { int r; while(1) { r = close(fd); if (r >= 0) return r; if (errno != EINTR) return r; } } /********************************************************************** * %FUNCTION: validate_smtp_code * %ARGUMENTS: * code -- an SMTP code (eg 451) * first -- what the first char must be * %RETURNS: * 1 if it's a valid code; 0 otherwise. A valid code consists * of three decimal digits only. The first digit must match "first". ***********************************************************************/ int validate_smtp_code(char const *code, char const *first) { if (!code) return 0; if (*code != *first) return 0; if (!isdigit(*(code+1))) return 0; if (!isdigit(*(code+2))) return 0; if (*(code+3)) return 0; return 1; } /********************************************************************** * %FUNCTION: validate_smtp_dsn * %ARGUMENTS: * dsn -- an SMTP dsn reply (eg 4.7.1) * first -- what the first char must be * %RETURNS: * 1 if it's a valid dsn; 0 otherwise. A valid DSN consists of three * numerical fields separated by periods. The first field must be a * single digit that matches "first". The second and * third fields can be 1-3 digits long each. ***********************************************************************/ int validate_smtp_dsn(char const *dsn, char const *first) { char const *s; int count; if (!dsn) return 0; if (*dsn != *first) return 0; if (*(dsn+1) != '.') return 0; s = dsn+2; count = 0; while (isdigit(*s) && count < 4) { count++; s++; } if (count == 0 || count > 3) return 0; if (*s != '.') return 0; s++; count = 0; while (isdigit(*s) && count < 4) { count++; s++; } if (count == 0 || count > 3) return 0; if (*s) return 0; return 1; } /********************************************************************** * %FUNCTION: remove_local_socket * %ARGUMENTS: * str -- a string of the form: * /path/to/sock (assumed to be unix:/path/to/sock) * unix:/path/to/sock * local:/path/to/sock * inet:port (host defaults to LOOPBACK) * inet_any:port (host defaults to INADDR_ANY) * %RETURNS: * Whatever remove() returns if it's a local socket; otherwise zero * %DESCRIPTION: * If the socket is local, then it's removed from the file system ***********************************************************************/ int remove_local_socket(char const *str) { char const *path; if (*str == '/') { path = str; } else if (!strncmp(str, "unix:", 5)) { path = str+5; } else if (!strncmp(str, "local:", 6)) { path = str+6; } else { /* Not unix-domain socket */ return 0; } return remove(path); } /********************************************************************** * %FUNCTION: make_listening_socket * %ARGUMENTS: * str -- a string of the form: * /path/to/sock (assumed to be unix:/path/to/sock) * unix:/path/to/sock * local:/path/to/sock * inet:port (host defaults to LOOPBACK) * inet_any:port (host defaults to INADDR_ANY) * inet6:port (host defaults to in6addr_loopback) * inet6_any:port (host defaults to in6addr_any) * backlog -- listen backlog. If -1, we use default of 5 * must_be_unix -- If true, do not accept the inet: or inet_any: sockets. * %RETURNS: * A listening UNIX-domain or TCP socket. If must_be_unix is true * and we asked for a TCP socket, return -2. Other failures return -1. * %DESCRIPTION: * Utility function for opening a listening socket. ***********************************************************************/ int make_listening_socket(char const *str, int backlog, int must_be_unix) { char const *path; int sock; struct sockaddr_in addr_in; struct sockaddr_un addr_un; int opt; int port; uint32_t bind_addr; if (backlog <= 0) backlog = 5; if (!strncmp(str, "inet:", 5) || !strncmp(str, "inet_any:", 9)) { if (must_be_unix) { return -2; } if (!strncmp(str, "inet:", 5)) { path = str+5; bind_addr = htonl(INADDR_LOOPBACK); } else { path = str+9; bind_addr = htonl(INADDR_ANY); } if (sscanf(path, "%d", &port) != 1 || port < 1 || port > 65535) { syslog(LOG_ERR, "make_listening_socket: Invalid port %s", path); errno = EINVAL; return -1; } sock = socket(PF_INET, SOCK_STREAM, 0); if (sock < 0) { syslog(LOG_ERR, "make_listening_socket: socket: %m"); return -1; } opt = 1; /* Reuse port */ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { syslog(LOG_ERR, "make_listening_socket: setsockopt: %m"); close(sock); return -1; } addr_in.sin_family = AF_INET; addr_in.sin_port = htons(port); addr_in.sin_addr.s_addr = bind_addr; if (bind(sock, (struct sockaddr *) &addr_in, sizeof(addr_in)) < 0) { syslog(LOG_ERR, "make_listening_socket: bind: %m"); close(sock); return -1; } } else if (!strncmp(str, "inet6:", 6) || !strncmp(str, "inet6_any:", 10)) { #if defined(AF_INET6) struct in6_addr const *bind6_addr; struct sockaddr_in6 addr6; if (must_be_unix) { return -2; } if (!strncmp(str, "inet6:", 6)) { path = str+6; bind6_addr = &in6addr_loopback; } else { path = str+10; bind6_addr = &in6addr_any; } if (sscanf(path, "%d", &port) != 1 || port < 1 || port > 65535) { syslog(LOG_ERR, "make_listening_socket: Invalid port %s", path); errno = EINVAL; return -1; } sock = socket(PF_INET6, SOCK_STREAM, 0); if (sock < 0) { syslog(LOG_ERR, "make_listening_socket: socket: %m"); return -1; } opt = 1; /* Reuse port */ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { syslog(LOG_ERR, "make_listening_socket: setsockopt: %m"); close(sock); return -1; } addr6.sin6_family = AF_INET6; addr6.sin6_port = htons(port); addr6.sin6_addr = *bind6_addr; if (bind(sock, (struct sockaddr *) &addr6, sizeof(addr6)) < 0) { syslog(LOG_ERR, "make_listening_socket: bind: %m"); close(sock); return -1; } #else syslog(LOG_ERR, "Cannot specify inet6 socket: No IPv6 support"); fprintf(stderr, "Cannot specify inet6 socket: No IPv6 support\n"); return -1; #endif } else { /* Assume unix-domain socket */ path = str; if (!strncmp(str, "unix:", 5)) path = str+5; else if (!strncmp(str, "local:", 6)) path = str+6; (void) remove(path); sock = socket(AF_LOCAL, SOCK_STREAM, 0); if (sock < 0) { syslog(LOG_ERR, "make_listening_socket: socket: %m"); return -1; } memset(&addr_un, 0, sizeof(addr_un)); addr_un.sun_family = AF_LOCAL; strncpy(addr_un.sun_path, path, sizeof(addr_un.sun_path)-1); if (bind(sock, (struct sockaddr *) &addr_un, SUN_LEN(&addr_un)) < 0) { syslog(LOG_ERR, "make_listening_socket: bind: %m"); close(sock); return -1; } } if (listen(sock, backlog) < 0) { /* Maybe backlog is too high... try again */ if (backlog > 5) { if (listen(sock, 5) < 0) { syslog(LOG_ERR, "make_listening_socket: listen: %m"); close(sock); return -1; } } syslog(LOG_ERR, "make_listening_socket: listen: %m"); close(sock); return -1; } return sock; } /********************************************************************** * %FUNCTION: do_delay * %ARGUMENTS: * sleepstr -- Number of seconds to delay as an ASCII string. * %RETURNS: * Nothing * %DESCRIPTION: * Sleeps for specified number of seconds ***********************************************************************/ void do_delay(char const *sleepstr) { int snooze; if (!sleepstr || !*sleepstr) { return; } if (sscanf(sleepstr, "%d", &snooze) != 1 || snooze <= 0) { return; } while(snooze) { snooze = sleep(snooze); } } /********************************************************************** * %FUNCTION: is_localhost * %ARGUMENTS: * sa -- a socket address * %RETURNS: * True if sa is the loopback address; false otherwise. ***********************************************************************/ int is_localhost(struct sockaddr *sa) { if (sa->sa_family == AF_INET) { struct sockaddr_in *sa_in = (struct sockaddr_in *) sa; return (sa_in->sin_addr.s_addr == htonl(INADDR_LOOPBACK)); } #ifdef AF_INET6 if (sa->sa_family == AF_INET6) { struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *) sa; return IN6_IS_ADDR_LOOPBACK(&sa_in6->sin6_addr); } #endif return 0; } #ifdef ENABLE_DEBUGGING void * malloc_debug(void *ctx, size_t x, char const *fname, int line) { void *ptr = (malloc) (x); syslog(LOG_DEBUG, "%p: %s(%d): malloc(%lu) = %p\n", ctx, fname, line, (unsigned long) x, ptr); return ptr; } char * strdup_debug(void *ctx, char const *s, char const *fname, int line) { char *dup = (strdup) (s); syslog(LOG_DEBUG, "%p: %s(%d): strdup(\"%.25s\") = %p\n", ctx, fname, line, s, dup); return dup; } void free_debug(void *ctx, void *x, char const *fname, int line) { syslog(LOG_DEBUG, "%p: %s(%d): free(%p)\n", ctx, fname, line, x); (free)(x); } #endif int write_and_lock_pidfile(char const *pidfile, char **lockfile, int pidfile_fd) { struct flock fl; char buf[64]; int lockfile_fd; size_t len; if (!*lockfile) { if (!pidfile) { return -1; } len = strlen(pidfile) + 6; /* If no lockfile was supplied, construct one based on pidfile */ *lockfile = malloc(len); if (!*lockfile) { return -1; } snprintf(*lockfile, len, "%s.lock", pidfile); } lockfile_fd = open(*lockfile, O_RDWR|O_CREAT, 0666); if (lockfile_fd < 0) { free(*lockfile); *lockfile = NULL; return -1; } fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; if (fcntl(lockfile_fd, F_SETLK, &fl) < 0) { syslog(LOG_ERR, "Could not lock lockfile file %s: %m. Is another copy running?", *lockfile); return -1; } if (pidfile_fd >= 0) { ftruncate(pidfile_fd, 0); snprintf(buf, sizeof(buf), "%lu\n", (unsigned long) getpid()); write(pidfile_fd, buf, strlen(buf)); /* Close the pidfile fd; no longer needed */ if (close(pidfile_fd) < 0) { return -1; } } /* Do NOT close lockfile_fd... it will close and lock will be released when we exit */ return lockfile_fd; } mimedefang-3.6/watch-mimedefang.8000066400000000000000000000124361475763067200167760ustar00rootroot00000000000000.\" $Id$ .\"" .TH WATCH-MIMEDEFANG 8 "24 October 2002" .UC 4 .SH NAME watch-mimedefang \- Keep an eye on mimedefang-multiplexor .SH SYNOPSIS .B watch-mimedefang \fR[\fIoptions\fR] .SH DESCRIPTION \fBwatch-mimedefang\fR is a Tk script which graphically displays the status of \fBmimedefang-multiplexor\fR(8). Note that Tcl/Tk 8.0 or higher is required to run \fBwatch-mimedefang\fR. .SH DISPLAY \fBwatch-mimedefang\fR's display is divided into three main areas, from top to bottom: .TP .B o The graph display shows six graphs representing various statistics about the multiplexor. .TP .B o The control area shows the maximum number of workers, number of busy workers, and number of idle workers. The "Control Command" text entry lets you monitor remote MIMEDefang servers; see "REMOTE MONITORING" for details. The "10s", "1m", "5m" and "10m" checkboxes allow you to enable or disable the graphing of statistics averaged over the last 10 seconds, 1 minute, 5 minutes and 10 minutes. The "Reread Filters" button forces the multiplexor to reread filter rules. The "Quit" button terminates \fBwatch-mimedefang\fR. .TP .B o The scale at the bottom lets you change the update interval (default 500ms). Be aware that frequent updating may make \fBwatch-mimedefang\fR consume a significant fraction of CPU time. .SH THE GRAPH AREA The graph area contains six graphs: .TP .B o Busy Workers This graph shows the number of busy workers each time the statistics are sampled. .TP .B o Workers/scan This graph shows the average number of busy workers each time a message was scanned. The red plot shows the average over the last ten seconds, the blue plot shows the average over the last minute, and the green and yellow plots show the average over the last five and ten minutes, respectively. .TP .B o Latency (ms) This graph shows the average time taken by each SCAN command over the last ten seconds, one minute, five minutes and ten minutes. .TP .B o Messages/s This graph shows the average number of messages per second that were scanned. .TP .B o Activations/s This graph shows the average number of new workers activated per second. .TP .B o Reaps/s This graph shows the average number of workers that have terminated per second. If the Activations and Reaps increase significantly, it could indicate heavy load, or an error causing filters to terminate abnormally. If only Activations increase, then it probably indicates a sudden increase in e-mail volume. If only Reaps increase, then it probably indicates a quiet time just after a period of heavy mail volume. .PP You can enable or disable the ten second, one minute, five minute or ten minute plots by clicking on the "10s", "1m", "5m" or "10m" checkbox. .SH REMOTE MONITORING Running \fBwatch-mimedefang\fR on your actual mail server poses two problems: You need Tcl/Tk and the X client libraries installed, and the CPU consumption of \fBwatch-mimedefang\fR can be considerable. A better method is to run \fBwatch-mimedefang\fR on a desktop machine, but have it monitor the mail server with a remote session. The best way to establish a remote session is via SSH. For example, if you are running MIMEDefang on "mail.mydomain.net", you can type the following into the \fBControl Command\fR entry box and press enter: .nf ssh root@mail.mydomain.net md-mx-ctrl .fi You \fImust\fR have a public/private key for the target machine and you must be running ssh-agent or equivalent; you cannot use an ssh command that requires entry of a password or passphrase. Assuming your SSH setup is correct, \fBwatch-mimedefang\fR will open an SSH connection to mail.mydomain.net and exchange low-bandwith information over that session for monitoring purposes. .SH COMMAND-LINE OPTIONS \fBwatch-mimedefang\fR accepts the following command-line options: .TP .B -command \fIcmd\fR Use \fIcmd\fR as the control command. You may need to quote \fIcmd\fR .TP .B -interval \fImsec\fR Set the update interval to \fImsec\fR milliseconds. .TP .B -10s \fIflag\fR Enable the "10s" graph plot if \fIflag\fR is 1, or disable it if \fIflag\fR is 0. .TP .B -1m \fIflag\fR Enable the "1m" graph plot if \fIflag\fR is 1, or disable it if \fIflag\fR is 0. .TP .B -5m \fIflag\fR Enable the "5m" graph plot if \fIflag\fR is 1, or disable it if \fIflag\fR is 0. .TP .B -10m \fIflag\fR Enable the "10m" graph plot if \fIflag\fR is 1, or disable it if \fIflag\fR is 0. .TP .B -title \fIstring\fR Add \fIstring\fR to the title of the main window. .TP .B -help Print usage information. .SH EXAMPLE As an example, to monitor MIMEDefang on the machine "mail.mydomain.net" updating once a second, with the 5m and 10m plots turned off, use: .nf watch-mimedefang -command 'ssh root@mail.mydomain.net md-mx-ctrl' \\ -interval 1000 -5m 0 -10m 0 -title "mail" .fi .SH AUTHOR \fBwatch-mimedefang\fR was written by Dianne Skoll . The \fBmimedefang\fR home page is \fIhttps://www.mimedefang.org/\fR. .SH PERMISSIONS \fBwatch-mimedefang\fR uses \fBmd-mx-ctrl\fR; therefore, it probably needs to be run as \fIroot\fR or the same user as \fBmimedefang-multiplexor\fR, or (if you are monitoring a remote machine), the SSH connection must be done as root or the MIMEDefang user. .SH SEE ALSO mimedefang.pl(8), mimedefang-filter(5), mimedefang(8), mimedefang-protocol(7), md-mx-ctrl(8), watch-multiple-mimedefangs(8) mimedefang-3.6/watch-mimedefang.in000077500000000000000000000564521475763067200172460ustar00rootroot00000000000000#!/bin/sh # -*-Mode: TCL;-*- # Next line restarts using wish \ exec wish "$0" -- "$@" ; clear; echo "*****"; echo "Cannot find 'wish' -- you need Tcl/Tk installed to run this program"; exit 1 # Update interval in milliseconds set UpdateInterval 500 # Message list set MsgList {} set Title "" # Graph margins set Margin(left) 60 set Margin(right) 15 set Margin(top) 15 set Margin(bottom) 15 # Which plots to show set Show(0) 1 set Show(1) 1 set Show(5) 1 set Show(10) 1 # Last time we did MSGS set MSGS -1 # File descriptor for md-mx-ctrl set CtrlFD 0 # Default md-mx-ctrl command set MD_MX_CTRL "md-mx-ctrl" set AfterResult 0 ### Built-in graphing library global GraphData set GraphData(_attributes) { width height xmin ymin xmax ymax origin_x origin_y xticks yticks } set GraphData(_data_attributes) { color width } ## translated from C-code in Blt, who got it from: ## Taken from Paul Heckbert's "Nice Numbers for Graph Labels" in ## Graphics Gems (pp 61-63). Finds a "nice" number approximately ## equal to x. proc nicenum {x floor} { if {$x == 0} { return 0 } set negative 0 if {$x < 0} { set x [expr -$x] set negative 1 } set exponX [expr floor(log10($x))] set fractX [expr $x/pow(10,$exponX)]; # between 1 and 10 if {$floor} { if {$fractX < 2.0} { set nf 1.0 } elseif {$fractX < 5.0} { set nf 2.0 } elseif {$fractX < 10.0} { set nf 5.0 } else { set nf 10.0 } } elseif {$fractX <= 1.0} { set nf 1.0 } elseif {$fractX <= 2.0} { set nf 2.0 } elseif {$fractX <= 5.0} { set nf 5.0 } else { set nf 10.0 } if { $negative } { return [expr -$nf * pow(10,$exponX)] } else { set value [expr $nf * pow(10,$exponX)] return $value } } proc graph_create { name } { graph_configure $name width 300 graph_configure $name height 120 graph_configure $name sxmin auto graph_configure $name symin auto graph_configure $name sxmax auto graph_configure $name symax auto graph_configure $name origin_x 0 graph_configure $name origin_y 0 graph_configure $name xticks 10 graph_configure $name yticks 10 graph_configure $name gridcolor "#C0C0C0" graph_configure $name gridwidth 1 } proc graph_configure { name attribute value } { global GraphData set GraphData(g$name,$attribute) $value } proc graph_cget { name {attribute ""} } { global GraphData if {"$attribute" == ""} { graph_cget_all $name } else { if { [info exists GraphData(g$name,$attribute)] } { return $GraphData(g$name,$attribute) } else { return "" } } } proc graph_cget_all { name } { global GraphData set keys [array names GraphData "g$name*"] set ans {} foreach thing $keys { set stuff [split $thing ,] if { [lindex $stuff 1] == "data" } { continue } lappend ans [lindex $stuff 1] lappend ans $GraphData($thing) } return $ans } proc graph_add_data { name tag points } { graph_configure_data $name $tag points $points } proc graph_get_points { name tag } { graph_cget_data $name $tag points } proc graph_next_auto_x { name tag } { set x [graph_cget_data $name $tag auto_x] if {"$x" == ""} { set x 0 } else { incr x } graph_configure_data $name $tag auto_x $x return $x } proc graph_add_point { name tag x y } { global GraphData if { "$x" == "auto" } { set x [graph_next_auto_x $name $tag] } lappend GraphData(g$name,data,$tag,points) $x $y if {[graph_cget $name sxmax] == "timeseries"} { graph_keep_lastn $name $tag [graph_cget $name width] } } proc graph_keep_lastn { name tag n } { global GraphData set l [llength $GraphData(g$name,data,$tag,points)] if {$l > $n * 2} { set toChop [expr $l - $n * 2] set GraphData(g$name,data,$tag,points) [lrange $GraphData(g$name,data,$tag,points) $toChop end] } } proc graph_configure_data { name tag attribute value } { graph_configure $name "data,$tag,$attribute" $value } proc graph_cget_data { name tag attribute } { graph_cget $name "data,$tag,$attribute" } proc graph_get_data_tags { name } { global GraphData set keys [array names GraphData "g$name,data,*,points"] set ans {} foreach thing $keys { set stuff [split $thing ,] set tag [lindex $stuff 2] if { ! [info exists done($tag)] } { set done($tag) 1 lappend ans $tag } } return $ans } proc _graph_set_scale { name } { set tags [graph_get_data_tags $name] set sxmin [graph_cget $name sxmin] set sxmax [graph_cget $name sxmax] set symin [graph_cget $name symin] set symax [graph_cget $name symax] set xmin 1e60 set ymin 1e60 set xmax -1e60 set ymax -1e60 foreach tag $tags { set points [graph_cget_data $name $tag points] foreach {x y} $points { if { $x < $xmin } { set xmin $x } if { $y < $ymin } { set ymin $y } if { $x > $xmax } { set xmax $x } if { $y > $ymax } { set ymax $y } } } set nxmin [nicenum $xmin 1] set nxmax [nicenum $xmax 0] set nymin [nicenum $ymin 1] set nymax [nicenum $ymax 0] set width [graph_cget $name width] if { $xmin == $xmax } { set nxmin [expr $xmin - 0.1] set nxmax [expr $xmin + 0.1] } if { $ymin == $ymax } { set nymin [expr $ymin - 0.1] set nymax [expr $ymin + 0.1] } if { "$sxmin" == "auto" } { graph_configure $name xmin $nxmin } elseif { "$sxmin" == "timeseries" } { graph_configure $name xmin $xmin } else { graph_configure $name xmin $sxmin } if { "$symin" == "auto" } { graph_configure $name ymin $nymin } else { graph_configure $name ymin $symin } if { "$sxmax" == "auto" } { graph_configure $name xmax $nxmax } elseif { "$sxmin" == "timeseries" } { graph_configure $name xmax [expr $xmin + $width] } else { graph_configure $name xmax $sxmax } if { "$symax" == "auto" } { graph_configure $name ymax $nymax } else { graph_configure $name ymax $symax } } proc graph_draw { name canvas } { _graph_set_scale $name set xmin [graph_cget $name xmin] set ymin [graph_cget $name ymin] set xmax [graph_cget $name xmax] set ymax [graph_cget $name ymax] set delta_x [expr 1.0 * ($xmax - $xmin)] set delta_y [expr 1.0 * ($ymax - $ymin)] if { $delta_x == 0 } { set delta_x 1} if { $delta_y == 0 } { set delta_y 1} set ox [graph_cget $name origin_x] set oy [graph_cget $name origin_y] set width [graph_cget $name width] set height [graph_cget $name height] set cheight [winfo height .c] set cwidth [winfo width .c] set tags [lsort [graph_get_data_tags $name]] $canvas delete withtag graph_$name _graph_draw_grids $name $canvas $xmin $ymin $xmax $ymax $delta_x $delta_y foreach tag $tags { set ans {} set offset [graph_cget_data $name $tag yoffset] if {"$offset" == ""} { set offset 0 } set points [graph_cget_data $name $tag points] set color [graph_cget_data $name $tag color] if { "$color" == "" } { set color "black" } set lwidth [graph_cget_data $name $tag width] if { "$lwidth" == "" } { set lwidth 1 } foreach {x y} $points { set dx [expr ($x - $xmin) / $delta_x] set dy [expr ($y - $ymin) / $delta_y] set x [expr $dx * $width + $ox] set y [expr $oy - ($dy * $height) - $offset] lappend ans $x $y } if {[llength $ans] >= 4} { $canvas create line $ans -fill $color -width $lwidth -tag graph_$name } } # Draw the title in the upper left-hand corner set title [graph_cget $name title] if {"$title" != ""} { set x [expr $ox + $width] set y [expr $oy - $height] set t [$canvas create text $x $y -anchor ne -text $title -tag graph_$name -font fixed] set bbox [$canvas bbox $t] $canvas create rectangle $bbox -fill white -outline white $canvas raise $t } } proc _graph_draw_grids { name canvas xmin ymin xmax ymax delta_x delta_y } { set ox [graph_cget $name origin_x] set oy [graph_cget $name origin_y] set width [graph_cget $name width] set height [graph_cget $name height] set cheight [winfo height .c] set cwidth [winfo width .c] set xticks [graph_cget $name xticks] set yticks [graph_cget $name yticks] set gridcolor [graph_cget $name gridcolor] set gridwidth [graph_cget $name gridwidth] if {$xticks > 0 && $xmax > $xmin} { set diff [expr ($xmax - $xmin) / $xticks] set diff [nicenum $diff 1] if { $diff > 0 } { set last_item 0 for {set x $xmin} {$x <= $xmax} {set x [expr $x + $diff]} { # Draw gridline set x1 [expr (($x - $xmin) / $delta_x) * $width + $ox] set y1 $oy set y2 [expr $oy - $height] $canvas create line $x1 $y1 $x1 $y2 -fill $gridcolor -width $gridwidth -tag graph_$name -stipple gray25 set this_item [$canvas create text $x1 [expr $y1 + 2] -text [format %.5g $x] -anchor n -tag graph_$name -font fixed] foreach {bx1 by1 bx2 by2} [$canvas bbox $this_item] { break } set overlaps [$canvas find overlapping $bx1 $by1 $bx2 $by2] if {[lsearch -exact $overlaps $last_item] >= 0} { $canvas delete $this_item } else { set last_item $this_item } } } } if {$yticks > 0 && $ymax > $ymin} { set diff [expr ($ymax - $ymin) / $yticks] set diff [nicenum $diff 1] if { $diff > 0 } { set last_item 0 for {set y $ymin} {$y <= $ymax} {set y [expr $y + $diff]} { # Draw gridline set x1 $ox set x2 [expr $ox + $width] set y1 [expr $oy - (($y - $ymin) / $delta_y * $height)] $canvas create line $x1 $y1 $x2 $y1 -fill $gridcolor -width $gridwidth -tag graph_$name -stipple gray25 set this_item [$canvas create text [expr $x1 - 2] $y1 -text [format %.5g $y] -anchor e -tag graph_$name -font fixed] foreach {bx1 by1 bx2 by2} [$canvas bbox $this_item] { break } set overlaps [$canvas find overlapping $bx1 $by1 $bx2 $by2] if {[lsearch -exact $overlaps $last_item] >= 0} { $canvas delete $this_item } else { set last_item $this_item } } } } } proc y {h max val border} { set inner [expr $h - 2 * $border] if {$max > 0} { set step [expr (1.0 * $inner) / (1.0 * $max)] } else { set step 1 } set y [expr $h - ($border + $val * $step)] return $y } #*********************************************************************** # %PROCEDURE: get_status # %ARGUMENTS: # None # %RETURNS: # A status string from the multiplexor # %DESCRIPTION: # Gets mimedefang-multiplexor status #*********************************************************************** proc get_status {} { mx_command "rawload" } proc mx_command { cmd } { global CtrlFD open_command_channel puts $CtrlFD $cmd flush $CtrlFD gets $CtrlFD line if {[string match "ERROR *" $line]} { error $line } return $line } proc open_command_channel {} { global CtrlFD global MD_MX_CTRL if {"$CtrlFD" != "0"} { return } set CtrlFD [open "|$MD_MX_CTRL -i" "r+"] # Fix for Windoze boxes... fconfigure $CtrlFD -translation binary } proc close_command_channel {} { global CtrlFD catch { close $CtrlFD } set CtrlFD 0 } #*********************************************************************** # %PROCEDURE: create_gui # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Creates the GUI #*********************************************************************** proc create_gui {} { global Margin global MD_MX_CTRL global Title if {"$Title" != ""} { wm title . "Watch MIMEDefang - $Title" wm iconname . "Watch MIMEDefang - $Title" } else { wm title . "Watch MIMEDefang" wm iconname . "Watch MIMEDefang" } canvas .c -relief sunken -width 400 -height 120 -takefocus 0 -borderwidth 2 -background white canvas .load -relief sunken -width 400 -height 120 -takefocus 0 -borderwidth 2 -background white canvas .latency -relief sunken -width 400 -height 120 -takefocus 0 -borderwidth 2 -background white canvas .mps -relief sunken -width 400 -height 120 -takefocus 0 -borderwidth 2 -background white canvas .activations -relief sunken -width 400 -height 120 -takefocus 0 -borderwidth 2 -background white canvas .reaps -relief sunken -width 400 -height 120 -takefocus 0 -borderwidth 2 -background white frame .f scale .s -from 100 -to 10000 -resolution 100 -orient horizontal \ -label "Update Interval (ms)" -variable UpdateInterval grid .c -row 0 -column 0 -sticky nsew grid .load -row 0 -column 1 -sticky nsew grid .latency -row 1 -column 0 -sticky nsew grid .mps -row 1 -column 1 -sticky nsew grid .activations -row 2 -column 0 -sticky nsew grid .reaps -row 2 -column 1 -sticky nsew grid .f -row 3 -column 0 -columnspan 2 -sticky nsew grid columnconfigure . 0 -weight 1 grid columnconfigure . 1 -weight 1 grid rowconfigure . 0 -weight 1 grid rowconfigure . 1 -weight 1 grid rowconfigure . 2 -weight 1 label .f.l1 -text "Max: " -fg black label .f.l2 -text "Busy: " -fg "#A00000" label .f.l3 -text "Idle: " -fg "#00A000" label .f.l4 -text "Queued: " -fg "#A0A000" label .f.max -fg black -width 4 -anchor w label .f.busy -fg "#A00000" -width 4 -anchor w label .f.idle -fg "#00A000" -width 4 -anchor w label .f.queued -fg "#A0A000" -width 4 -anchor w button .f.reread -text "Reread Filters" -command reread button .f.quit -text "Quit" -command exit label .f.result -text "" -relief sunken -anchor w frame .f.g entry .f.g.cmd -width 40 -insertofftime 0 .f.g.cmd delete 0 end .f.g.cmd insert end $MD_MX_CTRL bind .f.g.cmd set_ctrl_command label .f.g.cmdup -text "Control Command: " pack .f.g.cmdup -side left -expand 0 -fill none pack .f.g.cmd -side left -expand 1 -fill x frame .f.legend label .f.legend.uptime -text "" checkbutton .f.legend.t0 -fg "#A00000" -text "10s " -variable Show(0) -command update_show checkbutton .f.legend.t10 -fg "#A0A000" -text "10m" -variable Show(10) -command update_show checkbutton .f.legend.t5 -fg "#00A000" -text "5m " -variable Show(5) -command update_show checkbutton .f.legend.t1 -fg "#0000A0" -text "1m " -variable Show(1) -command update_show pack .f.legend.uptime .f.legend.t0 .f.legend.t1 .f.legend.t5 .f.legend.t10 -side left -expand 0 -anchor center grid .f.l1 -row 0 -column 0 -sticky w grid .f.max -row 0 -column 1 -sticky w grid .f.legend -row 0 -column 2 grid .f.reread -row 0 -column 3 -sticky e grid .f.l2 -row 1 -column 0 -sticky e grid .f.busy -row 1 -column 1 -sticky w grid .f.quit -row 1 -column 3 -sticky e grid .f.l4 -row 2 -column 0 -sticky e grid .f.queued -row 2 -column 1 -sticky w grid .f.g -row 2 -column 2 -sticky ew grid .f.l3 -row 3 -column 0 -sticky e grid .f.idle -row 3 -column 1 -sticky w grid .f.result -row 3 -column 2 -columnspan 2 -sticky ew grid columnconfigure .f 0 -weight 0 grid columnconfigure .f 1 -weight 0 grid columnconfigure .f 2 -weight 1 grid columnconfigure .f 3 -weight 0 grid .s -row 4 -column 0 -columnspan 2 -sticky ew bind .c [list canvas_resized .c BusyGraph] bind .load [list canvas_resized .load LoadGraph] bind .latency [list canvas_resized .latency LatencyGraph] bind .mps [list canvas_resized .mps MPSGraph] bind .activations [list canvas_resized .activations ActGraph] bind .reaps [list canvas_resized .reaps ReapGraph] foreach i {BusyGraph LoadGraph LatencyGraph MPSGraph ActGraph ReapGraph} { graph_create $i graph_configure $i width [expr 400 - $Margin(left) - $Margin(right)] graph_configure $i height [expr 120 - $Margin(top) - $Margin(bottom)] graph_configure $i sxmin timeseries graph_configure $i sxmax timeseries graph_configure $i origin_y [expr 120 - $Margin(bottom)] graph_configure $i origin_x $Margin(left) graph_configure $i xticks 0 graph_configure $i yticks 5 } graph_configure_data BusyGraph Busy color "#A00000" graph_configure_data BusyGraph Busy width 1 graph_configure BusyGraph title "Busy Workers" graph_configure LatencyGraph title "Latency (ms)" graph_configure LoadGraph title "Workers/scan" graph_configure MPSGraph title "Messages/s" graph_configure ActGraph title "Activations/s" graph_configure ReapGraph title "Reaps/s" foreach i {LoadGraph LatencyGraph MPSGraph ActGraph ReapGraph} { graph_configure_data $i D0 color "#A00000" graph_configure_data $i D1 color "#0000A0" graph_configure_data $i D5 color "#00A000" graph_configure_data $i D10 color "#A0A000" graph_configure_data $i D0 width 1 graph_configure_data $i D1 width 1 graph_configure_data $i D5 width 1 graph_configure_data $i D10 width 1 graph_configure_data $i D0 yoffset -1 graph_configure_data $i D1 yoffset 0 graph_configure_data $i D5 yoffset 1 graph_configure_data $i D10 yoffset 2 } } proc reread {} { if {[catch {set ans [mx_command reread]} err]} { do_result $err "#A00000" 3000 } else { do_result $ans black 3000 } } proc set_ctrl_command {} { global MD_MX_CTRL global AfterResult set MD_MX_CTRL [.f.g.cmd get] close_command_channel clear_result catch {after cancel $AfterResult} update_show graph_add_data BusyGraph Busy {} } proc do_result { text color delay } { global AfterResult .f.result configure -fg $color -text $text catch {after cancel $AfterResult} set AfterResult [after $delay clear_result] } proc clear_result {} { global AfterResult set AfterResult 0 .f.result configure -text "" -fg black } proc update_show {} { foreach g {LatencyGraph MPSGraph LoadGraph ActGraph ReapGraph} { foreach d {D0 D1 D5 D10} { graph_add_data $g $d {} graph_configure_data $g $d auto_x 0 } } } proc canvas_resized {c g} { global Margin set w [winfo width $c] set h [winfo height $c] graph_configure $g width [expr $w - $Margin(left) - $Margin(right)] graph_configure $g height [expr $h - $Margin(top) - $Margin(bottom)] graph_configure $g origin_y [expr $h - $Margin(bottom)] graph_draw $g $c } proc clear_after_error { msg } { global UpdateInterval do_result $msg "#A00000" 3000 close_command_channel graph_add_data BusyGraph Busy {} foreach i {LoadGraph LatencyGraph MPSGraph ActGraph ReapGraph} { foreach d {D0 D1 D5 D10} { graph_add_data $i $d {} } } graph_draw LoadGraph .load graph_draw LatencyGraph .latency graph_draw MPSGraph .mps graph_draw BusyGraph .c graph_draw ActGraph .activations graph_draw ReapGraph .reaps .f.max configure -text "???" .f.busy configure -text "???" .f.idle configure -text "???" .f.queued configure -text "???" .f.legend.uptime configure -text "Uptime ??? " return } proc uptime { secs } { set weeks [expr $secs / 604800] set secs [expr $secs - ($weeks * 604800)] set days [expr $secs / 86400] set secs [expr $secs - ($days * 86400)] set hours [expr $secs / 3600] set secs [expr $secs - ($hours * 3600)] set mins [expr $secs / 60] set secs [expr $secs - ($mins * 60)] set ans "" if {$weeks != 0} { append ans "${weeks}w " } if {$days != 0} { append ans "${days}d " } if {$hours != 0} { append ans [format "%02dh " $hours]} if {$mins != 0} { append ans [format "%02dm " $mins]} append ans [format "%02ds" $secs] return $ans } proc take_reading {} { global UpdateInterval if {[catch {take_reading_aux} ans]} { clear_after_error $ans after $UpdateInterval take_reading return } after $UpdateInterval take_reading } #*********************************************************************** # %PROCEDURE: take_reading_aux # %ARGUMENTS: # None # %RETURNS: # Nothing # %DESCRIPTION: # Takes a reading and updates GUI #*********************************************************************** proc take_reading_aux {} { global Show set line [get_status] foreach {msgs_0 msgs_1 msgs_5 msgs_10 avg_0 avg_1 avg_5 avg_10 ams_0 ams_1 ams_5 ams_10 a0 a1 a5 a10 r0 r1 r5 r10 nbusy nidle nstopped nkilled msgs activations qsize numq secs} $line {break} set secs [uptime $secs] if {![info exists msgs_0] || ![info exists secs]} { error "Error: Unable to interpret result: $line" return } set mps_0 [expr $msgs_0 / 10.0] set mps_1 [expr $msgs_1 / 60.0] set mps_5 [expr $msgs_5 / 300.0] set mps_10 [expr $msgs_10 / 600.0] set a0 [expr $a0 / 10.0] set a1 [expr $a1 / 60.0] set a5 [expr $a5 / 300.0] set a10 [expr $a10 / 600.0] set r0 [expr $r0 / 10.0] set r1 [expr $r1 / 60.0] set r5 [expr $r5 / 300.0] set r10 [expr $r10 / 600.0] graph_add_point BusyGraph Busy auto $nbusy graph_keep_lastn BusyGraph Busy [graph_cget BusyGraph width] graph_configure BusyGraph symin 0 set total [expr $nbusy + $nidle + $nstopped + $nkilled] graph_configure BusyGraph symax $total graph_configure BusyGraph yticks $total graph_draw BusyGraph .c if {$Show(0)} { graph_add_point LoadGraph D0 auto $avg_0 graph_add_point LatencyGraph D0 auto $ams_0 graph_add_point MPSGraph D0 auto $mps_0 graph_add_point ActGraph D0 auto $a0 graph_add_point ReapGraph D0 auto $r0 } if {$Show(1)} { graph_add_point LoadGraph D1 auto $avg_1 graph_add_point LatencyGraph D1 auto $ams_1 graph_add_point MPSGraph D1 auto $mps_1 graph_add_point ActGraph D1 auto $a1 graph_add_point ReapGraph D1 auto $r1 } if {$Show(5)} { graph_add_point LoadGraph D5 auto $avg_5 graph_add_point LatencyGraph D5 auto $ams_5 graph_add_point MPSGraph D5 auto $mps_5 graph_add_point ActGraph D5 auto $a5 graph_add_point ReapGraph D5 auto $r5 } if {$Show(10)} { graph_add_point LoadGraph D10 auto $avg_10 graph_add_point LatencyGraph D10 auto $ams_10 graph_add_point MPSGraph D10 auto $mps_10 graph_add_point ActGraph D10 auto $a10 graph_add_point ReapGraph D10 auto $r10 } graph_draw LoadGraph .load graph_draw LatencyGraph .latency graph_draw MPSGraph .mps graph_draw ActGraph .activations graph_draw ReapGraph .reaps .f.max configure -text "$total" .f.legend.uptime configure -text "Uptime $secs " .f.busy configure -text $nbusy .f.idle configure -text $nidle .f.queued configure -text $numq } proc usage {} { global argv0 puts stderr "Usage: $argv0 options" puts stderr "\nOptions are:" puts stderr " -command cmd Use 'cmd' as Control Command" puts stderr " -interval msec Update every msec milliseconds" puts stderr " -10s 0_or_1 Show or hide 10s graph plot" puts stderr " -1m 0_or_1 Show or hide 1m graph plot" puts stderr " -5m 0_or_1 Show or hide 5ms graph plot" puts stderr " -10m 0_or_1 Show or hide 10m graph plot" puts stderr " -title string Add string to window title" puts stderr " -help Show this usage info" exit 0 } proc parse_cmdline_args {} { global argv global MD_MX_CTRL global UpdateInterval global Show global Title foreach {opt val} $argv { switch -exact -- $opt { -command { set MD_MX_CTRL $val } -interval { set UpdateInterval $val } -10s { set Show(0) $val } -1m { set Show(1) $val } -5m { set Show(5) $val } -10m { set Show(10) $val } -title { set Title $val } -help { usage exit 0 } default { puts stderr "Unrecognized option $opt" usage exit 1 } } } } parse_cmdline_args create_gui # Kick things off take_reading mimedefang-3.6/watch-multiple-mimedefangs.8000066400000000000000000000072721475763067200210140ustar00rootroot00000000000000.\" $Id: watch-multiple-mimdefangs.8 9989 2004-09-22 14:08:58Z dfs $ .\"" .TH WATCH-MULTIPLE-MIMDEFANGS 8 "12 January 2007" .UC 4 .SH NAME watch-multiple-mimdefangs.tcl \- Keep an eye on a cluster of MIMEDefang machines .SH SYNOPSIS .B watch-multiple-mimdefangs.tcl [-n] [-r] [-s] [-t] [-archive] \fR\fImachine-1 machine-2 ... machine-N\fR .SH DESCRIPTION \fBwatch-multiple-mimdefangs.tcl\fR is a Tk script that graphically displays the status of \fBmimedefang-multiplexor\fR(8) on a cluster of machines. Note that Tcl/Tk 8.4 or higher is required to run \fBwatch-multiple-mimdefangs.tcl\fR. If you supply the \fB-archive\fR command-line option, then \fBwatch-multiple-mimdefangs.tcl\fR logs the output of \fBmd-mx-ctrl rawload\fR for each machine. The output for \fImachine_name\fR is logged in: $HOME/.watch-multiple-mimedefangs/\fImachine_name\fR/data. If you supply any of \fB-n\fR, \fB-r\fR, \fB-s\fR or \fB-t\fR options, then \fBwatch-multiple-mimdefangs.tcl\fR uses the \fBrawload1\fR command rather than \fBrawload\fR command to read performance data. \fIDo not use these options unless all monitored machines are running MIMEDefang 2.74 or newer!\fR. The \fB-n\fR, \fB-r\fR, \fB-s\fR, and \fB-t\fR options enable monitoring of scan times, filter_relay times, filter_sender times and filter_recipient times, respectively. .SH PREREQUISITES To use \fBwatch-multiple-mimdefangs.tcl\fR to monitor a cluster of machines, you need the following prerequisites: .TP .B o A UNIX-like machine with Tcl/Tk 8.4, the X window system and an SSH client. You also need "xterm" to use some of the features. .TP .B o The ability to SSH \fIas root\fR to all of the machines you want to monitor. Ideally, you should be using the SSH agent so that you can SSH to the monitored machines as root without being prompted for a password or pass phrase. .TP .B o The program \fBmd-mx-ctrl\fR on root's path on all the machines you want to monitor. .SH DISPLAY For each machine specified on the command line, \fBwatch-multiple-mimdefangs.tcl\fR creates a chart with five columns. The columns are: .TP .B o A button with the name of the machine. Clicking the button pops up a menu that lets you take various actions, as described later. If all workers on the machine are busy, the button turns yellow. .TP .B o A label showing the number of busy workers in the form "busy/total", where total is the total number of workers. .TP .B o A label showing the average number of messages per second over the last 10 seconds. .TP .B o A label showing the average number of milliseconds per scan over the last 10 seconds. .TP .B o A chart that graphs the average number of busy workers, the average number of messages per second and the average scan time in milliseconds, all averaged over the last 10 seconds. .SH MACHINE MENU If you click on a machine name, a menu with three options pops up: .TP .B SSH Open an xterm session and ssh as root to the machine. .TP .B Busy Workers Monitor the busy workers on the machine. If you click on the process-ID of a worker, an xterm will open up and the command "strace -s 100 -t -p \fIpid\fR" will be executed on the remote machine. This is Linux-specific, but you can edit \fBwatch-multiple-mimdefangs.tcl\fR to replace the command with your particular system's command for tracing system calls. .TP .B Delete Remove the machine from the list of machines being monitored. .SH ADDING A MACHINE If you need to add a machine to the display, simply type the name of the machine in the "Add Machine:" box and press Enter. .SH AUTHOR \fBwatch-multiple-mimdefangs.tcl\fR was written by Dianne Skoll. .SH SEE ALSO mimedefang.pl(8), mimedefang-filter(5), mimedefang(8), mimedefang-protocol(7), md-mx-ctrl(8), watch-mimedefang(8) mimedefang-3.6/watch-multiple-mimedefangs.tcl000077500000000000000000001414701475763067200214310ustar00rootroot00000000000000#!/bin/sh # -*-Mode: TCL;-*- # # Copyright (C) 2007 Roaring Penguin Software Inc. This file may be # distributed under the terms of the GNU General Public License, Version 2. # Next line restarts using wish \ exec wish "$0" -- "$@" ; clear; echo "*****"; echo "Cannot find 'wish' -- you need Tcl/Tk installed to run this program"; exit 1 # Main update interval (ms) set MainUpdateInterval 1500 # Busy worker update interval (ms) set BusyWorkerUpdateInterval 3000 # Trace command - pid is appended set TraceCommand "strace -s 100 -t -p" # Command to run SSH set SSHCommand "ssh" # Command to run md-mx-ctrl set MD_MX_CTRL "md-mx-ctrl -i" # Archive readings? set DoArchive 0 # Have we done a redraw since the last update? # Start out set to yes to kick things off! set DoneARedrawSinceLastUpdate 1 # Use new-style "rawload1" command? set NewStyle 0 set NewStyleShowScans 0 set NewStyleShowRelayoks 0 set NewStyleShowSenderoks 0 set NewStyleShowRecipoks 0 # Time scale for graph y-axes (seconds) set NewStyleTimeInterval 1 set MachinesAwaitingReply {} if {[info exists env(MD_MX_CTRL)]} { set x $env(MD_MX_CTRL) if {"$x" != ""} { set MD_MX_CTRL $x } } proc strip_zeros { num } { return [regsub {\.0+$} $num ""] } # Don't edit anything below here! set Machines {} set ctr 0 set after_token {} proc find_machine { mach } { global Machines set index 0 foreach m $Machines { if {"[lindex $m 0]" == "$mach"} { return $index } incr index } return -1 } proc add_machine { mach } { if {[find_machine $mach] >= 0} { return } global Machines if {[catch { set fp [open_connection $mach] lappend Machines [list $mach $fp] } err]} { puts stderr $err } } proc host_plus_user { mach } { if { [string match "*@*" $mach] } { return $mach } return "root@$mach" } proc del_machine { mach } { global Machines global Data set mnew {} set index 0 set did_something 0 foreach m $Machines { if { "[lindex $m 0]" == "$mach"} { catch { close [lindex $m 1] } catch { unset Data($mach,busy) } catch { unset Data($mach,time) } catch { unset Data($mach,persec) } catch { unset Data($mach,busy_snap) } catch { unset Data($mach,persec_snap) } catch { unset Data($mach,time_snap) } catch { unset Data($mach,qsize) } catch { unset Data($mach,busyworkerwin) } catch { unset Data($mach,busyworkerafter) } catch { unset Data($mach,error) } set did_something 1 continue } lappend mnew $m } set Machines $mnew if {$did_something} { reconfigure } } proc open_connection { mach } { global SSHCommand global MD_MX_CTRL set hmach [host_plus_user $mach] set fp [open "| $SSHCommand $hmach $MD_MX_CTRL" "r+"] fconfigure $fp -blocking 0 #fconfigure $fp -translation binary fileevent $fp readable [list connection_readable $mach $fp] return $fp } proc connection_readable { mach fp } { global DoArchive global MachinesAwaitingReply global after_token # Delete from MachinesAwaitingReply set index [lsearch -exact $MachinesAwaitingReply $mach] if {$index >= 0} { set MachinesAwaitingReply [lreplace $MachinesAwaitingReply $index $index] } gets $fp line if {"$line" == ""} { if {[eof $fp]} { catch { close $fp } del_machine $mach } } else { set index [find_machine $mach] if {$index >= 0} { if {[catch { update_machine $line $index $mach } err]} { mach_set_status_error $mach $index $err } if {$DoArchive} { if {[catch { log_stats $mach $line } err]} { puts stderr $err } } } } # If all machines have replied, redraw if {[llength $MachinesAwaitingReply] == 0} { if {"$after_token" != ""} { after cancel $after_token set after_token {} } redraw } } proc log_stats { mach line } { set dir "~/.watch-multiple-mimedefangs/$mach" if {![file isdirectory $dir]} { file mkdir $dir } set fp [open "$dir/data" "a"] puts $fp "[clock seconds] $line" close $fp } proc get_machine_windows { } { set kids [winfo children .top] set result {} set hi 0 foreach k $kids { if {[regexp {^\.top\.name([0-9]+)$} $k dummy index]} { if {$index > $hi} { set hi $index } } } foreach k $kids { if {[regexp {^\.top\.[^0-9]+([0-9]+)$} $k dummy index]} { if {$index <= $hi} { lappend result $k } } } return $result } proc mach_set_status_error { mach index err } { global Data set Data($mach,error) 1 .top.name$index configure -foreground red .top.c$index itemconfigure statusText -text $err .top.c$index delete withtag data1 .top.c$index delete withtag data2 .top.c$index delete withtag data3 } proc mach_set_status_normal { mach index status } { global Data set Data($mach,error) 0 .top.name$index configure -foreground black .top.c$index itemconfigure statusText -text $status } proc mach_populate_data { mach index line } { global Data global NewStyle if {$NewStyle} { mach_populate_data_new_style $mach $index $line return } foreach { msg0 msg1 msg5 msg10 busy0 busy1 busy5 busy10 ms0 ms1 ms5 ms10 a0 a1 a5 a10 r0 r1 r5 r10 busy idle stopped killed msgs activations qsize qnum uptime} $line { break } set total_workers [expr $busy + $idle + $stopped + $killed] set ms0 [format "%.0f" $ms0] set msg0 [format "%.2f" [expr $msg0 / 10.0]] lappend Data($mach,busy) $busy0 lappend Data($mach,time) $ms0 lappend Data($mach,persec) $msg0 set Data($mach,total_workers) $total_workers set Data($mach,busy_snap) $busy set Data($mach,persec_snap) $msg0 set Data($mach,time_snap) $ms0 set Data($mach,qsize) $qsize schedule_redraw } proc mach_populate_data_new_style { mach index line } { global Data foreach { scans avgbusyscans scantime relayoks avgbusyrelayoks relayoktime senderoks avgbusysenderoks senderoktime recipoks avgbusyrecipoks recipoktime busyworkers idleworkers stoppedworkers killedworkers msgs activations qsize numqueued uptime back } $line { break } set scantime [format "%.0f" $scantime] set relayoktime [format "%.0f" $relayoktime] set senderoktime [format "%.0f" $senderoktime] set recipoktime [format "%.0f" $recipoktime] set total_workers [expr $busyworkers + $idleworkers + $stoppedworkers + $killedworkers] set back [expr 1.0 * $back] set scanspersec [expr (1.0 * $scans) / $back ] set relayokspersec [expr (1.0 * $relayoks) / $back ] set senderokspersec [expr (1.0 * $senderoks) / $back ] set recipokspersec [expr (1.0 * $recipoks) / $back ] set scanspersec [format "%.2f" $scanspersec] set relayokspersec [format "%.2f" $relayokspersec] set senderokspersec [format "%.2f" $senderokspersec] set recipokspersec [format "%.2f" $recipokspersec] lappend Data($mach,busy) $busyworkers lappend Data($mach,scantime) $scantime lappend Data($mach,scanspersec) $scanspersec lappend Data($mach,relayoktime) $relayoktime lappend Data($mach,relayokspersec) $relayokspersec lappend Data($mach,senderoktime) $senderoktime lappend Data($mach,senderokspersec) $senderokspersec lappend Data($mach,recipoktime) $recipoktime lappend Data($mach,recipokspersec) $recipokspersec set Data($mach,total_workers) $total_workers set Data($mach,busy_snap) $busyworkers set Data($mach,scanspersec_snap) $scanspersec set Data($mach,relayokspersec_snap) $relayokspersec set Data($mach,senderokspersec_snap) $senderokspersec set Data($mach,recipokspersec_snap) $recipokspersec set Data($mach,scantime_snap) $scantime set Data($mach,relayoktime_snap) $relayoktime set Data($mach,senderoktime_snap) $senderoktime set Data($mach,recipoktime_snap) $recipoktime set Data($mach,qsize) $qsize set Data($mach,numqueued) $numqueued schedule_redraw } proc schedule_redraw {} { global MainUpdateInterval global after_token if {"$after_token" == ""} { set after_token [after $MainUpdateInterval redraw] } } proc redraw_new_style {} { global Machines global after_token global Data global TotalData global NewStyleShowScans NewStyleShowRelayoks NewStyleShowSenderoks NewStyleShowRecipoks set after_token "" set index 0 set scanspersec_total 0 set relayokspersec_total 0 set senderokspersec_total 0 set recipokspersec_total 0 set scantime_total 0 set relayoktime_total 0 set senderoktime_total 0 set recipoktime_total 0 set busyworkers_total 0 set totalworkers_total 0 set busyworkers_active 0 set totalworkers_active 0 set num_machines 0 set num_graphs 1 if {$NewStyleShowScans} { incr num_graphs 2 } if {$NewStyleShowRelayoks} { incr num_graphs 2 } if {$NewStyleShowSenderoks} { incr num_graphs 2 } if {$NewStyleShowRecipoks} { incr num_graphs 2 } set wid [winfo width .top.clabels] .top.clabels delete all set spacing [expr $wid / (1.0 * $num_graphs)] set x [expr $spacing / 2.0] .top.clabels create text $x 2 -anchor n -fill "#A00000" -text "Busy Workers" if {$NewStyleShowScans} { set x [expr $x + $spacing] .top.clabels create text $x 2 -anchor n -fill "#00A000" -text "Scans/d" set x [expr $x + $spacing] .top.clabels create text $x 2 -anchor n -fill "#0000A0" -text "ms/scan" } if {$NewStyleShowRelayoks} { set x [expr $x + $spacing] .top.clabels create text $x 2 -anchor n -fill "#808000" -text "Relays/d" set x [expr $x + $spacing] .top.clabels create text $x 2 -anchor n -fill "#008080" -text "ms/relay" } if {$NewStyleShowSenderoks} { set x [expr $x + $spacing] .top.clabels create text $x 2 -anchor n -fill "#808080" -text "Senders/d" set x [expr $x + $spacing] .top.clabels create text $x 2 -anchor n -fill "#800080" -text "ms/sender" } if {$NewStyleShowRecipoks} { set x [expr $x + $spacing] .top.clabels create text $x 2 -anchor n -fill "#008000" -text "Recips/d" set x [expr $x + $spacing] .top.clabels create text $x 2 -anchor n -fill "#000000" -text "ms/recip" } foreach m $Machines { set mach [lindex $m 0] if {![info exists Data($mach,busy)]} { incr index continue } if {$Data($mach,error)} { incr index continue } # Update totals set busy $Data($mach,busy_snap) set totalworkers $Data($mach,total_workers) # Only update worker counts for machines that are actually doing something if {$Data($mach,scanspersec_snap) > 0 || $Data($mach,relayokspersec_snap) > 0 || $Data($mach,senderokspersec_snap) > 0 || $Data($mach,recipokspersec_snap) > 0} { set totalworkers_active [expr $totalworkers_active + $totalworkers] set busyworkers_active [expr $busyworkers_active + 1.0*$busy] } set totalworkers_total [expr $totalworkers_total + $totalworkers] set busyworkers_total [expr $busyworkers_total + 1.0*$busy] set scanspersec_total [expr $scanspersec_total + 1.0*$Data($mach,scanspersec_snap)] set scantime_total [expr $scantime_total + (1.0*$Data($mach,scanspersec_snap) * $Data($mach,scantime_snap))] set relayokspersec_total [expr $relayokspersec_total + 1.0*$Data($mach,relayokspersec_snap)] set relayoktime_total [expr $relayoktime_total + (1.0*$Data($mach,relayokspersec_snap) * $Data($mach,relayoktime_snap))] set senderokspersec_total [expr $senderokspersec_total + 1.0*$Data($mach,senderokspersec_snap)] set senderoktime_total [expr $senderoktime_total + (1.0*$Data($mach,senderokspersec_snap) * $Data($mach,senderoktime_snap))] set recipokspersec_total [expr $recipokspersec_total + 1.0*$Data($mach,recipokspersec_snap)] set recipoktime_total [expr $recipoktime_total + (1.0*$Data($mach,recipokspersec_snap) * $Data($mach,recipoktime_snap))] set graph 0 set Data($mach,busy) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 $Data($mach,total_workers) $Data($mach,busy) $index "Busy" $graph "#A00000" 1] if {$totalworkers > 0} { set pctbusy [expr int((1.0*$busy) / (1.0*$totalworkers) * 100)] } else { set pctbusy 100 } if {$pctbusy < 80} { .top.busy$index configure -background #D9D9D9 -foreground "#A00000" } elseif {$pctbusy < 90} { .top.busy$index configure -background #C0C000 -foreground "#A00000" } else { .top.busy$index configure -background #C00000 -foreground "#000000" } .top.busy$index configure -text "$busy/$totalworkers\n$pctbusy%" incr graph if {$NewStyleShowScans} { set Data($mach,scanspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,scanspersec) $index "Scans/s" $graph "#00A000" 1] incr graph set Data($mach,scantime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,scantime) $index "ms/scan" $graph "#0000A0" 1] incr graph set s $Data($mach,scanspersec_snap) set h [human_number [expr $s * 3600]] set d [human_number [expr $s * 86400]] if {$s == 0} { .top.scanspersec$index configure -text "-" .top.scantime$index configure -text "-" } else { .top.scanspersec$index configure -text [format "%.2f\n%s/h\n%s/d" $s $h $d] .top.scantime$index configure -text $Data($mach,scantime_snap) } } else { set Data($mach,scanspersec) {} set Data($mach,scantime) {} } if {$NewStyleShowRelayoks} { set Data($mach,relayokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,relayokspersec) $index "Relayoks/s" $graph "#808000" 1] incr graph set Data($mach,relayoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,relayoktime) $index "ms/relayok" $graph "#008080" 1] incr graph set s $Data($mach,relayokspersec_snap) set h [human_number [expr $s * 3600]] set d [human_number [expr $s * 86400]] if {$s == 0} { .top.relayspersec$index configure -text "-" .top.relaytime$index configure -text "-" } else { .top.relayspersec$index configure -text [format "%.2f\n%s/h\n%s/d" $s $h $d] .top.relaytime$index configure -text $Data($mach,relayoktime_snap) } } else { set Data($mach,relayokspersec) {} set Data($mach,relayoktime) {} } if {$NewStyleShowSenderoks} { set Data($mach,senderokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,senderokspersec) $index "Senderoks/s" $graph "#808080" 1] incr graph set Data($mach,senderoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,senderoktime) $index "ms/senderok" $graph "#800080" 1] incr graph set s $Data($mach,senderokspersec_snap) set h [human_number [expr $s * 3600]] set d [human_number [expr $s * 86400]] if {$s == 0} { .top.senderspersec$index configure -text "-" .top.sendertime$index configure -text "-" } else { .top.senderspersec$index configure -text [format "%.2f\n%s/h\n%s/d" $s $h $d] .top.sendertime$index configure -text $Data($mach,senderoktime_snap) } } else { set Data($mach,senderokspersec) {} set Data($mach,senderoktime) {} } if {$NewStyleShowRecipoks} { set Data($mach,recipokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,recipokspersec) $index "Recipoks/s" $graph "#008000" 1] incr graph set Data($mach,recipoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $Data($mach,recipoktime) $index "ms/recipok" $graph "#000000" 1] incr graph set s $Data($mach,recipokspersec_snap) set h [human_number [expr $s * 3600]] set d [human_number [expr $s * 86400]] if {$s == 0} { .top.recipspersec$index configure -text "-" .top.reciptime$index configure -text "-" } else { .top.recipspersec$index configure -text [format "%.2f\n%s/h\n%s/d" $s $h $d] .top.reciptime$index configure -text $Data($mach,recipoktime_snap) } } else { set Data($mach,recipokspersec) {} set Data($mach,recipoktime) {} } incr index } lappend TotalData(busy) $busyworkers_total lappend TotalData(total) $totalworkers_total lappend TotalData(scanspersec) $scanspersec_total set scantime_avg [expr $scantime_total / ($scanspersec_total + 0.000000001)] set relayoktime_avg [expr $relayoktime_total / ($relayokspersec_total + 0.000000001)] set senderoktime_avg [expr $senderoktime_total / ($senderokspersec_total + 0.000000001)] set recipoktime_avg [expr $recipoktime_total / ($recipokspersec_total + 0.000000001)] lappend TotalData(scantime) $scantime_avg lappend TotalData(relayokspersec) $relayokspersec_total lappend TotalData(relayoktime) $relayoktime_avg lappend TotalData(senderokspersec) $senderokspersec_total lappend TotalData(senderoktime) $senderoktime_avg lappend TotalData(recipokspersec) $recipokspersec_total lappend TotalData(recipoktime) $recipoktime_avg set graph 0 set TotalData(busy) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 $totalworkers_total $TotalData(busy) $index "Busy" $graph "#A00000" 1] if {$totalworkers_total > 0} { set busyworkers_total [expr int($busyworkers_total)] set pctbusy [expr int((1.0 * $busyworkers_total) / (1.0 * $totalworkers_total) * 100)] } else { set pctbusy 100 } if {$totalworkers_active > 0} { set busyworkers_active [expr int($busyworkers_active)] set apct [expr int((1.0 * $busyworkers_active) / (1.0 * $totalworkers_active) * 100)] } else { set apct 100 } .top.busytotal configure -text "$busyworkers_total/$totalworkers_total\n$pctbusy%\n$busyworkers_active/$totalworkers_active\n$apct%" incr graph if {$NewStyleShowScans} { set TotalData(scanspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(scanspersec) $index "Scans/s" $graph "#00A000" 1] incr graph set TotalData(scantime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(scantime) $index "ms/scan" $graph "#0000A0" 1] incr graph set h [human_number [expr $scanspersec_total * 3600]] set d [human_number [expr $scanspersec_total * 86400]] .top.scanspersectotal configure -text [format "%.2f\n%s/h\n%s/d" $scanspersec_total $h $d] .top.avgscantime configure -text [format "%.0f" $scantime_avg] } else { set TotalData(scanspersec) {} set TotalData(scantime) {} } if {$NewStyleShowRelayoks} { set TotalData(relayokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(relayokspersec) $index "Relayoks/s" $graph "#808000" 1] incr graph set TotalData(relayoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(relayoktime) $index "ms/relayok" $graph "#008080" 1] incr graph set h [human_number [expr $relayokspersec_total * 3600]] set d [human_number [expr $relayokspersec_total * 86400]] .top.relayspersectotal configure -text [format "%.2f\n%s/h\n%s/d" $relayokspersec_total $h $d] .top.avgrelaytime configure -text [format "%.0f" $relayoktime_avg] } else { set TotalData(relayokspersec) {} set TotalData(relayoktime) {} } if {$NewStyleShowSenderoks} { set TotalData(senderokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(senderokspersec) $index "Senderoks/s" $graph "#808080" 1] incr graph set TotalData(senderoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(senderoktime) $index "ms/senderok" $graph "#800080" 1] incr graph set h [human_number [expr $senderokspersec_total * 3600]] set d [human_number [expr $senderokspersec_total * 86400]] .top.senderspersectotal configure -text [format "%.2f\n%s/h\n%s/d" $senderokspersec_total $h $d] .top.avgsendertime configure -text [format "%.0f" $senderoktime_avg] set s [human_number $senderokspersec_total] } else { set TotalData(senderokspersec) {} set TotalData(senderoktime) {} } if {$NewStyleShowRecipoks} { set TotalData(recipokspersec) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(recipokspersec) $index "Recipoks/s" $graph "#008000" 1] incr graph set TotalData(recipoktime) [graph [expr $graph / (1.0 * $num_graphs)] [expr ($graph + 1.0) / (1.0 * $num_graphs)] 0 auto $TotalData(recipoktime) $index "ms/recipok" $graph "#000000" 1] incr graph set h [human_number [expr $recipokspersec_total * 3600]] set d [human_number [expr $recipokspersec_total * 86400]] .top.recipspersectotal configure -text [format "%.2f\n%s/h\n%s/d" $recipokspersec_total $h $d] .top.avgreciptime configure -text [format "%.0f" $recipoktime_avg] } else { set TotalData(recipokspersec) {} set TotalData(recipoktime) {} } update } proc redraw {} { global Machines global NewStyle global after_token global Data global TotalData global DoneARedrawSinceLastUpdate set DoneARedrawSinceLastUpdate 1 if {$NewStyle} { redraw_new_style return } set after_token "" set index 0 set persec_total 0 set busy_workers_total 0 set avail_workers_total 0 set msgs_per_sec_total 0 set ms_per_scan_total 0 set num_machines 0 foreach m $Machines { set mach [lindex $m 0] if {![info exists Data($mach,busy)]} { incr index continue } if {$Data($mach,error)} { incr index continue } set busy $Data($mach,busy_snap) set total_workers $Data($mach,total_workers) set msg0 $Data($mach,persec_snap) set ms0 $Data($mach,time_snap) set persec_total [expr $persec_total + $msg0] # Format $busy to have as many characters as $total_workers set l [string length $total_workers] set busy [format "%${l}d" $busy] .top.busy$index configure -text "$busy/$total_workers" .top.persec$index configure -text $msg0 .top.time$index configure -text $ms0 if {$busy == $total_workers} { .top.name$index configure -background "#CCCC00" } else { .top.name$index configure -background "#D9D9D9" } set Data($mach,busy) [graph 0 [expr 1.0/3] 0 $Data($mach,total_workers) $Data($mach,busy) $index "Busy" 1 red 1] set Data($mach,persec) [graph [expr 1.0/3] [expr 2.0/3] 0 auto $Data($mach,persec) $index "Msgs/Sec" 2 green 1] set Data($mach,time) [graph [expr 2.0/3] 1 0 auto $Data($mach,time) $index "ms/scan" 3 blue 1] incr index if {$ms0 > 0 || $Data($mach,busy_snap) > 0 || $msg0 > 0} { incr num_machines incr busy_workers_total $Data($mach,busy_snap) incr avail_workers_total $Data($mach,total_workers); incr ms_per_scan_total $ms0 } } lappend TotalData(busy) $busy_workers_total if {$num_machines > 0} { lappend TotalData(time) [expr 1.0 * $ms_per_scan_total / (1.0 * $num_machines)] } else { lappend TotalData(time) 0 } lappend TotalData(persec) $persec_total incr index set TotalData(busy) [graph 0 [expr 1.0/3] 0 $avail_workers_total $TotalData(busy) $index "Busy" 1 red 1] set TotalData(persec) [graph [expr 1.0/3] [expr 2.0/3] 0 auto $TotalData(persec) $index "Msgs/Sec" 2 green 1] set TotalData(time) [graph [expr 2.0/3] 1 0 auto $TotalData(time) $index "ms/scan" 3 blue 1] set msgs_per_sec_total $persec_total set hour [human_number [expr $persec_total * 3600.0]] set day [human_number [expr $persec_total * 86400.0]] set persec_total [strip_zeros [format "%.1f" $persec_total]] .top.c configure -text "Total throughput $persec_total/s = $hour/hour = $day/day" set l [string length $avail_workers_total] set busy_workers_total [format "%${l}d" $busy_workers_total] .top.busytotal configure -text "$busy_workers_total/$avail_workers_total" .top.persectotal configure -text [strip_zeros [format "%.1f" $msgs_per_sec_total]] if {$num_machines > 0} { .top.avgtime configure -text [strip_zeros [format "%.0f" [expr 1.0 * $ms_per_scan_total / (1.0 * $num_machines)]]] } else { .top.avgtime configure -text "--" } update } proc graph { start_frac end_frac min max data index label tag fill_color line_width} { global NewStyleTimeInterval set tag "data$tag" set c .top.c$index set h [winfo height $c] set w [winfo width $c] set x0 [expr int($start_frac * $w)] set x1 [expr int($end_frac * $w)] set x0 [expr $x0 + 40] set x1 [expr $x1 - 5] set diff [expr $x1 - $x0] set gridline_spacing 15 if {[llength $data] > $diff} { set toChop [expr [llength $data] - $diff] set data [lrange $data $toChop end] } set multiplier 1 if {"$label" == "Scans/s"} { if {$NewStyleTimeInterval == 3600} { set label "Scans/h" set multiplier 3600 } elseif {$NewStyleTimeInterval == 86400} { set label "Scans/d" set multiplier 86400 } } if {"$label" == "Senderoks/s"} { if {$NewStyleTimeInterval == 3600} { set label "Senderoks/h" set multiplier 3600 } elseif {$NewStyleTimeInterval == 86400} { set label "Senderoks/d" set multiplier 86400 } } if {"$label" == "Relayoks/s"} { if {$NewStyleTimeInterval == 3600} { set label "Relayoks/h" set multiplier 3600 } elseif {$NewStyleTimeInterval == 86400} { set label "Relayoks/d" set multiplier 86400 } } if {"$label" == "Recipoks/s"} { if {$NewStyleTimeInterval == 3600} { set label "Recipoks/h" set multiplier 3600 } elseif {$NewStyleTimeInterval == 86400} { set label "Recipoks/d" set multiplier 86400 } } if {"$min" == "auto"} { set min [lindex $data 0] foreach thing $data { if {$thing < $min} {set min $thing} } set min [expr $multiplier * $min] set min [nicenum $min 1] } if {"$max" == "auto"} { set max [lindex $data 0] foreach thing $data { if {$thing > $max} {set max $thing} } set max [expr $multiplier * $max] set max [nicenum $max 0] } set x $x0 $c delete withtag $tag set coords {} if {$max == $min} { set max [expr $max + 1.0] } set diff [expr 1.0 * ($max - $min)] set num_gridlines [expr int((1.0 * $h) / (1.0 * $gridline_spacing))] if {$num_gridlines > 10} { set num_gridlines 10 } if {$num_gridlines < 1} { set num_gridlines 1 } set delta [nicenum [expr $diff / $num_gridlines] 1] foreach point $data { set y [expr $point * $multiplier - $min] set y [expr (1.0 * $y * $h) / (1.0 * $diff)] set y [expr $h - $y] if {$y < 1} { set y 1 } if {$y >= $h} { set y [expr $h - 1] } lappend coords $x $y incr x } if {$delta > 0.0} { set last_phys_y 99999 for {set y $min} {$y <= $max} {set y [expr $y + $delta]} { set cy [expr (1.0 * ($y-$min) * $h) / (1.0 * $diff)] set cy [expr $h - $cy] if {$cy <= 0} { continue } if {$cy > [expr $h-1]} { set cy [expr $h-1] } if {($last_phys_y - $cy) >= (2 * $gridline_spacing)} { set last_phys_y $cy set anc w if {$cy < $gridline_spacing} { set anc nw } if {$cy >= ($h - $gridline_spacing)} { set anc sw } $c create line [expr $x0 - 10] $cy $x1 $cy -fill "#A0A0A0" -tags $tag $c create text [expr $x0 - 37] $cy -text [human_number $y] -tag $tag -anchor $anc } else { $c create line $x0 $cy $x1 $cy -fill "#DDDDDD" -tags $tag } } } else { $c create text [expr $x0 - 37] 0 -anchor nw -text [human_number $max] -tag $tag $c create text [expr $x0 - 37] $h -anchor sw -text [human_number $min] -tag $tag } if {[llength $coords] >= 4} { $c create line $coords -fill $fill_color -width $line_width -tags $tag } return $data } proc update_machine { line index mach } { if {[string match "ERROR *" $line]} { mach_set_status_error $mach $index $line return } mach_set_status_normal $mach $index "" mach_populate_data $mach $index $line } proc interactive_add_machine {} { set mach [.top.new get] if {"$mach" != ""} { add_machine $mach reconfigure } } proc reconfigure_new_style {} { global Machines global NewStyleShowScans NewStyleShowRelayoks NewStyleShowSenderoks NewStyleShowRecipoks catch { destroy .top.name } catch { destroy .top.busy } catch { destroy .top.scanspersec } catch { destroy .top.scantime } catch { destroy .top.relayspersec } catch { destroy .top.relaytime } catch { destroy .top.senderspersec } catch { destroy .top.sendertime } catch { destroy .top.recipspersec } catch { destroy .top.reciptime } catch { destroy .top.c } set col 2 set canv_width 200 label .top.name -text "Machine Name" label .top.busy -text "Busy Workers " -foreground "#A00000" grid .top.name -row 0 -column 0 -sticky new grid .top.busy -row 0 -column 1 -sticky new catch { destroy .top.clabels} canvas .top.clabels -width $canv_width -height 10 -takefocus 0 -borderwidth 0 -background #ffffff -highlightthickness 0 if {$NewStyleShowScans} { incr canv_width 200 label .top.scanspersec -text "Scans/s " -foreground "#00A000" grid .top.scanspersec -row 0 -column $col -sticky new incr col label .top.scantime -text "ms/scan " -foreground "#0000A0" grid .top.scantime -row 0 -column $col -sticky new incr col } if {$NewStyleShowRelayoks} { incr canv_width 200 label .top.relayspersec -text "Relays/s " -foreground "#808000" grid .top.relayspersec -row 0 -column $col -sticky new incr col label .top.relaytime -text "ms/relay " -foreground "#008080" grid .top.relaytime -row 0 -column $col -sticky new incr col } if {$NewStyleShowSenderoks} { incr canv_width 200 label .top.senderspersec -text "Senders/s " -foreground "#808080" grid .top.senderspersec -row 0 -column $col -sticky new incr col label .top.sendertime -text "ms/sender " -foreground "#800080" grid .top.sendertime -row 0 -column $col -sticky new incr col } if {$NewStyleShowRecipoks} { incr canv_width 200 label .top.recipspersec -text "Recips/s " -foreground "#008000" grid .top.recipspersec -row 0 -column $col -sticky new incr col label .top.reciptime -text "ms/recip " -foreground "#000000" grid .top.reciptime -row 0 -column $col -sticky new incr col } grid .top.clabels -row 0 -column $col -sticky nsew grid rowconfigure .top 0 -weight 0 set index 0 foreach m $Machines { grid_machine_new_style $m $index incr index } # If a machine has been deleted, destroy its windows catch { destroy .top.name$index } catch { destroy .top.busy$index } catch { destroy .top.scanspersec$index } catch { destroy .top.scantime$index } catch { destroy .top.relayspersec$index } catch { destroy .top.relaytime$index } catch { destroy .top.senderspersec$index } catch { destroy .top.sendertime$index } catch { destroy .top.recipspersec$index } catch { destroy .top.reciptime$index } catch { destroy .top.c$index } # Bottom row of labels catch { destroy .top.totalrow } catch { destroy .top.busytotal } catch { destroy .top.scanspersectotal } catch { destroy .top.avgscantime } catch { destroy .top.relayspersectotal } catch { destroy .top.avgrelaytime } catch { destroy .top.senderspersectotal } catch { destroy .top.avgsendertime } catch { destroy .top.recipspersectotal } catch { destroy .top.avgreciptime } # Mop up total window if a machine has been deleted set row [expr $index + 1] catch { destroy .top.c$row } set col 2 label .top.totalrow -text "Totals:" label .top.busytotal -foreground "#A00000" grid .top.totalrow -row $row -column 0 -sticky new grid .top.busytotal -row $row -column 1 -sticky new if {$NewStyleShowScans} { label .top.scanspersectotal -foreground "#00A000" grid .top.scanspersectotal -row $row -column $col -sticky new incr col label .top.avgscantime -foreground "#0000A0" grid .top.avgscantime -row $row -column $col -sticky new incr col } if {$NewStyleShowRelayoks} { label .top.relayspersectotal -foreground "#808000" grid .top.relayspersectotal -row $row -column $col -sticky new incr col label .top.avgrelaytime -foreground "#008080" grid .top.avgrelaytime -row $row -column $col -sticky new incr col } if {$NewStyleShowSenderoks} { label .top.senderspersectotal -foreground "#808080" grid .top.senderspersectotal -row $row -column $col -sticky new incr col label .top.avgsendertime -foreground "#800080" grid .top.avgsendertime -row $row -column $col -sticky new incr col } if {$NewStyleShowRecipoks} { label .top.recipspersectotal -foreground "#008000" grid .top.recipspersectotal -row $row -column $col -sticky new incr col label .top.avgreciptime -foreground "#000000" grid .top.avgreciptime -row $row -column $col -sticky new incr col } set num_items [expr $col-1] canvas .top.c$index -width $canv_width -height 60 -takefocus 0 -borderwidth 0 -background #FFFFEE -highlightthickness 0 grid .top.c$index -row $row -column $col -sticky nsew -pady 1 grid rowconfigure .top $row -weight 3 for {set i 0} {$i < $col} {incr i} { grid columnconfigure .top $i -weight 0 } grid columnconfigure .top $col -weight 1 incr index incr row # Now a spot for adding a new machine... catch { destroy .top.newlab } catch { destroy .top.new } catch { destroy .top.all } label .top.newlab -text "Add Machine: " entry .top.new -width 20 grid .top.newlab -row $row -column 0 grid .top.new -row $row -column 1 -columnspan [expr $col - 1] -sticky ew bind .top.new interactive_add_machine button .top.all -text "Summary" -command all_or_summary grid .top.all -row $row -column [expr $col] -sticky w grid rowconfigure .top $row -weight 0 wm deiconify .top } proc all_or_summary {} { set text [.top.all cget -text] set win [get_machine_windows] set rowcol [grid size .top] set rows [lindex $rowcol 1] if {"$text" == "Summary"} { .top.all configure -text "All" foreach w $win { grid remove $w } for {set i 1} {$i < [expr $rows - 2]} {incr i} { grid rowconfigure .top $i -weight 0 } } else { .top.all configure -text "Summary" foreach w $win { grid $w } for {set i 1} {$i < [expr $rows - 2]} {incr i} { grid rowconfigure .top $i -weight 1 } } # Cancel any user-specified geometry wm geometry .top "" } proc reconfigure {} { global Machines global NewStyle if {$NewStyle} { reconfigure_new_style return } set index 0 foreach m $Machines { grid_machine $m $index incr index } # Top row of labels catch { destroy .top.busy } catch { destroy .top.persec } catch { destroy .top.time } catch { destroy .top.c } catch { destroy .top.name } label .top.name -text "Machine Name" label .top.busy -text "Busy Workers" -foreground "#A00000" label .top.persec -text "Msgs/s" -foreground "#00A000" label .top.time -text " ms/scan " -foreground "#0000A0" label .top.c -text "" grid .top.name -row 0 -column 0 -sticky new grid .top.busy -row 0 -column 1 -sticky new grid .top.persec -row 0 -column 2 -sticky new grid .top.time -row 0 -column 3 -sticky new grid .top.c -row 0 -column 4 -sticky new grid rowconfigure .top 0 -weight 0 # If a machine has been deleted, destroy its windows catch { destroy .top.name$index} catch { destroy .top.busy$index} catch { destroy .top.persec$index} catch { destroy .top.time$index} catch { destroy .top.c$index} incr index # Bottom row of labels catch { destroy .top.busytotal } catch { destroy .top.persectotal } catch { destroy .top.avgtime } catch { destroy .top.totalrow } catch { destroy .top.c$index } # Mop up total window if a machine has been deleted set i [expr $index + 1] catch { destroy .top.c$i } label .top.totalrow -text "Totals:" label .top.busytotal label .top.persectotal label .top.avgtime canvas .top.c$index -width 400 -height 60 -takefocus 0 -borderwidth 0 -background #FFFFF0 -highlightthickness 0 grid .top.totalrow -row $index -column 0 -sticky new grid .top.busytotal -row $index -column 1 -sticky new grid .top.persectotal -row $index -column 2 -sticky new grid .top.avgtime -row $index -column 3 -sticky new grid .top.c$index -row $index -column 4 -sticky nsew -pady 1 grid rowconfigure .top $index -weight 3 incr index # Now a spot for adding a new machine... catch { destroy .top.newlab } catch { destroy .top.new } label .top.newlab -text "Add Machine: " entry .top.new -width 20 grid .top.newlab -row $index -column 0 grid .top.new -row $index -column 1 -columnspan 3 -sticky ew bind .top.new interactive_add_machine button .top.all -text "Summary" -command all_or_summary grid .top.all -row $index -column 4 -sticky w grid rowconfigure .top $index -weight 0 grid columnconfigure .top 0 -weight 0 grid columnconfigure .top 1 -weight 0 grid columnconfigure .top 2 -weight 0 grid columnconfigure .top 3 -weight 0 grid columnconfigure .top 4 -weight 1 wm deiconify .top } proc busyworkers { mach } { global ctr global Data incr ctr set w .workers$ctr catch { destroy $w } toplevel $w wm title $w "Busy workers: $mach" wm iconname $w "$mach workers" set Data($mach,busyworkerwin) $w # Open a new SSH connection for the busyworkers info global SSHCommand global MD_MX_CTRL set hmach [host_plus_user $mach] set fp [open "| $SSHCommand $hmach $MD_MX_CTRL" "r+"] fconfigure $fp -blocking 0 fileevent $fp readable [list busyworkers_readable $mach $fp] tickle_busyworkers $mach $fp text $w.t -width 80 -height 35 pack $w.t -side left -expand 1 -fill both $w.t tag bind pid [list enter_pid $w.t] $w.t tag bind pid [list leave_pid $w.t] $w.t tag bind pid [list trace_worker $w.t $mach] } proc tickle_busyworkers { mach fp } { global Data catch { set Data($mach,busydata) "" # We have to use the old command for backware-compatibility. puts $fp "busyslaves\nfoo_no_such_command" flush $fp } } proc busyworkers_readable { mach fp } { global Data gets $fp line if {"$line" == ""} { if {[eof $fp]} { close $fp catch { destroy $Data($mach,busyworkerwin) } } return } if {"$line" != "error: Unknown command"} { lappend Data($mach,busydata) $line return } update_busyworkers $mach $fp global BusyWorkerUpdateInterval after $BusyWorkerUpdateInterval [list tickle_busyworkers $mach $fp] } proc trace_worker { w mach } { global TraceCommand set tags [$w tag names current] set index [lsearch -glob $tags "Z*"] if {$index >= 0} { set tag [lindex $tags $index] set pid [string range $tag 1 end] ssh $mach "$TraceCommand $pid" "Process $pid on $mach" } } proc enter_pid { w } { set tags [$w tag names current] set index [lsearch -glob $tags "Z*"] if {$index >= 0} { set tag [lindex $tags $index] $w tag configure $tag -foreground "#A00000" } } proc leave_pid { w } { set tags [$w tag names current] set index [lsearch -glob $tags "Z*"] if {$index >= 0} { set tag [lindex $tags $index] $w tag configure $tag -foreground "#000000" } } proc compare_workers { a b } { set acmd [lindex $a 3] set bcmd [lindex $b 3] set x [string compare $bcmd $acmd] if {$x != 0} { return $x } set an [lindex $a 0] set bn [lindex $b 0] set aago [lindex $a 5] set bago [lindex $b 5] if {[string match "ago=*" $aago] && [string match "ago=*" $bago]} { set aago [string range $aago 4 end] set bago [string range $bago 4 end] set x [expr $bago - $aago] if {$x != 0} { return $x } } return [expr $an - $bn] } proc update_busyworkers { mach fp} { global Data set w $Data($mach,busyworkerwin) if {![winfo exists $w]} { catch { close $fp } return } $w.t configure -state normal $w.t delete 1.0 end # Clear out tags foreach tag [$w.t tag names] { if {"$tag" != "pid"} { $w.t tag delete $tag } } set busyguys [lsort -command compare_workers $Data($mach,busydata)] set count(scan) 0 set count(relayok) 0 set count(senderok) 0 set count(recipok) 0 foreach line $busyguys { set lst [split $line] set workerno [lindex $lst 0] set pid [lindex $lst 2] set cmd [lindex $lst 3] incr count($cmd) set len [string length "$workerno B $pid "] set line [string range $line $len end] $w.t insert end [format "%4d" $workerno] workerno $w.t insert end " " $w.t tag delete "Z$pid" $w.t insert end [format "%6d" $pid] [list pid "Z$pid"] $w.t insert end " $line\n" } set title "Busy workers: $mach" foreach cmd {scan relayok senderok recipok} { if {$count($cmd) > 0} { set c $count($cmd) append title " $cmd=$c" } } wm title $w $title } proc popup_machine_menu { m index x y} { catch { destroy .m } menu .m -tearoff 0 .m add command -label "SSH" -command [list ssh $m] .m add command -label "Busy Workers" -command [list busyworkers $m] .m add separator .m add command -label "Delete" -command [list del_machine $m] tk_popup .m $x $y } proc grid_machine_new_style { m index } { global NewStyleShowScans NewStyleShowRelayoks NewStyleShowSenderoks NewStyleShowRecipoks set m [lindex $m 0] set disp_m $m if {[regexp {@(.*)$} $m foo host]} { set disp_m $host } # Chop off domain name from host if {[regexp {^([^.]+)\.} $disp_m foo new_m]} { set disp_m $new_m } set row [expr $index + 1] catch { destroy .top.name$index } catch { destroy .top.busy$index } catch { destroy .top.scanspersec$index } catch { destroy .top.scantime$index } catch { destroy .top.relayspersec$index } catch { destroy .top.relaytime$index } catch { destroy .top.senderspersec$index } catch { destroy .top.sendertime$index } catch { destroy .top.recipspersec$index } catch { destroy .top.reciptime$index } catch { destroy .top.c$index } set column 2 set canv_width 200 label .top.name$index -text $disp_m -relief raised bind .top.name$index [list popup_machine_menu $m $index %X %Y] bind .top.name$index [list popup_machine_menu $m $index %X %Y] bind .top.name$index [list popup_machine_menu $m $index %X %Y] label .top.busy$index -text "" -foreground "#A00000" grid .top.name$index -row $row -column 0 -sticky new grid .top.busy$index -row $row -column 1 -sticky new if {$NewStyleShowScans} { label .top.scanspersec$index -foreground "#00A000" grid .top.scanspersec$index -row $row -column $column -sticky new incr column label .top.scantime$index -foreground "#0000A0" grid .top.scantime$index -row $row -column $column -sticky new incr column incr canv_width 200 } if {$NewStyleShowRelayoks} { label .top.relayspersec$index -foreground "#808000" grid .top.relayspersec$index -row $row -column $column -sticky new incr column label .top.relaytime$index -foreground "#008080" grid .top.relaytime$index -row $row -column $column -sticky new incr column incr canv_width 200 } if {$NewStyleShowSenderoks} { label .top.senderspersec$index -foreground "#808080" grid .top.senderspersec$index -row $row -column $column -sticky new incr column label .top.sendertime$index -foreground "#800080" grid .top.sendertime$index -row $row -column $column -sticky new incr column incr canv_width 200 } if {$NewStyleShowRecipoks} { label .top.recipspersec$index -foreground "#008000" grid .top.recipspersec$index -row $row -column $column -sticky new incr column label .top.reciptime$index -foreground "#000000" grid .top.reciptime$index -row $row -column $column -sticky new incr column incr canv_width 200 } canvas .top.c$index -width $canv_width -height 60 -takefocus 0 -borderwidth 0 -background #FFFFFF -highlightthickness 0 grid .top.c$index -row $row -column $column -sticky nsew -pady 1 grid rowconfigure .top $row -weight 1 } proc grid_machine { m index } { set m [lindex $m 0] set row [expr $index + 1] catch { destroy .top.name$index} catch { destroy .top.busy$index} catch { destroy .top.persec$index} catch { destroy .top.time$index} catch { destroy .top.c$index} set disp_m $m if {[regexp {@(.*)$} $m foo host]} { set disp_m $host } label .top.name$index -text $disp_m -relief raised bind .top.name$index [list popup_machine_menu $m $index %X %Y] bind .top.name$index [list popup_machine_menu $m $index %X %Y] bind .top.name$index [list popup_machine_menu $m $index %X %Y] label .top.busy$index -text "" label .top.persec$index -text "" label .top.time$index -text "" canvas .top.c$index -width 600 -height 60 -takefocus 0 -borderwidth 0 -background white -highlightthickness 0 .top.c$index create text 2 2 -anchor nw -text "" -tags statusText grid .top.name$index -row $row -column 0 -sticky new grid .top.busy$index -row $row -column 1 -sticky new grid .top.persec$index -row $row -column 2 -sticky new grid .top.time$index -row $row -column 3 -sticky new grid .top.c$index -row $row -column 4 -sticky nsew -pady 1 grid rowconfigure .top $row -weight 1 } proc kick_off_update {} { global Machines global NewStyle global DoneARedrawSinceLastUpdate global MachinesAwaitingReply global MainUpdateInterval if {$DoneARedrawSinceLastUpdate} { set DoneARedrawSinceLastUpdate 0 if {$NewStyle} { set cmd "rawload1 60" } else { set cmd "rawload" } set MachinesAwaitingReply {} foreach m $Machines { catch { set fp [lindex $m 1] puts $fp $cmd flush $fp lappend MachinesAwaitingReply [lindex $m 0] } } } after $MainUpdateInterval kick_off_update } ## translated from C-code in Blt, who got it from: ## Taken from Paul Heckbert's "Nice Numbers for Graph Labels" in ## Graphics Gems (pp 61-63). Finds a "nice" number approximately ## equal to x. proc nicenum {x floor} { if {$x == 0} { return 0 } set negative 0 if {$x < 0} { set x [expr -$x] set negative 1 } set exponX [expr floor(log10($x))] set fractX [expr $x/pow(10,$exponX)]; # between 1 and 10 if {$floor} { if {$fractX < 2.0} { set nf 1.0 } elseif {$fractX < 3.0} { set nf 2.0 } elseif {$fractX < 4.0} { set nf 3.0 } elseif {$fractX < 5.0} { set nf 4.0 } elseif {$fractX < 10.0} { set nf 5.0 } else { set nf 10.0 } } elseif {$fractX <= 1.0} { set nf 1.0 } elseif {$fractX <= 1.5} { set nf 1.5 } elseif {$fractX <= 2.0} { set nf 2.0 } elseif {$fractX <= 2.5} { set nf 2.5 } elseif {$fractX <= 3.0} { set nf 3.0 } elseif {$fractX <= 4.0} { set nf 4.0 } elseif {$fractX <= 5.0} { set nf 5.0 } elseif {$fractX <= 6.0} { set nf 6.0 } elseif {$fractX <= 8.0} { set nf 8.0 } else { set nf 10.0 } if { $negative } { return [expr -$nf * pow(10,$exponX)] } else { set value [expr $nf * pow(10,$exponX)] return $value } } proc human_number { num } { if {$num <= 1000} { return [strip_zeros [format "%.1f" $num]] } set num [expr $num / 1000.0] if {$num <= 1000} { set num [strip_zeros [format "%.1f" $num]] return "${num}K" } set num [expr $num / 1000.0] if {$num <= 1000} { set num [strip_zeros [format "%.1f" $num]] return "${num}M" } set num [expr $num / 1000.0] set num [strip_zeros [format "%.1f" $num]] return "${num}G" } proc pick_color { host } { set color 0 set components {AA BB CC EE} catch { set host [lindex $host end] } set host [split $host ""] foreach char $host { set color [expr $color + 1] binary scan $char "c" x incr color $x if { $color <= 0 } { set color [expr $x + 1] } } set ans "#" expr srand($color) for {set i 0} {$i < 3} {incr i} { set off [expr int(4.0 * rand())] append ans [lindex $components $off] } return $ans } proc ssh { host {cmd ""} {title ""}} { set color [pick_color $host] if {"$title" == ""} { set title "SSH $host" } global SSHCommand set hmach [host_plus_user $host] exec xterm -hold -title $title -bg #000000 -fg $color -e $SSHCommand $hmach $cmd & } wm withdraw . foreach mach $argv { if {"$mach" == "-archive"} { set DoArchive 1 continue } if {"$mach" == "-d"} { set NewStyleTimeInterval 86400 continue } if {"$mach" == "-h"} { set NewStyleTimeInterval 3600 continue } if {"$mach" == "-n"} { set NewStyle 1 set NewStyleShowScans 1 continue } if {"$mach" == "-r"} { set NewStyle 1 set NewStyleShowRelayoks 1 continue } if {"$mach" == "-s"} { set NewStyle 1 set NewStyleShowSenderoks 1 continue } if {"$mach" == "-t"} { set NewStyle 1 set NewStyleShowRecipoks 1 continue } add_machine $mach } catch { destroy .top} toplevel .top wm title .top "Watch Multiple MIMEDefangs" wm iconname .top "MIMEDefangs" wm withdraw .top reconfigure wm deiconify .top update kick_off_update tkwait window .top exit