def do_contscan(self, directory_argument): directory = os.path.abspath( directory_argument) if directory_argument else '' if not directory or not os.path.exists(directory): logger.error( "Cannot handle CONTSCAN command with argument '%s' (path does not exist)", directory) self.send_response("ERROR: Wrong argument '%s'" % directory) return responses = ["AmavisVTd scan results:"] avt = AmavisVT(self.config) for resource, scan_result in avt.run(directory): if scan_result is None: responses.append("%s: Not scanned by virustotal" % resource) elif isinstance(scan_result, Exception): responses.append("%s: Error (%s)" % (resource, scan_result)) else: if scan_result.infected: matches = [ v['result'] for _, v in scan_result.scans.items() if v['detected'] ][:3] responses.append( "%s: Detected as %s (%s of %s)" % (resource, ', '.join(set(matches)), scan_result.positives, scan_result.total)) else: responses.append("%s: Clean" % resource) payload = '\n'.join(responses) self.request.sendall(payload.encode('utf-8'))
def test_is_included_by_extension(self): avt = AmavisVT(AmavisVTConfigurationParser()) for ext in [ ".exe", ".com", ".bat", ".cmd", ".tar.gz", ".zip", ".tar.bz2", ".tar.7z", ".doc", ".docx", ".docm", ".xls", ".xlsa", ".xlsx", ".xlsm", ".ppt", ".ppta", ".pptx", ".pptm", ".pdf", ".js", ".rtf", ".ttf", ".htm", ".html", ".vbs", ".wsf", "", ]: assert avt.is_included(DummyResource("/tmp/foo%s" % ext)), "Extension '%s' should be included" % ext
def test_report_to_vt_pretend(self, requests_post): avt = AmavisVT( AmavisVTConfigurationParser( {"database-path": ":memory:", "api-key": "my-api-key", "pretend": "true"}, path="/dev/null" ) ) avt.report_to_vt(DummyResource("file1", "application/zip")) assert not requests_post.called
def test_check_vt_pretend(self, requests_post): avt = AmavisVT( AmavisVTConfigurationParser( {"database-path": ":memory:", "api-key": "my-api-key", "pretend": "true"}, path="/dev/null" ) ) result = list(avt.check_vt(None)) assert not requests_post.called assert not result
def do_report(self, filename_argument): filename = os.path.abspath(filename_argument) if not (os.path.exists(filename) and os.path.isfile(filename) and os.access(filename, os.R_OK)): logger.error("File does not exist or is inaccessible: '%s'", filename) self.send_response("ERROR: File does not exist or is inaccessible: '%s'" % filename) return avt = AmavisVT(self.config) result = avt.report_to_vt(Resource(filename, cleanup=False, no_unpack=True)) self.send_response(str(result) if result else "No response")
def test_report_to_vt_pretend(self, requests_post): avt = AmavisVT( AmavisVTConfigurationParser( { 'database-path': ':memory:', 'api-key': 'my-api-key', 'pretend': 'true' }, path='/dev/null')) avt.report_to_vt(DummyResource('file1', 'application/zip')) assert not requests_post.called
def test_is_included_by_extension(self): avt = AmavisVT(AmavisVTConfigurationParser()) for ext in [ '.exe', '.com', '.bat', '.cmd', '.tar.gz', '.zip', '.tar.bz2', '.tar.7z', '.doc', '.docx', '.docm', '.xls', '.xlsa', '.xlsx', '.xlsm', '.ppt', '.ppta', '.pptx', '.pptm', '.pdf', '.js', '.rtf', '.ttf', '.htm', '.html', '.vbs', '.wsf', '' ]: assert avt.is_included(DummyResource( '/tmp/foo%s' % ext)), "Extension '%s' should be included" % ext
def test_check_vt_pretend(self, requests_post): avt = AmavisVT( AmavisVTConfigurationParser( { 'database-path': ':memory:', 'api-key': 'my-api-key', 'pretend': 'true' }, path='/dev/null')) result = list(avt.check_vt(None)) assert not requests_post.called assert not result
def do_report(self, filename_argument): filename = os.path.abspath(filename_argument) if not (os.path.exists(filename) and os.path.isfile(filename) and os.access(filename, os.R_OK)): logger.error("File does not exist or is inaccessible: '%s'", filename) self.send_response( "ERROR: File does not exist or is inaccessible: '%s'" % filename) return avt = AmavisVT(self.config) result = avt.report_to_vt( Resource(filename, cleanup=False, no_unpack=True)) self.send_response(str(result) if result else "No response")
def test_run_with_filename_pattern_detection_match_with_autoreport( self, database_mock, memcached_get_mock, memcached_set_mock, requests_mock ): memcached_get_mock.return_value = None database_mock.filename_pattern_match = mock.MagicMock() database_mock.filename_pattern_match.return_value = True avt = AmavisVT( AmavisVTConfigurationParser( { "database-path": ":memory:", "api-key": "my-api-key", "filename-pattern-detection": "true", "auto-report": "true", }, path="/dev/null", ) ) avt.database = database_mock mail = os.path.join(os.path.dirname(__file__), "samples/mail_with_attachment.eml") result = avt.run(mail) assert database_mock.filename_pattern_match.called call_result = database_mock.filename_pattern_match.call_args assert len(call_result) == 2 # resource and localpart call_args, call_kwargs = call_result # assert that one arg and one kwarg are passed assert len(call_args) == 1 assert len(call_kwargs) == 1 # the first arg must be our resource assert isinstance(call_args[0], Resource) assert call_args[0].filename == "textfile.zip" # the localpart kwarg should be 'alice' assert call_kwargs["localpart"] == "alice" assert requests_mock.called assert requests_mock.call_count == 2 # once for scan report and once for submitting assert len(result) == 1 resource, response = result[0] assert resource.filename == "textfile.zip" assert response.infected assert not any([os.path.exists(p) for p in avt.clean_paths])
def test_run_with_filename_pattern_detection_match_with_autoreport( self, database_mock, memcached_get_mock, memcached_set_mock, requests_mock): memcached_get_mock.return_value = None database_mock.filename_pattern_match = mock.MagicMock() database_mock.filename_pattern_match.return_value = True avt = AmavisVT( AmavisVTConfigurationParser( { 'database-path': ':memory:', 'api-key': 'my-api-key', 'filename-pattern-detection': 'true', 'auto-report': 'true' }, path='/dev/null')) avt.database = database_mock mail = os.path.join(os.path.dirname(__file__), 'samples/mail_with_attachment.eml') result = avt.run(mail) assert database_mock.filename_pattern_match.called call_result = database_mock.filename_pattern_match.call_args assert len(call_result) == 2 # resource and localpart call_args, call_kwargs = call_result # assert that one arg and one kwarg are passed assert len(call_args) == 1 assert len(call_kwargs) == 1 # the first arg must be our resource assert isinstance(call_args[0], Resource) assert call_args[0].filename == 'textfile.zip' # the localpart kwarg should be 'alice' assert call_kwargs['localpart'] == 'alice' assert requests_mock.called assert requests_mock.call_count == 2 # once for scan report and once for submitting assert len(result) == 1 resource, response = result[0] assert resource.filename == 'textfile.zip' assert response.infected assert not any([os.path.exists(p) for p in avt.clean_paths])
def avt(): return AmavisVT( AmavisVTConfigurationParser( { 'database-path': ':memory:', 'api-key': 'my-api-key' }, path='/dev/null'))
def test_is_included_by_mime_type(self): avt = AmavisVT(AmavisVTConfigurationParser()) assert avt.is_included(DummyResource(filename="foo.bar", mime_type="application/octet-stream")) assert avt.is_included(DummyResource(filename="foo.bar", mime_type="application/foobar")) assert avt.is_included(DummyResource(filename="foo.bar", mime_type="text/x-shellscript")) assert avt.is_included(DummyResource(filename="foo.bar", mime_type="text/x-perl")) assert avt.is_included(DummyResource(filename="foo.bar", mime_type="text/x-ruby")) assert avt.is_included(DummyResource(filename="foo.bar", mime_type="text/x-python")) assert not avt.is_included(DummyResource(filename="foo.bar", mime_type="text/plain")) assert not avt.is_included(DummyResource(filename="foo.bar", mime_type="message/rfc822")) assert not avt.is_included(DummyResource(filename="foo.bar", mime_type="image/png"))
def do_contscan(self, directory_argument): directory = os.path.abspath(directory_argument) if directory_argument else "" if not directory or not os.path.exists(directory): logger.error("Cannot handle CONTSCAN command with argument '%s' (path does not exist)", directory) self.send_response("ERROR: Wrong argument '%s'" % directory) return responses = ["AmavisVTd scan results:"] avt = AmavisVT(self.config) for resource, scan_result in avt.run(directory): if scan_result is None: responses.append("%s: Not scanned by virustotal" % resource) elif isinstance(scan_result, Exception): responses.append("%s: Error (%s)" % (resource, scan_result)) else: if scan_result.infected: matches = [v["result"] for _, v in scan_result.scans.items() if v["detected"]][:3] responses.append( "%s: Detected as %s (%s of %s)" % (resource, ", ".join(set(matches)), scan_result.positives, scan_result.total) ) else: responses.append("%s: Clean" % resource) self.request.sendall("\n".join(responses))
def test_is_included_by_mime_type(self): avt = AmavisVT(AmavisVTConfigurationParser()) assert avt.is_included( DummyResource(filename='foo.bar', mime_type='application/octet-stream')) assert avt.is_included( DummyResource(filename='foo.bar', mime_type='application/foobar')) assert avt.is_included( DummyResource(filename='foo.bar', mime_type='text/x-shellscript')) assert avt.is_included( DummyResource(filename='foo.bar', mime_type='text/x-perl')) assert avt.is_included( DummyResource(filename='foo.bar', mime_type='text/x-ruby')) assert avt.is_included( DummyResource(filename='foo.bar', mime_type='text/x-python')) assert not avt.is_included( DummyResource(filename='foo.bar', mime_type='text/plain')) assert not avt.is_included( DummyResource(filename='foo.bar', mime_type='message/rfc822')) assert not avt.is_included( DummyResource(filename='foo.bar', mime_type='image/png'))