proftpd-mod-clamav-0.14~rc2/0002755000175000017500000000000013046071251015560 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/.pc/0002755000175000017500000000000011645313224016242 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/.pc/.version0000644000175000017500000000000211645312514017717 0ustar frankiefrankie2 proftpd-mod-clamav-0.14~rc2/.pc/.quilt_series0000644000175000017500000000000711645312514020747 0ustar frankiefrankieseries proftpd-mod-clamav-0.14~rc2/.pc/.quilt_patches0000644000175000017500000000001711645312514021105 0ustar frankiefrankiedebian/patches proftpd-mod-clamav-0.14~rc2/t/0002755000175000017500000000000013046070743016030 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/t/modules/0002755000175000017500000000000013046070743017500 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/t/modules/mod_clamav/0002755000175000017500000000000013046070743021602 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/t/modules/mod_clamav/sftp.t0000644000175000017500000000035313046070743022742 0ustar frankiefrankie#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; # XXX Start clamd here my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_clamav::sftp"); # XXX Stop clamd here proftpd-mod-clamav-0.14~rc2/t/modules/mod_clamav/scp.t0000644000175000017500000000035213046070743022552 0ustar frankiefrankie#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; # XXX Start clamd here my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_clamav::scp"); # XXX Stop clamd here proftpd-mod-clamav-0.14~rc2/t/modules/mod_clamav.t0000644000175000017500000000114413046070743021765 0ustar frankiefrankie#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; # XXX Start clamd here =pod Example clamd.conf: LogFile /home/tj/tmp/clamd.log LogTime yes LogVerbose yes ExtendedDetectionInfo yes PidFile /home/tj/tmp/clamd.pid TCPSocket 8899 Debug yes Need to generate this config file on the fly, stick the randomly picked port number in the CLAMD_PORT environment variable, and read the PID file to shut the daemon down (using SIGTERM/SIGKILL). =cut my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_clamav"); # XXX Stop clamd here proftpd-mod-clamav-0.14~rc2/t/lib/0002755000175000017500000000000013046070743016576 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/t/lib/ProFTPD/0002755000175000017500000000000013046070743020014 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/t/lib/ProFTPD/Tests/0002755000175000017500000000000013046070743021116 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/t/lib/ProFTPD/Tests/Modules/0002755000175000017500000000000013046070743022526 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/t/lib/ProFTPD/Tests/Modules/mod_clamav/0002755000175000017500000000000013046070743024630 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/t/lib/ProFTPD/Tests/Modules/mod_clamav/sftp.pm0000644000175000017500000002410713046070743026144 0ustar frankiefrankiepackage ProFTPD::Tests::Modules::mod_clamav::sftp; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use POSIX qw(:fcntl_h); use Socket; use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :features :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { clamav_sftp_upload_file_ok => { order => ++$order, test_class => [qw(forking mod_clamav mod_sftp sftp ssh2)], }, clamav_sftp_upload_eicar_fails => { order => ++$order, test_class => [qw(forking mod_clamav mod_sftp sftp ssh2)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { # Check for the required Perl modules: # # Net-SSH2 # Net-SSH2-SFTP my $required = [qw( Net::SSH2 Net::SSH2::SFTP )]; foreach my $req (@$required) { eval "use $req"; if ($@) { print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n"; if ($ENV{TEST_VERBOSE}) { print STDERR "Unable to load $req: $@\n"; } return qw(testsuite_empty_test); } } return testsuite_get_runnable_tests($TESTS); } sub set_up { my $self = shift; $self->SUPER::set_up(@_); # Make sure that mod_sftp does not complain about permissions on the hostkey # files. my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); unless (chmod(0400, $rsa_host_key, $dsa_host_key)) { die("Can't set perms on $rsa_host_key, $dsa_host_key: $!"); } } sub clamav_sftp_upload_file_ok { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/sftp.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/sftp.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sftp.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/sftp.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/sftp.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); # my $clamd_port = $ENV{CLAMD_PORT}; my $clamd_port = 8899; my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 ssh2:20 sftp:20 scp:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, IfModules => { 'mod_clamav.c' => { ClamAV => 'on', ClamServer => '127.0.0.1', ClamPort => $clamd_port, }, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_sftp.c' => [ "SFTPEngine on", "SFTPLog $log_file", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $ssh2 = Net::SSH2->new(); sleep(1); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($user, $passwd)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $fh = $sftp->open('test.dat', O_WRONLY|O_CREAT|O_TRUNC, 0644); unless ($fh) { my ($err_code, $err_name) = $sftp->error(); die("Can't open test.dat: [$err_name] ($err_code)"); } my $count = 20; for (my $i = 0; $i < $count; $i++) { print $fh "ABCD" x 8192; } # To issue the FXP_CLOSE, we have to explicitly destroy the filehandle $fh = undef; $sftp = undef; $ssh2->disconnect(); $self->assert(-f $test_file, test_msg("File '$test_file' does not exist as expected")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub clamav_sftp_upload_eicar_fails { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/sftp.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/sftp.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sftp.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/sftp.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/sftp.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); # my $clamd_port = $ENV{CLAMD_PORT}; my $clamd_port = 8899; my $eicar_file = File::Spec->rel2abs("t/etc/modules/mod_clamav/eicar.dat"); my $eicar_data; if (open(my $fh, "< $eicar_file")) { local $/; $eicar_data = <$fh>; close($fh); } else { die("Can't read $eicar_file: $!"); } my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 ssh2:20 sftp:20 scp:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, IfModules => { 'mod_clamav.c' => { ClamAV => 'on', ClamServer => '127.0.0.1', ClamPort => $clamd_port, }, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_sftp.c' => [ "SFTPEngine on", "SFTPLog $log_file", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $ssh2 = Net::SSH2->new(); sleep(1); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($user, $passwd)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $fh = $sftp->open('test.dat', O_WRONLY|O_CREAT|O_TRUNC, 0644); unless ($fh) { my ($err_code, $err_name) = $sftp->error(); die("Can't open test.dat: [$err_name] ($err_code)"); } print $fh $eicar_data; # To issue the FXP_CLOSE, we have to explicitly destroy the filehandle $fh = undef; $sftp = undef; $ssh2->disconnect(); $self->assert(!-f $test_file, test_msg("File '$test_file' exists unexpectedly")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } 1; proftpd-mod-clamav-0.14~rc2/t/lib/ProFTPD/Tests/Modules/mod_clamav/scp.pm0000644000175000017500000002224713046070743025760 0ustar frankiefrankiepackage ProFTPD::Tests::Modules::mod_clamav::scp; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use POSIX qw(:fcntl_h); use Socket; use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :features :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { clamav_scp_upload_file_ok => { order => ++$order, test_class => [qw(forking mod_clamav mod_sftp scp ssh2)], }, clamav_scp_upload_eicar_fails => { order => ++$order, test_class => [qw(forking mod_clamav mod_sftp scp ssh2)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { # Check for the required Perl modules: # # Net-SSH2 # Net-SSH2-SFTP my $required = [qw( Net::SSH2 Net::SSH2::SFTP )]; foreach my $req (@$required) { eval "use $req"; if ($@) { print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n"; if ($ENV{TEST_VERBOSE}) { print STDERR "Unable to load $req: $@\n"; } return qw(testsuite_empty_test); } } return testsuite_get_runnable_tests($TESTS); } sub set_up { my $self = shift; $self->SUPER::set_up(@_); # Make sure that mod_sftp does not complain about permissions on the hostkey # files. my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); unless (chmod(0400, $rsa_host_key, $dsa_host_key)) { die("Can't set perms on $rsa_host_key, $dsa_host_key: $!"); } } sub clamav_scp_upload_file_ok { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/scp.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/scp.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/scp.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/scp.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/scp.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); # my $clamd_port = $ENV{CLAMD_PORT}; my $clamd_port = 8899; my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 ssh2:20 sftp:20 scp:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, IfModules => { 'mod_clamav.c' => { ClamAV => 'on', ClamServer => '127.0.0.1', ClamPort => $clamd_port, }, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_sftp.c' => [ "SFTPEngine on", "SFTPLog $log_file", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $ssh2 = Net::SSH2->new(); sleep(1); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($user, $passwd)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $ssh2->scp_put($config_file, 'test.dat'); unless ($res) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't upload $config_file to server: [$err_name] ($err_code) $err_str"); } $ssh2->disconnect(); $self->assert(-f $test_file, test_msg("File '$test_file' does not exist as expected")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub clamav_scp_upload_eicar_fails { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/scp.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/scp.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/scp.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/scp.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/scp.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); # my $clamd_port = $ENV{CLAMD_PORT}; my $clamd_port = 8899; my $eicar_file = File::Spec->rel2abs("t/etc/modules/mod_clamav/eicar.dat"); my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, TraceLog => $log_file, Trace => 'DEFAULT:10 ssh2:20 sftp:20 scp:20', AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, IfModules => { 'mod_clamav.c' => { ClamAV => 'on', ClamServer => '127.0.0.1', ClamPort => $clamd_port, }, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_sftp.c' => [ "SFTPEngine on", "SFTPLog $log_file", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $ssh2 = Net::SSH2->new(); sleep(1); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($user, $passwd)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $ssh2->scp_put($eicar_file, 'test.dat'); unless ($res) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't upload $eicar_file to server: [$err_name] ($err_code) $err_str"); } $ssh2->disconnect(); $self->assert(!-f $test_file, test_msg("File '$test_file' exists unexpectedly")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } 1; proftpd-mod-clamav-0.14~rc2/t/lib/ProFTPD/Tests/Modules/mod_clamav.pm0000644000175000017500000002710213046070743025166 0ustar frankiefrankiepackage ProFTPD::Tests::Modules::mod_clamav; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { clamav_upload_file_ok => { order => ++$order, test_class => [qw(forking)], }, clamav_upload_eicar_fails => { order => ++$order, test_class => [qw(forking)], }, clamav_config_maxsize => { order => ++$order, test_class => [qw(bug forking)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } sub clamav_upload_file_ok { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/clamav.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/clamav.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/clamav.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/clamav.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/clamav.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # my $clamd_port = $ENV{CLAMD_PORT}; my $clamd_port = 8899; my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, IfModules => { 'mod_clamav.c' => { ClamAV => 'on', ClamServer => '127.0.0.1', ClamPort => $clamd_port, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1); $client->login($user, $passwd); my $conn = $client->stor_raw('test.dat'); unless ($conn) { die("Failed to STOR: " . $client->response_code() . " " . $client->response_msg()); } my $buf = "Hello, World!\n"; $conn->write($buf, length($buf), 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected; $expected = 226; $self->assert($expected == $resp_code, test_msg("Expected $expected, got $resp_code")); $expected = "Transfer complete"; $self->assert($expected eq $resp_msg, test_msg("Expected '$expected', got '$resp_msg'")); $client->quit(); $self->assert(-f $test_file, test_msg("File '$test_file' does not exist as expected")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub clamav_upload_eicar_fails { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/clamav.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/clamav.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/clamav.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/clamav.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/clamav.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # my $clamd_port = $ENV{CLAMD_PORT}; my $clamd_port = 8899; my $eicar_file = File::Spec->rel2abs('t/etc/modules/mod_clamav/eicar.dat'); my $eicar_data; if (open(my $fh, "< $eicar_file")) { local $/; $eicar_data = <$fh>; close($fh); } else { die("Can't read $eicar_file: $!"); } my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, IfModules => { 'mod_clamav.c' => { ClamAV => 'on', ClamServer => '127.0.0.1', ClamPort => $clamd_port, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1); $client->login($user, $passwd); my $conn = $client->stor_raw('test.dat'); unless ($conn) { die("Failed to STOR: " . $client->response_code() . " " . $client->response_msg()); } my $buf = $eicar_data; $conn->write($buf, length($buf), 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msgs = $client->response_msgs(); my $resp_nmsgs = scalar(@$resp_msgs); my $expected; $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "test.dat: Operation not permitted"; my $resp_msg = $resp_msgs->[$resp_nmsgs-1]; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); $self->assert(!-f $test_file, test_msg("File '$test_file' exists expectedly")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } sub clamav_config_maxsize { my $self = shift; my $tmpdir = $self->{tmpdir}; my $config_file = "$tmpdir/clamav.conf"; my $pid_file = File::Spec->rel2abs("$tmpdir/clamav.pid"); my $scoreboard_file = File::Spec->rel2abs("$tmpdir/clamav.scoreboard"); my $log_file = test_get_logfile(); my $auth_user_file = File::Spec->rel2abs("$tmpdir/clamav.passwd"); my $auth_group_file = File::Spec->rel2abs("$tmpdir/clamav.group"); my $user = 'proftpd'; my $passwd = 'test'; my $group = 'ftpd'; my $home_dir = File::Spec->rel2abs($tmpdir); my $uid = 500; my $gid = 500; # Make sure that, if we're running as root, that the home directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $home_dir)) { die("Can't set perms on $home_dir to 0755: $!"); } unless (chown($uid, $gid, $home_dir)) { die("Can't set owner of $home_dir to $uid/$gid: $!"); } } auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, '/bin/bash'); auth_group_write($auth_group_file, $group, $gid, $user); # my $clamd_port = $ENV{CLAMD_PORT}; my $clamd_port = 8899; my $eicar_file = File::Spec->rel2abs('t/etc/modules/mod_clamav/eicar.dat'); my $eicar_data; if (open(my $fh, "< $eicar_file")) { local $/; $eicar_data = <$fh>; close($fh); } else { die("Can't read $eicar_file: $!"); } my $test_file = File::Spec->rel2abs("$tmpdir/test.dat"); my $config = { PidFile => $pid_file, ScoreboardFile => $scoreboard_file, SystemLog => $log_file, AuthUserFile => $auth_user_file, AuthGroupFile => $auth_group_file, IfModules => { 'mod_clamav.c' => { ClamAV => 'on', ClamServer => '127.0.0.1', ClamPort => $clamd_port, ClamMaxSize => '5 Mb', }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; my ($port, $config_user, $config_group) = config_write($config_file, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 1); $client->login($user, $passwd); my $conn = $client->stor_raw('test.dat'); unless ($conn) { die("Failed to STOR: " . $client->response_code() . " " . $client->response_msg()); } my $buf = $eicar_data; $conn->write($buf, length($buf), 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msgs = $client->response_msgs(); my $resp_nmsgs = scalar(@$resp_msgs); my $expected; $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "test.dat: Operation not permitted"; my $resp_msg = $resp_msgs->[$resp_nmsgs-1]; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); $self->assert(!-f $test_file, test_msg("File '$test_file' exists expectedly")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($config_file, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($pid_file); $self->assert_child_ok($pid); if ($ex) { test_append_logfile($log_file, $ex); unlink($log_file); die($ex); } unlink($log_file); } 1; proftpd-mod-clamav-0.14~rc2/t/etc/0002755000175000017500000000000013046070743016603 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/t/etc/modules/0002755000175000017500000000000013046070743020253 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/t/etc/modules/mod_clamav/0002755000175000017500000000000013046070743022355 5ustar frankiefrankieproftpd-mod-clamav-0.14~rc2/t/etc/modules/mod_clamav/eicar.dat0000644000175000017500000000010513046070743024124 0ustar frankiefrankieX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* proftpd-mod-clamav-0.14~rc2/t/etc/modules/mod_clamav/README0000644000175000017500000000012313046070743023227 0ustar frankiefrankieI obtained the EICAR contents from: http://www.eicar.org/86-0-Intended-use.html proftpd-mod-clamav-0.14~rc2/README0000644000175000017500000000046213046070743016445 0ustar frankiefrankieI've modified the mod_clamav version from: http://www.thrallingpenguin.com/resources/mod_clamav.htm Changes: 1. No patching of the core proftpd code required. 2. Works with SFTP/SCP transfers. 3. Generates event when an infested file is upload; mod_ban can then generate a ban based on this event. proftpd-mod-clamav-0.14~rc2/mod_clamav.h0000644000175000017500000000306413046070743020041 0ustar frankiefrankie/* * mod_clamav - ClamAV virus scanning module for ProFTPD * Copyright (c) 2005-2013, Joseph Benden * * 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. * * Furthermore, Joseph Benden gives permission to link this program with * ClamAV, and distribute the resulting executable, without including the * source code for ClamAV in the source distribution. * * ClamAV is available at http://www.clamav.net/ * * Thanks to TJ Saunders for his helpful comments and suggestions! * * DO NOT EDIT THE LINE BELOW */ #if !defined(MOD_CLAMAV_H) #define MOD_CLAMAV_H /** * Function declarations */ static int clamavd_result(int sockd, const char *abs_filename, const char *rel_filename); static int clamavd_connect_check(int sockd); static int clamavd_scan(int sockd, const char *abs_filename, const char *rel_filename); static int clamavd_connect(void); int clamav_scan(cmd_rec *cmd); #endif proftpd-mod-clamav-0.14~rc2/mod_clamav.c0000644000175000017500000006060213046070743020035 0ustar frankiefrankie/* * mod_clamav - ClamAV virus scanning module for ProFTPD * Copyright (c) 2005-2016, Joseph Benden * Copyright (c) 2012-2013, TJ Saunders * * 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. * * Furthermore, Joseph Benden gives permission to link this program with * ClamAV, and distribute the resulting executable, without including the * source code for ClamAV in the source distribution. * * ClamAV is available at http://www.clamav.net/ * * Thanks to TJ Saunders for his helpful comments and suggestions! * * DO NOT EDIT THE LINE BELOW */ #include "conf.h" #include "privs.h" #include #include #include "mod_clamav.h" /** * Module version and declaration */ #define MOD_CLAMAV_VERSION "mod_clamav/0.14rc2" module clamav_module; /** * Global variables */ static int clamd_sockd = 0, is_remote = 0; static char *clamd_host = NULL; static int clamd_port = 0; static unsigned long long clamd_minsize = 0, clamd_maxsize = 0; static int clam_errno; static int remove_on_failure = 0; static const char *trace_channel = "clamav"; /** * Local declarations */ static unsigned long parse_nbytes(char *nbytes_str, char *units_str); static int clamavd_result(int sockd, const char *abs_filename, const char *rel_filename); static int clamavd_connect_check(int sockd); static int clamavd_scan(int sockd, const char *abs_filename, const char *rel_filename); static int clamavd_connect(void); /** * Read the returned information from Clamavd. */ static int clamavd_result(int sockd, const char *abs_filename, const char *rel_filename) { int infected = 0, waserror = 0, ret; char buff[4096], *pt, *pt1; FILE *fd = 0; (void) pr_trace_msg("clamav", 1, "clamavd_result (sockd %d, abs_filename '%s', rel_filename '%s')", sockd, abs_filename, rel_filename); if ((fd=fdopen(dup(sockd), "r")) == NULL) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: Cant open descriptor for reading: %d", errno); return -1; } memset(buff, '\0', sizeof(buff)); if (fgets(buff, sizeof(buff) - 1, fd)) { if (strstr(buff, "FOUND\n")) { const char *proto; ++infected; pt = strrchr(buff, ':'); if (pt) *pt = 0; /* Delete the infected upload */ /* TODO: delete only the hiddenstore file, not the actual file. */ if ((ret=pr_fsio_unlink(rel_filename))!=0) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": notice: unlink() failed (%d): %s", errno, strerror(errno)); } /* clean up the response */ pt += 2; pt1 = strstr(pt, " FOUND"); if (pt1) { *pt1 = 0; } /* Generate a custom event for any listeners * (e.g. mod_ban) which might be listening. Pass * in the string containing the virus * information. */ pr_event_generate("mod_clamav.virus-found", pt); /* Inform the client the file contained a * virus (only for FTP/FTPS connections.) */ proto = pr_session_get_protocol(0); if (strncmp(proto, "ftp", 3) == 0 || strncmp(proto, "ftps", 4) == 0) { pr_response_send(R_550, "Virus Detected and Removed: %s", pt); } /* Log the fact */ pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": Virus '%s' found in '%s'", pt, abs_filename); } else if (strstr(buff, "ERROR\n") != NULL || strstr(buff, "UNKNOWN COMMAND") != NULL) { char *err = buff, *errend; errend = strstr(err, " ERROR"); if (errend) { *errend = 0; } errend = strstr(err, " UNKNOWN COMMAND"); if (errend) { *errend = 0; } pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": Clamd Error: %s", err); waserror = 1; pr_trace_msg("clamav", 1, "Clamd scanner was not able to function; please check that Clamd is functioning before filing a bug report."); } } fclose(fd); return infected ? infected : (waserror ? -1 : 0); } /** * Test the connection with Clamd. */ static int clamavd_connect_check(int sockd) { FILE *fd = NULL; char buff[32]; (void) pr_trace_msg("clamav", 6, "clamavd_connect_check (sockd %d)", sockd); if (sockd == -1) return 0; if (write(sockd, "PING\n", 5) <= 0) { pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": Clamd did not accept PING (%d): %s", errno, strerror(errno)); close(sockd); clamd_sockd = -1; clam_errno = errno; return 0; } if ((fd = fdopen(dup(sockd), "r")) == NULL) { pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": Clamd can not open descriptor for reading (%d): %s", errno, strerror(errno)); close(sockd); clamd_sockd = -1; clam_errno = errno; return 0; } if (fgets(buff, sizeof(buff), fd)) { if (strstr(buff, "PONG")) { fclose(fd); return 1; } pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": Clamd return unknown response to PING: '%s'", buff); } pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": Clamd did not respond to fgets (%d): %s", errno, strerror(errno)); fclose(fd); close(sockd); clamd_sockd = -1; clam_errno = errno; return 0; } /** * Request Clamavd to perform a scan of the file contents. */ static int clamavd_scan_stream(int sockd, const char *abs_filename, const char *rel_filename) { u_int32_t len = 0; char *buf; size_t bufsz = 4096; long res; FILE *fd; if (!clamavd_connect_check(sockd)) { if ((clamd_sockd = clamavd_connect()) < 0) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: Cannot re-connect to Clamd (%d): %s", errno, strerror(errno)); clam_errno = errno; return -1; } sockd = clamd_sockd; pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": Successfully reconnected to Clamd."); clam_errno = 0; } if (write(sockd, "nINSTREAM\n", 10) <= 0) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": Cannot write to the Clamd socket: %d", errno); clam_errno = errno; return -1; } fd = fopen(rel_filename, "r"); if (!fd) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": Cannot open file '%s' errno=%d.", rel_filename, errno); clam_errno = errno; return -1; } /* rewind file descriptor */ fseek(fd, 0, SEEK_SET); buf = malloc(bufsz); if (!buf) { pr_log_pri(PR_LOG_CRIT, "Out of memory!"); end_login(1); } /* send file contents using protocol defined by Clamd */ while ((res = fread(buf, 1, bufsz, fd)) > 0) { len = htonl(res); pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": Streaming %" PR_LU " bytes (%d, %u) to Clamd.", res, len, sizeof(len)); if (write(sockd, (void *) &len, sizeof(len)) <= 0) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": Cannot write byte count to Clamd socket: %d", errno); clam_errno = errno; fclose(fd); free(buf); return -1; } if (write(sockd, buf, res) != res) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": Cannot stream file to Clamd socket: %d", errno); clam_errno = errno; fclose(fd); free(buf); return -1; } if (feof(fd)) break; } fclose(fd); free(buf); /* send null length byte, to terminate stream */ len = 0; if (write(sockd, (void *) &len, sizeof(len)) <= 0) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": Cannot write termination byte to Clamd socket: %d", errno); clam_errno = errno; return -1; } if (write(sockd, "\n", 1) <= 0) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": Cannot write terminating return. %d", errno); } /* interpret results */ return clamavd_result(sockd, abs_filename, rel_filename); } /** * Request Clamavd to perform a scan. */ static int clamavd_scan(int sockd, const char *abs_filename, const char *rel_filename) { char *scancmd = NULL; scancmd = calloc(strlen(abs_filename) + 20, sizeof(char)); if (!scancmd) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: Cannot allocate memory."); return -1; } (void) pr_trace_msg("clamav", 6, "abs_filename '%s' being scanned.", abs_filename); sprintf(scancmd, "SCAN %s\n", abs_filename); if (!clamavd_connect_check(sockd)) { if ((clamd_sockd = clamavd_connect()) < 0) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: Cannot re-connect to Clamd (%d): %s", errno, strerror(errno)); clam_errno = errno; free(scancmd); scancmd = NULL; return -1; } sockd = clamd_sockd; (void) pr_trace_msg("clamav", 4, "Successfully reconnected to the ClamAV scanner."); clam_errno = 0; } if (write(sockd, scancmd, strlen(scancmd)) <= 0) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: Cannot write to the Clamd socket: %d", errno); free(scancmd); scancmd = NULL; clam_errno = errno; return -1; } free(scancmd); scancmd = NULL; return clamavd_result(sockd, abs_filename, rel_filename); } /** * Connect a socket to ClamAVd. */ static int clamavd_connect(void) { struct sockaddr_un server; struct sockaddr_in server2; struct hostent *he; int sockd, *port; /** * We will set the global socket to non-connected, just in-case. */ clamd_sockd = -1; memset((char*)&server, 0, sizeof(server)); memset((char*)&server2, 0, sizeof(server2)); clamd_host = (char *) get_param_ptr(CURRENT_CONF, "ClamLocalSocket", TRUE); if (!clamd_host) { clamd_host = (char *) get_param_ptr(CURRENT_CONF, "ClamServer", TRUE); if (!clamd_host) { pr_log_pri(PR_LOG_INFO, MOD_CLAMAV_VERSION ": warning: No local socket or server was specified."); return -1; } is_remote = 1; if ((port = (int *) get_param_ptr(CURRENT_CONF, "ClamPort", TRUE)) <= 0) clamd_port = 3310; else clamd_port = *port; (void) pr_trace_msg("clamav", 4, "Connecting to remote ClamAV scanner on host '%s' and port %d.", clamd_host, clamd_port); } else { (void) pr_trace_msg("clamav", 4, "Connecting to local ClamAV scanner on unix socket '%s'.", clamd_host); } PRIVS_ROOT; if (is_remote == 0) { /* Local Socket */ server.sun_family = AF_UNIX; strncpy(server.sun_path, clamd_host, sizeof(server.sun_path)); if ((sockd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { PRIVS_RELINQUISH; pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: Cannot create socket connection to Clamd (%d): %s", errno, strerror(errno)); clam_errno = errno; return -1; } if (connect(sockd, (struct sockaddr *)&server, sizeof(struct sockaddr_un)) < 0) { close(sockd); PRIVS_RELINQUISH; pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: Cannot connect to Clamd (%d): %s", errno, strerror(errno)); clam_errno = errno; return -1; } } else { /* Remote Socket */ server2.sin_family = AF_INET; server2.sin_port = htons(clamd_port); if ((sockd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { PRIVS_RELINQUISH; pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: Cannot create socket connection Clamd (%d): %s", errno, strerror(errno)); clam_errno = errno; return -1; } if ((he = gethostbyname(clamd_host)) == 0) { close(sockd); PRIVS_RELINQUISH; pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: Cannot resolve hostname '%s'", clamd_host); clam_errno = errno; return -1; } server2.sin_addr = *(struct in_addr *) he->h_addr_list[0]; if (connect(sockd, (struct sockaddr *)&server2, sizeof(struct sockaddr_in)) < 0) { close(sockd); PRIVS_RELINQUISH; pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": error: Cannot connect to Clamd (%d): %s", errno, strerror(errno)); clam_errno = errno; return -1; } } PRIVS_RELINQUISH; clam_errno = 0; return sockd; } /** * Entry point of mod_xfer during an upload */ static int clamav_fsio_close(pr_fh_t *fh, int fd) { char *abs_path = NULL, *rel_path = NULL; struct stat st; int do_scan = FALSE, res; config_rec *c = NULL; unsigned long *minsize, *maxsize; /* We're only interested in STOR, APPE, and maybe STOU commands. */ if (session.curr_cmd) { if (strcmp(session.curr_cmd, C_STOR) == 0 || strcmp(session.curr_cmd, C_APPE) == 0 || strcmp(session.curr_cmd, C_STOU) == 0) { do_scan = TRUE; } } if (!do_scan) { return close(fd); } /* Make sure the data is written to disk, so that the fstat(2) picks * up the size properly. */ if (fsync(fd) < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 9, "fsync(2) error on fd %d (path '%s'): %s", fd, fh->fh_path, strerror(xerrno)); errno = xerrno; return -1; } pr_fs_clear_cache(); if (pr_fsio_fstat(fh, &st) < 0) { int xerrno = errno; pr_trace_msg(trace_channel, 9, "pr_fsio_fstat() error on fd %d (path '%s'): %s", fd, fh->fh_path, strerror(xerrno)); errno = xerrno; return -1; } if (close(fd) < 0) { return -1; } c = find_config(CURRENT_CONF, CONF_PARAM, "ClamAV", FALSE); if (!c || !*(int *)(c->argv[0])) { (void) pr_trace_msg("clamav", 8, "skipping ClamAV virus scan."); return 0; } c = find_config(CURRENT_CONF, CONF_PARAM, "ClamFailsafe", FALSE); if (!c || *(int *)(c->argv[0])) remove_on_failure = 1; else remove_on_failure = 0; (void) pr_trace_msg("clamav", 8, "fail-safe mode is %s.", (remove_on_failure ? "ON" : "OFF")); /** * Figure out the absolute path of our directory. */ char buf[PR_TUNABLE_PATH_MAX + 1]; getcwd(buf, PR_TUNABLE_PATH_MAX); abs_path = fh->fh_path; if (abs_path) { (void) pr_trace_msg("clamav", 8, "vwd=%s fh_path=%s chroot=%s cwd=%s buf=%s", pr_fs_getvwd(), abs_path, session.chroot_path, pr_fs_getcwd(), buf); if (strcmp(buf, pr_fs_getcwd()) != 0) { if (strcmp(pr_fs_getcwd(), "/") != 0) { char *pos = strstr(buf, pr_fs_getcwd()); if (pos) { *pos = 0; } } abs_path = pdircat(fh->fh_pool, buf, abs_path, NULL); } else if (strcmp(buf, pr_fs_getcwd()) == 0 && session.chroot_path) abs_path = pdircat(fh->fh_pool, session.chroot_path, abs_path, NULL); } rel_path = pstrdup(fh->fh_pool, fh->fh_path); (void) pr_trace_msg("clamav", 6, "absolute path is '%s' and relative path is '%s'.", abs_path, rel_path); /** * Handle min/max settings */ if ((minsize = (unsigned long *) get_param_ptr(CURRENT_CONF, "ClamMinSize", TRUE)) == 0UL) clamd_minsize = 0; else clamd_minsize = *minsize; if ((maxsize = (unsigned long *) get_param_ptr(CURRENT_CONF, "ClamMaxSize", TRUE)) == 0UL) clamd_maxsize = 0; else clamd_maxsize = *maxsize; (void) pr_trace_msg("clamav", 6, "ClamMinSize=%" PR_LU " ClamMaxSize=%" PR_LU " Filesize=%" PR_LU, clamd_minsize, clamd_maxsize, (pr_off_t) st.st_size); if (clamd_minsize > 0) { /* test the minimum size */ if (st.st_size < clamd_minsize) { pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": File is too small, skipping virus scan. min = %" PR_LU " size = %" PR_LU, clamd_minsize, (pr_off_t) st.st_size); return 0; } } if (clamd_maxsize > 0) { /* test the maximum size */ if (st.st_size > clamd_maxsize) { pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": File is too large, skipping virus scan. max = %" PR_LU " size = %" PR_LU, clamd_maxsize, (pr_off_t) st.st_size); return 0; } } (void) pr_trace_msg("clamav", 1, "Going to virus scan absolute filename of '%s' and a relative filename of '%s'.", abs_path, rel_path); clam_errno = 0; c = find_config(CURRENT_CONF, CONF_PARAM, "ClamStream", FALSE); if (c && *(int *)(c->argv[0])) { res = clamavd_scan_stream(clamd_sockd, abs_path, rel_path); } else { res = clamavd_scan(clamd_sockd, abs_path, rel_path); } if (res < 0) { if (remove_on_failure) { pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": removing failed upload of filename = '%s' with relative filename = '%s'.", abs_path, rel_path); if (clam_errno && pr_fsio_unlink(rel_path)!=0) { pr_log_pri(PR_LOG_ERR, MOD_CLAMAV_VERSION ": notice : unlink() failed (%d): %s", errno, strerror(errno)); } } errno = EPERM; return -1; } if (clam_errno == 0) pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": No virus detected in filename = '%s'.", abs_path); else pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": Skipped virus scan due to errno = %d", clam_errno); return 0; } /** * Parse string size description and return value. */ static unsigned long parse_nbytes(char *nbytes_str, char *units_str) { long res; unsigned long nbytes; char *endp = NULL; float units_factor = 0.0; /* clear any previous local errors */ clam_errno = 0; /* first, check the given units to determine the correct multiplier */ if (!strcasecmp("Gb", units_str)) { units_factor = 1024.0 * 1024.0 * 1024.0; } else if (!strcasecmp("Mb", units_str)) { units_factor = 1024.0 * 1024.0; } else if (!strcasecmp("Kb", units_str)) { units_factor = 1024.0; } else if (!strcasecmp("b", units_str)) { units_factor = 1.0; } else { clam_errno = EINVAL; return 0; } /* make sure a number was given */ if (!isdigit((int) *nbytes_str)) { clam_errno = EINVAL; return 0; } /* knowing the factor, now convert the given number string to a real * number */ res = strtol(nbytes_str, &endp, 10); if (errno == ERANGE) { clam_errno = ERANGE; return 0; } if (endp && *endp) { clam_errno = EINVAL; return 0; } /* don't bother to apply the factor if that will cause the number to * overflow */ if (res > (ULONG_MAX / units_factor)) { clam_errno = ERANGE; return 0; } nbytes = (unsigned long) res * units_factor; return nbytes; } /** * Configuration setter: ClamAV */ MODRET set_clamav(cmd_rec *cmd) { int bool = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_LIMIT|CONF_VIRTUAL|CONF_GLOBAL|CONF_DIR); if ((bool = get_boolean(cmd,1)) == -1) CONF_ERROR(cmd, "expected Boolean parameter"); c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(unsigned char)); *((unsigned char *) c->argv[0]) = bool; c->flags |= CF_MERGEDOWN; return PR_HANDLED(cmd); } /** * Configuration setter: ClamStream */ MODRET set_clamstream(cmd_rec *cmd) { int bool = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_LIMIT|CONF_VIRTUAL|CONF_GLOBAL|CONF_DIR); if ((bool = get_boolean(cmd,1)) == -1) CONF_ERROR(cmd, "expected Boolean parameter"); c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(unsigned char)); *((unsigned char *) c->argv[0]) = bool; c->flags |= CF_MERGEDOWN; return PR_HANDLED(cmd); } /** * Configuration setter: ClamFailsafe */ MODRET set_clamfailsafe(cmd_rec *cmd) { int bool = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_LIMIT|CONF_VIRTUAL|CONF_GLOBAL|CONF_DIR); if ((bool = get_boolean(cmd, 1)) == -1) CONF_ERROR(cmd, "expected Boolean parameter"); c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(unsigned char)); *((unsigned char *) c->argv[0]) = bool; c->flags |= CF_MERGEDOWN; return PR_HANDLED(cmd); } /** * Configuration setter: ClamLocalSocket */ MODRET set_clamavd_local_socket(cmd_rec *cmd) { config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_DIR); c = add_config_param_str("ClamLocalSocket", 1, (void *) cmd->argv[1]); c->flags |= CF_MERGEDOWN; return PR_HANDLED(cmd); } /** * Configuration setter: ClamServer */ MODRET set_clamavd_server(cmd_rec *cmd) { config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_DIR); c = add_config_param_str("ClamServer", 1, (void *) cmd->argv[1]); c->flags |= CF_MERGEDOWN; return PR_HANDLED(cmd); } /** * Configuration setter: ClamPort */ MODRET set_clamavd_port(cmd_rec *cmd) { config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_DIR); c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = (int) atol(cmd->argv[1]); c->flags |= CF_MERGEDOWN; return PR_HANDLED(cmd); } /** * Configuration setter: ClamMinSize */ MODRET set_clamavd_minsize(cmd_rec *cmd) { config_rec *c = NULL; unsigned long nbytes = 0; CHECK_ARGS(cmd, 2); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_DIR); if ((nbytes = parse_nbytes(cmd->argv[1], cmd->argv[2])) == 0) { char ulong_max[80] = {'\0'}; sprintf(ulong_max, "%lu", (unsigned long) ULONG_MAX); if (clam_errno == EINVAL) CONF_ERROR(cmd, "invalid parameters"); if (clam_errno == ERANGE) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "number of bytes must be between 0 and ", ulong_max, NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(unsigned long)); *((unsigned long *) c->argv[0]) = nbytes; c->flags |= CF_MERGEDOWN; return PR_HANDLED(cmd); } /** * Configuration setter: ClamMaxSize */ MODRET set_clamavd_maxsize(cmd_rec *cmd) { config_rec *c = NULL; unsigned long nbytes = 0; CHECK_ARGS(cmd, 2); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_DIR); if ((nbytes = parse_nbytes(cmd->argv[1], cmd->argv[2])) == 0) { char ulong_max[80] = {'\0'}; sprintf(ulong_max, "%lu", (unsigned long) ULONG_MAX); if (clam_errno == EINVAL) CONF_ERROR(cmd, "invalid parameters"); if (clam_errno == ERANGE) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "number of bytes must be between 0 and ", ulong_max, NULL)); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(unsigned long)); *((unsigned long *) c->argv[0]) = nbytes; c->flags |= CF_MERGEDOWN; return PR_HANDLED(cmd); } /** * End FTP Session */ static void clamav_shutdown(const void *event_data, void *user_data) { if (clamd_sockd != -1) { close(clamd_sockd); clamd_sockd = -1; pr_log_debug(DEBUG4, MOD_CLAMAV_VERSION ": debug: disconnected from Clamd"); } } /** * Start FTP Session */ static int clamav_sess_init(void) { pr_fs_t *fs; is_remote = 0; clamd_sockd = -1; pr_event_register(&clamav_module, "core.exit", clamav_shutdown, NULL); fs = pr_register_fs(session.pool, "clamav", "/"); if (fs) { fs->close = clamav_fsio_close; } return 0; } static conftable clamav_conftab[] = { { "ClamAV", set_clamav, NULL }, { "ClamFailsafe", set_clamfailsafe, NULL }, { "ClamLocalSocket", set_clamavd_local_socket, NULL }, { "ClamServer", set_clamavd_server, NULL }, { "ClamPort", set_clamavd_port, NULL }, { "ClamMinSize", set_clamavd_minsize, NULL }, { "ClamMaxSize", set_clamavd_maxsize, NULL }, { "ClamStream", set_clamstream, NULL }, { NULL } }; module clamav_module = { NULL, /* Always NULL */ NULL, /* Always NULL */ 0x20, /* module api version */ "clamav", /* module name */ clamav_conftab, /* module configuration handler table */ NULL, /* module command handler table */ NULL, /* module authentication handler table */ NULL, /* module initialization */ clamav_sess_init, /* session initialization */ MOD_CLAMAV_VERSION /* module version */ };