diff --git a/warden3/warden_server/warden_server.py b/warden3/warden_server/warden_server.py
index 24cafc34dd8cc83edcafc1a050db598fd46e41b3..3b48297b96b9f37d45ba96f908d08faa5a255d44 100755
--- a/warden3/warden_server/warden_server.py
+++ b/warden3/warden_server/warden_server.py
@@ -14,6 +14,8 @@ import M2Crypto.X509
 import json
 import MySQLdb as my
 import MySQLdb.cursors as mycursors
+import re
+import email.utils
 from collections import namedtuple
 from uuid import uuid4
 from time import time, gmtime, sleep
@@ -210,17 +212,17 @@ def SysLogger(req, socket="/dev/log", facility=logging.handlers.SysLogHandler.LO
 
 class Client(namedtuple("ClientTuple",
     ["id", "registered", "requestor", "hostname", "service", "note",
-    "identity", "secret", "read", "debug", "write", "test"])):
+    "valid", "identity", "secret", "read", "debug", "write", "test"])):
 
     def __str__(self):
         return (
             "%s(id=%i, registered=%s, requestor=\"%s\", hostname=\"%s\", "
             "service=\"%s\", note=\"%s\", identity=\"%s\", secret=%s, "
-            "read=%i, debug=%i, write=%i, test=%i)") % (
+            "valid=%i read=%i, debug=%i, write=%i, test=%i)") % (
             type(self).__name__, self.id, self.registered,
             self.requestor, self.hostname, self.service, self.note,
             self.identity, "..." if self.secret is not None else "None",
-            self.read, self.debug, self.write, self.test)
+            self.valid, self.read, self.debug, self.write, self.test)
 
 
 
@@ -514,7 +516,7 @@ class MySQL(ObjectReq):
 
 
     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"]
+        query = ["SELECT id, registered, requestor, hostname, service, note, valid, identity, secret, `read`, debug, `write`, test FROM clients WHERE valid = 1"]
         params = []
         if identity:
             query.append(" AND identity = %s")
@@ -534,6 +536,42 @@ class MySQL(ObjectReq):
         return Client(**rows[0]) if rows else None
 
 
+    def get_clients(self, id=None):
+        query = ["SELECT id, registered, requestor, hostname, service, note, valid, identity, secret, `read`, debug, `write`, test FROM clients"]
+        params = []
+        if id:
+            query.append("WHERE id = %s")
+            params.append(id)
+        query.append("ORDER BY id")
+        rows = self.query(" ".join(query), params)
+        return [Client(**row) for row in rows]
+
+
+    def add_modify_client(self, id=None, **kwargs):
+        query = []
+        params = []
+        uquery = []
+        if id is None:
+            query.append("INSERT INTO clients SET")
+            uquery.append("registered = now()")
+        else:
+            query.append("UPDATE clients SET")
+        for attr in ["identity", "hostname", "requestor", "secret", "note",
+                      "valid", "read", "write", "debug", "test"]:
+            val = kwargs.get(attr, None)
+            if val is not None:
+                uquery.append("`%s` = %%s" % attr)
+                params.append(val)
+        if not uquery:
+            return id
+        query.append(", ".join(uquery))
+        if id is not None:
+            query.append("WHERE id = %s")
+            params.append(id)
+        self.query(" ".join(query), params)
+        return self.crs.lastrowid if id is None else id
+
+
     def get_debug(self):
         rows = self.query("SELECT VERSION() AS VER")
         tablestat = self.query("SHOW TABLE STATUS")
@@ -653,11 +691,13 @@ class MySQL(ObjectReq):
         self.query("INSERT INTO last_events(client_id, event_id, timestamp) VALUES(%s, %s, NOW())", (client.id, id), dml=True)
         self.con.commit()
 
+
     def getLastEventId(self):
         row = self.query("SELECT MAX(id) as id FROM events")[0]
 
         return row['id'] or 0
 
+
     def getLastReceivedId(self, client):
         row = self.query("SELECT MAX(event_id) as id FROM last_events WHERE client_id = %s", client.id)[0]
 
@@ -1163,11 +1203,191 @@ def build_server(conf):
         logging.debug("", exc_info=sys.exc_info())
         return fallback_wsgi
 
-    logging.info("Ready to serve")
+    logging.info("Server ready")
 
     return objects["server"]
 
 
+
+# Command line utilities
+
+def check_config():
+    # If we got so far, server object got set up fine
+    print >>sys.stderr, "Looks clear."
+    return 0
+
+
+def list_clients(id=None):
+    clients = server.handler.db.get_clients(id)
+    order = ["id", "registered", "requestor", "hostname", "service", "identity",
+             "secret", "valid", "read", "debug", "write", "test", "note"]
+    lines = [[str(getattr(client, col)) for col in order] for client in clients]
+    col_width = [max(len(val) for val in col) for col in zip(*(lines+[order]))]
+    divider = ["-" * l for l in col_width]
+    for line in [order, divider] + lines:
+        print " ".join([val.ljust(width) for val, width in zip(line, col_width)])
+
+
+def register_client(name, hostname, requestor, secret, note, valid, read, write, debug, test):
+    # argparse does _always_ return something, so we cannot rely on missing arguments
+    if valid is None: valid = 1
+    if read is None: read = 1
+    if write is None: write = 0
+    if debug is None: debug = 0
+    if test is None: test = 1
+    modify_client(id=None,
+            name=name, hostname=hostname, requestor=requestor, secret=secret,
+            note=note, valid=valid, read=read, write=write, debug=debug, test=test)
+
+
+def modify_client(id, name, hostname, requestor, secret, note, valid, read, write, debug, test):
+
+    def isValidHostname(hostname):
+        if len(hostname) > 255:
+            return False
+        if hostname.endswith("."): # A single trailing dot is legal
+            hostname = hostname[:-1] # strip exactly one dot from the right, if present
+        disallowed = re.compile("[^A-Z\d-]", re.IGNORECASE)
+        return all( # Split by labels and verify individually
+            (label and len(label) <= 63 # length is within proper range
+             and not label.startswith("-") and not label.endswith("-") # no bordering hyphens
+             and not disallowed.search(label)) # contains only legal characters
+            for label in hostname.split("."))
+
+    def isValidNSID(nsid):
+        allowed = re.compile("^(?:[a-zA-Z_][a-zA-Z0-9_]*\\.)*[a-zA-Z_][a-zA-Z0-9_]*$")
+        return allowed.match(nsid)
+
+    def isValidEmail(mail):
+        split = email.utils.parseaddr(mail)
+        allowed = re.compile("^[a-zA-Z0-9_.%!+-]+@[a-zA-Z0-9-.]+$") # just basic check
+        return allowed.match(split[1])
+
+    def isValidID(id):
+        client = server.handler.db.get_clients(id)
+        return client and True or False
+
+
+    if name is not None and not isValidNSID(name):
+        print >>sys.stderr, "Invalid client name \"%s\"." % name
+
+    if hostname is not None and not isValidHostname(hostname):
+        print >>sys.stderr, "Invalid hostname \"%s\"." % hostname
+        return 254
+
+    if requestor is not None and not isValidEmail(requestor):
+        print >>sys.stderr, "Invalid requestor email \"%s\"." % requestor
+        return 254
+
+    if id is not None and not isValidID(id):
+        print >>sys.stderr, "Invalid id \"%s\"." % id
+        return 254
+
+    existing_clients = server.handler.db.get_client_by_name([hostname], identity=name, secret=secret)
+    if existing_clients:
+        print >>sys.stderr, "Clash with existing hostname/identity/secret: %s" % str(existing_clients)
+        return 254
+
+    newid = server.handler.db.add_modify_client(
+        id=id, identity=name, hostname=hostname,
+        requestor=requestor, secret=secret, note=note, valid=valid,
+        read=read, write=write, debug=debug, test=test)
+
+    list_clients(id=newid)
+
+
+def add_client_args(subargp, mod=False):
+    subargp.add_argument("--help", action="help", help="show this help message and exit")
+    if mod:
+        subargp.add_argument("-i", "--id", required=True, type=int,
+            help="client id")
+    subargp.add_argument("-n", "--name", required=not mod,
+        help="client name (in dotted reverse path notation)")
+    subargp.add_argument("-h", "--hostname", required=not mod,
+        help="client FQDN hostname")
+    subargp.add_argument("-r", "--requestor", required=not mod,
+        help="requestor email")
+    subargp.add_argument("-s", "--secret",
+        help="authentication token")
+    subargp.add_argument("--note",
+        help="client freetext description")
+
+    reg_valid = subargp.add_mutually_exclusive_group(required=False)
+    reg_valid.add_argument("--valid", action="store_const", const=1, default=None,
+        help="valid client (default)")
+    reg_valid.add_argument("--novalid", action="store_const", const=0, dest="valid", default=None)
+
+    reg_read = subargp.add_mutually_exclusive_group(required=False)
+    reg_read.add_argument("--read", action="store_const", const=1, default=None,
+        help="client is allowed to read (default)")
+    reg_read.add_argument("--noread", action="store_const", const=0, dest="read", default=None)
+
+    reg_write = subargp.add_mutually_exclusive_group(required=False)
+    reg_write.add_argument("--nowrite", action="store_const", const=0, dest="write", default=None,
+        help="client is allowed to send (default - no)")
+    reg_write.add_argument("--write", action="store_const", const=1, default=None)
+
+    reg_debug = subargp.add_mutually_exclusive_group(required=False)
+    reg_debug.add_argument("--nodebug", action="store_const", const=0, dest="debug", default=None,
+        help="client is allowed receive debug output (default - no)")
+    reg_debug.add_argument("--debug", action="store_const", const=1, default=None)
+
+    reg_test = subargp.add_mutually_exclusive_group(required=False)
+    reg_test.add_argument("--test", action="store_const", const=1, default=None,
+        help="client is yet in testing phase (default - yes)")
+    reg_test.add_argument("--notest", action="store_const", const=0, dest="test", default=None)
+
+
+def get_args():
+    import argparse
+    argp = argparse.ArgumentParser(
+        description="Warden server " + VERSION, add_help=False)
+    argp.add_argument("--help", action="help",
+        help="show this help message and exit")
+    argp.add_argument("-c", "--config",
+        help="show this help message and exit")
+    subargp = argp.add_subparsers(title="commands")
+
+    subargp_check = subargp.add_parser("check", add_help=False,
+        description="Try to setup server based on configuration file.",
+        help="check configuration")
+    subargp_check.set_defaults(command=check_config)
+    subargp_check.add_argument("--help", action="help",
+        help="show this help message and exit")
+
+    subargp_reg = subargp.add_parser("register", add_help=False,
+        description="Add new client registration entry.",
+        help="register new client")
+    subargp_reg.set_defaults(command=register_client)
+    add_client_args(subargp_reg)
+
+    subargp_mod = subargp.add_parser("modify", add_help=False,
+        description="Modify details of client registration entry.",
+        help="modify client registration")
+    subargp_mod.set_defaults(command=modify_client)
+    add_client_args(subargp_mod, mod=True)
+
+    subargp_list = subargp.add_parser("list", add_help=False,
+        description="List details of client registration entries.",
+        help="list registered clients")
+    subargp_list.set_defaults(command=list_clients)
+    subargp_list.add_argument("--help", action="help",
+        help="show this help message and exit")
+    subargp_list.add_argument("--id", action="store", type=int,
+        help="client id", default=None)
+
+    return argp.parse_args()
+
+
 if __name__=="__main__":
-    # FIXME: just development stuff
-    srv = build_server(read_ini("warden3.cfg.wheezy-warden3"))
+    args = get_args()
+    config = path.join(path.dirname(__file__), args.config or "warden_server.cfg")
+    server = build_server(read_cfg(config))
+    command = args.command
+    subargs = vars(args)
+    del subargs["command"]
+    del subargs["config"]
+    if not server or server is fallback_wsgi:
+        print >>sys.stderr, "Failed initialization, check configured log targets for reasons."
+        sys.exit(255)
+    sys.exit(command(**subargs))