#!/usr/bin/perl -w # # University of Illinois/NCSA Open Source License # # Copyright (c) 2005, The Board of Trustees, University of Illinois. # All rights reserved. # # Developed by: Damian Menscher # Imaging Technology Group # Beckman Institute for Advanced Science and Technology # University of Illinois at Urbana-Champaign # http://www.itg.uiuc.edu/ # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the Software), to deal # with the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimers. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimers in the documentation # and/or other materials provided with the distribution. # * Neither the names of Imaging Technology Group, University of Illinois, nor # the names of its contributors may be used to endorse or promote products # derived from this Software without specific prior written permission. # # THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH # THE SOFTWARE. # # ChangeLog # v0.3 - 2005.04.08 - handle stale socket # v0.2 - 2005.03.22 - code cleanup and public release # v0.1 - 2005.02.21 - initial hack use Getopt::Std; use IO::Socket; use MIME::Base64; use strict; my $EXIT_WORKING = 1; my $EXIT_BROKEN = 0; my %options; getopts('hqds:t:L:', \%options); if($options{h}) { print(" Synopsis: clmilter_watch [-q] [-d] [-t timeout] [-s socket] [-L lockfile] -h This help screen -q Quiet mode (don't print status) -d Debug mode (lots of ugly information) -t timeout Seconds to wait for milter response (default: 15) -s socket Path to clamav-milter socket or TCP port -L lockfile Path to clamav-milter lockfile Returns 0 if clamav-milter should be restarted 1 if clamav-milter working, or administratively shut down Recommended cronjob: */15 * * * * root clmilter_watch -q && /etc/init.d/clamav-milter condrestart \n"); exit $EXIT_BROKEN; } my $quiet = $options{q} || 0; my $debug = $options{d} || 0; my $timeout = $options{t} || 15; my $socket = $options{s} || "/var/run/clamav/clmilter.sock"; my $lockfile = $options{L} || "/var/lock/subsys/clamav-milter"; my $ip = "localhost"; if (! -e $lockfile) { !$quiet && print("lockfile missing, clamav-milter not tested\n"); exit $EXIT_WORKING; } my $sock; if ( $socket =~ /^[0-9]+$/ ) { $sock = IO::Socket::INET->new(PeerAddr => $ip, PeerPort => $socket, Proto => 'tcp'); } else { $sock = IO::Socket::UNIX->new(Type => SOCK_STREAM, Timeout => $timeout, Peer => $socket); } if (!$sock) { printf("Couldn't open socket %s\n", $socket); printf("Error was: %s\n", $!); exit $EXIT_BROKEN; } my $relayname = 'localhost.localdomain'; my $relay_ip = '127.0.0.1'; my $message_id = 'clmilter_watch'; my $infected_host = 'infected.invalid'; my $infected_addr = 'virus@infected.invalid'; my $rcpt_addr = 'victim'; my $headers = "Content-Transfer-Encoding: BASE64\n"; my $racie; $racie = '*H+H$!ELIF-TSET-SURIVITNA-DRADNATS-RACIE$}7)CC7)^P(45XZP\4[PA@%P!O5X'; my $eicar = reverse($racie); my $eicar64 = encode_base64($eicar); my $body = "$eicar64\n"; $SIG{ALRM} = sub { printf("clamav-milter didn't respond within %d-second timeout\n", $timeout); exit $EXIT_BROKEN; }; alarm($timeout); # bail out if timeout is reached my $string; $string = "O"; # Option negotiation $string .= "\x00\x00\x00\x02\x00\x00\x00\x1f\x00\x00\x00\x7f"; send_string(); get_response(); $string = "D"; # Define macro $string .= "C"; # Connection information $string .= "j\x00$relayname\x00_\x00$relayname [$relay_ip]\x00"; $string .= "{daemon_name}\x00MTA\x00"; $string .= "{if_name}\x00$relayname\x00{if_addr}\x00$relay_ip\x00"; send_string(); $string = "C"; # Connection information $string .= "$relayname\x004\x86\xb2$relay_ip\x00"; send_string(); get_response(); $string = "D"; # Define macro $string .= "H"; # HELO/EHLO send_string(); $string = "D"; # Define macro $string .= "M"; # MAIL from $string .= "i\x00$message_id\x00{mail_mailer}\x00esmtp\x00"; $string .= "{mail_host}\x00$infected_host.\x00"; $string .= "{mail_addr}\x00$infected_addr\x00"; send_string(); $string = "M"; # MAIL from $string .= "$infected_addr\x00"; send_string(); get_response(); $string = "D"; # Define macro $string .= "R"; # RCPT to $string .= "{rcpt_mailer}\x00local\x00"; $string .= "{rcpt_host}\x00\x00{rcpt_addr}\x00$rcpt_addr\x00"; send_string(); $string = "R"; # RCPT to $string .= "$rcpt_addr\x00"; send_string(); get_response(); $string = "N"; # EOH send_string(); get_response(); $string = "B"; # Body chunk $string .= "$headers\n$body\n"; send_string(); get_response(); $string = "E"; # final body chunk (End) send_string(); my $scan_result = get_response(); while ($scan_result =~ "^[h+]") { # eat headers or extra recipients $scan_result = get_response(); } alarm(0); # disable timer; we got a response $string = "Q"; # QUIT send_string(); # codes specified in sendmail src: .../include/libmilter/mfdef.h if ($scan_result =~ "^([dryq]|-$rcpt_addr)") { # Acceptable actions: !$quiet && print "clamav-milter is working\n"; # Discard, Reject, exit $EXIT_WORKING; # replY, Quarantine } # -recipient print "clamav-milter didn't find the test virus!\n"; exit $EXIT_BROKEN; # Utility functions sub send_string { $sock->print(pack('N',length($string)), $string); } sub get_response { my $bytesNeeded = 4; my $content = ''; my $newBytes; while ($bytesNeeded > 0) { $sock->recv($newBytes, $bytesNeeded); $content .= $newBytes; $bytesNeeded -= length($newBytes); } $debug && printf("Milter returned 0x%x bytes: ", unpack('N', $content)); $bytesNeeded = unpack('N', $content); $content = ''; while ($bytesNeeded > 0) { $sock->recv($newBytes, $bytesNeeded); $content .= $newBytes; $bytesNeeded -= length($newBytes); } $debug && printf("%s\n", $content); return $content; }