rand.c: Some gratuitous reformatting.
[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
fd44d275 27###--------------------------------------------------------------------------
d1c45f5c 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###--------------------------------------------------------------------------
a41fd652
MW
42### Python version portability.
43
2aa7d3a9
MW
44def _text(bin): return bin
45def _bin(text): return text
46
a41fd652
MW
47def _excval(): return SYS.exc_info()[1]
48
49###--------------------------------------------------------------------------
d1c45f5c
MW
50### Utilities.
51
52## The program name.
2e6a3fda 53prog = re.sub(r'^.*[/\\]', '', argv[0])
d1c45f5c 54
2e6a3fda 55def moan(msg):
d1c45f5c 56 """Issue a warning message MSG."""
25e3ebd4 57 stderr.write('%s: %s\n' % (prog, msg))
d1c45f5c 58
2e6a3fda 59def die(msg):
d1c45f5c 60 """Report MSG as a fatal error, and exit."""
2e6a3fda 61 moan(msg)
62 exit(1)
d7ab1bab 63
d1c45f5c
MW
64###--------------------------------------------------------------------------
65### Subcommand implementations.
d7ab1bab 66
d7ab1bab 67def cmd_create(av):
d1c45f5c
MW
68
69 ## Default crypto-primitive selections.
6af1255c
MW
70 cipher = 'rijndael-cbc'
71 hash = 'sha256'
d7ab1bab 72 mac = None
d1c45f5c
MW
73
74 ## Parse the options.
d7ab1bab 75 try:
6baae405
MW
76 opts, args = getopt(av, 'c:d:h:m:',
77 ['cipher=', 'database=', 'mac=', 'hash='])
d7ab1bab 78 except GetoptError:
79 return 1
8501dc39 80 dbty = 'flat'
d7ab1bab 81 for o, a in opts:
9d0d0a7b
MW
82 if o in ('-c', '--cipher'): cipher = a
83 elif o in ('-m', '--mac'): mac = a
84 elif o in ('-h', '--hash'): hash = a
6baae405 85 elif o in ('-d', '--database'): dbty = a
e3b6c0d3 86 else: raise Exception("barf")
9d0d0a7b
MW
87 if len(args) > 2: return 1
88 if len(args) >= 1: tag = args[0]
89 else: tag = 'pwsafe'
d1c45f5c 90
09b8041d
MW
91 ## Set up the database.
92 if mac is None: mac = hash + '-hmac'
6baae405
MW
93 try: dbcls = StorageBackend.byname(dbty)
94 except KeyError: die("Unknown database backend `%s'" % dbty)
95 PW.create(dbcls, file, tag,
96 C.gcciphers[cipher], C.gchashes[hash], C.gcmacs[mac])
d7ab1bab 97
98def cmd_changepp(av):
9d0d0a7b 99 if len(av) != 0: return 1
5bf6e9f5 100 with PW(file, writep = True) as pw: pw.changepp()
d7ab1bab 101
102def cmd_find(av):
9d0d0a7b 103 if len(av) != 1: return 1
5bf6e9f5 104 with PW(file) as pw:
2aa7d3a9 105 try: print(_text(pw[_bin(av[0])]))
a41fd652 106 except KeyError: die("Password `%s' not found" % _excval().args[0])
d7ab1bab 107
108def cmd_store(av):
5bf6e9f5 109 if len(av) < 1 or len(av) > 2: return 1
d7ab1bab 110 tag = av[0]
5bf6e9f5
MW
111 with PW(file, writep = True) as pw:
112 if len(av) < 2:
113 pp = C.getpass("Enter passphrase `%s': " % tag)
114 vpp = C.getpass("Confirm passphrase `%s': " % tag)
115 if pp != vpp: die("passphrases don't match")
116 elif av[1] == '-':
2aa7d3a9 117 pp = _bin(stdin.readline().rstrip('\n'))
5bf6e9f5 118 else:
2aa7d3a9
MW
119 pp = _bin(av[1])
120 pw[_bin(av[0])] = pp
d7ab1bab 121
122def cmd_copy(av):
9d0d0a7b 123 if len(av) < 1 or len(av) > 2: return 1
5bf6e9f5
MW
124 with PW(file) as pw_in:
125 with PW(av[0], writep = True) as pw_out:
126 if len(av) >= 3: pat = av[1]
127 else: pat = None
128 for k in pw_in:
2aa7d3a9
MW
129 ktext = _text(k)
130 if pat is None or fnmatch(ktext, pat): pw_out[k] = pw_in[k]
d7ab1bab 131
132def cmd_list(av):
9d0d0a7b 133 if len(av) > 1: return 1
5bf6e9f5
MW
134 with PW(file) as pw:
135 if len(av) >= 1: pat = av[0]
136 else: pat = None
137 for k in pw:
2aa7d3a9
MW
138 ktext = _text(k)
139 if pat is None or fnmatch(ktext, pat): print(ktext)
d7ab1bab 140
141def cmd_topixie(av):
9d0d0a7b 142 if len(av) > 2: return 1
5bf6e9f5
MW
143 with PW(file) as pw:
144 pix = C.Pixie()
145 if len(av) == 0:
2aa7d3a9 146 for tag in pw: pix.set(tag, pw[_bin(tag)])
5bf6e9f5 147 else:
2aa7d3a9 148 tag = _bin(av[0])
5bf6e9f5
MW
149 if len(av) >= 2: pptag = av[1]
150 else: pptag = av[0]
151 pix.set(pptag, pw[tag])
d7ab1bab 152
3aa33042 153def cmd_del(av):
9d0d0a7b 154 if len(av) != 1: return 1
5bf6e9f5 155 with PW(file, writep = True) as pw:
2aa7d3a9 156 tag = _bin(av[0])
5bf6e9f5 157 try: del pw[tag]
a41fd652 158 except KeyError: die("Password `%s' not found" % _excval().args[0])
3aa33042 159
dab03511
MW
160def cmd_xfer(av):
161
162 ## Parse the command line.
163 try: opts, args = getopt(av, 'd:', ['database='])
164 except GetoptError: return 1
165 dbty = 'flat'
166 for o, a in opts:
167 if o in ('-d', '--database'): dbty = a
e3b6c0d3 168 else: raise Exception("barf")
dab03511
MW
169 if len(args) != 1: return 1
170 try: dbcls = StorageBackend.byname(dbty)
171 except KeyError: die("Unknown database backend `%s'" % dbty)
172
173 ## Create the target database.
174 with StorageBackend.open(file) as db_in:
175 with dbcls.create(args[0]) as db_out:
176 for k, v in db_in.iter_meta(): db_out.put_meta(k, v)
177 for k, v in db_in.iter_passwds(): db_out.put_passwd(k, v)
178
d7ab1bab 179commands = { 'create': [cmd_create,
6baae405 180 '[-c CIPHER] [-d DBTYPE] [-h HASH] [-m MAC] [PP-TAG]'],
d1c45f5c
MW
181 'find' : [cmd_find, 'LABEL'],
182 'store' : [cmd_store, 'LABEL [VALUE]'],
183 'list' : [cmd_list, '[GLOB-PATTERN]'],
184 'changepp' : [cmd_changepp, ''],
185 'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'],
186 'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'],
dab03511
MW
187 'delete' : [cmd_del, 'TAG'],
188 'xfer': [cmd_xfer, '[-d DBTYPE] DEST-FILE'] }
d1c45f5c
MW
189
190###--------------------------------------------------------------------------
191### Command-line handling and dispatch.
d7ab1bab 192
193def version():
25e3ebd4
MW
194 print('%s 1.0.0' % prog)
195 print('Backend types: %s' %
196 ' '.join([c.NAME for c in StorageBackend.classes()]))
d1c45f5c 197
d7ab1bab 198def usage(fp):
25e3ebd4 199 fp.write('Usage: %s COMMAND [ARGS...]\n' % prog)
d1c45f5c 200
d7ab1bab 201def help():
202 version()
25e3ebd4 203 print('')
b2687a0a 204 usage(stdout)
25e3ebd4 205 print('''
d7ab1bab 206Maintains passwords or other short secrets securely.
207
208Options:
209
210-h, --help Show this help text.
211-v, --version Show program version number.
212-u, --usage Show short usage message.
213
2e6a3fda 214-f, --file=FILE Where to find the password-safe file.
215
d7ab1bab 216Commands provided:
25e3ebd4 217''')
05a82542 218 for c in sorted(commands):
25e3ebd4 219 print('%s %s' % (c, commands[c][1]))
d7ab1bab 220
d1c45f5c
MW
221## Choose a default database file.
222if 'PWSAFE' in environ:
223 file = environ['PWSAFE']
224else:
225 file = '%s/.pwsafe' % environ['HOME']
226
227## Parse the command-line options.
d7ab1bab 228try:
d1c45f5c
MW
229 opts, argv = getopt(argv[1:], 'hvuf:',
230 ['help', 'version', 'usage', 'file='])
d7ab1bab 231except GetoptError:
232 usage(stderr)
233 exit(1)
234for o, a in opts:
235 if o in ('-h', '--help'):
236 help()
237 exit(0)
238 elif o in ('-v', '--version'):
239 version()
240 exit(0)
241 elif o in ('-u', '--usage'):
242 usage(stdout)
243 exit(0)
244 elif o in ('-f', '--file'):
245 file = a
246 else:
e3b6c0d3 247 raise Exception("barf")
d7ab1bab 248if len(argv) < 1:
249 usage(stderr)
250 exit(1)
251
d1c45f5c 252## Dispatch to a command handler.
d7ab1bab 253if argv[0] in commands:
254 c = argv[0]
255 argv = argv[1:]
256else:
257 c = 'find'
40b16f0c
MW
258try:
259 if commands[c][0](argv):
25e3ebd4 260 stderr.write('Usage: %s %s %s\n' % (prog, c, commands[c][1]))
40b16f0c
MW
261 exit(1)
262except DecryptError:
263 die("decryption failure")
d1c45f5c
MW
264
265###----- That's all, folks --------------------------------------------------