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