class StorageBackend (object):
"""
- I provide a backend for password and metadata storage.
+ I provide basic protocol for password storage backends.
+
+ I'm an abstract class: you want one of my subclasses if you actually want
+ to do something useful.
Backends are responsible for storing and retrieving stuff, but not for the
cryptographic details. Backends need to store two kinds of information:
* password mappings, consisting of a number of binary labels and
payloads.
+
+ Backends need to implement the following ordinary methods. See the calling
+ methods for details of the subclass responsibilities.
+
+ BE._create(FILE) Create a new database in FILE; used by `create'.
+
+ BE._open(FILE, WRITEP)
+ Open the existing database FILE; used by `open'.
+
+ BE._close() Close the database, freeing up any resources.
+
+ BE._get_meta(NAME, DEFAULT)
+ Return the value of the metadata item with the given
+ NAME, or DEFAULT if it doesn't exist; used by
+ `get_meta'.
+
+ BE._put_meta(NAME, VALUE)
+ Set the VALUE of the metadata item with the given
+ NAME, creating one if necessary; used by `put_meta'.
+
+ BE._del_meta(NAME) Forget the metadata item with the given NAME; raise
+ `KeyError' if there is no such item; used by
+ `del_meta'.
+
+ BE._iter_meta() Return an iterator over the metadata (NAME, VALUE)
+ pairs; used by `iter_meta'.
+
+ BE._get_passwd(LABEL)
+ Return the password payload stored with the (binary)
+ LABEL; used by `get_passwd'.
+
+ BE._put_passwd(LABEL, PAYLOAD)
+ Associate the (binary) PAYLOAD with the LABEL,
+ forgetting any previous payload for that LABEL; used
+ by `put_passwd'.
+
+ BE._del_passwd(LABEL) Forget the password record with the given LABEL; used
+ by `_del_passwd'.
+
+ BE._iter_passwds() Return an iterator over the password (LABEL, PAYLOAD)
+ pairs; used by `iter_passwds'.
"""
FAIL = ['FAIL']
@classmethod
def create(cls, file):
- """Create a new database in the named FILE, using this backend."""
+ """
+ Create a new database in the named FILE, using this backend.
+
+ Subclasses must implement the `_create' instance method.
+ """
return cls(writep = True, _magic = lambda me: me._create(file))
- def _create(me, file):
- me._db = _G.open(file, 'n', 0600)
def __init__(me, file = None, writep = False, _magic = None, *args, **kw):
"""
Main constructor.
+
+ Subclasses are not, in general, expected to override this: there's a
+ somewhat hairy protocol between the constructor and some of the class
+ methods. Instead, the main hook for customization is the subclass's
+ `_open' method, which is invoked in the usual case.
"""
super(StorageBackend, me).__init__(*args, **kw)
if _magic is not None: _magic(me)
elif file is None: raise ValueError, 'missing file parameter'
- else: me._db = _G.open(file, writep and 'w' or 'r')
+ else: me._open(file, writep)
me._writep = writep
me._livep = True
Close the database.
It is harmless to attempt to close a database which has been closed
- already.
+ already. Calls the subclass's `_close' method.
"""
if me._livep:
me._livep = False
- me._db.close()
+ me._close()
## Utilities.
If no such item exists, then return DEFAULT if that was set; otherwise
raise a `KeyError'.
+
+ This calls the subclass's `_get_meta' method, which should return the
+ requested item or return the given DEFAULT value. It may assume that the
+ name is valid and the database is open.
"""
me._check_meta_name(name)
me._check_live()
- try: value = me._db[name]
- except KeyError: value = default
+ value = me._get_meta(name, default)
if value is StorageBackend.FAIL: raise KeyError, name
return value
def put_meta(me, name, value):
- """Store VALUE in the metadata item called NAME."""
+ """
+ Store VALUE in the metadata item called NAME.
+
+ This calls the subclass's `_put_meta' method, which may assume that the
+ name is valid and the database is open for writing.
+ """
me._check_meta_name(name)
me._check_write()
- me._db[name] = value
+ me._put_meta(name, value)
def del_meta(me, name):
- """Forget about the metadata item with the given NAME."""
+ """
+ Forget about the metadata item with the given NAME.
+
+ This calls the subclass's `_del_meta' method, which may assume that the
+ name is valid and the database is open for writing.
+ """
me._check_meta_name(name)
me._check_write()
- del me._db[name]
+ me._del_meta(name)
def iter_meta(me):
- """Return an iterator over the name/value metadata items."""
- me._check_live()
- k = me._db.firstkey()
- while k is not None:
- if not k.startswith('$'): yield k, me._db[k]
- k = me._db.nextkey(k)
+ """
+ Return an iterator over the name/value metadata items.
- ## Passwords.
+ This calls the subclass's `_iter_meta' method, which may assume that the
+ database is open.
+ """
+ me._check_live()
+ return me._iter_meta()
def get_passwd(me, label):
"""
Fetch and return the payload stored with the (opaque, binary) LABEL.
If there is no such payload then raise `KeyError'.
+
+ This calls the subclass's `_get_passwd' method, which may assume that the
+ database is open.
"""
me._check_live()
- return me._db['$' + label]
+ return me._get_passwd(label)
def put_passwd(me, label, payload):
"""
Associate the (opaque, binary) PAYLOAD with the (opaque, binary) LABEL.
Any previous payload for LABEL is forgotten.
+
+ This calls the subclass's `_put_passwd' method, which may assume that the
+ database is open for writing.
"""
me._check_write()
- me._db['$' + label] = payload
+ me._put_passwd(label, payload)
def del_passwd(me, label):
"""
Forget any PAYLOAD associated with the (opaque, binary) LABEL.
If there is no such payload then raise `KeyError'.
+
+ This calls the subclass's `_del_passwd' method, which may assume that the
+ database is open for writing.
"""
me._check_write()
- del me._db['$' + label]
+ me._del_passwd(label, payload)
def iter_passwds(me):
- """Return an iterator over the stored password label/payload pairs."""
+ """
+ Return an iterator over the stored password label/payload pairs.
+
+ This calls the subclass's `_iter_passwds' method, which may assume that
+ the database is open.
+ """
me._check_live()
+ return me._iter_passwds()
+
+class GDBMStorageBackend (StorageBackend):
+ """
+ My instances store password data in a GDBM database.
+
+ Metadata and password entries are mixed into the same database. The key
+ for a metadata item is simply its name; the key for a password entry is
+ the entry's label prefixed by `$', since we're guaranteed that no
+ metadata item name begins with `$'.
+ """
+
+ def _open(me, file, writep):
+ try: me._db = _G.open(file, writep and 'w' or 'r')
+ except _G.error, e: raise StorageBackendRefusal, e
+
+ def _create(me, file):
+ me._db = _G.open(file, 'n', 0600)
+
+ def _close(me):
+ me._db.close()
+ me._db = None
+
+ def _get_meta(me, name, default):
+ try: return me._db[name]
+ except KeyError: return default
+
+ def _put_meta(me, name, value):
+ me._db[name] = value
+
+ def _del_meta(me, name):
+ del me._db[name]
+
+ def _iter_meta(me):
+ k = me._db.firstkey()
+ while k is not None:
+ if not k.startswith('$'): yield k, me._db[k]
+ k = me._db.nextkey(k)
+
+ def _get_passwd(me, label):
+ return me._db['$' + label]
+
+ def _put_passwd(me, label, payload):
+ me._db['$' + label] = payload
+
+ def _del_passwd(me, label):
+ del me._db['$' + label]
+
+ def _iter_passwds(me):
k = me._db.firstkey()
while k is not None:
if k.startswith('$'): yield k[1:], me._db[k]
"""
## Open the database.
- me.db = StorageBackend(file, writep)
+ me.db = GDBMStorageBackend(file, writep)
## Find out what crypto to use.
c = _C.gcciphers[me.db.get_meta('cipher')]
## Set up and initialize the database.
kct = ppk.encrypt(_C.WriteBuffer().putblk16(ck).putblk16(mk))
- with StorageBackend.create(file) as db:
+ with GDBM.StorageBackend.create(file) as db:
db.put_meta('tag', tag)
db.put_meta('salt', ppk.salt)
db.put_meta('cipher', c.name)