Commit | Line | Data |
---|---|---|
24b3d57b | 1 | #! /usr/bin/python |
3aa33042 | 2 | # -*-python-*- |
d7ab1bab | 3 | |
4 | import catacomb as C | |
43c09851 | 5 | from catacomb.pwsafe import * |
6 | import gdbm as G | |
7 | from os import environ | |
d7ab1bab | 8 | from sys import argv, exit, stdin, stdout, stderr |
9 | from getopt import getopt, GetoptError | |
d7ab1bab | 10 | from fnmatch import fnmatch |
80dc9c94 | 11 | import re |
2e6a3fda | 12 | |
13 | prog = re.sub(r'^.*[/\\]', '', argv[0]) | |
14 | def moan(msg): | |
15 | print >>stderr, '%s: %s' % (prog, msg) | |
16 | def die(msg): | |
17 | moan(msg) | |
18 | exit(1) | |
d7ab1bab | 19 | |
3aa33042 | 20 | if 'PWSAFE' in environ: |
21 | file = environ['PWSAFE'] | |
22 | else: | |
23 | file = '%s/.pwsafe' % environ['HOME'] | |
d7ab1bab | 24 | |
d7ab1bab | 25 | def 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 | 66 | def chomp(pp): |
67 | if len(pp) > 0 and pp[-1] == '\n': | |
68 | pp = pp[:-1] | |
69 | return pp | |
70 | ||
d7ab1bab | 71 | def cmd_changepp(av): |
72 | if len(av) != 0: | |
73 | return 1 | |
74 | pw = PW(file, 'w') | |
75 | pw.changepp() | |
76 | ||
77 | def 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 | |
86 | def 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 | |
102 | def 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 | ||
115 | def 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 | ||
127 | def 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 | 143 | def 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 | 153 | def asciip(s): |
154 | for ch in s: | |
155 | if ch < ' ' or ch > '~': return False | |
156 | return True | |
157 | def present(s): | |
158 | if asciip(s): return s | |
159 | return C.ByteString(s) | |
160 | def 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 | ||
168 | commands = { 'create': [cmd_create, | |
b2687a0a MW |
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]'], | |
175 | 'to-pixie' : [cmd_topixie, '[TAG [PIXIE-TAG]]'], | |
176 | 'delete' : [cmd_del, 'TAG'], | |
177 | 'dump' : [cmd_dump, '']} | |
d7ab1bab | 178 | |
179 | def version(): | |
2e6a3fda | 180 | print '%s 1.0.0' % prog |
d7ab1bab | 181 | def usage(fp): |
2e6a3fda | 182 | print >>fp, 'Usage: %s COMMAND [ARGS...]' % prog |
d7ab1bab | 183 | def help(): |
184 | version() | |
185 | ||
b2687a0a | 186 | usage(stdout) |
d7ab1bab | 187 | print ''' |
188 | Maintains passwords or other short secrets securely. | |
189 | ||
190 | Options: | |
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 | 198 | Commands provided: |
199 | ''' | |
200 | for c in commands: | |
201 | print '%s %s' % (c, commands[c][1]) | |
202 | ||
203 | try: | |
204 | opts, argv = getopt(argv[1:], | |
b2687a0a MW |
205 | 'hvuf:', |
206 | ['help', 'version', 'usage', 'file=']) | |
d7ab1bab | 207 | except GetoptError: |
208 | usage(stderr) | |
209 | exit(1) | |
210 | for 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!' | |
224 | if len(argv) < 1: | |
225 | usage(stderr) | |
226 | exit(1) | |
227 | ||
228 | if argv[0] in commands: | |
229 | c = argv[0] | |
230 | argv = argv[1:] | |
231 | else: | |
232 | c = 'find' | |
233 | if commands[c][0](argv): | |
2e6a3fda | 234 | print >>stderr, 'Usage: %s %s %s' % (prog, c, commands[c][1]) |
d7ab1bab | 235 | exit(1) |