diff --git a/warden3/contrib/connectors/hp-kippo/warden3-kippo-sender.py b/warden3/contrib/connectors/hp-kippo/warden3-kippo-sender.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf105f68fb069556e9ae38985388099d342b7751
--- /dev/null
+++ b/warden3/contrib/connectors/hp-kippo/warden3-kippo-sender.py
@@ -0,0 +1,130 @@
+#!/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
+from time import time, gmtime
+from math import trunc
+from uuid import uuid4
+# from pprint import pprint
+from os import path
+# from random import randint, randrange, choice, random;
+# from base64 import b64encode;
+
+import MySQLdb as my
+import MySQLdb.cursors as mycursors
+
+def get_precise_timestamp(epoch=None):
+    t = epoch if epoch else time()
+    us = trunc((t-trunc(t))*1000000)
+    g = gmtime(t)
+    iso = '%04d-%02d-%02dT%02d:%02d:%02d.%0dZ' % (g[0:6]+(us,))
+    return iso
+
+def gen_event_idea(client_name, detect_time, win_start_time, win_end_time, conn_count, src_ip4, dst_ip4):
+
+  event = {
+     "Format": "IDEA0",
+     "ID": str(uuid4()),
+     # "CreateTime": get_precise_timestamp(),
+     "DetectTime": detect_time,
+     "WinStartTime": win_start_time,
+     "WinEndTime": win_end_time,
+     # "EventTime": get_precise_timestamp(),
+     # "CeaseTime": get_precise_timestamp(),
+     "Category": ["Attempt.Login", "Test"],
+     # "Ref": ["cve:CVE-%s-%s" % (randstr(string.digits, 4), randstr()), "http://www.example.com/%s" % randstr()],
+     # "Confidence": random(),
+     "Note": "SSH login attempt",
+     "ConnCount": conn_count,
+#       "ConnCount": choice([randint(0, 65535), "asdf"]),    # Send wrong event sometimes
+     "Source": [
+        {
+           # "Type": ["Phishing"],
+           "IP4": [src_ip4],
+           # "IP6": [randip6() for i in range(randrange(1, 5))],
+           # "Hostname": ["example.com"],
+           # "Port": [src_ip4_port],
+           # "AttachHand": ["att1"],
+           # "Netname": ["arin:TEST-NET-1"]
+        }
+     ],
+     "Target": [
+        {
+           "IP4": [dst_ip4],
+           # "IP6": [randip6() for i in range(randrange(1, 5))],
+           # "URL": ["http://example.com/%s" % randstr()],
+           "Proto": ["tcp", "ssh"],
+           "Port" : [22]
+           # "Netname": ["arin:TEST-NET-1"]
+        }
+     ],
+     # "Attach": [
+     #    {
+     #       "Handle": "att1",
+     #       "FileName": [randstr()],
+     #       "Type": ["Malware"],
+     #       "ContentType": "application/octet-stream",
+     #       "Hash": ["sha1:%s" % randstr(string.hexdigits, 24)],
+     #       "Size": 46,
+     #       "Ref": ["cve:CVE-%s-%s" % (randstr(string.digits, 4), randstr())],
+     #       "ContentEncoding": "base64",
+     #       "Content": b64encode(randstr())
+     #    }
+     # ],
+     "Node": [
+        {
+           "Name": client_name,
+           "Tags": ["Connection","Honeypot","Recon"],
+           "SW": ["Kippo"],
+           "AggrWin": "00:05:00"
+        }
+     ]
+  }
+
+  return event
+
+def main():
+    wclient = Client(**read_cfg("warden_client.cfg"))
+    appconf = read_cfg("warden_client-kippo.cfg")
+
+    con = my.connect( host=appconf['dbhost'], user=appconf['dbuser'], passwd=appconf['dbpass'],
+                      db=appconf['dbname'], port=appconf['dbport'], cursorclass=mycursors.DictCursor)
+    
+    crs = con.cursor()
+
+    events = []
+    query = ["SELECT UNIX_TIMESTAMP(s.starttime) as starttime, s.ip, COUNT(s.id) as attack_scale \
+              FROM sessions s \
+              LEFT JOIN input i ON s.id = i.session \
+              WHERE s.starttime > DATE_SUB(UTC_TIMESTAMP(), INTERVAL + %s MINUTE) \
+              GROUP BY s.ip ORDER BY s.starttime ASC;"]
+
+
+    # crs.execute("".join(query), [appconf['a_win']])
+    crs.execute("".join(query), [5])
+    rows = crs.fetchall()
+    for row in rows:
+      dtime = get_precise_timestamp(row['starttime'])
+      etime = get_precise_timestamp(time())
+      stime = get_precise_timestamp(time() - appconf['a_win'] * 60)
+      events.append(gen_event_idea(client_name=appconf['name'], detect_time = dtime, win_start_time = stime, win_end_time = etime, conn_count = row['attack_scale'], src_ip4 = row['ip'], dst_ip4 = appconf['sensor_ip4']))
+        
+    print "=== Sending ==="
+    start = time()
+    ret = wclient.sendEvents(events)
+    if not ret:
+        print "%d event(s) successfully delivered." % len(rows)
+    else:
+        print ret
+
+    print "Time: %f" % (time()-start)
+
+
+
+if __name__ == "__main__":
+    main()
diff --git a/warden3/contrib/connectors/hp-kippo/warden_client-kippo.cfg b/warden3/contrib/connectors/hp-kippo/warden_client-kippo.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..5d9c65892ec42750de88d8d5db8343a4cd0a247c
--- /dev/null
+++ b/warden3/contrib/connectors/hp-kippo/warden_client-kippo.cfg
@@ -0,0 +1,12 @@
+{
+    "name": "cz.cesnet.server.kippo",
+    "sensor_ip4": "195.113.x.x",
+
+    "idstore": "warden_client.id",
+    "dbhost": "localhost",
+    "dbuser": "kippo",
+    "dbpass": "kippopass",
+    "dbname": "kippo",
+    "dbport": 3306,
+    "a_win": 5
+}
diff --git a/warden3/contrib/connectors/hp-kippo/warden_client.cfg b/warden3/contrib/connectors/hp-kippo/warden_client.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..9d5daa6d98557fc8d5e665dc07b716cff58f8023
--- /dev/null
+++ b/warden3/contrib/connectors/hp-kippo/warden_client.cfg
@@ -0,0 +1,18 @@
+{
+    "url": "https://warden-dev.cesnet.cz/warden3",
+
+    "certfile": "/etc/ssl/certs/server.cesnet.cz.pem",
+    "keyfile": "/etc/ssl/private/server.cesnet.cz.key",
+    "cafile": "/etc/ssl/certs/tcs-ca-bundle.pem",
+
+    "timeout": 60,
+    "recv_events_limit": 6000,
+
+    "errlog": {"level": "debug"},
+    "filelog": {"file": "warden_client.log", "level": "warning"},
+    #"syslog": {"socket": "/dev/log", "facility": "local7", "level": "warning"},
+
+    #"name": "xxx"
+    "secret": "xxxxxxxxxxxxxxxxxxxx"
+}
+
diff --git a/warden3/contrib/connectors/hp-kippo/warden_client.py b/warden3/contrib/connectors/hp-kippo/warden_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a807fca16533ec96adcd7156123f6d395b33eaa
--- /dev/null
+++ b/warden3/contrib/connectors/hp-kippo/warden_client.py
@@ -0,0 +1,425 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011-2013 Cesnet z.s.p.o
+# Use of this source is governed by a 3-clause BSD-style license, see LICENSE file.
+
+import json, httplib, ssl, socket, logging, logging.handlers
+from urlparse import urlparse
+from urllib import urlencode
+from sys import stderr, exc_info
+from pprint import pformat
+from traceback import format_tb
+from os import path
+
+
+class HTTPSConnection(httplib.HTTPSConnection):
+    '''
+    Overridden to allow peer certificate validation, configuration
+    of SSL/ TLS version and cipher selection.  See:
+    http://hg.python.org/cpython/file/c1c45755397b/Lib/httplib.py#l1144
+    and `ssl.wrap_socket()`
+    '''
+    def __init__(self, host, **kwargs):
+        self.ciphers = kwargs.pop('ciphers',None)
+        self.ca_certs = kwargs.pop('ca_certs',None)
+        self.ssl_version = kwargs.pop('ssl_version',ssl.PROTOCOL_SSLv23)
+
+        httplib.HTTPSConnection.__init__(self,host,**kwargs)
+
+    def connect(self):
+        sock = socket.create_connection( (self.host, self.port), self.timeout)
+
+        if self._tunnel_host:
+            self.sock = sock
+            self._tunnel()
+
+        self.sock = ssl.wrap_socket(
+            sock,
+            keyfile = self.key_file,
+            certfile = self.cert_file,
+            ca_certs = self.ca_certs,
+            cert_reqs = ssl.CERT_REQUIRED if self.ca_certs else ssl.CERT_NONE,
+            ssl_version = self.ssl_version)
+
+
+
+class Error(Exception):
+    """ Object for returning error messages to calling application.
+        Caller can test whether it received data or error by checking
+        isinstance(res, Error).
+        However if he does not want to deal with errors altogether,
+        this error object also returns False value if used in Bool
+        context (e.g. in "if res: print res" print is not evaluated),
+        and also acts as empty iterator (e.g. in "for e in res: print e"
+        print is also not evaluated).
+        Also, it can be raised as an exception.
+    """
+
+    def __init__(self, message, logger=None, error=None, prio="error", method=None,
+            req_id=None, detail=None, exc=None):
+
+        self.message = message
+        self.error = error
+        self.method = method
+        self.req_id = req_id
+        self.detail = detail
+        (self.exctype, self.excval, self.exctb) = exc or (None, None, None)
+        self.cause = self.excval # compatibility with other exceptions
+        if logger:
+            getattr(logger, prio, "error")(str(self))
+            info = self.info_str()
+            if info:
+                logger.info(info)
+            debug = self.debug_str()
+            if debug:
+                logger.debug(debug)
+
+
+    def __len__ (self):
+        """ In list or iterable context we're empty """
+        return 0
+
+
+    def __iter__(self):
+        """ We are the iterator """
+        return self
+
+
+    def next(self):
+        """ In list or iterable context we're empty """
+        raise StopIteration
+
+
+    def __bool__(self):
+        """ In boolean context we're never True """
+        return False
+
+
+    def __str__(self):
+        out = []
+        out.append("(%s)" % (self.error or "local"))
+        if self.method is not None:
+            out.append(" in %s" % self.method)
+        if self.req_id is not None:
+            out.append("(%8x)" % self.req_id)
+        if self.message is not None:
+            out.append(": %s" % self.message)
+        if self.excval is not None:
+            out.append(" - cause was %s: %s" % (type(self.excval).__name__, str(self.excval)))
+        return "".join(out)
+
+
+    def info_str(self):
+        return ("Detail: %s" % pformat(self.detail)) or ""
+
+
+    def debug_str(self):
+        out = []
+        if self.excval is not None:
+            out.append("Exception %s: %s\n" % (type(self.excval).__name__, str(self.excval)))
+        if self.exctb is not None:
+            out.append("Traceback:\n%s" % "".join(format_tb(self.exctb)))
+        return "".join(out)
+
+
+
+class Client(object):
+
+    def __init__(self,
+            url,
+            certfile=None,
+            keyfile=None,
+            cafile=None,
+            timeout=60,
+            recv_events_limit=6000,
+            errlog={"level": "debug"},
+            syslog=None,
+            filelog=None,
+            idstore=None,
+            name="warden_client",
+            secret=None):
+
+        self.name = name
+        self.secret = secret
+        # Init logging as soon as possible and make sure we don't
+        # spit out exceptions but just log or return Error objects
+        self.init_log(errlog, syslog, filelog)
+
+        self.url = urlparse(url, allow_fragments=False)
+
+        self.conn = None
+
+        base = path.join(path.dirname(__file__))
+        self.certfile = path.join(base, certfile or "cert.pem")
+        self.keyfile  = path.join(base, keyfile or "key.pem")
+        self.cafile = path.join(base, cafile or "ca.pem")
+        self.timeout = int(timeout)
+        self.recv_events_limit = int(recv_events_limit)
+        self.idstore = path.join(base, idstore) if idstore is not None else None
+
+        self.ciphers = 'TLS_RSA_WITH_AES_256_CBC_SHA'
+        self.sslversion = ssl.PROTOCOL_TLSv1
+
+
+    def init_log(self, errlog, syslog, filelog):
+
+        def loglevel(lev):
+            try:
+                return int(getattr(logging, lev.upper()))
+            except (AttributeError, ValueError):
+                self.logger.warning("Unknown loglevel \"%s\", using \"debug\"" % lev)
+                return logging.DEBUG
+
+        def facility(fac):
+            try:
+                return int(getattr(logging.handlers.SysLogHandler, "LOG_" + fac.upper()))
+            except (AttributeError, ValueError):
+                self.logger.warning("Unknown syslog facility \"%s\", using \"local7\"" % fac)
+                return logging.handlers.SysLogHandler.LOG_LOCAL7
+
+        form = "%(filename)s[%(process)d]: (%(levelname)s) %(name)s %(message)s"
+        format_notime = logging.Formatter(form)
+        format_time = logging.Formatter('%(asctime)s ' + form)
+
+        self.logger = logging.getLogger(self.name)
+        self.logger.propagate = False   # Don't bubble up to root logger
+        self.logger.setLevel(logging.DEBUG)
+
+        if errlog is not None:
+            el = logging.StreamHandler(stderr)
+            el.setFormatter(format_time)
+            el.setLevel(loglevel(errlog.get("level", "debug")))
+            self.logger.addHandler(el)
+
+        if filelog is not None:
+            try:
+                fl = logging.FileHandler(
+                    filename=path.join(
+                        path.dirname(__file__),
+                        filelog.get("file", "%s.log" % self.name)))
+                fl.setLevel(loglevel(filelog.get("level", "warning")))
+                fl.setFormatter(format_time)
+                self.logger.addHandler(fl)
+            except Exception as e:
+                Error("Unable to setup file logging", self.logger, exc=exc_info())
+
+        if syslog is not None:
+            try:
+                sl = logging.handlers.SysLogHandler(
+                    address=syslog.get("socket", "/dev/log"),
+                    facility=facility(syslog.get("facility", "local7")))
+                sl.setLevel(loglevel(syslog.get("level", "warning")))
+                sl.setFormatter(format_notime)
+                self.logger.addHandler(sl)
+            except Exception as e:
+                Error("Unable to setup syslog logging", self.logger, exc=exc_info())
+
+        if not (errlog or filelog or syslog):
+            # User wants explicitly no logging, so let him shoot his socks off.
+            # This silences complaining of logging module about no suitable
+            # handler.
+            self.logger.addHandler(logging.NullHandler())
+
+
+    def connect(self):
+
+        try:
+            if self.url.scheme=="https":
+                self.conn = HTTPSConnection(
+                    self.url.netloc,
+                    key_file = self.keyfile,
+                    cert_file = self.certfile,
+                    timeout = self.timeout,
+                    ciphers = self.ciphers,
+                    ca_certs = self.cafile,
+                    ssl_version = self.sslversion)
+            elif self.url.scheme=="http":
+                self.conn = httplib.HTTPConnection(
+                    self.url.netloc,
+                    timeout = self.timeout)
+            else:
+                return Error("Don't know how to connect to \"%s\"" % self.url.scheme, self.logger,
+                        detail={"url": self.url.geturl()})
+        except Exception:
+            return Error("HTTPS connection failed", self.logger, exc=exc_info(),
+                detail={
+                    "url": self.url.geturl(),
+                    "timeout": self.timeout,
+                    "key_file": self.keyfile,
+                    "cert_file": self.certfile,
+                    "cafile": self.cafile,
+                    "ciphers": self.ciphers,
+                    "ssl_version": self.sslversion})
+
+        return True
+
+
+    def sendRequest(self, func="", payload=None, **kwargs):
+
+        if self.secret is None:
+            kwargs["client"] = self.name
+        else:
+            kwargs["secret"] = self.secret
+
+        if kwargs:
+            for k in kwargs.keys():
+                if kwargs[k] is None:
+                    del kwargs[k]
+            argurl = "?" + urlencode(kwargs, doseq=True)
+        else:
+            argurl = ""
+
+        try:
+            if payload is None:
+                data = ""
+            else:
+                data = json.dumps(payload)
+        except:
+            return Error("Serialization to JSON failed", self.logger,
+                exc=exc_info(), method=func, detail=payload)
+
+        self.headers = {
+            "Content-Type": "application/json",
+            "Accept": "application/json",
+            "Content-Length": str(len(data))
+        }
+
+        # We are connecting here at first use instead of in
+        # constructor, because constructor cannot return data/errors
+        # and we don't want to spit exceptions into user's face
+        # And maaaybee sometime we will implement reconnection on errors
+        if self.conn is None:
+            err = self.connect()
+            if not err:
+                return err  # either False of Error instance
+
+        loc = '%s/%s%s' % (self.url.path, func, argurl)
+        try:
+            self.conn.request("POST", loc, data, self.headers)
+        except:
+            return Error("Sending of request to server failed", self.logger,
+                exc=exc_info(), method=func, detail={
+                    "loc": loc,
+                    "headers": self.headers,
+                    "data": data})
+
+        try:
+            res = self.conn.getresponse()
+        except:
+            return Error("HTTP reply failed", self.logger, method=func, exc=exc_info())
+
+        try:
+            response_data = res.read()
+        except:
+            return Error("Fetching HTTP data from server failed", self.logger, method=func, exc=exc_info())
+
+        if res.status==httplib.OK:
+            try:
+                data = json.loads(response_data)
+            except:
+                data = Error("JSON message parsing failed", self.logger,
+                    exc=exc_info(), method=func, detail={"response": response_data})
+        else:
+            try:
+                data = json.loads(response_data)
+                data["error"]   # trigger exception if not dict or no error key
+            except:
+                data = Error("Generic server HTTP error", self.logger,
+                    method=func,
+                    error=res.status,
+                    exc=exc_info(),
+                    detail={"response": response_data})
+            else:
+                data = Error(data.get("message", None), self.logger,
+                    method=data.get("method", None),
+                    error=res.status,
+                    req_id=data.get("req_id", None),
+                    detail=data.get("detail", None))
+
+        return data
+
+
+    def _saveID(self, id, idstore=None):
+        idf = idstore or self.idstore
+        if not idf:
+            return False
+        try:
+            with open(idf, "w+") as f:
+                f.write(str(id))
+        except (ValueError, IOError) as e:
+            # Use Error instance just for proper logging
+            Error("Writing id file \"%s\" failed" % idf, self.logger,
+                prio="info", exc=exc_info(), detail={"idstore": idf})
+        return id
+
+
+    def _loadID(self, idstore=None):
+        idf = idstore or self.idstore
+        if not idf:
+            return None
+        try:
+            with open(idf, "r") as f:
+                id = int(f.read())
+        except (ValueError, IOError) as e:
+            Error("Reading id file \"%s\" failed, relying on server" % idf,
+                self.logger, prio="info", exc=exc_info(), detail={"idstore": idf})
+            id = None
+        return id
+
+
+    def getDebug(self):
+        return self.sendRequest("getDebug")
+
+
+    def getInfo(self):
+        return self.sendRequest("getInfo")
+
+
+    def sendEvents(self, events=[]):
+        res = self.sendRequest(
+            "sendEvents", payload=events)
+        return res
+
+
+    def getEvents(self, id=None, idstore=None, count=1,
+            cat=None, nocat=None,
+            tag=None, notag=None,
+            group=None, nogroup=None):
+
+        if not id:
+            id = self._loadID(idstore)
+
+        res = self.sendRequest(
+            "getEvents", id=id, count=count or self.recv_events_limit, cat=cat,
+            nocat=nocat, tag=tag, notag=notag, group=group, nogroup=nogroup)
+        if not res:
+            return res  # Should be Error instance
+
+        try:
+            events = res["events"]
+            newid = res["lastid"]
+        except KeyError:
+            return Error("Server returned bogus reply", self.logger,
+                method="getEvents", exc=exc_info(), detail={"response": res})
+
+        self._saveID(newid)
+
+        return events
+
+
+    def close(self):
+
+        if hasattr(self, "conn") and hasattr(self.conn, "close"):
+            self.conn.close()
+
+
+    __del__ = close
+
+
+
+def read_cfg(cfgfile):
+    abspath = path.join(path.dirname(__file__), cfgfile)
+    with open(abspath, "r") as f:
+        stripcomments = "\n".join((l for l in f if not l.lstrip().startswith("#")))
+        return json.loads(stripcomments)