chiark / gitweb /
catacomb/__init__.py: Settle on SHAKE256 for X448 box-key generation.
[catacomb-python] / pwsafe
diff --git a/pwsafe b/pwsafe
index a5d15eac6cd78c53444982bc78b5b47660722304..c12f856bd5e7dee42bfec95c67dd254c2b16219c 100644 (file)
--- a/pwsafe
+++ b/pwsafe
@@ -27,7 +27,8 @@
 ###---------------------------------------------------------------------------
 ### Imported modules.
 
 ###---------------------------------------------------------------------------
 ### Imported modules.
 
-import gdbm as G
+from __future__ import with_statement
+
 from os import environ
 from sys import argv, exit, stdin, stdout, stderr
 from getopt import getopt, GetoptError
 from os import environ
 from sys import argv, exit, stdin, stdout, stderr
 from getopt import getopt, GetoptError
@@ -52,28 +53,6 @@ def die(msg):
   moan(msg)
   exit(1)
 
   moan(msg)
   exit(1)
 
-def chomp(pp):
-  """Return the string PP, without its trailing newline if it has one."""
-  if len(pp) > 0 and pp[-1] == '\n':
-    pp = pp[:-1]
-  return pp
-
-def asciip(s):
-  """Answer whether all of the characters of S are plain ASCII."""
-  for ch in s:
-    if ch < ' ' or ch > '~': return False
-  return True
-
-def present(s):
-  """
-  Return a presentation form of the string S.
-
-  If S is plain ASCII, then return S unchanged; otherwise return it as one of
-  Catacomb's ByteString objects.
-  """
-  if asciip(s): return s
-  return C.ByteString(s)
-
 ###--------------------------------------------------------------------------
 ### Subcommand implementations.
 
 ###--------------------------------------------------------------------------
 ### Subcommand implementations.
 
@@ -86,138 +65,109 @@ def cmd_create(av):
 
   ## Parse the options.
   try:
 
   ## Parse the options.
   try:
-    opts, args = getopt(av, 'c:h:m:', ['cipher=', 'mac=', 'hash='])
+    opts, args = getopt(av, 'c:d:h:m:',
+                        ['cipher=', 'database=', 'mac=', 'hash='])
   except GetoptError:
     return 1
   except GetoptError:
     return 1
+  dbty = 'flat'
   for o, a in opts:
   for o, a in opts:
-    if o in ('-c', '--cipher'):
-      cipher = a
-    elif o in ('-m', '--mac'):
-      mac = a
-    elif o in ('-h', '--hash'):
-      hash = a
-    else:
-      raise 'Barf!'
-  if len(args) > 2:
-    return 1
-  if len(args) >= 1:
-    tag = args[0]
-  else:
-    tag = 'pwsafe'
-
-  ## Choose a passphrase, and generate master keys.
-  pp = C.ppread(tag, C.PMODE_VERIFY)
-  if not mac: mac = hash + '-hmac'
-  c = C.gcciphers[cipher]
-  h = C.gchashes[hash]
-  m = C.gcmacs[mac]
-  ppk = PW.PPK(pp, c, h, m)
-  ck = C.rand.block(c.keysz.default)
-  mk = C.rand.block(m.keysz.default)
-  k = Crypto(c, h, m, ck, mk)
-
-  ## Set up the database, storing the basic information we need.
-  db = G.open(file, 'n', 0600)
-  db['tag'] = tag
-  db['salt'] = ppk.salt
-  db['cipher'] = cipher
-  db['hash'] = hash
-  db['mac'] = mac
-  db['key'] = ppk.encrypt(wrapstr(ck) + wrapstr(mk))
-  db['magic'] = k.encrypt(C.rand.block(h.hashsz))
+    if o in ('-c', '--cipher'): cipher = a
+    elif o in ('-m', '--mac'): mac = a
+    elif o in ('-h', '--hash'): hash = a
+    elif o in ('-d', '--database'): dbty = a
+    else: raise 'Barf!'
+  if len(args) > 2: return 1
+  if len(args) >= 1: tag = args[0]
+  else: tag = 'pwsafe'
+
+  ## Set up the database.
+  if mac is None: mac = hash + '-hmac'
+  try: dbcls = StorageBackend.byname(dbty)
+  except KeyError: die("Unknown database backend `%s'" % dbty)
+  PW.create(dbcls, file, tag,
+            C.gcciphers[cipher], C.gchashes[hash], C.gcmacs[mac])
 
 def cmd_changepp(av):
 
 def cmd_changepp(av):
-  if len(av) != 0:
-    return 1
-  pw = PW(file, 'w')
-  pw.changepp()
+  if len(av) != 0: return 1
+  with PW(file, writep = True) as pw: pw.changepp()
 
 def cmd_find(av):
 
 def cmd_find(av):
-  if len(av) != 1:
-    return 1
-  pw = PW(file)
-  try:
-    print pw[av[0]]
-  except KeyError, exc:
-    die('Password `%s\' not found.' % exc.args[0])
+  if len(av) != 1: return 1
+  with PW(file) as pw:
+    try: print pw[av[0]]
+    except KeyError, exc: die("Password `%s' not found" % exc.args[0])
 
 def cmd_store(av):
 
 def cmd_store(av):
-  if len(av) < 1 or len(av) > 2:
-    return 1
+  if len(av) < 1 or len(av) > 2: return 1
   tag = av[0]
   tag = av[0]
-  if len(av) < 2:
-    pp = C.getpass("Enter passphrase `%s': " % tag)
-    vpp = C.getpass("Confirm passphrase `%s': " % tag)
-    if pp != vpp:
-      raise ValueError, "passphrases don't match"
-  elif av[1] == '-':
-    pp = stdin.readline()
-  else:
-    pp = av[1]
-  pw = PW(file, 'w')
-  pw[av[0]] = chomp(pp)
+  with PW(file, writep = True) as pw:
+    if len(av) < 2:
+      pp = C.getpass("Enter passphrase `%s': " % tag)
+      vpp = C.getpass("Confirm passphrase `%s': " % tag)
+      if pp != vpp: die("passphrases don't match")
+    elif av[1] == '-':
+      pp = stdin.readline().rstrip('\n')
+    else:
+      pp = av[1]
+    pw[av[0]] = pp
 
 def cmd_copy(av):
 
 def cmd_copy(av):
-  if len(av) < 1 or len(av) > 2:
-    return 1
-  pw_in = PW(file)
-  pw_out = PW(av[0], 'w')
-  if len(av) >= 3:
-    pat = av[1]
-  else:
-    pat = None
-  for k in pw_in:
-    if pat is None or fnmatch(k, pat):
-      pw_out[k] = pw_in[k]
+  if len(av) < 1 or len(av) > 2: return 1
+  with PW(file) as pw_in:
+    with PW(av[0], writep = True) as pw_out:
+      if len(av) >= 3: pat = av[1]
+      else: pat = None
+      for k in pw_in:
+        if pat is None or fnmatch(k, pat): pw_out[k] = pw_in[k]
 
 def cmd_list(av):
 
 def cmd_list(av):
-  if len(av) > 1:
-    return 1
-  pw = PW(file)
-  if len(av) >= 1:
-    pat = av[0]
-  else:
-    pat = None
-  for k in pw:
-    if pat is None or fnmatch(k, pat):
-      print k
+  if len(av) > 1: return 1
+  with PW(file) as pw:
+    if len(av) >= 1: pat = av[0]
+    else: pat = None
+    for k in pw:
+      if pat is None or fnmatch(k, pat): print k
 
 def cmd_topixie(av):
 
 def cmd_topixie(av):
-  if len(av) > 2:
-    return 1
-  pw = PW(file)
-  pix = C.Pixie()
-  if len(av) == 0:
-    for tag in pw:
-      pix.set(tag, pw[tag])
-  else:
-    tag = av[0]
-    if len(av) >= 2:
-      pptag = av[1]
+  if len(av) > 2: return 1
+  with PW(file) as pw:
+    pix = C.Pixie()
+    if len(av) == 0:
+      for tag in pw: pix.set(tag, pw[tag])
     else:
     else:
-      pptag = av[0]
-    pix.set(pptag, pw[tag])
+      tag = av[0]
+      if len(av) >= 2: pptag = av[1]
+      else: pptag = av[0]
+      pix.set(pptag, pw[tag])
 
 def cmd_del(av):
 
 def cmd_del(av):
-  if len(av) != 1:
-    return 1
-  pw = PW(file, 'w')
-  tag = av[0]
-  try:
-    del pw[tag]
-  except KeyError, exc:
-    die('Password `%s\' not found.' % exc.args[0])
-
-def cmd_dump(av):
-  db = gdbm.open(file, 'r')
-  k = db.firstkey()
-  while True:
-    if k is None: break
-    print '%r: %r' % (present(k), present(db[k]))
-    k = db.nextkey(k)
+  if len(av) != 1: return 1
+  with PW(file, writep = True) as pw:
+    tag = av[0]
+    try: del pw[tag]
+    except KeyError, exc: die("Password `%s' not found" % exc.args[0])
+
+def cmd_xfer(av):
+
+  ## Parse the command line.
+  try: opts, args = getopt(av, 'd:', ['database='])
+  except GetoptError: return 1
+  dbty = 'flat'
+  for o, a in opts:
+    if o in ('-d', '--database'): dbty = a
+    else: raise 'Barf!'
+  if len(args) != 1: return 1
+  try: dbcls = StorageBackend.byname(dbty)
+  except KeyError: die("Unknown database backend `%s'" % dbty)
+
+  ## Create the target database.
+  with StorageBackend.open(file) as db_in:
+    with dbcls.create(args[0]) as db_out:
+      for k, v in db_in.iter_meta(): db_out.put_meta(k, v)
+      for k, v in db_in.iter_passwds(): db_out.put_passwd(k, v)
 
 commands = { 'create': [cmd_create,
 
 commands = { 'create': [cmd_create,
-                        '[-c CIPHER] [-h HASH] [-m MAC] [PP-TAG]'],
+                        '[-c CIPHER] [-d DBTYPE] [-h HASH] [-m MAC] [PP-TAG]'],
              'find' : [cmd_find, 'LABEL'],
              'store' : [cmd_store, 'LABEL [VALUE]'],
              'list' : [cmd_list, '[GLOB-PATTERN]'],
              'find' : [cmd_find, 'LABEL'],
              'store' : [cmd_store, 'LABEL [VALUE]'],
              'list' : [cmd_list, '[GLOB-PATTERN]'],
@@ -225,13 +175,15 @@ commands = { 'create': [cmd_create,
              'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'],
              'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'],
              'delete' : [cmd_del, 'TAG'],
              'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'],
              'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'],
              'delete' : [cmd_del, 'TAG'],
-             'dump' : [cmd_dump, '']}
+             'xfer': [cmd_xfer, '[-d DBTYPE] DEST-FILE'] }
 
 ###--------------------------------------------------------------------------
 ### Command-line handling and dispatch.
 
 def version():
   print '%s 1.0.0' % prog
 
 ###--------------------------------------------------------------------------
 ### Command-line handling and dispatch.
 
 def version():
   print '%s 1.0.0' % prog
+  print 'Backend types: %s' % \
+      ' '.join([c.NAME for c in StorageBackend.classes()])
 
 def usage(fp):
   print >>fp, 'Usage: %s COMMAND [ARGS...]' % prog
 
 def usage(fp):
   print >>fp, 'Usage: %s COMMAND [ARGS...]' % prog
@@ -253,7 +205,7 @@ Options:
 
 Commands provided:
 '''
 
 Commands provided:
 '''
-  for c in commands:
+  for c in sorted(commands):
     print '%s %s' % (c, commands[c][1])
 
 ## Choose a default database file.
     print '%s %s' % (c, commands[c][1])
 
 ## Choose a default database file.
@@ -293,8 +245,11 @@ if argv[0] in commands:
   argv = argv[1:]
 else:
   c = 'find'
   argv = argv[1:]
 else:
   c = 'find'
-if commands[c][0](argv):
-  print >>stderr, 'Usage: %s %s %s' % (prog, c, commands[c][1])
-  exit(1)
+try:
+  if commands[c][0](argv):
+    print >>stderr, 'Usage: %s %s %s' % (prog, c, commands[c][1])
+    exit(1)
+except DecryptError:
+  die("decryption failure")
 
 ###----- That's all, folks --------------------------------------------------
 
 ###----- That's all, folks --------------------------------------------------