diff --git a/warden3/contrib/warden_filer/LICENSE b/warden3/contrib/warden_filer/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..b1a5f2252f95e456260aca3e0cab6c244a981891
--- /dev/null
+++ b/warden3/contrib/warden_filer/LICENSE
@@ -0,0 +1,27 @@
+BSD License
+
+Copyright © 2011-2015 Cesnet z.s.p.o
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of the Cesnet z.s.p.o nor the names of its
+      contributors may be used to endorse or promote products derived from this
+      software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE Cesnet z.s.p.o BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/warden3/contrib/warden_filer/README b/warden3/contrib/warden_filer/README
new file mode 100644
index 0000000000000000000000000000000000000000..12f500af505e996bb9ad3d755bd1be336467241d
--- /dev/null
+++ b/warden3/contrib/warden_filer/README
@@ -0,0 +1,125 @@
++---------------------------------+
+| Warden Filer 0.1 for Warden 3.X |
++---------------------------------+
+
+Content
+
+  A. Introduction
+  B. Dependencies
+  C. Usage
+  D. Configuration
+  E. Directories and locking issues
+
+------------------------------------------------------------------------------
+A. Introduction
+
+   Warden Filer (executable warden_filer.py) is daemon for easy handling of
+Idea events transfer between plain local files and Warden server. The tool can
+be instructed to run as one of two daemons - reader and sender.
+   In reader mode, Filer polls Warden server and saves incoming events as
+plain files in directory.
+   In writer mode, Filer polls directory and sends out all new files out to
+Warden server.
+
+------------------------------------------------------------------------------
+B. Dependencies
+
+ 1. Platform
+
+    Python 2.7+
+
+ 2. Python packages
+
+    python-daemon 1.5+, warden_client 3.0+
+
+------------------------------------------------------------------------------
+C. Usage
+
+   warden_filer.py [-h] [-c CONFIG] [--oneshot] {sender,receiver}
+
+   Save Warden events as files or send files to Warden
+
+   positional arguments:
+     {sender,receiver}     choose direction: sender picks up files and submits
+                           them to Warden, receiver pulls events from Warden
+                           and saves them as files
+
+   optional arguments:
+     -h, --help            show this help message and exit
+     -c CONFIG, --config CONFIG
+                           configuration file path
+     --oneshot             don't daemonise, run just once
+
+
+   CONFIG denotes path to configuration file, default is warden_filer.cfg in
+current directory.
+   --oneshot prevents daemonizing, Filer just does its work once (fetches
+available events or sends event files present in directory), but obeys
+all other applicable options from configuration file (concerning logging,
+filtering, directories, etc.)
+   Without --oneshot Filer goes to full unix daemon mode.
+
+------------------------------------------------------------------------------
+D. Configuration
+
+   Configuration is JSON object in file - however, lines starting with "#"
+or "//" are allowed and will be ignored as comments. File must contain valid
+JSON object, containing configuration. See also warden_filer.cfg as example.
+
+   warden - can contain Warden 3 configuration (see Warden doc), or path
+            to Warden configuration file
+   sender - configuration section for sender mode
+      dir - directory, whose "incoming" subdir will be checked for Idea
+            events to send out
+      node - o information about detector to be prepended into event Node
+             array (see Idea doc)
+   receiver - configuration section for receiver mode
+      dir - directory, whose "incoming" subdir will serve as target for events
+      filter - filter fields for Warden query (see Warden and Idea doc,
+               possible keys: cat, nocat, group, nogroup, tag, notag)
+
+------------------------------------------------------------------------------
+E. Directories and locking issues
+
+   Working directories are not just simple paths, but contain structure,
+loosely mimicked from Maildir with slightly changed names to avoid first look
+confusion. Simple path suffers locking issue: when one process saves file
+there, another process has no way to know whether file is already complete
+or not, and starting to read prematurely can lead to corrupted data read.
+Also, two concurrent processes may decide to work on one file, stomping on
+others legs. So, your scripts and tools inserting data or taking data from
+working directories must obey simple protocols.
+
+ 1. Inserting file
+
+   * Use "temp" subdirectory to create new file; filename is arbitrary, but
+     must be unique among all subdirectories. 
+
+   * When done writing, rename the file into "incoming" subdir. Rename is
+     atomic operation, so for readers, file will appear either nonexistent
+     or complete.
+
+   For simple usage (bash scripts, etc.), just creating sufficiently random
+   filename in "temp" and then moving into "incoming" may be enough.
+   Concatenating $RANDOM couple of times will do. :)
+   
+   For advanced or potentially concurrent usage inserting enough of unique
+   information into name is recommended - Filer itself uses hostname, pid,
+   unixtime, milliseconds, device number and file inode number to avoid
+   locking issues both on local and network based filesystems and to be
+   prepared for hight traffic.
+
+ 2. Picking up file
+
+   * Rename the file to work with into "temp" directory.
+
+   * Do whatever you want with contents, and when finished, rename file back
+     into "incoming", or remove, or move somewhere else, or move into "errors"
+     directory, after all, it's your file.
+
+   Note that in concurrent environment file can disappear between directory
+   enumeration and attempt to rename - then just pick another one (and
+   repeat), someone was swifter.)
+
+------------------------------------------------------------------------------
+Copyright (C) 2011-2015 Cesnet z.s.p.o
diff --git a/warden3/contrib/warden_filer/warden_filer.cfg b/warden3/contrib/warden_filer/warden_filer.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..1c7ee0c044e73ba20b131952161b6fa71e963c8f
--- /dev/null
+++ b/warden3/contrib/warden_filer/warden_filer.cfg
@@ -0,0 +1,40 @@
+{
+    // Warden config can be also referenced as:
+    // "warden": "/path/to/warden_client.cfg"
+    "warden": {
+        "url": "https://example.com/warden3",
+        "cafile": "tcs-ca-bundle.pem",
+        "timeout": 10,
+        "errlog": {"level": "debug"},
+        "filelog": {"level": "debug"},
+        "idstore": "myclient.id",
+        "name": "com.example.warden.test",
+        "secret": "SeCrEt"
+    },
+    "sender": {
+        // Maildir like directory, whose "incoming" subdir will be checked
+        // for Idea events to send out
+        "dir": "warden_sender",
+        // Optional information about detector to be prepended into Idea Node array
+        "node": {
+            "Name": "cz.example.warden.test",
+            "Type": ["External"],
+            "SW": ["warden_filer"],
+            "AggrWin": "00:05:00",
+            "Note": "Test warden_filer sender"
+        }
+    },
+    "receiver": {
+        // Maildir like directory, whose "incoming" will serve as target for events
+        "dir": "warden_receiver",
+        // Optional filter fields for Warden query
+        "filter": {
+            "cat": ["Test", "Recon.Scanning"],
+            "nocat": null,
+            "group": ["cz.cesnet"],
+            "nogroup": null,
+            "tag": null,
+            "notag": ["Honeypot"]
+        }
+    }
+}
diff --git a/warden3/contrib/warden_filer/warden_filer.py b/warden3/contrib/warden_filer/warden_filer.py
new file mode 100644
index 0000000000000000000000000000000000000000..62b4852592554aee8378603c4a561cbdaa1ee567
--- /dev/null
+++ b/warden3/contrib/warden_filer/warden_filer.py
@@ -0,0 +1,365 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011-2015 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
+import json
+import string
+import os
+import sys
+import errno
+import socket
+import time
+import logging
+import signal
+import lockfile
+import argparse
+from os import path, mkdir
+from random import choice, randint;
+from daemon import DaemonContext
+from daemon.pidlockfile import TimeoutPIDLockFile
+
+
+class NamedFile(object):
+    """ Wrapper class for file objects, which allows and tracks filename
+        changes.
+    """
+
+    def __init__(self, pth, name, fd=None):
+        self.name = name
+        self.path = pth
+        if fd:
+            self.f = os.fdopen(fd, "w+b")
+        else:
+            self.f = None
+
+
+    def __str__(self):
+        return "%s(%s, %s)" % (type(self).__name__, self.path, self.name)
+
+
+    def get_path(self, basepath=None, name=None):
+        return path.join(basepath or self.path, name or self.name)
+
+
+    def open(self, mode):
+        return open(self.get_path(), mode)
+
+
+    def moveto(self, destpath):
+        os.rename(self.get_path(), self.get_path(basepath=destpath))
+        self.path = destpath
+
+
+    def rename(self, newname):
+        os.rename(self.get_path(), self.get_path(name=newname))
+        self.name = newname
+
+
+    def remove(self):
+        os.remove(self.get_path())
+
+
+
+class SafeDir(object):
+    """ Maildir like directory for safe file exchange.
+        - Producers are expected to drop files into "temp" under globally unique
+          filename and rename it into "incoming" atomically (newfile method)
+        - Workers pick files in "incoming", rename them into "temp",
+          do whatever they want, and either discard them or move into
+          "errors" directory
+    """
+
+    def __init__(self, p):
+        self.path = self._ensure_path(p)
+        self.incoming = self._ensure_path(path.join(self.path, "incoming"))
+        self.errors = self._ensure_path(path.join(self.path, "errors"))
+        self.temp = self._ensure_path(path.join(self.path, "temp"))
+        self.hostname = socket.gethostname()
+        self.pid = os.getpid()
+
+
+    def __str__(self):
+        return "%s(%s)" % (type(self).__name__, self.path)
+
+
+    def _ensure_path(self, p):
+        try:
+            mkdir(p)
+        except OSError:
+            if not path.isdir(p):
+                raise
+        return p
+
+
+    def _get_new_name(self, device=0, inode=0):
+        return "%s.%d.%f.%d.%d" % (
+            self.hostname, self.pid, time.time(), device, inode)
+
+
+    def newfile(self):
+        """ Creates file with unique filename within this SafeDir.
+            - hostname takes care of network filesystems
+            - pid distinguishes two daemons on one machine
+              (we are not multithreaded, so this is enough)
+            - time in best precision supported narrows window within process
+            - device/inode makes file unique on particular filesystem
+            In fact, device/inode is itself enough for uniqueness, however
+            if we mandate wider format, users can use simpler form with
+            random numbers instead of device/inode, if they choose to,
+            and it will still ensure reasonable uniqueness.
+        """
+
+        # Note: this simpler device/inode algorithm replaces original,
+        #       which checked uniqueness among all directories by atomic
+        #       links.
+
+        # First find and open name unique within temp
+        tmpname = None
+        while not tmpname:
+            tmpname = self._get_new_name()
+            try:
+                fd = os.open(path.join(self.temp, tmpname), os.O_CREAT | os.O_RDWR | os.O_EXCL)
+            except OSError as e:
+                if e.errno != errno.EEXIST:
+                    raise   # other errors than duplicates should get noticed
+                tmpname = None
+        # Now we know device/inode, rename to make unique within system
+        stat = os.fstat(fd)
+        newname = self._get_new_name(stat.st_dev, stat.st_ino)
+        nf = NamedFile(self.temp, tmpname, fd)
+        nf.rename(newname)
+        return nf
+
+
+    def get_incoming(self):
+        return [NamedFile(self.incoming, n) for n in os.listdir(self.incoming)]
+
+
+
+def receiver(config, wclient, sdir, oneshot):
+    poll_time = config.get("poll_time", 5)
+    conf_filt = config.get("filter", {})
+    filt = {}
+    # Extract filter explicitly to be sure we have right param names for getEvents
+    for s in ("cat", "nocat", "tag", "notag", "group", "nogroup"):
+        filt[s] = conf_filt.get(s, None)
+
+    while running_flag:
+        events = wclient.getEvents(**filt)
+        count_ok = count_err = 0
+        while events:
+            for event in events:
+                try:
+                    nf = None
+                    nf = sdir.newfile()
+                    with nf.f as f:
+                        data = json.dumps(event)
+                        f.write(data)
+                    nf.moveto(sdir.incoming)
+                    count_ok += 1
+                except Exception as e:
+                    Error("Error saving event", wclient.logger, exc=sys.exc_info(),
+                          detail={"file": str(nf), "event_id": event.get("ID"), "sdir": sdir.path})
+                    count_err += 1
+            wclient.logger.info(
+                "warden_filer: received %d, errors %d"
+                % (count_ok, count_err))
+            if oneshot:
+                events = None
+            else:
+                events = wclient.getEvents(**filt)
+        if oneshot:
+            terminate_me(None, None)
+        else:
+            time.sleep(poll_time)
+
+
+
+def sender(config, wclient, sdir, oneshot):
+    send_events_limit = config.get("send_events_limit", 500)
+    poll_time = config.get("poll_time", 5)
+    node = config.get("node", None)
+
+    while running_flag:
+        nflist = sdir.get_incoming()
+        if oneshot:
+            terminate_me(None, None)
+        while running_flag and not nflist:
+            time.sleep(poll_time)
+            nflist = sdir.get_incoming()
+        # count chunk iterations rounded up
+        count = len(nflist)
+        for i in range(0, count, send_events_limit):
+            # process one at most send_events_limit long chunk
+            events = []
+            nf_sent = []
+            for j in range(i, min(i+send_events_limit, count)):
+                nf = nflist[j]
+                # prepare event array from files
+                try:
+                    nf.moveto(sdir.temp)
+                except Exception:
+                    pass    # Silently go to next filename, somebody else might have interfered
+                try:
+                    with nf.open("rb") as fd:
+                        data = fd.read()
+                        event = json.loads(data)
+                        if node:
+                            nodelist = event.setdefault("Node", [])
+                            nodelist.insert(0, node)
+                        events.append(event)
+                        nf_sent.append(nf)
+                except Exception as e:
+                    Error("Error loading event", wclient.logger, exc=sys.exc_info(),
+                          detail={"file": str(nf), "sdir": sdir.path})
+                    nf.moveto(sdir.errors)
+
+            res = wclient.sendEvents(events)
+
+            count_ok = count_err = count_retry = 0
+            if isinstance(res, Error):
+                try:
+                    errs = res.detail["errors"]
+                except (KeyError, AttributeError, TypeError):
+                    errs = None
+                if errs:
+                    # Event errors - move bad events into "errors"
+                    for e in errs.iterkeys():
+                        try:
+                            idx = int(e)
+                        except ValueError:
+                            continue
+                        nf_sent[idx].moveto(sdir.errors)
+                        nf_sent[idx] = None
+                        count_err += 1
+                else:
+                    # Global errors - move all events back to "incoming" for attempt in next round
+                    for idx in range(len(nf_sent)):
+                        nf_sent[idx].moveto(sdir.incoming)
+                        nf_sent[idx] = None
+                        count_retry += 1
+            # Cleanup rest - succesfully sent events
+            for name in nf_sent:
+                if name:
+                    name.remove()
+                    count_ok += 1
+            wclient.logger.info(
+                "warden_filer: saved %d, errors %d, retreated %d"
+                % (count_ok, count_err, count_retry))
+
+
+
+def get_logger_files(logger):
+    """ Return file objects of loggers """
+    files = []
+    for handler in logger.handlers:
+        if hasattr(handler, 'stream') and hasattr(handler.stream, 'fileno'):
+            files.append(handler.stream)
+        if hasattr(handler, 'socket') and hasattr(handler.socket, 'fileno'):
+            files.append(handler.socket)
+    return files
+
+
+
+running_flag = True     # Daemon cleanly exits when set to False
+
+def terminate_me(signum, frame):
+    global running_flag
+    running_flag = False
+
+
+
+class DummyContext(object):
+    """ In one shot mode we use this instead of DaemonContext """
+    def __enter__(self): pass
+    def __exit__(self, *exc): pass
+
+
+
+def get_args():
+    argp = argparse.ArgumentParser(
+        description="Save Warden events as files or send files to Warden")
+    argp.add_argument("func",
+        choices=["sender", "receiver"],
+        action="store",
+        help="choose direction: sender picks up files and submits them to "
+              "Warden, receiver pulls events from Warden and saves them as files")
+    argp.add_argument("-c", "--config",
+        default=path.splitext(__file__)[0]+".cfg",
+        dest="config",
+        help="configuration file path")
+    argp.add_argument('--oneshot',
+        default=False,
+        dest="oneshot",
+        action="store_true",
+        help="don't daemonise, run just once")
+    return argp.parse_args()
+
+
+
+def get_configs():
+    config = read_cfg(args.config)
+
+    # Allow inline or external Warden config
+    wconfig = config.get("warden", "warden_client.cfg")
+    if isinstance(wconfig, str):
+        wconfig = read_cfg(wconfig)
+
+    fconfig = config.get(args.func, {})
+
+    return wconfig, fconfig
+
+
+
+if __name__ == "__main__":
+
+    args = get_args()
+
+    function = sender if args.func=="sender" else receiver
+
+    wconfig, fconfig = get_configs()
+
+    oneshot = args.oneshot
+
+    safe_dir = SafeDir(fconfig.get("dir", args.func))
+
+    wclient = Client(**wconfig)
+
+    if oneshot:
+        daemon = DummyContext()
+    else:
+        work_dir = fconfig.get("work_dir", ".")
+        chroot_dir = fconfig.get("chroot_dir")
+        umask = fconfig.get("umask", 0)
+        pid_file = fconfig.get("pid_file", "/var/run/warden_filer.pid")
+        uid = fconfig.get("uid")
+        gid = fconfig.get("gid")
+    
+        daemon = DaemonContext(
+            working_directory = work_dir,
+            chroot_directory = chroot_dir,
+            umask = umask,
+            pidfile = TimeoutPIDLockFile(pid_file, acquire_timeout=0),
+            uid = uid,
+            gid = gid,
+            files_preserve = get_logger_files(wclient.logger),
+            signal_map = {
+                signal.SIGTERM: terminate_me,
+                signal.SIGINT: terminate_me,
+                signal.SIGHUP: None
+            }
+        )
+        
+    try:
+        with daemon:
+            wclient.logger.info("Starting %s" % args.func)
+            function(fconfig, wclient, safe_dir, oneshot)
+            wclient.logger.info("Exiting %s" % args.func)
+    except lockfile.Error as e:
+        wclient.logger.critical("Error acquiring lockfile %s (%s)"
+            % (daemon.pidfile.lock_file, type(e).__name__))
+    except Exception:
+        wclient.logger.critical("%s daemon error" % args.func, exc_info=sys.exc_info())