chiark / gitweb /
gf: New methods for various modular operations.
[catacomb-python] / pwsafe
CommitLineData
d7ab1bab 1#! /usr/bin/python2.2
3aa33042 2# -*-python-*-
d7ab1bab 3
4import catacomb as C
43c09851 5from catacomb.pwsafe import *
6import gdbm as G
7from os import environ
d7ab1bab 8from sys import argv, exit, stdin, stdout, stderr
9from getopt import getopt, GetoptError
d7ab1bab 10from fnmatch import fnmatch
2e6a3fda 11import sre as re
12
13prog = re.sub(r'^.*[/\\]', '', argv[0])
14def moan(msg):
15 print >>stderr, '%s: %s' % (prog, msg)
16def die(msg):
17 moan(msg)
18 exit(1)
d7ab1bab 19
3aa33042 20if 'PWSAFE' in environ:
21 file = environ['PWSAFE']
22else:
23 file = '%s/.pwsafe' % environ['HOME']
d7ab1bab 24
d7ab1bab 25def cmd_create(av):
26 cipher = 'blowfish-cbc'
27 hash = 'rmd160'
28 mac = None
29 try:
30 opts, args = getopt(av, 'c:h:m:', ['cipher=', 'mac=', 'hash='])
31 except GetoptError:
32 return 1
33 for o, a in opts:
34 if o in ('-c', '--cipher'):
35 cipher = a
36 elif o in ('-m', '--mac'):
37 mac = a
38 elif o in ('-h', '--hash'):
39 hash = a
40 else:
41 raise 'Barf!'
42 if len(args) > 2:
43 return 1
44 if len(args) >= 1:
45 tag = args[0]
46 else:
47 tag = 'pwsafe'
43c09851 48 db = G.open(file, 'n', 0600)
d7ab1bab 49 pp = C.ppread(tag, C.PMODE_VERIFY)
50 if not mac: mac = hash + '-hmac'
51 c = C.gcciphers[cipher]
52 h = C.gchashes[hash]
53 m = C.gcmacs[mac]
43c09851 54 ppk = PW.PPK(pp, c, h, m)
d7ab1bab 55 ck = C.rand.block(c.keysz.default)
56 mk = C.rand.block(m.keysz.default)
57 k = Crypto(c, h, m, ck, mk)
58 db['tag'] = tag
59 db['salt'] = ppk.salt
60 db['cipher'] = cipher
61 db['hash'] = hash
62 db['mac'] = mac
63 db['key'] = ppk.encrypt(wrapstr(ck) + wrapstr(mk))
64 db['magic'] = k.encrypt(C.rand.block(h.hashsz))
65
43c09851 66def chomp(pp):
67 if len(pp) > 0 and pp[-1] == '\n':
68 pp = pp[:-1]
69 return pp
70
d7ab1bab 71def cmd_changepp(av):
72 if len(av) != 0:
73 return 1
74 pw = PW(file, 'w')
75 pw.changepp()
76
77def cmd_find(av):
78 if len(av) != 1:
79 return 1
80 pw = PW(file)
2e6a3fda 81 try:
82 print pw[av[0]]
83 except KeyError, exc:
84 die('Password `%s\' not found.' % exc.args[0])
d7ab1bab 85
86def cmd_store(av):
87 if len(av) < 1 or len(av) > 2:
88 return 1
89 tag = av[0]
90 if len(av) < 2:
91 pp = C.getpass("Enter passphrase `%s': " % tag)
92 vpp = C.getpass("Confirm passphrase `%s': " % tag)
93 if pp != vpp:
94 raise ValueError, "passphrases don't match"
95 elif av[1] == '-':
96 pp = stdin.readline()
97 else:
98 pp = av[1]
99 pw = PW(file, 'w')
43c09851 100 pw[av[0]] = chomp(pp)
d7ab1bab 101
102def cmd_copy(av):
103 if len(av) < 1 or len(av) > 2:
104 return 1
105 pw_in = PW(file)
106 pw_out = PW(av[0], 'w')
107 if len(av) >= 3:
108 pat = av[1]
109 else:
110 pat = None
111 for k in pw_in:
112 if pat is None or fnmatch(k, pat):
113 pw_out[k] = pw_in[k]
114
115def cmd_list(av):
116 if len(av) > 1:
117 return 1
118 pw = PW(file)
119 if len(av) >= 1:
120 pat = av[0]
121 else:
122 pat = None
123 for k in pw:
124 if pat is None or fnmatch(k, pat):
125 print k
126
127def cmd_topixie(av):
43c09851 128 if len(av) > 2:
d7ab1bab 129 return 1
130 pw = PW(file)
43c09851 131 pix = C.Pixie()
132 if len(av) == 0:
133 for tag in pw:
134 pix.set(tag, pw[tag])
d7ab1bab 135 else:
43c09851 136 tag = av[0]
137 if len(av) >= 2:
138 pptag = av[1]
139 else:
140 pptag = av[0]
141 pix.set(pptag, pw[tag])
d7ab1bab 142
3aa33042 143def cmd_del(av):
144 if len(av) != 1:
145 return 1
146 pw = PW(file, 'w')
147 tag = av[0]
2e6a3fda 148 try:
149 del pw[tag]
150 except KeyError, exc:
151 die('Password `%s\' not found.' % exc.args[0])
3aa33042 152
d7ab1bab 153def asciip(s):
154 for ch in s:
155 if ch < ' ' or ch > '~': return False
156 return True
157def present(s):
158 if asciip(s): return s
159 return C.ByteString(s)
160def cmd_dump(av):
161 db = gdbm.open(file, 'r')
162 k = db.firstkey()
163 while True:
164 if k is None: break
165 print '%r: %r' % (present(k), present(db[k]))
166 k = db.nextkey(k)
167
168commands = { 'create': [cmd_create,
169 '[-c CIPHER] [-h HASH] [-m MAC] [PP-TAG]'],
170 'find' : [cmd_find, 'LABEL'],
171 'store' : [cmd_store, 'LABEL [VALUE]'],
172 'list' : [cmd_list, '[GLOB-PATTERN]'],
173 'changepp' : [cmd_changepp, ''],
174 'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'],
43c09851 175 'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'],
3aa33042 176 'delete' : [cmd_del, 'TAG'],
d7ab1bab 177 'dump' : [cmd_dump, '']}
178
179def version():
2e6a3fda 180 print '%s 1.0.0' % prog
d7ab1bab 181def usage(fp):
2e6a3fda 182 print >>fp, 'Usage: %s COMMAND [ARGS...]' % prog
d7ab1bab 183def help():
184 version()
185 print
186 usage(stdout)
187 print '''
188Maintains passwords or other short secrets securely.
189
190Options:
191
192-h, --help Show this help text.
193-v, --version Show program version number.
194-u, --usage Show short usage message.
195
2e6a3fda 196-f, --file=FILE Where to find the password-safe file.
197
d7ab1bab 198Commands provided:
199'''
200 for c in commands:
201 print '%s %s' % (c, commands[c][1])
202
203try:
204 opts, argv = getopt(argv[1:],
205 'hvuf:',
206 ['help', 'version', 'usage', 'file='])
207except GetoptError:
208 usage(stderr)
209 exit(1)
210for o, a in opts:
211 if o in ('-h', '--help'):
212 help()
213 exit(0)
214 elif o in ('-v', '--version'):
215 version()
216 exit(0)
217 elif o in ('-u', '--usage'):
218 usage(stdout)
219 exit(0)
220 elif o in ('-f', '--file'):
221 file = a
222 else:
223 raise 'Barf!'
224if len(argv) < 1:
225 usage(stderr)
226 exit(1)
227
228if argv[0] in commands:
229 c = argv[0]
230 argv = argv[1:]
231else:
232 c = 'find'
233if commands[c][0](argv):
2e6a3fda 234 print >>stderr, 'Usage: %s %s %s' % (prog, c, commands[c][1])
d7ab1bab 235 exit(1)