#! /usr/bin/python ### -*-python-*- ### ### Tool for maintaining a secure-ish password database ### ### (c) 2005 Straylight/Edgeware ### ###----- Licensing notice --------------------------------------------------- ### ### This file is part of the Python interface to Catacomb. ### ### Catacomb/Python is free software; you can redistribute it and/or modify ### it under the terms of the GNU General Public License as published by ### the Free Software Foundation; either version 2 of the License, or ### (at your option) any later version. ### ### Catacomb/Python 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 Catacomb/Python; if not, write to the Free Software Foundation, ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ###--------------------------------------------------------------------------- ### Imported modules. from __future__ import with_statement from os import environ from sys import argv, exit, stdin, stdout, stderr from getopt import getopt, GetoptError from fnmatch import fnmatch import re import catacomb as C from catacomb.pwsafe import * ###-------------------------------------------------------------------------- ### Utilities. ## The program name. prog = re.sub(r'^.*[/\\]', '', argv[0]) def moan(msg): """Issue a warning message MSG.""" print >>stderr, '%s: %s' % (prog, msg) def die(msg): """Report MSG as a fatal error, and exit.""" moan(msg) exit(1) ###-------------------------------------------------------------------------- ### Subcommand implementations. def cmd_create(av): ## Default crypto-primitive selections. cipher = 'blowfish-cbc' hash = 'rmd160' mac = None ## Parse the options. try: opts, args = getopt(av, 'c:d:h:m:', ['cipher=', 'database=', 'mac=', 'hash=']) except GetoptError: return 1 dbty = 'flat' 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 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): if len(av) != 0: return 1 with PW(file, writep = True) as pw: pw.changepp() def cmd_find(av): 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): if len(av) < 1 or len(av) > 2: return 1 tag = av[0] 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): 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): 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): 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: tag = av[0] if len(av) >= 2: pptag = av[1] else: pptag = av[0] pix.set(pptag, pw[tag]) def cmd_del(av): 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, '[-c CIPHER] [-d DBTYPE] [-h HASH] [-m MAC] [PP-TAG]'], 'find' : [cmd_find, 'LABEL'], 'store' : [cmd_store, 'LABEL [VALUE]'], 'list' : [cmd_list, '[GLOB-PATTERN]'], 'changepp' : [cmd_changepp, ''], 'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'], 'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'], 'delete' : [cmd_del, 'TAG'], 'xfer': [cmd_xfer, '[-d DBTYPE] DEST-FILE'] } ###-------------------------------------------------------------------------- ### 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 help(): version() print usage(stdout) print ''' Maintains passwords or other short secrets securely. Options: -h, --help Show this help text. -v, --version Show program version number. -u, --usage Show short usage message. -f, --file=FILE Where to find the password-safe file. Commands provided: ''' for c in sorted(commands): print '%s %s' % (c, commands[c][1]) ## Choose a default database file. if 'PWSAFE' in environ: file = environ['PWSAFE'] else: file = '%s/.pwsafe' % environ['HOME'] ## Parse the command-line options. try: opts, argv = getopt(argv[1:], 'hvuf:', ['help', 'version', 'usage', 'file=']) except GetoptError: usage(stderr) exit(1) for o, a in opts: if o in ('-h', '--help'): help() exit(0) elif o in ('-v', '--version'): version() exit(0) elif o in ('-u', '--usage'): usage(stdout) exit(0) elif o in ('-f', '--file'): file = a else: raise 'Barf!' if len(argv) < 1: usage(stderr) exit(1) ## Dispatch to a command handler. if argv[0] in commands: c = argv[0] argv = argv[1:] else: c = 'find' 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 --------------------------------------------------