@ -14,6 +14,7 @@ import subprocess
import sys
import time
import unittest
from functools import partial
from http . server import HTTPServer , BaseHTTPRequestHandler
import cgi
@ -32,16 +33,13 @@ class TC(testcase.TestCase):
# Prepare a directory to host our test databases
TC . path_www = Path ( TC . path_tmp , ' www ' )
TC . path_www . mkdir ( )
shutil . copy (
str ( TC . path_build / ' unit_tests ' / ' clamav.hdb ' ) ,
str ( TC . path_www ) ,
)
TC . path_db = Path ( TC . path_tmp , ' database ' )
TC . freshclam_pid = Path ( TC . path_tmp , ' freshclam-test.pid ' )
TC . freshclam_config = Path ( TC . path_tmp , ' freshclam-test.conf ' )
TC . mock_mirror_port = 8000
TC . mock_mirror_port = 8001 # Chosen instead of 8000 because CVD-Update tool serves on 8000 by default.
# TODO: Ideally we'd find an open port to use for these tests instead of crossing our fingers.
TC . mock_mirror = None
@classmethod
@ -56,8 +54,19 @@ class TC(testcase.TestCase):
TC . mock_mirror . terminate ( )
TC . mock_mirror = None
if ( TC . path_db / ' freshclam.dat ' ) . exists ( ) :
os . remove ( str ( TC . path_db / ' freshclam.dat ' ) )
# Clear the database directory
try :
shutil . rmtree ( self . path_db )
except Exception :
pass
self . path_db . mkdir ( )
# Clear the www directory
try :
shutil . rmtree ( self . path_www )
except Exception :
pass
self . path_www . mkdir ( )
super ( TC , self ) . tearDown ( )
self . verify_valgrind_log ( )
@ -94,6 +103,12 @@ class TC(testcase.TestCase):
if TC . freshclam_config . exists ( ) :
os . remove ( str ( TC . freshclam_config ) )
# Select database files for test
shutil . copy (
str ( TC . path_build / ' unit_tests ' / ' clamav.hdb ' ) ,
str ( TC . path_www ) ,
)
TC . freshclam_config . write_text ( '''
DatabaseMirror http : / / localhost : { port }
PidFile { freshclam_pid }
@ -242,7 +257,205 @@ class TC(testcase.TestCase):
]
self . verify_output ( output . out , expected = expected_results )
def mock_database_mirror ( handler , port = 8000 ) :
def test_freshclam_05_cdiff_update ( self ) :
self . step_name ( ' Verify that freshclam can update from an older CVD to a newer with CDIFF patches ' )
# start with this CVD
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-1.cvd ' , TC . path_db / ' test.cvd ' )
# update to this CVD
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-6.cvd ' , TC . path_www / ' test.cvd ' )
# using these CDIFFs
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-2.cdiff ' , TC . path_www )
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-3.cdiff ' , TC . path_www )
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-4.cdiff ' , TC . path_www )
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-5.cdiff ' , TC . path_www )
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-6.cdiff ' , TC . path_www )
handler = partial ( WebServerHandler_WWW , TC . path_www )
TC . mock_mirror = Process ( target = mock_database_mirror , args = ( handler , TC . mock_mirror_port ) )
TC . mock_mirror . start ( )
if TC . freshclam_config . exists ( ) :
os . remove ( str ( TC . freshclam_config ) )
TC . freshclam_config . write_text ( '''
DatabaseMirror http : / / localhost : { port }
DNSDatabaseInfo no
PidFile { freshclam_pid }
LogVerbose yes
LogFileMaxSize 0
LogTime yes
DatabaseDirectory { path_db }
DatabaseOwner { user }
''' .format(
freshclam_pid = TC . freshclam_pid ,
path_db = TC . path_db ,
port = TC . mock_mirror_port ,
user = getpass . getuser ( ) ,
) )
command = ' {valgrind} {valgrind_args} {freshclam} --config-file= {freshclam_config} --update-db=test ' . format (
valgrind = TC . valgrind , valgrind_args = TC . valgrind_args , freshclam = TC . freshclam , freshclam_config = TC . freshclam_config
)
output = self . execute_command ( command )
assert output . ec == 0 # success
expected_results = [
' test.cld updated ' ,
]
unexpected_results = [
' already up-to-date '
]
self . verify_output ( output . out , expected = expected_results , unexpected = unexpected_results )
def test_freshclam_06_cdiff_partial_minus_1 ( self ) :
self . step_name ( ' Verify that freshclam can update from an older CVD to a newer with CDIFF patches ' )
# start with this CVD
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-3.cvd ' , TC . path_db / ' test.cvd ' )
# update to this CVD
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-6.cvd ' , TC . path_www / ' test.cvd ' )
# using these CDIFFs
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-4.cdiff ' , TC . path_www )
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-5.cdiff ' , TC . path_www )
#shutil.copy(TC.path_source / 'unit_tests' / 'test_db' / 'test-6.cdiff', TC.path_www) # <-- don't give them the last CDIFF
handler = partial ( WebServerHandler_WWW , TC . path_www )
TC . mock_mirror = Process ( target = mock_database_mirror , args = ( handler , TC . mock_mirror_port ) )
TC . mock_mirror . start ( )
if TC . freshclam_config . exists ( ) :
os . remove ( str ( TC . freshclam_config ) )
TC . freshclam_config . write_text ( '''
DatabaseMirror http : / / localhost : { port }
DNSDatabaseInfo no
PidFile { freshclam_pid }
LogVerbose yes
LogFileMaxSize 0
LogTime yes
DatabaseDirectory { path_db }
DatabaseOwner { user }
''' .format(
freshclam_pid = TC . freshclam_pid ,
path_db = TC . path_db ,
port = TC . mock_mirror_port ,
user = getpass . getuser ( ) ,
) )
command = ' {valgrind} {valgrind_args} {freshclam} --config-file= {freshclam_config} --update-db=test ' . format (
valgrind = TC . valgrind , valgrind_args = TC . valgrind_args , freshclam = TC . freshclam , freshclam_config = TC . freshclam_config
)
output = self . execute_command ( command )
assert output . ec == 0 # success
expected_results = [
' Downloaded 2 patches for test, which is fewer than the 3 expected patches ' ,
' We \' ll settle for this partial-update, at least for now ' ,
' test.cld updated ' ,
]
unexpected_results = [
' already up-to-date '
]
self . verify_output ( output . out , expected = expected_results , unexpected = unexpected_results )
#
# Try again, we should be 1 behind which is tolerable and should not trigger a full CVD download
#
command = ' {valgrind} {valgrind_args} {freshclam} --config-file= {freshclam_config} --update-db=test ' . format (
valgrind = TC . valgrind , valgrind_args = TC . valgrind_args , freshclam = TC . freshclam , freshclam_config = TC . freshclam_config
)
output = self . execute_command ( command )
assert output . ec == 0 # success
expected_results = [
' The database server doesn \' t have the latest patch ' ,
' The server will likely have updated if you check again in a few hours ' ,
]
unexpected_results = [
' test.cld updated ' ,
' test.cvd updated ' ,
]
self . verify_output ( output . out , expected = expected_results , unexpected = unexpected_results )
def test_freshclam_07_cdiff_partial_minus_2 ( self ) :
self . step_name ( ' Verify that freshclam can update from an older CVD to a newer with CDIFF patches ' )
# start with this CVD
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-3.cvd ' , TC . path_db / ' test.cvd ' )
# update to this CVD
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-6.cvd ' , TC . path_www / ' test.cvd ' )
# using these CDIFFs
shutil . copy ( TC . path_source / ' unit_tests ' / ' test_db ' / ' test-4.cdiff ' , TC . path_www )
# shutil.copy(TC.path_source / 'unit_tests' / 'test_db' / 'test-5.cdiff', TC.path_www) <--- dont' give them the second to last, either!
# shutil.copy(TC.path_source / 'unit_tests' / 'test_db' / 'test-6.cdiff', TC.path_www) <--- don't give them the last CDIFF
handler = partial ( WebServerHandler_WWW , TC . path_www )
TC . mock_mirror = Process ( target = mock_database_mirror , args = ( handler , TC . mock_mirror_port ) )
TC . mock_mirror . start ( )
if TC . freshclam_config . exists ( ) :
os . remove ( str ( TC . freshclam_config ) )
TC . freshclam_config . write_text ( '''
DatabaseMirror http : / / localhost : { port }
DNSDatabaseInfo no
PidFile { freshclam_pid }
LogVerbose yes
LogFileMaxSize 0
LogTime yes
DatabaseDirectory { path_db }
DatabaseOwner { user }
''' .format(
freshclam_pid = TC . freshclam_pid ,
path_db = TC . path_db ,
port = TC . mock_mirror_port ,
user = getpass . getuser ( ) ,
) )
command = ' {valgrind} {valgrind_args} {freshclam} --config-file= {freshclam_config} --update-db=test ' . format (
valgrind = TC . valgrind , valgrind_args = TC . valgrind_args , freshclam = TC . freshclam , freshclam_config = TC . freshclam_config
)
output = self . execute_command ( command )
assert output . ec == 0 # success
expected_results = [
' Downloaded 1 patches for test, which is fewer than the 3 expected patches ' ,
' We \' ll settle for this partial-update, at least for now ' ,
' test.cld updated ' ,
]
unexpected_results = [
' already up-to-date '
]
self . verify_output ( output . out , expected = expected_results , unexpected = unexpected_results )
#
# Try again, we should be 2 behind which is NOT tolerable and SHOULD trigger a full CVD download
#
command = ' {valgrind} {valgrind_args} {freshclam} --config-file= {freshclam_config} --update-db=test ' . format (
valgrind = TC . valgrind , valgrind_args = TC . valgrind_args , freshclam = TC . freshclam , freshclam_config = TC . freshclam_config
)
output = self . execute_command ( command )
assert output . ec == 0 # success
expected_results = [
' Incremental update failed, trying to download test.cvd ' ,
' test.cvd updated ' ,
]
unexpected_results = [
' test.cld updated ' ,
]
self . verify_output ( output . out , expected = expected_results , unexpected = unexpected_results )
def mock_database_mirror ( handler , port = 8001 ) :
'''
Process entry point for our HTTP Server to mock a database mirror .
'''
@ -309,3 +522,50 @@ class WebServerHandler_04(BaseHTTPRequestHandler):
Retry later please !
< / body > < / html > '''
self . wfile . write ( page )
class WebServerHandler_WWW ( BaseHTTPRequestHandler ) :
'''
Make an HTTP server handler that has a configurable directory for hosting files .
Server handler to send a CVD header indicating an update is available ,
and then to serve up CDIFFs that should allow the test to do an incremental update .
'''
def __init__ ( self , path_www , * args , * * kwargs ) :
self . path_www = path_www
super ( ) . __init__ ( * args , * * kwargs )
def do_GET ( self ) :
requested_file = self . path_www / self . path . lstrip ( ' / ' )
print ( " Mock Server: Test requested: {} " . format ( requested_file ) )
if not requested_file . exists ( ) :
self . send_error ( 404 , " {} Not Found " . format ( self . path . lstrip ( ' / ' ) ) )
elif ' Range ' in self . headers :
# This will send a CVD header so FreshClam thinks there is an update.
( range_start , range_end ) = self . headers [ ' Range ' ] . split ( ' = ' ) [ - 1 ] . split ( ' - ' )
print ( " Mock Server: But they only want bytes {} through {} ... " . format ( range_start , range_end ) )
with requested_file . open ( ' rb ' ) as the_file :
self . send_response ( 206 ) # Partial file
self . send_header ( ' Content-type ' , ' application/octet-stream ' )
self . end_headers ( )
the_file . seek ( int ( range_start ) )
page = the_file . read ( int ( range_end ) - int ( range_start ) + 1 )
bytes_written = self . wfile . write ( page )
print ( " Mock Server: Sending {} bytes back to client. " . format ( bytes_written ) )
else :
# Send back some whole files
with requested_file . open ( ' rb ' ) as the_file :
self . send_response ( 200 ) # Partial file
self . send_header ( ' Content-type ' , ' application/octet-stream ' )
self . end_headers ( )
page = the_file . read ( )
bytes_written = self . wfile . write ( page )
print ( " Mock Server: Sending {} bytes back to client. " . format ( bytes_written ) )