public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
[Olefy] New container and image
aea957e9
data/Dockerfiles/olefy/Dockerfile | 14 +++
data/Dockerfiles/olefy/olefy.py | 184 ++++++++++++++++++++++++++++++++++++++
2 files changed, 198 insertions(+)
create mode 100644 data/Dockerfiles/olefy/Dockerfile
create mode 100755 data/Dockerfiles/olefy/olefy.py
Diff
diff --git a/data/Dockerfiles/olefy/Dockerfile b/data/Dockerfiles/olefy/Dockerfile
new file mode 100644
index 00000000..748abc0c
--- /dev/null
+++ b/data/Dockerfiles/olefy/Dockerfile
@@ -0,0 +1,14 @@
+FROM alpine:3.9
+LABEL maintainer "Andre Peters <[email protected]>"
+
+WORKDIR /app
+
+RUN apk add --virtual .build-deps gcc python3-dev musl-dev libffi-dev openssl-dev \
+ && apk add --update --no-cache python3 openssl tzdata libmagic \
+ && pip3 install --upgrade pip \
+ && pip3 install --upgrade oletools asyncio python-magic \
+ && apk del .build-deps
+
+COPY olefy.py /app/
+
+CMD ["python3", "-u", "/app/olefy.py"]
diff --git a/data/Dockerfiles/olefy/olefy.py b/data/Dockerfiles/olefy/olefy.py
new file mode 100755
index 00000000..163775c1
--- /dev/null
+++ b/data/Dockerfiles/olefy/olefy.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019, Dennis Kalbhen <[email protected]>
+# Copyright (c) 2019, Carsten Rosenberg <[email protected]>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+###
+#
+# olefy is a little helper socket to use oletools with rspamd. (https://rspamd.com)
+# Please find updates and issues here: https://github.com/HeinleinSupport/olefy
+#
+###
+
+from subprocess import Popen, PIPE
+import sys
+import os
+import logging
+import asyncio
+import time
+import magic
+
+# merge variables from /etc/olefy.conf and the defaults
+olefy_listen_addr = os.getenv('OLEFY_BINDADDRESS', '127.0.0.1')
+olefy_listen_port = int(os.getenv('OLEFY_BINDPORT', '10050'))
+olefy_tmp_dir = os.getenv('OLEFY_TMPDIR', '/tmp')
+olefy_python_path = os.getenv('OLEFY_PYTHON_PATH', '/usr/bin/python3')
+olefy_olevba_path = os.getenv('OLEFY_OLEVBA_PATH', '/usr/local/bin/olevba3')
+# 10:DEBUG, 20:INFO, 30:WARNING, 40:ERROR, 50:CRITICAL
+olefy_loglvl = int(os.getenv('OLEFY_LOGLVL', 20))
+olefy_min_length = int(os.getenv('OLEFY_MINLENGTH', 500))
+olefy_del_tmp = int(os.getenv('OLEFY_DEL_TMP', 1))
+
+# internal used variables
+request_time = '0000000000.000000'
+olefy_protocol = 'OLEFY'
+olefy_protocol_sep = '\\n\\n'
+olefy_headers = {}
+
+# init logging
+logger = logging.getLogger('olefy')
+logging.basicConfig(stream=sys.stdout, level=olefy_loglvl, format='olefy %(levelname)s %(funcName)s %(message)s')
+
+# log runtime variables
+logger.info('olefy listen address: {}'.format(olefy_listen_addr))
+logger.info('olefy listen port: {}'.format(olefy_listen_port))
+logger.info('olefy tmp dir: {}'.format(olefy_tmp_dir))
+logger.info('olefy python path: {}'.format(olefy_python_path))
+logger.info('olefy olvba path: {}'.format(olefy_olevba_path))
+logger.info('olefy log level: {}'.format(olefy_loglvl))
+logger.info('olefy min file length: {}'.format(olefy_min_length))
+logger.info('olefy delete tmp file: {}'.format(olefy_del_tmp))
+
+if not os.path.isfile(olefy_python_path):
+ logger.critical('python path not found: {}'.format(olefy_python_path))
+ exit(1)
+if not os.path.isfile(olefy_olevba_path):
+ logger.critical('olevba path not found: {}'.format(olefy_olevba_path))
+ exit(1)
+
+# olefy protocol function
+def protocol_split( olefy_line ):
+ header_lines = olefy_line.split('\\n')
+ for line in header_lines:
+ if line == 'OLEFY/1.0':
+ olefy_headers['olefy'] = line
+ elif line != '':
+ kv = line.split(': ')
+ if kv[0] != '' and kv[1] != '':
+ olefy_headers[kv[0]] = kv[1]
+ logger.debug('olefy_headers: {}'.format(olefy_headers))
+
+# calling oletools
+def oletools( stream, tmp_file_name, lid ):
+ if olefy_min_length > stream.__len__():
+ logger.error('{} {} bytes (Not Scanning! File smaller than {!r})'.format(lid, stream.__len__(), olefy_min_length))
+ out = b'[ { "error": "File too small" } ]'
+ else:
+ tmp_file = open(tmp_file_name, 'wb')
+ tmp_file.write(stream)
+ tmp_file.close()
+
+ file_magic = magic.Magic(mime=True, uncompress=True)
+ file_mime = file_magic.from_file(tmp_file_name)
+ logger.info('{} {} (libmagic output)'.format(lid, file_mime))
+
+ # do the olefy
+ cmd_tmp = Popen([olefy_python_path, olefy_olevba_path, '-a', '-j', tmp_file_name], stdout=PIPE, stderr=PIPE)
+ out, err = cmd_tmp.communicate()
+ if out.__len__() < 10:
+ logger.error('{} olevba returned <10 chars - rc: {!r}, response: {!r}'.format(lid,cmd_tmp.returncode, out.decode('ascii')))
+ out = b'[ { "error": "Unhandled oletools response" } ]'
+ if err.__len__() > 10:
+ logger.error('{} olevba stderr >10 chars - rc: {!r}, response: {!r}'.format(lid, cmd_tmp.returncode, err.decode('ascii')))
+ out = b'[ { "error": "Unhandled oletools error" } ]'
+ if cmd_tmp.returncode != 0:
+ logger.error('{} olevba exited with code {!r}; err: {!r}'.format(lid, cmd_tmp.returncode, err.decode('ascii')))
+
+ if olefy_del_tmp == 1:
+ logger.debug('{} {} deleting tmp file'.format(lid, tmp_file_name))
+ os.remove(tmp_file_name)
+
+ logger.debug('{} response: {}'.format(lid, out.decode()))
+ return out
+
+# Asyncio data handling, default AIO-Functions
+class AIO(asyncio.Protocol):
+ def __init__(self):
+ self.extra = bytearray()
+
+ def connection_made(self, transport):
+ global request_time
+ peer = transport.get_extra_info('peername')
+ logger.debug('{} new connection was made'.format(peer))
+ self.transport = transport
+ request_time = str(time.time())
+
+ def data_received(self, request, msgid=1):
+ peer = self.transport.get_extra_info('peername')
+ logger.debug('{} data received from new connection'.format(peer))
+ self.extra.extend(request)
+
+ def eof_received(self):
+ peer = self.transport.get_extra_info('peername')
+ olefy_protocol_err = False
+ proto_ck = str(self.extra[0:2000])
+ if olefy_protocol in proto_ck:
+ olefy_line = proto_ck[12:proto_ck.find(olefy_protocol_sep)]
+ self.extra = bytearray(self.extra[59:len(self.extra)])
+ protocol_split(olefy_line)
+ else:
+ olefy_protocol_err = True
+
+ lid = 'Rspamd-ID' in olefy_headers and '<'+olefy_headers['Rspamd-ID'][:6]+'>' or '<>'
+
+ tmp_file_name = olefy_tmp_dir+'/'+request_time+'.'+str(peer[1])
+ logger.debug('{} {} choosen as tmp filename'.format(lid, tmp_file_name))
+
+ logger.info('{} {} bytes (stream size)'.format(lid, self.extra.__len__()))
+
+ if olefy_protocol_err == True or olefy_headers['olefy'] != 'OLEFY/1.0':
+ logger.error('Protocol ERROR: no OLEFY/1.0 found)')
+ out = b'[ { "error": "Protocol error" } ]'
+ elif 'Method' in olefy_headers:
+ if olefy_headers['Method'] == 'oletools':
+ out = oletools(self.extra, tmp_file_name, lid)
+ else:
+ logger.error('Protocol ERROR: Method header not found')
+ out = b'[ { "error": "Protocol error: Method header not found" } ]'
+
+ self.transport.write(out)
+ logger.info('{} response send: {!r}'.format(peer, out))
+ self.transport.close()
+
+
+# start the listeners
+loop = asyncio.get_event_loop()
+# each client connection will create a new protocol instance
+coro = loop.create_server(AIO, olefy_listen_addr, olefy_listen_port)
+server = loop.run_until_complete(coro)
+logger.info('serving on {}'.format(server.sockets[0].getsockname()))
+
+# XXX serve requests until KeyboardInterrupt, not needed for production
+try:
+ loop.run_forever()
+except KeyboardInterrupt:
+ pass
+
+# graceful shutdown/reload
+server.close()
+loop.run_until_complete(server.wait_closed())
+loop.close()
+logger.info('stopped serving')