diff --git a/warden3/contrib/connectors/hp-labrea/labrea-idea.py b/warden3/contrib/connectors/hp-labrea/labrea-idea.py
new file mode 100755
index 0000000000000000000000000000000000000000..369083deeaad62db9eb0141978b0fcb7cf787c1c
--- /dev/null
+++ b/warden3/contrib/connectors/hp-labrea/labrea-idea.py
@@ -0,0 +1,500 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import re
+import time
+import heapq
+import optparse
+import itertools
+import signal
+import contextlib
+import uuid
+import json
+import os.path as pth
+from collections import namedtuple
+
+class FileWatcher(object):
+
+    def __init__(self, filename, tail=True):
+        self.filename = filename
+        self.open()
+        if tail and self.f:
+            self.f.seek(0, os.SEEK_END)
+
+    def open(self):
+        try:
+            self.f = open(self.filename, "r")
+            st = os.fstat(self.f.fileno())
+            self.inode, self.size = st.st_ino, st.st_size
+        except IOError:
+            self.f = None
+            self.inode = -1
+            self.size = -1
+
+    def _check_reopen(self):
+        try:
+            st = os.stat(self.filename)
+            cur_inode, cur_size = st.st_ino, st.st_size
+        except OSError as e:
+            cur_inode = -1
+            cur_size = -1
+        if cur_inode != self.inode or cur_size < self.size:
+            self.close()
+            self.open()
+
+    def readline(self):
+        if self.f:
+            self.open()
+            if self.f:
+                return ""
+        res = self.f.readline()
+        if not res:
+            self._check_reopen()
+            if self.f:
+                return ""
+            res = self.f.readline()
+        return res
+
+    def close(self):
+        try:
+            if self.f:
+                self.f.close()
+        except IOError:
+            pass
+        self.inode = -1
+        self.size = -1
+
+    def __repr__(self):
+        return '%s("%s")' % (type(self).__name__, self.filename)
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.close()
+        return False
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        return self.readline()
+
+
+class WindowContextMgr(object):
+
+    def __init__(self, window=60*10, timeout=60*5, ideagen=None):
+        self.contexts = {}
+        self.update_timestamp = 0
+        self.window = window
+        self.timeout = timeout
+        self.ideagen = ideagen
+        self.first_update_queue = []
+        self.last_update_queue = []
+
+    def expire_queue(self, queue, time_key, window):
+        aggr_events = []
+        while queue:
+            ctx = queue[0][1]
+            if ctx not in self.contexts:
+                # event went away while processing another queue, so just clean up
+                heapq.heappop(queue)
+            elif self.contexts[ctx][time_key] < self.update_timestamp - window:
+                heapq.heappop(queue)
+                closed = self.ctx_close(self.contexts[ctx])
+                if closed is not None:
+                    aggr_events.append(closed)
+                    del self.contexts[ctx]
+            else:
+                break
+        return aggr_events
+    
+    def process(self, event=None, timestamp=None):
+        self.update_timestamp = timestamp
+
+        aggr_events = []
+
+        aggr_events.extend(self.expire_queue(self.first_update_queue, "first_update", self.window))
+        aggr_events.extend(self.expire_queue(self.last_update_queue, "last_update", self.timeout))
+
+        if event is not None:
+            ctx = self.match(event)
+            if ctx is not None:
+                if ctx not in self.contexts:
+                    self.contexts[ctx] = self.ctx_create(event)
+                    heapq.heappush(self.first_update_queue, (self.update_timestamp, ctx))
+                else:
+                    self.ctx_append(self.contexts[ctx], event)
+                    heapq.heappush(self.last_update_queue, (self.update_timestamp, ctx))
+
+        return aggr_events
+
+
+    def close(self):
+        aggr_events = []
+        for context in self.contexts.values():
+            closed = self.ctx_close(context)
+            if closed is not None:
+                aggr_events.append(closed)
+        self.contexts = {}
+        self.first_update_queue = []
+        self.last_update_queue = []
+        return aggr_events
+
+
+class PingContextMgr(WindowContextMgr):
+
+    def match(self, event):
+        return event.src_ip if 'Ping' in event.message else None
+
+    def ctx_create(self, event):
+        ctx = dict(
+            src_ip = event.src_ip,
+            tgt_ips = set([event.tgt_ip]),
+            count = 1,
+            first_update = self.update_timestamp,
+            last_update = self.update_timestamp
+        )
+        return ctx
+
+    def ctx_append(self, ctx, event):
+        ctx["tgt_ips"].add(event.tgt_ip)
+        ctx["count"] += 1
+        ctx["last_update"] = self.update_timestamp
+
+    def ctx_close(self, ctx):
+        return self.ideagen.gen_idea(
+            src = ctx["src_ip"],
+            src_ports = None,
+            targets = [(ip, []) for ip in ctx["tgt_ips"]],
+            detect_time = self.update_timestamp,
+            event_time = ctx["first_update"],
+            cease_time = ctx["last_update"],
+            count = ctx["count"],
+            template = "ping"
+        )
+
+
+class ConnectContextMgr(WindowContextMgr):
+
+    def match(self, event):
+        return event.src_ip if 'Connect' in event.message else None
+
+    def ctx_create(self, event):
+        ctx = dict(
+            src_ip = event.src_ip,
+            src_ports = set([event.src_port]),
+            tgt_ips_ports = {event.tgt_ip: set([event.tgt_port])},
+            count = 1,
+            first_update = self.update_timestamp,
+            last_update = self.update_timestamp
+        )
+        return ctx
+
+    def ctx_append(self, ctx, event):
+        tgt_ports = ctx["tgt_ips_ports"].setdefault(event.tgt_ip, set())
+        tgt_ports.add(event.tgt_port)
+        ctx["src_ports"].add(event.src_port)
+        ctx["count"] += 1
+        ctx["last_update"] = self.update_timestamp
+
+    def ctx_close(self, ctx):
+        return self.ideagen.gen_idea(
+            src = ctx["src_ip"],
+            src_ports = ctx["src_ports"],
+            targets = ctx["tgt_ips_ports"].items(),
+            detect_time = self.update_timestamp,
+            event_time = ctx["first_update"],
+            cease_time = ctx["last_update"],
+            count = ctx["count"],
+            template = "connect"
+        )
+
+
+class InboundContextMgr(ConnectContextMgr):
+
+    def match(self, event):
+        return event.src_ip if 'Inbound' in event.message else None
+
+    def ctx_close(self, ctx):
+        return self.ideagen.gen_idea(
+            src = ctx["src_ip"],
+            src_ports = ctx["src_ports"],
+            targets = ctx["tgt_ips_ports"].items(),
+            detect_time = self.update_timestamp,
+            event_time = ctx["first_update"],
+            cease_time = ctx["last_update"],
+            count = ctx["count"],
+            template = "synack"
+        )
+
+
+class IdeaGen(object):
+
+    def __init__(self, name, test=False):
+        self.name = name
+        self.test = test
+        self.template = {
+            "connect": {
+                "category": ["Recon.Scanning"],
+                "description": "TCP connection/scan",
+                "template": "labrea-001",
+                "note": "Connections from remote host to never assigned IP"
+            },
+            "ping": {
+                "category": ["Recon.Scanning"],
+                "description": "Ping scan",
+                "template": "labrea-002",
+                "note": "Ping requests from remote host to never assigned IP"
+            },
+            "synack": {
+                "category": ["Recon.Scanning"],
+                "description": "SYN/ACK connections/scan",
+                "template": "labrea-003",
+                "note": "Unsolicited SYN/ACK packet received from remote host to never assigned IP"
+            }
+        }
+
+    def format_timestamp(self, epoch):
+        return "%04d-%02d-%02dT%02d:%02d:%02dZ" % time.gmtime(epoch)[:6]
+
+    def gen_idea(self, src, src_ports, targets, detect_time, event_time, cease_time, count, template):
+        tmpl = self.template[template]
+        isource = {
+            "IP6" if ":" in src else "IP4": [src],
+            "Proto": ["tcp"]
+        }
+        if src_ports:
+            isource["Port"] = [int(port) for port in src_ports]
+        # Fold multiple IPs with the same portset
+        folded_tgt = {}
+        for tgt, ports in targets:
+            folded_tgt.setdefault(frozenset(ports), []).append(tgt)
+        itargets = []
+        for ports, tgt in folded_tgt.items():
+            itarget = {"Proto": ["tcp"]}
+            tgts4 = [ip for ip in tgt if ":" not in ip]
+            tgts6 = [ip for ip in tgt if ":" in ip]
+            if tgts4:
+                itarget["IP4"] = tgts4
+            if tgts6:
+                itarget["IP6"] = tgts6
+            if ports:
+                itarget["Port"] = [int(port) for port in ports]
+            itargets.append(itarget)
+        inode = {
+            "SW": ["LaBrea"],
+            "Type": ["Connection", "Tarpit"],
+            "Name": self.name
+        }
+        idea = {
+            "Format": "IDEA0",
+            "ID": str(uuid.uuid4()),
+            "Category": tmpl["category"] + ["Test"] if self.test else [],
+            "Description": tmpl["description"],
+            "DetectTime": self.format_timestamp(detect_time),
+            "EventTime": self.format_timestamp(event_time),
+            "CeaseTime": self.format_timestamp(cease_time),
+            "ConnectionCount": count,
+            "Note": tmpl["note"],
+            "_CESNET": {
+                "EventTemplate": tmpl["template"],
+                "testtime": detect_time
+            },
+            "Source": [isource],
+            "Target": itargets,
+            "Node": [inode]
+        }
+        return json.dumps(idea)
+
+
+def daemonize(
+        work_dir = None, chroot_dir = None,
+        umask = None, uid = None, gid = None,
+        pidfile = None, files_preserve = [], signals = {}):
+    # Dirs, limits, users
+    if chroot_dir is not None:
+        os.chdir(chroot_dir)
+        os.chroot(chroot_dir)
+    if umask is not None:
+        os.umask(umask)
+    if work_dir is not None:
+        os.chdir(work_dir)
+    if gid is not None:
+        os.setgid(gid)
+    if uid is not None:
+        os.setuid(uid)
+    # Doublefork, split session
+    if os.fork()>0:
+        os._exit(0)
+    os.setsid()
+    if os.fork()>0:
+        os._exit(0)
+    # Setup signal handlers
+    for (signum, handler) in signals.items():
+        signal.signal(signum, handler)
+    # Close descriptors
+    descr_preserve = set(f.fileno() for f in files_preserve)
+    maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+    if maxfd==resource.RLIM_INFINITY:
+        maxfd = 65535
+    for fd in range(maxfd, 3, -1):  # 3 means omit stdin, stdout, stderr
+        if fd not in descr_preserve:
+            try:
+                os.close(fd)
+            except Exception:
+                pass
+    # Redirect stdin, stdout, stderr to /dev/null
+    devnull = os.open(os.devnull, os.O_RDWR)
+    for fd in range(3):
+        os.dup2(devnull, fd)
+    # PID file
+    if pidfile is not None:
+        pidd = os.open(pidfile, os.O_RDWR|os.O_CREAT|os.O_EXCL|os.O_TRUNC)
+        os.write(pidd, str(os.getpid())+"\n")
+        os.close(pidd)
+        # Define and setup atexit closure
+        @atexit.register
+        def unlink_pid():
+            try:
+                os.unlink(pidfile)
+            except Exception:
+                pass
+
+
+def get_args():
+    optp = optparse.OptionParser(
+        usage="usage: %prog [options] logfile ...",
+        description="Watch LaBrea logfiles and generate Idea events into directory")
+    optp.add_option("-w", "--window",
+        default=900,
+        dest="window",
+        type="int",
+        action="store",
+        help="max detection window (default: %default)")
+    optp.add_option("-t", "--timeout",
+        default=300,
+        dest="timeout",
+        type="int",
+        action="store",
+        help="detection timeout (default: %default)")
+    optp.add_option("-n", "--name",
+        default=None,
+        dest="name",
+        type="string",
+        action="store",
+        help="Warden client name")
+    optp.add_option("--test",
+        default=False,
+        dest="test",
+        action="store_true",
+        help="Add Test category")
+    optp.add_option("-o", "--oneshot",
+        default=False,
+        dest="oneshot",
+        action="store_true",
+        help="process files and quit (do not daemonize)")
+    optp.add_option("-p", "--pid",
+        default=pth.join("/var/run", pth.splitext(pth.basename(sys.argv[0]))[0] + ".pid"),
+        dest="pid",
+        type="string",
+        action="store",
+        help="create PID file with this name (default: %default)")
+    optp.add_option("--realtime",
+        default=True,
+        dest="realtime",
+        action="store_true",
+        help="use system time along with log timestamps (default)")
+    optp.add_option("--norealtime",
+        dest="realtime",
+        action="store_false",
+        help="don't system time, use solely log timestamps")
+    return optp
+
+
+# 1493035442 Initial Connect - tarpitting: 89.163.242.15 56736 -> 195.113.254.182 9898
+# 1493037991 Inbound SYN/ACK: 185.62.190.15 21001 -> 195.113.252.222 15584
+# 1492790713 Inbound SYN/ACK: 62.210.130.232 80 -> 78.128.253.197 58376
+labrea_re_connect_inbound = re.compile(r'([0-9]+) ([^:]*:) ([^ ]+) ([0-9]+) -> ([^ ]+) ([0-9]+).*')
+connect_tuple = namedtuple("connect_tuple", ("timestamp", "message", "src_ip", "src_port", "tgt_ip", "tgt_port"))
+
+# 1493035442 Responded to a Ping: 88.86.96.25 -> 195.113.253.87 *
+labrea_re_ping = re.compile(r'([0-9]+) ([^:]*:) ([^ ]+) -> ([^ ]+).*')
+ping_tuple = namedtuple("ping_tuple", ("timestamp", "message", "src_ip", "tgt_ip"))
+
+re_list = [labrea_re_connect_inbound, labrea_re_ping]
+event_list = [connect_tuple, ping_tuple]
+
+running_flag = True
+reload_flag = False
+
+def terminate_me(signum, frame):
+    global running_flag
+    running_flag = False
+
+def reload_me(signum, frame):
+    global reload_flag
+    reload_flag = True
+
+def main():
+    # safedir output
+    # daemon
+    global reload_flag
+    optp = get_args()
+    opts, args = optp.parse_args()
+    if not args or opts.name is None:
+        optp.print_help()
+        sys.exit()
+    signal.signal(signal.SIGINT, terminate_me)
+    signal.signal(signal.SIGTERM, terminate_me)
+    signal.signal(signal.SIGHUP, reload_me)
+    if opts.oneshot:
+        files = [open(arg) for arg in args]
+    else:
+        files = [FileWatcher(arg) for arg in args]
+    with contextlib.nested(*files):
+        lines = itertools.izip(*files)
+        ideagen = IdeaGen(name=opts.name, test=opts.test)
+        contexts = [context(window=opts.window, timeout=opts.timeout, ideagen=ideagen) for context in [PingContextMgr, ConnectContextMgr, InboundContextMgr]]
+        for line_set in lines:
+            for line in line_set:
+                if line:
+                    line = line.strip()
+                    event = None
+                    for labrea_re, event_tuple in zip(re_list, event_list):
+                        match = labrea_re.match(line)
+                        if match:
+                            event = event_tuple(*match.groups())
+                            break
+                    aggr = []
+                    if event is not None:
+                        timestamp = int(event.timestamp)
+                    else:
+                        if opts.realtime:
+                            timestamp = int(time.time())
+                        else:
+                            timestamp = None
+                    if timestamp is not None:
+                        for context in contexts:
+                            aggr.extend(context.process(event, timestamp))
+                    for a in aggr:
+                        print(a)
+            if not running_flag:
+                break
+            if reload_flag:
+                for f in files:
+                    f.close()
+                    f.open()
+                    reload_flag = False
+            if not any(line_set):
+                time.sleep(1)
+            line = ""
+    aggr = []
+    for context in contexts:
+        aggr.extend(context.close())
+    for a in aggr:
+        print(a)
+
+main()