*.py: Hack around Python exception-catching syntax mismatch.
[catacomb-python] / pwsafe
CommitLineData
24b3d57b 1#! /usr/bin/python
d1c45f5c
MW
2### -*-python-*-
3###
4### Tool for maintaining a secure-ish password database
5###
6### (c) 2005 Straylight/Edgeware
7###
8
9###----- Licensing notice ---------------------------------------------------
10###
11### This file is part of the Python interface to Catacomb.
12###
13### Catacomb/Python is free software; you can redistribute it and/or modify
14### it under the terms of the GNU General Public License as published by
15### the Free Software Foundation; either version 2 of the License, or
16### (at your option) any later version.
17###
18### Catacomb/Python is distributed in the hope that it will be useful,
19### but WITHOUT ANY WARRANTY; without even the implied warranty of
20### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21### GNU General Public License for more details.
22###
23### You should have received a copy of the GNU General Public License
24### along with Catacomb/Python; if not, write to the Free Software Foundation,
25### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26
27###---------------------------------------------------------------------------
28### Imported modules.
d7ab1bab 29
85f15f07
MW
30from __future__ import with_statement
31
43c09851 32from os import environ
d7ab1bab 33from sys import argv, exit, stdin, stdout, stderr
34from getopt import getopt, GetoptError
d7ab1bab 35from fnmatch import fnmatch
80dc9c94 36import re
2e6a3fda 37
d1c45f5c
MW
38import catacomb as C
39from catacomb.pwsafe import *
40
41###--------------------------------------------------------------------------
f6d012db
MW
42### Python version portability.
43
44def _excval(): return SYS.exc_info()[1]
45
46###--------------------------------------------------------------------------
d1c45f5c
MW
47### Utilities.
48
49## The program name.
2e6a3fda 50prog = re.sub(r'^.*[/\\]', '', argv[0])
d1c45f5c 51
2e6a3fda 52def moan(msg):
d1c45f5c 53 """Issue a warning message MSG."""
2e6a3fda 54 print >>stderr, '%s: %s' % (prog, msg)
d1c45f5c 55
2e6a3fda 56def die(msg):
d1c45f5c 57 """Report MSG as a fatal error, and exit."""
2e6a3fda 58 moan(msg)
59 exit(1)
d7ab1bab 60
d1c45f5c
MW
61###--------------------------------------------------------------------------
62### Subcommand implementations.
d7ab1bab 63
d7ab1bab 64def cmd_create(av):
d1c45f5c
MW
65
66 ## Default crypto-primitive selections.
6af1255c
MW
67 cipher = 'rijndael-cbc'
68 hash = 'sha256'
d7ab1bab 69 mac = None
d1c45f5c
MW
70
71 ## Parse the options.
d7ab1bab 72 try:
6baae405
MW
73 opts, args = getopt(av, 'c:d:h:m:',
74 ['cipher=', 'database=', 'mac=', 'hash='])
d7ab1bab 75 except GetoptError:
76 return 1
8501dc39 77 dbty = 'flat'
d7ab1bab 78 for o, a in opts:
9d0d0a7b
MW
79 if o in ('-c', '--cipher'): cipher = a
80 elif o in ('-m', '--mac'): mac = a
81 elif o in ('-h', '--hash'): hash = a
6baae405 82 elif o in ('-d', '--database'): dbty = a
1c7419c9 83 else: raise Exception("barf")
9d0d0a7b
MW
84 if len(args) > 2: return 1
85 if len(args) >= 1: tag = args[0]
86 else: tag = 'pwsafe'
d1c45f5c 87
09b8041d
MW
88 ## Set up the database.
89 if mac is None: mac = hash + '-hmac'
6baae405
MW
90 try: dbcls = StorageBackend.byname(dbty)
91 except KeyError: die("Unknown database backend `%s'" % dbty)
92 PW.create(dbcls, file, tag,
93 C.gcciphers[cipher], C.gchashes[hash], C.gcmacs[mac])
d7ab1bab 94
95def cmd_changepp(av):
9d0d0a7b 96 if len(av) != 0: return 1
5bf6e9f5 97 with PW(file, writep = True) as pw: pw.changepp()
d7ab1bab 98
99def cmd_find(av):
9d0d0a7b 100 if len(av) != 1: return 1
5bf6e9f5
MW
101 with PW(file) as pw:
102 try: print pw[av[0]]
f6d012db 103 except KeyError: die("Password `%s' not found" % _excval().args[0])
d7ab1bab 104
105def cmd_store(av):
5bf6e9f5 106 if len(av) < 1 or len(av) > 2: return 1
d7ab1bab 107 tag = av[0]
5bf6e9f5
MW
108 with PW(file, writep = True) as pw:
109 if len(av) < 2:
110 pp = C.getpass("Enter passphrase `%s': " % tag)
111 vpp = C.getpass("Confirm passphrase `%s': " % tag)
112 if pp != vpp: die("passphrases don't match")
113 elif av[1] == '-':
63ba6dfa 114 pp = stdin.readline().rstrip('\n')
5bf6e9f5
MW
115 else:
116 pp = av[1]
63ba6dfa 117 pw[av[0]] = pp
d7ab1bab 118
119def cmd_copy(av):
9d0d0a7b 120 if len(av) < 1 or len(av) > 2: return 1
5bf6e9f5
MW
121 with PW(file) as pw_in:
122 with PW(av[0], writep = True) as pw_out:
123 if len(av) >= 3: pat = av[1]
124 else: pat = None
125 for k in pw_in:
126 if pat is None or fnmatch(k, pat): pw_out[k] = pw_in[k]
d7ab1bab 127
128def cmd_list(av):
9d0d0a7b 129 if len(av) > 1: return 1
5bf6e9f5
MW
130 with PW(file) as pw:
131 if len(av) >= 1: pat = av[0]
132 else: pat = None
133 for k in pw:
134 if pat is None or fnmatch(k, pat): print k
d7ab1bab 135
136def cmd_topixie(av):
9d0d0a7b 137 if len(av) > 2: return 1
5bf6e9f5
MW
138 with PW(file) as pw:
139 pix = C.Pixie()
140 if len(av) == 0:
141 for tag in pw: pix.set(tag, pw[tag])
142 else:
143 tag = av[0]
144 if len(av) >= 2: pptag = av[1]
145 else: pptag = av[0]
146 pix.set(pptag, pw[tag])
d7ab1bab 147
3aa33042 148def cmd_del(av):
9d0d0a7b 149 if len(av) != 1: return 1
5bf6e9f5
MW
150 with PW(file, writep = True) as pw:
151 tag = av[0]
152 try: del pw[tag]
f6d012db 153 except KeyError: die("Password `%s' not found" % _excval().args[0])
3aa33042 154
dab03511
MW
155def cmd_xfer(av):
156
157 ## Parse the command line.
158 try: opts, args = getopt(av, 'd:', ['database='])
159 except GetoptError: return 1
160 dbty = 'flat'
161 for o, a in opts:
162 if o in ('-d', '--database'): dbty = a
1c7419c9 163 else: raise Exception("barf")
dab03511
MW
164 if len(args) != 1: return 1
165 try: dbcls = StorageBackend.byname(dbty)
166 except KeyError: die("Unknown database backend `%s'" % dbty)
167
168 ## Create the target database.
169 with StorageBackend.open(file) as db_in:
170 with dbcls.create(args[0]) as db_out:
171 for k, v in db_in.iter_meta(): db_out.put_meta(k, v)
172 for k, v in db_in.iter_passwds(): db_out.put_passwd(k, v)
173
d7ab1bab 174commands = { 'create': [cmd_create,
6baae405 175 '[-c CIPHER] [-d DBTYPE] [-h HASH] [-m MAC] [PP-TAG]'],
d1c45f5c
MW
176 'find' : [cmd_find, 'LABEL'],
177 'store' : [cmd_store, 'LABEL [VALUE]'],
178 'list' : [cmd_list, '[GLOB-PATTERN]'],
179 'changepp' : [cmd_changepp, ''],
180 'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'],
181 'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'],
dab03511
MW
182 'delete' : [cmd_del, 'TAG'],
183 'xfer': [cmd_xfer, '[-d DBTYPE] DEST-FILE'] }
d1c45f5c
MW
184
185###--------------------------------------------------------------------------
186### Command-line handling and dispatch.
d7ab1bab 187
188def version():
2e6a3fda 189 print '%s 1.0.0' % prog
6baae405
MW
190 print 'Backend types: %s' % \
191 ' '.join([c.NAME for c in StorageBackend.classes()])
d1c45f5c 192
d7ab1bab 193def usage(fp):
2e6a3fda 194 print >>fp, 'Usage: %s COMMAND [ARGS...]' % prog
d1c45f5c 195
d7ab1bab 196def help():
197 version()
198 print
b2687a0a 199 usage(stdout)
d7ab1bab 200 print '''
201Maintains passwords or other short secrets securely.
202
203Options:
204
205-h, --help Show this help text.
206-v, --version Show program version number.
207-u, --usage Show short usage message.
208
2e6a3fda 209-f, --file=FILE Where to find the password-safe file.
210
d7ab1bab 211Commands provided:
212'''
05a82542 213 for c in sorted(commands):
d7ab1bab 214 print '%s %s' % (c, commands[c][1])
215
d1c45f5c
MW
216## Choose a default database file.
217if 'PWSAFE' in environ:
218 file = environ['PWSAFE']
219else:
220 file = '%s/.pwsafe' % environ['HOME']
221
222## Parse the command-line options.
d7ab1bab 223try:
d1c45f5c
MW
224 opts, argv = getopt(argv[1:], 'hvuf:',
225 ['help', 'version', 'usage', 'file='])
d7ab1bab 226except GetoptError:
227 usage(stderr)
228 exit(1)
229for o, a in opts:
230 if o in ('-h', '--help'):
231 help()
232 exit(0)
233 elif o in ('-v', '--version'):
234 version()
235 exit(0)
236 elif o in ('-u', '--usage'):
237 usage(stdout)
238 exit(0)
239 elif o in ('-f', '--file'):
240 file = a
241 else:
1c7419c9 242 raise Exception("barf")
d7ab1bab 243if len(argv) < 1:
244 usage(stderr)
245 exit(1)
246
d1c45f5c 247## Dispatch to a command handler.
d7ab1bab 248if argv[0] in commands:
249 c = argv[0]
250 argv = argv[1:]
251else:
252 c = 'find'
40b16f0c
MW
253try:
254 if commands[c][0](argv):
255 print >>stderr, 'Usage: %s %s %s' % (prog, c, commands[c][1])
256 exit(1)
257except DecryptError:
258 die("decryption failure")
d1c45f5c
MW
259
260###----- That's all, folks --------------------------------------------------