Explorar el Código

Merge branch 'merge_secure_list_into_v0.3'

Conflicts:
	pwman/data/drivers/sqlite.py
	pwman/util/crypto.py
	scripts/pwman3
oz123 hace 11 años
padre
commit
ae597643c6
Se han modificado 7 ficheros con 962 adiciones y 103 borrados
  1. 119 0
      pwman/data/convertdb.py
  2. 382 40
      pwman/data/drivers/sqlite.py
  3. 29 18
      pwman/data/factory.py
  4. 122 1
      pwman/data/nodes.py
  5. 253 19
      pwman/ui/cli.py
  6. 4 1
      pwman/util/crypto.py
  7. 53 24
      scripts/pwman3

+ 119 - 0
pwman/data/convertdb.py

@@ -0,0 +1,119 @@
+#============================================================================
+# This file is part of Pwman3.
+#
+# Pwman3 is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License, version 2
+# as published by the Free Software Foundation;
+#
+# Pwman3 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Pwman3; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#============================================================================
+# Copyright (C) 2013 Oz Nahum <nahumoz@gmail.com>
+#============================================================================
+
+import os
+import shutil
+import os.path
+import time
+import getpass
+from pwman.util.crypto import CryptoEngine
+import pwman.data.factory
+from pwman.util.callback import Callback
+#from pwman.data.nodes import Node
+from pwman.data.nodes import NewNode
+
+_NEWVERSION = 0.4
+
+
+class CLICallback(Callback):
+    def getinput(self, question):
+        return raw_input(question)
+
+    def getsecret(self, question):
+        return getpass.getpass(question + ":")
+
+
+class PwmanConvertDB(object):
+    """
+    Class to migrate from DB in version 0.3 to
+    DB used in later versions.
+    """
+
+    def __init__(self, args, config):
+        self.dbname = config.get_value('Database', 'filename')
+        self.dbtype = config.get_value("Database", "type")
+        print "Will convert the following Database: %s " % self.dbname
+        if os.path.exists(config.get_value("Database", "filename")):
+            dbver = pwman.data.factory.check_db_version(self.dbtype)
+            self.dbver = float(dbver.strip("\'"))
+        backup = '.backup-%s'.join(os.path.splitext(self.dbname)) % \
+            time.strftime(
+                '%Y-%m-%d-%H:%m')
+        shutil.copy(self.dbname, backup)
+        print "backup created in ", backup
+
+    def read_old_db(self):
+        "read the old db and get all nodes"
+        self.db = pwman.data.factory.create(self.dbtype, self.dbver)
+        enc = CryptoEngine.get()
+        enc.set_callback(CLICallback())
+        self.db.open()
+        self.oldnodes = self.db.listnodes()
+        self.oldnodes = self.db.getnodes(self.oldnodes)
+
+    def create_new_db(self):
+        self.newdb_name = '-newdb'.join(os.path.splitext(self.dbname))
+
+        self.newdb = pwman.data.factory.create(self.dbtype, _NEWVERSION,
+                                               self.newdb_name)
+        self.newdb._open()
+
+    def convert_nodes(self):
+        """convert old nodes instances to new format"""
+        self.NewNodes = []
+        for node in self.oldnodes:
+            username = node.get_username()
+            password = node.get_password()
+            url = node.get_url()
+            notes = node.get_notes()
+            tags = node.get_tags()
+            tags_strings = [tag.get_name() for tag in tags]
+            newNode = NewNode(username=username,
+                              password=password,
+                              url=url,
+                              notes=notes,
+                              tags=tags_strings
+                              )
+            self.NewNodes.append(newNode)
+
+    def save_new_nodes_to_db(self):
+        self.newdb.addnodes(self.NewNodes)
+        self.newdb._commit()
+
+    def save_old_key(self):
+        enc = CryptoEngine.get()
+        self.oldkey = enc.get_cryptedkey()
+        self.newdb.savekey(self.oldkey)
+
+    def print_success(self):
+        print """pwman successfully converted the old database to the new
+format.\nPlease run `pwman3 -d %s` to make sure your password and
+data are still correct. If you are convinced that no harm was done,
+update your config file to indicate the permanent location
+to your new database.
+If you found errors, please report a bug in Pwman homepage in github.
+""" % self.newdb_name
+
+    def run(self):
+        self.read_old_db()
+        self.create_new_db()
+        self.convert_nodes()
+        self.save_new_nodes_to_db()
+        self.save_old_key()
+        self.print_success()

+ 382 - 40
pwman/data/drivers/sqlite.py

@@ -23,6 +23,7 @@
 """SQLite Database implementation."""
 from pwman.data.database import Database, DatabaseException
 from pwman.data.nodes import Node
+from pwman.data.nodes import NewNode
 from pwman.data.tags import Tag
 import re
 import sys
@@ -38,6 +39,344 @@ import pwman.util.config as config
 
 import cPickle
 
+
+def check_db_version():
+    """
+    check the data base version query the right table
+    """
+    try:
+        filename = config.get_value('Database', 'filename')
+        con = sqlite.connect(filename)
+        cur = con.cursor()
+        cur.execute("PRAGMA TABLE_INFO(DBVERSION)")
+        row = cur.fetchone()
+        if row is None:
+            return "0.3"
+        try:
+            return row[-2]
+        except IndexError:
+            raise DatabaseException("Something seems fishy with the DB")
+
+    except sqlite.DatabaseError, e:
+        raise DatabaseException("SQLite: %s" % (e))
+
+
+class SQLiteDatabaseNewForm(Database):
+    """SQLite Database implementation"""
+
+    def __init__(self, filename=None):
+        """Initialise SQLitePwmanDatabase instance."""
+        Database.__init__(self)
+        if filename:
+            self._filename = filename
+        else:
+            try:
+                self._filename = config.get_value('Database', 'filename')
+            except KeyError, e:
+                raise DatabaseException(
+                    "SQLite: missing parameter [%s]" % (e))
+
+    def _open(self):
+        try:
+            self._con = sqlite.connect(self._filename)
+            self._cur = self._con.cursor()
+            self._checktables()
+        except sqlite.DatabaseError, e:
+            raise DatabaseException("SQLite: %s" % (e))
+
+    def close(self):
+        self._cur.close()
+        self._con.close()
+
+    def listtags(self, alltags=False):
+        sql = ''
+        params = []
+        if len(self._filtertags) == 0 or alltags:
+            sql = "SELECT DATA FROM TAGS ORDER BY DATA ASC"
+        else:
+            sql = ("SELECT TAGS.DATA FROM LOOKUP"
+                   + " INNER JOIN TAGS ON LOOKUP.TAG = TAGS.ID"
+                   + " WHERE NODE IN (")
+            first = True
+            # if using the command filter, the code crashes ...
+            #
+            for t in self._filtertags:
+                if not first:
+                    sql += " INTERSECT "
+                else:
+                    first = False
+
+                sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS ON TAG = "
+                        + " TAGS.ID WHERE TAGS.DATA = ?")
+                params.append(t.get_name())
+            sql += ") EXCEPT SELECT DATA FROM TAGS WHERE "
+            first = True
+            for t in self._filtertags:
+                if not first:
+                    sql += " OR "
+                else:
+                    first = False
+                sql += "TAGS.DATA = ?"
+                params.append(t)
+        try:
+            self._cur.execute(sql, params)
+            tags = []
+            row = self._cur.fetchone()
+            while row is not None:
+                tagstring = str(row[0])
+                m = re.search('S\"S\'(.+?)\'', tagstring)
+                if m:
+                    found = m.group(1)
+                    tags.append(Tag(found))
+                row = self._cur.fetchone()
+            return tags
+        except sqlite.DatabaseError, e:
+            raise DatabaseException("SQLite: %s" % (e))
+
+    def parse_node_string(self, string):
+        nodestring = string.split("##")
+        keyvals = {}
+        for pair in nodestring[:-1]:
+            key, val = pair.split(":")
+            keyvals[key.lstrip('##')] = val
+        tags = nodestring[-1]
+        tags = tags.lstrip("tags:")
+        tags = tags.split("tag:")
+        taginsts = []
+        for tag in tags:
+            _Tag = tag.rstrip("**endtag**")
+            Tag = (_Tag)
+            taginsts.append(Tag)
+        return keyvals, taginsts
+
+    def getnodes(self, ids):
+        """
+        object should always be: (ipwman.data.nodes
+        """
+        nodes = []
+        for i in ids:
+                sql = "SELECT DATA FROM NODES WHERE ID = ?"
+            # try:
+                self._cur.execute(sql, [i])
+                row = self._cur.fetchone()
+                if row is not None:
+                    nodestring = str(row[0])
+                    # if not nodestring.startswith("(ipwman.data.nodes"):
+                    #     raise DatabaseException(
+                #"Tried to load foreign object from database," \
+                #+ " this looks fishy in here...")
+                    nodeargs, tags = self.parse_node_string(nodestring)
+                    node = NewNode(**nodeargs)
+                    node.set_tags(tags)
+                    node.set_id(i)
+                    nodes.append(node)
+            # except sqlite.DatabaseError, e:
+            #   raise DatabaseException("SQLite: %s" % (e))
+        return nodes
+
+    def editnode(self, id, node):
+        try:
+            sql = "UPDATE NODES SET DATA = ? WHERE ID = ?"
+            self._cur.execute(sql, [node.dump_edit_to_db()[0], id])
+        except sqlite.DatabaseError, e:
+            raise DatabaseException("SQLite: %s" % (e))
+        self._setnodetags(node)
+        self._checktags()
+        self._commit()
+
+    def addnodes(self, nodes):
+        """
+        This method injects the data as PWMAN object using cPickle.
+        To make pwman more secure, either this method has to replaced.
+        Or whenever stuff is read from the database, there must be a
+        security check that it contains the correct objects!
+        Nodes passed to this methos are instances!
+        """
+        for n in nodes:
+            sql = "INSERT INTO NODES(DATA) VALUES(?)"
+            # if not isinstance(n, Node): raise DatabaseException(
+            #    "Tried to insert foreign object into database [%s]", n)
+            value = n.dump_to_db()
+            try:
+                self._cur.execute(sql, value)
+            except sqlite.DatabaseError, e:
+                raise DatabaseException("SQLite: %s" % (e))
+            idx = self._cur.lastrowid
+            n.set_id(idx)
+            self._setnodetags(n)
+            self._commit()
+
+    def removenodes(self, nodes):
+        for n in nodes:
+            # if not isinstance(n, Node): raise DatabaseException(
+            #    "Tried to delete foreign object from database [%s]", n)
+            try:
+                sql = "DELETE FROM NODES WHERE ID = ?"
+                self._cur.execute(sql, [n.get_id()])
+
+            except sqlite.DatabaseError, e:
+                raise DatabaseException("SQLite: %s" % (e))
+            self._deletenodetags(n)
+
+        self._checktags()
+        self._commit()
+
+    def listnodes(self):
+        sql = ''
+        params = []
+        if len(self._filtertags) == 0:
+            sql = "SELECT ID FROM NODES ORDER BY ID ASC"
+        else:
+            first = True
+            for t in self._filtertags:
+                if not first:
+                    sql += " INTERSECT "
+                else:
+                    first = False
+                sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS ON TAG = "
+                        " TAGS.ID WHERE TAGS.DATA = ? ")
+                params.append(t.get_name())
+        try:
+            self._cur.execute(sql, params)
+            ids = []
+            row = self._cur.fetchone()
+            while row is not None:
+                ids.append(row[0])
+                row = self._cur.fetchone()
+            return ids
+        except sqlite.DatabaseError, e:
+            raise DatabaseException("SQLite: %s" % (e))
+
+    def _commit(self):
+        try:
+            self._con.commit()
+        except sqlite.DatabaseError, e:
+            self._con.rollback()
+            raise DatabaseException(
+                "SQLite: Error commiting data to db [%s]" % (e))
+
+    def _tagids(self, tags):
+        ids = []
+        for tag in tags:
+            sql = "SELECT ID FROM TAGS WHERE DATA = ?"
+            # if not isinstance(t, Tag): raise DatabaseException(
+            #    "Tried to insert foreign object into database [%s]", t)
+            try:
+                if isinstance(tag, str):
+                    self._cur.execute(sql, [tag])
+                else:
+                    self._cur.execute(sql, [tag.get_name()])
+                row = self._cur.fetchone()
+                if (row is not None):
+                    ids.append(row[0])
+                else:
+                    sql = "INSERT INTO TAGS(DATA) VALUES(?)"
+                    if isinstance(tag, str):
+                        self._cur.execute(sql, [tag])
+                    else:
+                        self._cur.execute(sql, [tag.get_name()])
+
+                    ids.append(self._cur.lastrowid)
+            except sqlite.DatabaseError, e:
+                raise DatabaseException("SQLite: %s" % (e))
+        return ids
+
+    def _deletenodetags(self, node):
+        try:
+            sql = "DELETE FROM LOOKUP WHERE NODE = ?"
+            self._cur.execute(sql, [node.get_id()])
+        except sqlite.DatabaseError, e:
+            raise DatabaseException("SQLite: %s" % (e))
+        self._commit()
+
+    def _setnodetags(self, node):
+        self._deletenodetags(node)
+        ids = self._tagids(node.get_tags())
+        for i in ids:
+            sql = "INSERT OR REPLACE INTO LOOKUP VALUES(?, ?)"
+            params = [node.get_id(), i]
+
+            try:
+                self._cur.execute(sql, params)
+            except sqlite.DatabaseError, e:
+                raise DatabaseException("SQLite: %s" % (e))
+        self._commit()
+
+    def _checktags(self):
+        try:
+            sql = "DELETE FROM TAGS WHERE ID NOT IN (SELECT TAG FROM" \
+                + " LOOKUP GROUP BY TAG)"
+            self._cur.execute(sql)
+        except sqlite.DatabaseError, e:
+            raise DatabaseException("SQLite: %s" % (e))
+        self._commit()
+
+    def _checktables(self):
+        """
+        Check if the Pwman tables exist.
+        TODO: This method should check the version of the
+        database. If it finds an old format it should
+        exis, and prompt the user to convert the database
+        to the new version with a designated script.
+        """
+        self._cur.execute("PRAGMA TABLE_INFO(NODES)")
+        if (self._cur.fetchone() is None):
+            # table doesn't exist, create it
+            # SQLite does have constraints implemented at the moment
+            # so datatype will just be a string
+            self._cur.execute("CREATE TABLE NODES (ID INTEGER PRIMARY KEY"
+                              + " AUTOINCREMENT,DATA BLOB NOT NULL)")
+            self._cur.execute("CREATE TABLE TAGS"
+                              + "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
+                              + "DATA BLOB NOT NULL UNIQUE)")
+            self._cur.execute("CREATE TABLE LOOKUP"
+                              + "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
+                              + " PRIMARY KEY(NODE, TAG))")
+
+            self._cur.execute("CREATE TABLE KEY"
+                              + "(THEKEY TEXT NOT NULL DEFAULT '')")
+            self._cur.execute("INSERT INTO KEY VALUES('')")
+            # create a table to hold DB version info
+            self._cur.execute("CREATE TABLE DBVERSION"
+                              + "(DBVERSION TEXT NOT NULL DEFAULT '0.4')")
+            self._cur.execute("INSERT INTO DBVERSION VALUES('0.4')")
+            try:
+                self._con.commit()
+            except DatabaseException, e:
+                self._con.rollback()
+                raise e
+
+    def savekey(self, key):
+        """
+        This function is saving the key to table KEY.
+        The key already arrives as an encrypted string.
+        It is the same self._keycrypted from
+        crypto py (check with id(self._keycrypted) and
+        id(key) here.
+        """
+        sql = "UPDATE KEY SET THEKEY = ?"
+        values = [key]
+        self._cur.execute(sql, values)
+        try:
+            self._con.commit()
+        except sqlite.DatabaseError, e:
+            self._con.rollback()
+            raise DatabaseException(
+                "SQLite: Error saving key [%s]" % (e))
+
+    def loadkey(self):
+        """
+        fetch the key to database. the key is also stored
+        encrypted.
+        """
+        self._cur.execute("SELECT THEKEY FROM KEY")
+        keyrow = self._cur.fetchone()
+        if (keyrow[0] == ''):
+            return None
+        else:
+            return keyrow[0]
+
+
 class SQLiteDatabase(Database):
     """SQLite Database implementation"""
 
@@ -70,8 +409,8 @@ class SQLiteDatabase(Database):
             sql = "SELECT DATA FROM TAGS ORDER BY DATA ASC"
         else:
             sql = ("SELECT TAGS.DATA FROM LOOKUP"
-                   +" INNER JOIN TAGS ON LOOKUP.TAG = TAGS.ID"
-                   +" WHERE NODE IN (")
+                   + " INNER JOIN TAGS ON LOOKUP.TAG = TAGS.ID"
+                   + " WHERE NODE IN (")
             first = True
             # if using the command filter, the code crashes ...
             # 
@@ -81,7 +420,8 @@ class SQLiteDatabase(Database):
                 else:
                     first = False
 
-                sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS ON TAG = TAGS.ID "
+                sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS ON TAG " +
+                        + " = TAGS.ID "
                         + " WHERE TAGS.DATA = ?")
                 params.append(cPickle.dumps(t))
             sql += ") EXCEPT SELECT DATA FROM TAGS WHERE "
@@ -98,12 +438,9 @@ class SQLiteDatabase(Database):
             self._cur.execute(sql, params)
             tags = []
             row = self._cur.fetchone()
-            while (row != None):
-                tagstring = str(row[0])
-                m = re.search('S\"S\'(.+?)\'', tagstring)
-                if m:
-                    found = m.group(1)
-                    tags.append(Tag(found))
+            while (row is not None):
+                tag = cPickle.loads(str(row[0]))
+                tags.append(tag)
                 row = self._cur.fetchone()
             return tags
         except sqlite.DatabaseError, e:
@@ -117,7 +454,7 @@ class SQLiteDatabase(Database):
                 self._cur.execute(sql, [i])
 
                 row = self._cur.fetchone()
-                if row != None:
+                if row is not None:
                     node = cPickle.loads(str(row[0]))
                     node.set_id(i)
                     nodes.append(node)
@@ -126,10 +463,11 @@ class SQLiteDatabase(Database):
         return nodes
 
     def editnode(self, id, node):
-        if not isinstance(node, Node): raise DatabaseException(
+        if not isinstance(node, Node):
+            raise DatabaseException(
                 "Tried to insert foreign object into database [%s]" % node)
         try:
-            sql = "UPDATE NODES SET DATA = ? WHERE ID = ?";
+            sql = "UPDATE NODES SET DATA = ? WHERE ID = ?"
             self._cur.execute(sql, [cPickle.dumps(node), id])
 
         except sqlite.DatabaseError, e:
@@ -141,8 +479,9 @@ class SQLiteDatabase(Database):
     def addnodes(self, nodes):
         for n in nodes:
             sql = "INSERT INTO NODES(DATA) VALUES(?)"
-            if not isinstance(n, Node): raise DatabaseException(
-                "Tried to insert foreign object into database [%s]", n)
+            if not isinstance(n, Node):
+                raise DatabaseException(
+                    "Tried to insert foreign object into database [%s]", n)
             value = cPickle.dumps(n)
             try:
                 self._cur.execute(sql, [value])
@@ -156,10 +495,11 @@ class SQLiteDatabase(Database):
 
     def removenodes(self, nodes):
         for n in nodes:
-            if not isinstance(n, Node): raise DatabaseException(
-                "Tried to delete foreign object from database [%s]", n)
+            if not isinstance(n, Node):
+                raise DatabaseException(
+                    "Tried to delete foreign object from database [%s]", n)
             try:
-                sql = "DELETE FROM NODES WHERE ID = ?";
+                sql = "DELETE FROM NODES WHERE ID = ?"
                 self._cur.execute(sql, [n.get_id()])
 
             except sqlite.DatabaseError, e:
@@ -181,7 +521,8 @@ class SQLiteDatabase(Database):
                     sql += " INTERSECT "
                 else:
                     first = False
-                sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS ON TAG = TAGS.ID"
+                sql += ("SELECT NODE FROM LOOKUP LEFT JOIN TAGS "
+                        + " ON TAG = TAGS.ID"
                         + " WHERE TAGS.DATA = ? ")
 
                 params.append(cPickle.dumps(t))
@@ -190,7 +531,7 @@ class SQLiteDatabase(Database):
 
             ids = []
             row = self._cur.fetchone()
-            while (row != None):
+            while (row is not None):
                 ids.append(row[0])
                 row = self._cur.fetchone()
             return ids
@@ -209,14 +550,15 @@ class SQLiteDatabase(Database):
         ids = []
         for t in tags:
             sql = "SELECT ID FROM TAGS WHERE DATA = ?"
-            if not isinstance(t, Tag): raise DatabaseException(
-                "Tried to insert foreign object into database [%s]", t)
+            if not isinstance(t, Tag):
+                raise DatabaseException(
+                    "Tried to insert foreign object into database [%s]", t)
             data = cPickle.dumps(t)
 
             try:
                 self._cur.execute(sql, [data])
                 row = self._cur.fetchone()
-                if (row != None):
+                if (row is not None):
                     ids.append(row[0])
                 else:
                     sql = "INSERT INTO TAGS(DATA) VALUES(?)"
@@ -251,7 +593,8 @@ class SQLiteDatabase(Database):
 
     def _checktags(self):
         try:
-            sql = "DELETE FROM TAGS WHERE ID NOT IN (SELECT TAG FROM LOOKUP GROUP BY TAG)"
+            sql = "DELETE FROM TAGS WHERE ID NOT IN (SELECT TAG FROM " \
+                  + "LOOKUP GROUP BY TAG)"
             self._cur.execute(sql)
         except sqlite.DatabaseError, e:
             raise DatabaseException("SQLite: %s" % (e))
@@ -260,26 +603,26 @@ class SQLiteDatabase(Database):
     def _checktables(self):
         """ Check if the Pwman tables exist """
         self._cur.execute("PRAGMA TABLE_INFO(NODES)")
-        if (self._cur.fetchone() == None):
+        if (self._cur.fetchone() is None):
             # table doesn't exist, create it
             # SQLite does have constraints implemented at the moment
             # so datatype will just be a string
-            self._cur.execute("CREATE TABLE NODES"
-                             + "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
-                             + "DATA BLOB NOT NULL)")
-            self._cur.execute("CREATE TABLE TAGS"
-                              + "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
-                              + "DATA BLOB NOT NULL UNIQUE)")
-            self._cur.execute("CREATE TABLE LOOKUP"
-                              + "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
-                              + " PRIMARY KEY(NODE, TAG))")
-
-            self._cur.execute("CREATE TABLE KEY"
-                              + "(THEKEY TEXT NOT NULL DEFAULT '')");
-            self._cur.execute("INSERT INTO KEY VALUES('')");
+            self._cur.execute("CREATE TABLE NODES "
+                              "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
+                              "DATA BLOB NOT NULL)")
+            self._cur.execute("CREATE TABLE TAGS "
+                              "(ID INTEGER PRIMARY KEY AUTOINCREMENT,"
+                              "DATA BLOB NOT NULL UNIQUE)")
+            self._cur.execute("CREATE TABLE LOOKUP "
+                              "(NODE INTEGER NOT NULL, TAG INTEGER NOT NULL,"
+                              " PRIMARY KEY(NODE, TAG))")
+
+            self._cur.execute("CREATE TABLE KEY "
+                              + "(THEKEY TEXT NOT NULL DEFAULT '')")
+            self._cur.execute("INSERT INTO KEY VALUES('')")
             try:
                 self._con.commit()
-            except DatabaseError, e:
+            except DatabaseException, e:
                 self._con.rollback()
                 raise e
 
@@ -301,13 +644,12 @@ class SQLiteDatabase(Database):
             raise DatabaseException(
                 "SQLite: Error saving key [%s]" % (e))
 
-
     def loadkey(self):
         """
         fetch the key to database. the key is also stored
         encrypted.
         """
-        self._cur.execute("SELECT THEKEY FROM KEY");
+        self._cur.execute("SELECT THEKEY FROM KEY")
         keyrow = self._cur.fetchone()
         if (keyrow[0] == ''):
             return None

+ 29 - 18
pwman/data/factory.py

@@ -1,15 +1,15 @@
 #============================================================================
 # This file is part of Pwman3.
-# 
+#
 # Pwman3 is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License, version 2
-# as published by the Free Software Foundation; 
-# 
+# as published by the Free Software Foundation;
+#
 # Pwman3 is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License
 # along with Pwman3; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
@@ -30,37 +30,48 @@ db = DBFactory.create(params)
 db.open()
 .....
 """
-from pwman.data.database import Database, DatabaseException
+from pwman.data.database import DatabaseException
 
-import pwman.util.config as config
 
-def create(type):
+def check_db_version(type):
+    if type == "SQLite":
+        try:
+            from pwman.data.drivers import sqlite
+        except ImportError:
+            raise DatabaseException("python-sqlite not installed")
+        ver = sqlite.check_db_version()
+        return ver
+     # TODO: implement version checks for other supported DBs.
+
+
+def create(type, version=None, filename=None):
     """
     create(params) -> Database
-    Create a Database instance. 
+    Create a Database instance.
     'type' can only be 'SQLite' at the moment
     """
-
-    if type == "BerkeleyDB":
-        pass
-#        db = BerkeleyDatabase.BerkeleyDatabase(params)
-    elif (type == "SQLite"):
-        try: 
+    if (type == "SQLite"):
+        try:
             from pwman.data.drivers import sqlite
-            db = sqlite.SQLiteDatabase()
-        except ImportError, e:
+            if version == 0.4 and filename:
+                db = sqlite.SQLiteDatabaseNewForm(filename)
+            elif version == 0.4:
+                db = sqlite.SQLiteDatabaseNewForm()
+            else:
+                db = sqlite.SQLiteDatabase()
+        except ImportError:
             raise DatabaseException("python-sqlite not installed")
     elif (type == "Postgresql"):
         try:
             from pwman.data.drivers import postgresql
             db = postgresql.PostgresqlDatabase()
-        except ImportError, e:
+        except ImportError:
             raise DatabaseException("python-pygresql not installed")
     elif (type == "MySQL"):
         try:
             from pwman.data.drivers import mysql
             db = mysql.MySQLDatabase()
-        except ImportError, e:
+        except ImportError:
             raise DatabaseException("python-mysqldb not installed")
     else:
         raise DatabaseException("Unknown database type specified")

+ 122 - 1
pwman/data/nodes.py

@@ -22,7 +22,128 @@
 
 from pwman.util.crypto import CryptoEngine
 
-class Node:
+class NewNode(object):
+
+    def __init__(self, username="", password="", url="", notes="", tags=[]):
+        """Initialise everything to null."""
+        self._id = 0
+        self._username = username
+        self._password = password
+        self._url = url
+        self._notes = notes
+        self._tags = []
+        self.set_tags(tags)
+
+
+    def dump_edit_to_db(self):
+        enc = CryptoEngine.get()
+        dump = ""
+        dump += "username:"+self._username+"##"
+        dump += "password:"+self._password+"##"
+        dump += "url:"+self._url+"##"
+        dump += "notes:"+self._notes+"##"
+        dump += "tags:"
+        tagsloc=""
+        for tag in self._tags:
+            if isinstance(tag , str):
+                tagsloc += "tag:"+tag.strip()+"**endtag**"
+            else:
+                tagsloc += "tag:"+tag.get_name()+"**endtag**"
+        dump += tagsloc
+        dump = [dump]
+        return dump
+
+    def dump_to_db(self):
+        enc = CryptoEngine.get()
+        dump = ""
+        dump += "username:"+enc.encrypt(self._username)+"##"
+        dump += "password:"+enc.encrypt(self._password)+"##"
+        dump += "url:"+enc.encrypt(self._url)+"##"
+        dump += "notes:"+enc.encrypt(self._notes)+"##"
+        dump += "tags:"
+        tagsloc=""
+        for tag in self._tags:
+            if isinstance(tag , str):
+                tagsloc += "tag:"+tag.strip()+"**endtag**"
+            else:
+                tagsloc += "tag:"+tag.get_name()+"**endtag**"
+        dump += tagsloc
+        dump = [dump]
+        return dump
+
+    def get_tags(self):
+        tags = []
+        for tag in self._tags:
+            tags.append(tag)
+        return tags
+
+    def set_tags(self, tags):
+        """
+        this method expects a list of tag instances.
+        hence feed it with them.
+
+        fixed! the method parse_node_string in
+        SQLiteDatabase returns a dictionary,
+        but a also, Tags instances..."""
+        self._tags = []
+        for tag in tags:
+            self._tags.append(tag)
+#            self._tags.append(tag._name)
+
+    def get_id(self):
+        return self._id
+
+    def set_id(self, id):
+        self._id = id
+
+    def get_username(self):
+        """
+        Return the username.
+        This solution with strip is horribly assuming that
+        the username does not containg space as the last character.
+        The same is also true for the password.
+        """
+
+        enc = CryptoEngine.get()
+        return enc.decrypt(self._username).strip()
+
+    def set_username(self, username):
+        """Set the username."""
+        enc = CryptoEngine.get()
+        self._username = enc.encrypt(username)
+
+    def get_password(self):
+        """Return the password."""
+        enc = CryptoEngine.get()
+        return enc.decrypt(self._password).strip()
+
+    def set_password(self, password):
+        """Set the password."""
+        enc = CryptoEngine.get()
+        self._password = enc.encrypt(password).strip()
+
+    def get_url(self):
+        """Return the URL."""
+        enc = CryptoEngine.get()
+        return enc.decrypt(self._url).strip()
+
+    def set_url(self, url):
+        """Set the URL."""
+        enc = CryptoEngine.get()
+        self._url = enc.encrypt(url)
+
+    def get_notes(self):
+        """Return the Notes."""
+        enc = CryptoEngine.get()
+        return enc.decrypt(self._notes)
+
+    def set_notes(self, notes):
+        """Set the Notes."""
+        enc = CryptoEngine.get()
+        self._notes = enc.encrypt(notes)
+
+
+class Node(object):
     def __init__(self,username="",password="",url="",notes="",tags=[]):
         """Initialise everything to null."""
         self._id = 0;

+ 253 - 19
pwman/ui/cli.py

@@ -27,8 +27,10 @@ import pwman.exchange.importer as importer
 import pwman.exchange.exporter as exporter
 import pwman.util.generator as generator
 from pwman.data.nodes import Node
+from pwman.data.nodes import NewNode
 from pwman.data.tags import Tag
 from pwman.util.crypto import CryptoEngine
+from pwman.util.crypto import zerome
 #, CryptoBadKeyException, \
 #     CryptoPasswordMismatchException
 from pwman.util.callback import Callback
@@ -128,7 +130,7 @@ class PwmanCli(cmd.Cmd):
         numerics -> numerics
         leetify -> symbols
         special_chars -> special_signs
-        """        
+        """
         if argsgiven == 1:
             length = getinput("Password length (default 7): ", "7")
             length = int(length)
@@ -142,13 +144,13 @@ class PwmanCli(cmd.Cmd):
         if len(password) == 0:
             length = getinput("Password length (default 7): ", "7")
             length = int(length)
-        
+
             (password, dumpme) = generator.generate_password(length, length, \
                 True, leetify, numerics, special_signs)
             print "New password: %s" % (password)
-        
+
         return password
-        
+
     def get_url(self, default=""):
         return getinput("Url: ", default)
 
@@ -228,7 +230,7 @@ class PwmanCli(cmd.Cmd):
                     break
                 time.sleep(period)
             self.do_cls('')
-        
+
         flushtimeout = int(config.get_value("Global", "cls_timeout"))
         if flushtimeout > 0:
             if sys.platform != 'win32':
@@ -399,8 +401,8 @@ class PwmanCli(cmd.Cmd):
             else:
                 numerics = config.get_value("Generator", "numerics").lower() == 'true'
                 # TODO: allow custom leetifying through the config
-                leetify = config.get_value("Generator", "leetify").lower() == 'true' 
-                special_chars = config.get_value("Generator", "special_chars").lower() == 'true' 
+                leetify = config.get_value("Generator", "leetify").lower() == 'true'
+                special_chars = config.get_value("Generator", "special_chars").lower() == 'true'
                 password = self.get_password(0,
                                              numerics=numerics,
                                              symbols=leetify,
@@ -574,6 +576,9 @@ class PwmanCli(cmd.Cmd):
 
     def do_open(self, args):
         ids = self.get_ids(args)
+        if not args:
+            self.help_open()
+            return
         if len(ids) > 1:
             print "Can open only 1 link at a time ..."
             return None
@@ -594,9 +599,8 @@ class PwmanCli(cmd.Cmd):
 
     def help_open(self):
         self.usage("open <ID>")
-        print "Launch default browser with 'xdg-open url',\n\
-the url must contain http:// or https://."
-
+        print "Launch default browser with 'xdg-open url',\n" \
+              + "the url must contain http:// or https://."
     def help_o(self):
         self.help_open()
 
@@ -658,8 +662,8 @@ the url must contain http:// or https://."
 
     def help_new(self):
         self.usage("new")
-        print """Creates a new node., 
-You can override default config settings the following way:      
+        print """Creates a new node.,
+You can override default config settings the following way:
 pwman> n {'leetify':False, 'numerics':True}"""
 
     def help_rm(self):
@@ -711,6 +715,42 @@ pwman> n {'leetify':False, 'numerics':True}"""
         except Exception:
             pass
 
+    def __init__(self, db, hasxsel):
+        """
+        initialize CLI interface, set up the DB
+        connecion, see if we have xsel ...
+        """
+        _dbwarning = "\n*** WARNNING: You are using the old db format which" \
+                     + " uses cPickle, please upgrade your db !!! ***"
+        cmd.Cmd.__init__(self)
+        self.intro = "%s %s (c) visit: %s %s" % (pwman.appname, pwman.version,
+                                                 pwman.website, _dbwarning)
+        self._historyfile = config.get_value("Readline", "history")
+        self.hasxsel = hasxsel
+        try:
+            enc = CryptoEngine.get()
+            enc.set_callback(CLICallback())
+            self._db = db
+            self._db.open()
+        except Exception, e:
+            self.error(e)
+            sys.exit(1)
+
+        try:
+            readline.read_history_file(self._historyfile)
+        except IOError, e:
+            pass
+
+        self.prompt = "!pwman> "
+
+
+class PwmanCliNew(PwmanCli):
+    """
+    inherit from the old class, override
+    all the methods related to tags, and
+    newer Node format, so backward compatability is kept...
+    """
+
     def __init__(self, db, hasxsel):
         """
         initialize CLI interface, set up the DB
@@ -718,7 +758,7 @@ pwman> n {'leetify':False, 'numerics':True}"""
         """
         cmd.Cmd.__init__(self)
         self.intro = "%s %s (c) visit: %s" % (pwman.appname, pwman.version,
-                                            pwman.website)
+                                                 pwman.website)
         self._historyfile = config.get_value("Readline", "history")
         self.hasxsel = hasxsel
         try:
@@ -736,7 +776,195 @@ pwman> n {'leetify':False, 'numerics':True}"""
             pass
 
         self.prompt = "pwman> "
+    def print_node(self, node):
+        width = str(_defaultwidth)
+        print "Node %d." % (node.get_id())
+        print ("%"+width+"s %s") % (typeset("Username:", ANSI.Red),
+                                    node.get_username())
+        print ("%"+width+"s %s") % (typeset("Password:", ANSI.Red),
+                                    node.get_password())
+        print ("%"+width+"s %s") % (typeset("Url:", ANSI.Red),
+                                    node.get_url())
+        print ("%"+width+"s %s") % (typeset("Notes:", ANSI.Red),
+                                    node.get_notes())
+        print typeset("Tags: ", ANSI.Red),
+        for t in node.get_tags():
+            print " %s " % t
+        print
 
+        def heardEnter():
+            i, o, e = uselect.select([sys.stdin], [], [], 0.0001)
+            for s in i:
+                if s == sys.stdin:
+                    sys.stdin.readline()
+                    return True
+                return False
+
+        def heardEnterWin():
+            import msvcrt
+            c = msvcrt.kbhit()
+            if c == 1:
+                ret = msvcrt.getch()
+                if ret is not None:
+                    return True
+            return False
+
+        def waituntil_enter(somepredicate, timeout, period=0.25):
+            mustend = time.time() + timeout
+            while time.time() < mustend:
+                cond = somepredicate()
+                if cond:
+                    break
+                time.sleep(period)
+            self.do_cls('')
+
+        flushtimeout = int(config.get_value("Global", "cls_timeout"))
+        if flushtimeout > 0:
+            if sys.platform != 'win32':
+                print "Type Enter to flush screen (autoflash in 5 sec.)"
+                waituntil_enter(heardEnter, flushtimeout)
+            else:
+                print "Press any key to flush screen (autoflash in 5 sec.)"
+                waituntil_enter(heardEnterWin, flushtimeout)
+
+    def do_tags(self, arg):
+        tags = self._db.listtags()
+        if len(tags) > 0:
+            tags[0].get_name()  # hack to get password request before output
+        print "Tags: ",
+        if len(tags) == 0:
+            print "None",
+        for t in tags:
+            print "%s " % (t.get_name()),
+        print
+
+    def get_tags(self, default=None):
+        defaultstr = ''
+
+        if default:
+            for t in default:
+                defaultstr += "%s " % (t.get_name())
+        else:
+            tags = self._db.currenttags()
+            for t in tags:
+                defaultstr += "%s " % (t.get_name())
+
+        strings = []
+        tags = self._db.listtags(True)
+        for t in tags:
+            strings.append(t.get_name())
+
+        def complete(text, state):
+            count = 0
+            for s in strings:
+                if s.startswith(text):
+                    if count == state:
+                        return s
+                    else:
+                        count += 1
+
+        taglist = getinput("Tags: ", defaultstr, complete)
+        tagstrings = taglist.split()
+        tags = []
+        for tn in tagstrings:
+            _Tag = Tag(tn)
+            tags.append(_Tag)
+        return tags
+
+    def do_list(self, args):
+        if len(args.split()) > 0:
+            self.do_clear('')
+            self.do_filter(args)
+        try:
+            if sys.platform != 'win32':
+                rows, cols = gettermsize()
+            else:
+                rows, cols = 18, 80  # fix this !
+            nodeids = self._db.listnodes()
+            nodes = self._db.getnodes(nodeids)
+            cols -= 8
+            i = 0
+            for n in nodes:
+                tags = n.get_tags()
+                tagstring = ''
+                first = True
+                for t in tags:
+                    if not first:
+                        tagstring += ", "
+                    else:
+                        first = False
+                    tagstring += t
+
+                name = "%s@%s" % (n.get_username(), n.get_url())
+
+                name_len = cols * 2 / 3
+                tagstring_len = cols / 3
+                if len(name) > name_len:
+                    name = name[:name_len-3] + "..."
+                if len(tagstring) > tagstring_len:
+                    tagstring = tagstring[:tagstring_len-3] + "..."
+                fmt = "%%5d. %%-%ds %%-%ds" % (name_len, tagstring_len)
+                formatted_entry = typeset(fmt % (n.get_id(), name, tagstring),
+                                          ANSI.Yellow, False)
+                print formatted_entry
+                i += 1
+                if i > rows-2:
+                    i = 0
+                    c = getonechar("Press <Space> for more, or 'Q' to cancel")
+                    if c == 'q':
+                        break
+
+        except Exception, e:
+            self.error(e)
+
+    def do_new(self, args):
+        """
+        can override default config settings the following way:
+        Pwman3 0.2.1 (c) visit: http://github.com/pwman3/pwman3
+        pwman> n {'leetify':False, 'numerics':True, 'special_chars':True}
+        Password (Blank to generate):
+        """
+        errmsg = """could not parse config override, please input some"""\
+                 +""" kind of dictionary, e.g.: n {'leetify':False, """\
+                 +"""'numerics':True, 'special_chars':True}"""
+        try:
+            username = self.get_username()
+            if args:
+                try:
+                    args = ast.literal_eval(args)
+                except Exception:
+                    raise Exception(errmsg)
+                if not isinstance(args, dict):
+                    raise Exception(errmsg)
+                password = self.get_password(1, **args)
+            else:
+                numerics = config.get_value("Generator", "numerics").lower() == 'true'
+                # TODO: allow custom leetifying through the config
+                leetify = config.get_value("Generator", "leetify").lower() == 'true'
+                special_chars = config.get_value("Generator", "special_chars").lower() == 'true'
+                password = self.get_password(0,
+                                             numerics=numerics,
+                                             symbols=leetify,
+                                             special_signs=special_chars)
+            url = self.get_url()
+            notes = self.get_notes()
+            node = NewNode(username, password, url, notes)
+            tags = self.get_tags()
+            node.set_tags(tags)
+            self._db.addnodes([node])
+            print "Password ID: %d" % (node.get_id())
+        except Exception, e:
+            self.error(e)
+
+    def do_print(self, arg):
+        for i in self.get_ids(arg):
+            try:
+                node = self._db.getnodes([i])
+                self.print_node(node[0])
+                # when done with node erase it
+                zerome(node[0]._password)
+            except Exception, e:
+                self.error(e)
 
 
 class PwmanCliMac(PwmanCli):
@@ -763,6 +991,9 @@ class PwmanCliMac(PwmanCli):
 
     def do_open(self, args):
         ids = self.get_ids(args)
+        if not args:
+            self.help_open()
+            return
         if len(ids) > 1:
             print "Can open only 1 link at a time ..."
             return None
@@ -781,8 +1012,8 @@ class PwmanCliMac(PwmanCli):
     ##
     def help_open(self):
         self.usage("open <ID>")
-        print "Launch default browser with 'open url',\n\
-the url must contain http:// or https://."
+        print "Launch default browser with 'open url',\n" \
+              + "the url must contain http:// or https://."
 
     def help_o(self):
         self.help_open()
@@ -794,6 +1025,9 @@ the url must contain http:// or https://."
     def help_cp(self):
         self.help_copy()
 
+class PwmanCliMacNew(PwmanCliMac):
+    pass
+
 _defaultwidth = 10
 
 def getonechar(question, width=_defaultwidth):
@@ -840,7 +1074,7 @@ def getinput(question, default="", completer=None, width=_defaultwidth):
     if (not _readline_available):
         return raw_input(question.ljust(width))
     else:
-        def defaulter(): 
+        def defaulter():
             """define default behavior startup"""
             readline.insert_text(default)
 
@@ -968,15 +1202,15 @@ class CliMenu(object):
                 # substract 1 because array subscripts start at 0
                 selection = int(option) - 1
                 print "selection, ", selection
-                # new value is created by calling the editor with the 
-                # previous value as a parameter 
+                # new value is created by calling the editor with the
+                # previous value as a parameter
                 # TODO: enable overriding password policy as if new node
                 # is created.
                 if selection == 1: # for password
                     value = self.items[selection].editor(0)
                 else:
                     value = self.items[selection].editor(self.items[selection].getter())
-                
+
                 self.items[selection].setter(value)
             except (ValueError, IndexError):
                 if (option.upper() == 'X'):

+ 4 - 1
pwman/util/crypto.py

@@ -68,7 +68,10 @@ def zerome(string):
     bufsize = len(string) + 1
     offset = sys.getsizeof(string) - bufsize
     ctypes.memset(id(string) + offset, 0, bufsize)
-    
+
+# Use this to tell if crypto is successful or not
+_TAG = "PWMANCRYPTO"
+
 _INSTANCE = None
 
 # Use this to tell if crypto is successful or not

+ 53 - 24
scripts/pwman3

@@ -21,49 +21,53 @@
 #============================================================================
 import os
 import os.path
-import subprocess as sp
 
 _saveconfig = True
 
 import argparse
 
 
-parser = argparse.ArgumentParser(description='pwman3 - a command line password'\
-+'manager.')
-parser.add_argument('-c','--config', dest='cfile',
+parser = argparse.ArgumentParser(description='pwman3 - a command line password'
+                                 + ' manager.')
+parser.add_argument('-c', '--config', dest='cfile',
                     default=os.path.expanduser("~/.pwman/config"),
                     help='cofiguration file to read')
 parser.add_argument('-d', '--database', dest='dbase')
-
-parser.add_argument('-e', '--encryption', dest="algo", default="AES",
-                help="Possible options are: AES(default), ARC2, ARC4, "\
-                +"Blowfish, CAST, DES, DES3, IDEA, RC5")
-
-parser.add_argument('-t','--test',  help="Run pwman from current directory \
+parser.add_argument('-e', '--encryption', dest="algo",
+                    help="Possible options are: AES(default), ARC2, ARC4, "
+                    + "Blowfish, CAST, DES, DES3, IDEA, RC5")
+parser.add_argument('-k', '--convert', dest='dbconvert',
+                    action='store_true', default=False,
+                    # os.path.expanduser('~/.pwman/pwman.db'),
+                    help="Convert old DB format to Version >= 0.4.",
+                    )
+
+parser.add_argument('-t', '--test',  help="Run pwman from current directory \
 without installation", action="store_true")
+
 args = parser.parse_args()
 
+import sys
 if args.test:
-    import sys
-    sys.path.insert(0,os.getcwd())
+    sys.path.insert(0, os.getcwd())
 
 from pwman.util.crypto import CryptoEngine
-import getopt
-import sys
 
 if 'darwin' in sys.platform:
     from pwman.ui.cli import PwmanCliMac as PwmanCli
+    from pwman.ui.cli import PwmanCliMacNew as PwmanCliNew
     OSX = True
 else:
     from pwman.ui.cli import PwmanCli
+    from pwman.ui.cli import PwmanCliNew
     OSX = False
 
 
 import pwman.util.config as config
 import pwman.data.factory
+from pwman.data.convertdb import PwmanConvertDB
 
-config_file=args.cfile
-
+config_file = args.cfile
 
 
 def which(cmd):
@@ -85,7 +89,7 @@ try:
     # set cls_timout to negative number (e.g. -1) to disable
     default_config = {'Global': {'umask': '0100', 'colors': 'yes',
                                  'cls_timeout': '5'
-                                },
+                                 },
                       'Database': {'type': 'SQLite',
                                    'filename': os.path.join(config_dir,
                                                             "pwman.db")},
@@ -99,7 +103,7 @@ try:
     if os.path.exists(config_file):
         config.load(config_file)
         xselpath = config.get_value("Global", "xselpath")
-    elif not OSX :
+    elif not OSX:
         xselpath = which("xsel")
         config.set_value("Global", "xsel", xselpath)
     elif OSX:
@@ -109,7 +113,7 @@ try:
     if args.dbase:
         config.set_value("Database", "filename", args.dbase)
         _saveconfig = False
-    if args.algo != "Blowfish":
+    if args.algo:
             config.set_value("Encryption", "algorithm", args.algo)
             _saveconfig = False
     # set umask before creating/opening any files
@@ -119,13 +123,38 @@ try:
     enc = CryptoEngine.get()
 
     dbtype = config.get_value("Database", "type")
-    db = pwman.data.factory.create(dbtype)
-    cli = PwmanCli(db, xselpath)
+    # if it is done here, we could do the following:
+    # if db.ver == 0.4 :
+    #     db = pwman.data.factory.create(dbtyp, new_version)
+    # else:
+    #     we use the old code untouched ... insecure, but
+    #     keeps backwards compatibility ...
+    # if the database file exists check it's version
+    # else: force version 0.4
+    if os.path.exists(config.get_value("Database", "filename")):
+        dbver = pwman.data.factory.check_db_version(dbtype)
+        dbver = float(dbver.strip("\'"))
+    else:
+        dbver = 0.4
+    # the method create could create an old instance that
+    # accepts cPickle object or new style instance that
+    # accepts only strings.
+    # The user should be STRONGLY Prompted to CONVERT the
+    # database to the new format using a command line tool.
+    # version 0.5 pwman will depreciate that old and insecure
+    # code ...
+    if args.dbconvert:
+        dbconvertor = PwmanConvertDB(args, config)
+        status = dbconvertor.run()
+        sys.exit(status)
+
+    db = pwman.data.factory.create(dbtype, dbver)
+    if dbver >= 0.4:
+        cli = PwmanCliNew(db, xselpath)
+    elif dbver < 0.4:
+        cli = PwmanCli(db, xselpath)
 except SystemExit, e:
     sys.exit(e)
-except Exception, e:
-    print "Error: %s" % (e)
-    sys.exit(-1)
 
 try:
     try: