diff --git a/ckanext/odsh/lib/__init__.py b/ckanext/odsh/lib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ckanext/odsh/lib/odsh_icap_client.cfg b/ckanext/odsh/lib/odsh_icap_client.cfg new file mode 100644 index 0000000000000000000000000000000000000000..654b57a83fc705ebff6769c89035df98f583432a --- /dev/null +++ b/ckanext/odsh/lib/odsh_icap_client.cfg @@ -0,0 +1,9 @@ +[DEFAULT] +# the IP of the ICAP-Server +host = 10.61.127.77 + +# The port of the ICAP-Server +port = 1344 + +# the IP of the client-machine +clientip = 127.0.0.1 diff --git a/ckanext/odsh/lib/odsh_icap_client.py b/ckanext/odsh/lib/odsh_icap_client.py new file mode 100644 index 0000000000000000000000000000000000000000..d781aafeecab8e7fc8cef34d391207c394d656bb --- /dev/null +++ b/ckanext/odsh/lib/odsh_icap_client.py @@ -0,0 +1,206 @@ +import socket +import sys +import time +import ConfigParser + + +class ODSHICAPRequest(object): + + def __init__(self, FILENAME, FILEBUFF, cfg_file='odsh_icap_client.cfg'): + config = ConfigParser.ConfigParser() + config.read(cfg_file) + self.HOST = '10.61.127.77' + self.PORT = 1344 + self.CLIENTIP = '127.0.0.1' + self.FILENAME = FILENAME + self.FILEBUFF = FILEBUFF + + def send(self): + print("----- Starting ICAP-Request via RESPMOD -----") + + # socket connect + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as msg: + sys.stderr.write("[ERROR] %s\n" % msg[1]) + sys.exit(1) + + try: + sock.connect((self.HOST, self.PORT)) + except socket.error as msg: + sys.stderr.write("[ERROR] %s\n" % msg[1]) + sys.exit(2) + + # create and send header + header = self._get_icap_header(self.FILENAME, self.HOST, self.PORT, self.CLIENTIP).encode() + sock.send(header) + + # send file and terminating signal + self._sendfile(self.FILEBUFF, sock) + sock.send('0\r\n\r\n') + + # fetch and parse the response + data_response = self._recvall(sock) + response_object = self._parse_response(data_response) + + print("----- Finished ICAP-Request via RESPMOD -----") + + return response_object + + def _get_icap_header(self, fileName, host, port, clientIP): + uniqueInt = time.time() # used to generate "unique" int for disabling cache + + icapRequest = 'RESPMOD' + ' ' + 'icap://' + host + ':' + str(port) + '/RESPMOD' + \ + ' ICAP/1.0\r\n' + 'Host: ' + host + ':' + str(port) + '\r\n' + icapRequest += 'Allow: 204\r\n' + icapRequest += 'X-Client-IP: ' + clientIP + '\r\n' + + httpRequest = "GET http://" + clientIP + "/" + str(uniqueInt).replace('.', '_') + "/" + \ + fileName + ' HTTP/1.1\r\nHost: ' + clientIP + '\r\n\r\n' + + httpResponse = 'HTTP/1.1 200 OK\r\n' + httpResponse += 'Transfer-Encoding: chunked\r\n' + httpResponse += '\r\n' + + httpRequestLength = len(httpRequest) + httpResponseLength = len(httpResponse) + + icapRequest += 'Encapsulated: req-hdr=0, res-hdr=' + str(httpRequestLength) + ', res-body=' + \ + str(httpRequestLength + httpResponseLength) + '\r\n\r\n' + httpRequest + httpResponse; + + return icapRequest + + def _sendfile(self, fileBuffer, sock): + print('start sending file') + PACK_SIZE = 1024 # in bytes + + l = fileBuffer.read(PACK_SIZE) + while(l): + print('sending %d bytes of data...' % len(l)) + sock.send('{:02X}'.format(len(l)).encode()) + sock.send("\r\n".encode()) + sock.send(l) + sock.send("\r\n".encode()) + l = fileBuffer.read(PACK_SIZE) + + def _sendfile_old(self, fileName, sock): + print('start sending file') + PACK_SIZE = 1024 # in bytes + + with open(fileName) as f: + l = f.read(PACK_SIZE) + while(l): + print('sending %d bytes of data...' % len(l)) + sock.send('{:02X}'.format(len(l)).encode()) + sock.send("\r\n".encode()) + sock.send(l) + sock.send("\r\n".encode()) + l = f.read(PACK_SIZE) + print('done sending') + + def _recvall(self, sock): + print('receiving response from icap server') + BUFF_SIZE = 4096 # 4 KiB + data = b'' + while True: + part = sock.recv(BUFF_SIZE) + data += part + if len(part) < BUFF_SIZE: + # either 0 or end of data + break + return data + + def _parse_response(self, data_response): + print('parsing response') + lines = data_response.split('\r\n') + http_status_code = self._parse_response_http_statuscode(lines) + http_block = self._parse_block(lines, 'HTTP/1.1') + icap_block = self._parse_block(lines, 'ICAP/1.0') + + response_object = ODSHParsedICAPResponse(data_response, http_status_code, http_block, icap_block) + return response_object + + def _parse_response_http_statuscode(self, data_response_lines): + http_status_code_found = False + http_status_code = None + for line in data_response_lines: + if line.startswith('HTTP/1.1'): + http_status_code = int(line.split(' ')[1]) # example: HTTP/1.1 403 VirusFound + http_status_code_found = True + + if not http_status_code_found: + http_status_code = 200 # if no virus is found, no http_status_code is given, defaulting to 200 OK + + return http_status_code + + def _parse_block(self, data_response_lines, block_start_signal): + block_data = None + in_block = False + + for line in data_response_lines: + if line.startswith(block_start_signal): + in_block = True + block_data = '' + if in_block and not len(line): + in_block = False + break + if in_block: + block_data += line + '\r\n' + + return block_data + + +class ODSHParsedICAPResponse(object): + + def __init__(self, full_response, http_status_code, http_block, icap_block): + self.full_response = full_response + self.http_status_code = http_status_code + self.http_block = http_block + self.icap_block = icap_block + + def virus_found(self): + if (self.http_status_code != 200) and (self.http_status_code != 403): + raise UnknownResponseException('Received an unknown http response code: %d' % self.http_status_code) + return self.http_status_code != 200 + + +class UnknownResponseException(Exception): + pass + + +def example_print_response(response_object): + print('') + print('Example output of response_object:') + print('') + + #print('Full ICAP-Response: ') + #print(response_object.full_response) + #print('') + + print('HTTP-Status-Code (explicit or implied):') + print(response_object.http_status_code) + print('') + + print('HTTP-Block:') + print(response_object.http_block) + print('') + + print('ICAP-Block:') + print(response_object.icap_block) + print('') + + print('Virus found?') + print(response_object.virus_found()) + print('') + + +if __name__ == "__main__": + + # example file with virus + FILENAME = 'test_files/eicar.txt' + + # example file without virus + #FILENAME = 'test_files/lorem-ipsum.pdf' + + odsh_parsed_icap_response = ODSHICAPRequest(FILENAME).send() + example_print_response(odsh_parsed_icap_response) diff --git a/ckanext/odsh/lib/uploader.py b/ckanext/odsh/lib/uploader.py new file mode 100644 index 0000000000000000000000000000000000000000..21558bd2218ce45b61e71835d64f918c50ea56a1 --- /dev/null +++ b/ckanext/odsh/lib/uploader.py @@ -0,0 +1,21 @@ +import ckan.logic as logic +from ckan.lib.uploader import ResourceUpload +from odsh_icap_client import ODSHICAPRequest + +import logging + +log = logging.getLogger(__name__) + +ValidationError = logic.ValidationError + +class ODSHResourceUpload(ResourceUpload): + + def __init__(self, resource): + super(ODSHResourceUpload, self).__init__(resource) + if self._icap_virus_found(): + raise ValidationError(['Virus gefunden']) + + def _icap_virus_found(self): + response_object = ODSHICAPRequest(self.filename, self.upload_file).send() + return response_object.virus_found() + diff --git a/ckanext/odsh/plugin.py b/ckanext/odsh/plugin.py index eb1454f99b43c5b63efe939d7865cd07273a43d0..1a525e5779f5e6fb4ce862e6153c282eb2104243 100644 --- a/ckanext/odsh/plugin.py +++ b/ckanext/odsh/plugin.py @@ -4,6 +4,7 @@ import ckan.plugins.toolkit as toolkit from ckan.lib.plugins import DefaultTranslation from ckan.lib.plugins import DefaultDatasetForm from ckan.common import OrderedDict +from ckanext.odsh.lib.uploader import ODSHResourceUpload import ckan.lib.helpers as helpers import helpers as odsh_helpers from routes.mapper import SubMapper @@ -61,6 +62,13 @@ def odsh_group_id_selected(selected, group_id): return False +class OdshIcapPlugin(plugins.SingletonPlugin): + plugins.implements(plugins.IUploader, inherit=True) + + def get_resource_uploader(self, data_dict): + return ODSHResourceUpload(data_dict) + + class OdshPlugin(plugins.SingletonPlugin, DefaultTranslation, DefaultDatasetForm): plugins.implements(plugins.IConfigurer) plugins.implements(plugins.ITemplateHelpers) diff --git a/setup.py b/setup.py index 68cb9f7d10456a451ee9250ca59c8f2a8037038f..22cb91a1d3710cef8488e3945616f7bcbc0d6562 100755 --- a/setup.py +++ b/setup.py @@ -81,12 +81,13 @@ setup( entry_points=''' [ckan.plugins] odsh=ckanext.odsh.plugin:OdshPlugin + odsh_icap=ckanext.odsh.plugin:OdshIcapPlugin statistikamtnord_harvester=ckanext.odsh.harvesters:StatistikamtNordHarvester kiel_harvester=ckanext.odsh.harvesters:KielHarvester - + [paste.paster_command] odsh_initialization = ckanext.odsh.commands.initialization:Initialization - + [babel.extractors] ckan = ckan.lib.extract:extract_ckan ''',