diff --git a/warden3/warden_client/warden_client_test.py b/warden3/warden_client/warden_client_test.py
index 1cc4b4dd7dd34e0992b920f9f8e2b33c7a99985f..3eb85c43af1a3775193c2bd4e3872980a04fba38 100755
--- a/warden3/warden_client/warden_client_test.py
+++ b/warden3/warden_client/warden_client_test.py
@@ -75,8 +75,9 @@ def gen_random_idea(client_name="cz.example.warden.test"):
        "WinEndTime": get_precise_timestamp(),
        "EventTime": get_precise_timestamp(),
        "CeaseTime": get_precise_timestamp(),
-       "Category": ["Abusive.Spam","Fraud.Copyright","Test"],
+       #"Category": ["Abusive.Spam","Abusive.Harassment","Malware","Fraud.Copyright","Test","Fraud.Phishing","Fraud.Scam"],
        # "Category": ["Abusive.Spam","Fraud.Copyright"],
+       "Category": [choice(["Abusive.Spam","Abusive.Harassment","Malware","Fraud.Copyright","Test","Fraud.Phishing","Fraud.Scam"]) for dummy in range(randint(1, 3))],
        "Ref": ["cve:CVE-%s-%s" % (randstr(string.digits, 4), randstr()), "http://www.example.com/%s" % randstr()],
        "Confidence": random(),
        "Note": "Random event",
@@ -117,7 +118,7 @@ def gen_random_idea(client_name="cz.example.warden.test"):
        "Node": [
           {
              "Name": client_name,
-             "Tags": ["Protocol", "Honeypot"],
+             "Tags": [choice(["Data", "Protocol", "Honeypot", "Heuristic", "Log"]) for dummy in range(randint(1, 3))],
              "SW": ["Kippo"],
              "AggrWin": "00:05:00"
           }
@@ -141,12 +142,28 @@ def main():
     #     idstore="MyClient.id",
     #     name="cz.example.warden.test")
 
+    print "=== Debug ==="
+    info = wclient.getDebug()
+    if not isinstance(info, Error):
+        pprint(info)
+
+    print "=== Server info ==="
+    info = wclient.getInfo()
+    if not isinstance(info, Error):
+        pprint(info)
+
+    print "=== Sending 10 event(s) ==="
+    start = time()
+    ret = wclient.sendEvents([gen_random_idea(client_name=wclient.name) for i in range(10)])
+    print ret
+    print "Time: %f" % (time()-start)
+
     print "=== Getting 10 events ==="
     start = time()
 
     # cat = ['Availability', 'Abusive.Spam','Attempt.Login']
     # cat = ['Attempt', 'Information','Fraud.Scam','Malware.Virus']
-    cat = ['Attempt']
+    cat = ['Fraud', 'Abusive.Spam']
     nocat = ['Availability', 'Information', 'Fraud.Scam']
 
     tag = ['Log', 'Data']
@@ -155,31 +172,14 @@ def main():
     group = ['cz.tul.ward.kippo','cz.vsb.buldog.kippo']
     nogroup = ['cz.zcu.civ.afrodita','cz.vutbr.net.bee.hpscan']
 
-    ret = wclient.getEvents(count=10, cat=None, nocat=None, tag=None, notag=None, group=None, nogroup=nogroup)
-    ret = wclient.getEvents(count=10)
+    ret = wclient.getEvents(count=10, cat=cat, nocat=None, tag=tag, notag=None, group=None, nogroup=nogroup)
     print "Time: %f" % (time()-start)
     print "Got %i events" % len(ret)
     for e in ret:
-        print e
+        print e["Category"], e["Node"][0]["Tags"], e["Node"][0]["Name"]
     if isinstance(ret, Error):
         print ret
 
-    print "=== Sending 1 event(s) ==="
-    start = time()
-    ret = wclient.sendEvents([gen_random_idea(client_name=wclient.name) for i in range(1)])
-    print ret
-    print "Time: %f" % (time()-start)
-
-    print "=== Server info ==="
-    info = wclient.getInfo()
-    if not isinstance(info, Error):
-        pprint(info)
-
-    print "=== Debug ==="
-    info = wclient.getDebug()
-    if not isinstance(info, Error):
-        pprint(info)
-
 
 if __name__ == "__main__":
     main()
diff --git a/warden3/warden_server/warden3.0-alpha.sql b/warden3/warden_server/warden3.0-alpha.sql
index 16d78b539dda27566524a20caa6ecbce8905ce9c..cfd494b1f919a89db9e1aa1d3ca5cd9b2f643bbd 100644
--- a/warden3/warden_server/warden3.0-alpha.sql
+++ b/warden3/warden_server/warden3.0-alpha.sql
@@ -39,40 +39,50 @@ CREATE TABLE IF NOT EXISTS `categories` (
 --
 
 INSERT INTO `categories` (`id`, `category`, `subcategory`, `cat_subcat`) VALUES
+(100, 'Abusive', NULL, 'Abusive'),
 (101, 'Abusive', 'Spam', 'Abusive.Spam'),
 (102, 'Abusive', 'Harassment', 'Abusive.Harassment'),
 (103, 'Abusive', 'Child', 'Abusive.Child'),
 (104, 'Abusive', 'Sexual', 'Abusive.Sexual'),
 (105, 'Abusive', 'Violence', 'Abusive.Violence'),
+(200, 'Malware', NULL, 'Malware'),
 (201, 'Malware', 'Virus', 'Malware.Virus'),
 (202, 'Malware', 'Worm', 'Malware.Worm'),
 (203, 'Malware', 'Trojan', 'Malware.Trojan'),
 (204, 'Malware', 'Spyware', 'Malware.Spyware'),
 (205, 'Malware', 'Dialer', 'Malware.Dialer'),
 (206, 'Malware', 'Rootkit', 'Malware.Rootkit'),
+(300, 'Recon', NULL, 'Recon'),
 (301, 'Recon', 'Scanning', 'Recon.Scanning'),
 (302, 'Recon', 'Sniffing', 'Recon.Sniffing'),
 (303, 'Recon', 'SocialEngineering', 'Recon.SocialEngineering'),
 (304, 'Recon', 'Searching', 'Recon.Searching'),
+(400, 'Attempt', NULL, 'Attempt'),
 (401, 'Attempt', 'Exploit', 'Attempt.Exploit'),
 (402, 'Attempt', 'Login', 'Attempt.Login'),
 (403, 'Attempt', 'NewSignature', 'Attempt.NewSignature'),
+(500, 'Intrusion', NULL, 'Intrusion'),
 (501, 'Intrusion', 'AdminCompromise', 'Intrusion.AdminCompromise'),
 (502, 'Intrusion', 'UserCompromise', 'Intrusion.UserCompromise'),
 (503, 'Intrusion', 'AppCompromise', 'Intrusion.AppCompromise'),
 (504, 'Intrusion', 'Botnet', 'Intrusion.Botnet'),
+(600, 'Availability', NULL, 'Availability'),
 (601, 'Availability', 'DoS', 'Availability.DoS'),
 (602, 'Availability', 'DDoS', 'Availability.DDoS'),
 (603, 'Availability', 'Sabotage', 'Availability.Sabotage'),
 (604, 'Availability', 'Outage', 'Availability.Outage'),
+(700, 'Information', NULL, 'Information'),
 (701, 'Information', 'UnauthorizedAccess', 'Information.UnauthorizedAccess'),
 (702, 'Information', 'UnauthorizedModification', 'Information.UnauthorizedModification'),
+(800, 'Fraud', NULL, 'Fraud'),
 (801, 'Fraud', 'UnauthorizedUsage', 'Fraud.UnauthorizedUsage'),
 (802, 'Fraud', 'Copyright', 'Fraud.Copyright'),
 (803, 'Fraud', 'Masquerade', 'Fraud.Masquerade'),
 (804, 'Fraud', 'Phishing', 'Fraud.Phishing'),
 (805, 'Fraud', 'Scam', 'Fraud.Scam'),
+(900, 'Vulnerable', NULL, 'Vulnerable'),
 (901, 'Vulnerable', 'Open', 'Vulnerable.Open'),
+(1000, 'Anomaly', NULL, 'Anomaly'),
 (1001, 'Anomaly', 'Traffic', 'Anomaly.Traffic'),
 (1002, 'Anomaly', 'Connection', 'Anomaly.Connection'),
 (1003, 'Anomaly', 'Protocol', 'Anomaly.Protocol'),
diff --git a/warden3/warden_server/warden_server.py b/warden3/warden_server/warden_server.py
index 9c34759a69faeb0d99e020e91cb9b29d27e6823e..07e7df14936815dfd7c8f88ad6af0beabceb58e2 100755
--- a/warden3/warden_server/warden_server.py
+++ b/warden3/warden_server/warden_server.py
@@ -294,7 +294,6 @@ class X509Authenticator(NoAuthenticator):
 
         identity = args.get("client", [None])[0]
         secret =  args.get("secret", [None])[0]
-        args["secret"] = ["..."]    # Prevent to spill it over logs
 
         client = self.db.get_client_by_name(cert_names, identity, secret)
 
@@ -412,6 +411,14 @@ class MySQL(ObjectReq):
             type(self).__name__, type(self.req).__name__, self.host, self.user, self.dbname, self.port, self.catmap_filename, self.tagmap_filename)
 
 
+    def _get_comma_perc(self, l):
+        return ','.join(['%s'] * len(l))
+
+
+    def _get_not(self, b):
+        return "" if b else "NOT"
+
+
     def get_client_by_name(self, cert_names, identity=None, secret=None):
         query = ["SELECT id, registered, requestor, hostname, service, note, identity, secret, `read`, debug, `write`, test FROM clients WHERE valid = 1"]
         params = []
@@ -421,7 +428,7 @@ class MySQL(ObjectReq):
         if secret:
             query.append(" AND secret = %s")
             params.append(secret)
-        query.append(" AND hostname IN (%s)" % ','.join(['%s'] * len(cert_names)))
+        query.append(" AND hostname IN (%s)" % self._get_comma_perc(cert_names))
         params.extend(cert_names)
         self.crs.execute("".join(query), params)
         rows = self.crs.fetchall()
@@ -446,30 +453,16 @@ class MySQL(ObjectReq):
         }
 
 
-    def generateDynamicQuery(self, section, query_string, variables, parent_cats = []):
-        variables_id = []
-    
+    def getMaps(self, section, variables):
+        maps = []
         for v in variables:
             try:
-                mapped_id = section[v]
+                mapped = section[v]
             except KeyError:
                 raise self.req.error("Wrong tag or category used in query.", 422,
                     sys.exc_info(), detail={"key": v})
-
-            if mapped_id % 100:
-                variables_id.append(mapped_id)
-            else:
-                parent_cats.append(mapped_id)
-    
-        temp_string = ""
-
-        if len(variables_id) > 0:
-            format_strings = ','.join(['%s'] * len(variables_id))
-            logging.debug("query_string: %s" % query_string)
-            logging.debug("format_strings: %s" % format_strings)
-            temp_string = query_string % format_strings
-        
-        return temp_string, variables_id
+            maps.append(mapped)
+        return set(maps)    # unique
 
 
     def fetch_events(self, client, id, count,
@@ -489,42 +482,41 @@ class MySQL(ObjectReq):
             raise self.req.error("Unrealizable conditions. Choose group or nogroup option.", 422,
                         detail={'tag': cat, 'notag' : nocat})
 
-        sqlwhere = []
-        sqlparams = []
-
-        sqlwhere.append("SELECT e.id, e.data FROM clients c RIGHT JOIN events e ON c.id = e.client_id WHERE e.id > %s")
-        sqlparams.append(id or 0)
+        query = ["SELECT e.id, e.data FROM clients c RIGHT JOIN events e ON c.id = e.client_id WHERE e.id > %s"]
+        params = [id or 0]
 
         if cat or nocat:
-            parent_cats = []
-            sqltemp, sqlpar = self.generateDynamicQuery(self.catmap, "category_id IN (%s)", (cat or nocat), parent_cats)
-            for pcat in parent_cats:
-                sqltemp += " %s (category_id > %s AND category_id < %s) " % (("OR" if sqltemp else ""), pcat, pcat + 100)
-             
-            sqlwhere.append(" AND e.id %s IN (SELECT event_id FROM event_category_mapping WHERE %s)" % (("NOT" if nocat else ""), sqltemp))
-            sqlparams.extend(sqlpar)
+            cats = self.getMaps(self.catmap, (cat or nocat))
+            query.append(
+                " AND e.id %s IN (SELECT event_id FROM event_category_mapping WHERE category_id IN (%s))" % (
+                    self._get_not(cat), self._get_comma_perc(cats)))
+            params.extend(cats)
 
         if tag or notag:
-            sqltemp, sqlpar = self.generateDynamicQuery(self.tagmap, "tag_id IN (%s)", (tag or notag))
-
-            sqlwhere.append(" AND e.id %s IN (SELECT event_id FROM event_tag_mapping WHERE %s)" % (("NOT" if notag else ""), sqltemp))
-            sqlparams.extend(sqlpar)
+            tags = self.getMaps(self.tagmap, (tag or notag))
+            query.append(
+                " AND e.id %s IN (SELECT event_id FROM event_tag_mapping WHERE tag_id IN (%s))" % (
+                    self._get_not(tag), self._get_comma_perc(tags)))
+            params.extend(tags)
 
         if group or nogroup:
-            not_op = "" if group else "NOT"
-            
+            subquery = []
             for identity in (group or nogroup):
-                sqlwhere.append(" AND c.identity %s LIKE %%s" % not_op)
-                sqlparams.append(identity + "%")
+                subquery.append("c.identity = %s")      # exact client
+                params.append(identity)
+                subquery.append("c.identity LIKE %s")   # whole subtree
+                params.append(identity + ".%")
+
+            query.append(" AND %s (%s)" % (self._get_not(group), " OR ".join(subquery)))
 
-        sqlwhere.append(" AND e.valid = 1 LIMIT %s")
-        sqlparams.append(count)
+        query.append(" AND e.valid = 1 LIMIT %s")
+        params.append(count)
 
-        sqlwhere_string = "".join(sqlwhere)
-        logging.debug("fetch_events: query - %s" % sqlwhere_string)
-        logging.debug("fetch_events: params - %s", str(sqlparams))
+        query_string = "".join(query)
+        logging.debug("fetch_events: query - %s" % query_string)
+        logging.debug("fetch_events: params - %s", str(params))
 
-        self.crs.execute(sqlwhere_string, sqlparams)
+        self.crs.execute(query_string, params)
         row = self.crs.fetchall()
 
         if row:
@@ -544,11 +536,11 @@ class MySQL(ObjectReq):
         try:
             self.crs.execute("INSERT INTO events (received,client_id,data) VALUES (NOW(), %s, %s)", (client.id, json.dumps(event)))
             lastid = self.crs.lastrowid
-            logging.debug("store_event: Last ID in events - %i" % lastid)
 
-            for cat in event.get('Category', ["Other"]):
+            catlist = event.get('Category', ["Other"])
+            cats = set(catlist) | {cat.split(".", 1)[0] for cat in catlist}
+            for cat in cats:
                 cat_id = self.catmap.get(cat, self.catmap_other)
-                logging.debug("store_event: Category \"%s\" translated to %i" % (cat, cat_id))
                 self.crs.execute("INSERT INTO event_category_mapping (event_id,category_id) VALUES (%s, %s)", (lastid, cat_id))
                 
             try:
@@ -558,7 +550,6 @@ class MySQL(ObjectReq):
                 
             for tag in tags:
                 tag_id = self.tagmap.get(tag, self.tagmap_other)
-                logging.debug("store_event: Tag \"%s\" translated to %i" % (tag, tag_id))
                 self.crs.execute("INSERT INTO event_tag_mapping (event_id,tag_id) VALUES (%s, %s)", (lastid, tag_id))    
 
             self.con.commit()
@@ -671,8 +662,6 @@ class Server(ObjectReq):
             if not auth:
                 raise self.req.error("I'm watching. Not authorized.", 403, detail={"client": client.identity})
 
-            logging.debug("arguments: %s" % str(args))
-
             # These args are not for handler
             args.pop("client", None)
             args.pop("secret", None)
@@ -812,11 +801,11 @@ class WardenHandler(ObjectReq):
             count = min(count, self.get_events_limit)
 
         res = self.db.fetch_events(self.req.client, id, count, cat, nocat, tag, notag, group, nogroup)
-        logging.info("fetch_events(%d, %d, %s, %s, %s, %s, %s, %s) returned %d events" % (
-            id, count, cat, nocat, tag, notag, group, nogroup, len(res["events"])))
 
         self.db.insertLastReceivedId(self.req.client, res['lastid'])
 
+        logging.info("sending %d events, lastid is %i" % (len(res["events"]), res["lastid"]))
+
         return res