diff --git a/cowrie/cowrie.cfg.example b/cowrie/cowrie.cfg.example
new file mode 100644
index 0000000000000000000000000000000000000000..88640533ab43a0f1938a2df174f0677f68700ffc
--- /dev/null
+++ b/cowrie/cowrie.cfg.example
@@ -0,0 +1,103 @@
+# ============================================================================
+# General Cowrie Options
+# ============================================================================
+[honeypot]
+
+sensor_name= hugo.example.org
+hostname = dbsrv-07.accounting
+log_path = var/log
+state_path = var/lib
+download_path = ${honeypot:state_path}/downloads
+share_path = share/cowrie
+etc_path = etc
+contents_path = honeyfs
+txtcmds_path = ${honeypot:share_path}/txtcmds
+download_limit_size = 10485760
+ttylog = false
+ttylog_path = ${honeypot:state_path}/tty
+interactive_timeout = 180
+authentication_timeout = 120
+backend = shell
+timezone = UTC
+auth_class = UserDB
+
+# ============================================================================
+# Shell Options
+# Options around Cowrie's Shell Emulation
+# ============================================================================
+[shell]
+
+filesystem = ${honeypot:share_path}/fs.pickle
+processes = ${honeypot:share_path}/cmdoutput.json
+arch = linux-x64-lsb
+kernel_version = 3.2.0-4-amd64
+kernel_build_string = #1 SMP Debian 3.2.68-1+deb7u1
+hardware_platform = x86_64
+operating_system = GNU/Linux
+ssh_version = OpenSSH_7.9p1, OpenSSL 1.1.1a  20 Nov 2018
+
+# ============================================================================
+# SSH Specific Options
+# ============================================================================
+[ssh]
+
+enabled = true
+rsa_public_key = ${honeypot:state_path}/ssh_host_rsa_key.pub
+rsa_private_key = ${honeypot:state_path}/ssh_host_rsa_key
+dsa_public_key = ${honeypot:state_path}/ssh_host_dsa_key.pub
+dsa_private_key = ${honeypot:state_path}/ssh_host_dsa_key
+version = SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2
+ciphers = aes128-ctr,aes192-ctr,aes256-ctr,aes256-cbc,aes192-cbc,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc
+macs = hmac-sha2-512,hmac-sha2-384,hmac-sha2-56,hmac-sha1,hmac-md5
+compression = zlib@openssh.com,zlib,none
+listen_endpoints = systemd:domain=INET6:index=0 systemd:domain=INET6:index=1
+sftp_enabled = true
+forwarding = false
+forward_redirect = false
+forward_tunnel = false
+auth_none_enabled = false
+auth_keyboard_interactive_enabled = false
+
+# ============================================================================
+# Telnet Specific Options
+# ============================================================================
+[telnet]
+
+enabled = false
+
+# ============================================================================
+# Output Plugins
+# These provide an extensible mechanism to send audit log entries to third
+# parties. The audit entries contain information on clients connecting to
+# the honeypot.
+# 
+# Output entries need to start with 'output_' and have the 'enabled' entry.
+# ============================================================================
+
+# JSON based logging module
+#
+
+[output_jsonlog]
+
+enabled = false
+
+# Wardefiler logging module
+#
+
+[output_wardenfiler]
+
+enabled = true
+detector_name = org.example.hugo.cowrie
+resolve_nat = no
+#reported_public_ipv4 =
+#reported_public_ipv6 =
+reported_ssh_port = 22
+#nat_host = gateway
+#nat_port = 1456
+#anon_mask_4 = 24
+#anon_mask_6 = 64
+aggr_win = 300
+test_mode = true
+output_dir = var/spool/warden
+port_xlat = 2223:22 2222:2222
+drop_malware = true
diff --git a/cowrie/cowrie.service.example b/cowrie/cowrie.service.example
new file mode 100644
index 0000000000000000000000000000000000000000..b3ead69176b019c05066bc323c4f08120b40f33c
--- /dev/null
+++ b/cowrie/cowrie.service.example
@@ -0,0 +1,29 @@
+[Unit]
+Description=A SSH and Telnet honeypot service
+After=network.target
+After=rsyslog.service
+Requires=cowrie.socket
+
+[Service]
+User=cowrie
+Group=cowrie
+PIDFile=/opt/cowrie/var/run/cowrie.pid
+
+Restart=always
+RestartSec=5
+
+Environment=PYTHONPATH=/opt/cowrie/src
+Environment=COWRIE_VIRTUAL_ENV=/opt/cowrie/usr
+Environment=TZ="/usr/share/zoneinfo/UTC"
+WorkingDirectory=/opt/cowrie
+
+ExecStart=/opt/cowrie/bin/cowrie start
+ExecStop=/opt/cowrie/bin/cowrie stop
+ExecRestart=/opt/cowrie/bin/cowrie restart
+
+StandardOutput=journal
+StandardError=journal
+SyslogIdentifier=cowrie
+
+[Install]
+WantedBy=multi-user.target
diff --git a/cowrie/cowrie.socket.example b/cowrie/cowrie.socket.example
new file mode 100644
index 0000000000000000000000000000000000000000..bf8b56b89a78ae8af06f5374de4d18b47ebdf2b7
--- /dev/null
+++ b/cowrie/cowrie.socket.example
@@ -0,0 +1,7 @@
+[Unit]
+Description=Cowrie socket
+
+[Socket]
+ListenStream=2223
+ListenStream=2222
+NoDelay=true
diff --git a/cowrie/warden_client_cowrie.cfg b/cowrie/warden_client_cowrie.cfg
deleted file mode 100644
index 5668548946e964bee92ae0705d758418ee639ab9..0000000000000000000000000000000000000000
--- a/cowrie/warden_client_cowrie.cfg
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-    "warden": "warden_client.cfg",
-    "name": "cz.cesnet.server.kippo",
-    "secret": "",
-
-    "anonymised": "no",
-    "target_net": "195.113.0.0/16",
-
-    "dbhost": "localhost",
-    "dbuser": "kippo",
-    "dbpass": "kippopass",
-    "dbname": "kippo",
-    "dbport": 3306,
-    "awin": 5
-}
diff --git a/cowrie/warden_sender_cowrie.py b/cowrie/warden_sender_cowrie.py
deleted file mode 100755
index db056aaa18cc2a1e75477e24fffc6c6e950e2872..0000000000000000000000000000000000000000
--- a/cowrie/warden_sender_cowrie.py
+++ /dev/null
@@ -1,305 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-
-from warden_client import Client, Error, read_cfg, format_timestamp
-from time import time, gmtime, strftime
-from math import trunc
-from uuid import uuid4
-import MySQLdb as my
-import MySQLdb.cursors as mycursors
-import tempfile, subprocess, base64
-import json
-import string
-import os
-import sys
-
-#warden client startup
-aconfig = read_cfg('warden_client_cowrie.cfg')
-wconfig = read_cfg('warden_client.cfg')
-aclient_name = aconfig['name']
-wconfig['name'] = aclient_name
-aanonymised = aconfig['anonymised']
-aanonymised_net  = aconfig['target_net']
-aanonymised = aanonymised if (aanonymised_net != '0.0.0.0/0') or (aanonymised_net == 'omit') else '0.0.0.0/0'
-awin = aconfig['awin'] * 60
-atest = aconfig['test_mode']
-
-wclient = Client(**wconfig)
-
-def idea_fill_addresses(event, source_ip, destination_ip, anonymised, anonymised_net):
-   af = "IP4" if not ':' in source_ip else "IP6"
-   event['Source'][0][af] = [source_ip]
-   if anonymised != 'omit':
-           if anonymised == 'yes':
-                   event['Target'][0]['Anonymised'] = True
-                   event['Target'][0][af] = [anonymised_net]
-           else:
-                   event['Target'][0][af] = [destination_ip]
-   return event
-
-def gen_event_idea_cowrie_info(detect_time, src_ip, dst_ip, win_start_time, win_end_time, aggr_win, conn_count):
-	event = {
-		"Format": "IDEA0",
-		"ID": str(uuid4()),
-		"DetectTime": detect_time,
-		"WinStartTime": win_start_time,
-		"WinEndTime": win_end_time,
-		"Category": ["Attempt.Login"],
-		"Note": "SSH login attempt",
-		"ConnCount": conn_count,
-		"Source": [{}],
-		"Target": [{ "Proto": ["tcp", "ssh"], "Port": [22]}],
-		"Node": [
-			{
-				"Name": aclient_name,
-				"Type": ["Connection","Honeypot","Recon"],
-				"SW": ["Cowrie"],
-				"AggrWin": strftime("%H:%M:%S", gmtime(aggr_win))
-			}
-			]
-	}
-	# Test if we're testing
-	if atest == "true":
-	    event["Category"].append('Test')
-
-	event = idea_fill_addresses(event, src_ip, dst_ip, aanonymised, aanonymised_net)
-	return event
-
-
-def gen_event_idea_cowrie_auth(detect_time, src_ip, dst_ip, username, password, sessionid):
-
-	event = {
-		"Format": "IDEA0",
-		"ID": str(uuid4()),
-		"DetectTime": detect_time,
-		"Category": ["Information.UnauthorizedAccess"],
-		"Note": "SSH successfull attempt",
-		"ConnCount": 1,
-		"Source": [{}],
-		"Target": [{ "Proto": ["tcp", "ssh"], "Port" : [22] }],
-		"Node": [
-			{
-				"Name": aclient_name,
-				"Type": ["Honeypot", "Connection", "Auth"],
-				"SW": ["Cowrie"],
-			}
-		],
-		"Attach": [{ "sessionid": sessionid, "username": username, "password": password }]
-	}
-	# Test if we're testing
-	if atest == "true":
-	    event["Category"].append('Test')
-
-	event = idea_fill_addresses(event, src_ip, dst_ip, aanonymised, aanonymised_net)
-  
-	return event
-
-
-def gen_event_idea_cowrie_ttylog(detect_time, src_ip, dst_ip, sessionid, ttylog, iinput):
-
-	event = {
-		"Format": "IDEA0",
-		"ID": str(uuid4()),
-		"DetectTime": detect_time,
-		"Category": ["Information.UnauthorizedAccess"],
-		"Note": "Cowrie ttylog",
-		"ConnCount": 1,
-		"Source": [{}],
-		"Target": [{ "Proto": ["tcp", "ssh"], "Port" : [22] }],
-		"Node": [
-			{
-				"Name": aclient_name,
-				"Type": ["Honeypot", "Data"],
-				"SW": ["Cowrie"],
-			}
-		],
-		"Attach": [ { "sessionid": sessionid, "ttylog": ttylog, "iinput": iinput, "smart": iinput } ]
-  	}
-	# Test if we're testing
-	if atest == "true":
-	    event["Category"].append('Test')
-	
-	event = idea_fill_addresses(event, src_ip, dst_ip, aanonymised, aanonymised_net) 
- 
-	return event
-
-
-def gen_event_idea_cowrie_download(detect_time, src_ip, dst_ip,	sessionid, url, outfile):
-
-	event = {
-		"Format": "IDEA0",
-		"ID": str(uuid4()),
-		"DetectTime": detect_time,
-		"Category": ["Malware"],
-		"Note": "Cowrie download",
-		"ConnCount": 1,
-		"Source": [{}],
-		"Target": [{ "Proto": ["tcp", "ssh"], "Port" : [22]}],
-		"Node": [
-			{
-				"Name": aclient_name,
-				"Type": ["Honeypot", "Data"],
-				"SW": ["Cowrie"],     
-			}
-			],
-		"Attach": [{ "sessionid": sessionid, "url": url, "outfile": outfile, "smart": url }]
-	}
-	# Test if we're testing
-	if atest == "true":
-	    event["Category"].append('Test')
-
-	event = idea_fill_addresses(event, src_ip, dst_ip, aanonymised, aanonymised_net)	
-	
-	return event
-
-
-def get_iinput(sessionid):
-	ret = []
-       	query = "SELECT GROUP_CONCAT(input SEPARATOR '--SEP--') as i FROM input WHERE session=%s GROUP BY session;"
-	crs.execute(query, (sessionid,))
-        rows = crs.fetchall()
-        for row in rows:
-		ret.append(row["i"])
-	return ''.join(ret)
-
-
-def get_ttylog(sessionid):
-	ret = ""
-       	query = "SELECT id, session, ttylog FROM ttylog WHERE session=%s;"
-	crs.execute(query, (sessionid,))
-        rows = crs.fetchall()
-        for row in rows:
-		try:
-			tf = tempfile.NamedTemporaryFile(delete=False)
-			with open(tf.name, 'w') as f:
-				f.write(row['ttylog'])
-			ret = subprocess.check_output(["/opt/cowrie/bin/playlog", "-m0", tf.name])
-		finally:
-			os.remove(tf.name)
-
-		#try to dumpit to json to see if there are some binary input and perhaps wrap it to base64
-		try:
-			a = json.dumps(ret)
-		except UnicodeDecodeError as e:
-			wclient.logger.warning("wraping binary content")
-			ret = base64.b64encode(ret)
-
-	return ret
-
-
-
-con = my.connect( host=aconfig['dbhost'], user=aconfig['dbuser'], passwd=aconfig['dbpass'],
-       		  db=aconfig['dbname'], port=aconfig['dbport'], cursorclass=mycursors.DictCursor)
-crs = con.cursor()
-events = []
-
-#kippo vs cowrie
-#cowrie/core/dblog.py:    def nowUnix(self):
-#cowrie/core/dblog.py-        """return the current UTC time as an UNIX timestamp"""
-#cowrie/core/dblog.py-        return int(time.time())
-#kippo/core/dblog.py:    def nowUnix(self):
-#kippo/core/dblog.py-        """return the current UTC time as an UNIX timestamp"""
-#kippo/core/dblog.py-        return int(time.mktime(time.gmtime()[:-1] + (-1,)))
-# k sozalenju 
-# >>> int(time.mktime(time.gmtime()[:-1] + (-1,)))-int(time.time()) != 0
-
-
-#old senders data
-query = "SELECT UNIX_TIMESTAMP(s.starttime) as starttime, s.ip, COUNT(s.id) as attack_scale, sn.ip as sensor \
-	FROM sessions s \
-	LEFT JOIN sensors sn ON s.sensor=sn.id \
-	WHERE s.starttime > DATE_SUB(UTC_TIMESTAMP(), INTERVAL + %s SECOND) \
-	GROUP BY s.ip ORDER BY s.starttime ASC;"
-
-crs.execute(query, (awin,))
-etime = format_timestamp(time())
-stime = format_timestamp(time() - awin)
-rows = crs.fetchall()
-for row in rows:
-	a = gen_event_idea_cowrie_info(
-		detect_time = format_timestamp(row['starttime']),
-		src_ip = row['ip'],
-		dst_ip = row['sensor'],
-	
-		win_start_time = stime,
-		win_end_time = etime, 
-		aggr_win = awin,
-		conn_count = row['attack_scale']
-	)
-	events.append(a)
-
-
-
-#success login
-query =  "SELECT UNIX_TIMESTAMP(a.timestamp) as timestamp, s.ip as sourceip, sn.ip as sensor, a.session as sessionid, a.username as username, a.password as password \
-	FROM auth a JOIN sessions s ON s.id=a.session JOIN sensors sn ON s.sensor=sn.id \
-	WHERE a.success=1 AND a.timestamp > DATE_SUB(UTC_TIMESTAMP(), INTERVAL + %s SECOND) \
-	ORDER BY a.timestamp ASC;"
-
-crs.execute(query, (awin,))
-rows = crs.fetchall()
-for row in rows:
-	a = gen_event_idea_cowrie_auth(
-		detect_time = format_timestamp(row['timestamp']), 
-		src_ip = row['sourceip'], 
-		dst_ip = row['sensor'],
-		
-		username = row['username'],
-		password = row['password'],
-		sessionid = row['sessionid']
-	)
-	events.append(a)
-
-#ttylog+iinput reporter
-query =  "SELECT UNIX_TIMESTAMP(s.starttime) as starttime, s.ip as sourceip, sn.ip as sensor, t.session as sessionid \
-          FROM ttylog t JOIN sessions s ON s.id=t.session JOIN sensors sn ON s.sensor=sn.id \
-          WHERE s.starttime > DATE_SUB(UTC_TIMESTAMP(), INTERVAL + %s SECOND) \
-          ORDER BY s.starttime ASC;"
-
-crs.execute(query, (awin,))
-rows = crs.fetchall()
-for row in rows:
-	a = gen_event_idea_cowrie_ttylog(
-		detect_time = format_timestamp(row['starttime']), 
-		src_ip = row['sourceip'], 
-		dst_ip = row['sensor'],
-
-		sessionid = row['sessionid'],
-		ttylog = get_ttylog(row['sessionid']),
-		iinput = get_iinput(row['sessionid'])
-	)	
-	events.append(a)
-
-
-#download
-query =  "SELECT UNIX_TIMESTAMP(s.starttime) as starttime, s.ip as sourceip, sn.ip as sensor, d.session as sessionid, d.url as url, d.outfile as ofile \
-	FROM downloads d JOIN sessions s ON s.id=d.session JOIN sensors sn ON s.sensor=sn.id \
-	WHERE s.starttime > DATE_SUB(UTC_TIMESTAMP(), INTERVAL + %s SECOND) \
-	ORDER BY s.starttime ASC;"
-
-crs.execute(query, (awin,))
-rows = crs.fetchall()
-for row in rows:
-	a = gen_event_idea_cowrie_download(
-		detect_time = format_timestamp(row['starttime']), 
-		src_ip = row['sourceip'], 
-		dst_ip = row['sensor'],
-
-		sessionid = row['sessionid'],
-		url = row['url'],
-		outfile = row['ofile']
-	)
-	events.append(a)
-
-
-print "=== Sending ==="
-start = time()
-ret = wclient.sendEvents(events)
-
-if 'saved' in ret:
-	wclient.logger.info("%d event(s) successfully delivered." % ret['saved'])
-
-print "Time: %f" % (time() - start)
-
-
diff --git a/cowrie/wardenfiler.py b/cowrie/wardenfiler.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f21ec8fadbcef220653c1674793e732d93434fd
--- /dev/null
+++ b/cowrie/wardenfiler.py
@@ -0,0 +1,338 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2019 Cesnet z.s.p.o
+# Use of this source is governed by a 3-clause BSD-style license, see LICENSE file.
+
+"""
+Wardenfiler output connector. Writes audit logs to Wardenfiler spool directory in IDEA format
+"""
+
+import os
+import errno
+import socket
+import json
+import string
+from urllib.parse import urlparse
+from time import time, gmtime, strftime
+from datetime import datetime
+from uuid import uuid4
+from hashlib import sha1
+from base64 import b64encode
+from ipaddress import IPv4Network
+from ipaddress import IPv6Network
+from cowrie.core.config import CowrieConfig
+import cowrie.core.output
+
+class Filer(object):
+    """
+    IDEA files creator
+    """
+
+    def __init__(self, directory):
+        self.basedir = self._ensure_path(directory)
+        self.tmp = self._ensure_path(os.path.join(self.basedir, "tmp"))
+        self.incoming = self._ensure_path(os.path.join(self.basedir, "incoming"))
+        self.hostname = socket.gethostname()
+        self.pid = os.getpid()
+
+    def _ensure_path(self, p):
+        try:
+            os.mkdir(p)
+        except OSError:
+            if not os.path.isdir(p):
+                raise
+        return p
+
+    def _get_new_name(self, fd=None):
+        (inode, device) = os.fstat(fd)[1:3] if fd else (0, 0)
+        return "%s.%d.%f.%d.%d" % (
+            self.hostname, self.pid, time(), device, inode)
+
+    def create_unique_file(self):
+        tmpname = None
+        while not tmpname:
+            tmpname = self._get_new_name()
+            try:
+                fd = os.open(os.path.join(self.tmp, tmpname), os.O_CREAT | os.O_RDWR | os.O_EXCL)
+            except OSError as e:
+                if e.errno != errno.EEXIST:
+                    raise
+                tmpname = None
+        newname = self._get_new_name(fd)
+        os.rename(os.path.join(self.tmp, tmpname), os.path.join(self.tmp, newname))
+        nf = os.fdopen(fd, "w")
+        return nf, newname
+
+    def publish_file(self, short_name):
+        os.rename(os.path.join(self.tmp, short_name), os.path.join(self.incoming, short_name))
+
+class Output(cowrie.core.output.Output):
+    """
+    Wardenfiler Output
+    """
+    detector_name = None
+    resolve_nat = False
+    reported_public_ipv4 = None
+    reported_public_ipv6 = None
+    reported_ssh_port = None
+    nat_host = "gateway"
+    nat_port = 1456
+    anon_mask_4 = 32
+    anon_mask_6 = 128
+    aggr_win = 5 * 60
+    test_mode = True
+    output_dir = "var/spool/warden"
+    drop_malware = True
+    win_start = None
+    attackers = {}
+    sessions = {}
+    port_xlat = {}
+
+
+    def save_event(self, event):
+        f, name = self.filer.create_unique_file()
+        with f:
+            f.write(json.dumps(event, ensure_ascii=True))
+        self.filer.publish_file(name)
+
+
+    def start(self):
+        if CowrieConfig.has_option('output_wardenfiler', 'detector_name'):
+            self.detector_name = CowrieConfig.get('output_wardenfiler', 'detector_name')
+        if CowrieConfig.has_option('output_wardenfiler', 'resolve_nat'):
+            self.resolve_nat = CowrieConfig.getboolean('output_wardenfiler', 'resolve_nat')
+        if CowrieConfig.has_option('output_wardenfiler', 'reported_public_ipv4'):
+            self.reported_public_ipv4 = CowrieConfig.get('output_wardenfiler', 'reported_public_ipv4')
+        if CowrieConfig.has_option('output_wardenfiler', 'reported_public_ipv6'):
+            self.reported_public_ipv6 = CowrieConfig.get('output_wardenfiler', 'reported_public_ipv6')
+        if CowrieConfig.has_option('output_wardenfiler', 'reported_ssh_port'):
+            self.reported_ssh_port = CowrieConfig.getint('output_wardenfiler', 'reported_ssh_port')
+        if CowrieConfig.has_option('output_wardenfiler', 'nat_host'):
+            self.nat_host = CowrieConfig.get('output_wardenfiler', 'nat_host')
+        if CowrieConfig.has_option('output_wardenfiler', 'nat_port'):
+            self.nat_port = CowrieConfig.getint('output_wardenfiler', 'nat_port')
+        if CowrieConfig.has_option('output_wardenfiler', 'anon_mask_4'):
+            self.anon_mask_4 = CowrieConfig.getint('output_wardenfiler', 'anon_mask_4')
+        if CowrieConfig.has_option('output_wardenfiler', 'anon_mask_6'):
+            self.anon_mask_6 = CowrieConfig.getint('output_wardenfiler', 'anon_mask_6')
+        if CowrieConfig.has_option('output_wardenfiler', 'aggr_win'):
+            self.aggr_win = CowrieConfig.getint('output_wardenfiler', 'aggr_win')
+        if CowrieConfig.has_option('output_wardenfiler', 'test_mode'):
+            self.test_mode = CowrieConfig.getboolean('output_wardenfiler', 'test_mode')
+        if CowrieConfig.has_option('output_wardenfiler', 'output_dir'):
+            self.output_dir = CowrieConfig.get('output_wardenfiler', 'output_dir')
+        if CowrieConfig.has_option('output_wardenfiler', 'port_xlat'):
+            self.port_xlat = dict((int(x), int(y)) for x, y in (e.split(':') for e in CowrieConfig.get('output_wardenfiler', 'port_xlat').split()))
+        if CowrieConfig.has_option('output_wardenfiler', 'drop_malware'):
+            self.drop_malware = CowrieConfig.getboolean('output_wardenfiler', 'drop_malware')
+
+        self.filer = Filer(self.output_dir)
+
+
+    def stop(self):
+        """
+        No actions needed on honeypot shutdown
+        """
+
+
+    def write(self, entry):
+        event = {
+            "Format": "IDEA0",
+            "ID": str(uuid4()),
+            "DetectTime": entry['timestamp'],
+            "Category": [],
+            "Source": [{"Proto": ["tcp", "ssh"]}],
+            "Target": [{ "Proto": ["tcp", "ssh"]}],
+            "Node": [
+                {
+                    "Name": self.detector_name,
+                    "Type": ["Connection", "Auth", "Honeypot"],
+                    "SW": ["Cowrie with Warden Filer output module"],
+                }
+            ]
+        }
+
+        if self.test_mode:
+            event["Category"].append("Test")
+
+        entry["src_ip"] = entry["src_ip"].lstrip("::ffff:")
+        if entry.get("dst_ip"):
+            entry["dst_ip"] = entry["dst_ip"].lstrip("::ffff:")
+
+        # detect IPv4 or IPv6
+        af = "IP4" if not ':' in entry["src_ip"] else "IP6"
+
+        # If configured, override destination IP and port
+        if entry.get("dst_ip"):
+            if af == "IP4" and self.reported_public_ipv4:
+                entry["dst_ip"] = self.reported_public_ipv4
+            elif af == "IP6" and self.reported_public_ipv6:
+                entry["dst_ip"] = self.reported_public_ipv6
+
+        if entry.get("dst_port") and self.reported_ssh_port:
+            entry["dst_port"] = self.reported_ssh_port
+
+        if entry["eventid"] == 'cowrie.session.connect':
+            if self.resolve_nat:
+                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                s.connect((self.nat_host, self.nat_port))
+                s.sendall((','.join((entry["src_ip"], str(entry["src_port"]))).encode("utf-8")))
+                dst = s.recv(50).decode("utf-8")
+                s.close()
+                if dst != "NE":
+                    entry["dst_ip"] = dst
+                else:
+                    return()
+
+            entry["dst_ip"] = (
+                str(IPv4Network("/".join((entry["dst_ip"], str(self.anon_mask_4))), False).network_address) if not ':' in  entry["dst_ip"] else
+                str(IPv6Network("/".join((entry["dst_ip"], str(self.anon_mask_6))), False).network_address)
+            )
+
+            entry["loggedin"] = False
+            self.sessions[entry["session"]] = entry
+            ws = self.win_start or time()
+            aid = ','.join((entry["src_ip"], entry["dst_ip"]))
+            cnt = self.attackers.get(aid, 0)
+
+            if (time() - ws < self.aggr_win):
+                self.attackers[aid] = cnt + 1
+            else:
+                event["Node"][0]["AggrWin"] = strftime("%H:%M:%S", gmtime(float(self.aggr_win)))
+                event["WinStartTime"] = datetime.utcfromtimestamp(ws).isoformat() + 'Z'
+                event["WinEndTime"] = datetime.utcfromtimestamp(ws + self.aggr_win).isoformat() + 'Z'
+                event["Category"].append("Attempt.Login")
+                event["Note"] = "SSH login attempt"
+                for i, c in self.attackers.items():
+                    src_ip, dst_ip = i.split(',')
+                    af = "IP4" if not ':' in src_ip else "IP6"
+                    event["ID"] = str(uuid4())
+                    event["DetectTime"] = event["WinEndTime"]
+                    event["ConnCount"] = c
+                    event["Source"] = [{"Proto": ["tcp", "ssh"], af: [src_ip]}]
+                    event["Target"] = [{"Proto": ["tcp", "ssh"], af: [dst_ip]}]
+                    if (self.anon_mask_4 < 32) and (not ':' in  entry["dst_ip"]) or (self.anon_mask_6 < 128):
+                        event["Target"][0]["Anonymised"] = True
+                    self.save_event(event)
+                self.attackers = {}
+                ws = time()
+                self.attackers[aid] = 1
+            self.win_start = ws
+
+        elif entry["eventid"] == 'cowrie.login.success':
+            s = entry["session"]
+            if s in self.sessions:
+                self.sessions[s]["input"] = []
+                self.sessions[s]["loggedin"] = True
+
+        elif entry["eventid"] == 'cowrie.command.input':
+            s = entry["session"]
+            if s in self.sessions:
+                self.sessions[s]["input"].append(entry["input"])
+
+        elif entry["eventid"] == 'cowrie.session.file_download':
+            s = entry["session"]
+            if s in self.sessions:
+                sch = { "http": 80, "https": 443, "ftp": 21 }
+                
+                # deal with the file first (drop even if not reported)
+                mware = None
+                fname = None
+                if "outfile" in entry and os.path.exists(entry["outfile"]):
+                    fp = open(entry["outfile"], "r")
+                    mware = fp.read()
+                    fp.close()
+                    if self.drop_malware:
+                        os.remove(entry["outfile"])
+                
+                if mware:
+                    # TODO: Classify everything as Malware?
+                    event["Category"].append("Malware")
+                    event["Note"] = "Malware download during honeypot session"
+                    
+                    if "url" in entry and entry["url"].startswith(tuple(sch.keys())):
+                        url = urlparse(entry["url"])
+                        host = url.hostname
+                        ai = socket.getaddrinfo(host, None)[0]
+                        af = "IP6" if ai[0] == socket.AddressFamily.AF_INET6 else "IP4"
+                        ip = ai[4][0]
+                        proto = [ "tcp", url.scheme ]
+                        port = url.port or sch[url.scheme]
+                        
+                        fname = os.path.basename(entry["url"])
+                        if not fname and 'destfile' in entry:
+                            fname = os.path.basename(entry['destfile'])
+                            
+                    elif not "url" in entry:
+                        # TODO implement the path for other files after discussion
+                        return()
+                        # The remainder of this branch will not execute now
+                        if entry["format"].startswith("Saved redir"):
+                            event["Note"] = "Saved file during honeypot session"
+                            fname = os.path.basename(entry['destfile'])
+                        else:
+                            event["Note"] = "Stdin contents during honeypot session"
+                        # End of the not executed part
+
+                    else:
+                        # TODO: Some exotic protocol? Let's not worry with that now
+                        return()
+                        
+                    event["DetectTime"] = entry["timestamp"]
+                    if "url" in entry:
+                        event["Source"][0] = { "Type": ["Malware"] }
+                        event["Source"][0]["URL"] = [entry["url"]]
+                        event["Source"][0][af] = [ip]
+                        event["Source"][0]["Proto"] = proto
+                        event["Source"][0]["Port"] = [port]
+                        if ip != host:
+                            event["Source"][0]["Hostname"] = [host]
+                    else:
+                        # TODO implement later
+                        pass
+
+                    del event["Target"]
+                    event["Attach"] = [{
+                        "Type": ["ShellCode"],
+                        "Hash": ["sha1:" + entry["shasum"]],
+                        "Size": len(mware),
+                        "Note": "Some probably malicious code downloaded during honeypot SSH session",
+                        "ContentEncoding": "base64",
+                        "Content": b64encode(mware.encode()).decode(),
+                    }]
+                    if fname:
+                        event["Attach"][0]["FileName"] = [fname]
+                    if "url" in entry:
+                        event["Attach"][0]["ExternalURI"] = [entry["url"]]
+                    self.save_event(event)
+
+        elif entry["eventid"] == 'cowrie.session.closed':
+            s = entry["session"]
+            if s in self.sessions and self.sessions[s]["loggedin"]:
+                idata = '\n'.join(self.sessions[s]["input"])
+                plain = all(c in string.printable for c in idata)
+                event["Category"].append("Intrusion.UserCompromise")
+                event["Note"] = "SSH successful login" + (" with unauthorized command input" if len(idata) else "")
+                af = "IP4" if not ':' in entry["src_ip"] else "IP6"
+                event["Source"][0][af] = [entry["src_ip"]]
+                event["Target"][0][af] = [self.sessions[s]["dst_ip"]]
+                event["Source"][0]["Port"] = [self.sessions[s]["src_port"]]
+                dst_port = self.sessions[s]["dst_port"]
+                if dst_port in self.port_xlat:
+                    dst_port = self.port_xlat[dst_port]
+                event["Target"][0]["Port"] = [dst_port]
+                if len(idata):
+                    eidata = idata if plain else b64encode(idata.encode()).decode()
+                    attach = {
+                        "Type": ["Exploit"],
+                        "Hash": ["sha1:" + sha1(idata.encode("utf-8")).hexdigest()],
+                        "Size": len(idata),
+                        "Note": "Commands entered by attacker during honeypot SSH session",
+                        "Content": eidata
+                    }
+                    if not plain:
+                        attach["ContentEncoding"] = "base64"
+                    event["Attach"] = [attach]
+                self.save_event(event)
+            self.sessions.pop(s, None)
diff --git a/dionaea/dionaea.service.example b/dionaea/dionaea.service.example
new file mode 100644
index 0000000000000000000000000000000000000000..d592b1fe9f781302f2ad96ef50ae06de196b9df9
--- /dev/null
+++ b/dionaea/dionaea.service.example
@@ -0,0 +1,22 @@
+[Unit]
+Description=A malware capturing honeypot service
+After=network.target
+After=rsyslog.service
+#Requires=dionaea.socket
+
+[Service]
+User=root
+Group=root
+PIDFile=/opt/dionaea/var/run/dionaea.pid
+
+Restart=always
+RestartSec=5
+
+WorkingDirectory=/opt/dionaea
+
+ExecStart=/opt/dionaea/bin/dionaea -c /opt/dionaea/etc/dionaea/dionaea.cfg -w /opt/dionaea -D -p /opt/dionaea/var/run/dionaea.pid -u dionaea -g dionaea
+KillMode=process
+Type=forking
+
+[Install]
+WantedBy=multi-user.target
diff --git a/dionaea/log_wardenfiler.py b/dionaea/log_wardenfiler.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7b34749b152fa3ee888d78a935ff4c8adda0e34
--- /dev/null
+++ b/dionaea/log_wardenfiler.py
@@ -0,0 +1,410 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 Cesnet z.s.p.o
+# Use of this source is governed by a 3-clause BSD-style license, see LICENSE file.
+
+"""
+Wardenfiler output connector. Writes audit logs to Wardenfiler spool directory in IDEA format
+"""
+
+import os
+import errno
+import socket
+import json
+import hashlib
+import logging
+import string
+from urllib.parse import urlparse
+from time import time, gmtime, strftime
+from datetime import datetime
+from uuid import uuid4
+from hashlib import sha1
+from base64 import b64encode
+from ipaddress import IPv4Network
+from ipaddress import IPv6Network
+
+from dionaea import IHandlerLoader
+from dionaea.core import ihandler, connection
+from dionaea.exception import LoaderError
+
+logger = logging.getLogger("log_wardenfiler")
+logger.setLevel(logging.DEBUG)
+
+class Filer(object):
+    """
+    IDEA files creator
+    """
+
+    def __init__(self, directory):
+        self.basedir = self._ensure_path(directory)
+        self.tmp = self._ensure_path(os.path.join(self.basedir, "tmp"))
+        self.incoming = self._ensure_path(os.path.join(self.basedir, "incoming"))
+        self.hostname = socket.gethostname()
+        self.pid = os.getpid()
+
+    def _ensure_path(self, p):
+        try:
+            os.mkdir(p)
+        except OSError:
+            if not os.path.isdir(p):
+                raise
+        return p
+
+    def _get_new_name(self, fd=None):
+        (inode, device) = os.fstat(fd)[1:3] if fd else (0, 0)
+        return "%s.%d.%f.%d.%d" % (
+            self.hostname, self.pid, time(), device, inode)
+
+    def create_unique_file(self):
+        tmpname = None
+        while not tmpname:
+            tmpname = self._get_new_name()
+            try:
+                fd = os.open(os.path.join(self.tmp, tmpname), os.O_CREAT | os.O_RDWR | os.O_EXCL)
+            except OSError as e:
+                if e.errno != errno.EEXIST:
+                    raise
+                tmpname = None
+        newname = self._get_new_name(fd)
+        os.rename(os.path.join(self.tmp, tmpname), os.path.join(self.tmp, newname))
+        nf = os.fdopen(fd, "w")
+        return nf, newname
+
+    def publish_file(self, short_name):
+        os.rename(os.path.join(self.tmp, short_name), os.path.join(self.incoming, short_name))
+
+
+class LogWardenfilerHandlerLoader(IHandlerLoader):
+    name = "log_wardenfiler"
+
+    @classmethod
+    def start(cls, config=None):
+        try:
+            return LogWardenfilerHandler("*", config=config)
+        except LoaderError as e:
+            logger.error(e.msg, *e.args)
+            return None
+
+class LogWardenfilerHandler(ihandler):
+    detector_name = None
+    resolve_nat = False
+    nat_host = "gateway"
+    nat_port = 1456
+    anon_mask_4 = 32
+    anon_mask_6 = 128
+    aggr_win = 5 * 60
+    test_mode = True
+    output_dir = "var/spool/warden"
+    drop_malware = True
+    win_start = None
+    attackers = {}
+    sessions = {}
+
+    def __init__(self, path, config = None):
+        logger.debug("%s ready!", self.__class__.__name__)
+        ihandler.__init__(self, path)
+        self.path = path
+        self._config = config
+
+    def _save_event(self, event):
+        f, name = self.filer.create_unique_file()
+        with f:
+            f.write(json.dumps(event, ensure_ascii = True))
+        self.filer.publish_file(name)
+
+    def start(self):
+        if 'detector_name' in self._config:
+            self.detector_name = self._config.get('detector_name')
+        if 'resolve_nat' in self._config:
+            self.resolve_nat = self._config.get('resolve_nat')
+        if 'nat_host' in self._config:
+            self.nat_host = self._config.get('nat_host')
+        if 'nat_port' in self._config:
+            self.nat_port = self._config.get('nat_port')
+        if 'anon_mask_4' in self._config:
+            self.nat_port = self._config.get('anon_mask_4')
+        if 'anon_mask_6' in self._config:
+            self.nat_port = self._config.get('anon_mask_6')
+        if 'aggr_win' in self._config:
+            self.aggr_win = self._config.get('aggr_win')
+        if 'test_mode' in self._config:
+            self.test_mode = self._config.get('test_mode')
+        if 'output_dir' in self._config:
+            self.output_dir = self._config.get('output_dir')
+        if 'drop_malware' in self._config:
+            self.drop_malware = self._config.get('drop_malware')
+
+        self.filer = Filer(self.output_dir)
+
+    def _aggregate(self):
+        ws = self.win_start or time()
+        if (time() - ws >= self.aggr_win):
+            logger.info("Counting attacks: %s" % json.dumps(self.attackers, ensure_ascii = True))
+            we = datetime.utcfromtimestamp(ws + self.aggr_win).isoformat() + 'Z'
+            sevent = {
+                "Format": "IDEA0",
+                "WinStartTime": datetime.utcfromtimestamp(ws).isoformat() + 'Z',
+                "WinEndTime": we,
+                "DetectTime": we,
+                "Category": [],
+                "Node": [
+                    {
+                        "Name": self.detector_name,
+                        "Type": ["Connection", "Auth", "Honeypot"],
+                        "SW": ["Dionaea with Warden Filer output module"],
+                        "AggrWin": strftime("%H:%M:%S", gmtime(float(self.aggr_win)))
+                    }
+                ]
+            }
+            if self.test_mode:
+                sevent["Category"].append("Test")
+
+            for i, a in self.attackers.items():
+                c = a["count"]
+                if c > 1:
+                    src_ip, dst_ip, dst_port, proto = i.split(',')
+                    if (self.anon_mask_4 < 32) and (not ':' in  dst_ip) or (self.anon_mask_6 < 128):
+                        Target[0]["Anonymised"] = "true"
+                    sevent["ID"] = str(uuid4())
+                    if len(a["creds"]):
+                        sevent["Category"] = ["Recon.Scanning"]
+                        sevent["Note"] = "Successful logins to honeypoted service."
+                    else:
+                        sevent["Category"] = ["Attempt.Login"]
+                        sevent["Note"] = "Connection attempts to IPs assigned to honeypot."
+                    sevent["ConnCount"] = c
+                    af = "IP4" if not ':' in src_ip else "IP6"
+                    proto = [proto]
+                    if a["proto"]
+                        proto.append(a["proto"])
+                    sevent["Source"] = [{"Proto": proto, af: [src_ip], "Port": a["sports"]}]
+                    sevent["Target"] = [{"Proto": proto, af: [dst_ip], "Port": [int(dst_port)]}]
+                    if len(a["creds"]):
+                        attach = {
+                            "Type": ["Credentials"],
+                            "Note": "Credentials used by attacker used for simulated honeypot login",
+                            "Credentials": a["creds"]
+                        }
+                        sevent["Attach"] = attach
+                    self._save_event(sevent)
+                    logger.info("sending scanning event for %s probing %s (%i times)" % (src_ip, dst_ip, c))
+            self.attackers = {}
+            self.win_start = time()
+
+    def _make_idea(self, con):
+        s = self.sessions[con]
+        proto = [s["trans"]]
+        if s["proto"]:
+            proto.append(s["proto"])
+        event = {
+            "Format": "IDEA0",
+            "ID": s["id"],
+            "DetectTime": s["dt"],
+            "Category": s["cat"],
+            "Source": [{"Proto": proto, s["af"]: [s["src_ip"]], "Port": [s["src_port"]]}],
+            "Target": [{ "Proto": proto, s["af"]: [s["dst_ip"]], "Port": [s["dst_port"]]}],
+            "Node": [
+                {
+                    "Name": self.detector_name,
+                    "Type": ["Connection", "Auth", "Honeypot"],
+                    "SW": ["Dionaea with Warden Filer output module"],
+                }
+            ]
+        }
+
+        if s["anon"]:
+            event["Target"][0]["Anonymised"] = "true"
+
+        if len(s["creds"]):
+            p = {
+                "ftp": "FTP",
+                "mysql": "MySQL",
+                "ms-sql-s": "MSSQL",
+            }
+            event["Category"].append("Intrusion.UserCompromise")
+            if s["proto"]:
+                event["Note"] = p[s["proto"]] + "successful login"
+            else
+                event["Note"] = "Successful login attempt"
+            attach = {
+                "Type": ["Credentials"],
+                "Note": "Credentials used by attacker used for simulated honeypot login",
+                "Credentials": s["creds"]
+            }
+            event["Attach"] = [attach]
+
+        if len(s["cmds"]):
+            event["Category"].append("Attempt.Exploit")
+            event["Note"] += " with unauthorized command input"
+            idata = "\n".join(str(c) for c in s[cmds])
+            plain = all(c in string.printable for c in idata)
+            eidata = idata if plain else b64encode(idata.encode()).decode()
+            attach = {
+                "Type": ["Exploit"],
+                "Hash": ["sha1:" + sha1(idata.encode("utf-8")).hexdigest()],
+                "Size": len(idata),
+                "Note": "Commands entered by attacker during honeypot session",
+                "Content": eidata
+            }
+            if not plain:
+                attach["ContentEncoding"] = "base64"
+            event["Attach"].append(attach)
+
+        return(event)
+
+    def _register_connection(self, con, proto = None, cred = None, cmd = None)
+        if not con in self.sessions:
+            src_ip = con.remote.host.lstrip("::ffff:")
+            dst_ip = con.local.host.lstrip("::ffff:")
+
+            if self.resolve_nat:
+                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                s.connect((self.nat_host, self.nat_port))
+                s.sendall((','.join((src_ip, str(con.local.port))).encode("utf-8")))
+                dst = s.recv(50).decode("utf-8")
+                s.close()
+                if dst != "NE":
+                    dst_ip = dst
+                else:
+                    logger.warn("no translation for %s:%s" % (src_ip, con.local.port))
+                    return()
+
+            af = "IP4" if not ':' in src_ip else "IP6"
+            anon = (self.anon_mask_4 < 32) and (not ':' in  dst_ip) or (self.anon_mask_6 < 128)
+            if anon:
+                dst_ip = [(
+                    str(IPv4Network("/".join((dst_ip, str(self.anon_mask_4))), False).network_address) if not ':' in dst_ip else
+                    str(IPv6Network("/".join((dst_ip, str(self.anon_mask_6))), False).network_address)
+                )]
+
+            self.sessions[con]["id"] = str(uuid4())
+            self.sessions[con]["dt"] = datetime.utcnow().isoformat() + "Z"
+            self.sessions[con]["cat"] = ["Test"] if self.test_mode else []
+            self.sessions[con]["af"] = af
+            self.sessions[con]["anon"] = anon
+            self.sessions[con]["src_ip"] = src_ip
+            self.sessions[con]["dst_ip"] = dst_ip
+            self.sessions[con]["src_port"] = con.remote.port
+            self.sessions[con]["dst_port"] = con.local.port
+            self.sessions[con]["trans"] = con.transport
+            self.sessions[con]["proto"] = None
+            self.sessions[con]["creds"] = []
+            self.sessions[con]["cmds"] = []
+
+        aid = ','.join((src_ip, dst_ip, str(con.local.port), con.transport))
+
+        if not aid in in self.attackers:
+            self.attackers[aid] = {
+                "count": 0,
+                "sports": [],
+                "creds": [],
+                "proto": None
+            }
+
+        self.attackers[aid]["count"] += 1
+        if not con.remote.port in self.attackers[aid]["sports"]:
+            self.attackers[aid]["sports"].append(con.remote.port)
+        if proto:
+            self.sessions[con]["proto"] = proto
+            self.attackers[aid]["proto"] = proto
+        if cred:
+            self.sessions[con]["creds"].append(cred)
+            self.attackers[aid]["creds"].append(cred)
+        if cmd:
+            self.sessions[con]["cmds"].append(cmd)
+
+    def handle_incident(self, icd):
+        pass
+
+    def handle_incident_dionaea_connection_tcp_listen(self, icd):
+        pass;
+
+    def handle_incident_dionaea_connection_tls_listen(self, icd):
+        pass
+
+    def handle_incident_dionaea_connection_tcp_connect(self, icd):
+        con = icd.con
+        self._register_connection(con)
+        logger.info("connect connection to %s/%s:%i from %s:%i" % (con.remote.host, con.remote.hostname, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_connection_tls_connect(self, icd):
+        con = icd.con
+        self._register_connection(con, "ssl-tls")
+        logger.info("connect connection to %s/%s:%i from %s:%i" % (con.remote.host, con.remote.hostname, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_connection_udp_connect(self, icd):
+        con = icd.con
+        self._register_connection(con)
+        logger.info("connect connection to %s/%s:%i from %s:%i" % (con.remote.host, con.remote.hostname, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_connection_tcp_accept(self, icd):
+        con = icd.con
+        self._register_connection(con)
+        logger.info("accepted connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_connection_tls_accept(self, icd):
+        con = icd.con
+        self._register_connection(con, "ssl-tls")
+        logger.info("accepted connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_connection_tcp_reject(self, icd):
+        con = icd.con
+        self._register_connection(con)
+        logger.info("reject connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_modules_python_ftp_command(self, icd):
+        con = icd.con
+        cmd = icd.command.decode()
+        if hasattr(icd, 'arguments'):
+            cmd = " ".join([cmd], icd.arguments)
+        self._register_connection(con, "ftp", cmd = cmd)
+        logger.info("new FTP command within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_modules_python_mssql_cmd(self, icd):
+        con = icd.con
+        self._register_connection(con, "ms-sql-s", cmd = icd.cmd)
+        logger.info("new MSSQL command within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_modules_python_mysql_command(self, icd):
+        con = icd.con
+        cmd = icd.command
+        if hasattr(icd, 'args'):
+            cmd = " ".join([cmd], icd.args)
+        self._register_connection(con, "mysql", cmd = cmd)
+        logger.info("new MYSQL command within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_modules_python_ftp_login(self, icd):
+        con = icd.con
+        self._register_connection(con, "ftp",  cred = {"User": icd.username, "Password": icd.password})
+        logger.info("new FTP login within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_modules_python_mssql_login(self, icd):
+        con = icd.con
+        self._register_connection(con, "ms-sql-s",  cred = {"User": icd.username, "Password": icd.password})
+        logger.info("new MSSQL login within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_modules_python_mysql_login(self, icd):
+        con = icd.con
+        self._register_connection(con, "mysql",  cred = {"User": icd.username, "Password": icd.password})
+        logger.info("new MySQL login within connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port))
+
+    def handle_incident_dionaea_modules_python_p0f(self, icd):
+        pass;
+
+    def handle_incident_dionaea_connection_free(self, icd):
+        con = icd.con
+
+        self._aggregate()
+
+        if con in self.sessions:
+            s = self.sessions[con]
+            if len(s["cmds"]):
+                event = self._make_idea(con)
+                self._save_event(event)
+                logger.info("sending connection event from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port))
+            self.sessions.pop(con, None)
+            logger.info("closing connection from %s:%i to %s:%i" % (con.remote.host, con.remote.port, con.local.host, con.local.port))
+        else:
+            logger.warn("no attack data for %s:%s" % (con.remote.host, con.remote.port))
+
diff --git a/dionaea/log_wardenfiler.yaml.example b/dionaea/log_wardenfiler.yaml.example
new file mode 100644
index 0000000000000000000000000000000000000000..3d96b513589b1e18965dfb27e2fccef15a30779f
--- /dev/null
+++ b/dionaea/log_wardenfiler.yaml.example
@@ -0,0 +1,13 @@
+- name: log_wardenfiler
+  config:
+    detector_name: "org.example.hugo.dionaea"
+    resolve_nat: no
+    # nat_host: "gateway"
+    # nat_port: 1456
+    # anon_mask_4: 24
+    # anon_mask_6: 64
+    aggr_win: 300
+    test_mode: True
+    output_dir: "var/spool/warden"
+    drop_malware: True
+    drop_stream: True
diff --git a/dionaea/warden3-dio-sender.py b/dionaea/warden3-dio-sender.py
deleted file mode 100755
index b992ad495c2bad5a742968758b206231799c017f..0000000000000000000000000000000000000000
--- a/dionaea/warden3-dio-sender.py
+++ /dev/null
@@ -1,246 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2011-2018 Cesnet z.s.p.o
-# Use of this source is governed by a 3-clause BSD-style license, see LICENSE file.
-
-from warden_client import Client, Error, read_cfg, format_timestamp
-import json
-import string
-import urllib
-from time import time, gmtime, strftime, sleep
-from math import trunc
-from uuid import uuid4
-from os import path, remove
-import base64
-import sqlite3
-import sys
-import re
-
-aconfig = read_cfg('warden_client_dio.cfg')
-wconfig = read_cfg('warden_client.cfg')
-aclient_name = aconfig['name']
-wconfig['name'] = aclient_name
-aanonymised = aconfig['anonymised']
-aanonymised_net  = aconfig['target_net']
-aanonymised = aanonymised if (aanonymised_net != '0.0.0.0/0') or (aanonymised_net == 'omit') else '0.0.0.0/0'
-atest = aconfig['test_mode']
-
-awin = aconfig['awin'] * 60
-abinpath = aconfig['binaries_path']
-adbfile = aconfig['dbfile']
-aconattempts = aconfig['con_attempts']
-aretryinterval = aconfig['con_retry_interval']
-areportbinaries = aconfig['report_binaries']
-apurgebinaries =  aconfig['purge_binaries']
-
-wconfig['secret'] = aconfig.get('secret', '')
-wclient = Client(**wconfig)
-
-def idea_fill_addresses(event, source_ip, destination_ip, anonymised, anonymised_net):
-   af = "IP4" if not ':' in source_ip else "IP6"
-   event['Source'][0][af] = [source_ip]
-   if anonymised != 'omit':
-           if anonymised == 'yes':
-                   event['Target'][0]['Anonymised'] = True
-                   event['Target'][0][af] = [anonymised_net]
-           else:
-                   event['Target'][0][af] = [destination_ip]
-
-   return event
-
-def gen_attach_idea_smb(logger, report_binaries, purge_binaries, binaries_path, filename, hashtype, hashdigest, vtpermalink, avref):
-
-  refs = []
-  attach = {
-         "Handle": 'att1',
-         "FileName": [filename],
-         "Type": ["Malware"],
-         "Hash": ["%s:%s" % (hashtype, hashdigest)],
-      }
-
-  if vtpermalink is not None:
-    refs.append('url:' + vtpermalink)
-
-  if avref is not None:
-    refs.extend(avref.split(';'))
-
-  if refs:
-    refs = [urllib.quote(ref, safe=':') for ref in refs]
-    refs = list(set(refs))
-    attach['Ref'] = refs
-
-  if report_binaries == 'true':
-    try:
-      fpath = path.join(binaries_path, hashdigest)
-      with open(fpath, "r") as f:
-        fdata = f.read()
-        attach['ContentType'] = 'application/octet-stream'
-        attach['ContentEncoding'] = 'base64'
-        attach['Size'] = len(fdata)
-        attach['Content'] = base64.b64encode(fdata)
-    except (IOError) as e:
-      logger.info("Reading id file \"%s\" with malware failed, information will not be attached." % (fpath))
-    if purge_binaries == 'true':
-      try:
-        remove(filename)
-      except OSError:
-        pass
-
-  return attach
-
-def gen_attach_idea_db(logger, data):
-
-  attach = {}
-  attach["Handle"] = 'att1'
-  attach["Type"] = ["Malware"]
-  attach['ContentType'] = 'application/octet-stream'
-  attach['ContentEncoding'] = 'base64'
-  attach['Size'] = len(data)
-  attach['Content'] = base64.b64encode(data)
-
-  return attach
-
-def gen_event_idea_dio(logger, binaries_path, report_binaries, purge_binaries, client_name, anonymised, target_net, detect_time, win_start_time, win_end_time, aggr_win, data):
-
-  category = []
-  event = {
-     "Format": "IDEA0",
-     "ID": str(uuid4()),
-     "DetectTime": detect_time,
-     "WinStartTime": win_start_time,
-     "WinEndTime": win_end_time,
-     "ConnCount": data['attack_scale'],
-     "Source": [{}],
-     "Target": [{}],
-     "Node": [
-        {
-           "Name": client_name,
-           "Type": ["Connection", "Protocol", "Honeypot"],
-           "SW": ["Dionaea"],
-           "AggrWin": strftime("%H:%M:%S", gmtime(aggr_win))
-        }
-     ]
-  }
-
-  # Save TCP/UDP proto
-  proto = [data['proto']]
-
-  # smbd allows save malware
-  if data['service'] == 'smbd' and data['download_md5_hash'] is not None:
-    category.append('Attempt.Exploit')
-    category.append('Malware')
-    proto.append('smb')
-
-    if data['download_url'] != '':
-        event['Source'][0]['URL'] = [data['download_url']]
-    filename = data['download_url'].split('/')[-1]
-
-    if filename != '' and data['download_md5_hash'] != '':
-      # Generate "SMB Attach" part of IDEA
-      a = gen_attach_idea_smb(logger, report_binaries, binaries_path, filename, "md5", data['download_md5_hash'], data['virustotal_permalink'], data['scan_result'])
-
-      event['Source'][0]['AttachHand'] = ['att1']
-      event['Attach'] = [a]
-
-  if data['service'] == 'mysqld':
-    #Clean exported data
-    if data['mysql_query'] is not None:
-          mysql_data = re.sub("select @@version_comment limit 1,?", "", data['mysql_query'])
-          if mysql_data != "":
-            # Generate "MySQL Attach" part of IDEA
-            a = gen_attach_idea_db(logger, mysql_data)
-
-            category.append('Attempt.Exploit')
-            proto.append('mysql')
-            event['Source'][0]['AttachHand'] = ['att1']
-            event['Attach'] = [a]
-
-  if data['service'] == 'mssqld':
-    #Clean exported data
-    if data['mssql_query'] is not None:
-          mssql_data = data['mssql_query']
-          if mssql_data != "":
-            # Generate "MSSQL Attach" part of IDEA
-            a = gen_attach_idea_db(logger, mssql_data)
-
-            category.append('Attempt.Exploit')
-            proto.append('mssql')
-            event['Source'][0]['AttachHand'] = ['att1']
-            event['Attach'] = [a]
-
-  event['Source'][0]['Port']  = [data['src_port']]
-  event['Target'][0]['Port']  = [data['dst_port']]
-  event['Source'][0]['Proto'] = proto
-  event['Target'][0]['Proto'] = proto
-
-  idea_fill_addresses(event, data['src_ip'], data['dst_ip'], aanonymised, aanonymised_net)
-
-  # Add default category
-  if not category:
-        category.append('Recon.Scanning')
-
-  # Test if we're testing
-  if atest == "true":
-    category.append('Test')
-
-  event['Category'] = category
-
-  return event
-
-def main():
-
-  con = sqlite3.connect(adbfile)
-  con.row_factory = sqlite3.Row
-  con.text_factory = str
-  crs = con.cursor()
-
-  events = []
-
-  query = "SELECT c.connection_timestamp AS timestamp, c.remote_host AS src_ip, c.remote_port AS src_port, c.connection_transport AS proto, \
-            c.local_host AS dst_ip, c.local_port AS dst_port, COUNT(c.connection) as attack_scale, c.connection_protocol AS service, d.download_url, d.download_md5_hash, \
-            v.virustotal_permalink, GROUP_CONCAT('urn:' || vt.virustotalscan_scanner || ':' || vt.virustotalscan_result,';') AS scan_result, \
-            group_concat(mca.mysql_command_arg_data) as mysql_query, \
-          group_concat(msc.mssql_command_cmd) as mssql_query \
-            FROM connections AS c LEFT JOIN downloads AS d ON c.connection = d.connection \
-            LEFT JOIN virustotals AS v ON d.download_md5_hash = v.virustotal_md5_hash \
-            LEFT JOIN virustotalscans vt ON v.virustotal = vt.virustotal \
-            LEFT JOIN mysql_commands mc ON c.connection = mc.connection  \
-            LEFT JOIN mysql_command_args mca ON mc.mysql_command = mca.mysql_command \
-          LEFT JOIN mssql_commands msc ON c.connection = msc.connection \
-          WHERE datetime(connection_timestamp,'unixepoch') > datetime('now','-%d seconds') AND c.remote_host != '' \
-          GROUP BY c.remote_host, c.local_port ORDER BY c.connection_timestamp ASC;" % (awin)
-
-  attempts = 0
-  while attempts < aconattempts:
-    try:
-      crs.execute(query)
-      break
-    except sqlite3.Error, e:
-      attempts += 1
-      wclient.logger.info("Info: %s - attempt %d/%d." % (e.args[0], attempts, aconattempts))
-      if attempts == aconattempts:
-        wclient.logger.error("Error: %s (dbfile: %s)" % (e.args[0], adbfile))
-
-      sleep(aretryinterval)
-
-  rows = crs.fetchall()
-
-  if con:
-    con.close
-
-  etime = format_timestamp(time())
-  stime = format_timestamp(time() - awin)
-
-  for row in rows:
-    dtime = format_timestamp(row['timestamp'])
-    events.append(gen_event_idea_dio(logger = wclient.logger, binaries_path = abinpath, report_binaries = areportbinaries, purge_binaries = apurgebinaries, client_name = aclient_name, anonymised = aanonymised, target_net = aanonymised_net, detect_time = dtime, win_start_time = stime, win_end_time = etime, aggr_win = awin, data = row))
-
-  start = time()
-  ret = wclient.sendEvents(events)
-  if 'saved' in ret:
-    wclient.logger.info("%d event(s) successfully delivered in %d seconds" % (ret['saved'], (time() - start)))
-
-
-if __name__ == "__main__":
-    main()
diff --git a/dionaea/warden_client-dio.cfg b/dionaea/warden_client-dio.cfg
deleted file mode 100644
index 537d96ab12e92c58163b9c2c1a34e8962bc6f714..0000000000000000000000000000000000000000
--- a/dionaea/warden_client-dio.cfg
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-    "warden": "warden_client.cfg",
-    "name": "cz.cesnet.server.dionaea",
-    "secret": "",
-
-    "anonymised": "no",
-    "target_net": "195.113.0.0/16",
-
-    "dbfile": "/opt/dionaea/var/dionaea/logsql.sqlite",
-    "binaries_path" : "/opt/dionaea/var/dionaea/binaries",
-    "report_binaries" : "true",
-    "con_attempts" : 3,
-    "con_retry_interval" : 5,
-    "awin": 5
-}