From 2efd27e40e6ecc59cc61858906b07be23b1a9fd0 Mon Sep 17 00:00:00 2001 From: andryyy Date: Tue, 25 Jun 2019 18:52:05 +0200 Subject: [PATCH] [Olefy] A new container is born, thanks to @c-rosenberg [ACME] Autoconfig is back (re-added to SAN list by default for all mail domains) [Rspamd] Added comment to composite --- data/Dockerfiles/acme/docker-entrypoint.sh | 2 +- data/Dockerfiles/olefy/Dockerfile | 7 +- data/Dockerfiles/olefy/olefy.py | 184 --------------------- data/conf/rspamd/local.d/composites.conf | 1 + 4 files changed, 8 insertions(+), 186 deletions(-) delete mode 100755 data/Dockerfiles/olefy/olefy.py diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh index ff98aeec..3de8d46e 100755 --- a/data/Dockerfiles/acme/docker-entrypoint.sh +++ b/data/Dockerfiles/acme/docker-entrypoint.sh @@ -244,7 +244,7 @@ while true; do ADDITIONAL_SAN_ARR+=($i) fi done - ADDITIONAL_WC_ARR+=('autodiscover') + ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig') # Start IP detection log_f "Detecting IP addresses... " no_nl diff --git a/data/Dockerfiles/olefy/Dockerfile b/data/Dockerfiles/olefy/Dockerfile index 748abc0c..cb42d054 100644 --- a/data/Dockerfiles/olefy/Dockerfile +++ b/data/Dockerfiles/olefy/Dockerfile @@ -3,12 +3,17 @@ LABEL maintainer "Andre Peters " WORKDIR /app +#RUN addgroup -S olefy && adduser -S olefy -G olefy \ 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/ +ADD https://raw.githubusercontent.com/HeinleinSupport/olefy/master/olefy.py /app/ + +RUN chown -R nobody:nobody /app /tmp + +USER nobody CMD ["python3", "-u", "/app/olefy.py"] diff --git a/data/Dockerfiles/olefy/olefy.py b/data/Dockerfiles/olefy/olefy.py deleted file mode 100755 index 163775c1..00000000 --- a/data/Dockerfiles/olefy/olefy.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# Copyright (c) 2019, Dennis Kalbhen -# Copyright (c) 2019, Carsten Rosenberg -# -# 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') diff --git a/data/conf/rspamd/local.d/composites.conf b/data/conf/rspamd/local.d/composites.conf index c662b5ab..d49dbcf5 100644 --- a/data/conf/rspamd/local.d/composites.conf +++ b/data/conf/rspamd/local.d/composites.conf @@ -20,6 +20,7 @@ SPOOFED_UNAUTH { expression = "!MAILCOW_AUTH & !MAILCOW_WHITE & !R_SPF_ALLOW & !DMARC_POLICY_ALLOW & !ARC_ALLOW & !SIEVE_HOST & MAILCOW_DOMAIN_HEADER_FROM"; score = 5.0; } +# Only apply to inbound unauthed and not whitelisted OLEFY_MACRO { expression = "!MAILCOW_AUTH & !MAILCOW_WHITE & OLETOOLS"; score = 20.0;