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
Trace
data/Dockerfiles/olefy/olefy.py
Trace helps you understand code history line by line. See who changed each line, when it changed, and which commit introduced it.
Author
Date
Commit
Line
Code
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
4
# Copyright (c) 2020, Dennis Kalbhen <[email protected]>
5
# Copyright (c) 2020, Carsten Rosenberg <[email protected]>
6
#
7
# Licensed under the Apache License, Version 2.0 (the "License");
8
# you may not use this file except in compliance with the License.
9
# You may obtain a copy of the License at
10
#
11
# http://www.apache.org/licenses/LICENSE-2.0
12
#
13
# Unless required by applicable law or agreed to in writing, software
14
# distributed under the License is distributed on an "AS IS" BASIS,
15
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
# See the License for the specific language governing permissions and
17
# limitations under the License.
19
###
20
#
21
# olefy is a little helper socket to use oletools with rspamd. (https://rspamd.com)
22
# Please find updates and issues here: https://github.com/HeinleinSupport/olefy
23
#
24
###
26
from subprocess import Popen, PIPE
27
import sys
28
import os
29
import logging
30
import asyncio
31
import time
32
import magic
33
import re
35
skip_olefy = os.getenv('SKIP_OLEFY', '')
37
if skip_olefy.lower() in ['yes', 'y']:
38
print("SKIP_OLEFY=y, skipping Olefy...")
39
time.sleep(365 * 24 * 60 * 60)
40
sys.exit(0)
42
# merge variables from /etc/olefy.conf and the defaults
43
olefy_listen_addr_string = os.getenv('OLEFY_BINDADDRESS', '127.0.0.1,::1')
44
olefy_listen_port = int(os.getenv('OLEFY_BINDPORT', '10050'))
45
olefy_tmp_dir = os.getenv('OLEFY_TMPDIR', '/tmp')
46
olefy_python_path = os.getenv('OLEFY_PYTHON_PATH', '/usr/bin/python3')
47
olefy_olevba_path = os.getenv('OLEFY_OLEVBA_PATH', '/usr/local/bin/olevba3')
48
# 10:DEBUG, 20:INFO, 30:WARNING, 40:ERROR, 50:CRITICAL
49
olefy_loglvl = int(os.getenv('OLEFY_LOGLVL', 20))
50
olefy_min_length = int(os.getenv('OLEFY_MINLENGTH', 500))
51
olefy_del_tmp = int(os.getenv('OLEFY_DEL_TMP', 1))
52
olefy_del_tmp_failed = int(os.getenv('OLEFY_DEL_TMP_FAILED', 1))
54
# internal used variables
55
request_time = '0000000000.000000'
56
olefy_protocol = 'OLEFY'
57
olefy_ping = 'PING'
58
olefy_protocol_sep = '\n\n'
59
olefy_headers = {}
61
# init logging
62
logger = logging.getLogger('olefy')
63
logging.basicConfig(stream=sys.stdout, level=olefy_loglvl, format='olefy %(levelname)s %(funcName)s %(message)s')
65
logger.debug('olefy listen address string: {} (type {})'.format(olefy_listen_addr_string, type(olefy_listen_addr_string)))
67
if not olefy_listen_addr_string:
68
olefy_listen_addr = ""
69
else:
70
addr_re = re.compile('[\[" \]]')
71
olefy_listen_addr = addr_re.sub('', olefy_listen_addr_string.replace("'", "")).split(',')
73
# log runtime variables
74
logger.info('olefy listen address: {} (type: {})'.format(olefy_listen_addr, type(olefy_listen_addr)))
75
logger.info('olefy listen port: {}'.format(olefy_listen_port))
76
logger.info('olefy tmp dir: {}'.format(olefy_tmp_dir))
77
logger.info('olefy python path: {}'.format(olefy_python_path))
78
logger.info('olefy olvba path: {}'.format(olefy_olevba_path))
79
logger.info('olefy log level: {}'.format(olefy_loglvl))
80
logger.info('olefy min file length: {}'.format(olefy_min_length))
81
logger.info('olefy delete tmp file: {}'.format(olefy_del_tmp))
82
logger.info('olefy delete tmp file when failed: {}'.format(olefy_del_tmp_failed))
84
if not os.path.isfile(olefy_python_path):
85
logger.critical('python path not found: {}'.format(olefy_python_path))
86
exit(1)
87
if not os.path.isfile(olefy_olevba_path):
88
logger.critical('olevba path not found: {}'.format(olefy_olevba_path))
89
exit(1)
91
# olefy protocol function
92
def protocol_split( olefy_line ):
93
header_lines = olefy_line.split('\n')
94
for line in header_lines:
95
if line == 'OLEFY/1.0':
96
olefy_headers['olefy'] = line
97
elif line != '':
98
kv = line.split(': ')
99
if kv[0] != '' and kv[1] != '':
100
olefy_headers[kv[0]] = kv[1]
101
logger.debug('olefy_headers: {}'.format(olefy_headers))
103
# calling oletools
104
def oletools( stream, tmp_file_name, lid ):
105
if olefy_min_length > stream.__len__():
106
logger.error('{} {} bytes (Not Scanning! File smaller than {!r})'.format(lid, stream.__len__(), olefy_min_length))
107
out = b'[ { "error": "File too small" } ]'
108
else:
109
tmp_file = open(tmp_file_name, 'wb')
110
tmp_file.write(stream)
111
tmp_file.close()
113
file_magic = magic.Magic(mime=True, uncompress=True)
114
file_mime = file_magic.from_file(tmp_file_name)
115
logger.info('{} {} (libmagic output)'.format(lid, file_mime))
117
# do the olefy
118
cmd_tmp = Popen([olefy_python_path, olefy_olevba_path, '-a', '-j' , '-l', 'error', tmp_file_name], stdout=PIPE, stderr=PIPE)
119
out, err = cmd_tmp.communicate()
120
out = bytes(out.decode('utf-8', 'ignore').replace(' ', ' ').replace('\t', '').replace('\n', '').replace('XLMMacroDeobfuscator: pywin32 is not installed (only is required if you want to use MS Excel)', ''), encoding="utf-8")
121
failed = False
122
if out.__len__() < 30:
123
logger.error('{} olevba returned <30 chars - rc: {!r}, response: {!r}, error: {!r}'.format(lid,cmd_tmp.returncode,
124
out.decode('utf-8', 'ignore'), err.decode('utf-8', 'ignore')))
125
out = b'[ { "error": "Unhandled error - too short olevba response" } ]'
126
failed = True
127
elif err.__len__() > 10 and cmd_tmp.returncode == 9:
128
logger.error("{} olevba stderr >10 chars - rc: {!r}, response: {!r}".format(lid, cmd_tmp.returncode, err.decode("utf-8", "ignore")))
129
out = b'[ { "error": "Decrypt failed" } ]'
130
failed = True
131
elif err.__len__() > 10 and cmd_tmp.returncode > 9:
132
logger.error('{} olevba stderr >10 chars - rc: {!r}, response: {!r}'.format(lid, cmd_tmp.returncode, err.decode('utf-8', 'ignore')))
133
out = b'[ { "error": "Unhandled oletools error" } ]'
134
failed = True
135
elif cmd_tmp.returncode != 0:
136
logger.error('{} olevba exited with code {!r}; err: {!r}'.format(lid, cmd_tmp.returncode, err.decode('utf-8', 'ignore')))
137
failed = True
139
if failed and olefy_del_tmp_failed == 0:
140
logger.debug('{} {} FAILED: not deleting tmp file'.format(lid, tmp_file_name))
141
elif olefy_del_tmp == 1:
142
logger.debug('{} {} deleting tmp file'.format(lid, tmp_file_name))
143
os.remove(tmp_file_name)
145
logger.debug('{} response: {}'.format(lid, out.decode('utf-8', 'ignore')))
146
return out + b'\t\n\n\t'
148
# Asyncio data handling, default AIO-Functions
149
class AIO(asyncio.Protocol):
150
def __init__(self):
151
self.extra = bytearray()
153
def connection_made(self, transport):
154
global request_time
155
peer = transport.get_extra_info('peername')
156
logger.debug('{} new connection was made'.format(peer))
157
self.transport = transport
158
request_time = str(time.time())
160
def data_received(self, request, msgid=1):
161
peer = self.transport.get_extra_info('peername')
162
logger.debug('{} data received from new connection'.format(peer))
163
self.extra.extend(request)
165
def eof_received(self):
166
peer = self.transport.get_extra_info('peername')
167
olefy_protocol_err = False
168
proto_ck = self.extra[0:2000].decode('utf-8', 'ignore')
170
headers = proto_ck[0:proto_ck.find(olefy_protocol_sep)]
172
if olefy_protocol == headers[0:5]:
173
self.extra = bytearray(self.extra[len(headers)+2:len(self.extra)])
174
protocol_split(headers)
175
else:
176
olefy_protocol_err = True
178
if olefy_ping == headers[0:4]:
179
is_ping = True
180
else:
181
is_ping = False
182
rspamd_id = olefy_headers['Rspamd-ID'][:6] or ''
183
lid = 'Rspamd-ID' in olefy_headers and '<'+rspamd_id+'>'
184
tmp_file_name = olefy_tmp_dir+'/'+request_time+'.'+str(peer[1])+'.'+rspamd_id
185
logger.debug('{} {} choosen as tmp filename'.format(lid, tmp_file_name))
187
if not is_ping or olefy_loglvl == 10:
188
logger.info('{} {} bytes (stream size)'.format(lid, self.extra.__len__()))
190
if olefy_ping == headers[0:4]:
191
logger.debug('{} PING request'.format(peer))
192
out = b'PONG'
193
elif olefy_protocol_err == True or olefy_headers['olefy'] != 'OLEFY/1.0':
194
logger.error('{} Protocol ERROR: no OLEFY/1.0 found'.format(lid))
195
out = b'[ { "error": "Protocol error" } ]'
196
elif 'Method' in olefy_headers:
197
if olefy_headers['Method'] == 'oletools':
198
out = oletools(self.extra, tmp_file_name, lid)
199
else:
200
logger.error('Protocol ERROR: Method header not found')
201
out = b'[ { "error": "Protocol error: Method header not found" } ]'
203
self.transport.write(out)
204
if not is_ping or olefy_loglvl == 10:
205
logger.info('{} {} response send: {!r}'.format(lid, peer, out))
206
self.transport.close()
209
# start the listeners
210
loop = asyncio.get_event_loop()
211
# each client connection will create a new protocol instance
212
coro = loop.create_server(AIO, olefy_listen_addr, olefy_listen_port)
213
server = loop.run_until_complete(coro)
214
for sockets in server.sockets:
215
logger.info('serving on {}'.format(sockets.getsockname()))
217
# XXX serve requests until KeyboardInterrupt, not needed for production
218
try:
219
loop.run_forever()
220
except KeyboardInterrupt:
221
pass
223
# graceful shutdown/reload
224
server.close()
225
loop.run_until_complete(server.wait_closed())
226
loop.close()
227
logger.info('stopped serving')