X-Git-Url: https://git.distorted.org.uk/~mdw/catacomb-python/blobdiff_plain/2aa7d3a9238bfb3d117ca23191ea402c5c5d6f40..54fd7594ee5df9dbc9745d98adaa01a5ed43b6e4:/catacomb/pwsafe.py diff --git a/catacomb/pwsafe.py b/catacomb/pwsafe.py index a96cda8..944cfa7 100644 --- a/catacomb/pwsafe.py +++ b/catacomb/pwsafe.py @@ -28,24 +28,45 @@ from __future__ import with_statement +import binascii as _B import errno as _E import os as _OS -from cStringIO import StringIO as _StringIO +import sys as _SYS + +if _SYS.version_info >= (3,): from io import StringIO as _StringIO +else: from cStringIO import StringIO as _StringIO import catacomb as _C ###-------------------------------------------------------------------------- ### Python version portability. -def _bin(text): return text -def _text(bin): return bin +if _SYS.version_info >= (3,): + def _iterkeys(dict): return dict.keys() + def _itervalues(dict): return dict.values() + def _iteritems(dict): return dict.items() + def _bin(text): return text.encode(errors = "surrogateescape") + def _text(bin): return bin.decode(errors = "surrogateescape") +else: + def _iterkeys(dict): return dict.iterkeys() + def _itervalues(dict): return dict.itervalues() + def _iteritems(dict): return dict.iteritems() + def _bin(text): return text + def _text(bin): return bin _NUL = _bin('\0') _CIPHER = _bin('cipher:') _MAC = _bin('mac:') +def _with_metaclass(meta, *supers): + return meta("#" % meta.__name__, + supers or (object,), dict()) + def _excval(): return SYS.exc_info()[1] +_M600 = int("600", 8) +_M700 = int("700", 8) + ###-------------------------------------------------------------------------- ### Text encoding utilities. @@ -92,10 +113,10 @@ def _dec_metaname(name): def _b64(s): """Encode S as base64, without newlines, and trimming `=' padding.""" - return s.encode('base64').replace('\n', '').rstrip('=') + return _text(_B.b2a_base64(s)).replace('\n', '').rstrip('=') def _unb64(s): """Decode S as base64 with trimmed `=' padding.""" - return (s + '='*((4 - len(s))%4)).decode('base64') + return _B.a2b_base64(s + '='*((4 - len(s))%4)) def _enc_metaval(val): """Encode VAL as a metadata item value, returning the result.""" @@ -224,9 +245,11 @@ class StorageBackendClass (type): Register a new concrete StorageBackend subclass. """ super(StorageBackendClass, me).__init__(name, supers, dict) - if me.NAME is not None: StorageBackend.register_concrete_subclass(me) + try: name = me.NAME + except AttributeError: pass + else: StorageBackend.register_concrete_subclass(me) -class StorageBackend (object): +class StorageBackend (_with_metaclass(StorageBackendClass)): """ I provide basic protocol for password storage backends. @@ -294,8 +317,6 @@ class StorageBackend (object): priority order when opening an existing database. """ - __metaclass__ = StorageBackendClass - NAME = None PRIO = 10 ## The registry of subclasses. @@ -320,7 +341,7 @@ class StorageBackend (object): @staticmethod def classes(): """Return an iterator over the concrete subclasses.""" - return StorageBackend.CLASSES.itervalues() + return _itervalues(StorageBackend.CLASSES) @staticmethod def open(file, writep = False): @@ -516,7 +537,7 @@ else: except _G.error: raise StorageBackendRefusal(_excval()) def _create(me, file): - me._db = _G.open(file, 'n', 0600) + me._db = _G.open(file, 'n', _M600) def _close(me, abruptp): me._db.close() @@ -584,7 +605,7 @@ else: ('unknown database schema version (%d > %d)' % (ver, me.VERSION)) def _create(me, file): - fd = _OS.open(file, _OS.O_WRONLY | _OS.O_CREAT | _OS.O_EXCL, 0600) + fd = _OS.open(file, _OS.O_WRONLY | _OS.O_CREAT | _OS.O_EXCL, _M600) _OS.close(fd) try: me._db = _Q.connect(file) @@ -727,7 +748,7 @@ class PlainTextBackend (StorageBackend): me._dirtyp = False super(PlainTextBackend, me).__init__(*args, **kw) - def _create_file(me, file, mode = 0600, freshp = False): + def _create_file(me, file, mode = _M600, freshp = False): """ Make sure FILE exists, creating it with the given MODE if necessary. @@ -779,7 +800,7 @@ class PlainTextBackend (StorageBackend): if not line or line.startswith('#'): continue me._parse_line(line) - def _write_file(me, file, writebody, mode = 0600, magic = None): + def _write_file(me, file, writebody, mode = _M600, magic = None): """ Update FILE atomically. @@ -801,7 +822,7 @@ class PlainTextBackend (StorageBackend): def _write_meta(me, f, prefix = ''): """Write the metadata records to F, each with the given PREFIX.""" f.write('\n## Metadata.\n') - for k, v in me._meta.iteritems(): + for k, v in _iteritems(me._meta): f.write('%s%s=%s\n' % (prefix, _enc_metaname(k), _enc_metaval(v))) def _get_meta(me, name, default): @@ -813,7 +834,7 @@ class PlainTextBackend (StorageBackend): me._mark_dirty() del me._meta[name] def _iter_meta(me): - return me._meta.iteritems() + return _iteritems(me._meta) def _parse_passwd(me, line): """Parse LINE as a password LABEL=PAYLOAD pair, and updates `_pw'.""" @@ -823,7 +844,7 @@ class PlainTextBackend (StorageBackend): def _write_passwd(me, f, prefix = ''): """Write the password records to F, each with the given PREFIX.""" f.write('\n## Password data.\n') - for k, v in me._pw.iteritems(): + for k, v in _iteritems(me._pw): f.write('%s%s=%s\n' % (prefix, _b64(k), _b64(v))) def _get_passwd(me, label): @@ -835,7 +856,7 @@ class PlainTextBackend (StorageBackend): me._mark_dirty() del me._pw[str(label)] def _iter_passwds(me): - return me._pw.iteritems() + return _iteritems(me._pw) class FlatFileStorageBackend (PlainTextBackend): """ @@ -922,9 +943,9 @@ class DirectoryStorageBackend (PlainTextBackend): me._parse_meta(line) def _create(me, file): - _OS.mkdir(file, 0700) - _OS.mkdir(_OS.path.join(file, 'pw'), 0700) - _OS.mkdir(_OS.path.join(file, 'tmp'), 0700) + _OS.mkdir(file, _M700) + _OS.mkdir(_OS.path.join(file, 'pw'), _M700) + _OS.mkdir(_OS.path.join(file, 'tmp'), _M700) me._mark_dirty() me._dir = file @@ -944,7 +965,7 @@ class DirectoryStorageBackend (PlainTextBackend): with f: return f.read() def _put_passwd(me, label, payload): new = me._pwfile(label, 'tmp') - fd = _OS.open(new, _OS.O_WRONLY | _OS.O_CREAT | _OS.O_TRUNC, 0600) + fd = _OS.open(new, _OS.O_WRONLY | _OS.O_CREAT | _OS.O_TRUNC, _M600) _OS.close(fd) with open(new, 'wb') as f: f.write(payload) _OS.rename(new, me._pwfile(label))